diff --git a/query/lex.go b/query/lex.go index dc95d8d9..c02623fb 100644 --- a/query/lex.go +++ b/query/lex.go @@ -28,6 +28,8 @@ const ( itemEq // = itemPatMatch // % itemRegexp // ~ + itemLeftCurly // { + itemRightCurly // } itemString ) @@ -152,6 +154,10 @@ func lexMain(l *lexer) stateFn { l.emit(itemLeftParen) case r == ')': l.emit(itemRightParen) + case r == '{': + l.emit(itemLeftCurly) + case r == '}': + l.emit(itemRightCurly) case r == '|': l.emit(itemOr) case r == ',': @@ -195,7 +201,7 @@ func lexMain(l *lexer) stateFn { func lexString(l *lexer) stateFn { for { r := l.next() - if unicode.IsSpace(r) || strings.IndexRune("()|,!<>=%~", r) > 0 { + if unicode.IsSpace(r) || strings.IndexRune("()|,!{}", r) > 0 { l.backup() l.emit(itemString) return lexMain(l) diff --git a/query/lex_test.go b/query/lex_test.go index 6a492553..4639435f 100644 --- a/query/lex_test.go +++ b/query/lex_test.go @@ -11,7 +11,7 @@ type LexerSuite struct { var _ = Suite(&LexerSuite{}) func (s *LexerSuite) TestLexing(c *C) { - _, ch := lex("query", "package (<< 1.3), $Source | !app") + _, ch := lex("query", "package (<< 1.3), $Source | !app, data {i386}") c.Check(<-ch, Equals, item{typ: itemString, val: "package"}) c.Check(<-ch, Equals, item{typ: itemLeftParen, val: "("}) @@ -23,6 +23,11 @@ func (s *LexerSuite) TestLexing(c *C) { c.Check(<-ch, Equals, item{typ: itemOr, val: "|"}) c.Check(<-ch, Equals, item{typ: itemNot, val: "!"}) c.Check(<-ch, Equals, item{typ: itemString, val: "app"}) + c.Check(<-ch, Equals, item{typ: itemAnd, val: ","}) + c.Check(<-ch, Equals, item{typ: itemString, val: "data"}) + c.Check(<-ch, Equals, item{typ: itemLeftCurly, val: "{"}) + c.Check(<-ch, Equals, item{typ: itemString, val: "i386"}) + c.Check(<-ch, Equals, item{typ: itemRightCurly, val: "}"}) c.Check(<-ch, Equals, item{typ: itemEOF, val: ""}) } diff --git a/query/query.go b/query/query.go index 03e9c4a9..cfcf4cef 100644 --- a/query/query.go +++ b/query/query.go @@ -1,6 +1,10 @@ // Package query implements query language for package query +import ( + "github.com/smira/aptly/deb" +) + /* Query language resembling Debian dependencies and reprepro @@ -10,8 +14,16 @@ package query A := B | B ',' A B := C | '!' B C := '(' Query ')' | D - D := + D := | __ field := | | $special_field condition := '(' value ')' | + arch_condition := '{' arch '}' | operator := | << | < | <= | > | >> | >= | = | % | ~ */ + +// Parse parses input package query into PackageQuery tree ready for evaluation +func Parse(query string) (result deb.PackageQuery, err error) { + l, _ := lex("", query) + result, err = parse(l) + return +} diff --git a/query/syntax.go b/query/syntax.go index 8c62eaaf..aa7f808c 100644 --- a/query/syntax.go +++ b/query/syntax.go @@ -106,7 +106,21 @@ func operatorToRelation(operator itemType) int { panic("unable to map token to relation") } -// D := +// isPackageRef returns ok true if field has format pkg_version_arch +func parsePackageRef(query string) (pkg, version, arch string, ok bool) { + i := strings.Index(query, "_") + if i != -1 { + pkg, query = query[:i], query[i+1:] + j := strings.LastIndex(query, "_") + if j != -1 { + version, arch = query[:j], query[j+1:] + ok = true + } + } + return +} + +// D := | __ // field := | | $special_field func (p *parser) D() deb.PackageQuery { if p.input.Current().typ != itemString { @@ -122,10 +136,19 @@ func (p *parser) D() deb.PackageQuery { if strings.HasPrefix(field, "$") || unicode.IsUpper(r) { // special field or regular field return &deb.FieldQuery{Field: field, Relation: operatorToRelation(operator), Value: value} + } else if operator == 0 && value == "" { + if pkg, version, arch, ok := parsePackageRef(field); ok { + // query for specific package + return &deb.PkgQuery{Pkg: pkg, Version: version, Arch: arch} + } } // regular dependency-like query - return &deb.DependencyQuery{Dep: deb.Dependency{Pkg: field, Relation: operatorToRelation(operator), Version: value}} + return &deb.DependencyQuery{Dep: deb.Dependency{ + Pkg: field, + Relation: operatorToRelation(operator), + Version: value, + Architecture: p.ArchCondition()}} } // condition := '(' value ')' | @@ -162,3 +185,24 @@ func (p *parser) Condition() (operator itemType, value string) { return } + +// arch_condition := '{' arch '}' | +func (p *parser) ArchCondition() (arch string) { + if p.input.Current().typ != itemLeftCurly { + return + } + p.input.Consume() + + if p.input.Current().typ != itemString { + panic(fmt.Sprintf("unexpected token %s: expecting architecture", p.input.Current())) + } + arch = p.input.Current().val + p.input.Consume() + + if p.input.Current().typ != itemRightCurly { + panic(fmt.Sprintf("unexpected token %s: expecting '}'", p.input.Current())) + } + p.input.Consume() + + return +} diff --git a/query/syntax_test.go b/query/syntax_test.go index 83765847..3f68192b 100644 --- a/query/syntax_test.go +++ b/query/syntax_test.go @@ -11,11 +11,11 @@ type SyntaxSuite struct { var _ = Suite(&SyntaxSuite{}) func (s *SyntaxSuite) TestParsing(c *C) { - l, _ := lex("query", "package (<< 1.3), $Source") + l, _ := lex("query", "package (<< 1.3~dev), $Source") q, err := parse(l) c.Assert(err, IsNil) - c.Check(q.(*deb.AndQuery).L, DeepEquals, &deb.DependencyQuery{Dep: deb.Dependency{Pkg: "package", Relation: deb.VersionLess, Version: "1.3"}}) + c.Check(q.(*deb.AndQuery).L, DeepEquals, &deb.DependencyQuery{Dep: deb.Dependency{Pkg: "package", Relation: deb.VersionLess, Version: "1.3~dev"}}) c.Check(q.(*deb.AndQuery).R, DeepEquals, &deb.FieldQuery{Field: "$Source"}) l, _ = lex("query", "package (1.3), Name (lala) | !$Source") @@ -39,6 +39,19 @@ func (s *SyntaxSuite) TestParsing(c *C) { c.Assert(err, IsNil) c.Check(q, DeepEquals, &deb.DependencyQuery{Dep: deb.Dependency{Pkg: "package", Relation: deb.VersionGreaterOrEqual, Version: "5.3.7"}}) + + l, _ = lex("query", "alien-data_1.3.4~dev_i386") + q, err = parse(l) + + c.Assert(err, IsNil) + c.Check(q, DeepEquals, &deb.PkgQuery{Pkg: "alien-data", Version: "1.3.4~dev", Arch: "i386"}) + + l, _ = lex("query", "package (> 5.3.7) {amd64}") + q, err = parse(l) + + c.Assert(err, IsNil) + c.Check(q, DeepEquals, &deb.DependencyQuery{ + Dep: deb.Dependency{Pkg: "package", Relation: deb.VersionGreaterOrEqual, Version: "5.3.7", Architecture: "amd64"}}) } func (s *SyntaxSuite) TestParsingErrors(c *C) { @@ -48,7 +61,7 @@ func (s *SyntaxSuite) TestParsingErrors(c *C) { l, _ = lex("query", "package>5.3.7)") _, err = parse(l) - c.Check(err, ErrorMatches, "parsing failed: unexpected token >: expecting end of query") + c.Check(err, ErrorMatches, "parsing failed: unexpected token \\): expecting end of query") l, _ = lex("query", "package | !|") _, err = parse(l)