Matt Chaput avatar Matt Chaput committed 2e8b9bf

Fixed dumb regex for NOT op where it only worked after string start or a space. Fixes issue #252.

Comments (0)

Files changed (3)

src/whoosh/qparser/plugins.py

         self.closeexpr = closeexpr
 
     def taggers(self, parser):
-        return [(FnTagger(self.openexpr, self.OpenBracket), 0),
-                (FnTagger(self.closeexpr, self.CloseBracket), 0)]
+        return [(FnTagger(self.openexpr, self.OpenBracket, "openB"), 0),
+                (FnTagger(self.closeexpr, self.CloseBracket, "closeB"), 0)]
 
     def filters(self, parser):
         return [(self.do_groups, 0)]
 
     class OpTagger(RegexTagger):
         def __init__(self, expr, grouptype, optype=syntax.InfixOperator,
-                     leftassoc=True):
+                     leftassoc=True, memo=""):
             RegexTagger.__init__(self, expr)
             self.grouptype = grouptype
             self.optype = optype
             self.leftassoc = leftassoc
+            self.memo = memo
+
+        def __repr__(self):
+            return "<%s %r (%s)>" % (self.__class__.__name__, self.expr,
+                                     self.memo)
 
         def create(self, parser, match):
             return self.optype(match.group(0), self.grouptype, self.leftassoc)
 
     def __init__(self, ops=None, clean=False, And=r"\sAND\s", Or=r"\sOR\s",
                  AndNot=r"\sANDNOT\s", AndMaybe=r"\sANDMAYBE\s",
-                 Not=r"(^|(?<= ))NOT\s", Require=r"(^|(?<= ))REQUIRE\s"):
+                 Not=r"(^|(?<=[ \r\n\t()]))NOT\s",
+                 Require=r"(^|(?<= ))REQUIRE\s"):
         if ops:
             ops = list(ops)
         else:
         if not clean:
             ot = self.OpTagger
             if Not:
-                ops.append((ot(Not, syntax.NotGroup, syntax.PrefixOperator),
-                            0))
+                ops.append((ot(Not, syntax.NotGroup, syntax.PrefixOperator,
+                               memo="not"), 0))
             if And:
-                ops.append((ot(And, syntax.AndGroup), 0))
+                ops.append((ot(And, syntax.AndGroup, memo="and"), 0))
             if Or:
-                ops.append((ot(Or, syntax.OrGroup), 0))
+                ops.append((ot(Or, syntax.OrGroup, memo="or"), 0))
             if AndNot:
-                ops.append((ot(AndNot, syntax.AndNotGroup), -5))
+                ops.append((ot(AndNot, syntax.AndNotGroup,
+                               memo="anot"), -5))
             if AndMaybe:
-                ops.append((ot(AndMaybe, syntax.AndMaybeGroup), -5))
+                ops.append((ot(AndMaybe, syntax.AndMaybeGroup,
+                               memo="amaybe"), -5))
             if Require:
-                ops.append((ot(Require, syntax.RequireGroup), 0))
+                ops.append((ot(Require, syntax.RequireGroup,
+                               memo="req"), 0))
 
         self.ops = ops
 
         self.minusexpr = minusexpr
 
     def taggers(self, parser):
-        return [(FnTagger(self.plusexpr, self.Plus), 0),
-                (FnTagger(self.minusexpr, self.Minus), 0)]
+        return [(FnTagger(self.plusexpr, self.Plus, "plus"), 0),
+                (FnTagger(self.minusexpr, self.Minus, "minus"), 0)]
 
     def filters(self, parser):
         return [(self.do_plusminus, 510)]

src/whoosh/qparser/taggers.py

     keyword arguments.
     """
 
-    def __init__(self, expr, fn):
+    def __init__(self, expr, fn, memo=""):
         RegexTagger.__init__(self, expr)
         self.fn = fn
+        self.memo = memo
+
+    def __repr__(self):
+        return "<%s %r (%s)>" % (self.__class__.__name__, self.expr, self.memo)
 
     def create(self, parser, match):
         return self.fn(**match.groupdict())

tests/test_queries.py

 from __future__ import with_statement
 
-from nose.tools import assert_equal, assert_not_equal  #@UnresolvedImport
+from nose.tools import assert_equal, assert_not_equal  # @UnresolvedImport
 
 import copy
 
-from whoosh import fields, query
+from whoosh import fields, qparser, query
 from whoosh.compat import u
 from whoosh.filedb.filestore import RamStorage
 from whoosh.qparser import QueryParser
         assert_equal(q._find_prefix(q.text), "")
 
 
+def test_not_order():
+    domain = [(1, 5, 1), (2, 4, 1), (3, 3, 1), (4, 20, None), (5, 0, 1)]
+    schema = fields.Schema(id=fields.STORED,
+                           count=fields.KEYWORD(lowercase=True),
+                           cats=fields.KEYWORD(lowercase=True))
+    ix = RamStorage().create_index(schema)
+    with ix.writer() as w:
+        for idnum, count, cats in domain:
+            count = u(str(count))
+            cats = u("1") if cats else None
+            w.add_document(id=idnum, count=count, cats=cats)
 
+    with ix.searcher() as s:
+        qp = qparser.QueryParser("count", ix.schema)
 
+        q1 = qp.parse(u("(NOT (count:0) AND cats:1)"))
+        assert_equal(q1.__class__, query.And)
+        assert_equal(q1[0].__class__, query.Not)
+        assert_equal(q1[1].__class__, query.Term)
+        assert_equal(q1.__unicode__(), '(NOT count:0 AND cats:1)')
 
+        q2 = qp.parse(u("(cats:1 AND NOT (count:0))"))
+        assert_equal(q2.__class__, query.And)
+        assert_equal(q2[0].__class__, query.Term)
+        assert_equal(q2[1].__class__, query.Not)
+        assert_equal(q2.__unicode__(), '(cats:1 AND NOT count:0)')
 
 
 
 
 
 
+
+
+
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.