From b3ce580c25e62da64475574ee5cfe8b1a3bd9871 Mon Sep 17 00:00:00 2001 From: Bryan Matsuo Date: Mon, 30 Jun 2014 19:12:29 -0700 Subject: [PATCH] parse older jq versions BUG: the format of version number is not checked against the version number --- jq.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++-------- jq_test.go | 33 +++++++++++++++++++ 2 files changed, 117 insertions(+), 13 deletions(-) diff --git a/jq.go b/jq.go index 1526119..8df9c95 100644 --- a/jq.go +++ b/jq.go @@ -6,6 +6,7 @@ import ( "io" "log" "os/exec" + "strconv" "strings" "sync/atomic" "unicode" @@ -16,7 +17,10 @@ import ( var ErrJQNotFound = fmt.Errorf("jq executable not found") -var jqVersionPrefixBytes = []byte("jq-") +var jqVersionPrefixes = [][]byte{ + []byte("jq-"), + []byte("jq version "), +} func LocateJQ(path string) (string, error) { if path == "" { @@ -33,7 +37,14 @@ func LocateJQ(path string) (string, error) { if err != nil { return "", err } - if !bytes.HasPrefix(bs, jqVersionPrefixBytes) { + var ok bool + for _, prefix := range jqVersionPrefixes { + if bytes.HasPrefix(bs, prefix) { + ok = true + + } + } + if !ok { return "", fmt.Errorf("executable doesn't look like jq") } return path, nil @@ -51,31 +62,65 @@ func CheckJQVersion(path string) (string, error) { if err != nil { return "", err } - lex := lexer.New(scanJQVersion, string(bs)) + vstr, _, _, _, err := ParseJQVersion(string(bs)) + if err != nil { + return "", err + } + return vstr, nil +} + +const ( + jqVersionMajor lexer.ItemType = iota + jqVersionMinor + jqVersionSuffix +) + +func ParseJQVersion(vstr string) (s string, major, minor int, suffix string, err error) { + vstr = strings.TrimFunc(vstr, unicode.IsSpace) // BUG this breaks error position information (currently unused) + lex := lexer.New(scanJQVersion, vstr) var items []*lexer.Item for { item := lex.Next() if item.Type == lexer.ItemError { - return "", fmt.Errorf("%s", item.Value) + return "", 0, 0, "", fmt.Errorf("%s", item.Value) } if item.Type == lexer.ItemEOF { break } items = append(items, item) } + s = vstr if len(items) < 2 { panic("expect at least two tokens") } - vstr := string(bytes.TrimFunc(bs, unicode.IsSpace)) - return vstr, nil + if items[0].Type != jqVersionMajor { + err = fmt.Errorf("unexpected token %v", items[0]) + return + } + major, err = strconv.Atoi(items[0].String()) + if err != nil { + err = fmt.Errorf("invalid major version: %v", err) + return + } + if items[1].Type != jqVersionMinor { + err = fmt.Errorf("unexpected token %v", items[0]) + return + } + minor, err = strconv.Atoi(items[1].String()) + if err != nil { + err = fmt.Errorf("invalid minor version: %v", err) + return + } + if len(items) > 2 { + if items[2].Type != jqVersionSuffix { + err = fmt.Errorf("unexpected token: %q (%d)", items[2], items[2].Type) + return + } + suffix = items[2].String() + } + return } -const ( - jqVersionMajor lexer.ItemType = iota - jqVersionMinor - jqVersionSuffix -) - func scanJQVersion(lex *lexer.Lexer) lexer.StateFn { // prefix "jq-" if !lex.Accept("j") { @@ -85,7 +130,33 @@ func scanJQVersion(lex *lexer.Lexer) lexer.StateFn { return lex.Errorf("not a jq version") } if !lex.Accept("-") { - return lex.Errorf("not a jq version") + if !lex.Accept(" ") { + return lex.Errorf("not a jq version") + } + if !lex.Accept("v") { + return lex.Errorf("not a jq version") + } + if !lex.Accept("e") { + return lex.Errorf("not a jq version") + } + if !lex.Accept("r") { + return lex.Errorf("not a jq version") + } + if !lex.Accept("s") { + return lex.Errorf("not a jq version") + } + if !lex.Accept("i") { + return lex.Errorf("not a jq version") + } + if !lex.Accept("o") { + return lex.Errorf("not a jq version") + } + if !lex.Accept("n") { + return lex.Errorf("not a jq version") + } + if !lex.Accept(" ") { + return lex.Errorf("not a jq version") + } } lex.Ignore() diff --git a/jq_test.go b/jq_test.go index 85dff9f..808d0a2 100644 --- a/jq_test.go +++ b/jq_test.go @@ -75,6 +75,39 @@ func TestCheckJQVersion(t *testing.T) { } } +func TestParseJQVersion(t *testing.T) { + for _, test := range []struct { + in string + s string + maj, min int + suf string + err bool + }{ + {"jq-1.4\n", "jq-1.4", 1, 4, "", false}, + {"jq version 1.3\n", "jq version 1.3", 1, 3, "", false}, + } { + s, maj, min, suf, err := ParseJQVersion(test.in) + if s != test.s { + t.Errorf("%q unexpected version string: %q", test.in, s) + } + if maj != test.maj { + t.Errorf("%q unexpected major version: %q", test.in, maj) + } + if min != test.min { + t.Errorf("%q unexpected minor version: %q", test.in, min) + } + if suf != test.suf { + t.Errorf("%q unexpected version suffix: %q", test.in, suf) + } + if !test.err && err != nil { + t.Errorf("%q unexpected error: %v", test.in, err) + } + if test.err && err == nil { + t.Errorf("%q expected an error", test.in, err) + } + } +} + func TestJoinFilter(t *testing.T) { filter := JoinFilter(MockFilter{"hello", "world"}) if filter != "hello | world" {