New upstream version 0.27
Stuart Prescott
1 year, 8 months ago
31 | 31 | too-many-public-methods, |
32 | 32 | too-many-return-statements, |
33 | 33 | too-many-statements, |
34 | use-sequence-for-iteration, | |
34 | 35 | |
35 | 36 | [BASIC] |
36 | 37 | no-docstring-rgx = .* |
0 | # Copyright © 2012-2018 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2021 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
33 | 33 | .PHONY: install |
34 | 34 | install: i18nspector |
35 | 35 | # executable: |
36 | $(INSTALL) -d -m755 $(DESTDIR)$(bindir) | |
36 | $(INSTALL) -d $(DESTDIR)$(bindir) | |
37 | 37 | python_exe=$$($(PYTHON) -c 'import sys; print(sys.executable)') && \ |
38 | 38 | sed \ |
39 | 39 | -e "1 s@^#!.*@#!$$python_exe@" \ |
7 | 7 | # https://www.boost.org/doc/libs/1_68_0/libs/format/doc/format.html |
8 | 8 | |
9 | 9 | c = %d |
10 | # http://man7.org/linux/man-pages/man3/printf.3.html | |
10 | # https://man7.org/linux/man-pages/man3/printf.3.html | |
11 | 11 | |
12 | 12 | csharp = {0} |
13 | 13 | # https://docs.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting |
0 | Copyright © 2012-2020 Jakub Wilk <jwilk@jwilk.net> | |
0 | Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | |
2 | 2 | Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | of this software and associated documentation files (the “Software”), to deal |
10 | 10 | |
11 | 11 | The following software is needed to run i18nspector: |
12 | 12 | |
13 | * Python ≥ 3.4; | |
13 | * Python ≥ 3.6; | |
14 | 14 | |
15 | 15 | * polib_ ≥ 1.0.0, a gettext catalogs manipulation library; |
16 | 16 | |
38 | 38 | .. _RPLY: |
39 | 39 | https://pypi.org/project/rply/ |
40 | 40 | .. _docutils: |
41 | http://docutils.sourceforge.net/ | |
41 | https://docutils.sourceforge.io/ | |
42 | 42 | |
43 | 43 | .. vim:ft=rst ts=3 sts=3 sw=3 |
0 | i18nspector (0.27) unstable; urgency=low | |
1 | ||
2 | * Recognize the “markdown-text” message flag. | |
3 | Thanks to intrigeri for the bug report. | |
4 | https://github.com/jwilk/i18nspector/issues/11 | |
5 | * Drop support for Python < 3.6. | |
6 | * Make “-j auto” take CPU affinity into account. | |
7 | * Stop using deprecated abc.abstractproperty(). | |
8 | * Improve documentation: | |
9 | + Update Docutils homepage URL. | |
10 | ||
11 | -- Jakub Wilk <jwilk@jwilk.net> Wed, 22 Jun 2022 22:09:41 +0200 | |
12 | ||
0 | 13 | i18nspector (0.26) unstable; urgency=low |
1 | 14 | |
2 | 15 | * Summary of tag changes: |
0 | 0 | .\" Man page generated from reStructuredText. |
1 | 1 | . |
2 | .TH I18NSPECTOR 1 "2020-09-26" "i18nspector 0.26" "" | |
2 | .TH I18NSPECTOR 1 "2022-06-22" "i18nspector 0.27" "" | |
3 | 3 | .SH NAME |
4 | 4 | i18nspector \- checking tool for gettext POT, PO and MO files |
5 | 5 | . |
6 | 6 | ---------------------------------------------- |
7 | 7 | |
8 | 8 | :manual section: 1 |
9 | :version: i18nspector 0.26 | |
9 | :version: i18nspector 0.27 | |
10 | 10 | :date: |date| |
11 | 11 | |
12 | 12 | Synopsis |
0 | 0 | Add more ``Plural-Forms``. Document how the current ``Plural-Forms`` were |
1 | 1 | obtained. |
2 | 2 | |
3 | * http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html?id=l10n/pluralforms | |
3 | * https://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html?id=l10n/pluralforms | |
4 | 4 | * https://unicode.org/cldr/trac/browser/trunk/common/supplemental/plurals.xml |
5 | 5 | |
6 | 6 | Complain about incorrect locale codes: |
190 | 190 | SHIFT_JIS and JOHAB encodings are broken in Python; or at least they |
191 | 191 | are not compatible with glibc. Implement a work-around. Test-case:: |
192 | 192 | |
193 | assert b'\\'.decode('SHIFT_JIS') == '\N{YEN SIGN}' | |
194 | assert b'\\'.decode('JOHAB') == '\N{WON SIGN}' | |
193 | assert b'\\'.decode('SHIFT_JIS') == '\N{YEN SIGN}' | |
194 | assert b'\\'.decode('JOHAB') == '\N{WON SIGN}' | |
195 | 195 | |
196 | 196 | Timezone abbreviations not only are not unique, but also can change their |
197 | 197 | meaning over time. |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | # encoding=UTF-8 |
2 | 2 | |
3 | # Copyright © 2012-2019 Jakub Wilk <jwilk@jwilk.net> | |
3 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
4 | 4 | # |
5 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
6 | 6 | # of this software and associated documentation files (the “Software”), to deal |
26 | 26 | |
27 | 27 | import os |
28 | 28 | import sys |
29 | ||
30 | # pylint: disable=consider-using-f-string | |
29 | 31 | |
30 | 32 | # ---------------------------------------- |
31 | 33 | |
57 | 59 | message = 'polib >= {ver} is required'.format(ver=version_str) |
58 | 60 | error(message) |
59 | 61 | |
60 | require_python(3, 4) | |
62 | require_python(3, 6) | |
61 | 63 | require_polib(1, 0, 0) |
62 | 64 | |
63 | 65 | # ---------------------------------------- |
1 | 1 | i18nspector's private modules |
2 | 2 | ''' |
3 | 3 | |
4 | type(...) # Python >= 3 is required | |
4 | int(0_0) # Python >= 3.6 is required |
0 | # Copyright © 2012-2018 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
130 | 130 | elif extension == '.pot': |
131 | 131 | constructor = polib.pofile |
132 | 132 | is_template = True |
133 | elif extension in ('.mo', '.gmo'): | |
133 | elif extension in {'.mo', '.gmo'}: | |
134 | 134 | constructor = polib.mofile |
135 | 135 | is_binary = True |
136 | 136 | else: |
158 | 158 | message = message[len(self.path)+1:] |
159 | 159 | match = re.match(r'^\(line ([0-9]+)\)(?:: (.+))?$', message) |
160 | 160 | if match is not None: |
161 | lineno_part = 'line {}'.format(match.group(1)) | |
161 | lineno_part = f'line {match.group(1)}' | |
162 | 162 | message = match.group(2) |
163 | 163 | if message is not None: |
164 | 164 | lineno_part += ':' |
303 | 303 | self.tag('invalid-language', orig_meta_language) |
304 | 304 | meta_language = None |
305 | 305 | if language_source_quality <= 0 and ( |
306 | '/{lang}/'.format(lang=meta_language) in self.path or | |
307 | '/{lang}/'.format(lang=str(meta_language).replace('_', '-')) in self.path | |
306 | f'/{meta_language}/' in self.path or | |
307 | f'/{meta_language}/'.replace('_', '-') in self.path | |
308 | 308 | ): |
309 | 309 | # For LibreOffice, PO basename does not designate translation |
310 | 310 | # language, but one of the path components does. |
318 | 318 | language_source = 'Language header field' |
319 | 319 | elif language != meta_language: |
320 | 320 | self.tag('language-disparity', |
321 | language, tags.safestr('({})'.format(language_source)), | |
321 | language, tags.safestr(f'({language_source})'), | |
322 | 322 | '!=', |
323 | 323 | meta_language, tags.safestr('(Language header field)') |
324 | 324 | ) |
343 | 343 | language_source = 'X-Poedit-Language header field' |
344 | 344 | elif language.language_code != poedit_language.language_code: |
345 | 345 | self.tag('language-disparity', |
346 | language, tags.safestr('({})'.format(language_source)), | |
346 | language, tags.safestr(f'({language_source})'), | |
347 | 347 | '!=', |
348 | 348 | poedit_language, tags.safestr('(X-Poedit-Language header field)') |
349 | 349 | ) |
448 | 448 | for i in range(codomain_limit): |
449 | 449 | fi = expr(i) |
450 | 450 | if fi >= n: |
451 | message = tags.safe_format('f({}) = {} >= {}'.format(i, fi, n)) | |
451 | message = tags.safe_format(f'f({i}) = {fi} >= {n}') | |
452 | 452 | if has_plurals: |
453 | 453 | self.tag('codomain-error-in-plural-forms', message) |
454 | 454 | else: |
497 | 497 | break |
498 | 498 | for rng in uncov_rngs: |
499 | 499 | rng = misc.format_range(rng, max=5) |
500 | message = tags.safestr('f(x) != {}'.format(rng)) | |
500 | message = tags.safestr(f'f(x) != {rng}') | |
501 | 501 | if has_plurals: |
502 | 502 | self.tag('codomain-error-in-plural-forms', message) |
503 | 503 | else: |
759 | 759 | unusual_chars = set(find_unusual_characters(msgstr)) |
760 | 760 | if unusual_chars: |
761 | 761 | unusual_char_names = ', '.join( |
762 | 'U+{:04X} {}'.format(ord(ch), encinfo.get_character_name(ch)) | |
762 | f'U+{ord(ch):04X} {encinfo.get_character_name(ch)}' | |
763 | 763 | for ch in sorted(unusual_chars) |
764 | 764 | ) |
765 | 765 | self.tag('unusual-character-in-header-entry', tags.safestr(unusual_char_names)) |
856 | 856 | if not uc: |
857 | 857 | continue |
858 | 858 | names = ', '.join( |
859 | 'U+{:04X} {}'.format(ord(ch), encinfo.get_character_name(ch)) | |
859 | f'U+{ord(ch):04X} {encinfo.get_character_name(ch)}' | |
860 | 860 | for ch in sorted(uc) |
861 | 861 | ) |
862 | 862 | self.tag('unusual-character-in-translation', |
935 | 935 | known_flag = True |
936 | 936 | format_flags[tp][string_format] = flag |
937 | 937 | break |
938 | elif flag == 'markdown-text': | |
939 | # supported by: | |
940 | # * po4a >= 0.58: | |
941 | # https://github.com/mquinson/po4a/commit/08f93cbe0cc0bcf1 | |
942 | # * weblate >= 4.0: | |
943 | # https://github.com/WeblateOrg/weblate/commit/8832525871c07779 | |
944 | pass | |
938 | 945 | else: |
939 | 946 | known_flag = False |
940 | 947 | if not known_flag: |
993 | 1000 | self.tag('redundant-message-flag', |
994 | 1001 | message_repr(message, template='{}:'), |
995 | 1002 | possible_format_flags[fmt], |
996 | tags.safe_format('(implied by {flag})'.format(flag=positive_format_flags[fmt])) | |
1003 | tags.safe_format(f'(implied by {positive_format_flags[fmt]})') | |
997 | 1004 | ) |
998 | 1005 | return info |
999 | 1006 | |
1004 | 1011 | except KeyError: |
1005 | 1012 | continue |
1006 | 1013 | checker.check_message(ctx, message, flags) |
1007 | if re.match(r'\Atype: Content of: (<{xmlname}>)+\Z'.format(xmlname=xml.name_re), message.comment or ''): | |
1014 | if re.match(fr'\Atype: Content of: (<{xml.name_re}>)+\Z', message.comment or ''): | |
1008 | 1015 | self._check_message_xml_format(ctx, message, flags) |
1009 | 1016 | |
1010 | 1017 | def _check_message_xml_format(self, ctx, message, flags): |
0 | # Copyright © 2014-2017 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
29 | 29 | def __init__(self, parent): |
30 | 30 | self.parent = parent |
31 | 31 | |
32 | @abc.abstractproperty | |
32 | @property | |
33 | @abc.abstractmethod | |
33 | 34 | def backend(self): |
34 | 35 | pass |
35 | 36 | |
84 | 85 | msgid_plural_fmt = msgid_fmts.get(1) |
85 | 86 | d.src_loc = 'msgid_plural' |
86 | 87 | d.src_fmt = msgid_plural_fmt |
87 | d.dst_loc = 'msgstr[{}]'.format(i) | |
88 | d.dst_loc = f'msgstr[{i}]' | |
88 | 89 | d.dst_fmt = self.check_string(ctx, message, s) |
89 | 90 | if d.dst_fmt is None: |
90 | 91 | continue |
0 | # Copyright © 2014-2017 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
51 | 51 | self.tag('c-format-string-error', |
52 | 52 | prefix, |
53 | 53 | tags.safestr(exc.message), |
54 | tags.safestr('{1}$'.format(*exc.args)), | |
54 | tags.safestr(f'{exc.args[1]}$'), | |
55 | 55 | ) |
56 | 56 | except backend.ArgumentTypeMismatch as exc: |
57 | 57 | self.tag('c-format-string-error', |
58 | 58 | prefix, |
59 | 59 | tags.safestr(exc.message), |
60 | tags.safestr('{1}$'.format(*exc.args)), | |
60 | tags.safestr(f'{exc.args[1]}$'), | |
61 | 61 | tags.safestr(', '.join(sorted(x for x in exc.args[2]))), |
62 | 62 | ) |
63 | 63 | except backend.FlagError as exc: |
109 | 109 | dst_args = dst_fmt.arguments |
110 | 110 | if len(dst_args) > len(src_args): |
111 | 111 | self.tag('c-format-string-excess-arguments', prefix, |
112 | len(dst_args), tags.safestr('({})'.format(dst_loc)), '>', | |
113 | len(src_args), tags.safestr('({})'.format(src_loc)), | |
112 | len(dst_args), tags.safestr(f'({dst_loc})'), '>', | |
113 | len(src_args), tags.safestr(f'({src_loc})'), | |
114 | 114 | ) |
115 | 115 | elif len(dst_args) < len(src_args): |
116 | 116 | if omitted_int_conv_ok: |
118 | 118 | omitted_int_conv_ok = src_fmt.get_last_integer_conversion(n=n_args_omitted) |
119 | 119 | if not omitted_int_conv_ok: |
120 | 120 | self.tag('c-format-string-missing-arguments', prefix, |
121 | len(dst_args), tags.safestr('({})'.format(dst_loc)), '<', | |
122 | len(src_args), tags.safestr('({})'.format(src_loc)), | |
121 | len(dst_args), tags.safestr(f'({dst_loc})'), '<', | |
122 | len(src_args), tags.safestr(f'({src_loc})'), | |
123 | 123 | ) |
124 | 124 | for src_arg, dst_arg in zip(src_args, dst_args): |
125 | 125 | src_arg = src_arg[0] |
126 | 126 | dst_arg = dst_arg[0] |
127 | 127 | if src_arg.type != dst_arg.type: |
128 | 128 | self.tag('c-format-string-argument-type-mismatch', prefix, |
129 | tags.safestr(dst_arg.type), tags.safestr('({})'.format(dst_loc)), '!=', | |
130 | tags.safestr(src_arg.type), tags.safestr('({})'.format(src_loc)), | |
129 | tags.safestr(dst_arg.type), tags.safestr(f'({dst_loc})'), '!=', | |
130 | tags.safestr(src_arg.type), tags.safestr(f'({src_loc})'), | |
131 | 131 | ) |
132 | 132 | |
133 | 133 | __all__ = ['Checker'] |
0 | # Copyright © 2016-2017 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2016-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
57 | 57 | dst_arg = dst_args[key][0] |
58 | 58 | if not (src_arg.types & dst_arg.types): |
59 | 59 | self.tag('python-brace-format-string-argument-type-mismatch', prefix, |
60 | tags.safestr(', '.join(dst_arg.types)), tags.safestr('({})'.format(dst_loc)), '!=', | |
61 | tags.safestr(', '.join(src_arg.types)), tags.safestr('({})'.format(src_loc)), | |
60 | tags.safestr(', '.join(dst_arg.types)), tags.safestr(f'({dst_loc})'), '!=', | |
61 | tags.safestr(', '.join(src_arg.types)), tags.safestr(f'({src_loc})'), | |
62 | 62 | ) |
63 | 63 | for key in sorted(dst_args.keys() - src_args.keys(), key=sort_key): |
64 | 64 | self.tag('python-brace-format-string-unknown-argument', prefix, key, |
0 | # Copyright © 2015 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2015-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
114 | 114 | dst_args = dst_fmt.seq_arguments |
115 | 115 | if len(dst_args) != len(src_args): |
116 | 116 | self.tag('python-format-string-argument-number-mismatch', prefix, |
117 | len(dst_args), tags.safestr('({})'.format(dst_loc)), '!=', | |
118 | len(src_args), tags.safestr('({})'.format(src_loc)), | |
117 | len(dst_args), tags.safestr(f'({dst_loc})'), '!=', | |
118 | len(src_args), tags.safestr(f'({src_loc})'), | |
119 | 119 | ) |
120 | 120 | for src_arg, dst_arg in zip(src_args, dst_args): |
121 | 121 | if src_arg.type != dst_arg.type: |
122 | 122 | self.tag('python-format-string-argument-type-mismatch', prefix, |
123 | tags.safestr(dst_arg.type), tags.safestr('({})'.format(dst_loc)), '!=', | |
124 | tags.safestr(src_arg.type), tags.safestr('({})'.format(src_loc)), | |
123 | tags.safestr(dst_arg.type), tags.safestr(f'({dst_loc})'), '!=', | |
124 | tags.safestr(src_arg.type), tags.safestr(f'({src_loc})'), | |
125 | 125 | ) |
126 | 126 | # named arguments: |
127 | 127 | src_args = src_fmt.map_arguments |
131 | 131 | dst_arg = dst_args[key][0] |
132 | 132 | if src_arg.type != dst_arg.type: |
133 | 133 | self.tag('python-format-string-argument-type-mismatch', prefix, |
134 | tags.safestr(dst_arg.type), tags.safestr('({})'.format(dst_loc)), '!=', | |
135 | tags.safestr(src_arg.type), tags.safestr('({})'.format(src_loc)), | |
134 | tags.safestr(dst_arg.type), tags.safestr(f'({dst_loc})'), '!=', | |
135 | tags.safestr(src_arg.type), tags.safestr(f'({src_loc})'), | |
136 | 136 | ) |
137 | 137 | for key in sorted(dst_args.keys() - src_args.keys()): |
138 | 138 | self.tag('python-format-string-unknown-argument', prefix, key, |
0 | # Copyright © 2012-2020 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
25 | 25 | import concurrent.futures |
26 | 26 | import functools |
27 | 27 | import io |
28 | import multiprocessing | |
29 | 28 | import os |
30 | 29 | import subprocess as ipc |
31 | 30 | import sys |
38 | 37 | from lib import tags |
39 | 38 | from lib import terminal |
40 | 39 | |
41 | __version__ = '0.26' | |
40 | __version__ = '0.27' | |
42 | 41 | |
43 | 42 | def initialize_terminal(): |
44 | 43 | if sys.stdout.isatty(): |
60 | 59 | tag = tags.get_tag(tagname) |
61 | 60 | except KeyError: |
62 | 61 | raise misc.DataIntegrityError( |
63 | 'attempted to emit an unknown tag: {tag!r}'.format(tag=tagname) | |
62 | f'attempted to emit an unknown tag: {tagname!r}' | |
64 | 63 | ) |
65 | 64 | s = tag.format(self.fake_path, *extra, color=True) |
66 | 65 | print(s) |
134 | 133 | for path in paths: |
135 | 134 | check_file(path, options=options) |
136 | 135 | else: |
137 | executor = concurrent.futures.ProcessPoolExecutor(max_workers=options.jobs) | |
138 | with executor: | |
136 | Executor = concurrent.futures.ProcessPoolExecutor | |
137 | with Executor(max_workers=options.jobs) as executor: | |
139 | 138 | check_file_opt = functools.partial(check_file_s, options=options) |
140 | 139 | for s in executor.map(check_file_opt, paths): |
141 | 140 | sys.stdout.write(s) |
142 | 141 | |
142 | def get_cpu_count(): | |
143 | try: | |
144 | sched_getaffinity = os.sched_getaffinity | |
145 | except AttributeError: | |
146 | return os.cpu_count() or 1 | |
147 | else: | |
148 | return len(sched_getaffinity(0)) | |
149 | ||
143 | 150 | def parse_jobs(s): |
144 | 151 | if s == 'auto': |
145 | try: | |
146 | return multiprocessing.cpu_count() | |
147 | except NotImplementedError: | |
148 | return 1 | |
152 | return get_cpu_count() | |
149 | 153 | n = int(s) |
150 | 154 | if n <= 0: |
151 | 155 | raise ValueError |
185 | 189 | return dist.version |
186 | 190 | |
187 | 191 | def __call__(self, parser, namespace, values, option_string=None): |
188 | print('{prog} {0}'.format(__version__, prog=parser.prog)) | |
189 | print('+ Python {0}.{1}.{2}'.format(*sys.version_info)) | |
190 | print('+ polib {0}'.format(check.polib.__version__)) | |
192 | print(f'{parser.prog} {__version__}') | |
193 | print('+ Python {0}.{1}.{2}'.format(*sys.version_info)) # pylint: disable=consider-using-f-string | |
194 | print(f'+ polib {check.polib.__version__}') | |
191 | 195 | rply_version = self._get_rply_version() |
192 | 196 | if rply_version is not None: |
193 | print('+ rply {0}'.format(rply_version)) | |
197 | print(f'+ rply {rply_version}') | |
194 | 198 | parser.exit() |
195 | 199 | |
196 | 200 | def main(): |
0 | # Copyright © 2013-2016 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2013-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
38 | 38 | # RFC 6762, §3 <https://tools.ietf.org/html/rfc6762#section-3>: |
39 | 39 | '(.+[.])local', |
40 | 40 | ] |
41 | ||
42 | _is_special = re.compile( | |
43 | '^({re})$'.format(re='|'.join(_regexps)) | |
44 | ).match | |
41 | _regexps = '|'.join(_regexps) | |
42 | _is_special = re.compile(f'^({_regexps})$').match | |
45 | 43 | |
46 | 44 | def is_special_domain(domain): |
47 | 45 | domain = domain.lower() |
0 | # Copyright © 2012-2020 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2021 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
54 | 54 | |
55 | 55 | path = os.path.join(paths.datadir, 'charmaps', encoding.upper()) |
56 | 56 | try: |
57 | file = open(path, 'rb') | |
57 | file = open(path, 'rb') # pylint: disable=consider-using-with | |
58 | 58 | except FileNotFoundError: |
59 | 59 | raise EncodingLookupError(encoding) |
60 | 60 | with file: |
0 | # Copyright © 2012-2018 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
181 | 181 | zone = tz_hint |
182 | 182 | else: |
183 | 183 | raise DateSyntaxError |
184 | s = '{} {}{}'.format(date, time, zone) | |
185 | assert len(s) == 21, 'len({!r}) != 21'.format(s) | |
184 | s = f'{date} {time}{zone}' | |
185 | assert len(s) == 21, f'len({s!r}) != 21' | |
186 | 186 | parse_date(s) # just check syntax |
187 | 187 | return s |
188 | 188 |
0 | # Copyright © 2012-2017 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
53 | 53 | def _popen(*args): |
54 | 54 | def set_lc_all_c(): |
55 | 55 | os.environ['LC_ALL'] = 'C' # no coverage |
56 | return ipc.Popen(args, | |
56 | return ipc.Popen(args, # pylint: disable=consider-using-with | |
57 | 57 | stdin=ipc.PIPE, stdout=ipc.PIPE, stderr=ipc.PIPE, |
58 | 58 | preexec_fn=set_lc_all_c, |
59 | 59 | ) |
60 | 60 | |
61 | 61 | def encode(input: str, encoding=default_encoding, errors='strict'): |
62 | 62 | if not isinstance(input, str): |
63 | raise TypeError('input must be str, not {tp}'.format(tp=type(input).__name__)) | |
63 | raise TypeError(f'input must be str, not {type(input).__name__}') | |
64 | 64 | if not isinstance(encoding, str): |
65 | raise TypeError('encoding must be str, not {tp}'.format(tp=type(encoding).__name__)) | |
65 | raise TypeError(f'encoding must be str, not {type(encoding).__name__}') | |
66 | 66 | if not isinstance(errors, str): |
67 | raise TypeError('errors must be str, not {tp}'.format(tp=type(errors).__name__)) | |
67 | raise TypeError(f'errors must be str, not {type(errors).__name__}') | |
68 | 68 | if len(input) == 0: |
69 | 69 | return b'' |
70 | 70 | if errors != 'strict': |
71 | raise NotImplementedError('error handler {e!r} is not implemented'.format(e=errors)) | |
71 | raise NotImplementedError(f'error handler {errors!r} is not implemented') | |
72 | 72 | return _encode(input, encoding=encoding) |
73 | 73 | |
74 | 74 | def _encode_dl(input: str, *, encoding): |
120 | 120 | os.strerror(errno.EILSEQ), |
121 | 121 | ) |
122 | 122 | raise OSError(rc, os.strerror(rc)) |
123 | assert inbytesleft.value == 0, '{n} bytes left'.format(n=inbytesleft.value) | |
123 | assert inbytesleft.value == 0, f'{inbytesleft.value} bytes left' | |
124 | 124 | output_len -= outbytesleft.value |
125 | 125 | return outbuf[:output_len] |
126 | 126 | finally: |
147 | 147 | |
148 | 148 | def decode(input: bytes, encoding=default_encoding, errors='strict'): |
149 | 149 | if not isinstance(input, bytes): |
150 | raise TypeError('input must be bytes, not {tp}'.format(tp=type(input).__name__)) | |
150 | raise TypeError(f'input must be bytes, not {type(input).__name__}') | |
151 | 151 | if not isinstance(encoding, str): |
152 | raise TypeError('encoding must be str, not {tp}'.format(tp=type(encoding).__name__)) | |
152 | raise TypeError(f'encoding must be str, not {type(encoding).__name__}') | |
153 | 153 | if not isinstance(errors, str): |
154 | raise TypeError('errors must be str, not {tp}'.format(tp=type(errors).__name__)) | |
154 | raise TypeError(f'errors must be str, not {type(errors).__name__}') | |
155 | 155 | if len(input) == 0: |
156 | 156 | return '' |
157 | 157 | if errors != 'strict': |
158 | raise NotImplementedError('error handler {e!r} is not implemented'.format(e=errors)) | |
158 | raise NotImplementedError(f'error handler {errors!r} is not implemented') | |
159 | 159 | return _decode(input, encoding=encoding) |
160 | 160 | |
161 | 161 | def _decode_dl(input: bytes, *, encoding): |
210 | 210 | os.strerror(errno.EILSEQ), |
211 | 211 | ) |
212 | 212 | raise OSError(rc, os.strerror(rc)) |
213 | assert inbytesleft.value == 0, '{n} bytes left'.format(n=inbytesleft.value) | |
213 | assert inbytesleft.value == 0, f'{inbytesleft.value} bytes left' | |
214 | 214 | output_len -= outbytesleft.value |
215 | 215 | assert output_len % ctypes.sizeof(ctypes.c_wchar) == 0 |
216 | 216 | unicode_output_len = output_len // ctypes.sizeof(ctypes.c_wchar) |
0 | # Copyright © 2012-2016 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
197 | 197 | return s |
198 | 198 | |
199 | 199 | def __repr__(self): |
200 | return '<Language {}>'.format(self) | |
200 | return f'<Language {self}>' | |
201 | 201 | |
202 | 202 | def _read_iso_codes(): |
203 | 203 | # ISO language/territory codes: |
236 | 236 | continue |
237 | 237 | if key.startswith('characters@'): |
238 | 238 | continue |
239 | raise misc.DataIntegrityError('unknown key: {}'.format(key)) | |
239 | raise misc.DataIntegrityError(f'unknown key: {key}') | |
240 | 240 | for name in section['names'].splitlines(): |
241 | 241 | name = _munch_language_name(name) |
242 | 242 | if name: |
339 | 339 | return |
340 | 340 | section_name = 'characters' |
341 | 341 | if modifier is not None: |
342 | section_name += '@{}'.format(modifier) | |
342 | section_name += f'@{modifier}' | |
343 | 343 | result = section.get(section_name) |
344 | 344 | if result is None: |
345 | 345 | return |
0 | # Copyright © 2012-2016 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
47 | 47 | ''' |
48 | 48 | cx = unsorted(iterable) |
49 | 49 | if cx is not None: |
50 | raise exception('{0!r} > {1!r}'.format(*cx)) | |
50 | raise exception(f'{cx[0]!r} > {cx[1]!r}') | |
51 | 51 | |
52 | 52 | def sorted_vk(d): |
53 | 53 | ''' |
80 | 80 | |
81 | 81 | @contextlib.contextmanager |
82 | 82 | def throwaway_tempdir(context): |
83 | with tempfile.TemporaryDirectory(prefix='i18nspector.{}.'.format(context)) as new_tempdir: | |
83 | with tempfile.TemporaryDirectory(prefix=f'i18nspector.{context}.') as new_tempdir: | |
84 | 84 | original_tempdir = tempfile.tempdir |
85 | 85 | try: |
86 | 86 | tempfile.tempdir = new_tempdir |
0 | # Copyright © 2013-2018 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2013-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
89 | 89 | [revision] = self._read_ints(at=4) |
90 | 90 | major_revision, minor_revision = divmod(revision, 1 << 16) |
91 | 91 | if major_revision > 1: |
92 | raise SyntaxError('unexpected major revision number: {n}'.format(n=major_revision)) | |
92 | raise SyntaxError(f'unexpected major revision number: {major_revision}') | |
93 | 93 | [n_strings] = self._read_ints(at=8) |
94 | 94 | possible_hidden_strings = False |
95 | 95 | if minor_revision > 1: |
0 | # Copyright © 2012-2019 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
23 | 23 | |
24 | 24 | import ast |
25 | 25 | import codecs |
26 | import contextlib | |
27 | 26 | import inspect |
28 | 27 | import re |
29 | 28 | |
43 | 42 | __all__ = ['install_patches'] |
44 | 43 | |
45 | 44 | def register_patch(patch): |
46 | patches.append(contextlib.contextmanager(patch)) | |
45 | patches.append(patch) | |
47 | 46 | |
48 | 47 | # polib.default_encoding |
49 | 48 | # ====================== |
86 | 85 | if line[:2] in {'', '# '} or line.isspace(): |
87 | 86 | pending_comments += [line] |
88 | 87 | else: |
89 | for comment_line in pending_comments: | |
90 | yield comment_line | |
88 | yield from pending_comments | |
91 | 89 | pending_comments = [] |
92 | 90 | yield line |
93 | 91 | empty = False |
132 | 130 | def unescape(match): |
133 | 131 | s = match.group() |
134 | 132 | s = _short_x_escape_re.sub(r'\\x0\1', s) |
135 | result = ast.literal_eval("b'{}'".format(s)) | |
133 | result = ast.literal_eval(f"b'{s}'") | |
136 | 134 | try: |
137 | 135 | return result.decode('ASCII') # pylint: disable=no-member |
138 | 136 | except UnicodeDecodeError: |
0 | # Copyright © 2014-2018 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
76 | 76 | t = ptrdiff_t | [unsigned ptrdiff_t] |
77 | 77 | = int | unsigned int |
78 | 78 | ''' |
79 | int_types = dict( | |
80 | (key, tuple(str.strip(v) for v in values.split('|'))) | |
79 | int_types = { | |
80 | key: tuple(str.strip(v) for v in values.split('|')) | |
81 | 81 | for line in int_types.strip().splitlines() |
82 | 82 | for key, values in [map(str.strip, line.split('='))] |
83 | ) | |
83 | } | |
84 | 84 | # FIXME: The printf(3) manpage says that the type for signed integer with |
85 | 85 | # “z“ size is ssize_t. |
86 | 86 | # But SUSv3 says it's a signed integer type corresponding to size_t, |
89 | 89 | # https://www.gnu.org/software/libc/manual/html_node/Integer-Conversions.html |
90 | 90 | portable_int_lengths = dict( |
91 | 91 | L='ll', |
92 | # The use of “L” with integer conversions is not documented | |
93 | # in the printf(3) manpage. https://bugs.debian.org/757151 | |
94 | 92 | q='ll', |
95 | 93 | Z='z', |
96 | 94 | ) |
253 | 251 | if conv is not arg: |
254 | 252 | return |
255 | 253 | else: |
256 | assert False, 'type(arg) == {!r}'.format(type(arg)) # no coverage | |
254 | assert False, f'type(arg) == {type(arg)!r}' # no coverage | |
257 | 255 | if conv is None: |
258 | 256 | return |
259 | 257 | if not conv.integer: |
402 | 400 | except IndexError: |
403 | 401 | raise ArgumentNumberingMixture(s) |
404 | 402 | except OverflowError as exc: |
405 | raise ArgumentRangeError(s, '{}$'.format(exc)) | |
403 | raise ArgumentRangeError(s, f'{exc}$') | |
406 | 404 | width = ... |
407 | 405 | if width is not None: |
408 | 406 | if conversion in '%n': |
427 | 425 | except IndexError: |
428 | 426 | raise ArgumentNumberingMixture(s) |
429 | 427 | except OverflowError as exc: |
430 | raise ArgumentRangeError(s, '{}$'.format(exc)) | |
428 | raise ArgumentRangeError(s, f'{exc}$') | |
431 | 429 | precision = ... |
432 | 430 | if precision is not None: |
433 | 431 | if conversion in i.int_cvt + i.float_cvt + i.str_cvt: |
463 | 461 | except IndexError: |
464 | 462 | raise ArgumentNumberingMixture(s) |
465 | 463 | except OverflowError as exc: |
466 | raise ArgumentRangeError(s, '{}$'.format(exc)) | |
464 | raise ArgumentRangeError(s, f'{exc}$') | |
467 | 465 | |
468 | 466 | # vim:ts=4 sts=4 sw=4 et |
0 | # Copyright © 2015-2018 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2015-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
235 | 235 | class Conversion(): |
236 | 236 | |
237 | 237 | def __init__(self, parent, s, *, key, flags, width, var_width, prec, var_prec, length, conv): |
238 | assert s[-1] == conv, '{0} != {1}'.format(s[-1], conv) | |
238 | assert s[-1] == conv, f'{s[-1]} != {conv}' | |
239 | 239 | i = _info |
240 | 240 | for flag, count in flags.items(): |
241 | 241 | if count != 1: |
0 | # Copyright © 2012-2020 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
47 | 47 | def __hash__(self): # pylint: disable=invalid-hash-returned |
48 | 48 | return self.value |
49 | 49 | |
50 | severities = OrderedEnum('Severity', [ | |
50 | severities = OrderedEnum('Severity', [ # pylint: disable=too-many-function-args | |
51 | 51 | 'pedantic', |
52 | 52 | 'wishlist', |
53 | 53 | 'minor', |
56 | 56 | 'serious', |
57 | 57 | ]) |
58 | 58 | |
59 | certainties = OrderedEnum('Certainty', [ | |
59 | certainties = OrderedEnum('Certainty', [ # pylint: disable=too-many-function-args | |
60 | 60 | 'wild-guess', |
61 | 61 | 'possible', |
62 | 62 | 'certain', |
164 | 164 | S = severities |
165 | 165 | c = self.certainty |
166 | 166 | C = certainties |
167 | # pylint: disable=no-member | |
167 | 168 | return { |
168 | 169 | S.pedantic: 'P', |
169 | 170 | S.wishlist: 'I', |
172 | 173 | S.important: 'WE'[c >= C.possible], |
173 | 174 | S.serious: 'E', |
174 | 175 | }[s] |
176 | # pylint: enable=no-member | |
175 | 177 | |
176 | 178 | def format(self, target, *extra, color=False): |
177 | 179 | if color: |
178 | 180 | color_on, color_off = self.get_colors() |
179 | 181 | else: |
180 | 182 | color_on = color_off = '' |
181 | s = '{prio}: {target}: {on}{tag}{off}'.format( | |
182 | prio=self.get_priority(), | |
183 | target=target, | |
184 | tag=self.name, | |
185 | on=color_on, | |
186 | off=color_off, | |
187 | ) | |
183 | s = f'{self.get_priority()}: {target}: {color_on}{self.name}{color_off}' | |
188 | 184 | if extra: |
189 | 185 | s += ' ' + ' '.join(map(_escape, extra)) |
190 | 186 | return s |
0 | # Copyright © 2014 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
58 | 58 | # pylint: disable=line-too-long |
59 | 59 | _start_char = ':A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\U00010000-\U000EFFFF' |
60 | 60 | _next_char = _start_char + '.0-9\xB7\u0300-\u036F\u203F\u2040-' |
61 | name_re = '[{0}][{1}]*'.format(_start_char, _next_char) | |
61 | name_re = f'[{_start_char}][{_next_char}]*' | |
62 | 62 | |
63 | 63 | # vim:ts=4 sts=4 sw=4 et |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | |
2 | # Copyright © 2012-2016 Jakub Wilk <jwilk@jwilk.net> | |
2 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
3 | 3 | # |
4 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | # of this software and associated documentation files (the “Software”), to deal |
25 | 25 | import sys |
26 | 26 | import json |
27 | 27 | |
28 | int(0_0) # Python >= 3.6 is required | |
29 | ||
28 | 30 | @functools.lru_cache() |
29 | 31 | def get_iso_codes_dir(): |
30 | 32 | prefix = ipc.check_output(['pkg-config', 'iso-codes', '--variable=prefix']) |
31 | 33 | prefix = prefix.decode('ASCII').strip() |
32 | return '{prefix}/share/iso-codes/json'.format(prefix=prefix) | |
34 | return f'{prefix}/share/iso-codes/json' | |
33 | 35 | |
34 | 36 | def main(): |
35 | 37 | ap = argparse.ArgumentParser() |
36 | ap.add_argument('languages', metavar='<lang>', nargs='+') | |
38 | ap.add_argument('languages', metavar='LANG', nargs='+') | |
37 | 39 | options = ap.parse_args() |
38 | 40 | languages = set(options.languages) |
39 | 41 | path = get_iso_codes_dir() + '/iso_639-2.json' |
58 | 60 | s.strip() |
59 | 61 | for s in item.get('name').split(';') |
60 | 62 | ] |
61 | print('[{}]'.format(requested)) | |
63 | print(f'[{requested}]') | |
62 | 64 | if lang != requested: |
63 | print('# XXX mapping {} => {}'.format(requested, lang)) | |
65 | print(f'# XXX mapping {requested} => {lang}') | |
64 | 66 | if len(names) == 1: |
65 | 67 | print('names =', *names) |
66 | 68 | else: |
69 | 71 | print('', name) |
70 | 72 | print() |
71 | 73 | for lang in sorted(languages): |
72 | print('# Unknown language code: {}'.format(lang)) | |
74 | print(f'# Unknown language code: {lang}') | |
73 | 75 | sys.exit(len(languages) > 0) |
74 | 76 | |
75 | 77 | if __name__ == '__main__': |
0 | 0 | #!/bin/sh |
1 | 1 | |
2 | # Copyright © 2016-2018 Jakub Wilk <jwilk@jwilk.net> | |
2 | # Copyright © 2016-2022 Jakub Wilk <jwilk@jwilk.net> | |
3 | 3 | # |
4 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | # of this software and associated documentation files (the “Software”), to deal |
35 | 35 | else |
36 | 36 | printf '%s\n' "$@" |
37 | 37 | fi | |
38 | xargs -L1 -t -I{} "$rst2xml" $options {} /dev/null | |
38 | xargs -t -I{} "$rst2xml" $options {} /dev/null | |
39 | 39 | |
40 | 40 | # vim:ts=4 sts=4 sw=4 et |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | |
2 | # Copyright © 2012-2016 Jakub Wilk <jwilk@jwilk.net> | |
2 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
3 | 3 | # |
4 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | # of this software and associated documentation files (the “Software”), to deal |
21 | 21 | |
22 | 22 | import argparse |
23 | 23 | import collections |
24 | import functools | |
24 | 25 | import itertools |
25 | 26 | import os |
26 | 27 | import sys |
27 | 28 | |
28 | 29 | import polib |
30 | ||
31 | int(0_0) # Python >= 3.6 is required | |
29 | 32 | |
30 | 33 | def init_polib(): |
31 | 34 | # don't assume UTF-8 encoding |
34 | 37 | def main(): |
35 | 38 | init_polib() |
36 | 39 | ap = argparse.ArgumentParser() |
37 | ap.add_argument('files', metavar='<file>', nargs='*') | |
40 | ap.add_argument('files', metavar='FILE', nargs='+') | |
38 | 41 | ap.add_argument('--skip-ascii', action='store_true', help='skip ASCII characters') |
39 | 42 | ap.add_argument('--stdin', action='store_true', help='read filenames from stdin') |
40 | 43 | options = ap.parse_args() |
47 | 50 | char_counter = collections.Counter() |
48 | 51 | char_files = collections.defaultdict(set) |
49 | 52 | n_files = 0 |
53 | eprint = functools.partial(print, file=sys.stderr, flush=True) | |
50 | 54 | for path in files: |
51 | print(path, end=' ... ', file=sys.stderr) | |
52 | sys.stderr.flush() | |
55 | eprint(path, end=' ... ') | |
53 | 56 | try: |
54 | 57 | extension = os.path.splitext(path)[-1] |
55 | 58 | if extension == '.po': |
56 | 59 | constructor = polib.pofile |
57 | elif extension in ('.mo', '.gmo'): | |
60 | elif extension in {'.mo', '.gmo'}: | |
58 | 61 | constructor = polib.mofile |
59 | 62 | else: |
60 | 63 | raise NotImplementedError(repr(extension)) |
66 | 69 | char_files[ch].add(path) |
67 | 70 | file = None |
68 | 71 | except Exception as exc: # pylint: disable=broad-except |
69 | print('error:', exc, file=sys.stderr) | |
72 | eprint('error:', exc) | |
70 | 73 | else: |
71 | print('ok', file=sys.stderr) | |
72 | sys.stderr.flush() | |
74 | eprint('ok') | |
73 | 75 | n_files += 1 |
74 | 76 | for n, ch in sorted(((n, ch) for ch, n in char_counter.items()), reverse=True): |
75 | 77 | if options.skip_ascii and ord(ch) < 0x80: |
76 | 78 | continue |
77 | 79 | filecov = len(char_files[ch]) / n_files |
78 | print('{n:6} [{fc:6.1%}] {ch!r}'.format(n=n, fc=filecov, ch=ch)) | |
80 | print(f'{n:6} [{filecov:6.1%}] {ch!r}') | |
79 | 81 | |
80 | 82 | if __name__ == '__main__': |
81 | 83 | main() |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | |
2 | # Copyright © 2012-2016 Jakub Wilk <jwilk@jwilk.net> | |
2 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
3 | 3 | # |
4 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | # of this software and associated documentation files (the “Software”), to deal |
21 | 21 | |
22 | 22 | import argparse |
23 | 23 | import collections |
24 | import functools | |
24 | 25 | import itertools |
25 | 26 | import os |
26 | 27 | import re |
27 | 28 | import sys |
28 | 29 | |
29 | 30 | import polib |
31 | ||
32 | int(0_0) # Python >= 3.6 is required | |
30 | 33 | |
31 | 34 | def init_polib(): |
32 | 35 | # Happily decode any file, even when encoding declaration is broken or |
53 | 56 | def main(): |
54 | 57 | init_polib() |
55 | 58 | ap = argparse.ArgumentParser() |
56 | ap.add_argument('files', metavar='<file>', nargs='*') | |
59 | ap.add_argument('files', metavar='FILE', nargs='+') | |
57 | 60 | ap.add_argument('-F', '--field', help='only this field') |
58 | 61 | ap.add_argument('--insane', action='store_true', help='allow insane field names') |
59 | 62 | ap.add_argument('--extract-addresses', action='store_true') |
72 | 75 | files, |
73 | 76 | (l.rstrip() for l in sys.stdin) |
74 | 77 | ) |
78 | eprint = functools.partial(print, file=sys.stderr, flush=True) | |
75 | 79 | for path in files: |
76 | print(path, end=' ... ', file=sys.stderr) | |
77 | sys.stderr.flush() | |
80 | eprint(path, end=' ... ') | |
78 | 81 | try: |
79 | 82 | extension = os.path.splitext(path)[-1] |
80 | 83 | if extension == '.po': |
81 | 84 | constructor = polib.pofile |
82 | elif extension in ('.mo', '.gmo'): | |
85 | elif extension in {'.mo', '.gmo'}: | |
83 | 86 | constructor = polib.mofile |
84 | 87 | else: |
85 | 88 | raise NotImplementedError(repr(extension)) |
89 | 92 | if msg.msgstr_plural: |
90 | 93 | break |
91 | 94 | else: |
92 | print('skip', file=sys.stderr) | |
93 | sys.stderr.flush() | |
95 | eprint('skip') | |
94 | 96 | continue |
95 | 97 | for k, v in file.metadata.items(): |
96 | 98 | if (not is_sane_field_name(k)) and (not options.insane): |
106 | 108 | test_cases[k, v] = path |
107 | 109 | file = None |
108 | 110 | except Exception as exc: # pylint: disable=broad-except |
109 | print('error:', exc, file=sys.stderr) | |
111 | eprint('error:', exc) | |
110 | 112 | else: |
111 | print('ok', file=sys.stderr) | |
112 | sys.stderr.flush() | |
113 | eprint('ok') | |
113 | 114 | for key, values in sorted(metadata.items()): |
114 | print('{key!r}:'.format(key=key)) | |
115 | print(f'{key!r}:') | |
115 | 116 | for value, n in values.most_common(): |
116 | 117 | if options.all_test_cases: |
117 | print(' {n:6} {value!r}'.format(n=n, value=value)) | |
118 | print(f' {n:6} {value!r}') | |
118 | 119 | for path in sorted(test_cases[key, value]): |
119 | print(' + {path!r}'.format(path=path)) | |
120 | print(f' + {path!r}') | |
120 | 121 | else: |
121 | 122 | path = test_cases[key, value] |
122 | print(' {n:6} {value!r}; test-case: {path!r}'.format(n=n, value=value, path=path)) | |
123 | print(f' {n:6} {value!r}; test-case: {path!r}') | |
123 | 124 | |
124 | 125 | if __name__ == '__main__': |
125 | 126 | main() |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | |
2 | # Copyright © 2012-2020 Jakub Wilk <jwilk@jwilk.net> | |
2 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
3 | 3 | # |
4 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | # of this software and associated documentation files (the “Software”), to deal |
33 | 33 | import apt |
34 | 34 | import apt_pkg |
35 | 35 | |
36 | int(0_0) # Python >= 3.6 is required | |
37 | ||
36 | 38 | def is_po_path(path): |
37 | 39 | return path.endswith(('.po', '.pot')) |
38 | 40 | |
43 | 45 | self._progress = apt.progress.text.AcquireProgress() |
44 | 46 | self._acquire = apt_pkg.Acquire(self._progress) |
45 | 47 | self._files = [] |
46 | self._tmpdir = tempfile.TemporaryDirectory(prefix='i18nspector.private.') | |
48 | self._tmpdir = tempfile.TemporaryDirectory(prefix='i18nspector.private.') # pylint: disable=consider-using-with | |
47 | 49 | |
48 | 50 | def add(self, url, name, urlbase=None): |
49 | 51 | if urlbase is None: |
80 | 82 | if _package_name_re.match(pkg): |
81 | 83 | pass |
82 | 84 | else: |
83 | raise RuntimeError('invalid package name: {pkg!r}'.format(pkg=pkg)) | |
85 | raise RuntimeError(f'invalid package name: {pkg!r}') | |
84 | 86 | |
85 | 87 | @contextlib.contextmanager |
86 | 88 | def chdir(new_cwd): |
98 | 100 | def main(): |
99 | 101 | ap = argparse.ArgumentParser() |
100 | 102 | ap.add_argument('--mirror', metavar='URL', default=default_mirror, |
101 | help='Debian mirror to use (default: {mirror})'.format(mirror=default_mirror) | |
103 | help=f'Debian mirror to use (default: {default_mirror})' | |
102 | 104 | ) |
103 | 105 | ap.add_argument('--contents-mirror', metavar='URL', default=None, |
104 | 106 | help='Debian mirror of Contents-* files to use (default: same as --mirror)' |
105 | 107 | ) |
106 | 108 | ap.add_argument('--distribution', metavar='DIST', default=default_dist, |
107 | help='Debian distribution to use (default: {dist})'.format(dist=default_dist) | |
109 | help=f'Debian distribution to use (default: {default_dist})' | |
108 | 110 | ) |
109 | 111 | ap.add_argument('--areas', metavar='AREA[,AREA...]', default=default_areas, |
110 | help='archive areas to use (default: {areas})'.format(areas=default_areas) | |
112 | help=f'archive areas to use (default: {default_areas})' | |
111 | 113 | ) |
112 | 114 | ap.add_argument('--output', metavar='TARFILE', required=True, |
113 | 115 | help='output tar file' |
119 | 121 | if options.output.endswith('.' + ext): |
120 | 122 | tarmode += ext |
121 | 123 | break |
122 | tar = tarfile.open(output, mode=tarmode) | |
124 | tar = tarfile.open(output, mode=tarmode) # pylint: disable=consider-using-with | |
123 | 125 | mirror = options.mirror |
124 | 126 | cnts_mirror = options.contents_mirror |
125 | 127 | if cnts_mirror is None: |
133 | 135 | tarinfo.mode |= 0o644 |
134 | 136 | if tarinfo.mode & 0o100: |
135 | 137 | tarinfo.mode |= 0o111 |
136 | prefix = 'po-corpus-{dist}/'.format(dist=dist) | |
138 | prefix = f'po-corpus-{dist}/' | |
137 | 139 | tarinfo.name = prefix + tarinfo.name |
138 | 140 | if tarinfo.linkname: |
139 | 141 | tarinfo.linkname = prefix + tarinfo.linkname |
140 | 142 | return tarinfo |
141 | 143 | subprocess.check_call(['dpkg-source', '--version'], stdout=subprocess.DEVNULL) |
142 | 144 | for area in areas: |
143 | urlbase = '{mirror}/dists/{dist}/{area}/'.format(mirror=mirror, dist=dist, area=area) | |
144 | cnts_urlbase = '{mirror}/dists/{dist}/{area}/'.format(mirror=cnts_mirror, dist=dist, area=area) | |
145 | urlbase = f'{mirror}/dists/{dist}/{area}/' | |
146 | cnts_urlbase = f'{cnts_mirror}/dists/{dist}/{area}/' | |
145 | 147 | with Fetcher(urlbase=urlbase) as fetcher: |
146 | 148 | fetcher.add('Contents-source.gz', 'Contents.gz', urlbase=cnts_urlbase) |
147 | 149 | fetcher.add('source/Sources.xz', 'Sources.xz') |
152 | 154 | try: |
153 | 155 | path, packages = line.rsplit('\t', 1) |
154 | 156 | except ValueError: |
155 | raise RuntimeError('malformed line: {!r}'.format(line)) | |
157 | raise RuntimeError(f'malformed line: {line!r}') | |
156 | 158 | if is_po_path(path): |
157 | 159 | packages = packages.rstrip('\n').split(',') |
158 | 160 | for pkg in packages: |
173 | 175 | name not in names |
174 | 176 | ) |
175 | 177 | if not ok: |
176 | raise RuntimeError('Package {pkg}: bad Files line: {line!r}'.format(pkg=pkg, line=line)) | |
178 | raise RuntimeError(f'Package {pkg}: bad Files line: {line!r}') | |
177 | 179 | names.add(name) |
178 | 180 | if name.endswith('.dsc'): |
179 | 181 | if dscname is not None: |
180 | raise RuntimeError('Package {pkg}: duplicate .dsc file'.format(pkg=pkg)) | |
182 | raise RuntimeError(f'Package {pkg}: duplicate .dsc file') | |
181 | 183 | dscname = name |
182 | 184 | if dscname is None: |
183 | raise RuntimeError('Package {pkg}: missing .dsc file'.format(pkg=pkg)) | |
185 | raise RuntimeError(f'Package {pkg}: missing .dsc file') | |
184 | 186 | names = list(names) |
185 | 187 | names.sort(key=dscname.__eq__) |
186 | 188 | pkgdir = para['Directory'] |
187 | pkgurlbase = '{mirror}/{dir}/'.format(mirror=mirror, dir=pkgdir) | |
189 | pkgurlbase = f'{mirror}/{pkgdir}/' | |
188 | 190 | with Fetcher(urlbase=pkgurlbase) as pkgfetcher: |
189 | 191 | for name in names: |
190 | 192 | pkgfetcher.add(name, name) |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | |
2 | # Copyright © 2012-2017 Jakub Wilk <jwilk@jwilk.net> | |
2 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
3 | 3 | # |
4 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | # of this software and associated documentation files (the “Software”), to deal |
28 | 28 | from lib import gettext |
29 | 29 | from lib import ling |
30 | 30 | |
31 | int(0_0) # Python >= 3.6 is required | |
32 | ||
31 | 33 | def strip_plural_forms(s): |
32 | 34 | # Remove superfluous parentheses around the plural formula: |
33 | 35 | return re.sub(r'\bplural=\((.*?)\);$', r'plural=\1;', s) |
47 | 49 | gettext_plural_forms = strip_plural_forms(gettext_plural_forms) |
48 | 50 | our_plural_forms = language.get_plural_forms() or [] |
49 | 51 | if gettext_plural_forms not in our_plural_forms: |
50 | print('[{}]'.format(language)) | |
52 | print(f'[{language}]') | |
51 | 53 | if our_plural_forms: |
52 | 54 | if len(our_plural_forms) == 1: |
53 | 55 | print('# plural-forms =', *our_plural_forms) |
60 | 62 | else: |
61 | 63 | okay.add(str(language)) |
62 | 64 | n_okay = len(okay) |
63 | print('# No plural-forms changes required for {} languages'.format(n_okay)) | |
65 | print(f'# No plural-forms changes required for {n_okay} languages') | |
64 | 66 | print() |
65 | 67 | |
66 | 68 | blacklist = { |
92 | 94 | if our_cc is not None: |
93 | 95 | okay.add(language) |
94 | 96 | continue |
95 | print('[{}]'.format(language)) | |
97 | print(f'[{language}]') | |
96 | 98 | if our_cc is not None: |
97 | 99 | print('# WAS: principal-territory =', our_cc) |
98 | 100 | if ling.lookup_territory_code(gettext_cc) is None: |
109 | 111 | if cc is None: |
110 | 112 | continue |
111 | 113 | if language not in data: |
112 | print('# WAS: [{}]'.format(language)) | |
114 | print(f'# WAS: [{language}]') | |
113 | 115 | print('# WAS: principal-territory =', cc) |
114 | 116 | print() |
115 | 117 | n_okay = len(okay) |
116 | print('# No principal-territory changes required for {} languages'.format(n_okay)) | |
118 | print(f'# No principal-territory changes required for {n_okay} languages') | |
117 | 119 | print() |
118 | 120 | |
119 | 121 | def do_string_formats(): |
136 | 138 | print('[formats]') |
137 | 139 | for fmt in difference: |
138 | 140 | if fmt in our_formats: |
139 | print('# MISSING: {fmt} = ...'.format(fmt=fmt)) | |
141 | print(f'# MISSING: {fmt} = ...') | |
140 | 142 | else: |
141 | print('{fmt} = # TODO'.format(fmt=fmt)) | |
143 | print(f'{fmt} = # TODO') | |
142 | 144 | n_okay = len(our_formats - difference) |
143 | print('# No changes required for {} string formats'.format(n_okay)) | |
145 | print(f'# No changes required for {n_okay} string formats') | |
144 | 146 | print() |
145 | 147 | |
146 | 148 | def main(): |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | |
2 | # Copyright © 2012-2020 Jakub Wilk <jwilk@jwilk.net> | |
2 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
3 | 3 | # |
4 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | # of this software and associated documentation files (the “Software”), to deal |
27 | 27 | |
28 | 28 | from lib import tags as tagmod |
29 | 29 | |
30 | int(0_0) # Python >= 3.6 is required | |
31 | ||
30 | 32 | def output_tags_rst(tags): |
31 | 33 | print('''\ |
32 | 34 | .. This file has been generated automatically by private/tags-as-rst. |
47 | 49 | print(' |', ref) |
48 | 50 | print() |
49 | 51 | print('Severity, certainty:', end='\n\n') |
50 | print(' {}, {}'.format(tag.severity.name, tag.certainty.name)) | |
52 | print(f' {tag.severity.name}, {tag.certainty.name}') | |
51 | 53 | print() |
52 | 54 | |
53 | 55 | def main(): |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | |
2 | # Copyright © 2013 Jakub Wilk <jwilk@jwilk.net> | |
2 | # Copyright © 2013-2022 Jakub Wilk <jwilk@jwilk.net> | |
3 | 3 | # |
4 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | # of this software and associated documentation files (the “Software”), to deal |
22 | 22 | import os |
23 | 23 | import subprocess as ipc |
24 | 24 | |
25 | int(0_0) # Python >= 3.6 is required | |
26 | ||
25 | 27 | def main(): |
26 | test_dir = '{here}/../tests/blackbox_tests/'.format( | |
27 | here=os.path.dirname(__file__), | |
28 | ) | |
28 | here = os.path.dirname(__file__) | |
29 | test_dir = f'{here}/../tests/blackbox_tests/' | |
29 | 30 | os.chdir(test_dir) |
30 | 31 | for generator in os.listdir('.'): |
31 | 32 | if not generator.endswith('.gen'): |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | |
2 | # Copyright © 2013-2016 Jakub Wilk <jwilk@jwilk.net> | |
2 | # Copyright © 2013-2022 Jakub Wilk <jwilk@jwilk.net> | |
3 | 3 | # |
4 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | # of this software and associated documentation files (the “Software”), to deal |
19 | 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
20 | 20 | # SOFTWARE. |
21 | 21 | |
22 | import functools | |
22 | 23 | import sys |
23 | 24 | |
24 | 25 | # pylint: disable=wrong-import-position |
29 | 30 | from lib import encodings |
30 | 31 | from lib import iconv |
31 | 32 | |
33 | int(0_0) # Python >= 3.6 is required | |
34 | ||
32 | 35 | def generate_charmap(encoding): |
33 | 36 | encoding = encoding.upper() |
34 | print(encoding, '...', end=' ') | |
35 | sys.stdout.flush() | |
36 | path = '{base}/data/charmaps/{enc}'.format(base=basedir, enc=encoding) | |
37 | eprint = functools.partial(print, file=sys.stderr, flush=True) | |
38 | eprint(encoding, '...', end=' ') | |
39 | path = f'{basedir}/data/charmaps/{encoding}' | |
37 | 40 | n = 0 |
38 | 41 | us = [] |
39 | 42 | for b in range(0x100): |
47 | 50 | assert len(u) == 1 |
48 | 51 | us += [u] |
49 | 52 | if n <= 128: |
50 | print('SKIP (not 8-bit)') | |
53 | eprint('SKIP (not 8-bit)') | |
51 | 54 | return |
52 | 55 | assert len(us) == 0x100 |
53 | 56 | with open(path, 'wb') as file: |
54 | 57 | us = ''.join(us) |
55 | 58 | assert len(us) == 0x100 |
56 | 59 | file.write(us.encode('UTF-8')) |
57 | print('ok') | |
60 | eprint('ok') | |
58 | 61 | |
59 | 62 | def main(): |
60 | 63 | # pylint: disable=protected-access |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | |
2 | # Copyright © 2012-2016 Jakub Wilk <jwilk@jwilk.net> | |
2 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
3 | 3 | # |
4 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | # of this software and associated documentation files (the “Software”), to deal |
26 | 26 | import sys |
27 | 27 | import json |
28 | 28 | |
29 | int(0_0) # Python >= 3.6 is required | |
30 | ||
29 | 31 | class Panic(ValueError): |
30 | 32 | pass |
31 | 33 | |
39 | 41 | def get_iso_codes_dir(): |
40 | 42 | prefix = ipc.check_output(['pkg-config', 'iso-codes', '--variable=prefix']) |
41 | 43 | prefix = prefix.decode('ASCII').strip() |
42 | return '{prefix}/share/iso-codes/json'.format(prefix=prefix) | |
44 | return f'{prefix}/share/iso-codes/json' | |
43 | 45 | |
44 | 46 | def main(): |
45 | 47 | basedir = os.path.join( |
47 | 49 | os.pardir, |
48 | 50 | ) |
49 | 51 | path = os.path.join(basedir, 'data', 'iso-codes') |
50 | sys.stdout = open(path + '.tmp', 'wt', encoding='UTF-8') | |
51 | print('''\ | |
52 | sys.stdout = open(path + '.tmp', 'wt', encoding='UTF-8') # pylint: disable=consider-using-with | |
53 | iso_codes_version = get_iso_codes_version() | |
54 | today = datetime.date.today() | |
55 | print(f'''\ | |
52 | 56 | # This file has been generated automatically by private/update-iso-codes. |
53 | 57 | # Do not edit. |
54 | # iso-codes version: {version} | |
58 | # iso-codes version: {iso_codes_version} | |
55 | 59 | # Last update: {today} |
56 | '''.format(version=get_iso_codes_version(), today=datetime.date.today())) | |
60 | ''') | |
57 | 61 | generate_iso_639() |
58 | 62 | generate_iso_3166() |
59 | 63 | sys.stdout.close() |
77 | 81 | continue |
78 | 82 | for l in l2b, l2t: |
79 | 83 | if len(l) != 3: |
80 | raise Panic('len({!r}) != 3'.format(l)) | |
84 | raise Panic(f'len({l!r}) != 3') | |
81 | 85 | if l2b != l2t: |
82 | 86 | l2t_to_2b[l2t] = l2b |
83 | 87 | iso_639 = {} |
85 | 89 | for item in data['639-3']: |
86 | 90 | code = item['alpha_3'] |
87 | 91 | if len(code) != 3: |
88 | raise Panic('len({!r}) != 3'.format(code)) | |
92 | raise Panic(f'len({code!r}) != 3') | |
89 | 93 | code1 = item.get('alpha_2') |
90 | 94 | code2 = item.get('terminology') |
91 | 95 | if code2 is None: |
92 | 96 | # We're not interested in languages that are not in 639-2 (yet?). |
93 | 97 | continue |
94 | 98 | if code2 != code: |
95 | raise Panic('{!r} != {!r}'.format(code, code2)) | |
99 | raise Panic(f'{code!r} != {code2!r}') | |
96 | 100 | scope = item['scope'] |
97 | 101 | if scope == 'S': |
98 | 102 | # Not a real language, ignore. |
100 | 104 | elif scope in {'M', 'I'}: |
101 | 105 | pass |
102 | 106 | else: |
103 | raise Panic('unknown scope: {!r}'.format(scope)) | |
107 | raise Panic(f'unknown scope: {scope!r}') | |
104 | 108 | reference_name = item['name'] |
105 | 109 | if reference_name in iso_639: |
106 | raise Panic('duplicate reference name: {!r}'.format(reference_name)) | |
110 | raise Panic(f'duplicate reference name: {reference_name!r}') | |
107 | 111 | if code1 is not None: |
108 | 112 | if len(code1) == 2: |
109 | 113 | codelist = [code1, code] |
110 | 114 | else: |
111 | raise Panic('len({!r}) != 2'.format(code1)) | |
115 | raise Panic(f'len({code1!r}) != 2') | |
112 | 116 | else: |
113 | 117 | codelist = [code] |
114 | 118 | try: |
124 | 128 | if not aliases: |
125 | 129 | iso_639_rev[code] = '' |
126 | 130 | for alias, code in sorted(iso_639_rev.items()): |
127 | print('{} = {}'.format(alias, code).rstrip()) | |
131 | print(f'{alias} = {code}'.rstrip()) | |
128 | 132 | print() |
129 | 133 | |
130 | 134 | def generate_iso_3166(): |
138 | 142 | iso_3166.add(cc) |
139 | 143 | print('[territory-codes]') |
140 | 144 | for cc in sorted(iso_3166): |
141 | print('{} ='.format(cc)) | |
145 | print(f'{cc} =') | |
142 | 146 | print() |
143 | print('# vi''m:ft=dosini') | |
147 | print('# vim\72ft=dosini') | |
144 | 148 | |
145 | 149 | if __name__ == '__main__': |
146 | 150 | main() |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | |
2 | # Copyright © 2012-2018 Jakub Wilk <jwilk@jwilk.net> | |
2 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
3 | 3 | # |
4 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | # of this software and associated documentation files (the “Software”), to deal |
27 | 27 | from lib import tags |
28 | 28 | from tests import blackbox_tests |
29 | 29 | |
30 | int(0_0) # Python >= 3.6 is required | |
31 | ||
30 | 32 | def main(): |
31 | 33 | coverage = blackbox_tests.get_coverage() |
32 | 34 | path = os.path.join( |
33 | 35 | os.path.dirname(blackbox_tests.__file__), |
34 | 36 | 'coverage' |
35 | 37 | ) |
36 | sys.stdout = open(path + '.tmp', 'wt', encoding='ASCII') | |
38 | sys.stdout = open(path + '.tmp', 'wt', encoding='ASCII') # pylint: disable=consider-using-with | |
37 | 39 | print('Generated automatically by private/update-tag-coverage. ' |
38 | 40 | 'Do not edit.\n') |
39 | 41 | for tag in tags.iter_tags(): |
40 | 42 | check = 'X' if tag.name in coverage else ' ' |
41 | print('[{check}] {tag}'.format(check=check, tag=tag.name)) | |
43 | print(f'[{check}] {tag.name}') | |
42 | 44 | sys.stdout.close() |
43 | 45 | os.rename(path + '.tmp', path) |
44 | 46 | rc = 0 |
45 | for tag in sorted(set(coverage) - set(t.name for t in tags.iter_tags())): | |
46 | print('update-coverage: error: unknown tag {tag}'.format(tag=tag), file=sys.stderr) | |
47 | for tag in sorted(set(coverage) - {t.name for t in tags.iter_tags()}): | |
48 | print(f'update-coverage: error: unknown tag {tag}', file=sys.stderr) | |
47 | 49 | rc = 1 |
48 | 50 | sys.exit(rc) |
49 | 51 |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | |
2 | # Copyright © 2013-2016 Jakub Wilk <jwilk@jwilk.net> | |
2 | # Copyright © 2013-2022 Jakub Wilk <jwilk@jwilk.net> | |
3 | 3 | # |
4 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | # of this software and associated documentation files (the “Software”), to deal |
34 | 34 | |
35 | 35 | from lib import gettext |
36 | 36 | |
37 | int(0_0) # Python >= 3.6 is required | |
38 | ||
37 | 39 | def get_tzdata_version(): |
38 | 40 | version = pytz.OLSON_VERSION |
39 | 41 | if os.path.exists('/etc/debian_version'): |
66 | 68 | assert 3 <= len(code) <= 6 |
67 | 69 | tzdata[code].add(offset) |
68 | 70 | path = os.path.join(basedir, 'data', 'timezones') |
69 | sys.stdout = open(path + '.tmp', 'wt', encoding='ASCII') | |
70 | print('''\ | |
71 | sys.stdout = open(path + '.tmp', 'wt', encoding='ASCII') # pylint: disable=consider-using-with | |
72 | tzdata_version = get_tzdata_version() | |
73 | today = datetime.date.today() | |
74 | print(f'''\ | |
71 | 75 | # This file has been generated automatically by private/update-timezones. |
72 | 76 | # Do not edit. |
73 | # Timezone database version: {version} | |
77 | # Timezone database version: {tzdata_version} | |
74 | 78 | # Last update: {today} |
75 | '''.format( | |
76 | version=get_tzdata_version(), | |
77 | today=datetime.date.today(), | |
78 | )) | |
79 | ''') | |
79 | 80 | print('[timezones]') |
80 | 81 | for code, offsets in sorted(tzdata.items()): |
81 | print('{code} = {offsets}'.format(code=code, offsets=' '.join(sorted(offsets)))) | |
82 | offsets = ' '.join(sorted(offsets)) | |
83 | print(f'{code} = {offsets}') | |
82 | 84 | print() |
83 | print('# vi''m:ft=dosini') | |
85 | print('# vim\72ft=dosini') | |
84 | 86 | sys.stdout.close() |
85 | 87 | os.rename(path + '.tmp', path) |
86 | 88 |
0 | # Copyright © 2012-2017 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
78 | 78 | class ETag(): |
79 | 79 | |
80 | 80 | _ellipsis = '<...>' |
81 | _split = re.compile('({})'.format(re.escape(_ellipsis))).split | |
81 | _split = re.compile(f'({re.escape(_ellipsis)})').split | |
82 | 82 | |
83 | 83 | def __init__(self, code, path, rest): |
84 | self._s = s = '{code}: {path}: {rest}'.format( | |
85 | code=code, | |
86 | path=path, | |
87 | rest=rest, | |
88 | ) | |
84 | self._s = s = f'{code}: {path}: {rest}' | |
89 | 85 | self.tag = rest.split(None, 1)[0] |
90 | 86 | regexp = ''.join( |
91 | 87 | '.*' if chunk == self._ellipsis else re.escape(chunk) |
92 | 88 | for chunk in self._split(s) |
93 | 89 | ) |
94 | self._regexp = re.compile('^{}$'.format(regexp)) | |
90 | self._regexp = re.compile(f'^{regexp}$') | |
95 | 91 | |
96 | 92 | def __eq__(self, other): |
97 | 93 | if isinstance(other, str): |
108 | 104 | # ---------------------------------------- |
109 | 105 | |
110 | 106 | def _get_signal_names(): |
111 | data = dict( | |
112 | (name, getattr(signal, name)) | |
107 | data = { | |
108 | name: getattr(signal, name) | |
113 | 109 | for name in dir(signal) |
114 | 110 | if re.compile('^SIG[A-Z0-9]*$').match(name) |
115 | ) | |
111 | } | |
116 | 112 | try: |
117 | 113 | if data['SIGABRT'] == data['SIGIOT']: |
118 | 114 | del data['SIGIOT'] |
218 | 214 | commandline += options |
219 | 215 | commandline += [path] |
220 | 216 | fixed_env = dict(os.environ, PYTHONIOENCODING='UTF-8') |
221 | child = ipc.Popen(commandline, stdout=ipc.PIPE, stderr=ipc.PIPE, env=fixed_env) | |
222 | stdout, stderr = ( | |
223 | s.decode('UTF-8').splitlines() | |
224 | for s in child.communicate() | |
225 | ) | |
217 | with ipc.Popen(commandline, stdout=ipc.PIPE, stderr=ipc.PIPE, env=fixed_env) as child: | |
218 | stdout, stderr = ( | |
219 | s.decode('UTF-8').splitlines() | |
220 | for s in child.communicate() | |
221 | ) | |
226 | 222 | rc = child.poll() |
227 | 223 | assert isinstance(rc, int) |
228 | 224 | if rc == 0: |
229 | 225 | return stdout |
230 | 226 | if rc < 0: |
231 | message = ['command was interrupted by signal {sig}'.format(sig=get_signal_name(-rc))] # pylint: disable=invalid-unary-operand-type | |
232 | else: | |
233 | message = ['command exited with status {rc}'.format(rc=rc)] | |
227 | message = [f'command was interrupted by signal {get_signal_name(-rc)}'] # pylint: disable=invalid-unary-operand-type | |
228 | else: | |
229 | message = [f'command exited with status {rc}'] | |
234 | 230 | message += [''] |
235 | 231 | if stdout: |
236 | 232 | message += ['stdout:'] |
257 | 253 | __file__=prog, |
258 | 254 | ) |
259 | 255 | (sys.stdout, sys.stderr) = (io_stdout, io_stderr) |
256 | stdout = stderr = '' | |
260 | 257 | try: |
261 | 258 | try: |
262 | 259 | exec(code, gvars) # pylint: disable=exec-used |
326 | 323 | def _parse_test_headers(path): |
327 | 324 | # <path>.tags: |
328 | 325 | try: |
329 | file = open(path + '.tags', encoding='UTF-8') | |
326 | file = open(path + '.tags', encoding='UTF-8') # pylint: disable=consider-using-with | |
330 | 327 | except FileNotFoundError: |
331 | 328 | pass |
332 | 329 | else: |
334 | 331 | return _parse_test_header_file(file, path, comments_only=False) |
335 | 332 | # <path>.gen: |
336 | 333 | try: |
337 | file = open(path + '.gen', encoding='UTF-8', errors='ignore') | |
334 | file = open(path + '.gen', encoding='UTF-8', errors='ignore') # pylint: disable=consider-using-with | |
338 | 335 | except FileNotFoundError: |
339 | 336 | pass |
340 | 337 | else: |
80 | 80 | [X] non-portable-encoding |
81 | 81 | [X] os-error |
82 | 82 | [X] partially-translated-message |
83 | [X] perl-brace-format-string-error | |
84 | [X] perl-brace-format-string-missing-argument | |
85 | [X] perl-brace-format-string-unknown-argument | |
83 | 86 | [X] python-brace-format-string-argument-type-mismatch |
84 | 87 | [X] python-brace-format-string-error |
85 | 88 | [X] python-brace-format-string-missing-argument |
3 | 3 | # counter-productive. |
4 | 4 | # |
5 | 5 | # [0] https://docs.python.org/3/library/gettext.html#internationalizing-your-programs-and-modules |
6 | # [1] http://babel.pocoo.org/ | |
6 | # [1] https://babel.pocoo.org/ | |
7 | 7 | |
8 | 8 | msgid "" |
9 | 9 | msgstr "" |
0 | msgid "" | |
1 | msgstr "" | |
2 | "Project-Id-Version: Gizmo Enhancer 1.0\n" | |
3 | "Report-Msgid-Bugs-To: gizmoenhancer@jwilk.net\n" | |
4 | "POT-Creation-Date: 2012-11-01 14:42+0100\n" | |
5 | "PO-Revision-Date: 2012-11-01 14:42+0100\n" | |
6 | "Last-Translator: Jakub Wilk <jwilk@jwilk.net>\n" | |
7 | "Language-Team: Latin <la@li.org>\n" | |
8 | "Language: la\n" | |
9 | "MIME-Version: 1.0\n" | |
10 | "Content-Type: text/plain; charset=UTF-8\n" | |
11 | "Content-Transfer-Encoding: 8bit\n" | |
12 | ||
13 | #, markdown-text | |
14 | msgid "A quick brown **fox** jumps over the lazy dog." | |
15 | msgstr "Sic fugiens, **dux**, zelotypos, quam Karus haberis." |
0 | # Copyright © 2014 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
107 | 107 | continue |
108 | 108 | line = line.rstrip('\n') |
109 | 109 | if line[-1:].isspace(): |
110 | raise AssertionError('trailing whitespace at line {0}'.format(n)) | |
110 | raise AssertionError(f'trailing whitespace at line {n}') | |
111 | 111 | |
112 | 112 | # vim:ts=4 sts=4 sw=4 et |
0 | # Copyright © 2014-2016 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
59 | 59 | def test_example(self): |
60 | 60 | self.t('example') |
61 | 61 | for tld in 'com', 'net', 'org': |
62 | self.t('example.{tld}'.format(tld=tld)) | |
63 | self.t('eggs.example.{tld}'.format(tld=tld)) | |
62 | self.t(f'example.{tld}') | |
63 | self.t(f'eggs.example.{tld}') | |
64 | 64 | |
65 | 65 | class test_special_domain_emails: |
66 | 66 |
0 | # Copyright © 2012-2020 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
423 | 423 | else: |
424 | 424 | w = y - x + 1 |
425 | 425 | assert w >= 2 |
426 | s = '{x} + ({var})%{w}'.format(x=x, w=w, var=var) | |
426 | s = f'{x} + ({var})%{w}' | |
427 | 427 | self.t(s, x, y) |
428 | 428 | return s |
429 | 429 | |
432 | 432 | for x in range(4): |
433 | 433 | for y in range(x, 5): |
434 | 434 | sr = self.r(x, y, var='n/3') |
435 | s = '{l} {cmp} {r}'.format(l=sl, cmp=ccmp, r=sr) | |
435 | s = f'{sl} {ccmp} {sr}' | |
436 | 436 | vals = { |
437 | 437 | int(pycmp(i, j)) |
438 | 438 | for i in range(1, 4) |
440 | 440 | } |
441 | 441 | self.t(s, min(vals), max(vals)) |
442 | 442 | n = 42 |
443 | s = '{l} {cmp} {r}'.format(l=n, cmp=ccmp, r=n) | |
443 | s = f'{n} {ccmp} {n}' | |
444 | 444 | self.t(s, pycmp(n, n), pycmp(n, n)) |
445 | 445 | |
446 | 446 | def test_lt(self): |
471 | 471 | sl = self.r(lx, ly, var='n%3') |
472 | 472 | for rx, ry in ranges: |
473 | 473 | sr = self.r(rx, ry, var='n/3') |
474 | s = '{l} {op} {r}'.format(l=sl, op=cop, r=sr) | |
474 | s = f'{sl} {cop} {sr}' | |
475 | 475 | vals = { |
476 | 476 | int(pyop(i, j)) |
477 | 477 | for i in range(lx, ly + 1) |
558 | 558 | def test_const_cmp(self): |
559 | 559 | for op in {'!=', '==', '<', '<=', '>', '>='}: |
560 | 560 | shift = self._cmp_shift(op) |
561 | self.t('n {op} {i}'.format(op=op, i=37), 37 + shift, 1) | |
561 | self.t(f'n {op} {37}', 37 + shift, 1) | |
562 | 562 | |
563 | 563 | def test_const_cmp_overflow(self): |
564 | 564 | ops = {'!=', '==', '<', '<=', '>', '>='} |
565 | 565 | m = (1 << 32) - 1 |
566 | 566 | for op in ops: |
567 | 567 | shift = self._cmp_shift(op) |
568 | self.t('n {op} {m}'.format(op=op, m=(m - 1)), m - 1 + shift, 1) | |
568 | self.t(f'n {op} {(m - 1)}', m - 1 + shift, 1) | |
569 | 569 | if shift: |
570 | self.t('n {op} {m}'.format(op=op, m=m), None) | |
570 | self.t(f'n {op} {m}', None) | |
571 | 571 | else: |
572 | self.t('n {op} {m}'.format(op=op, m=m), m, 1) | |
573 | self.t('n {op} {m}'.format(op=op, m=(m + 1)), None) | |
574 | self.t('n {op} {m}'.format(op=op, m=(m + 42)), None) | |
572 | self.t(f'n {op} {m}', m, 1) | |
573 | self.t(f'n {op} {(m + 1)}', None) | |
574 | self.t(f'n {op} {(m + 42)}', None) | |
575 | 575 | |
576 | 576 | def test_compare(self): |
577 | 577 | self.t('n < (n % 37)', None) |
636 | 636 | |
637 | 637 | def test_nplurals_positive(self): |
638 | 638 | for n in 1, 2, 10, 42: |
639 | self.t('nplurals={}; plural=0;'.format(n), n=n) | |
639 | self.t(f'nplurals={n}; plural=0;', n=n) | |
640 | 640 | |
641 | 641 | def test_missing_trailing_semicolon(self): |
642 | 642 | self.t('nplurals=1; plural=0', n=1) |
0 | # Copyright © 2012-2017 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2021 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
342 | 342 | def test_not_found(self): |
343 | 343 | lang = L.parse_language('ry') |
344 | 344 | cc = lang.get_principal_territory_code() |
345 | assert_equal(cc, None) | |
345 | assert_is_none(cc) | |
346 | 346 | |
347 | 347 | class test_unrepresentable_characters: |
348 | 348 | |
425 | 425 | raise |
426 | 426 | assert_equal(str(lang), l) |
427 | 427 | try: |
428 | file = open('/usr/share/i18n/SUPPORTED', encoding='ASCII') | |
428 | file = open('/usr/share/i18n/SUPPORTED', encoding='ASCII') # pylint: disable=consider-using-with | |
429 | 429 | except OSError as exc: |
430 | 430 | raise nose.SkipTest(exc) |
431 | 431 | locales = set() |
0 | # Copyright © 2014-2018 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
101 | 101 | |
102 | 102 | def test_integer(self): |
103 | 103 | def t(s, tp, warn_type=None): |
104 | integer = not suffix | |
105 | self.t(s, tp + suffix, warn_type, integer) | |
104 | if s[-1] == 'n': | |
105 | tp += ' *' | |
106 | integer = False | |
107 | else: | |
108 | integer = True | |
109 | return (self.t, s, tp, warn_type, integer) | |
106 | 110 | for c in 'din': |
107 | suffix = '' | |
108 | if c == 'n': | |
109 | suffix = ' *' | |
110 | yield t, ('%hh' + c), 'signed char' | |
111 | yield t, ('%h' + c), 'short int' | |
112 | yield t, ('%' + c), 'int' | |
113 | yield t, ('%l' + c), 'long int' | |
114 | yield t, ('%ll' + c), 'long long int' | |
115 | yield t, ('%L' + c), 'long long int', M.NonPortableConversion | |
116 | yield t, ('%q' + c), 'long long int', M.NonPortableConversion | |
117 | yield t, ('%j' + c), 'intmax_t' | |
118 | yield t, ('%z' + c), 'ssize_t' | |
119 | yield t, ('%Z' + c), 'ssize_t', M.NonPortableConversion | |
120 | yield t, ('%t' + c), 'ptrdiff_t' | |
111 | yield t('%hh' + c, 'signed char') | |
112 | yield t('%h' + c, 'short int') | |
113 | yield t('%' + c, 'int') | |
114 | yield t('%l' + c, 'long int') | |
115 | yield t('%ll' + c, 'long long int') | |
116 | yield t('%L' + c, 'long long int', M.NonPortableConversion) | |
117 | yield t('%q' + c, 'long long int', M.NonPortableConversion) | |
118 | yield t('%j' + c, 'intmax_t') | |
119 | yield t('%z' + c, 'ssize_t') | |
120 | yield t('%Z' + c, 'ssize_t', M.NonPortableConversion) | |
121 | yield t('%t' + c, 'ptrdiff_t') | |
121 | 122 | for c in 'ouxX': |
122 | suffix = '' | |
123 | yield t, ('%hh' + c), 'unsigned char' | |
124 | yield t, ('%h' + c), 'unsigned short int' | |
125 | yield t, ('%' + c), 'unsigned int' | |
126 | yield t, ('%l' + c), 'unsigned long int' | |
127 | yield t, ('%ll' + c), 'unsigned long long int' | |
128 | yield t, ('%L' + c), 'unsigned long long int', M.NonPortableConversion | |
129 | yield t, ('%q' + c), 'unsigned long long int', M.NonPortableConversion | |
130 | yield t, ('%j' + c), 'uintmax_t' | |
131 | yield t, ('%z' + c), 'size_t' | |
132 | yield t, ('%Z' + c), 'size_t', M.NonPortableConversion | |
133 | yield t, ('%t' + c), '[unsigned ptrdiff_t]' | |
123 | yield t('%hh' + c, 'unsigned char') | |
124 | yield t('%h' + c, 'unsigned short int') | |
125 | yield t('%' + c, 'unsigned int') | |
126 | yield t('%l' + c, 'unsigned long int') | |
127 | yield t('%ll' + c, 'unsigned long long int') | |
128 | yield t('%L' + c, 'unsigned long long int', M.NonPortableConversion) | |
129 | yield t('%q' + c, 'unsigned long long int', M.NonPortableConversion) | |
130 | yield t('%j' + c, 'uintmax_t') | |
131 | yield t('%z' + c, 'size_t') | |
132 | yield t('%Z' + c, 'size_t', M.NonPortableConversion) | |
133 | yield t('%t' + c, '[unsigned ptrdiff_t]') | |
134 | 134 | |
135 | 135 | def test_double(self): |
136 | 136 | t = self.t |
161 | 161 | def t(s, tp): |
162 | 162 | return ( |
163 | 163 | _t, |
164 | '%<{macro}>'.format(macro=s.format(c=c, n=n)), | |
164 | f'%<{s.format(c=c, n=n)}>', | |
165 | 165 | ('u' if unsigned else '') + tp.format(n=n) |
166 | 166 | ) |
167 | 167 | # pylint: enable=undefined-loop-variable |
235 | 235 | M.FormatString('%0$d') |
236 | 236 | def fs(n): |
237 | 237 | s = ''.join( |
238 | '%{0}$d'.format(i) | |
238 | f'%{i}$d' | |
239 | 239 | for i in range(1, n + 1) |
240 | 240 | ) |
241 | 241 | return M.FormatString(s) |
333 | 333 | M.FormatString('%1' + c) |
334 | 334 | |
335 | 335 | def test_too_large(self): |
336 | fmt = M.FormatString('%{0}d'.format(M.INT_MAX)) | |
336 | fmt = M.FormatString(f'%{M.INT_MAX}d') | |
337 | 337 | assert_equal(len(fmt), 1) |
338 | 338 | assert_equal(len(fmt.arguments), 1) |
339 | 339 | with assert_raises(M.WidthRangeError): |
340 | M.FormatString('%{0}d'.format(M.INT_MAX + 1)) | |
340 | M.FormatString(f'%{M.INT_MAX + 1}d') | |
341 | 341 | |
342 | 342 | def test_variable(self): |
343 | 343 | fmt = M.FormatString('%*s') |
348 | 348 | assert_equal(a2.type, 'const char *') |
349 | 349 | |
350 | 350 | def _test_index(self, i): |
351 | fmt = M.FormatString('%2$*{0}$s'.format(i)) | |
351 | fmt = M.FormatString(f'%2$*{i}$s') | |
352 | 352 | assert_equal(len(fmt), 1) |
353 | 353 | assert_equal(len(fmt.arguments), 2) |
354 | 354 | [a1], [a2] = fmt.arguments |
368 | 368 | M.FormatString('%1$*0$s') |
369 | 369 | def fs(n): |
370 | 370 | s = ''.join( |
371 | '%{0}$d'.format(i) | |
371 | f'%{i}$d' | |
372 | 372 | for i in range(2, n) |
373 | ) + '%1$*{0}$s'.format(n) | |
373 | ) + f'%1$*{n}$s' | |
374 | 374 | return M.FormatString(s) |
375 | 375 | fmt = fs(M.NL_ARGMAX) |
376 | 376 | assert_equal(len(fmt), M.NL_ARGMAX - 1) |
421 | 421 | yield t, ('%.1' + c) |
422 | 422 | |
423 | 423 | def test_too_large(self): |
424 | fmt = M.FormatString('%.{0}f'.format(M.INT_MAX)) | |
424 | fmt = M.FormatString(f'%.{M.INT_MAX}f') | |
425 | 425 | assert_equal(len(fmt), 1) |
426 | 426 | with assert_raises(M.PrecisionRangeError): |
427 | M.FormatString('%.{0}f'.format(M.INT_MAX + 1)) | |
427 | M.FormatString(f'%.{M.INT_MAX + 1}f') | |
428 | 428 | |
429 | 429 | def test_variable(self): |
430 | 430 | fmt = M.FormatString('%.*f') |
435 | 435 | assert_equal(a2.type, 'double') |
436 | 436 | |
437 | 437 | def _test_index(self, i): |
438 | fmt = M.FormatString('%2$.*{0}$f'.format(i)) | |
438 | fmt = M.FormatString(f'%2$.*{i}$f') | |
439 | 439 | assert_equal(len(fmt), 1) |
440 | 440 | assert_equal(len(fmt.arguments), 2) |
441 | 441 | [a1], [a2] = fmt.arguments |
455 | 455 | M.FormatString('%1$.*0$f') |
456 | 456 | def fs(n): |
457 | 457 | s = ''.join( |
458 | '%{0}$d'.format(i) | |
458 | f'%{i}$d' | |
459 | 459 | for i in range(2, n) |
460 | ) + '%1$.*{0}$f'.format(n) | |
460 | ) + f'%1$.*{n}$f' | |
461 | 461 | return M.FormatString(s) |
462 | 462 | fmt = fs(M.NL_ARGMAX) |
463 | 463 | assert_equal(len(fmt), M.NL_ARGMAX - 1) |
0 | # Copyright © 2014-2016 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
187 | 187 | yield t, ('%1' + c) |
188 | 188 | |
189 | 189 | def test_too_large(self): |
190 | fmt = M.FormatString('%{0}d'.format(M.SSIZE_MAX)) | |
190 | fmt = M.FormatString(f'%{M.SSIZE_MAX}d') | |
191 | 191 | assert_equal(len(fmt), 1) |
192 | 192 | assert_equal(len(fmt.seq_arguments), 1) |
193 | 193 | assert_equal(len(fmt.map_arguments), 0) |
194 | 194 | with assert_raises(M.WidthRangeError): |
195 | M.FormatString('%{0}d'.format(M.SSIZE_MAX + 1)) | |
195 | M.FormatString(f'%{M.SSIZE_MAX + 1}d') | |
196 | 196 | |
197 | 197 | def test_variable(self): |
198 | 198 | fmt = M.FormatString('%*s') |
245 | 245 | yield t, ('%.1' + c) |
246 | 246 | |
247 | 247 | def test_too_large(self): |
248 | fmt = M.FormatString('%.{0}f'.format(M.SSIZE_MAX)) | |
248 | fmt = M.FormatString(f'%.{M.SSIZE_MAX}f') | |
249 | 249 | assert_equal(len(fmt), 1) |
250 | 250 | with assert_raises(M.PrecisionRangeError): |
251 | M.FormatString('%.{0}f'.format(M.SSIZE_MAX + 1)) | |
251 | M.FormatString(f'%.{M.SSIZE_MAX + 1}f') | |
252 | 252 | |
253 | 253 | def test_variable(self): |
254 | 254 | fmt = M.FormatString('%.*f') |
0 | # Copyright © 2012-2019 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
62 | 62 | |
63 | 63 | def ast_to_tagnames(node): |
64 | 64 | for child in ast.iter_child_nodes(node): |
65 | for t in ast_to_tagnames(child): | |
66 | yield t | |
65 | yield from ast_to_tagnames(child) | |
67 | 66 | ok = ( |
68 | 67 | isinstance(node, ast.Call) and |
69 | 68 | isinstance(node.func, ast.Attribute) and |
93 | 92 | if tag not in tagnames: |
94 | 93 | raise AssertionError( |
95 | 94 | 'tag missing in data/tags:\n\n' |
96 | '[{tag}]\n' | |
95 | f'[{tag}]\n' | |
97 | 96 | 'severity = wishlist\n' |
98 | 97 | 'certainty = wild-guess\n' |
99 | 'description = TODO'.format(tag=tag) | |
98 | 'description = TODO' | |
100 | 99 | ) |
101 | 100 | for tag in sorted(source_tagnames | tagnames): |
102 | 101 | yield test, tag |
0 | # Copyright © 2014 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
67 | 67 | self.t(s) |
68 | 68 | |
69 | 69 | class test_name_re(): |
70 | regexp = re.compile(r'\A{re}\Z'.format(re=M.name_re)) | |
70 | regexp = re.compile(fr'\A{M.name_re}\Z') | |
71 | 71 | |
72 | 72 | def test_good(self): |
73 | 73 | def t(s): |
0 | # Copyright © 2012-2019 Jakub Wilk <jwilk@jwilk.net> | |
0 | # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net> | |
1 | 1 | # |
2 | 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
3 | 3 | # of this software and associated documentation files (the “Software”), to deal |
93 | 93 | elif status == 0 and msg == '': |
94 | 94 | pass |
95 | 95 | else: |
96 | raise RuntimeError('unexpected isolated process status {}'.format(status)) | |
96 | raise RuntimeError(f'unexpected isolated process status {status}') | |
97 | 97 | |
98 | 98 | # pylint:enable=consider-using-sys-exit |
99 | 99 |