Project

General

Profile

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

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

1
# 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
import re
28
import json
29
import locale
30
import gettext
31

    
32
from pathlib import Path
33
from typing import Optional, Union
34

    
35
from jsonschema import RefResolver, Draft7Validator
36

    
37
here = Path(__file__).resolve().parent
38

    
39
_strip_comment_re = re.compile(r'''
40
^ # match from the beginning of each line
41
( # catch the part before '//' comment
42
  (?: # this group matches either a string or a single out-of-string character
43
    [^"/] |
44
    "
45
    (?: # this group matches any in-a-string character
46
      [^"\\] |          # match any normal character
47
      \\[^u] |          # match any escaped character like '\f' or '\n'
48
      \\u[a-fA-F0-9]{4} # match an escape
49
    )*
50
    "
51
  )*
52
)
53
# expect either end-of-line or a comment:
54
# * unterminated strings will cause matching to fail
55
# * bad comment (with '/' instead of '//') will be indicated by second group
56
#   having length 1 instead of 2 or 0
57
(//?|$)
58
''', re.VERBOSE)
59

    
60
def strip_json_comments(text: str) -> str:
61
    """
62
    Accept JSON text with optional C++-style ('//') comments and return the text
63
    with comments removed. Consecutive slashes inside strings are handled
64
    properly. A spurious single slash ('/') shall generate an error. Errors in
65
    JSON itself shall be ignored.
66
    """
67
    processed = 0
68
    stripped_text = []
69
    for line in text.split('\n'):
70
        match = _strip_comment_re.match(line)
71

    
72
        if match is None: # unterminated string
73
            # ignore this error, let json module report it
74
            stripped = line
75
        elif len(match[2]) == 1:
76
            raise json.JSONDecodeError(_('bad_comment'), text,
77
                                       processed + len(match[1]))
78
        else:
79
            stripped = match[1]
80

    
81
        stripped_text.append(stripped)
82
        processed += len(line) + 1
83

    
84
    return '\n'.join(stripped_text)
85

    
86
def normalize_version(ver: list[int]) -> list[int]:
87
    """Strip right-most zeroes from 'ver'. The original list is not modified."""
88
    new_len = 0
89
    for i, num in enumerate(ver):
90
        if num != 0:
91
            new_len = i + 1
92

    
93
    return ver[:new_len]
94

    
95
def parse_version(ver_str: str) -> list[int]:
96
    """
97
    Convert 'ver_str' into an array representation, e.g. for ver_str="4.6.13.0"
98
    return [4, 6, 13, 0].
99
    """
100
    return [int(num) for num in ver_str.split('.')]
101

    
102
def version_string(ver: list[int], rev: Optional[int]=None) -> str:
103
    """
104
    Produce version's string representation (optionally with revision), like:
105
        1.2.3-5
106
    No version normalization is performed.
107
    """
108
    return '.'.join([str(n) for n in ver]) + ('' if rev is None else f'-{rev}')
109

    
110
schemas = {}
111
for path in (here.parent / 'schemas').glob('*-1.schema.json'):
112
    schema = json.loads(path.read_text())
113
    schemas[schema['$id']] = schema
114

    
115
common_schema_filename = 'common_definitions-1.schema.json'
116
common_schema_path = here.parent / "schemas" / common_schema_filename
117

    
118
resolver = RefResolver(
119
    base_uri=f'file://{str(common_schema_path)}',
120
    referrer=f'https://hydrilla.koszko.org/{common_schema_filename}',
121
    store=schemas
122
)
123

    
124
def validator_for(schema_filename: str) -> Draft7Validator:
125
    """
126
    Prepare a validator for one of the schemas in '../schemas'.
127

    
128
    This function is not thread-safe.
129
    """
130
    return Draft7Validator(resolver.resolve(schema_filename)[1],
131
                           resolver=resolver)
132

    
133
def translation(localedir: Union[Path, str], lang: Optional[str]=None) \
134
    -> gettext.GNUTranslations:
135
    """
136
    Configure translations for domain 'messages' and return the object that
137
    represents them.
138

    
139
    If `lang` is set, look for translations for `lang`. Otherwise, try to
140
    determine system's default language and use that.
141
    """
142
    # https://stackoverflow.com/questions/3425294/how-to-detect-the-os-default-language-in-python
143
    # But I am not going to surrender to Microbugs' nonfree, crappy OS to test
144
    # it, to the lines inside try: may fail.
145
    try:
146
        from ctypes.windll import kernel32 as windll
147
        lang = locale.windows_locale[windll.GetUserDefaultUILanguage()]
148
    except:
149
        lang = locale.getdefaultlocale()[0] or 'C'
150

    
151
    return gettext.translation('messages', localedir=localedir,
152
                               languages=[lang, 'en_US'])
153

    
154
_ = translation(here.parent / 'builder' / 'locales').gettext
(2-2/2)