🍽 Fork YunoHost — snapshot mangé par la machine à tsoins
Upstream: https://github.com/YunoHost/yunohost @ 3a5f8bac8301c450897b96cbd43a4c7d3ba750fb But (José) : transformer tout le code en bions + ploxions du xerboxion. La carte de digestion vit au labo : /yunohost-digest.json
This commit is contained in:
16
maintenance/agplv3.tpl
Normal file
16
maintenance/agplv3.tpl
Normal file
@@ -0,0 +1,16 @@
|
||||
Copyright (c) ${years} ${owner}
|
||||
|
||||
This file is part of ${projectname} (see ${projecturl})
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
187
maintenance/autofix_locale_format.py
Executable file
187
maintenance/autofix_locale_format.py
Executable file
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2024 YunoHost Contributors
|
||||
#
|
||||
# This file is part of YunoHost (see https://yunohost.org)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
|
||||
Locale = dict[str, str]
|
||||
|
||||
|
||||
def autofix_i18n_placeholders(
|
||||
reference: Locale, locale: Locale, reference_filename: str, filename: str
|
||||
) -> tuple[bool, Locale]:
|
||||
"""
|
||||
This tries for magically fix mismatch between en.json format and other.json format
|
||||
e.g. an i18n string with:
|
||||
source: "Lorem ipsum {some_var}"
|
||||
fr: "Lorem ipsum {une_variable}"
|
||||
(ie the keyword in {} was translated but shouldnt have been)
|
||||
"""
|
||||
fatal_errors = False
|
||||
|
||||
# We iterate over all keys/string in en.json
|
||||
for key, string in reference.items():
|
||||
# Ignore check if there's no translation yet for this key
|
||||
if key not in locale:
|
||||
continue
|
||||
|
||||
# Then we check that every "{stuff}" (for python's .format())
|
||||
# should also be in the translated string, otherwise the .format
|
||||
# will trigger an exception!
|
||||
subkeys_in_ref = [k[0] for k in re.findall(r"{(\w+)(:\w)?}", string)]
|
||||
subkeys_in_this_locale = [
|
||||
k[0] for k in re.findall(r"{(\w+)(:\w)?}", locale[key])
|
||||
]
|
||||
|
||||
if set(subkeys_in_ref) != set(subkeys_in_this_locale) and (
|
||||
len(subkeys_in_ref) == len(subkeys_in_this_locale)
|
||||
):
|
||||
for i, subkey in enumerate(subkeys_in_ref):
|
||||
locale[key] = locale[key].replace(
|
||||
"{%s}" % subkeys_in_this_locale[i], "{%s}" % subkey
|
||||
)
|
||||
|
||||
# Validate that now it's okay ?
|
||||
subkeys_in_ref = [k[0] for k in re.findall(r"{(\w+)(:\w)?}", string)]
|
||||
subkeys_in_this_locale = [
|
||||
k[0] for k in re.findall(r"{(\w+)(:\w)?}", locale[key])
|
||||
]
|
||||
if any(k not in subkeys_in_ref for k in subkeys_in_this_locale):
|
||||
errmsg = textwrap.dedent(f"""\
|
||||
==========================
|
||||
Format inconsistency for string {key} in {filename}:
|
||||
{reference_filename} -> {string.encode("utf-8")}
|
||||
{filename} -> {locale[key].encode("utf-8")}
|
||||
Please fix it manually !
|
||||
""")
|
||||
print(errmsg)
|
||||
fatal_errors = True
|
||||
|
||||
return fatal_errors, locale
|
||||
|
||||
|
||||
def autofix_orthotypography_and_standardized_words(
|
||||
locale: Locale, filename: str
|
||||
) -> Locale:
|
||||
godamn_spaces_of_hell = [
|
||||
"\u00a0",
|
||||
"\u2000",
|
||||
"\u2001",
|
||||
"\u2002",
|
||||
"\u2003",
|
||||
"\u2004",
|
||||
"\u2005",
|
||||
"\u2006",
|
||||
"\u2007",
|
||||
"\u2008",
|
||||
"\u2009",
|
||||
"\u200a",
|
||||
# "\u202f",
|
||||
# "\u202F",
|
||||
"\u3000",
|
||||
]
|
||||
transformations_space = {s: " " for s in godamn_spaces_of_hell}
|
||||
|
||||
transformations_misc = {
|
||||
r"\.\.\.": "…",
|
||||
"https ://": "https://",
|
||||
}
|
||||
|
||||
transformations_fr = {
|
||||
"courriel": "email",
|
||||
"e-mail": "email",
|
||||
"Courriel": "Email",
|
||||
"E-mail": "Email",
|
||||
"« ": "'",
|
||||
"«": "'",
|
||||
" »": "'",
|
||||
"»": "'",
|
||||
"’": "'",
|
||||
# r"$(\w{1,2})'|( \w{1,2})'": r"\1\2’",
|
||||
}
|
||||
|
||||
match filename:
|
||||
case "en.json":
|
||||
transformations = transformations_space | transformations_misc
|
||||
case "fr.json":
|
||||
transformations = (
|
||||
transformations_space | transformations_misc | transformations_fr
|
||||
)
|
||||
case _:
|
||||
transformations = {}
|
||||
|
||||
for pattern, replace in transformations.items():
|
||||
for key, value in locale.items():
|
||||
locale[key] = re.sub(pattern, replace, value)
|
||||
return locale
|
||||
|
||||
|
||||
def remove_stale_translated_strings(reference: Locale, locale: Locale) -> Locale:
|
||||
return {k: v for k, v in locale.items() if k in reference}
|
||||
|
||||
|
||||
def sort_locale(locale: Locale) -> Locale:
|
||||
return dict(sorted(locale.items()))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
project_dir: Path = Path(__file__).resolve().parent.parent
|
||||
locale_dir = project_dir / "locales"
|
||||
|
||||
reference_file = locale_dir / "en.json"
|
||||
locale_files = list(locale_dir.glob("*.json"))
|
||||
locale_files.remove(reference_file)
|
||||
|
||||
reference = json.load(reference_file.open())
|
||||
fatal_errors = []
|
||||
|
||||
for file in locale_files:
|
||||
locale = json.load(file.open(), object_pairs_hook=OrderedDict)
|
||||
|
||||
locale = autofix_orthotypography_and_standardized_words(locale, file.name)
|
||||
locale = remove_stale_translated_strings(reference, locale)
|
||||
errors, locale = autofix_i18n_placeholders(
|
||||
reference, locale, reference_file.name, file.name
|
||||
)
|
||||
if errors:
|
||||
fatal_errors.append(file.name)
|
||||
|
||||
# locale = sort_locale(locale)
|
||||
|
||||
with file.open("w") as locale_io:
|
||||
json.dump(
|
||||
locale,
|
||||
locale_io,
|
||||
indent=4,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
locale_io.write("\n")
|
||||
|
||||
if fatal_errors:
|
||||
print(f"Errors found in files: {', '.join(fatal_errors)}.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
150
maintenance/make_changelog.sh
Executable file
150
maintenance/make_changelog.sh
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Copyright (c) 2024 YunoHost Contributors
|
||||
#
|
||||
# This file is part of YunoHost (see https://yunohost.org)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
set -eu
|
||||
|
||||
function increment_version() {
|
||||
local version=$1
|
||||
local incr_type=$2
|
||||
|
||||
local major=$(awk -F. '{print $1}' <<< "$version")
|
||||
local medium=$(awk -F. '{print $2}' <<< "$version")
|
||||
local minor=$(awk -F. '{print $3}' <<< "$version")
|
||||
local patch=$(awk -F. '{print $4}' <<< "$version")
|
||||
patch=${patch:-0}
|
||||
|
||||
if [[ "$incr_type" == "patch" ]]
|
||||
then
|
||||
echo "$major.$medium.$minor.$((patch+1))"
|
||||
elif [[ "$incr_type" == "minor" ]]
|
||||
then
|
||||
echo "$major.$medium.$((minor+1))"
|
||||
elif [[ "$incr_type" == "medium" ]]
|
||||
then
|
||||
echo "$major.$((medium+1)).0"
|
||||
else
|
||||
echo "Unhandled version increment type '$incr_type', should be either 'patch', 'minor' or 'medium'" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
RELEASE="stable"
|
||||
ME=$(git config --get user.name)
|
||||
EMAIL=$(git config --get user.email)
|
||||
|
||||
REPO=$(head -n1 debian/changelog | awk '{print $1}')
|
||||
CURRENT_VERSION=$(head -n1 debian/changelog | awk '{print $2}' | tr -d '()')
|
||||
CURRENT_RELEASE_TYPE=$(head -n1 debian/changelog | awk '{print $3}' | tr -d ';')
|
||||
|
||||
INCR_VERSION_TYPE="${1:-}"
|
||||
if [[ -n "$INCR_VERSION_TYPE" ]]
|
||||
then
|
||||
NEW_VERSION="$(increment_version "$CURRENT_VERSION" "$INCR_VERSION_TYPE")"
|
||||
else
|
||||
NEW_VERSION="x.y.z"
|
||||
fi
|
||||
|
||||
RELEASE_TYPE="${2:-}"
|
||||
if [[ -n "$RELEASE_TYPE" ]]
|
||||
then
|
||||
[[ $RELEASE_TYPE == "stable" ]] || [[ $RELEASE_TYPE == "testing" ]] || ( echo "Release type should be either 'stable' or 'testing'" >&2; exit 1; )
|
||||
NEW_RELEASE_TYPE=$RELEASE_TYPE
|
||||
else
|
||||
NEW_VERSION_TYPE=$CURRENT_RELEASE_TYPE
|
||||
fi
|
||||
|
||||
|
||||
|
||||
echo "$REPO ($NEW_VERSION) $CURRENT_RELEASE_TYPE; urgency=low"
|
||||
echo ""
|
||||
|
||||
PREVIOUS_TAG="debian/$CURRENT_VERSION"
|
||||
COMMITS=$(git log "$PREVIOUS_TAG".. -n 10000 --first-parent --pretty=tformat:'%h')
|
||||
for COMMIT in $COMMITS
|
||||
do
|
||||
SUBJECT="$(git show -s "$COMMIT" --pretty="%s")"
|
||||
# "Regular" PRs merge commit
|
||||
if grep -q "^Merge pull request #" <<< "$SUBJECT"
|
||||
then
|
||||
PR_LINK=$(sed -E "s@Merge .*#([0-9]+).*\$@[#\1]\(http://github.com/YunoHost/$REPO/pull/\1\)@g" <<< "$SUBJECT")
|
||||
BODY="$(git show -s "$COMMIT" --pretty="%b")"
|
||||
echo " - $BODY ($PR_LINK)"
|
||||
# PRs merged via stash
|
||||
elif grep -q " (#[0-9]*)$" <<< "$SUBJECT"
|
||||
then
|
||||
SUBJECT=$(sed -E "s@(.*) \(#([0-9]*)\)\$@\1 ([#\2]\(http://github.com/YunoHost/$REPO/pull/\2\))@g" <<< "$SUBJECT")
|
||||
echo " - $SUBJECT"
|
||||
# Other "direct" commits
|
||||
else
|
||||
echo " - $SUBJECT ($COMMIT)"
|
||||
fi
|
||||
done \
|
||||
| sed -E "/Co-authored-by: .* <.*>/d" \
|
||||
| grep -v "Translations update from Weblate" \
|
||||
| grep -v "Translated using Weblate" \
|
||||
| grep -v ":art: Format Python code" \
|
||||
| tac
|
||||
|
||||
TRANSLATIONS=$(git log "$PREVIOUS_TAG"... -n 10000 --pretty=format:"%s" \
|
||||
| grep "Translated using Weblate" \
|
||||
| sed -E "s/Translated using Weblate \((.*)\)/\1/g" \
|
||||
| sort | uniq | tr '\n' ', ' | sed -e 's/,$//g' -e 's/,/, /g')
|
||||
[[ -z "$TRANSLATIONS" ]] || echo " - i18n: Translations updated for $TRANSLATIONS"
|
||||
|
||||
echo ""
|
||||
CONTRIBUTORS=$(git log -n10 --pretty=format:'%Cred%h%Creset %C(bold blue)(%an) %Creset%Cgreen(%cr)%Creset - %s %C(yellow)%d%Creset' --abbrev-commit "$PREVIOUS_TAG"... -n 10000 --pretty=format:"%an" \
|
||||
| sort | uniq | grep -v "$ME" | grep -vi 'yunohost-bot\|YunoHost bot\|weblate' \
|
||||
| tr '\n' ', ' | sed -e 's/,$//g' -e 's/,/, /g')
|
||||
[[ -z "$CONTRIBUTORS" ]] || echo " Thanks to all contributors <3 ! ($CONTRIBUTORS)"
|
||||
echo ""
|
||||
echo " -- $ME <$EMAIL> $(date -R)"
|
||||
echo ""
|
||||
|
||||
echo "===================================="
|
||||
echo "To complete the release"
|
||||
echo "===================================="
|
||||
[[ -n "$INCR_VERSION_TYPE" ]] || \
|
||||
cat << EOF
|
||||
- Fix the version number (or call this script with 'patch', 'minor' or 'medium' as first argument)
|
||||
- 'patch' is meant for shameful bugfixes like typo breaking everything, need fix ASAP
|
||||
- 'minor' for regular iterations on YunoHost with small features, minor fixes/improvements
|
||||
- 'medium' typically when releasing a bunch of important, major-ish changes (not counting Debian versions which is the first number)
|
||||
EOF
|
||||
[[ -n "$RELEASE_TYPE" ]] || \
|
||||
echo "- Confirm that this is still a '$NEW_VERSION_TYPE' release (you can also specify 'stable' or 'testing' as second arg to this command)"
|
||||
|
||||
cat << EOF
|
||||
- Copypasta this new changelog to the top of debian/changelog, beware of formatting, empty line, leading/trailing spaces...
|
||||
- Re-read carefully the changelog and smooth the messages to:
|
||||
- cleanup bumpy syntax/formatting
|
||||
- each line give a pretty good idea of what this is about without opening the commit/PR... Ideally at least prefix them with the general topic (eg 'apps:', 'dns:', 'nginx:', ...)
|
||||
- possibly trim stuff that are way too technical or irrelevant (typo fixes, syntax updates from bot, purely test/quality fixes, ...)
|
||||
- Conclude with:
|
||||
NEW_VERSION="$NEW_VERSION"
|
||||
git commit debian/changelog -m "Update changelog for \$NEW_VERSION"
|
||||
git tag debian/\$NEW_VERSION
|
||||
git push origin $(git branch --show-current) --tags
|
||||
- Connect to the infra' 'repo' machine, in vinaigrette directory
|
||||
- Edit and run the 'release' script
|
||||
EOF
|
||||
|
||||
# PR links can be converted to regular texts using : sed -E 's@\[(#[0-9]*)\]\([^ )]*\)@\1@g'
|
||||
# Or readded with sed -E 's@#([0-9]*)@[YunoHost#\1](https://github.com/yunohost/yunohost/pull/\1)@g' | sed -E 's@\((\w+)\)@([YunoHost/\1](https://github.com/yunohost/yunohost/commit/\1))@g'
|
||||
303
maintenance/missing_i18n_keys.py
Executable file
303
maintenance/missing_i18n_keys.py
Executable file
@@ -0,0 +1,303 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2024 YunoHost Contributors
|
||||
#
|
||||
# This file is part of YunoHost (see https://yunohost.org)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
from typing import Generator
|
||||
|
||||
import yaml
|
||||
|
||||
###############################################################################
|
||||
# Find used keys in python code #
|
||||
###############################################################################
|
||||
|
||||
|
||||
def find_expected_string_keys(project: Path) -> Generator[str, None, None]:
|
||||
# Try to find :
|
||||
# m18n.n( "foo"
|
||||
# YunohostError("foo"
|
||||
# YunohostValidationError("foo"
|
||||
# # i18n: foo
|
||||
regex_m18n = re.compile(r"m18n\.n\(\n*\s*[\"\'](\w+)[\"\']")
|
||||
regex_ynherr = re.compile(r"YunohostError\(\n*\s*[\'\"](\w+)[\'\"]")
|
||||
regex_ynhxerr = re.compile(
|
||||
r"Yunohost(?:Validation|Authentication)Error\(\n*\s*[\'\"](\w+)[\'\"]"
|
||||
)
|
||||
regex_comment = re.compile(r"# i18n: [\'\"]?(\w+)[\'\"]?")
|
||||
|
||||
srcdir = project / "src"
|
||||
python_files: list[Path] = [
|
||||
*srcdir.rglob("*.py"),
|
||||
*srcdir.rglob("*.py.disabled"),
|
||||
project / "bin" / "yunohost",
|
||||
]
|
||||
|
||||
for file in python_files:
|
||||
content = file.read_text()
|
||||
for regex in [regex_m18n, regex_ynherr, regex_ynhxerr, regex_comment]:
|
||||
for match in regex.findall(content):
|
||||
if not match.endswith("_"):
|
||||
yield match
|
||||
|
||||
# For each diagnosis, try to find strings like "diagnosis_stuff_foo" (c.f. diagnosis summaries)
|
||||
# Also we expect to have "diagnosis_description_<name>" for each diagnosis
|
||||
regex_diagnosis = re.compile(r"[\"\'](diagnosis_[a-z]+_\w+)[\"\']")
|
||||
|
||||
diagnoser_files: list[Path] = [
|
||||
*(srcdir / "diagnosers").glob("*.py"),
|
||||
*(srcdir / "diagnosers").glob("*.py.disabled"),
|
||||
]
|
||||
for file in diagnoser_files:
|
||||
if file.name == "__init__.py":
|
||||
continue
|
||||
content = file.read_text()
|
||||
for match in regex_diagnosis.findall(content):
|
||||
if match.endswith("_"):
|
||||
# Ignore some name fragments which are actually concatenated with other stuff..
|
||||
continue
|
||||
yield match
|
||||
|
||||
name = file.name.removesuffix(".disabled").removesuffix(".py")
|
||||
yield f"diagnosis_description_{name.split('-')[-1]}"
|
||||
|
||||
# For each migration, expect to find "migration_description_<name>"
|
||||
migration_files: list[Path] = [
|
||||
*(srcdir / "migrations").glob("0*.py"),
|
||||
*(srcdir / "migrations").glob("0*.py.disabled"),
|
||||
]
|
||||
for file in migration_files:
|
||||
name = file.name.removesuffix(".disabled").removesuffix(".py")
|
||||
yield f"migration_description_{name}"
|
||||
|
||||
# For each default service, expect to find "service_description_<name>"
|
||||
services_yml = project / "conf" / "yunohost" / "services.yml"
|
||||
for service, info in yaml.safe_load(services_yml.open("r")).items():
|
||||
if info is None:
|
||||
continue
|
||||
yield f"service_description_{service}"
|
||||
|
||||
# For all unit operations, expect to find "log_<name>"
|
||||
# A unit operation is created either using the @is_unit_operation decorator
|
||||
# or using OperationLogger(
|
||||
for file in python_files:
|
||||
lines = iter(file.read_text().splitlines())
|
||||
for line in lines:
|
||||
if line.startswith("@is_unit_operation(") and "flash=True" not in line:
|
||||
line = next(lines)
|
||||
funcname = line.removeprefix("def ").split("(")[0]
|
||||
yield f"log_{funcname}"
|
||||
|
||||
regex_logger = re.compile(r"OperationLogger\(\n*\s*[\"\'](\w+)[\"\']")
|
||||
for python_file in python_files:
|
||||
content = open(python_file).read()
|
||||
for match in regex_logger.findall(content):
|
||||
yield f"log_{match}"
|
||||
|
||||
# Keys for the actionmap ...
|
||||
actionsmap_yml = project / "share" / "actionsmap.yml"
|
||||
for category in yaml.safe_load(actionsmap_yml.open("r")).values():
|
||||
if "actions" not in category.keys():
|
||||
continue
|
||||
for action in category["actions"].values():
|
||||
if "arguments" not in action.keys():
|
||||
continue
|
||||
for argument in action["arguments"].values():
|
||||
extra = argument.get("extra")
|
||||
if not extra:
|
||||
continue
|
||||
if "password" in extra:
|
||||
yield extra["password"]
|
||||
if "ask" in extra:
|
||||
yield extra["ask"]
|
||||
if "comment" in extra:
|
||||
yield extra["comment"]
|
||||
if "pattern" in extra:
|
||||
yield extra["pattern"][1]
|
||||
if "help" in extra:
|
||||
yield extra["help"]
|
||||
|
||||
# Hardcoded expected keys ...
|
||||
yield "admin_password" # Not sure that's actually used nowadays...
|
||||
|
||||
for method in ["tar", "copy", "custom"]:
|
||||
yield "backup_applying_method_%s" % method
|
||||
yield "backup_method_%s_finished" % method
|
||||
|
||||
registrar_list = project / "share" / "registrar_list.toml"
|
||||
registrars = tomllib.load(registrar_list.open("rb"))
|
||||
supported_registrars = ["ovh", "gandi", "godaddy"]
|
||||
for registrar in supported_registrars:
|
||||
for key in registrars[registrar].keys():
|
||||
yield f"domain_config_{key}"
|
||||
|
||||
# Domain config panel
|
||||
domain_settings_with_help_key = [
|
||||
"portal_logo",
|
||||
"portal_public_intro",
|
||||
"portal_theme",
|
||||
"portal_user_intro",
|
||||
"search_engine",
|
||||
"custom_css",
|
||||
"dns",
|
||||
"enable_public_apps_page",
|
||||
]
|
||||
domain_section_with_no_name = ["app", "cert_", "mail", "registrar"]
|
||||
config_domain_toml = project / "share" / "config_domain.toml"
|
||||
for panel_key, panel in tomllib.load(config_domain_toml.open("rb")).items():
|
||||
if not isinstance(panel, dict):
|
||||
continue
|
||||
yield f"domain_config_{panel_key}_name"
|
||||
for section_key, section in panel.items():
|
||||
if not isinstance(section, dict):
|
||||
continue
|
||||
if section_key not in domain_section_with_no_name:
|
||||
yield f"domain_config_{section_key}_name"
|
||||
for key, values in section.items():
|
||||
if not isinstance(values, dict):
|
||||
continue
|
||||
yield f"domain_config_{key}"
|
||||
if key in domain_settings_with_help_key:
|
||||
yield f"domain_config_{key}_help"
|
||||
|
||||
# App config panel
|
||||
app_settings_with_help_key = [
|
||||
"logo",
|
||||
"description",
|
||||
"force_upgrade",
|
||||
]
|
||||
config_app_toml = project / "share" / "config_app.toml"
|
||||
for panel_key, panel in tomllib.load(config_app_toml.open("rb")).items():
|
||||
if not isinstance(panel, dict):
|
||||
continue
|
||||
yield f"app_config_{panel_key}_name"
|
||||
for section_key, section in panel.items():
|
||||
if not isinstance(section, dict):
|
||||
continue
|
||||
if section_key != "permissions":
|
||||
yield f"app_config_{section_key}_name"
|
||||
for key, values in section.items():
|
||||
if not isinstance(values, dict) or values.get("visible") is False:
|
||||
continue
|
||||
if section_key == "permissions":
|
||||
key_ = f"permission_{key}"
|
||||
else:
|
||||
key_ = key
|
||||
yield f"app_config_{key_}"
|
||||
if key in app_settings_with_help_key:
|
||||
yield f"app_config_{key_}_help"
|
||||
|
||||
# Global settings
|
||||
# Boring hard-coding because there's no simple other way idk
|
||||
settings_without_help_key = [
|
||||
"passwordless_sudo",
|
||||
"smtp_relay_host",
|
||||
"smtp_relay_password",
|
||||
"smtp_relay_port",
|
||||
"smtp_relay_user",
|
||||
"ssowat_panel_overlay_enabled",
|
||||
"root_password",
|
||||
"root_access_explain",
|
||||
"root_password_confirm",
|
||||
"tls_passthrough_explain",
|
||||
"allow_edit_email",
|
||||
"allow_edit_email_alias",
|
||||
"allow_edit_email_forward",
|
||||
]
|
||||
|
||||
config_global_toml = project / "share" / "config_global.toml"
|
||||
for panel_key, panel in tomllib.load(config_global_toml.open("rb")).items():
|
||||
if not isinstance(panel, dict):
|
||||
continue
|
||||
yield f"global_settings_setting_{panel_key}_name"
|
||||
for section_key, section in panel.items():
|
||||
if not isinstance(section, dict):
|
||||
continue
|
||||
yield f"global_settings_setting_{section_key}_name"
|
||||
for key, values in section.items():
|
||||
if not isinstance(values, dict):
|
||||
continue
|
||||
yield f"global_settings_setting_{key}"
|
||||
if key not in settings_without_help_key:
|
||||
yield f"global_settings_setting_{key}_help"
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Compare keys used and keys defined #
|
||||
###############################################################################
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("mode", type=str, choices=["check", "fix"])
|
||||
parser.add_argument("--path", type=Path, help="Path to the project")
|
||||
args = parser.parse_args()
|
||||
|
||||
project_dir: Path = args.path or Path(__file__).resolve().parent.parent
|
||||
locale_dir = project_dir / "locales"
|
||||
reference_file = locale_dir / "en.json"
|
||||
|
||||
expected_string_keys = set(find_expected_string_keys(project_dir))
|
||||
keys_defined_for_en = json.load(reference_file.open("r")).keys()
|
||||
keys_defined = set(keys_defined_for_en)
|
||||
|
||||
unused_keys = keys_defined.difference(expected_string_keys)
|
||||
unused_keys = sorted(unused_keys)
|
||||
|
||||
undefined_keys = expected_string_keys.difference(keys_defined)
|
||||
undefined_keys = sorted(undefined_keys)
|
||||
|
||||
if args.mode == "check":
|
||||
# Unused keys are not too problematic, will be automatically
|
||||
# removed by the other autoreformat script,
|
||||
# but still informative to display them
|
||||
if unused_keys:
|
||||
print("Those i18n keys appears unused:")
|
||||
for key in unused_keys:
|
||||
print(f" - {key}")
|
||||
if undefined_keys:
|
||||
print("Those i18n keys should be defined in en.json:")
|
||||
for key in undefined_keys:
|
||||
print(f" - {key}")
|
||||
sys.exit(1)
|
||||
|
||||
if args.mode == "fix":
|
||||
data = json.load(reference_file.open("r"))
|
||||
for key in undefined_keys:
|
||||
data[key] = "FIXME"
|
||||
for key in unused_keys:
|
||||
del data[key]
|
||||
|
||||
with reference_file.open("w") as reference:
|
||||
json.dump(
|
||||
data,
|
||||
reference,
|
||||
indent=4,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
)
|
||||
reference.write("\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
29
maintenance/shfmt.sh
Executable file
29
maintenance/shfmt.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Copyright (c) 2024 YunoHost Contributors
|
||||
#
|
||||
# This file is part of YunoHost (see https://yunohost.org)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
shfmt_args=(
|
||||
-i=4
|
||||
-kp # keep column alignment paddings
|
||||
-sr # redirect operators will be followed by a space
|
||||
-bn # binary ops like && and | may start a line
|
||||
-ci # switch cases will be indented
|
||||
)
|
||||
|
||||
shfmt "${shfmt_args[@]}" "$@"
|
||||
31
maintenance/update_copyright_headers.sh
Executable file
31
maintenance/update_copyright_headers.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Copyright (c) 2024 YunoHost Contributors
|
||||
#
|
||||
# This file is part of YunoHost (see https://yunohost.org)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# To run this you'll need to:
|
||||
#
|
||||
# pip3 install licenseheaders
|
||||
|
||||
licenseheaders \
|
||||
-o "YunoHost Contributors" \
|
||||
-n "YunoHost" \
|
||||
-u "https://yunohost.org" \
|
||||
-t ./agplv3.tpl \
|
||||
--current-year \
|
||||
-f ../src/*.py ../src/{utils,diagnosers,authenticators}/*.py
|
||||
Reference in New Issue
Block a user