summaryrefslogtreecommitdiffstats
path: root/clock
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2023-01-05 10:38:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2023-01-05 10:39:17 +0000
commitd6d80a17444c90259c5bfdacb84c61e6bfece655 (patch)
tree157bff98bd572acf0b64cd5d478b0bdac87a37ae /clock
parentReleasing debian version 2.1.2-4. (diff)
downloadpendulum-d6d80a17444c90259c5bfdacb84c61e6bfece655.tar.xz
pendulum-d6d80a17444c90259c5bfdacb84c61e6bfece655.zip
Merging upstream version 3.0.0~a1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'clock')
-rwxr-xr-xclock274
1 files changed, 274 insertions, 0 deletions
diff --git a/clock b/clock
new file mode 100755
index 0000000..17f35a3
--- /dev/null
+++ b/clock
@@ -0,0 +1,274 @@
+#!/usr/bin/env python
+
+from __future__ import annotations
+
+import glob
+import json
+import os
+
+from babel.core import get_global
+from babel.dates import PATTERN_CHARS
+from babel.dates import tokenize_pattern
+from babel.localedata import LocaleDataDict
+from babel.localedata import load
+from babel.localedata import normalize_locale
+from babel.plural import PluralRule
+from babel.plural import _binary_compiler
+from babel.plural import _GettextCompiler
+from babel.plural import _unary_compiler
+from babel.plural import compile_zero
+from cleo.application import Application
+from cleo.commands.command import Command
+from cleo.helpers import argument
+
+from pendulum import __version__
+
+
+class _LambdaCompiler(_GettextCompiler):
+ """Compiles the expression to lambda function."""
+
+ compile_v = compile_zero
+ compile_w = compile_zero
+ compile_f = compile_zero
+ compile_t = compile_zero
+ compile_and = _binary_compiler("(%s and %s)")
+ compile_or = _binary_compiler("(%s or %s)")
+ compile_not = _unary_compiler("(not %s)")
+ compile_mod = _binary_compiler("(%s %% %s)")
+
+ def compile_relation(self, method, expr, range_list):
+ code = _GettextCompiler.compile_relation(self, method, expr, range_list)
+ code = code.replace("&&", "and")
+ code = code.replace("||", "or")
+ if method == "in":
+ expr = self.compile(expr)
+ code = f"({expr} == {expr} and {code})"
+ return code
+
+
+class LocaleCreate(Command):
+
+ name = "locale create"
+ description = "Creates locale translations."
+
+ arguments = [argument("locales", "Locales to dump.", optional=False, multiple=True)]
+
+ TEMPLATE = """from .custom import translations as custom_translations
+
+
+\"\"\"
+{locale} locale file.
+
+It has been generated automatically and must not be modified directly.
+\"\"\"
+
+
+locale = {{
+ 'plural': {plural},
+ 'ordinal': {ordinal},
+ 'translations': {translations},
+ 'custom': custom_translations
+}}
+"""
+
+ CUSTOM_TEMPLATE = """\"\"\"
+{locale} custom locale file.
+\"\"\"
+
+translations = {{}}
+"""
+
+ LOCALE_DIR = os.path.join("pendulum", "locales")
+
+ def handle(self):
+ locales = self.argument("locales")
+ if not locales:
+ return
+
+ for locale in locales:
+ data = {}
+ parts = locale.split("-")
+ if len(parts) > 1:
+ parts[1] = parts[1].upper()
+
+ normalized = normalize_locale(locale.replace("-", "_"))
+ if not normalized:
+ self.line(f"<error>Locale [{locale}] does not exist.</error>")
+ continue
+
+ self.line(f"<info>Generating <comment>{locale}</> locale.</>")
+
+ content = LocaleDataDict(load(normalized))
+
+ # Pluralization rule
+ rule = content["plural_form"]
+ plural = self.plural_rule_to_lambda(rule)
+
+ # Ordinal rule
+ rule = content["ordinal_form"]
+ ordinal = self.plural_rule_to_lambda(rule)
+
+ # Getting days names
+ days = content["days"]["format"]
+ data["days"] = {}
+ for fmt, names in days.items():
+ data["days"][fmt] = {}
+ for value, name in names.items():
+ data["days"][fmt][(value + 1) % 7] = name
+
+ # Getting months names
+ months = content["months"]["format"]
+ data["months"] = months
+
+ # Units
+ patterns = content["unit_patterns"]
+ units = [
+ "year",
+ "month",
+ "week",
+ "day",
+ "hour",
+ "minute",
+ "second",
+ "microsecond",
+ ]
+ data["units"] = {}
+ for unit in units:
+ pattern = patterns[f"duration-{unit}"]["long"]
+ if "per" in pattern:
+ del pattern["per"]
+
+ data["units"][unit] = pattern
+
+ # Relative
+ data["relative"] = {}
+ for key in content["date_fields"]:
+ if key not in [
+ "year",
+ "month",
+ "week",
+ "day",
+ "hour",
+ "minute",
+ "second",
+ ]:
+ continue
+
+ data["relative"][key] = content["date_fields"][key]
+
+ # Day periods
+ data["day_periods"] = content["day_periods"]["format"]["wide"]
+
+ result = self.TEMPLATE.format(
+ locale=locale,
+ plural=plural,
+ ordinal=ordinal,
+ translations=self.format_dict(data, tab=2),
+ )
+
+ dest_dir = os.path.join(self.LOCALE_DIR, locale.replace("-", "_"))
+ if not os.path.exists(dest_dir):
+ os.mkdir(dest_dir)
+
+ init = os.path.join(dest_dir, "__init__.py")
+ main = os.path.join(dest_dir, "locale.py")
+ custom = os.path.join(dest_dir, "custom.py")
+
+ if not os.path.exists(init):
+ with open(init, "w"):
+ os.utime(init)
+
+ with open(main, "w") as fw:
+ fw.write(result)
+
+ if not os.path.exists(custom):
+ with open(custom, "w") as fw:
+ fw.write(self.CUSTOM_TEMPLATE.format(locale=locale))
+
+ def format_dict(self, d, tab=1):
+ s = ["{\n"]
+ for k, v in d.items():
+ if isinstance(v, (dict, LocaleDataDict)):
+ v = self.format_dict(v, tab + 1)
+ else:
+ v = repr(v)
+
+ s.append(f"{' ' * tab}{k!r}: {v},\n")
+ s.append(f'{" " * (tab - 1)}}}')
+
+ return "".join(s)
+
+ def plural_rule_to_lambda(self, rule):
+ to_py = _LambdaCompiler().compile
+ result = ["lambda n: "]
+ for tag, ast in PluralRule.parse(rule).abstract:
+ result.append(f"'{tag}' if {to_py(ast)} else ")
+ result.append("'other'")
+ return "".join(result)
+
+ def convert_ldml_format(self, fmt):
+ result = []
+
+ for tok_type, tok_value in tokenize_pattern(fmt):
+ if tok_type == "chars":
+ result.append(tok_value.replace("%", "%%"))
+ elif tok_type == "field":
+ fieldchar, fieldnum = tok_value
+ limit = PATTERN_CHARS[fieldchar]
+ if limit and fieldnum not in limit:
+ raise ValueError(
+ f"Invalid length for field: {(fieldchar * fieldnum)!r}"
+ )
+ result.append(
+ self.TOKENS_MAP.get(fieldchar * fieldnum, fieldchar * fieldnum)
+ )
+ else:
+ raise NotImplementedError(f"Unknown token type: {tok_type}")
+
+ return "".join(result)
+
+
+class LocaleRecreate(Command):
+
+ name = "locale recreate"
+ description = "Recreate existing locales."
+
+ def handle(self):
+ # Listing locales
+
+ locales_dir = os.path.join("pendulum", "locales")
+ locales = glob.glob(os.path.join(locales_dir, "*", "locale.py"))
+ locales = [os.path.basename(os.path.dirname(locale)) for locale in locales]
+
+ self.call("locale:create", [("locales", locales)])
+
+
+class WindowsTzDump(Command):
+
+ name = "windows dump-timezones"
+ description = "Dumps the mapping of Windows timezones to IANA timezones."
+
+ MAPPING_DIR = os.path.join("pendulum", "tz", "data")
+
+ def handle(self):
+ raw_tznames = get_global("windows_zone_mapping")
+ sorted_names = sorted(raw_tznames.keys())
+
+ tznames = {}
+ for name in sorted_names:
+ tznames[name] = raw_tznames[name]
+
+ mapping = json.dumps(tznames, indent=4).replace('"', "'")
+
+ with open(os.path.join(self.MAPPING_DIR, "windows.py"), "w") as f:
+ f.write(f"windows_timezones = {mapping}\n")
+
+
+app = Application("clock", __version__)
+app.add(LocaleCreate())
+app.add(LocaleRecreate())
+app.add(WindowsTzDump())
+
+
+if __name__ == "__main__":
+ app.run()