diff options
| -rw-r--r-- | CHANGELOG.rst | 2 | ||||
| -rw-r--r-- | docs/source/man/lesana-search.rst | 5 | ||||
| -rw-r--r-- | docs/source/user/search.rst | 18 | ||||
| -rw-r--r-- | docs/source/user/settings.rst | 3 | ||||
| -rw-r--r-- | lesana/collection.py | 7 | ||||
| -rw-r--r-- | lesana/command.py | 15 | ||||
| -rw-r--r-- | tests/data/complex/settings.yaml | 3 | ||||
| -rw-r--r-- | tests/test_collection.py | 12 | ||||
| -rw-r--r-- | tests/test_commands.py | 18 | 
9 files changed, 80 insertions, 3 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 48486ff..8b2d9c4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -24,6 +24,8 @@ Unreleased    contents of a field.  * New property ``precision`` for ``decimal`` fields, to force rounding    values to that number of decimal digits. +* New settings property ``search_aliases`` with a dict of values to be +  filled in in a jinja2 template for a search query.  Bugfixes  -------- diff --git a/docs/source/man/lesana-search.rst b/docs/source/man/lesana-search.rst index fff4b8e..f7bcf9c 100644 --- a/docs/source/man/lesana-search.rst +++ b/docs/source/man/lesana-search.rst @@ -7,7 +7,8 @@ SYNOPSIS  lesana search [--help] [--collection COLLECTION] [--template TEMPLATE] \    [--offset OFFSET] [--pagesize PAGESIZE] [--all] \ -  [--sort FIELD1 [--sort FIELD2 ...]] [query [query ...]] +  [--expand-query-template] [--sort FIELD1 [--sort FIELD2 ...]] \ +  [query [query ...]]  DESCRIPTION  =========== @@ -50,4 +51,6 @@ OPTIONS     This option can be added multiple times; prefix the name of the field     with ``-`` to reverse the results (e.g. ``--sort='-date'``). +expand-query-template +   Render search_aliases in the query as a jinja2 template diff --git a/docs/source/user/search.rst b/docs/source/user/search.rst index 41746a3..d14f37f 100644 --- a/docs/source/user/search.rst +++ b/docs/source/user/search.rst @@ -22,3 +22,21 @@ xapian for details.  .. _`Query Parser`: https://getting-started-with-xapian.readthedocs.io/en/latest/concepts/search/queryparser.html +.. _search aliases: + +Search templates and ``search_aliases`` +======================================= + +In some contexts, search queries are rendered as jinja2 templates with +the contents of the ``search_aliases`` property as set in +``settings.yaml``. + +The values of those search aliases should be valid search snippets with +the syntax documented above; it's usually a good idea to wrap them in +parenthesis, so that they are easier to use in complex queries; e.g.:: + +   my_alias: '(name:object OR name:thing)' + +can correctly be used in a query like:: + +   {{ my_alias }} AND description:shiny diff --git a/docs/source/user/settings.rst b/docs/source/user/settings.rst index 2d3a3fa..ba14e37 100644 --- a/docs/source/user/settings.rst +++ b/docs/source/user/settings.rst @@ -20,6 +20,9 @@ It is a yaml file with a dict of properties and their values.     a list of field names (possibly prefixed by + or -) that are used by     default to sort results of searches in the collection.     The fields must be marked as sortable in their definition, see below. +``search_aliases``: +   a dict of <name>: <search snippet> which can be used in a query +   template. For more details see :ref:`search aliases`  ``fields``:     The list of fields used by the collection, as described below. diff --git a/lesana/collection.py b/lesana/collection.py index d51187f..e428ae1 100644 --- a/lesana/collection.py +++ b/lesana/collection.py @@ -334,6 +334,13 @@ class Collection(object):              )          return cache +    def render_query_template(self, query): +        """ +        Render a query template, filling it with search_aliases. +        """ +        t = jinja2.Template(query) +        return t.render(**self.settings.get('search_aliases', {})) +      def start_search(self, querystring, sort_by=None):          """          Prepare a search for querystring. diff --git a/lesana/command.py b/lesana/command.py index 92f82df..ac7eba2 100644 --- a/lesana/command.py +++ b/lesana/command.py @@ -291,6 +291,14 @@ class Search(Command):              dict(action='append', help='Sort results by a sortable field'),          ),          ( +            ['--expand-query-template', '-e'], +            { +                'action': 'store_true', +                'help': +                    'Render search_aliases in the query as a jinja2 template', +            }, +        ), +        (              ['query'],              {                  'help': 'Xapian query to search in the collection', @@ -315,16 +323,19 @@ class Search(Command):          offset = self.args.offset or 0          pagesize = self.args.pagesize or 12          collection = self.collection_class(self.args.collection) +        query = self.args.query +        if self.args.expand_query_template: +            query = collection.render_query_template(query)          # sorted results require a less efficient full search rather          # than being able to use the list of all documents. -        if self.args.query == ['*'] and not ( +        if query == ['*'] and not (              self.args.sort              or getattr(collection.settings, 'default_sort', False)          ):              results = collection.get_all_documents()          else:              collection.start_search( -                ' '.join(self.args.query), +                ' '.join(query),                  sort_by=self.args.sort              )              if self.args.all: diff --git a/tests/data/complex/settings.yaml b/tests/data/complex/settings.yaml index f4ad574..09e9c98 100644 --- a/tests/data/complex/settings.yaml +++ b/tests/data/complex/settings.yaml @@ -63,3 +63,6 @@ fields:      - name: price        type: decimal        precision: 2 +search_aliases: +  nice: '(category:first OR category:second)' +  bad: category:third diff --git a/tests/test_collection.py b/tests/test_collection.py index de349ed..1400f57 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -550,6 +550,18 @@ class testComplexCollection(unittest.TestCase):          entry = self.collection.entry_from_eid(eid)          self.assertEqual(entry.data['price'], "1.90") +    def test_search_aliases(self): +        search_query = "{{ nice }}" +        search_query = self.collection.render_query_template(search_query) +        print("QUERY IS", search_query) +        self.collection.start_search(search_query) +        res = self.collection.get_search_results() +        matches = list(res) +        self.assertEqual(len(matches), 2) +        matches_ids = [m.eid for m in matches] +        self.assertIn('8e9fa1ed3c1b4a30a6be7a98eda0cfa7', matches_ids) +        self.assertIn('5084bc6e94f24dc6976629282ef30419', matches_ids) +  class testCollectionWithErrors(unittest.TestCase):      def setUp(self): diff --git a/tests/test_commands.py b/tests/test_commands.py index 1a7d83b..91a8894 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -115,6 +115,7 @@ class testCommandsSimple(unittest.TestCase, CommandsMixin):              'offset': None,              'pagesize': None,              'sort': None, +            'expand_query_template': False,              'all': False,          }          streams = self._run_command(command.Search(), args) @@ -205,6 +206,23 @@ class testCommandsComplex(unittest.TestCase, CommandsMixin):          self.assertIn('this: 1', streams['stdout'].getvalue())          self.assertEqual(streams['stderr'].getvalue(), '') +    def test_search_template(self): +        args = { +            'collection': self.tmpdir.name, +            'git': True, +            'template': False, +            'query': '{{ nice }}', +            'expand_query_template': True, +            'offset': None, +            'pagesize': None, +            'sort': None, +            'all': False, +        } +        streams = self._run_command(command.Search(), args) +        self.assertIn('8e9fa1ed', streams['stdout'].getvalue()) +        self.assertIn('5084bc6e', streams['stdout'].getvalue()) +        self.assertEqual(streams['stderr'].getvalue(), '') +  if __name__ == '__main__':      unittest.main()  | 
