Codebase list i18nspector / 845551c
New upstream version 0.27 Stuart Prescott 1 year, 8 months ago
55 changed file(s) with 375 addition(s) and 319 deletion(s). Raw diff Collapse all Expand all
3131 too-many-public-methods,
3232 too-many-return-statements,
3333 too-many-statements,
34 use-sequence-for-iteration,
3435
3536 [BASIC]
3637 no-docstring-rgx = .*
0 # Copyright © 2012-2018 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2012-2021 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
3333 .PHONY: install
3434 install: i18nspector
3535 # executable:
36 $(INSTALL) -d -m755 $(DESTDIR)$(bindir)
36 $(INSTALL) -d $(DESTDIR)$(bindir)
3737 python_exe=$$($(PYTHON) -c 'import sys; print(sys.executable)') && \
3838 sed \
3939 -e "1 s@^#!.*@#!$$python_exe@" \
77 # https://www.boost.org/doc/libs/1_68_0/libs/format/doc/format.html
88
99 c = %d
10 # http://man7.org/linux/man-pages/man3/printf.3.html
10 # https://man7.org/linux/man-pages/man3/printf.3.html
1111
1212 csharp = {0}
1313 # 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>
11
22 Permission is hereby granted, free of charge, to any person obtaining a copy
33 of this software and associated documentation files (the “Software”), to deal
1010
1111 The following software is needed to run i18nspector:
1212
13 * Python ≥ 3.4;
13 * Python ≥ 3.6;
1414
1515 * polib_ ≥ 1.0.0, a gettext catalogs manipulation library;
1616
3838 .. _RPLY:
3939 https://pypi.org/project/rply/
4040 .. _docutils:
41 http://docutils.sourceforge.net/
41 https://docutils.sourceforge.io/
4242
4343 .. 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
013 i18nspector (0.26) unstable; urgency=low
114
215 * Summary of tag changes:
00 .\" Man page generated from reStructuredText.
11 .
2 .TH I18NSPECTOR 1 "2020-09-26" "i18nspector 0.26" ""
2 .TH I18NSPECTOR 1 "2022-06-22" "i18nspector 0.27" ""
33 .SH NAME
44 i18nspector \- checking tool for gettext POT, PO and MO files
55 .
66 ----------------------------------------------
77
88 :manual section: 1
9 :version: i18nspector 0.26
9 :version: i18nspector 0.27
1010 :date: |date|
1111
1212 Synopsis
00 Add more ``Plural-Forms``. Document how the current ``Plural-Forms`` were
11 obtained.
22
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
44 * https://unicode.org/cldr/trac/browser/trunk/common/supplemental/plurals.xml
55
66 Complain about incorrect locale codes:
190190 SHIFT_JIS and JOHAB encodings are broken in Python; or at least they
191191 are not compatible with glibc. Implement a work-around. Test-case::
192192
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}'
195195
196196 Timezone abbreviations not only are not unique, but also can change their
197197 meaning over time.
00 #!/usr/bin/env python3
11 # encoding=UTF-8
22
3 # Copyright © 2012-2019 Jakub Wilk <jwilk@jwilk.net>
3 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
44 #
55 # Permission is hereby granted, free of charge, to any person obtaining a copy
66 # of this software and associated documentation files (the “Software”), to deal
2626
2727 import os
2828 import sys
29
30 # pylint: disable=consider-using-f-string
2931
3032 # ----------------------------------------
3133
5759 message = 'polib >= {ver} is required'.format(ver=version_str)
5860 error(message)
5961
60 require_python(3, 4)
62 require_python(3, 6)
6163 require_polib(1, 0, 0)
6264
6365 # ----------------------------------------
11 i18nspector's private modules
22 '''
33
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>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
130130 elif extension == '.pot':
131131 constructor = polib.pofile
132132 is_template = True
133 elif extension in ('.mo', '.gmo'):
133 elif extension in {'.mo', '.gmo'}:
134134 constructor = polib.mofile
135135 is_binary = True
136136 else:
158158 message = message[len(self.path)+1:]
159159 match = re.match(r'^\(line ([0-9]+)\)(?:: (.+))?$', message)
160160 if match is not None:
161 lineno_part = 'line {}'.format(match.group(1))
161 lineno_part = f'line {match.group(1)}'
162162 message = match.group(2)
163163 if message is not None:
164164 lineno_part += ':'
303303 self.tag('invalid-language', orig_meta_language)
304304 meta_language = None
305305 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
308308 ):
309309 # For LibreOffice, PO basename does not designate translation
310310 # language, but one of the path components does.
318318 language_source = 'Language header field'
319319 elif language != meta_language:
320320 self.tag('language-disparity',
321 language, tags.safestr('({})'.format(language_source)),
321 language, tags.safestr(f'({language_source})'),
322322 '!=',
323323 meta_language, tags.safestr('(Language header field)')
324324 )
343343 language_source = 'X-Poedit-Language header field'
344344 elif language.language_code != poedit_language.language_code:
345345 self.tag('language-disparity',
346 language, tags.safestr('({})'.format(language_source)),
346 language, tags.safestr(f'({language_source})'),
347347 '!=',
348348 poedit_language, tags.safestr('(X-Poedit-Language header field)')
349349 )
448448 for i in range(codomain_limit):
449449 fi = expr(i)
450450 if fi >= n:
451 message = tags.safe_format('f({}) = {} >= {}'.format(i, fi, n))
451 message = tags.safe_format(f'f({i}) = {fi} >= {n}')
452452 if has_plurals:
453453 self.tag('codomain-error-in-plural-forms', message)
454454 else:
497497 break
498498 for rng in uncov_rngs:
499499 rng = misc.format_range(rng, max=5)
500 message = tags.safestr('f(x) != {}'.format(rng))
500 message = tags.safestr(f'f(x) != {rng}')
501501 if has_plurals:
502502 self.tag('codomain-error-in-plural-forms', message)
503503 else:
759759 unusual_chars = set(find_unusual_characters(msgstr))
760760 if unusual_chars:
761761 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)}'
763763 for ch in sorted(unusual_chars)
764764 )
765765 self.tag('unusual-character-in-header-entry', tags.safestr(unusual_char_names))
856856 if not uc:
857857 continue
858858 names = ', '.join(
859 'U+{:04X} {}'.format(ord(ch), encinfo.get_character_name(ch))
859 f'U+{ord(ch):04X} {encinfo.get_character_name(ch)}'
860860 for ch in sorted(uc)
861861 )
862862 self.tag('unusual-character-in-translation',
935935 known_flag = True
936936 format_flags[tp][string_format] = flag
937937 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
938945 else:
939946 known_flag = False
940947 if not known_flag:
9931000 self.tag('redundant-message-flag',
9941001 message_repr(message, template='{}:'),
9951002 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]})')
9971004 )
9981005 return info
9991006
10041011 except KeyError:
10051012 continue
10061013 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 ''):
10081015 self._check_message_xml_format(ctx, message, flags)
10091016
10101017 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>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
2929 def __init__(self, parent):
3030 self.parent = parent
3131
32 @abc.abstractproperty
32 @property
33 @abc.abstractmethod
3334 def backend(self):
3435 pass
3536
8485 msgid_plural_fmt = msgid_fmts.get(1)
8586 d.src_loc = 'msgid_plural'
8687 d.src_fmt = msgid_plural_fmt
87 d.dst_loc = 'msgstr[{}]'.format(i)
88 d.dst_loc = f'msgstr[{i}]'
8889 d.dst_fmt = self.check_string(ctx, message, s)
8990 if d.dst_fmt is None:
9091 continue
0 # Copyright © 2014-2017 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
5151 self.tag('c-format-string-error',
5252 prefix,
5353 tags.safestr(exc.message),
54 tags.safestr('{1}$'.format(*exc.args)),
54 tags.safestr(f'{exc.args[1]}$'),
5555 )
5656 except backend.ArgumentTypeMismatch as exc:
5757 self.tag('c-format-string-error',
5858 prefix,
5959 tags.safestr(exc.message),
60 tags.safestr('{1}$'.format(*exc.args)),
60 tags.safestr(f'{exc.args[1]}$'),
6161 tags.safestr(', '.join(sorted(x for x in exc.args[2]))),
6262 )
6363 except backend.FlagError as exc:
109109 dst_args = dst_fmt.arguments
110110 if len(dst_args) > len(src_args):
111111 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})'),
114114 )
115115 elif len(dst_args) < len(src_args):
116116 if omitted_int_conv_ok:
118118 omitted_int_conv_ok = src_fmt.get_last_integer_conversion(n=n_args_omitted)
119119 if not omitted_int_conv_ok:
120120 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})'),
123123 )
124124 for src_arg, dst_arg in zip(src_args, dst_args):
125125 src_arg = src_arg[0]
126126 dst_arg = dst_arg[0]
127127 if src_arg.type != dst_arg.type:
128128 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})'),
131131 )
132132
133133 __all__ = ['Checker']
0 # Copyright © 2016-2017 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2016-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
5757 dst_arg = dst_args[key][0]
5858 if not (src_arg.types & dst_arg.types):
5959 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})'),
6262 )
6363 for key in sorted(dst_args.keys() - src_args.keys(), key=sort_key):
6464 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>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
114114 dst_args = dst_fmt.seq_arguments
115115 if len(dst_args) != len(src_args):
116116 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})'),
119119 )
120120 for src_arg, dst_arg in zip(src_args, dst_args):
121121 if src_arg.type != dst_arg.type:
122122 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})'),
125125 )
126126 # named arguments:
127127 src_args = src_fmt.map_arguments
131131 dst_arg = dst_args[key][0]
132132 if src_arg.type != dst_arg.type:
133133 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})'),
136136 )
137137 for key in sorted(dst_args.keys() - src_args.keys()):
138138 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>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
2525 import concurrent.futures
2626 import functools
2727 import io
28 import multiprocessing
2928 import os
3029 import subprocess as ipc
3130 import sys
3837 from lib import tags
3938 from lib import terminal
4039
41 __version__ = '0.26'
40 __version__ = '0.27'
4241
4342 def initialize_terminal():
4443 if sys.stdout.isatty():
6059 tag = tags.get_tag(tagname)
6160 except KeyError:
6261 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}'
6463 )
6564 s = tag.format(self.fake_path, *extra, color=True)
6665 print(s)
134133 for path in paths:
135134 check_file(path, options=options)
136135 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:
139138 check_file_opt = functools.partial(check_file_s, options=options)
140139 for s in executor.map(check_file_opt, paths):
141140 sys.stdout.write(s)
142141
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
143150 def parse_jobs(s):
144151 if s == 'auto':
145 try:
146 return multiprocessing.cpu_count()
147 except NotImplementedError:
148 return 1
152 return get_cpu_count()
149153 n = int(s)
150154 if n <= 0:
151155 raise ValueError
185189 return dist.version
186190
187191 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__}')
191195 rply_version = self._get_rply_version()
192196 if rply_version is not None:
193 print('+ rply {0}'.format(rply_version))
197 print(f'+ rply {rply_version}')
194198 parser.exit()
195199
196200 def main():
0 # Copyright © 2013-2016 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2013-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
3838 # RFC 6762, §3 <https://tools.ietf.org/html/rfc6762#section-3>:
3939 '(.+[.])local',
4040 ]
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
4543
4644 def is_special_domain(domain):
4745 domain = domain.lower()
0 # Copyright © 2012-2020 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2012-2021 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
5454
5555 path = os.path.join(paths.datadir, 'charmaps', encoding.upper())
5656 try:
57 file = open(path, 'rb')
57 file = open(path, 'rb') # pylint: disable=consider-using-with
5858 except FileNotFoundError:
5959 raise EncodingLookupError(encoding)
6060 with file:
0 # Copyright © 2012-2018 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
181181 zone = tz_hint
182182 else:
183183 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'
186186 parse_date(s) # just check syntax
187187 return s
188188
0 # Copyright © 2012-2017 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
5353 def _popen(*args):
5454 def set_lc_all_c():
5555 os.environ['LC_ALL'] = 'C' # no coverage
56 return ipc.Popen(args,
56 return ipc.Popen(args, # pylint: disable=consider-using-with
5757 stdin=ipc.PIPE, stdout=ipc.PIPE, stderr=ipc.PIPE,
5858 preexec_fn=set_lc_all_c,
5959 )
6060
6161 def encode(input: str, encoding=default_encoding, errors='strict'):
6262 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__}')
6464 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__}')
6666 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__}')
6868 if len(input) == 0:
6969 return b''
7070 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')
7272 return _encode(input, encoding=encoding)
7373
7474 def _encode_dl(input: str, *, encoding):
120120 os.strerror(errno.EILSEQ),
121121 )
122122 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'
124124 output_len -= outbytesleft.value
125125 return outbuf[:output_len]
126126 finally:
147147
148148 def decode(input: bytes, encoding=default_encoding, errors='strict'):
149149 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__}')
151151 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__}')
153153 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__}')
155155 if len(input) == 0:
156156 return ''
157157 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')
159159 return _decode(input, encoding=encoding)
160160
161161 def _decode_dl(input: bytes, *, encoding):
210210 os.strerror(errno.EILSEQ),
211211 )
212212 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'
214214 output_len -= outbytesleft.value
215215 assert output_len % ctypes.sizeof(ctypes.c_wchar) == 0
216216 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>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
197197 return s
198198
199199 def __repr__(self):
200 return '<Language {}>'.format(self)
200 return f'<Language {self}>'
201201
202202 def _read_iso_codes():
203203 # ISO language/territory codes:
236236 continue
237237 if key.startswith('characters@'):
238238 continue
239 raise misc.DataIntegrityError('unknown key: {}'.format(key))
239 raise misc.DataIntegrityError(f'unknown key: {key}')
240240 for name in section['names'].splitlines():
241241 name = _munch_language_name(name)
242242 if name:
339339 return
340340 section_name = 'characters'
341341 if modifier is not None:
342 section_name += '@{}'.format(modifier)
342 section_name += f'@{modifier}'
343343 result = section.get(section_name)
344344 if result is None:
345345 return
0 # Copyright © 2012-2016 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
4747 '''
4848 cx = unsorted(iterable)
4949 if cx is not None:
50 raise exception('{0!r} > {1!r}'.format(*cx))
50 raise exception(f'{cx[0]!r} > {cx[1]!r}')
5151
5252 def sorted_vk(d):
5353 '''
8080
8181 @contextlib.contextmanager
8282 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:
8484 original_tempdir = tempfile.tempdir
8585 try:
8686 tempfile.tempdir = new_tempdir
0 # Copyright © 2013-2018 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2013-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
8989 [revision] = self._read_ints(at=4)
9090 major_revision, minor_revision = divmod(revision, 1 << 16)
9191 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}')
9393 [n_strings] = self._read_ints(at=8)
9494 possible_hidden_strings = False
9595 if minor_revision > 1:
0 # Copyright © 2012-2019 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
2323
2424 import ast
2525 import codecs
26 import contextlib
2726 import inspect
2827 import re
2928
4342 __all__ = ['install_patches']
4443
4544 def register_patch(patch):
46 patches.append(contextlib.contextmanager(patch))
45 patches.append(patch)
4746
4847 # polib.default_encoding
4948 # ======================
8685 if line[:2] in {'', '# '} or line.isspace():
8786 pending_comments += [line]
8887 else:
89 for comment_line in pending_comments:
90 yield comment_line
88 yield from pending_comments
9189 pending_comments = []
9290 yield line
9391 empty = False
132130 def unescape(match):
133131 s = match.group()
134132 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}'")
136134 try:
137135 return result.decode('ASCII') # pylint: disable=no-member
138136 except UnicodeDecodeError:
0 # Copyright © 2014-2018 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
7676 t = ptrdiff_t | [unsigned ptrdiff_t]
7777 = int | unsigned int
7878 '''
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('|'))
8181 for line in int_types.strip().splitlines()
8282 for key, values in [map(str.strip, line.split('='))]
83 )
83 }
8484 # FIXME: The printf(3) manpage says that the type for signed integer with
8585 # “z“ size is ssize_t.
8686 # But SUSv3 says it's a signed integer type corresponding to size_t,
8989 # https://www.gnu.org/software/libc/manual/html_node/Integer-Conversions.html
9090 portable_int_lengths = dict(
9191 L='ll',
92 # The use of “L” with integer conversions is not documented
93 # in the printf(3) manpage. https://bugs.debian.org/757151
9492 q='ll',
9593 Z='z',
9694 )
253251 if conv is not arg:
254252 return
255253 else:
256 assert False, 'type(arg) == {!r}'.format(type(arg)) # no coverage
254 assert False, f'type(arg) == {type(arg)!r}' # no coverage
257255 if conv is None:
258256 return
259257 if not conv.integer:
402400 except IndexError:
403401 raise ArgumentNumberingMixture(s)
404402 except OverflowError as exc:
405 raise ArgumentRangeError(s, '{}$'.format(exc))
403 raise ArgumentRangeError(s, f'{exc}$')
406404 width = ...
407405 if width is not None:
408406 if conversion in '%n':
427425 except IndexError:
428426 raise ArgumentNumberingMixture(s)
429427 except OverflowError as exc:
430 raise ArgumentRangeError(s, '{}$'.format(exc))
428 raise ArgumentRangeError(s, f'{exc}$')
431429 precision = ...
432430 if precision is not None:
433431 if conversion in i.int_cvt + i.float_cvt + i.str_cvt:
463461 except IndexError:
464462 raise ArgumentNumberingMixture(s)
465463 except OverflowError as exc:
466 raise ArgumentRangeError(s, '{}$'.format(exc))
464 raise ArgumentRangeError(s, f'{exc}$')
467465
468466 # 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>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
235235 class Conversion():
236236
237237 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}'
239239 i = _info
240240 for flag, count in flags.items():
241241 if count != 1:
0 # Copyright © 2012-2020 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
4747 def __hash__(self): # pylint: disable=invalid-hash-returned
4848 return self.value
4949
50 severities = OrderedEnum('Severity', [
50 severities = OrderedEnum('Severity', [ # pylint: disable=too-many-function-args
5151 'pedantic',
5252 'wishlist',
5353 'minor',
5656 'serious',
5757 ])
5858
59 certainties = OrderedEnum('Certainty', [
59 certainties = OrderedEnum('Certainty', [ # pylint: disable=too-many-function-args
6060 'wild-guess',
6161 'possible',
6262 'certain',
164164 S = severities
165165 c = self.certainty
166166 C = certainties
167 # pylint: disable=no-member
167168 return {
168169 S.pedantic: 'P',
169170 S.wishlist: 'I',
172173 S.important: 'WE'[c >= C.possible],
173174 S.serious: 'E',
174175 }[s]
176 # pylint: enable=no-member
175177
176178 def format(self, target, *extra, color=False):
177179 if color:
178180 color_on, color_off = self.get_colors()
179181 else:
180182 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}'
188184 if extra:
189185 s += ' ' + ' '.join(map(_escape, extra))
190186 return s
0 # Copyright © 2014 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
5858 # pylint: disable=line-too-long
5959 _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'
6060 _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}]*'
6262
6363 # vim:ts=4 sts=4 sw=4 et
00 #!/usr/bin/env python3
11
2 # Copyright © 2012-2016 Jakub Wilk <jwilk@jwilk.net>
2 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
33 #
44 # Permission is hereby granted, free of charge, to any person obtaining a copy
55 # of this software and associated documentation files (the “Software”), to deal
2525 import sys
2626 import json
2727
28 int(0_0) # Python >= 3.6 is required
29
2830 @functools.lru_cache()
2931 def get_iso_codes_dir():
3032 prefix = ipc.check_output(['pkg-config', 'iso-codes', '--variable=prefix'])
3133 prefix = prefix.decode('ASCII').strip()
32 return '{prefix}/share/iso-codes/json'.format(prefix=prefix)
34 return f'{prefix}/share/iso-codes/json'
3335
3436 def main():
3537 ap = argparse.ArgumentParser()
36 ap.add_argument('languages', metavar='<lang>', nargs='+')
38 ap.add_argument('languages', metavar='LANG', nargs='+')
3739 options = ap.parse_args()
3840 languages = set(options.languages)
3941 path = get_iso_codes_dir() + '/iso_639-2.json'
5860 s.strip()
5961 for s in item.get('name').split(';')
6062 ]
61 print('[{}]'.format(requested))
63 print(f'[{requested}]')
6264 if lang != requested:
63 print('# XXX mapping {} => {}'.format(requested, lang))
65 print(f'# XXX mapping {requested} => {lang}')
6466 if len(names) == 1:
6567 print('names =', *names)
6668 else:
6971 print('', name)
7072 print()
7173 for lang in sorted(languages):
72 print('# Unknown language code: {}'.format(lang))
74 print(f'# Unknown language code: {lang}')
7375 sys.exit(len(languages) > 0)
7476
7577 if __name__ == '__main__':
00 #!/bin/sh
11
2 # Copyright © 2016-2018 Jakub Wilk <jwilk@jwilk.net>
2 # Copyright © 2016-2022 Jakub Wilk <jwilk@jwilk.net>
33 #
44 # Permission is hereby granted, free of charge, to any person obtaining a copy
55 # of this software and associated documentation files (the “Software”), to deal
3535 else
3636 printf '%s\n' "$@"
3737 fi |
38 xargs -L1 -t -I{} "$rst2xml" $options {} /dev/null
38 xargs -t -I{} "$rst2xml" $options {} /dev/null
3939
4040 # vim:ts=4 sts=4 sw=4 et
00 #!/usr/bin/env python3
11
2 # Copyright © 2012-2016 Jakub Wilk <jwilk@jwilk.net>
2 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
33 #
44 # Permission is hereby granted, free of charge, to any person obtaining a copy
55 # of this software and associated documentation files (the “Software”), to deal
2121
2222 import argparse
2323 import collections
24 import functools
2425 import itertools
2526 import os
2627 import sys
2728
2829 import polib
30
31 int(0_0) # Python >= 3.6 is required
2932
3033 def init_polib():
3134 # don't assume UTF-8 encoding
3437 def main():
3538 init_polib()
3639 ap = argparse.ArgumentParser()
37 ap.add_argument('files', metavar='<file>', nargs='*')
40 ap.add_argument('files', metavar='FILE', nargs='+')
3841 ap.add_argument('--skip-ascii', action='store_true', help='skip ASCII characters')
3942 ap.add_argument('--stdin', action='store_true', help='read filenames from stdin')
4043 options = ap.parse_args()
4750 char_counter = collections.Counter()
4851 char_files = collections.defaultdict(set)
4952 n_files = 0
53 eprint = functools.partial(print, file=sys.stderr, flush=True)
5054 for path in files:
51 print(path, end=' ... ', file=sys.stderr)
52 sys.stderr.flush()
55 eprint(path, end=' ... ')
5356 try:
5457 extension = os.path.splitext(path)[-1]
5558 if extension == '.po':
5659 constructor = polib.pofile
57 elif extension in ('.mo', '.gmo'):
60 elif extension in {'.mo', '.gmo'}:
5861 constructor = polib.mofile
5962 else:
6063 raise NotImplementedError(repr(extension))
6669 char_files[ch].add(path)
6770 file = None
6871 except Exception as exc: # pylint: disable=broad-except
69 print('error:', exc, file=sys.stderr)
72 eprint('error:', exc)
7073 else:
71 print('ok', file=sys.stderr)
72 sys.stderr.flush()
74 eprint('ok')
7375 n_files += 1
7476 for n, ch in sorted(((n, ch) for ch, n in char_counter.items()), reverse=True):
7577 if options.skip_ascii and ord(ch) < 0x80:
7678 continue
7779 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}')
7981
8082 if __name__ == '__main__':
8183 main()
00 #!/usr/bin/env python3
11
2 # Copyright © 2012-2016 Jakub Wilk <jwilk@jwilk.net>
2 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
33 #
44 # Permission is hereby granted, free of charge, to any person obtaining a copy
55 # of this software and associated documentation files (the “Software”), to deal
2121
2222 import argparse
2323 import collections
24 import functools
2425 import itertools
2526 import os
2627 import re
2728 import sys
2829
2930 import polib
31
32 int(0_0) # Python >= 3.6 is required
3033
3134 def init_polib():
3235 # Happily decode any file, even when encoding declaration is broken or
5356 def main():
5457 init_polib()
5558 ap = argparse.ArgumentParser()
56 ap.add_argument('files', metavar='<file>', nargs='*')
59 ap.add_argument('files', metavar='FILE', nargs='+')
5760 ap.add_argument('-F', '--field', help='only this field')
5861 ap.add_argument('--insane', action='store_true', help='allow insane field names')
5962 ap.add_argument('--extract-addresses', action='store_true')
7275 files,
7376 (l.rstrip() for l in sys.stdin)
7477 )
78 eprint = functools.partial(print, file=sys.stderr, flush=True)
7579 for path in files:
76 print(path, end=' ... ', file=sys.stderr)
77 sys.stderr.flush()
80 eprint(path, end=' ... ')
7881 try:
7982 extension = os.path.splitext(path)[-1]
8083 if extension == '.po':
8184 constructor = polib.pofile
82 elif extension in ('.mo', '.gmo'):
85 elif extension in {'.mo', '.gmo'}:
8386 constructor = polib.mofile
8487 else:
8588 raise NotImplementedError(repr(extension))
8992 if msg.msgstr_plural:
9093 break
9194 else:
92 print('skip', file=sys.stderr)
93 sys.stderr.flush()
95 eprint('skip')
9496 continue
9597 for k, v in file.metadata.items():
9698 if (not is_sane_field_name(k)) and (not options.insane):
106108 test_cases[k, v] = path
107109 file = None
108110 except Exception as exc: # pylint: disable=broad-except
109 print('error:', exc, file=sys.stderr)
111 eprint('error:', exc)
110112 else:
111 print('ok', file=sys.stderr)
112 sys.stderr.flush()
113 eprint('ok')
113114 for key, values in sorted(metadata.items()):
114 print('{key!r}:'.format(key=key))
115 print(f'{key!r}:')
115116 for value, n in values.most_common():
116117 if options.all_test_cases:
117 print(' {n:6} {value!r}'.format(n=n, value=value))
118 print(f' {n:6} {value!r}')
118119 for path in sorted(test_cases[key, value]):
119 print(' + {path!r}'.format(path=path))
120 print(f' + {path!r}')
120121 else:
121122 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}')
123124
124125 if __name__ == '__main__':
125126 main()
00 #!/usr/bin/env python3
11
2 # Copyright © 2012-2020 Jakub Wilk <jwilk@jwilk.net>
2 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
33 #
44 # Permission is hereby granted, free of charge, to any person obtaining a copy
55 # of this software and associated documentation files (the “Software”), to deal
3333 import apt
3434 import apt_pkg
3535
36 int(0_0) # Python >= 3.6 is required
37
3638 def is_po_path(path):
3739 return path.endswith(('.po', '.pot'))
3840
4345 self._progress = apt.progress.text.AcquireProgress()
4446 self._acquire = apt_pkg.Acquire(self._progress)
4547 self._files = []
46 self._tmpdir = tempfile.TemporaryDirectory(prefix='i18nspector.private.')
48 self._tmpdir = tempfile.TemporaryDirectory(prefix='i18nspector.private.') # pylint: disable=consider-using-with
4749
4850 def add(self, url, name, urlbase=None):
4951 if urlbase is None:
8082 if _package_name_re.match(pkg):
8183 pass
8284 else:
83 raise RuntimeError('invalid package name: {pkg!r}'.format(pkg=pkg))
85 raise RuntimeError(f'invalid package name: {pkg!r}')
8486
8587 @contextlib.contextmanager
8688 def chdir(new_cwd):
98100 def main():
99101 ap = argparse.ArgumentParser()
100102 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})'
102104 )
103105 ap.add_argument('--contents-mirror', metavar='URL', default=None,
104106 help='Debian mirror of Contents-* files to use (default: same as --mirror)'
105107 )
106108 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})'
108110 )
109111 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})'
111113 )
112114 ap.add_argument('--output', metavar='TARFILE', required=True,
113115 help='output tar file'
119121 if options.output.endswith('.' + ext):
120122 tarmode += ext
121123 break
122 tar = tarfile.open(output, mode=tarmode)
124 tar = tarfile.open(output, mode=tarmode) # pylint: disable=consider-using-with
123125 mirror = options.mirror
124126 cnts_mirror = options.contents_mirror
125127 if cnts_mirror is None:
133135 tarinfo.mode |= 0o644
134136 if tarinfo.mode & 0o100:
135137 tarinfo.mode |= 0o111
136 prefix = 'po-corpus-{dist}/'.format(dist=dist)
138 prefix = f'po-corpus-{dist}/'
137139 tarinfo.name = prefix + tarinfo.name
138140 if tarinfo.linkname:
139141 tarinfo.linkname = prefix + tarinfo.linkname
140142 return tarinfo
141143 subprocess.check_call(['dpkg-source', '--version'], stdout=subprocess.DEVNULL)
142144 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}/'
145147 with Fetcher(urlbase=urlbase) as fetcher:
146148 fetcher.add('Contents-source.gz', 'Contents.gz', urlbase=cnts_urlbase)
147149 fetcher.add('source/Sources.xz', 'Sources.xz')
152154 try:
153155 path, packages = line.rsplit('\t', 1)
154156 except ValueError:
155 raise RuntimeError('malformed line: {!r}'.format(line))
157 raise RuntimeError(f'malformed line: {line!r}')
156158 if is_po_path(path):
157159 packages = packages.rstrip('\n').split(',')
158160 for pkg in packages:
173175 name not in names
174176 )
175177 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}')
177179 names.add(name)
178180 if name.endswith('.dsc'):
179181 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')
181183 dscname = name
182184 if dscname is None:
183 raise RuntimeError('Package {pkg}: missing .dsc file'.format(pkg=pkg))
185 raise RuntimeError(f'Package {pkg}: missing .dsc file')
184186 names = list(names)
185187 names.sort(key=dscname.__eq__)
186188 pkgdir = para['Directory']
187 pkgurlbase = '{mirror}/{dir}/'.format(mirror=mirror, dir=pkgdir)
189 pkgurlbase = f'{mirror}/{pkgdir}/'
188190 with Fetcher(urlbase=pkgurlbase) as pkgfetcher:
189191 for name in names:
190192 pkgfetcher.add(name, name)
00 #!/usr/bin/env python3
11
2 # Copyright © 2012-2017 Jakub Wilk <jwilk@jwilk.net>
2 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
33 #
44 # Permission is hereby granted, free of charge, to any person obtaining a copy
55 # of this software and associated documentation files (the “Software”), to deal
2828 from lib import gettext
2929 from lib import ling
3030
31 int(0_0) # Python >= 3.6 is required
32
3133 def strip_plural_forms(s):
3234 # Remove superfluous parentheses around the plural formula:
3335 return re.sub(r'\bplural=\((.*?)\);$', r'plural=\1;', s)
4749 gettext_plural_forms = strip_plural_forms(gettext_plural_forms)
4850 our_plural_forms = language.get_plural_forms() or []
4951 if gettext_plural_forms not in our_plural_forms:
50 print('[{}]'.format(language))
52 print(f'[{language}]')
5153 if our_plural_forms:
5254 if len(our_plural_forms) == 1:
5355 print('# plural-forms =', *our_plural_forms)
6062 else:
6163 okay.add(str(language))
6264 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')
6466 print()
6567
6668 blacklist = {
9294 if our_cc is not None:
9395 okay.add(language)
9496 continue
95 print('[{}]'.format(language))
97 print(f'[{language}]')
9698 if our_cc is not None:
9799 print('# WAS: principal-territory =', our_cc)
98100 if ling.lookup_territory_code(gettext_cc) is None:
109111 if cc is None:
110112 continue
111113 if language not in data:
112 print('# WAS: [{}]'.format(language))
114 print(f'# WAS: [{language}]')
113115 print('# WAS: principal-territory =', cc)
114116 print()
115117 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')
117119 print()
118120
119121 def do_string_formats():
136138 print('[formats]')
137139 for fmt in difference:
138140 if fmt in our_formats:
139 print('# MISSING: {fmt} = ...'.format(fmt=fmt))
141 print(f'# MISSING: {fmt} = ...')
140142 else:
141 print('{fmt} = # TODO'.format(fmt=fmt))
143 print(f'{fmt} = # TODO')
142144 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')
144146 print()
145147
146148 def main():
00 #!/usr/bin/env python3
11
2 # Copyright © 2012-2020 Jakub Wilk <jwilk@jwilk.net>
2 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
33 #
44 # Permission is hereby granted, free of charge, to any person obtaining a copy
55 # of this software and associated documentation files (the “Software”), to deal
2727
2828 from lib import tags as tagmod
2929
30 int(0_0) # Python >= 3.6 is required
31
3032 def output_tags_rst(tags):
3133 print('''\
3234 .. This file has been generated automatically by private/tags-as-rst.
4749 print(' |', ref)
4850 print()
4951 print('Severity, certainty:', end='\n\n')
50 print(' {}, {}'.format(tag.severity.name, tag.certainty.name))
52 print(f' {tag.severity.name}, {tag.certainty.name}')
5153 print()
5254
5355 def main():
00 #!/usr/bin/env python3
11
2 # Copyright © 2013 Jakub Wilk <jwilk@jwilk.net>
2 # Copyright © 2013-2022 Jakub Wilk <jwilk@jwilk.net>
33 #
44 # Permission is hereby granted, free of charge, to any person obtaining a copy
55 # of this software and associated documentation files (the “Software”), to deal
2222 import os
2323 import subprocess as ipc
2424
25 int(0_0) # Python >= 3.6 is required
26
2527 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/'
2930 os.chdir(test_dir)
3031 for generator in os.listdir('.'):
3132 if not generator.endswith('.gen'):
00 #!/usr/bin/env python3
11
2 # Copyright © 2013-2016 Jakub Wilk <jwilk@jwilk.net>
2 # Copyright © 2013-2022 Jakub Wilk <jwilk@jwilk.net>
33 #
44 # Permission is hereby granted, free of charge, to any person obtaining a copy
55 # of this software and associated documentation files (the “Software”), to deal
1919 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2020 # SOFTWARE.
2121
22 import functools
2223 import sys
2324
2425 # pylint: disable=wrong-import-position
2930 from lib import encodings
3031 from lib import iconv
3132
33 int(0_0) # Python >= 3.6 is required
34
3235 def generate_charmap(encoding):
3336 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}'
3740 n = 0
3841 us = []
3942 for b in range(0x100):
4750 assert len(u) == 1
4851 us += [u]
4952 if n <= 128:
50 print('SKIP (not 8-bit)')
53 eprint('SKIP (not 8-bit)')
5154 return
5255 assert len(us) == 0x100
5356 with open(path, 'wb') as file:
5457 us = ''.join(us)
5558 assert len(us) == 0x100
5659 file.write(us.encode('UTF-8'))
57 print('ok')
60 eprint('ok')
5861
5962 def main():
6063 # pylint: disable=protected-access
00 #!/usr/bin/env python3
11
2 # Copyright © 2012-2016 Jakub Wilk <jwilk@jwilk.net>
2 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
33 #
44 # Permission is hereby granted, free of charge, to any person obtaining a copy
55 # of this software and associated documentation files (the “Software”), to deal
2626 import sys
2727 import json
2828
29 int(0_0) # Python >= 3.6 is required
30
2931 class Panic(ValueError):
3032 pass
3133
3941 def get_iso_codes_dir():
4042 prefix = ipc.check_output(['pkg-config', 'iso-codes', '--variable=prefix'])
4143 prefix = prefix.decode('ASCII').strip()
42 return '{prefix}/share/iso-codes/json'.format(prefix=prefix)
44 return f'{prefix}/share/iso-codes/json'
4345
4446 def main():
4547 basedir = os.path.join(
4749 os.pardir,
4850 )
4951 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'''\
5256 # This file has been generated automatically by private/update-iso-codes.
5357 # Do not edit.
54 # iso-codes version: {version}
58 # iso-codes version: {iso_codes_version}
5559 # Last update: {today}
56 '''.format(version=get_iso_codes_version(), today=datetime.date.today()))
60 ''')
5761 generate_iso_639()
5862 generate_iso_3166()
5963 sys.stdout.close()
7781 continue
7882 for l in l2b, l2t:
7983 if len(l) != 3:
80 raise Panic('len({!r}) != 3'.format(l))
84 raise Panic(f'len({l!r}) != 3')
8185 if l2b != l2t:
8286 l2t_to_2b[l2t] = l2b
8387 iso_639 = {}
8589 for item in data['639-3']:
8690 code = item['alpha_3']
8791 if len(code) != 3:
88 raise Panic('len({!r}) != 3'.format(code))
92 raise Panic(f'len({code!r}) != 3')
8993 code1 = item.get('alpha_2')
9094 code2 = item.get('terminology')
9195 if code2 is None:
9296 # We're not interested in languages that are not in 639-2 (yet?).
9397 continue
9498 if code2 != code:
95 raise Panic('{!r} != {!r}'.format(code, code2))
99 raise Panic(f'{code!r} != {code2!r}')
96100 scope = item['scope']
97101 if scope == 'S':
98102 # Not a real language, ignore.
100104 elif scope in {'M', 'I'}:
101105 pass
102106 else:
103 raise Panic('unknown scope: {!r}'.format(scope))
107 raise Panic(f'unknown scope: {scope!r}')
104108 reference_name = item['name']
105109 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}')
107111 if code1 is not None:
108112 if len(code1) == 2:
109113 codelist = [code1, code]
110114 else:
111 raise Panic('len({!r}) != 2'.format(code1))
115 raise Panic(f'len({code1!r}) != 2')
112116 else:
113117 codelist = [code]
114118 try:
124128 if not aliases:
125129 iso_639_rev[code] = ''
126130 for alias, code in sorted(iso_639_rev.items()):
127 print('{} = {}'.format(alias, code).rstrip())
131 print(f'{alias} = {code}'.rstrip())
128132 print()
129133
130134 def generate_iso_3166():
138142 iso_3166.add(cc)
139143 print('[territory-codes]')
140144 for cc in sorted(iso_3166):
141 print('{} ='.format(cc))
145 print(f'{cc} =')
142146 print()
143 print('# vi''m:ft=dosini')
147 print('# vim\72ft=dosini')
144148
145149 if __name__ == '__main__':
146150 main()
00 #!/usr/bin/env python3
11
2 # Copyright © 2012-2018 Jakub Wilk <jwilk@jwilk.net>
2 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
33 #
44 # Permission is hereby granted, free of charge, to any person obtaining a copy
55 # of this software and associated documentation files (the “Software”), to deal
2727 from lib import tags
2828 from tests import blackbox_tests
2929
30 int(0_0) # Python >= 3.6 is required
31
3032 def main():
3133 coverage = blackbox_tests.get_coverage()
3234 path = os.path.join(
3335 os.path.dirname(blackbox_tests.__file__),
3436 'coverage'
3537 )
36 sys.stdout = open(path + '.tmp', 'wt', encoding='ASCII')
38 sys.stdout = open(path + '.tmp', 'wt', encoding='ASCII') # pylint: disable=consider-using-with
3739 print('Generated automatically by private/update-tag-coverage. '
3840 'Do not edit.\n')
3941 for tag in tags.iter_tags():
4042 check = 'X' if tag.name in coverage else ' '
41 print('[{check}] {tag}'.format(check=check, tag=tag.name))
43 print(f'[{check}] {tag.name}')
4244 sys.stdout.close()
4345 os.rename(path + '.tmp', path)
4446 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)
4749 rc = 1
4850 sys.exit(rc)
4951
00 #!/usr/bin/env python3
11
2 # Copyright © 2013-2016 Jakub Wilk <jwilk@jwilk.net>
2 # Copyright © 2013-2022 Jakub Wilk <jwilk@jwilk.net>
33 #
44 # Permission is hereby granted, free of charge, to any person obtaining a copy
55 # of this software and associated documentation files (the “Software”), to deal
3434
3535 from lib import gettext
3636
37 int(0_0) # Python >= 3.6 is required
38
3739 def get_tzdata_version():
3840 version = pytz.OLSON_VERSION
3941 if os.path.exists('/etc/debian_version'):
6668 assert 3 <= len(code) <= 6
6769 tzdata[code].add(offset)
6870 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'''\
7175 # This file has been generated automatically by private/update-timezones.
7276 # Do not edit.
73 # Timezone database version: {version}
77 # Timezone database version: {tzdata_version}
7478 # Last update: {today}
75 '''.format(
76 version=get_tzdata_version(),
77 today=datetime.date.today(),
78 ))
79 ''')
7980 print('[timezones]')
8081 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}')
8284 print()
83 print('# vi''m:ft=dosini')
85 print('# vim\72ft=dosini')
8486 sys.stdout.close()
8587 os.rename(path + '.tmp', path)
8688
0 type(...) # Python >= 3 is required
0 int(0_0) # Python >= 3.6 is required
0 # Copyright © 2012-2017 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
7878 class ETag():
7979
8080 _ellipsis = '<...>'
81 _split = re.compile('({})'.format(re.escape(_ellipsis))).split
81 _split = re.compile(f'({re.escape(_ellipsis)})').split
8282
8383 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}'
8985 self.tag = rest.split(None, 1)[0]
9086 regexp = ''.join(
9187 '.*' if chunk == self._ellipsis else re.escape(chunk)
9288 for chunk in self._split(s)
9389 )
94 self._regexp = re.compile('^{}$'.format(regexp))
90 self._regexp = re.compile(f'^{regexp}$')
9591
9692 def __eq__(self, other):
9793 if isinstance(other, str):
108104 # ----------------------------------------
109105
110106 def _get_signal_names():
111 data = dict(
112 (name, getattr(signal, name))
107 data = {
108 name: getattr(signal, name)
113109 for name in dir(signal)
114110 if re.compile('^SIG[A-Z0-9]*$').match(name)
115 )
111 }
116112 try:
117113 if data['SIGABRT'] == data['SIGIOT']:
118114 del data['SIGIOT']
218214 commandline += options
219215 commandline += [path]
220216 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 )
226222 rc = child.poll()
227223 assert isinstance(rc, int)
228224 if rc == 0:
229225 return stdout
230226 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}']
234230 message += ['']
235231 if stdout:
236232 message += ['stdout:']
257253 __file__=prog,
258254 )
259255 (sys.stdout, sys.stderr) = (io_stdout, io_stderr)
256 stdout = stderr = ''
260257 try:
261258 try:
262259 exec(code, gvars) # pylint: disable=exec-used
326323 def _parse_test_headers(path):
327324 # <path>.tags:
328325 try:
329 file = open(path + '.tags', encoding='UTF-8')
326 file = open(path + '.tags', encoding='UTF-8') # pylint: disable=consider-using-with
330327 except FileNotFoundError:
331328 pass
332329 else:
334331 return _parse_test_header_file(file, path, comments_only=False)
335332 # <path>.gen:
336333 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
338335 except FileNotFoundError:
339336 pass
340337 else:
8080 [X] non-portable-encoding
8181 [X] os-error
8282 [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
8386 [X] python-brace-format-string-argument-type-mismatch
8487 [X] python-brace-format-string-error
8588 [X] python-brace-format-string-missing-argument
33 # counter-productive.
44 #
55 # [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/
77
88 msgid ""
99 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>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
107107 continue
108108 line = line.rstrip('\n')
109109 if line[-1:].isspace():
110 raise AssertionError('trailing whitespace at line {0}'.format(n))
110 raise AssertionError(f'trailing whitespace at line {n}')
111111
112112 # 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>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
5959 def test_example(self):
6060 self.t('example')
6161 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}')
6464
6565 class test_special_domain_emails:
6666
0 # Copyright © 2012-2020 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
423423 else:
424424 w = y - x + 1
425425 assert w >= 2
426 s = '{x} + ({var})%{w}'.format(x=x, w=w, var=var)
426 s = f'{x} + ({var})%{w}'
427427 self.t(s, x, y)
428428 return s
429429
432432 for x in range(4):
433433 for y in range(x, 5):
434434 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}'
436436 vals = {
437437 int(pycmp(i, j))
438438 for i in range(1, 4)
440440 }
441441 self.t(s, min(vals), max(vals))
442442 n = 42
443 s = '{l} {cmp} {r}'.format(l=n, cmp=ccmp, r=n)
443 s = f'{n} {ccmp} {n}'
444444 self.t(s, pycmp(n, n), pycmp(n, n))
445445
446446 def test_lt(self):
471471 sl = self.r(lx, ly, var='n%3')
472472 for rx, ry in ranges:
473473 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}'
475475 vals = {
476476 int(pyop(i, j))
477477 for i in range(lx, ly + 1)
558558 def test_const_cmp(self):
559559 for op in {'!=', '==', '<', '<=', '>', '>='}:
560560 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)
562562
563563 def test_const_cmp_overflow(self):
564564 ops = {'!=', '==', '<', '<=', '>', '>='}
565565 m = (1 << 32) - 1
566566 for op in ops:
567567 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)
569569 if shift:
570 self.t('n {op} {m}'.format(op=op, m=m), None)
570 self.t(f'n {op} {m}', None)
571571 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)
575575
576576 def test_compare(self):
577577 self.t('n < (n % 37)', None)
636636
637637 def test_nplurals_positive(self):
638638 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)
640640
641641 def test_missing_trailing_semicolon(self):
642642 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>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
342342 def test_not_found(self):
343343 lang = L.parse_language('ry')
344344 cc = lang.get_principal_territory_code()
345 assert_equal(cc, None)
345 assert_is_none(cc)
346346
347347 class test_unrepresentable_characters:
348348
425425 raise
426426 assert_equal(str(lang), l)
427427 try:
428 file = open('/usr/share/i18n/SUPPORTED', encoding='ASCII')
428 file = open('/usr/share/i18n/SUPPORTED', encoding='ASCII') # pylint: disable=consider-using-with
429429 except OSError as exc:
430430 raise nose.SkipTest(exc)
431431 locales = set()
0 # Copyright © 2014-2018 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
101101
102102 def test_integer(self):
103103 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)
106110 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')
121122 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]')
134134
135135 def test_double(self):
136136 t = self.t
161161 def t(s, tp):
162162 return (
163163 _t,
164 '%<{macro}>'.format(macro=s.format(c=c, n=n)),
164 f'%<{s.format(c=c, n=n)}>',
165165 ('u' if unsigned else '') + tp.format(n=n)
166166 )
167167 # pylint: enable=undefined-loop-variable
235235 M.FormatString('%0$d')
236236 def fs(n):
237237 s = ''.join(
238 '%{0}$d'.format(i)
238 f'%{i}$d'
239239 for i in range(1, n + 1)
240240 )
241241 return M.FormatString(s)
333333 M.FormatString('%1' + c)
334334
335335 def test_too_large(self):
336 fmt = M.FormatString('%{0}d'.format(M.INT_MAX))
336 fmt = M.FormatString(f'%{M.INT_MAX}d')
337337 assert_equal(len(fmt), 1)
338338 assert_equal(len(fmt.arguments), 1)
339339 with assert_raises(M.WidthRangeError):
340 M.FormatString('%{0}d'.format(M.INT_MAX + 1))
340 M.FormatString(f'%{M.INT_MAX + 1}d')
341341
342342 def test_variable(self):
343343 fmt = M.FormatString('%*s')
348348 assert_equal(a2.type, 'const char *')
349349
350350 def _test_index(self, i):
351 fmt = M.FormatString('%2$*{0}$s'.format(i))
351 fmt = M.FormatString(f'%2$*{i}$s')
352352 assert_equal(len(fmt), 1)
353353 assert_equal(len(fmt.arguments), 2)
354354 [a1], [a2] = fmt.arguments
368368 M.FormatString('%1$*0$s')
369369 def fs(n):
370370 s = ''.join(
371 '%{0}$d'.format(i)
371 f'%{i}$d'
372372 for i in range(2, n)
373 ) + '%1$*{0}$s'.format(n)
373 ) + f'%1$*{n}$s'
374374 return M.FormatString(s)
375375 fmt = fs(M.NL_ARGMAX)
376376 assert_equal(len(fmt), M.NL_ARGMAX - 1)
421421 yield t, ('%.1' + c)
422422
423423 def test_too_large(self):
424 fmt = M.FormatString('%.{0}f'.format(M.INT_MAX))
424 fmt = M.FormatString(f'%.{M.INT_MAX}f')
425425 assert_equal(len(fmt), 1)
426426 with assert_raises(M.PrecisionRangeError):
427 M.FormatString('%.{0}f'.format(M.INT_MAX + 1))
427 M.FormatString(f'%.{M.INT_MAX + 1}f')
428428
429429 def test_variable(self):
430430 fmt = M.FormatString('%.*f')
435435 assert_equal(a2.type, 'double')
436436
437437 def _test_index(self, i):
438 fmt = M.FormatString('%2$.*{0}$f'.format(i))
438 fmt = M.FormatString(f'%2$.*{i}$f')
439439 assert_equal(len(fmt), 1)
440440 assert_equal(len(fmt.arguments), 2)
441441 [a1], [a2] = fmt.arguments
455455 M.FormatString('%1$.*0$f')
456456 def fs(n):
457457 s = ''.join(
458 '%{0}$d'.format(i)
458 f'%{i}$d'
459459 for i in range(2, n)
460 ) + '%1$.*{0}$f'.format(n)
460 ) + f'%1$.*{n}$f'
461461 return M.FormatString(s)
462462 fmt = fs(M.NL_ARGMAX)
463463 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>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
187187 yield t, ('%1' + c)
188188
189189 def test_too_large(self):
190 fmt = M.FormatString('%{0}d'.format(M.SSIZE_MAX))
190 fmt = M.FormatString(f'%{M.SSIZE_MAX}d')
191191 assert_equal(len(fmt), 1)
192192 assert_equal(len(fmt.seq_arguments), 1)
193193 assert_equal(len(fmt.map_arguments), 0)
194194 with assert_raises(M.WidthRangeError):
195 M.FormatString('%{0}d'.format(M.SSIZE_MAX + 1))
195 M.FormatString(f'%{M.SSIZE_MAX + 1}d')
196196
197197 def test_variable(self):
198198 fmt = M.FormatString('%*s')
245245 yield t, ('%.1' + c)
246246
247247 def test_too_large(self):
248 fmt = M.FormatString('%.{0}f'.format(M.SSIZE_MAX))
248 fmt = M.FormatString(f'%.{M.SSIZE_MAX}f')
249249 assert_equal(len(fmt), 1)
250250 with assert_raises(M.PrecisionRangeError):
251 M.FormatString('%.{0}f'.format(M.SSIZE_MAX + 1))
251 M.FormatString(f'%.{M.SSIZE_MAX + 1}f')
252252
253253 def test_variable(self):
254254 fmt = M.FormatString('%.*f')
0 # Copyright © 2012-2019 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
6262
6363 def ast_to_tagnames(node):
6464 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)
6766 ok = (
6867 isinstance(node, ast.Call) and
6968 isinstance(node.func, ast.Attribute) and
9392 if tag not in tagnames:
9493 raise AssertionError(
9594 'tag missing in data/tags:\n\n'
96 '[{tag}]\n'
95 f'[{tag}]\n'
9796 'severity = wishlist\n'
9897 'certainty = wild-guess\n'
99 'description = TODO'.format(tag=tag)
98 'description = TODO'
10099 )
101100 for tag in sorted(source_tagnames | tagnames):
102101 yield test, tag
0 # Copyright © 2014 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2014-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
6767 self.t(s)
6868
6969 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')
7171
7272 def test_good(self):
7373 def t(s):
0 # Copyright © 2012-2019 Jakub Wilk <jwilk@jwilk.net>
0 # Copyright © 2012-2022 Jakub Wilk <jwilk@jwilk.net>
11 #
22 # Permission is hereby granted, free of charge, to any person obtaining a copy
33 # of this software and associated documentation files (the “Software”), to deal
9393 elif status == 0 and msg == '':
9494 pass
9595 else:
96 raise RuntimeError('unexpected isolated process status {}'.format(status))
96 raise RuntimeError(f'unexpected isolated process status {status}')
9797
9898 # pylint:enable=consider-using-sys-exit
9999