Project

General

Profile

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

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

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
# Enable using with Python 3.7.
28
from __future__ import annotations
29

    
30
import re
31
import json
32
import locale
33
import gettext
34

    
35
from pathlib import Path
36
from typing import Optional, Union
37

    
38
from jsonschema import RefResolver, Draft7Validator
39

    
40
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
            raise json.JSONDecodeError(_('bad_comment'), text,
80
                                       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
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

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

    
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
    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

    
159
    return gettext.translation('hydrilla-messages', localedir=localedir,
160
                               languages=[lang])
161

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