Project

General

Profile

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

hydrilla-builder / src / hydrilla / util / _util.py @ 403ca642

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
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(domain: str, lang: Optional[str]=None):
134
    """
135
    Configure translation and return the object that represents it.
136

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

    
149
    return gettext.translation(
150
        domain,
151
        localedir=(here.parent / 'locales'),
152
        languages=[lang, 'en_US']
153
    )
154

    
155
_ = translation('hydrilla_builder').gettext
(2-2/2)