lltsv-0.7.0/0000755000175000001760000000000013753150070012216 5ustar kenhysdockerlltsv-0.7.0/CHANGELOG.md0000644000175000001760000000330113753150070014024 0ustar kenhysdocker## 0.7.0 (2020/11/12) Enhancements: * Use mattn-isatty instead of andrew-d/go-termutil #28 * Introduce Go Modules #27 * Use https instead of http in comment. #24 * Add tests. #23 ## 0.6.1 (2017/07/14) Fixes: * Show compiler version by -v. refs: #19 * feature: introduced --ignore-key. refs #20 ## 0.6.0 (2017/04/11) Fixes: * sort keys by default. refs: #16 * build: fixed `go get` error for ghr. refs #15 ## 0.5.6 (2017/03/21) Fixes: * feature: introduced not-equal operators for string. * expr: re-implemented by go package. ## 0.5.5 (2016/11/24) Fixes: * filter: exited when invalid filter expression was given (thanks to @cubicdaiya) ## 0.5.4 (2016/10/31) Fixes: * Typo fixed ## 0.5.3 (2016/10/31) Enhancements: * Introduce case-insentive comparing operators such as `==*`, `=~*`, `!~*` (thanks to @cubicdaiya) ## 0.5.2 (2016/10/28) Enhancements: * Enable perfect string match by '==' (thanks to @cubicdaiya) Changes: * Replace codegangsta/cli with urfave/cli (thanks to @b4b4r07) ## 0.5.1 (2016/08/25) Enhancements: * Fix typo ## 0.5.0 (2016/08/24) Enhancements: * Add new option -expr, -e (thanks to @cubicdaiya) ## 0.4.1 (2015/06/13) Enhancements: * filter: fixed panic error when the invalid filter expression is given. (thanks to @cubicdaiya) ## 0.4.0 (2015/10/20) Enhancements: * Add `filter` option (thanks to @hirose31) ## 0.3.1 (2014/11/21) Enhancements: * Add line feed to error messages ## 0.3.0 (2014/11/21) Enhancements: * Read from multiple files ## 0.2.0 (2014/08/13) Enhancements: * Read from a file (not only from STDIN) Changes: * Print all fields if -k option is not specified * Print fields in specified keys order ## 0.1.0 (2014/08/13) * Initial Release lltsv-0.7.0/README.md0000644000175000001760000000766013753150070013506 0ustar kenhysdocker# lltsv List specified keys of LTSV (Labeled Tab Separated Values) # Description `lltsv` is a command line tool written in golang to list specified keys of LTSV (Labeled Tab Separated Values) text. Example 1: ```bash $ echo "foo:aaa\tbar:bbb\tbaz:ccc" | lltsv -k foo,bar foo:aaa bar:bbb ``` The output is colorized as default when you outputs to a terminal. The coloring is disabled if you pipe or redirect outputs. Example 2: ```bash $ echo "foo:aaa\tbar:bbb\tbaz:ccc" | lltsv -k foo,bar -K aaa bbb ``` Eliminate labels with `-K` option. Example 3: ```bash $ lltsv -k foo,bar -K file*.log ``` Specify input files as arguments. Example4: ```bash $ lltsv -k resptime,status,uri -f 'resptime > 6' access_log $ lltsv -k resptime,status,uri -f 'resptime > 6' -f 'uri =~ ^/foo' access_log ``` Filter output with "-f" option. Available comparing operators are: ``` >= > == < <= (arithmetic (float64)) == ==* != !=* (string comparison (string)) =~ !~ =~* !~* (regular expression (string)) ``` The comparing operators terminated by __*__ behave in case-insensitive. You can specify multiple -f options (AND condition). Example5: ```bash $ lltsv -k resptime,upstream_resptime,diff -e 'diff = resptime - upstream_resptime' access_log $ lltsv -k resptime,upstream_resptime,diff_ms -e 'diff_ms = (resptime - upstream_resptime) * 1000' access_log ``` Evaluate value with "-e" option. Available operators are: ``` + - * / (arithmetic (float64)) ``` **How Useful?** LTSV format is not `awk` friendly (I think), but `lltsv` can help it: ```bash $ echo -e "time:2014-08-13T14:10:10Z\tstatus:200\ntime:2014-08-13T14:10:12Z\tstatus:500" \ | lltsv -k time,status -K | awk '$2 == 500' 2014-08-13T14:10:12Z 500 ``` Useful! ## Installation Executable binaries are available at [releases](https://github.com/sonots/lltsv/releases). For example, for linux x86_64, ```bash $ wget https://github.com/sonots/lltsv/releases/download/v0.3.0/lltsv_linux_amd64 -O lltsv $ chmod a+x lltsv ``` If you have the go runtime installed, you may use go get. ```bash $ go get github.com/sonots/lltsv ``` ## Usage ``` $ lltsv -h NAME: lltsv - List specified keys of LTSV (Labeled Tab Separated Values) USAGE: lltsv [global options] command [command options] [arguments...] VERSION: 0.5.1 AUTHOR(S): sonots COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --key, -k keys to output (multiple keys separated by ,) --no-key, -K output without keys (and without color) --ignore-key value, -i value ignored keys to output (multiple keys separated by ,) --filter, -f [--filter option --filter option] filter expression to output --expr, -e [--expr option --expr option] evaluate value by expression to output --help, -h show help --version, -v print the version ``` ## ToDo 1. write tests ## Build To build, use go get and make ``` $ go get -d github.com/sonots/lltsv $ cd $GOPATH/src/github.com/sonots/lltsv $ make ``` To release binaries, I use [gox](https://github.com/mitchellh/gox) and [ghr](https://github.com/tcnksm/ghr) ``` go get github.com/mitchellh/gox gox -build-toolchain # only first time go get github.com/tcnksm/ghr mkdir -p pkg && cd pkg && gox ../... ghr vX.X.X . ``` ## Contribution 1. Fork (https://github.com/sonots/lltsv/fork) 2. Create a feature branch 3. Commit your changes 4. Rebase your local changes against the master branch 5. Run test suite with the go test ./... command and confirm that it passes 6. Run gofmt -s 7. Create new Pull Request ## Copyright See [LICENSE](./LICENSE) ## Special Thanks This is a golang fork of perl version created by [id:key_amb](http://keyamb.hatenablog.com/). Thanks! MEMO: golang version was 5x faster than perl version lltsv-0.7.0/eval.go0000644000175000001760000000321613753150070013476 0ustar kenhysdockerpackage main import ( "errors" "go/ast" "go/constant" "go/parser" "strconv" ) // Vars is a map for LTSV's label and value. type Vars map[string]string // ExprRunner is an expression runner for Go code. type ExprRunner struct { expr ast.Expr } // ExprContext is a context for ExprRunner. type ExprContext struct { vars Vars } func parseExpr(e string) (ast.Expr, error) { expr, err := parser.ParseExpr(e) if err != nil { return nil, err } return expr, nil } func evalExpr(expr ast.Expr, ctx *ExprContext) (constant.Value, error) { switch e := expr.(type) { case *ast.BasicLit: return evalBasicLit(e, ctx) case *ast.BinaryExpr: return evalBinaryExpr(e, ctx) case *ast.Ident: return evalIdent(e, ctx) case *ast.ParenExpr: return evalExpr(e.X, ctx) } return constant.MakeUnknown(), errors.New("unknown expr") } func evalBasicLit(expr *ast.BasicLit, ctx *ExprContext) (constant.Value, error) { return constant.MakeFromLiteral(expr.Value, expr.Kind, 0), nil } func evalBinaryExpr(expr *ast.BinaryExpr, ctx *ExprContext) (constant.Value, error) { x, err := evalExpr(expr.X, ctx) if err != nil { return constant.MakeUnknown(), err } y, err := evalExpr(expr.Y, ctx) if err != nil { return constant.MakeUnknown(), err } return constant.BinaryOp(x, expr.Op, y), nil } func evalIdent(expr *ast.Ident, ctx *ExprContext) (constant.Value, error) { name, ok := ctx.vars[expr.Name] if !ok { return constant.MakeUnknown(), errors.New("unknown variable name") } n, err := strconv.ParseFloat(name, 64) if err != nil { return constant.MakeUnknown(), errors.New("variable type must be numeric") } return constant.MakeFloat64(n), nil } lltsv-0.7.0/go.sum0000644000175000001760000000566713753150070013367 0ustar kenhysdockergithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= lltsv-0.7.0/LICENSE0000644000175000001760000000205513753150070013225 0ustar kenhysdockerCopyright (c) 2014 Naotoshi Seo MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. lltsv-0.7.0/eval_test.go0000644000175000001760000000160413753150070014534 0ustar kenhysdockerpackage main import ( "testing" "github.com/stretchr/testify/assert" ) func TestEval(t *testing.T) { assert := assert.New(t) tests := []struct { expression string vars Vars result string }{ { // no space expression: "1+1", vars: nil, result: "2", }, { expression: "1 + 1", vars: nil, result: "2", }, { expression: "resptime - upstream_resptime", vars: Vars{"resptime": "5", "upstream_resptime": "3"}, result: "2", }, { expression: "(resptime - upstream_resptime) * 1000", vars: Vars{"resptime": "5", "upstream_resptime": "3"}, result: "2000", }, } for _, test := range tests { expr, err := parseExpr(test.expression) assert.Nil(err) ctx := &ExprContext{ vars: test.vars, } v, err := evalExpr(expr, ctx) assert.Nil(err) assert.Equal(test.result, v.String()) } } lltsv-0.7.0/.gitignore0000644000175000001760000000001413753150070014201 0ustar kenhysdockerpkg/* lltsv lltsv-0.7.0/go.mod0000644000175000001760000000041013753150070013317 0ustar kenhysdockermodule github.com/sonots/lltsv go 1.13 require ( github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-isatty v0.0.12 github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b github.com/stretchr/testify v1.4.0 github.com/urfave/cli v1.22.2 ) lltsv-0.7.0/main.go0000644000175000001760000000675413753150070013505 0ustar kenhysdockerpackage main import ( "fmt" "os" "runtime" "strings" "github.com/urfave/cli" ) // os.Exit forcely kills process, so let me share this global variable to terminate at the last var exitCode = 0 func main() { app := cli.NewApp() app.Name = "lltsv" app.Version = Version app.Usage = `List specified keys of LTSV (Labeled Tab Separated Values) Example1 $ echo "foo:aaa\tbar:bbb" | lltsv -k foo,bar foo:aaa bar:bbb The output is colorized as default when you outputs to a terminal. The coloring is disabled if you pipe or redirect outputs. Example2 $ echo "foo:aaa\tbar:bbb" | lltsv -k foo,bar -K aaa bbb Eliminate labels with "-K" option. Example3 $ lltsv -k foo,bar -K file*.log Specify input files as arguments. Example4 $ lltsv -k resptime,status,uri -f 'resptime > 6' access_log $ lltsv -k resptime,status,uri -f 'resptime > 6' -f 'uri =~ ^/foo' access_log Filter output with "-f" option. Available comparing operators are: >= > == < <= (arithmetic (float64)) == ==* != !=* (string comparison (string)) =~ !~ =~* !~* (regular expression (string)) The comparing operators terminated by * behave in case-insensitive. You can specify multiple -f options (AND condition). Example5 $ lltsv -k resptime,upstream_resptime,diff -f 'diff = resptime - upstream_resptime' access_log $ lltsv -k resptime,upstream_resptime,diff_ms -e 'diff_ms = (resptime - upstream_resptime) * 1000' access_log Evaluate value with "-e" option. Available operators are: + - * / (arithmetic (float64)) Homepage: https://github.com/sonots/lltsv` app.Author = "sonots" app.Email = "sonots@gmail.com" app.Flags = []cli.Flag{ cli.StringFlag{ Name: "key, k", Usage: "keys to output (multiple keys separated by ,)", }, cli.BoolFlag{ Name: "no-key, K", Usage: "output without keys (and without color)", }, cli.StringFlag{ Name: "ignore-key, i", Usage: "ignored keys to output (multiple keys separated by ,)", }, cli.StringSliceFlag{ Name: "filter, f", Usage: "filter expression to output", }, cli.StringSliceFlag{ Name: "expr, e", Usage: "evaluate value by expression to output", }, } app.Action = doMain cli.VersionPrinter = func(c *cli.Context) { fmt.Fprintf(app.Writer, `%v version %v Compiler: %s %s `, app.Name, app.Version, runtime.Compiler, runtime.Version()) } app.Run(os.Args) os.Exit(exitCode) } func doMain(c *cli.Context) error { keys := make([]string, 0, 0) // slice with length 0 if c.String("key") != "" { keys = strings.Split(c.String("key"), ",") } noKey := c.Bool("no-key") filters := c.StringSlice("filter") exprs := c.StringSlice("expr") ignoreKeys := make([]string, 0, 0) // If -k,--key is specified, -i,--ignore-key is ignored. if len(keys) == 0 && c.String("ignore-key") != "" { ignoreKeys = strings.Split(c.String("ignore-key"), ",") } lltsv := newLltsv(keys, ignoreKeys, noKey, filters, exprs) if len(c.Args()) > 0 { for _, filename := range c.Args() { file, err := os.Open(filename) if err != nil { os.Stderr.WriteString("failed to open and read `" + filename + "`.\n") exitCode = 1 return err } err = lltsv.scanAndWrite(file) file.Close() if err != nil { os.Stderr.WriteString("reading input errored\n") exitCode = 1 return err } } } else { file := os.Stdin err := lltsv.scanAndWrite(file) file.Close() if err != nil { os.Stderr.WriteString("reading input errored\n") exitCode = 1 return err } } return nil } lltsv-0.7.0/lltsv_test.go0000644000175000001760000000333513753150070014754 0ustar kenhysdockerpackage main import ( "testing" "github.com/stretchr/testify/assert" ) func TestFilter(t *testing.T) { assert := assert.New(t) tests := []struct { filter string lvs map[string]string result bool }{ // arithmetic comparison { filter: "resptime > 6", lvs: map[string]string{"resptime": "10"}, result: true, }, { filter: "resptime >= 6", lvs: map[string]string{"resptime": "6"}, result: true, }, { filter: "resptime == 60", lvs: map[string]string{"resptime": "60"}, result: true, }, { filter: "resptime < 6", lvs: map[string]string{"resptime": "7"}, result: false, }, { filter: "resptime <= 6", lvs: map[string]string{"resptime": "7"}, result: false, }, // string comparison { filter: "uri == /top", lvs: map[string]string{"uri": "/top"}, result: true, }, { filter: "uri ==* /TOP", lvs: map[string]string{"uri": "/top"}, result: true, }, { filter: "uri != /top", lvs: map[string]string{"uri": "/bottom"}, result: true, }, { filter: "uri !=* /top", lvs: map[string]string{"uri": "/TOP"}, result: false, }, // regular expression { filter: "uri =~ ^/", lvs: map[string]string{"uri": "/top"}, result: true, }, { filter: "uri !~ ^/", lvs: map[string]string{"uri": "/top"}, result: false, }, { filter: "uri =~* ^/", lvs: map[string]string{"uri": "/TOP"}, result: true, }, { filter: "uri !~* /top", lvs: map[string]string{"uri": "/TOP"}, result: false, }, } for _, test := range tests { filters := getFuncFilters([]string{test.filter}) for k, filter := range filters { assert.Equal(test.result, filter(test.lvs[k])) } } } lltsv-0.7.0/version.go0000644000175000001760000000012013753150070014223 0ustar kenhysdockerpackage main // Version is lltsv version string const Version string = "0.7.0" lltsv-0.7.0/Makefile0000644000175000001760000000051113753150070013653 0ustar kenhysdockerDEBUG_FLAG = $(if $(DEBUG),-debug) build: GO111MODULE=on go build test: GO111MODULE=on go test -v ./... install: GO111MODULE=on go install fmt: GO111MODULE=on go fmt ./... lint: golint . pkg: go get github.com/mitchellh/gox/... go get github.com/tcnksm/ghr mkdir -p pkg && cd pkg && gox ../... clean: rm -f lltsv lltsv-0.7.0/lltsv.go0000644000175000001760000001274113753150070013716 0ustar kenhysdockerpackage main import ( "bufio" "log" "os" "regexp" "strconv" "strings" "github.com/mattn/go-isatty" "github.com/mgutz/ansi" ) type tFuncAppend func([]string, string, string) []string type tFuncFilter func(string) bool // Lltsv is a context for processing LTSV. type Lltsv struct { keys []string ignoreKeyMap map[string]struct{} noKey bool filters []string exprs []string funcAppend tFuncAppend funcFilters map[string]tFuncFilter exprRunners map[string]*ExprRunner } func newLltsv(keys []string, ignoreKeys []string, noKey bool, filters []string, exprs []string) *Lltsv { ignoreKeyMap := make(map[string]struct{}) for _, key := range ignoreKeys { ignoreKeyMap[key] = struct{}{} } return &Lltsv{ keys: keys, ignoreKeyMap: ignoreKeyMap, noKey: noKey, filters: filters, exprs: exprs, funcAppend: getFuncAppend(noKey), funcFilters: getFuncFilters(filters), exprRunners: getExprRunners(exprs), } } func (lltsv *Lltsv) scanAndWrite(file *os.File) error { scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() lvs, keys := lltsv.parseLtsv(line) lltsv.expr(lvs) if lltsv.filter(lvs) { ltsv := lltsv.restructLtsv(lvs, keys) os.Stdout.WriteString(ltsv + "\n") } } return scanner.Err() } func (lltsv *Lltsv) filter(lvs map[string]string) bool { shouldOutput := true for key, funcFilter := range lltsv.funcFilters { if !funcFilter(lvs[key]) { shouldOutput = false break } } return shouldOutput } func (lltsv *Lltsv) expr(lvs map[string]string) { ctx := &ExprContext{ vars: lvs, } for key, runner := range lltsv.exprRunners { v, err := evalExpr(runner.expr, ctx) if err == nil { lvs[key] = v.String() } } } // lvs: label and value pairs func (lltsv *Lltsv) restructLtsv(lvs map[string]string, keys []string) string { // specified keys or all keys orders := lltsv.keys if len(lltsv.keys) == 0 { orders = keys } // make slice with enough capacity so that append does not newly create object // cf. https://golang.org/pkg/builtin/#append selected := make([]string, 0, len(orders)) for _, label := range orders { if _, ok := lltsv.ignoreKeyMap[label]; ok { continue } value := lvs[label] selected = lltsv.funcAppend(selected, label, value) } return strings.Join(selected, "\t") } func (lltsv *Lltsv) parseLtsv(line string) (map[string]string, []string) { columns := strings.Split(line, "\t") lvs := make(map[string]string) keys := make([]string, 0, len(columns)) for _, column := range columns { lv := strings.SplitN(column, ":", 2) if len(lv) < 2 { continue } label, value := lv[0], lv[1] lvs[label] = value keys = append(keys, label) } return lvs, keys } // Return function pointer to avoid `if` evaluation occurs in each iteration func getFuncAppend(noKey bool) tFuncAppend { if noKey { return func(selected []string, label string, value string) []string { return append(selected, value) } } if isatty.IsTerminal(os.Stdout.Fd()) { return func(selected []string, label string, value string) []string { return append(selected, ansi.Color(label, "green")+":"+ansi.Color(value, "magenta")) } } // if pipe or redirect return func(selected []string, label string, value string) []string { return append(selected, label+":"+value) } } func getFuncFilters(filters []string) map[string]tFuncFilter { funcFilters := map[string]tFuncFilter{} for _, f := range filters { token := strings.SplitN(f, " ", 3) if len(token) < 3 { log.Fatalf("filter expression is invalid: %s\n", f) } key := token[0] switch token[1] { case ">", ">=", "<=", "<": r, err := strconv.ParseFloat(token[2], 64) if err != nil { log.Fatal(err) } funcFilters[key] = func(val string) bool { num, err := strconv.ParseFloat(val, 64) if err != nil { log.Println(err) return false } switch token[1] { case ">": return num > r case ">=": return num >= r case "<=": return num <= r case "<": return num < r default: return false } } case "==": funcFilters[key] = func(val string) bool { return val == token[2] } case "==*": funcFilters[key] = func(val string) bool { return strings.ToLower(val) == strings.ToLower(token[2]) } case "!=": funcFilters[key] = func(val string) bool { return val != token[2] } case "!=*": funcFilters[key] = func(val string) bool { return strings.ToLower(val) != strings.ToLower(token[2]) } case "=~", "!~", "=~*", "!~*": if token[1] == "=~*" || token[1] == "!~*" { token[2] = strings.ToLower(token[2]) } re := regexp.MustCompile(token[2]) funcFilters[key] = func(val string) bool { switch token[1] { case "=~": return re.MatchString(val) case "!~": return !re.MatchString(val) case "=~*": return re.MatchString(strings.ToLower(val)) case "!~*": return !re.MatchString(strings.ToLower(val)) default: return false } } } } return funcFilters } func getExprRunners(exprs []string) map[string]*ExprRunner { funcExprs := make(map[string]*ExprRunner, len(exprs)) for _, f := range exprs { token := strings.SplitN(f, "=", 2) if len(token) != 2 { log.Printf("expression is invalid: %s\n", f) continue } expr, err := parseExpr(token[1]) if err != nil { log.Printf("expression is invalid: %s\n", f) continue } key := strings.Trim(token[0], " ") funcExprs[key] = &ExprRunner{ expr: expr, } } return funcExprs }