Project

General

Profile

Download (5.39 KB) Statistics
| Branch: | Tag: | Revision:

hydrilla-builder / src / hydrilla / util / _util.py @ c1c7f969

1 ad4331a4 Wojtek Kosior
# SPDX-License-Identifier: AGPL-3.0-or-later
2
3
# Building Hydrilla packages.
4
#
5
# This file is part of Hydrilla
6
#
7
# Copyright (C) 2021, 2022 Wojtek Kosior
8
#
9
# This program is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU Affero General Public License as
11
# published by the Free Software Foundation, either version 3 of the
12
# License, or (at your option) any later version.
13
#
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
# GNU Affero General Public License for more details.
18
#
19
# You should have received a copy of the GNU Affero General Public License
20
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
21
#
22
#
23
# I, Wojtek Kosior, thereby promise not to sue for violation of this
24
# file's license. Although I request that you do not make use this code
25
# in a proprietary program, I am not going to enforce this in court.
26
27 143aed2d Wojtek Kosior
# Enable using with Python 3.7.
28
from __future__ import annotations
29
30 ad4331a4 Wojtek Kosior
import re
31
import json
32 9e71d383 Wojtek Kosior
import locale
33
import gettext
34 ad4331a4 Wojtek Kosior
35
from pathlib import Path
36 33097569 Wojtek Kosior
from typing import Optional, Union
37 ad4331a4 Wojtek Kosior
38 456ad6c0 Wojtek Kosior
from jsonschema import RefResolver, Draft7Validator
39
40 ad4331a4 Wojtek Kosior
here = Path(__file__).resolve().parent
41
42
_strip_comment_re = re.compile(r'''
43
^ # match from the beginning of each line
44
( # catch the part before '//' comment
45
  (?: # this group matches either a string or a single out-of-string character
46
    [^"/] |
47
    "
48
    (?: # this group matches any in-a-string character
49
      [^"\\] |          # match any normal character
50
      \\[^u] |          # match any escaped character like '\f' or '\n'
51
      \\u[a-fA-F0-9]{4} # match an escape
52
    )*
53
    "
54
  )*
55
)
56
# expect either end-of-line or a comment:
57
# * unterminated strings will cause matching to fail
58
# * bad comment (with '/' instead of '//') will be indicated by second group
59
#   having length 1 instead of 2 or 0
60
(//?|$)
61
''', re.VERBOSE)
62
63
def strip_json_comments(text: str) -> str:
64
    """
65
    Accept JSON text with optional C++-style ('//') comments and return the text
66
    with comments removed. Consecutive slashes inside strings are handled
67
    properly. A spurious single slash ('/') shall generate an error. Errors in
68
    JSON itself shall be ignored.
69
    """
70
    processed = 0
71
    stripped_text = []
72
    for line in text.split('\n'):
73
        match = _strip_comment_re.match(line)
74
75
        if match is None: # unterminated string
76
            # ignore this error, let json module report it
77
            stripped = line
78
        elif len(match[2]) == 1:
79 9e71d383 Wojtek Kosior
            raise json.JSONDecodeError(_('bad_comment'), text,
80 ad4331a4 Wojtek Kosior
                                       processed + len(match[1]))
81
        else:
82
            stripped = match[1]
83
84
        stripped_text.append(stripped)
85
        processed += len(line) + 1
86
87
    return '\n'.join(stripped_text)
88
89
def normalize_version(ver: list[int]) -> list[int]:
90
    """Strip right-most zeroes from 'ver'. The original list is not modified."""
91
    new_len = 0
92
    for i, num in enumerate(ver):
93
        if num != 0:
94
            new_len = i + 1
95
96
    return ver[:new_len]
97
98
def parse_version(ver_str: str) -> list[int]:
99
    """
100
    Convert 'ver_str' into an array representation, e.g. for ver_str="4.6.13.0"
101
    return [4, 6, 13, 0].
102
    """
103
    return [int(num) for num in ver_str.split('.')]
104
105
def version_string(ver: list[int], rev: Optional[int]=None) -> str:
106
    """
107
    Produce version's string representation (optionally with revision), like:
108
        1.2.3-5
109
    No version normalization is performed.
110
    """
111
    return '.'.join([str(n) for n in ver]) + ('' if rev is None else f'-{rev}')
112
113 456ad6c0 Wojtek Kosior
schemas = {}
114
for path in (here.parent / 'schemas').glob('*-1.schema.json'):
115
    schema = json.loads(path.read_text())
116
    schemas[schema['$id']] = schema
117
118
common_schema_filename = 'common_definitions-1.schema.json'
119
common_schema_path = here.parent / "schemas" / common_schema_filename
120
121
resolver = RefResolver(
122
    base_uri=f'file://{str(common_schema_path)}',
123
    referrer=f'https://hydrilla.koszko.org/{common_schema_filename}',
124
    store=schemas
125
)
126
127
def validator_for(schema_filename: str) -> Draft7Validator:
128
    """
129
    Prepare a validator for one of the schemas in '../schemas'.
130
131
    This function is not thread-safe.
132
    """
133
    return Draft7Validator(resolver.resolve(schema_filename)[1],
134
                           resolver=resolver)
135 9e71d383 Wojtek Kosior
136 33097569 Wojtek Kosior
def translation(localedir: Union[Path, str], lang: Optional[str]=None) \
137
    -> gettext.GNUTranslations:
138 9e71d383 Wojtek Kosior
    """
139 0fe371da Wojtek Kosior
    Configure translations for domain 'hydrilla-messages' and return the object
140
    that represents them.
141 9e71d383 Wojtek Kosior
142
    If `lang` is set, look for translations for `lang`. Otherwise, try to
143
    determine system's default language and use that.
144
    """
145
    # https://stackoverflow.com/questions/3425294/how-to-detect-the-os-default-language-in-python
146
    # But I am not going to surrender to Microbugs' nonfree, crappy OS to test
147
    # it, to the lines inside try: may fail.
148 c1c7f969 Wojtek Kosior
    if lang is None:
149
        try:
150
            from ctypes.windll import kernel32 as windll
151
            lang = locale.windows_locale[windll.GetUserDefaultUILanguage()]
152
        except:
153
            lang = locale.getdefaultlocale()[0] or 'en_US'
154
155
    localedir = Path(localedir)
156
    if not (localedir / lang).is_dir():
157
        lang = 'en_US'
158 9e71d383 Wojtek Kosior
159 0fe371da Wojtek Kosior
    return gettext.translation('hydrilla-messages', localedir=localedir,
160 c1c7f969 Wojtek Kosior
                               languages=[lang])
161 9e71d383 Wojtek Kosior
162 33097569 Wojtek Kosior
_ = translation(here.parent / 'builder' / 'locales').gettext