diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b61dac46b..7d6375d82 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,3 +16,53 @@ repos: exclude: (tests/messages/data/) - id: requirements-txt-fixer - id: trailing-whitespace + - repo: https://github.com/ikamensh/flynt + rev: "0.77" + hooks: + - id: flynt + exclude: (tests/messages/data/) + - repo: https://github.com/PyCQA/autoflake + rev: v2.0.0 + hooks: + - id: autoflake + exclude: (tests/messages/data/) + args: + - --in-place + - --remove-all-unused-imports + - --remove-duplicate-keys + - repo: https://github.com/pre-commit/mirrors-autopep8 + rev: 'v2.0.1' + hooks: + - id: autopep8 + exclude: (tests/messages/data/|docs/conf.py) + args: + - --in-place + - --ignore=E501,E731,E402 + - repo: https://github.com/asottile/pyupgrade + rev: v3.3.1 + hooks: + - id: pyupgrade + args: [ --py37-plus ] + exclude: (tests/messages/data/) + - repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + exclude: (tests/messages/data/|docs/conf.py) + additional_dependencies: &flake8_dependencies + - flake8==6.0.0 + - flake8-bugbear==22.12.6 + args: + - --extend-ignore=E501,W503,W504,E731 + - repo: https://github.com/asottile/yesqa + rev: v1.4.0 + hooks: + - id: yesqa + additional_dependencies: *flake8_dependencies + - repo: https://github.com/PyCQA/isort + rev: 5.11.4 + hooks: + - id: isort + exclude: (tests/messages/data/|docs/conf.py) + args: + - --profile=black diff --git a/CHANGES.rst b/CHANGES.rst index bee5c3f40..a146e8fba 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -613,7 +613,7 @@ Version 1.0 - Explicitly sort instead of using sorted() and don't assume ordering (Jython compatibility). - Removed ValueError raising for string formatting message checkers if the - string does not contain any string formattings (:trac:`150`). + string does not contain any string formatting (:trac:`150`). - Fix Serbian plural forms (:trac:`213`). - Small speed improvement in format_date() (:trac:`216`). - Fix so frontend.CommandLineInterface.run does not accumulate logging @@ -690,7 +690,7 @@ Version 0.9.6 - Explicitly sort instead of using sorted() and don't assume ordering (Python 2.3 and Jython compatibility). - Removed ValueError raising for string formatting message checkers if the - string does not contain any string formattings (:trac:`150`). + string does not contain any string formatting (:trac:`150`). - Fix Serbian plural forms (:trac:`213`). - Small speed improvement in format_date() (:trac:`216`). - Fix number formatting for locales where CLDR specifies alt or draft diff --git a/babel/__init__.py b/babel/__init__.py index df190033c..be35c0361 100644 --- a/babel/__init__.py +++ b/babel/__init__.py @@ -16,8 +16,22 @@ :license: BSD, see LICENSE for more details. """ -from babel.core import UnknownLocaleError, Locale, default_locale, \ - negotiate_locale, parse_locale, get_locale_identifier - +from babel.core import ( + Locale, + UnknownLocaleError, + default_locale, + get_locale_identifier, + negotiate_locale, + parse_locale, +) __version__ = '2.11.0' + +__all__ = [ + 'Locale', + 'UnknownLocaleError', + 'default_locale', + 'get_locale_identifier', + 'negotiate_locale', + 'parse_locale', +] diff --git a/babel/core.py b/babel/core.py index 704957b7e..2db4d04dd 100644 --- a/babel/core.py +++ b/babel/core.py @@ -46,6 +46,7 @@ _global_data = None _default_plural_rule = PluralRule({}) + def _raise_no_data_error(): raise RuntimeError('The babel data files are not available. ' 'This usually happens because you are using ' @@ -239,13 +240,13 @@ def negotiate( >>> Locale.negotiate(['de_DE', 'de'], ['en_US']) You can specify the character used in the locale identifiers to separate - the differnet components. This separator is applied to both lists. Also, + the different components. This separator is applied to both lists. Also, case is ignored in the comparison: >>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-') Locale('de', territory='DE') - :param preferred: the list of locale identifers preferred by the user + :param preferred: the list of locale identifiers preferred by the user :param available: the list of locale identifiers available :param aliases: a dictionary of aliases for locale identifiers """ @@ -383,10 +384,12 @@ def __eq__(self, other: object) -> bool: for key in ('language', 'territory', 'script', 'variant'): if not hasattr(other, key): return False - return (self.language == getattr(other, 'language')) and \ - (self.territory == getattr(other, 'territory')) and \ - (self.script == getattr(other, 'script')) and \ - (self.variant == getattr(other, 'variant')) + return ( + (self.language == getattr(other, 'language')) and # noqa: B009 + (self.territory == getattr(other, 'territory')) and # noqa: B009 + (self.script == getattr(other, 'script')) and # noqa: B009 + (self.variant == getattr(other, 'variant')) # noqa: B009 + ) def __ne__(self, other: object) -> bool: return not self.__eq__(other) diff --git a/babel/dates.py b/babel/dates.py index f7289619f..3dc859fd4 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -49,7 +49,7 @@ # empty set characters ( U+2205 )." # - https://www.unicode.org/reports/tr35/tr35-dates.html#Metazone_Names -NO_INHERITANCE_MARKER = u'\u2205\u2205\u2205' +NO_INHERITANCE_MARKER = '\u2205\u2205\u2205' if pytz: @@ -83,7 +83,6 @@ def _localize(tz: tzinfo, dt: datetime) -> datetime: return dt.astimezone(tz) - def _get_dt_and_tzinfo(dt_or_tzinfo: _DtOrTzinfo) -> tuple[datetime_ | None, tzinfo]: """ Parse a `dt_or_tzinfo` value into a datetime and a tzinfo. @@ -248,13 +247,13 @@ def get_timezone(zone: str | tzinfo | None = None) -> tzinfo: if pytz: try: return pytz.timezone(zone) - except pytz.UnknownTimeZoneError as exc: + except pytz.UnknownTimeZoneError as exc: # noqa: F841 pass else: assert zoneinfo try: return zoneinfo.ZoneInfo(zone) - except zoneinfo.ZoneInfoNotFoundError as exc: + except zoneinfo.ZoneInfoNotFoundError as exc: # noqa: F841 pass raise LookupError(f"Unknown timezone {zone}") from exc @@ -548,11 +547,11 @@ def get_timezone_gmt(datetime: _Instant = None, width: Literal['long', 'short', if return_z and hours == 0 and seconds == 0: return 'Z' elif seconds == 0 and width == 'iso8601_short': - return u'%+03d' % hours + return '%+03d' % hours elif width == 'short' or width == 'iso8601_short': - pattern = u'%+03d%02d' + pattern = '%+03d%02d' elif width == 'iso8601': - pattern = u'%+03d:%02d' + pattern = '%+03d:%02d' else: pattern = locale.zone_formats['gmt'] % '%+03d:%02d' return pattern % (hours, seconds // 60) @@ -560,7 +559,7 @@ def get_timezone_gmt(datetime: _Instant = None, width: Literal['long', 'short', def get_timezone_location(dt_or_tzinfo: _DtOrTzinfo = None, locale: Locale | str | None = LC_TIME, return_city: bool = False) -> str: - u"""Return a representation of the given timezone using "location format". + """Return a representation of the given timezone using "location format". The result depends on both the local display name of the country and the city associated with the time zone: @@ -1041,10 +1040,10 @@ def _iter_patterns(a_unit): break # This really should not happen if pattern is None: - return u'' + return '' return pattern.replace('{0}', str(value)) - return u'' + return '' def _format_fallback_interval(start: _Instant, end: _Instant, skeleton: str | None, tzinfo: tzinfo | None, @@ -1286,8 +1285,7 @@ def parse_date(string: str, locale: Locale | str | None = LC_TIME, format: _Pred month_idx = format_str.index('l') day_idx = format_str.index('d') - indexes = [(year_idx, 'Y'), (month_idx, 'M'), (day_idx, 'D')] - indexes.sort() + indexes = sorted([(year_idx, 'Y'), (month_idx, 'M'), (day_idx, 'D')]) indexes = {item[1]: idx for idx, item in enumerate(indexes)} # FIXME: this currently only supports numbers, but should also support month @@ -1332,8 +1330,7 @@ def parse_time(string: str, locale: Locale | str | None = LC_TIME, format: _Pred min_idx = format_str.index('m') sec_idx = format_str.index('s') - indexes = [(hour_idx, 'H'), (min_idx, 'M'), (sec_idx, 'S')] - indexes.sort() + indexes = sorted([(hour_idx, 'H'), (min_idx, 'M'), (sec_idx, 'S')]) indexes = {item[1]: idx for idx, item in enumerate(indexes)} # TODO: support time zones @@ -1369,7 +1366,7 @@ def __str__(self) -> str: return pat def __mod__(self, other: DateTimeFormat) -> str: - if type(other) is not DateTimeFormat: + if not isinstance(other, DateTimeFormat): return NotImplemented return self.format % other @@ -1690,7 +1687,7 @@ def get_week_number(self, day_of_period: int, day_of_week: int | None = None) -> :param day_of_period: the number of the day in the period (usually either the day of month or the day of year) - :param day_of_week: the week day; if ommitted, the week day of the + :param day_of_week: the week day; if omitted, the week day of the current date is assumed """ if day_of_week is None: @@ -1762,7 +1759,7 @@ def parse_pattern(pattern: str) -> DateTimePattern: :param pattern: the formatting pattern to parse """ - if type(pattern) is DateTimePattern: + if isinstance(pattern, DateTimePattern): return pattern if pattern in _pattern_cache: @@ -1782,7 +1779,7 @@ def parse_pattern(pattern: str) -> DateTimePattern: else: raise NotImplementedError(f"Unknown token type: {tok_type}") - _pattern_cache[pattern] = pat = DateTimePattern(pattern, u''.join(result)) + _pattern_cache[pattern] = pat = DateTimePattern(pattern, ''.join(result)) return pat @@ -1817,7 +1814,7 @@ def append_field(): fieldchar[0] = '' fieldnum[0] = 0 - for idx, char in enumerate(pattern.replace("''", '\0')): + for char in pattern.replace("''", '\0'): if quotebuf is None: if char == "'": # quote started if fieldchar[0]: diff --git a/babel/localedata.py b/babel/localedata.py index 0d3508d1e..c14391aa7 100644 --- a/babel/localedata.py +++ b/babel/localedata.py @@ -121,7 +121,7 @@ def load(name: os.PathLike[str] | str, merge_inherited: bool = True) -> dict[str :param merge_inherited: whether the inherited data should be merged into the data of the requested locale :raise `IOError`: if no locale data file is found for the given locale - identifer, or one of the locales it inherits from + identifier, or one of the locales it inherits from """ name = os.path.basename(name) _cache_lock.acquire() diff --git a/babel/localtime/_helpers.py b/babel/localtime/_helpers.py index b7238f6a6..159f9a569 100644 --- a/babel/localtime/_helpers.py +++ b/babel/localtime/_helpers.py @@ -24,6 +24,7 @@ def _get_tzinfo(tzenv: str): return None + def _get_tzinfo_or_raise(tzenv: str): tzinfo = _get_tzinfo(tzenv) if tzinfo is None: diff --git a/babel/localtime/_unix.py b/babel/localtime/_unix.py index 319c8cfbc..7c0f614ca 100644 --- a/babel/localtime/_unix.py +++ b/babel/localtime/_unix.py @@ -1,14 +1,14 @@ import os import re - from datetime import tzinfo from babel.localtime._helpers import ( + _get_tzinfo, _get_tzinfo_from_file, _get_tzinfo_or_raise, - _get_tzinfo, ) + def _tz_from_env(tzenv: str) -> tzinfo: if tzenv[0] == ':': tzenv = tzenv[1:] diff --git a/babel/localtime/_win32.py b/babel/localtime/_win32.py index 3d7e0d512..e7ff829e8 100644 --- a/babel/localtime/_win32.py +++ b/babel/localtime/_win32.py @@ -6,9 +6,10 @@ winreg = None from datetime import tzinfo +from typing import Any, Dict, cast + from babel.core import get_global from babel.localtime._helpers import _get_tzinfo_or_raise -from typing import Any, Dict, cast # When building the cldr data on windows this module gets imported. # Because at that point there is no global.dat yet this call will diff --git a/babel/messages/__init__.py b/babel/messages/__init__.py index ad4fd346d..1a2488c76 100644 --- a/babel/messages/__init__.py +++ b/babel/messages/__init__.py @@ -8,4 +8,10 @@ :license: BSD, see LICENSE for more details. """ -from babel.messages.catalog import * +from babel.messages.catalog import Catalog, Message, TranslationError + +__all__ = [ + "Catalog", + "Message", + "TranslationError", +] diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py index 0801de371..f16ea5b6b 100644 --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -10,20 +10,20 @@ from __future__ import annotations import re - from collections import OrderedDict -from collections.abc import Generator, Iterable, Iterator -from datetime import datetime, time as time_ +from collections.abc import Iterable, Iterator +from copy import copy +from datetime import datetime +from datetime import time as time_ from difflib import get_close_matches from email import message_from_string -from copy import copy from typing import TYPE_CHECKING from babel import __version__ as VERSION from babel.core import Locale, UnknownLocaleError from babel.dates import format_datetime from babel.messages.plurals import get_plural -from babel.util import distinct, LOCALTZ, FixedOffsetTimezone, _cmp +from babel.util import LOCALTZ, FixedOffsetTimezone, _cmp, distinct if TYPE_CHECKING: from typing_extensions import TypeAlias @@ -81,7 +81,7 @@ class Message: def __init__( self, id: _MessageID, - string: _MessageID | None = u'', + string: _MessageID | None = '', locations: Iterable[tuple[str, int]] = (), flags: Iterable[str] = (), auto_comments: Iterable[str] = (), @@ -108,7 +108,7 @@ def __init__( """ self.id = id if not string and self.pluralizable: - string = (u'', u'') + string = ('', '') self.string = string self.locations = list(distinct(locations)) self.flags = set(flags) @@ -234,13 +234,14 @@ class TranslationError(Exception): translations are encountered.""" -DEFAULT_HEADER = u"""\ +DEFAULT_HEADER = """\ # Translations template for PROJECT. # Copyright (C) YEAR ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR , YEAR. #""" + def parse_separated_header(value: str) -> dict[str, str]: # Adapted from https://peps.python.org/pep-0594/#cgi from email.message import Message @@ -445,7 +446,7 @@ def _set_mime_headers(self, headers: Iterable[tuple[str, str]]) -> None: value = self._force_text(value, encoding=self.charset) if name == 'project-id-version': parts = value.split(' ') - self.project = u' '.join(parts[:-1]) + self.project = ' '.join(parts[:-1]) self.version = parts[-1] elif name == 'report-msgid-bugs-to': self.msgid_bugs_address = value @@ -591,7 +592,7 @@ def __iter__(self) -> Iterator[Message]: flags = set() if self.fuzzy: flags |= {'fuzzy'} - yield Message(u'', '\n'.join(buf), flags=flags) + yield Message('', '\n'.join(buf), flags=flags) for key in self._messages: yield self._messages[key] @@ -736,7 +737,8 @@ def delete(self, id: _MessageID, context: str | None = None) -> None: if key in self._messages: del self._messages[key] - def update(self, + def update( + self, template: Catalog, no_fuzzy_matching: bool = False, update_header_comment: bool = False, @@ -831,7 +833,7 @@ def _merge(message: Message, oldkey: tuple[str, str] | str, newkey: tuple[str, s if not isinstance(message.string, (list, tuple)): fuzzy = True message.string = tuple( - [message.string] + ([u''] * (len(message.id) - 1)) + [message.string] + ([''] * (len(message.id) - 1)) ) elif len(message.string) != self.num_plurals: fuzzy = True @@ -841,7 +843,7 @@ def _merge(message: Message, oldkey: tuple[str, str] | str, newkey: tuple[str, s message.string = message.string[0] message.flags |= oldmsg.flags if fuzzy: - message.flags |= {u'fuzzy'} + message.flags |= {'fuzzy'} self[message.id] = message for message in template: diff --git a/babel/messages/checkers.py b/babel/messages/checkers.py index 9231c678e..38a26e844 100644 --- a/babel/messages/checkers.py +++ b/babel/messages/checkers.py @@ -13,8 +13,7 @@ from collections.abc import Callable -from babel.messages.catalog import Catalog, Message, TranslationError, PYTHON_FORMAT - +from babel.messages.catalog import PYTHON_FORMAT, Catalog, Message, TranslationError #: list of format chars that are compatible to each other _string_format_compatibilities = [ @@ -67,7 +66,7 @@ def _validate_format(format: str, alternative: str) -> None: placeholders if `format` uses named placeholders. The behavior of this function is undefined if the string does not use - string formattings. + string formatting. If the string formatting of `alternative` is compatible to `format` the function returns `None`, otherwise a `TranslationError` is raised. @@ -111,7 +110,7 @@ def _compatible(a: str, b: str) -> bool: def _check_positional(results: list[tuple[str, str]]) -> bool: positional = None - for name, char in results: + for name, _char in results: if positional is None: positional = name is None else: diff --git a/babel/messages/extract.py b/babel/messages/extract.py index 5c331c0a5..453742ed0 100644 --- a/babel/messages/extract.py +++ b/babel/messages/extract.py @@ -18,21 +18,29 @@ from __future__ import annotations import ast -from collections.abc import Callable, Collection, Generator, Iterable, Mapping, MutableSequence import io import os import sys +from collections.abc import ( + Callable, + Collection, + Generator, + Iterable, + Mapping, + MutableSequence, +) from os.path import relpath -from tokenize import generate_tokens, COMMENT, NAME, OP, STRING -from typing import Any, TYPE_CHECKING +from textwrap import dedent +from tokenize import COMMENT, NAME, OP, STRING, generate_tokens +from typing import TYPE_CHECKING, Any from babel.util import parse_encoding, parse_future_flags, pathmatch -from textwrap import dedent if TYPE_CHECKING: from typing import IO, Protocol - from typing_extensions import Final, TypeAlias, TypedDict + from _typeshed import SupportsItems, SupportsRead, SupportsReadline + from typing_extensions import Final, TypeAlias, TypedDict class _PyOptions(TypedDict, total=False): encoding: str @@ -82,7 +90,6 @@ def tell(self) -> int: ... DEFAULT_MAPPING: list[tuple[str, str]] = [('**.py', 'python')] - def _strip_comment_tags(comments: MutableSequence[str], tags: Iterable[str]): """Helper function for `extract` that strips comment tags from strings in a list of comment lines. This functions operates in-place. @@ -537,8 +544,8 @@ def extract_python( messages = tuple(messages) else: messages = messages[0] - # Comments don't apply unless they immediately preceed the - # message + # Comments don't apply unless they immediately + # precede the message if translator_comments and \ translator_comments[-1][0] < message_lineno - 1: translator_comments = [] @@ -652,8 +659,7 @@ def extract_javascript( token = Token('operator', ')', token.lineno) if options.get('parse_template_string') and not funcname and token.type == 'template_string': - for item in parse_template_string(token.value, keywords, comment_tags, options, token.lineno): - yield item + yield from parse_template_string(token.value, keywords, comment_tags, options, token.lineno) elif token.type == 'operator' and token.value == '(': if funcname: @@ -673,7 +679,7 @@ def extract_javascript( break elif token.type == 'multilinecomment': - # only one multi-line comment may preceed a translation + # only one multi-line comment may precede a translation translator_comments = [] value = token.value[2:-2].strip() for comment_tag in comment_tags: @@ -786,8 +792,7 @@ def parse_template_string( if level == 0 and expression_contents: expression_contents = expression_contents[0:-1] fake_file_obj = io.BytesIO(expression_contents.encode()) - for item in extract_javascript(fake_file_obj, keywords, comment_tags, options, lineno): - yield item + yield from extract_javascript(fake_file_obj, keywords, comment_tags, options, lineno) lineno += len(line_re.findall(expression_contents)) expression_contents = '' prev_character = character diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py index c42cdc2e2..029226edf 100644 --- a/babel/messages/frontend.py +++ b/babel/messages/frontend.py @@ -20,12 +20,19 @@ from configparser import RawConfigParser from datetime import datetime from io import StringIO +from typing import Iterable +from babel import Locale from babel import __version__ as VERSION -from babel import Locale, localedata +from babel import localedata from babel.core import UnknownLocaleError -from babel.messages.catalog import Catalog, DEFAULT_HEADER -from babel.messages.extract import DEFAULT_KEYWORDS, DEFAULT_MAPPING, check_and_call_extract_file, extract_from_dir +from babel.messages.catalog import DEFAULT_HEADER, Catalog +from babel.messages.extract import ( + DEFAULT_KEYWORDS, + DEFAULT_MAPPING, + check_and_call_extract_file, + extract_from_dir, +) from babel.messages.mofile import write_mo from babel.messages.pofile import read_po, write_po from babel.util import LOCALTZ @@ -38,15 +45,16 @@ distutils_log = log # "distutils.log → (no replacement yet)" try: - from setuptools.errors import OptionError, SetupError, BaseError + from setuptools.errors import BaseError, OptionError, SetupError except ImportError: # Error aliases only added in setuptools 59 (2021-11). OptionError = SetupError = BaseError = Exception except ImportError: from distutils import log as distutils_log from distutils.cmd import Command as _Command - from distutils.errors import DistutilsOptionError as OptionError, DistutilsSetupError as SetupError, DistutilsError as BaseError - + from distutils.errors import DistutilsError as BaseError + from distutils.errors import DistutilsOptionError as OptionError + from distutils.errors import DistutilsSetupError as SetupError def listify_value(arg, split=None): @@ -188,7 +196,7 @@ def finalize_options(self): def run(self): n_errors = 0 for domain in self.domain: - for catalog, errors in self._run_domain(domain).items(): + for errors in self._run_domain(domain).values(): n_errors += len(errors) if n_errors: self.log.error('%d errors encountered.', n_errors) @@ -472,6 +480,27 @@ def finalize_options(self): else: self.directory_filter = None + def _build_callback(self, path: str): + def callback(filename: str, method: str, options: dict): + if method == 'ignore': + return + + # If we explicitly provide a full filepath, just use that. + # Otherwise, path will be the directory path and filename + # is the relative path from that dir to the file. + # So we can join those to get the full filepath. + if os.path.isfile(path): + filepath = path + else: + filepath = os.path.normpath(os.path.join(path, filename)) + + optstr = '' + if options: + opt_values = ", ".join(f'{k}="{v}"' for k, v in options.items()) + optstr = f" ({opt_values})" + self.log.info('extracting messages from %s%s', filepath, optstr) + return callback + def run(self): mappings = self._get_mappings() with open(self.output_file, 'wb') as outfile: @@ -483,25 +512,7 @@ def run(self): header_comment=(self.header_comment or DEFAULT_HEADER)) for path, method_map, options_map in mappings: - def callback(filename, method, options): - if method == 'ignore': - return - - # If we explicitly provide a full filepath, just use that. - # Otherwise, path will be the directory path and filename - # is the relative path from that dir to the file. - # So we can join those to get the full filepath. - if os.path.isfile(path): - filepath = path - else: - filepath = os.path.normpath(os.path.join(path, filename)) - - optstr = '' - if options: - opt_values = ", ".join(f'{k}="{v}"' for k, v in options.items()) - optstr = f" ({opt_values})" - self.log.info('extracting messages from %s%s', filepath, optstr) - + callback = self._build_callback(path) if os.path.isfile(path): current_dir = os.getcwd() extracted = check_and_call_extract_file( @@ -842,7 +853,7 @@ def run(self): omit_header=self.omit_header, ignore_obsolete=self.ignore_obsolete, include_previous=self.previous, width=self.width) - except: + except Exception: os.remove(tmpname) raise @@ -937,7 +948,7 @@ def run(self, argv=None): identifiers = localedata.locale_identifiers() longest = max(len(identifier) for identifier in identifiers) identifiers.sort() - format = u'%%-%ds %%s' % (longest + 1) + format = '%%-%ds %%s' % (longest + 1) for identifier in identifiers: locale = Locale.parse(identifier) print(format % (identifier, locale.english_name)) @@ -1105,7 +1116,7 @@ def parse_mapping(fileobj, filename=None): return method_map, options_map -def parse_keywords(strings=[]): +def parse_keywords(strings: Iterable[str] = ()): """Parse keywords specifications from the given list of strings. >>> kw = sorted(parse_keywords(['_', 'dgettext:2', 'dngettext:2,3', 'pgettext:1c,2']).items()) diff --git a/babel/messages/jslexer.py b/babel/messages/jslexer.py index 07fffdec7..0563f6221 100644 --- a/babel/messages/jslexer.py +++ b/babel/messages/jslexer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ babel.messages.jslexer ~~~~~~~~~~~~~~~~~~~~~~ @@ -11,9 +10,8 @@ """ from __future__ import annotations -from collections import namedtuple -from collections.abc import Generator, Iterator, Sequence import re +from collections.abc import Generator from typing import NamedTuple operators: list[str] = sorted([ @@ -34,11 +32,13 @@ uni_escape_re = re.compile(r'[a-fA-F0-9]{1,4}') hex_escape_re = re.compile(r'[a-fA-F0-9]{1,2}') + class Token(NamedTuple): type: str value: str lineno: int + _rules: list[tuple[str | None, re.Pattern[str]]] = [ (None, re.compile(r'\s+', re.UNICODE)), (None, re.compile(r'