diff --git a/README.md b/README.md index 5c41094..591d040 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ Documentation is still a work in progress. In general, I use: - Debate whether the `UserMixin.login` method should accept usernames or I should standardize the usage of IDs only internally. - Ability to access user page and user photos by user's ID, not just username. - Should album size be cached on disk? -- Organize the tag exporter functions better. - Replace columns like area, ratio, bitrate by using expression indices or views (`width * height` etc). - Add some way to support large image albums without flooding the search results. Considering a "hidden" property so that a handful of representative images can appear in the search results, and the rest can be found on the actual Album page. - Add a `Photo.merge` to combine duplicate entries. diff --git a/etiquette/__init__.py b/etiquette/__init__.py index 9202351..a06b92d 100644 --- a/etiquette/__init__.py +++ b/etiquette/__init__.py @@ -6,3 +6,4 @@ from . import jsonify from . import objects from . import photodb from . import searchhelpers +from . import tag_export diff --git a/etiquette/photodb.py b/etiquette/photodb.py index c45ab42..c0498c4 100644 --- a/etiquette/photodb.py +++ b/etiquette/photodb.py @@ -14,6 +14,7 @@ from . import exceptions from . import helpers from . import objects from . import searchhelpers +from . import tag_export from voussoirkit import cacheclass from voussoirkit import expressionmatch @@ -176,76 +177,6 @@ def searchfilter_must_may_forbid(photo_tags, tag_musts, tag_mays, tag_forbids, f return True -def tag_export_easybake(tags, depth=0): - lines = [] - for tag in tags: - if not hasattr(tag, 'string'): - tag.string = tag.name - children = tag.children() - synonyms = tag.synonyms() - lines.append(tag.string) - - for synonym in synonyms: - synonym = tag.string + '+' + synonym - lines.append(synonym) - - for child in children: - child.string = tag.string + '.' + child.name - child_bake = tag_export_easybake(children, depth=depth+1) - if child_bake != '': - lines.append(child_bake) - - lines = '\n'.join(lines) - return lines - -def tag_export_json(tags): - def fill(tag): - children = {child.name:fill(child) for child in tag.children()} - return children - result = {} - for tag in tags: - result[tag.name] = fill(tag) - return result - -def tag_export_qualname_map(tags): - lines = tag_export_easybake(tags) - lines = lines.split('\n') - lines = [line for line in lines if line] - qualname_map = {} - for line in lines: - key = line.split('.')[-1].split('+')[-1] - value = line.split('+')[0] - qualname_map[key] = value - return qualname_map - -def tag_export_stdout(tags, depth=0): - for tag in tags: - children = tag.children() - synonyms = tag.synonyms() - - pad = ' ' * depth - synpad = ' ' * (depth + 1) - print(pad + str(tag)) - - for synonym in synonyms: - print(synpad + synonym) - - tag_export_stdout(children, depth=depth+1) - - if tag.parent() is None: - print() - -@decorators.time_me -def tag_export_totally_flat(tags): - result = {} - for tag in tags: - for child in tag.walk_children(): - children = list(child.walk_children()) - result[child] = children - for synonym in child.synonyms(): - result[synonym] = children - return result - #################################################################################################### #################################################################################################### @@ -725,7 +656,7 @@ class PDBPhotoMixin: if self._cached_frozen_children: frozen_children = self._cached_frozen_children else: - frozen_children = self.export_tags(tag_export_totally_flat) + frozen_children = tag_export.flat_dict(self.get_tags()) self._cached_frozen_children = frozen_children else: frozen_children = None @@ -916,20 +847,6 @@ class PDBTagMixin: super().__init__() self._tag_cache = cacheclass.Cache() - def export_tags(self, exporter=tag_export_stdout, specific_tag=None): - ''' - Send the top-level tags to function `exporter`. - Strings 'start' and 'stop' are sent before and after the tags are sent. - Recursion is to be handled by the exporter. - ''' - if specific_tag is None: - items = list(self.get_tags()) - items = [item for item in items if item.parent() is None] - items.sort(key=lambda x: x.name) - else: - items = [self.get_tag(specific_tag)] - return exporter(items) - def get_tag(self, name=None, id=None): ''' Redirect to get_tag_by_id or get_tag_by_name after xor-checking the parameters. diff --git a/etiquette/tag_export.py b/etiquette/tag_export.py new file mode 100644 index 0000000..f79c85f --- /dev/null +++ b/etiquette/tag_export.py @@ -0,0 +1,98 @@ +def easybake(tags): + ''' + A string where every line is the qualified name of a tag or its synonyms. + + people + people.family + people.family.mother + people.family.mother+mom + ''' + lines = [] + tags = list(tags) + for tag in tags: + qualname = tag.qualified_name() + lines.append(qualname) + lines.extend(qualname + '+' + syn for syn in tag.synonyms()) + return '\n'.join(lines) + +def flat_dict(tags): + ''' + A dictionary where every tag is its own key, and the value is a list + containing itself all of its nested children. + Synonyms not included. + + { + people: [people, family, mother], + family: [family, mother], + mother: [mother], + } + + The list contains itself so that you can quickly ask whether a user's + requested tag exists in that tree without having to write separate checks + for equaling the main tag versus existing in the rest of the subtree. + ''' + result = {} + for tag in tags: + for child in tag.walk_children(): + children = list(child.walk_children()) + result[child] = children + for synonym in child.synonyms(): + result[synonym] = children + return result + +def nested_dict(tags): + ''' + A dictionary where keys are tags, values are recursive dictionaries + of children. + Synonyms not included. + + { + people: { + family: { + mother: {} + } + } + } + ''' + result = {} + for tag in tags: + result[tag] = nested_dict(tag) + + return result + +def qualified_names(tags): + ''' + A dictionary where keys are string names, values are qualified names. + Synonyms included. + + { + 'people': 'people', + 'family': 'people.family', + 'mother': 'people.family.mother', + 'mom': 'people.family.mother', + } + ''' + results = {} + for tag in tags: + qualname = tag.qualified_name() + results[tag.name] = qualname + for synonym in tag.synonyms(): + results[synonym] = qualname + return results + +def stdout(tags, depth=0): + for tag in tags: + children = tag.children() + synonyms = tag.synonyms() + + pad = ' ' * depth + print(pad + tag.name) + + synpad = ' ' * (depth + 1) + for synonym in synonyms: + print(synpad + '+' + synonym) + + stdout(children, depth=depth+1) + + if tag.parent() is None: + print() diff --git a/frontends/etiquette_flask/etiquette_flask/etiquette_flask.py b/frontends/etiquette_flask/etiquette_flask/etiquette_flask.py index 7be70b6..afd3dee 100644 --- a/frontends/etiquette_flask/etiquette_flask/etiquette_flask.py +++ b/frontends/etiquette_flask/etiquette_flask/etiquette_flask.py @@ -565,7 +565,7 @@ def get_search_core(): def get_search_html(): search_results = get_search_core() search_kwargs = search_results['search_kwargs'] - qualname_map = P.export_tags(exporter=etiquette.photodb.tag_export_qualname_map) + qualname_map = etiquette.tag_export.qualified_names(P.get_tags()) session = session_manager.get(request) response = flask.render_template( 'search.html', diff --git a/frontends/etiquette_repl.py b/frontends/etiquette_repl.py index dbda00e..69faebb 100644 --- a/frontends/etiquette_repl.py +++ b/frontends/etiquette_repl.py @@ -1,9 +1,10 @@ # Use with # py -i etiquette_easy.py -import etiquette.photodb +import etiquette import os import sys + P = etiquette.photodb.PhotoDB() import traceback @@ -13,7 +14,7 @@ def easytagger(): if i.startswith('?'): i = i.split('?')[1] or None try: - P.export_tags(specific_tag=i) + etiquette.tag_export.stdout([P.get_tag(i)]) except: traceback.print_exc() else: