# Path: standup/status/search.py
# def generate_query(field, text):
#     """Parses the search text and returns a Q
# 
#     This tries to handle parse errors. If the text is unparseable, it
#     returns a single Q.
# 
#     :arg field: the field to search
#     :arg text:  the user's search text
# 
#     :return: Q
# 
#     It uses a recursive descent parser with this grammar::
# 
#         query = match | match oper
#         oper  = "AND" query |
#                 "OR" query
#         match = token ... |
#                 '"' token '"'
# 
#     If it encounters a parse error, it attempts to recover, but if it
#     can't, then it just returns a single match query.
# 
#     """
#     # Build the Q tree data structure bottom up.
#     tokens = to_tokens(text)
#     tokens.reverse()
# 
#     try:
#         clauses = parse_query(field, tokens)
#     except ParseError:
#         return build_match(field, text)
# 
#     if len(clauses) > 1:
#         return build_or(clauses)
# 
#     return clauses[0]
# 
# def to_tokens(text):
#     """Breaks the search text into tokens"""
#     in_quotes = False
#     escape = False
# 
#     tokens = []
#     token = []
# 
#     for c in text:
#         if c == u'\\':
#             escape = True
#             token.append(c)
#             continue
# 
#         if in_quotes:
#             if not escape and c == u'"':
#                 in_quotes = False
#             token.append(c)
# 
#         elif not escape and c == u'"':
#             in_quotes = True
#             token.append(c)
# 
#         elif c in WHITESPACE:
#             if token:
#                 tokens.append(u''.join(token))
#                 token = []
# 
#         else:
#             token.append(c)
# 
#         escape = False
# 
#     if in_quotes:
#         # Finish off a missing quote
#         if token:
#             token.append(u'"')
#         else:
#             tokens[-1] = tokens[-1] + u'"'
# 
#     if token:
#         # Add last token
#         tokens.append(u''.join(token))
# 
#     return tokens
# 
# def unescape(text):
#     """Unescapes text
# 
#     >>> unescape(u'abc')
#     u'abc'
#     >>> unescape(u'\\abc')
#     u'abc'
#     >>> unescape(u'\\\\abc')
#     u'\\abc'
# 
#     """
#     # Note: We can ditch this and do it in tokenizing if tokenizing
#     # returned typed tokens rather than a list of strings.
#     new_text = []
#     escape = False
#     for c in text:
#         if not escape and c == u'\\':
#             escape = True
#             continue
# 
#         new_text.append(c)
#         escape = False
# 
#     return u''.join(new_text)


from django.db.models import Q
from standup.status.search import (
    generate_query,
    to_tokens,
    unescape,
)
import pytest




class TestToTokens:
    @pytest.mark.parametrize('text, expected', [
        (u'abc', [u'abc']),
        (u'abc def', [u'abc', u'def']),
        (u'abc OR "def"', [u'abc', u'OR', u'"def"']),
        (u'abc OR "def ghi"', [u'abc', u'OR', u'"def ghi"']),
        (u'abc AND "def ghi"', [u'abc', u'AND', u'"def ghi"']),
    ])
    def test_basic(self, text, expected):
        assert to_tokens(text) == expected

    @pytest.mark.parametrize('text, expected', [
        (u'\\"AND', [u'\\"AND']),
        (u'\\"AND\\"', [u'\\"AND\\"']),
    ])
    def test_escaping(self, text, expected):
        """Escaped things stay escaped"""
        assert to_tokens(text) == expected

    def test_edge_cases(self):

# Path: standup/status/search.py
# def generate_query(field, text):
#     """Parses the search text and returns a Q
# 
#     This tries to handle parse errors. If the text is unparseable, it
#     returns a single Q.
# 
#     :arg field: the field to search
#     :arg text:  the user's search text
# 
#     :return: Q
# 
#     It uses a recursive descent parser with this grammar::
# 
#         query = match | match oper
#         oper  = "AND" query |
#                 "OR" query
#         match = token ... |
#                 '"' token '"'
# 
#     If it encounters a parse error, it attempts to recover, but if it
#     can't, then it just returns a single match query.
# 
#     """
#     # Build the Q tree data structure bottom up.
#     tokens = to_tokens(text)
#     tokens.reverse()
# 
#     try:
#         clauses = parse_query(field, tokens)
#     except ParseError:
#         return build_match(field, text)
# 
#     if len(clauses) > 1:
#         return build_or(clauses)
# 
#     return clauses[0]
# 
# def to_tokens(text):
#     """Breaks the search text into tokens"""
#     in_quotes = False
#     escape = False
# 
#     tokens = []
#     token = []
# 
#     for c in text:
#         if c == u'\\':
#             escape = True
#             token.append(c)
#             continue
# 
#         if in_quotes:
#             if not escape and c == u'"':
#                 in_quotes = False
#             token.append(c)
# 
#         elif not escape and c == u'"':
#             in_quotes = True
#             token.append(c)
# 
#         elif c in WHITESPACE:
#             if token:
#                 tokens.append(u''.join(token))
#                 token = []
# 
#         else:
#             token.append(c)
# 
#         escape = False
# 
#     if in_quotes:
#         # Finish off a missing quote
#         if token:
#             token.append(u'"')
#         else:
#             tokens[-1] = tokens[-1] + u'"'
# 
#     if token:
#         # Add last token
#         tokens.append(u''.join(token))
# 
#     return tokens
# 
# def unescape(text):
#     """Unescapes text
# 
#     >>> unescape(u'abc')
#     u'abc'
#     >>> unescape(u'\\abc')
#     u'abc'
#     >>> unescape(u'\\\\abc')
#     u'\\abc'
# 
#     """
#     # Note: We can ditch this and do it in tokenizing if tokenizing
#     # returned typed tokens rather than a list of strings.
#     new_text = []
#     escape = False
#     for c in text:
#         if not escape and c == u'\\':
#             escape = True
#             continue
# 
#         new_text.append(c)
#         escape = False
# 
#     return u''.join(new_text)


# Path: standup/status/tests/test_search.py
from django.db.models import Q
from standup.status.search import (
    generate_query,
    to_tokens,
    unescape,
)
import pytest





class TestToTokens:
    @pytest.mark.parametrize('text, expected', [
        (u'abc', [u'abc']),
        (u'abc def', [u'abc', u'def']),
        (u'abc OR "def"', [u'abc', u'OR', u'"def"']),
        (u'abc OR "def ghi"', [u'abc', u'OR', u'"def ghi"']),
        (u'abc AND "def ghi"', [u'abc', u'AND', u'"def ghi"']),
    ])
    def test_basic(self, text, expected):
        assert to_tokens(text) == expected

    @pytest.mark.parametrize('text, expected', [
        (u'\\"AND', [u'\\"AND']),
        (u'\\"AND\\"', [u'\\"AND\\"']),
    ])
    def test_escaping(self, text, expected):
        """Escaped things stay escaped"""
        assert to_tokens(text) == expected

    def test_edge_cases(self):

        assert to_tokens(u'AND "def ghi') == [u'AND', u'"def ghi"']