From 547124605a9f86469a547fcaf38dc18ae57b707f Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Thu, 5 Jan 2023 23:55:02 +0100 Subject: essential structures --- tagit/utils/__init__.py | 18 ++++++++++++++++++ tagit/utils/bsfs.py | 15 +++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 tagit/utils/__init__.py create mode 100644 tagit/utils/bsfs.py (limited to 'tagit/utils') diff --git a/tagit/utils/__init__.py b/tagit/utils/__init__.py new file mode 100644 index 0000000..d5a8efe --- /dev/null +++ b/tagit/utils/__init__.py @@ -0,0 +1,18 @@ +""" + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import typing + +# inner-module imports +from . import bsfs + +# exports +__all__: typing.Sequence[str] = ( + 'bsfs', + ) + +## EOF ## diff --git a/tagit/utils/bsfs.py b/tagit/utils/bsfs.py new file mode 100644 index 0000000..0ab90a9 --- /dev/null +++ b/tagit/utils/bsfs.py @@ -0,0 +1,15 @@ +"""BSFS bridge, provides BSFS bindings for tagit. + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import typing + +# bsfs imports + +# exports +__all__: typing.Sequence[str] = [] + +## EOF ## -- cgit v1.2.3 From 079b4da93ea336b5bcc801cfd64c310aa7f8ddee Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Fri, 6 Jan 2023 12:20:22 +0100 Subject: config early port (test still fails) --- tagit/utils/__init__.py | 1 + tagit/utils/errors.py | 52 ++++++++++++++++++++++++++++++++++++++++ tagit/utils/shared.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 tagit/utils/errors.py create mode 100644 tagit/utils/shared.py (limited to 'tagit/utils') diff --git a/tagit/utils/__init__.py b/tagit/utils/__init__.py index d5a8efe..d143034 100644 --- a/tagit/utils/__init__.py +++ b/tagit/utils/__init__.py @@ -9,6 +9,7 @@ import typing # inner-module imports from . import bsfs +from .shared import * # FIXME: port properly # exports __all__: typing.Sequence[str] = ( diff --git a/tagit/utils/errors.py b/tagit/utils/errors.py new file mode 100644 index 0000000..1bed670 --- /dev/null +++ b/tagit/utils/errors.py @@ -0,0 +1,52 @@ +"""Module-wide errors. + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2018 +""" +# exports +__all__ = ( + 'EmptyFileError', + 'LoaderError', + 'NotAFileError', + 'ProgrammingError', + 'UserError', + 'abstract', + ) + + +## code ## + +def abstract(): + """Marks that a method has to be implemented in a child class.""" + raise NotImplementedError('abstract method that must be implemented in a subclass') + +class ProgrammingError(Exception): + """Reached a program state that shouldn't be reachable.""" + pass + +class UserError(ValueError): + """Found an illegal value that was specified directly by the user.""" + pass + +class NotAFileError(OSError): + """A file-system object is not a regular file.""" + pass + +class EmptyFileError(OSError): + """A file is unexpectedly empty.""" + pass + +class LoaderError(Exception): + """Failed to load or initialize a critical data structure.""" + pass + +class ParserFrontendError(Exception): + """Generic parser frontend error.""" + pass + +class ParserBackendError(Exception): + """Generic parser backend error.""" + pass + +## EOF ## diff --git a/tagit/utils/shared.py b/tagit/utils/shared.py new file mode 100644 index 0000000..13ffd2a --- /dev/null +++ b/tagit/utils/shared.py @@ -0,0 +1,63 @@ +# FIXME: port properly! +"""Shared functionality. + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +import logging +import pkgutil +import re +import typing + +# exports +__all__ = ('import_all', ) + + +## code ## + +# exports +__all__: typing.Sequence[str] = ( + 'fst', + 'is_list', + 'import_all', + ) + + +## code ## + +logger = logging.getLogger(__name__) + +fst = lambda lst: lst[0] + +def is_list(cand): + """Return true if *cand* is a list, a set, or a tuple""" + return isinstance(cand, list) or isinstance(cand, set) or isinstance(cand, tuple) + +def import_all(module, exclude=None, verbose=False): + """Recursively import all submodules of *module*. + *exclude* is a set of submodule names which will + be omitted. With *verbose*, all imports are logged + with level info. Returns all imported modules. + + >>> import tagit + >>> import_all(tagit, exclude={'tagit.shared.external'}) + + """ + exclude = set([] if exclude is None else exclude) + imports = [] + for importer, name, ispkg in pkgutil.iter_modules(module.__path__, module.__name__ + '.'): + if ispkg and all(re.match(exl, name) is None for exl in exclude): + if verbose: + logger.info(f'importing: {name}') + try: + module = __import__(name, fromlist='dummy') + imports.append(module) + imports += import_all(module, exclude, verbose) + except Exception as e: + logger.error(f'importing: {name}') + + return imports + +## EOF ## -- cgit v1.2.3 From ad49aedaad3acece200ea92fd5d5a5b3e19c143b Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Fri, 6 Jan 2023 14:07:15 +0100 Subject: desktop dependent widgets early port --- tagit/utils/__init__.py | 2 ++ tagit/utils/builder.py | 82 +++++++++++++++++++++++++++++++++++++++++++++++ tagit/utils/frame.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ tagit/utils/shared.py | 77 ++++++++++++++++++++++++++++++++++++++++----- tagit/utils/time.py | 63 +++++++++++++++++++++++++++++++++++++ 5 files changed, 301 insertions(+), 7 deletions(-) create mode 100644 tagit/utils/builder.py create mode 100644 tagit/utils/frame.py create mode 100644 tagit/utils/time.py (limited to 'tagit/utils') diff --git a/tagit/utils/__init__.py b/tagit/utils/__init__.py index d143034..3f09078 100644 --- a/tagit/utils/__init__.py +++ b/tagit/utils/__init__.py @@ -9,6 +9,8 @@ import typing # inner-module imports from . import bsfs +from . import time as ttime +from .frame import Frame from .shared import * # FIXME: port properly # exports diff --git a/tagit/utils/builder.py b/tagit/utils/builder.py new file mode 100644 index 0000000..f6c5818 --- /dev/null +++ b/tagit/utils/builder.py @@ -0,0 +1,82 @@ +""" + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +from collections import abc +from functools import partial +from inspect import isclass +import typing + +# exports +__all__: typing.Sequence[str] = ( + 'BuilderBase', + 'InvalidFactoryName', + ) + + +## code ## + +class InvalidFactoryName(KeyError): pass + +class BuilderBase(abc.Mapping, abc.Hashable): + _factories = dict() + + def __getitem__(self, key): + return self.get(key) + + def __contains__(self, key): + return key in self._factories + + def __iter__(self): + return iter(self._factories.keys()) + + def __hash__(self): + return hash(frozenset(self._factories.items())) + + def __len__(self): + return len(self._factories) + + def __eq__(self, other): + return type(self) == type(other) and self._factories == other._factories + + + def get(self, key): + if key not in self._factories: + raise InvalidFactoryName(key) + return self._factories[key] + + @classmethod + def keys(self): + return self._factories.keys() + + @classmethod + def items(self): + return self._factories.items() + + @classmethod + def describe(cls, key): + if key not in cls._factories: + raise InvalidFactoryName(key) + desc = cls._factories[key].__doc__ + return desc if desc is not None else '' + + def prepare(self, key, *args, **kwargs): + # If building is to be customized, overwrite this function. + return partial(self[key], *args, **kwargs) + + def build(self, key, *args, **kwargs): + fu = self.prepare(key, *args, **kwargs) + return fu() + + def key_from_instance(self, cls): + for key, clbk in self._factories.items(): + if isclass(clbk) and isinstance(cls, clbk): + return key + if not isclass(clbk) and cls == clbk: + return key + raise KeyError(type(cls)) + +## EOF ## diff --git a/tagit/utils/frame.py b/tagit/utils/frame.py new file mode 100644 index 0000000..c6bdc1e --- /dev/null +++ b/tagit/utils/frame.py @@ -0,0 +1,84 @@ +""" + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +import json + +# exports +__all__ = ('Frame', ) + + +## code ## + +class Frame(dict): + def __init__(self, cursor=None, selection=None, offset=0, **kwargs): + super(Frame, self).__init__(**kwargs) + selection = selection if selection is not None else [] + self['cursor'] = cursor + self['selection'] = selection + self['offset'] = offset + + @property + def selection(self): + return self['selection'] + + @property + def cursor(self): + return self['cursor'] + + @property + def offset(self): + return self['offset'] + + def copy(self): + return Frame(**super(Frame, self).copy()) + + def serialize(self): + return json.dumps({ + 'cursor': self.cursor.guid if self.cursor is not None else 'None', + 'group': self.cursor.group if hasattr(self.cursor, 'group') else 'None', + 'selection': [img.guid for img in self.selection], + 'offset': self.offset + }) + + @staticmethod + def from_serialized(lib, serialized, ignore_errors=True): + d = json.loads(serialized) + + # load cursor + cursor = None + try: + if 'cursor' in d and d['cursor'] is not None and d['cursor'].lower() != 'none': + cursor = lib.entity(d['cursor']) + except KeyError as err: + if not ignore_errors: + raise err + + if 'group' in d and d['group'] is not None and d['group'].lower() != 'none': + try: + # FIXME: late import; breaks module dependency structure + from tagit.storage.library.entity import Representative + cursor = Representative.Representative(lib, d['group']) + except ValueError: + # group doesn't exist anymore; ignore + pass + + # load selection + selection = [] + for guid in d.get('selection', []): + try: + selection.append(lib.entity(guid)) + except KeyError as err: + if not ignore_errors: + raise err + + return Frame( + cursor = cursor, + selection = selection, + offset = d.get('offset', 0) + ) + +## EOF ## diff --git a/tagit/utils/shared.py b/tagit/utils/shared.py index 13ffd2a..82fe672 100644 --- a/tagit/utils/shared.py +++ b/tagit/utils/shared.py @@ -6,22 +6,25 @@ A copy of the license is provided with the project. Author: Matthias Baumgartner, 2022 """ # standard imports +from collections import namedtuple import logging +import os import pkgutil import re import typing -# exports -__all__ = ('import_all', ) - - -## code ## - # exports __all__: typing.Sequence[str] = ( + 'Resolution', + 'Struct', + 'clamp', + 'flatten', 'fst', - 'is_list', 'import_all', + 'is_hex', + 'is_list', + 'magnitude_fmt', + 'truncate_dir', ) @@ -60,4 +63,64 @@ def import_all(module, exclude=None, verbose=False): return imports +def clamp(value, hi, lo=0): + """Restrain a *value* to the range *lo* to *hi*.""" + return max(lo, min(hi, value)) + +Resolution = namedtuple('resolution', ('width', 'height')) + +def truncate_dir(path, cutoff=3): + """Remove path up to last *cutoff* directories""" + if cutoff < 0: raise ValueError('path cutoff must be positive') + dirs = os.path.dirname(path).split(os.path.sep) + last_dirs = dirs[max(0, len(dirs) - cutoff):] + prefix = '' + if os.path.isabs(path) and len(last_dirs) == len(dirs): + prefix = os.path.sep + + return prefix + os.path.join(*(last_dirs + [os.path.basename(path)])) + +def magnitude_fmt(num, suffix='iB', scale=1024): + """Human-readable number format. + + adapted from Sridhar Ratnakumar, 2009 + https://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size + """ + for unit in ['','K','M','G','T','P','E','Z']: + if abs(num) < scale: + return "%3.1f%s%s" % (num, unit, suffix) + num /= scale + return "%.1f%s%s" % (num, 'Y', suffix) + +class Struct(dict): + """Dict with item access as members. + + >>> tup = Struct(timestamp=123, series=['1','2','3']) + >>> tup.timestamp + 123 + >>> tup['timestamp'] + 123 + + """ + def __getattr__(self, name): + return self[name] + def __setattr__(self, name, value): + self[name] = value + +def flatten(lst): + flat = [] + for itm in lst: + flat.extend(list(itm)) + return flat + +def is_hex(string): + """Return True if the *string* can be interpreted as a hex value.""" + try: + int(string, 16) + return True + except ValueError: + return False + except TypeError: + return False + ## EOF ## diff --git a/tagit/utils/time.py b/tagit/utils/time.py new file mode 100644 index 0000000..4260ac7 --- /dev/null +++ b/tagit/utils/time.py @@ -0,0 +1,63 @@ +"""Time helpers. + +* Camera local +* System local +* UTC + +Timestamp to datetime + * Timestamp + * in UTC + * Timezone + * Implicit system local timezone + * No known timezone + * Known timezone + +Datetime to timestamp + * always store as local time + * optionally with UTC offset + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +from datetime import timezone, datetime, tzinfo, timedelta + +# exports +__all__ = ('timestamp', 'from_timestamp') + + +## code ## + +timestamp_loc = lambda dt: dt.replace(tzinfo=timezone.utc).timestamp() + +timestamp_utc = lambda dt: dt.timestamp() + +from_timestamp_loc = lambda ts: datetime.utcfromtimestamp(ts) + +from_timestamp_utc = lambda ts: datetime.fromtimestamp(ts) + +now = datetime.now + +timestamp_min = timestamp_loc(datetime.min) + +timestamp_max = timestamp_loc(datetime.max) + +def utcoffset(dt): + if dt.tzinfo is None: + return local_tzo(dt) + elif dt.tzinfo is NoTimeZone: + return None + else: + return dt.tzinfo.utcoffset(dt).total_seconds() / 3600 + +NoTimeZone = timezone(timedelta(0), 'NoTimeZone') + +def local_tzo(dt=None): + """Return the offset between the local time and UTC. + (i.e. return the x of UTC+x). + """ + dt = datetime.now() if dt is None else dt + return (timestamp_loc(dt) - dt.timestamp()) / 3600 + +## EOF ## -- cgit v1.2.3 From 37f1ac3f456b6677d9ce14274f3987ccda752f03 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Sat, 7 Jan 2023 16:26:26 +0100 Subject: browser and misc actions --- tagit/utils/shared.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'tagit/utils') diff --git a/tagit/utils/shared.py b/tagit/utils/shared.py index 82fe672..0d496ed 100644 --- a/tagit/utils/shared.py +++ b/tagit/utils/shared.py @@ -10,14 +10,17 @@ from collections import namedtuple import logging import os import pkgutil +import platform import re import typing +import warnings # exports __all__: typing.Sequence[str] = ( 'Resolution', 'Struct', 'clamp', + 'fileopen', 'flatten', 'fst', 'import_all', @@ -123,4 +126,18 @@ def is_hex(string): except TypeError: return False +def fileopen(pth): + """Open a file in the preferred application as a subprocess. + This operation is platform dependent. + """ + try: + binopen = { + "Linux" : "xdg-open", # Linux + "darwin" : "open", # MAX OS X + "Windows" : "start", # Windows + }.get(platform.system()) + subprocess.call((binopen, pth)) + except KeyError: + warnings.warn('Unknown platform {}'.format(platform.system())) + ## EOF ## -- cgit v1.2.3 From 06904269d867ed7c8355c0615473baf524fcf30b Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Sun, 8 Jan 2023 17:21:19 +0100 Subject: parsing early port --- tagit/utils/errors.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'tagit/utils') diff --git a/tagit/utils/errors.py b/tagit/utils/errors.py index 1bed670..7a2556e 100644 --- a/tagit/utils/errors.py +++ b/tagit/utils/errors.py @@ -2,17 +2,17 @@ Part of the tagit module. A copy of the license is provided with the project. -Author: Matthias Baumgartner, 2018 +Author: Matthias Baumgartner, 2022 """ # exports __all__ = ( - 'EmptyFileError', - 'LoaderError', - 'NotAFileError', - 'ProgrammingError', - 'UserError', - 'abstract', - ) + 'EmptyFileError', + 'LoaderError', + 'NotAFileError', + 'ProgrammingError', + 'UserError', + 'abstract', + ) ## code ## @@ -49,4 +49,8 @@ class ParserBackendError(Exception): """Generic parser backend error.""" pass +class ParserError(Exception): + """String parsing failure.""" + pass + ## EOF ## -- cgit v1.2.3 From 9c366758665d9cfee7796ee45a8167a5412ae9ae Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Fri, 13 Jan 2023 09:49:10 +0100 Subject: filter early port, parsing adaptions --- tagit/utils/__init__.py | 1 + tagit/utils/bsfs.py | 10 +++++++++- tagit/utils/namespaces.py | 30 ++++++++++++++++++++++++++++++ tagit/utils/shared.py | 12 ++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tagit/utils/namespaces.py (limited to 'tagit/utils') diff --git a/tagit/utils/__init__.py b/tagit/utils/__init__.py index 3f09078..16dcd4d 100644 --- a/tagit/utils/__init__.py +++ b/tagit/utils/__init__.py @@ -9,6 +9,7 @@ import typing # inner-module imports from . import bsfs +from . import namespaces as ns from . import time as ttime from .frame import Frame from .shared import * # FIXME: port properly diff --git a/tagit/utils/bsfs.py b/tagit/utils/bsfs.py index 0ab90a9..d80efe0 100644 --- a/tagit/utils/bsfs.py +++ b/tagit/utils/bsfs.py @@ -8,8 +8,16 @@ Author: Matthias Baumgartner, 2022 import typing # bsfs imports +from bsfs import schema, Open +from bsfs.query import ast +from bsfs.namespace import Namespace # exports -__all__: typing.Sequence[str] = [] +__all__: typing.Sequence[str] = ( + 'Namespace', + 'Open', + 'ast', + 'schema', + ) ## EOF ## diff --git a/tagit/utils/namespaces.py b/tagit/utils/namespaces.py new file mode 100644 index 0000000..dd26eef --- /dev/null +++ b/tagit/utils/namespaces.py @@ -0,0 +1,30 @@ +"""Default namespaces used throughout tagit. + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +import typing + +# inner-module imports +from . import bsfs as _bsfs + +# constants +bse = _bsfs.Namespace('http://bsfs.ai/schema/Entity') +bsfs = _bsfs.Namespace('http://bsfs.ai/schema', fsep='/') +bsm = _bsfs.Namespace('http://bsfs.ai/schema/Meta') +bst = _bsfs.Namespace('http://bsfs.ai/schema/Tag') +xsd = _bsfs.Namespace('http://www.w3.org/2001/XMLSchema') + +# export +__all__: typing.Sequence[str] = ( + 'bse', + 'bsfs', + 'bsm', + 'xsd', + ) + +## EOF ## + + diff --git a/tagit/utils/shared.py b/tagit/utils/shared.py index 0d496ed..b5ab421 100644 --- a/tagit/utils/shared.py +++ b/tagit/utils/shared.py @@ -28,6 +28,7 @@ __all__: typing.Sequence[str] = ( 'is_list', 'magnitude_fmt', 'truncate_dir', + 'get_root', ) @@ -140,4 +141,15 @@ def fileopen(pth): except KeyError: warnings.warn('Unknown platform {}'.format(platform.system())) + +def get_root(obj): + """Traverse the widget tree upwards until the root is found.""" + while obj.parent is not None and obj.parent != obj.parent.parent: + if hasattr(obj, 'root') and obj.root is not None: + return obj.root + + obj = obj.parent + + return obj + ## EOF ## -- cgit v1.2.3 From 52fa64513dae60c3ed410622502f8c2369c1a348 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Fri, 13 Jan 2023 10:14:18 +0100 Subject: moved filter parsing code --- tagit/utils/errors.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'tagit/utils') diff --git a/tagit/utils/errors.py b/tagit/utils/errors.py index 7a2556e..8b5e21a 100644 --- a/tagit/utils/errors.py +++ b/tagit/utils/errors.py @@ -53,4 +53,8 @@ class ParserError(Exception): """String parsing failure.""" pass +class BackendError(Exception): + """Generic backend error.""" + pass + ## EOF ## -- cgit v1.2.3 From 20d31b0c4a61b5f026fc8b0ff98c43b8a00bee48 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Wed, 25 Jan 2023 10:38:00 +0100 Subject: browser folding --- tagit/utils/namespaces.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'tagit/utils') diff --git a/tagit/utils/namespaces.py b/tagit/utils/namespaces.py index dd26eef..5446922 100644 --- a/tagit/utils/namespaces.py +++ b/tagit/utils/namespaces.py @@ -10,18 +10,25 @@ import typing # inner-module imports from . import bsfs as _bsfs -# constants -bse = _bsfs.Namespace('http://bsfs.ai/schema/Entity') +# generic namespaces +xsd = _bsfs.Namespace('http://www.w3.org/2001/XMLSchema') + +# core bsfs namespaces bsfs = _bsfs.Namespace('http://bsfs.ai/schema', fsep='/') bsm = _bsfs.Namespace('http://bsfs.ai/schema/Meta') + +# auxiliary bsfs namespaces +bse = _bsfs.Namespace('http://bsfs.ai/schema/Entity') bst = _bsfs.Namespace('http://bsfs.ai/schema/Tag') -xsd = _bsfs.Namespace('http://www.w3.org/2001/XMLSchema') +bsg = _bsfs.Namespace('http://bsfs.ai/schema/Group') # export __all__: typing.Sequence[str] = ( 'bse', 'bsfs', + 'bsg', 'bsm', + 'bst', 'xsd', ) -- cgit v1.2.3 From df9adcb8d55300234d12f6737f9725df3bcfa4b4 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Wed, 25 Jan 2023 10:44:02 +0100 Subject: port app fixes --- tagit/utils/bsfs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'tagit/utils') diff --git a/tagit/utils/bsfs.py b/tagit/utils/bsfs.py index d80efe0..3c2b826 100644 --- a/tagit/utils/bsfs.py +++ b/tagit/utils/bsfs.py @@ -9,15 +9,18 @@ import typing # bsfs imports from bsfs import schema, Open -from bsfs.query import ast from bsfs.namespace import Namespace +from bsfs.query import ast +from bsfs.utils import URI, uuid # exports __all__: typing.Sequence[str] = ( 'Namespace', 'Open', + 'URI', 'ast', 'schema', + 'uuid' ) ## EOF ## -- cgit v1.2.3 From c6856aa6fe2ad478dd5bc6285fb2544c150b2033 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Thu, 2 Feb 2023 10:04:03 +0100 Subject: filter port --- tagit/utils/bsfs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tagit/utils') diff --git a/tagit/utils/bsfs.py b/tagit/utils/bsfs.py index 3c2b826..ab8baa5 100644 --- a/tagit/utils/bsfs.py +++ b/tagit/utils/bsfs.py @@ -10,7 +10,7 @@ import typing # bsfs imports from bsfs import schema, Open from bsfs.namespace import Namespace -from bsfs.query import ast +from bsfs.query import ast, matcher from bsfs.utils import URI, uuid # exports -- cgit v1.2.3 From e4b98fb261c83588ca1151a1c3f8891965051b2f Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Fri, 3 Feb 2023 17:24:40 +0100 Subject: previews in browser --- tagit/utils/__init__.py | 1 + tagit/utils/namespaces.py | 4 +++- tagit/utils/rmatcher.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tagit/utils/rmatcher.py (limited to 'tagit/utils') diff --git a/tagit/utils/__init__.py b/tagit/utils/__init__.py index 16dcd4d..daa9eab 100644 --- a/tagit/utils/__init__.py +++ b/tagit/utils/__init__.py @@ -10,6 +10,7 @@ import typing # inner-module imports from . import bsfs from . import namespaces as ns +from . import rmatcher from . import time as ttime from .frame import Frame from .shared import * # FIXME: port properly diff --git a/tagit/utils/namespaces.py b/tagit/utils/namespaces.py index 5446922..dbdb853 100644 --- a/tagit/utils/namespaces.py +++ b/tagit/utils/namespaces.py @@ -19,8 +19,9 @@ bsm = _bsfs.Namespace('http://bsfs.ai/schema/Meta') # auxiliary bsfs namespaces bse = _bsfs.Namespace('http://bsfs.ai/schema/Entity') -bst = _bsfs.Namespace('http://bsfs.ai/schema/Tag') bsg = _bsfs.Namespace('http://bsfs.ai/schema/Group') +bsp = _bsfs.Namespace('http://bsfs.ai/schema/Preview') +bst = _bsfs.Namespace('http://bsfs.ai/schema/Tag') # export __all__: typing.Sequence[str] = ( @@ -28,6 +29,7 @@ __all__: typing.Sequence[str] = ( 'bsfs', 'bsg', 'bsm', + 'bsp', 'bst', 'xsd', ) diff --git a/tagit/utils/rmatcher.py b/tagit/utils/rmatcher.py new file mode 100644 index 0000000..b5bb802 --- /dev/null +++ b/tagit/utils/rmatcher.py @@ -0,0 +1,53 @@ +""" + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# exports +__all__ = ( + 'by_area', + 'by_area_min', + ) + + +## code ## + +def by_area(target, candidates): + """Pick the item from *candidates* whose area is most similar to *target*.""" + target_area = target[0] * target[1] + scores = [ + (key, abs(target_area - res[0] * res[1])) + for key, res in candidates + ] + best_key, best_score = min(scores, key=lambda key_score: key_score[1]) + return best_key + + +def by_area_min(target, candidates): + """Pick the item from *candidates* whose area is at least that of *target*.""" + # rank the candidates by area difference + # a positive score means that the candidate is larger than the target. + target_area = target[0] * target[1] + scores = [(key, res[0] * res[1] - target_area) for key, res in candidates] + + # identify the two items with + # a) the smallest positive score (kmin), or + # b) the largest negative score (kmax) + kmin, kmax = None, None + cmin, cmax = float('inf'), float('-inf') + for key, score in scores: + if score >= 0 and score < cmin: + kmin, cmin = key, score + elif score < 0 and score > cmax: + kmax, cmax = key, score + + # prefer positive over negative scores + if cmin < float('inf'): + return kmin + if cmax > float('-inf'): + return kmax + # no viable resolution found + raise IndexError('list contains no valid element') + +## EOF ## -- cgit v1.2.3 From 141cfeade2750e0255ca010079421efce4abeca2 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Sat, 4 Mar 2023 14:16:00 +0100 Subject: namespace updates --- tagit/utils/namespaces.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'tagit/utils') diff --git a/tagit/utils/namespaces.py b/tagit/utils/namespaces.py index dbdb853..3fb889c 100644 --- a/tagit/utils/namespaces.py +++ b/tagit/utils/namespaces.py @@ -11,29 +11,31 @@ import typing from . import bsfs as _bsfs # generic namespaces -xsd = _bsfs.Namespace('http://www.w3.org/2001/XMLSchema') +xsd = _bsfs.Namespace('http://www.w3.org/2001/XMLSchema')() # core bsfs namespaces -bsfs = _bsfs.Namespace('http://bsfs.ai/schema', fsep='/') -bsm = _bsfs.Namespace('http://bsfs.ai/schema/Meta') +bsfs = _bsfs.Namespace('https://schema.bsfs.io/core') +bsie = _bsfs.Namespace('https://schema.bsfs.io/ie') # auxiliary bsfs namespaces -bse = _bsfs.Namespace('http://bsfs.ai/schema/Entity') -bsg = _bsfs.Namespace('http://bsfs.ai/schema/Group') -bsp = _bsfs.Namespace('http://bsfs.ai/schema/Preview') -bst = _bsfs.Namespace('http://bsfs.ai/schema/Tag') +bse = bsn.Entity() +bsg = bsn.Group() +bsl = bsfs.Literal +bsn = bsie.Node +bsp = bsn.Preview() +bst = bsn.Tag() # export __all__: typing.Sequence[str] = ( 'bse', 'bsfs', 'bsg', - 'bsm', + 'bsie', + 'bsl', + 'bsn', 'bsp', 'bst', 'xsd', ) ## EOF ## - - -- cgit v1.2.3 From b5746fd9d49ade49ea77f10010bbabe8da8656d0 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Sat, 4 Mar 2023 15:24:18 +0100 Subject: minor fixes --- tagit/utils/namespaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tagit/utils') diff --git a/tagit/utils/namespaces.py b/tagit/utils/namespaces.py index 3fb889c..a17a927 100644 --- a/tagit/utils/namespaces.py +++ b/tagit/utils/namespaces.py @@ -18,10 +18,10 @@ bsfs = _bsfs.Namespace('https://schema.bsfs.io/core') bsie = _bsfs.Namespace('https://schema.bsfs.io/ie') # auxiliary bsfs namespaces +bsn = bsie.Node bse = bsn.Entity() bsg = bsn.Group() bsl = bsfs.Literal -bsn = bsie.Node bsp = bsn.Preview() bst = bsn.Tag() -- cgit v1.2.3