11import re
22
3- from library .models import WorkType
4-
5- KEYWORDS = ["artist" , "work" , "title" ]
6-
73
84class QueryLanguageParser :
9- """Parser for search query mini language used to search song."""
5+ """Parser for search query mini language.
106
11- def __init__ ( self ) :
12- self . keywords_work_type = [
13- work_type . query_name for work_type in WorkType . objects . all ()
14- ]
7+ Args :
8+ keywords (list of str): List of keywords to use
9+ for parsing.
10+ """
1511
16- self .keywords = KEYWORDS + self .keywords_work_type
12+ def __init__ (self , keywords ):
13+ self .keywords = keywords
1714
1815 regex = r"""
1916 \b(?P<keyword>{keywords_regex}) # keyword
@@ -86,33 +83,24 @@ def parse(self, query):
8683 with spaces.
8784
8885 Returns:
89- dict: Query terms arranged among the following keys:
90- `artist`:
91- `contains`: List of list of artists names to match
92- partially.
93- `exact`: List of list of artists names to match exactly.
94- `work`:
95- `contains`: List of works names to match partially.
96- `exact`: List of works names to match exactly.
97- `title:
98- `contains`: Titles to match partially
99- `exact`: Titles to match exactly.
86+ dict: Query terms parsed according to the
87+ provided keywords. Each item is a dict
88+ containing two lists:
89+ `contains`: List of partial matches.
90+ `exact`: List of exact matches.
91+ In addition, two extra items are present in
92+ the dict:
10093 `tag`: List of tags to match in uppercase.
101- `work_type`: Dict with queryname as key and a dict as value
102- with the keys `contains` and `exact`.
103-
10494 `remaining`: Unparsed text.
10595 """
10696 # create results structure
107- # work_type will be filled only if necessary
108- result = {
109- "artist" : {"contains" : [], "exact" : []},
110- "work" : {"contains" : [], "exact" : []},
111- "title" : {"contains" : [], "exact" : []},
112- "work_type" : {},
113- "remaining" : [],
114- "tag" : [],
115- }
97+ result = {kw : {"contains" : [], "exact" : []} for kw in self .keywords }
98+ result .update (
99+ {
100+ "remaining" : [],
101+ "tag" : [],
102+ }
103+ )
116104
117105 for match in self .language_matcher .finditer (query ):
118106 group_index = match .groupdict ()
@@ -126,21 +114,11 @@ def parse(self, query):
126114 .strip ()
127115 )
128116
129- if target in self .keywords_work_type :
130- # create worktype if not exists
131- if target not in result ["work_type" ]:
132- result ["work_type" ][target ] = {"contains" : [], "exact" : []}
133-
134- result_target = result ["work_type" ][target ]
135-
136- else :
137- result_target = result [target ]
138-
139117 if value_contains and not value_exact :
140- result_target ["contains" ].append (value_contains )
118+ result [ target ] ["contains" ].append (value_contains )
141119
142120 elif value_exact and not value_contains :
143- result_target ["exact" ].append (value_exact )
121+ result [ target ] ["exact" ].append (value_exact )
144122
145123 else :
146124 raise ValueError ("Inconsistency" )
@@ -158,3 +136,23 @@ def parse(self, query):
158136 result ["tag" ].append (item_clean .upper ())
159137
160138 return result
139+
140+
141+ def regroup (res , key , keys ):
142+ """Regroup non empty keys in a specific key.
143+
144+ Args:
145+ res (dict): Dictionary where to regroup keys.
146+ key (str): Key where to regroup `keys`.
147+ keys (list of str): Keys to regroup in `key`.
148+ Any key with no items in `exact` and
149+ `contains` will just be removed.
150+ """
151+ res_copy = res .copy ()
152+ res_copy [key ] = {}
153+ for k in keys :
154+ val = res_copy .pop (k )
155+ if len (val ["exact" ]) or len (val ["contains" ]):
156+ res_copy [key ][k ] = val
157+
158+ return res_copy
0 commit comments