aboutsummaryrefslogtreecommitdiff
path: root/src/hydrilla/util/_util.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/hydrilla/util/_util.py')
-rw-r--r--src/hydrilla/util/_util.py162
1 files changed, 162 insertions, 0 deletions
diff --git a/src/hydrilla/util/_util.py b/src/hydrilla/util/_util.py
new file mode 100644
index 0000000..778e78f
--- /dev/null
+++ b/src/hydrilla/util/_util.py
@@ -0,0 +1,162 @@
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+# Building Hydrilla packages.
+#
+# This file is part of Hydrilla
+#
+# Copyright (C) 2021, 2022 Wojtek Kosior
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+#
+# I, Wojtek Kosior, thereby promise not to sue for violation of this
+# file's license. Although I request that you do not make use this code
+# in a proprietary program, I am not going to enforce this in court.
+
+# Enable using with Python 3.7.
+from __future__ import annotations
+
+import re
+import json
+import locale
+import gettext
+
+from pathlib import Path
+from typing import Optional, Union
+
+from jsonschema import RefResolver, Draft7Validator
+
+here = Path(__file__).resolve().parent
+
+_strip_comment_re = re.compile(r'''
+^ # match from the beginning of each line
+( # catch the part before '//' comment
+ (?: # this group matches either a string or a single out-of-string character
+ [^"/] |
+ "
+ (?: # this group matches any in-a-string character
+ [^"\\] | # match any normal character
+ \\[^u] | # match any escaped character like '\f' or '\n'
+ \\u[a-fA-F0-9]{4} # match an escape
+ )*
+ "
+ )*
+)
+# expect either end-of-line or a comment:
+# * unterminated strings will cause matching to fail
+# * bad comment (with '/' instead of '//') will be indicated by second group
+# having length 1 instead of 2 or 0
+(//?|$)
+''', re.VERBOSE)
+
+def strip_json_comments(text: str) -> str:
+ """
+ Accept JSON text with optional C++-style ('//') comments and return the text
+ with comments removed. Consecutive slashes inside strings are handled
+ properly. A spurious single slash ('/') shall generate an error. Errors in
+ JSON itself shall be ignored.
+ """
+ processed = 0
+ stripped_text = []
+ for line in text.split('\n'):
+ match = _strip_comment_re.match(line)
+
+ if match is None: # unterminated string
+ # ignore this error, let json module report it
+ stripped = line
+ elif len(match[2]) == 1:
+ raise json.JSONDecodeError(_('bad_comment'), text,
+ processed + len(match[1]))
+ else:
+ stripped = match[1]
+
+ stripped_text.append(stripped)
+ processed += len(line) + 1
+
+ return '\n'.join(stripped_text)
+
+def normalize_version(ver: list[int]) -> list[int]:
+ """Strip right-most zeroes from 'ver'. The original list is not modified."""
+ new_len = 0
+ for i, num in enumerate(ver):
+ if num != 0:
+ new_len = i + 1
+
+ return ver[:new_len]
+
+def parse_version(ver_str: str) -> list[int]:
+ """
+ Convert 'ver_str' into an array representation, e.g. for ver_str="4.6.13.0"
+ return [4, 6, 13, 0].
+ """
+ return [int(num) for num in ver_str.split('.')]
+
+def version_string(ver: list[int], rev: Optional[int]=None) -> str:
+ """
+ Produce version's string representation (optionally with revision), like:
+ 1.2.3-5
+ No version normalization is performed.
+ """
+ return '.'.join([str(n) for n in ver]) + ('' if rev is None else f'-{rev}')
+
+schemas = {}
+for path in (here.parent / 'schemas').glob('*-1.0.1.schema.json'):
+ schema = json.loads(path.read_text())
+ schemas[schema['$id']] = schema
+
+common_schema_filename = 'common_definitions-1.schema.json'
+common_schema_path = here.parent / "schemas" / common_schema_filename
+
+resolver = RefResolver(
+ base_uri=f'file://{str(common_schema_path)}',
+ referrer=f'https://hydrilla.koszko.org/{common_schema_filename}',
+ store=schemas
+)
+
+def validator_for(schema_filename: str) -> Draft7Validator:
+ """
+ Prepare a validator for one of the schemas in '../schemas'.
+
+ This function is not thread-safe.
+ """
+ return Draft7Validator(resolver.resolve(schema_filename)[1],
+ resolver=resolver)
+
+def translation(localedir: Union[Path, str], lang: Optional[str]=None) \
+ -> gettext.GNUTranslations:
+ """
+ Configure translations for domain 'hydrilla-messages' and return the object
+ that represents them.
+
+ If `lang` is set, look for translations for `lang`. Otherwise, try to
+ determine system's default language and use that.
+ """
+ # https://stackoverflow.com/questions/3425294/how-to-detect-the-os-default-language-in-python
+ # But I am not going to surrender to Microbugs' nonfree, crappy OS to test
+ # it, to the lines inside try: may fail.
+ if lang is None:
+ try:
+ from ctypes.windll import kernel32 as windll
+ lang = locale.windows_locale[windll.GetUserDefaultUILanguage()]
+ except:
+ lang = locale.getdefaultlocale()[0] or 'en_US'
+
+ localedir = Path(localedir)
+ if not (localedir / lang).is_dir():
+ lang = 'en_US'
+
+ return gettext.translation('hydrilla-messages', localedir=localedir,
+ languages=[lang])
+
+_ = translation(here.parent / 'builder' / 'locales').gettext