Compare commits

..

No commits in common. "dev" and "debian/11.1.0" have entirely different histories.

66 changed files with 412 additions and 377 deletions

View file

@ -8,23 +8,16 @@ jobs:
name: Check / auto apply black
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Check files using the black formatter
uses: psf/black@stable
id: black
uses: rickstaa/action-black@v1
id: action_black
with:
options: "."
black_args: "."
continue-on-error: true
- shell: pwsh
id: check_files_changed
run: |
# Diff HEAD with the previous commit
$diff = git diff
$HasDiff = $diff.Length -gt 0
Write-Host "::set-output name=files_changed::$HasDiff"
- name: Create Pull Request
if: steps.check_files_changed.outputs.files_changed == 'true'
uses: peter-evans/create-pull-request@v6
if: steps.action_black.outputs.is_formatted == 'true'
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
title: "Format Python code with Black"

View file

@ -8,7 +8,7 @@ jobs:
name: Autoreformat locale files
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Apply reformating scripts
id: action_reformat
run: |
@ -18,7 +18,7 @@ jobs:
git diff -w --exit-code
- name: Create Pull Request
if: ${{ failure() }}
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
title: "Reformat locale files"

View file

@ -14,9 +14,9 @@ jobs:
matrix:
python-version: [3.9]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install apt dependencies
@ -34,9 +34,9 @@ jobs:
matrix:
python-version: [3.9]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install tox

52
debian/changelog vendored
View file

@ -1,55 +1,3 @@
moulinette (11.2.1) stable; urgency=low
- repo chores: various black enhancements
- [i18n] Translations updated for Arabic, Basque, Catalan, Chinese (Simplified), Czech, Dutch, English, Esperanto, French, Galician, German, Hindi, Indonesian, Italian, Japanese, Nepali, Norwegian Bokmål, Persian, Polish, Portuguese, Russian, Spanish, Swedish, Turkish, Ukrainian
Thanks to all contributors <3 ! (Alexandre Aubin, Francescc, José M, Metin Bektas, ppr, Psycojoker, rosbeef andino, Tagada, xabirequejo, xaloc33)
-- OniriCorpe <oniricorpe@yunohost.org> Sun, 19 May 2024 23:49:00 +0200
moulinette (11.2) stable; urgency=low
- [i18n] Translations updated for Japanese
Thanks to all contributors <3 ! (motcha)
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 17 Jul 2023 16:32:34 +0200
moulinette (11.1.5) stable; urgency=low
- setup.py: fix version specifier in python_requires, python tooling not happy with * i guess (2373a7fa)
- auth: prevent stupid issue where outdated cookie usage would trigger error 500 intead of 401, resulting in a ~bug after Yunohost self-upgrade and the webadmin is confused about the API not being up again (c06e1a91)
- i18n: Translations updated for Chinese (Simplified), Indonesian, Japanese
Thanks to all contributors <3 ! (liimee, motcha, Neko Nekowazarashi, Poesty Li)
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 10 Jul 2023 21:32:20 +0200
moulinette (11.1.4) stable; urgency=low
- Releasing as stable
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 01 Feb 2023 20:28:56 +0100
moulinette (11.1.2.1) testing; urgency=low
- Handle calling 'yunohost' with no args more gracefully (4c03e16d)
- [i18n] Translations updated for Arabic
Thanks to all contributors <3 ! (ButterflyOfFire)
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 30 Jan 2023 16:34:23 +0100
moulinette (11.1.2) testing; urgency=low
- legacy: Remove the 'global argument' mechanism ... we only use it for --version and it's just batshit overly complicated for what this achieves... (50b19a95, 80873777, 7f4e8b39)
- Dont take lock for read/GET operations ([#327](https://github.com/yunohost/moulinette/pull/327))
- [i18n] Translations updated for Arabic, Basque, Dutch
Thanks to all contributors <3 ! (André Koot, ButterflyOfFire, xabirequejo)
-- Alexandre Aubin <alex.aubin@mailoo.org> Fri, 06 Jan 2023 00:37:23 +0100
moulinette (11.1.0) testing; urgency=low
- Bump version for testing release

View file

@ -43,7 +43,7 @@ class Element:
def __repr__(self):
"""Returns a basic state dump."""
return "Element" + str(self.index) + str(self.attributes)
return 'Element' + str(self.index) + str(self.attributes)
def add(self, line):
"""Adds a line of input to the object.
@ -57,10 +57,10 @@ class Element:
"""
def _valid(line):
return line and not line.startswith("#")
return line and not line.startswith('#')
def _interesting(line):
return line != "objectClass: top"
return line != 'objectClass: top'
if self.is_valid() and not _valid(line):
return True
@ -70,11 +70,11 @@ class Element:
def is_valid(self):
"""Indicates whether a valid entry has been read."""
return len(self.attributes) != 0 and self.attributes[0].startswith("dn: ")
return len(self.attributes) != 0 and self.attributes[0].startswith('dn: ')
def dn(self):
"""Returns the DN for this entry."""
if self.attributes[0].startswith("dn: "):
if self.attributes[0].startswith('dn: '):
return self.attributes[0][4:]
else:
return None
@ -86,12 +86,12 @@ class Element:
Element objects) and returns a string which declares a DOT edge, or an
empty string, if no parent was found.
"""
dn_components = self.dn().split(",")
dn_components = self.dn().split(',')
for i in range(1, len(dn_components) + 1):
parent = ",".join(dn_components[i:])
parent = ','.join(dn_components[i:])
if parent in dnmap:
return " n%d->n%d\n" % (dnmap[parent].index, self.index)
return ""
return ' n%d->n%d\n' % (dnmap[parent].index, self.index)
return ''
def dot(self, dnmap):
"""Returns a text representation of the node and perhaps its parent edge.
@ -99,7 +99,6 @@ class Element:
Args:
- dnmap: dictionary mapping dn names to Element objects
"""
def _format(attributes):
result = [TITLE_ENTRY_TEMPALTE % attributes[0]]
@ -108,12 +107,7 @@ class Element:
return result
return TABLE_TEMPLATE % (
self.index,
"\n ".join(_format(self.attributes)),
self.edge(dnmap),
)
return TABLE_TEMPLATE % (self.index, '\n '.join(_format(self.attributes)), self.edge(dnmap))
class Converter:
"""An LDIF to DOT converter."""
@ -150,11 +144,7 @@ class Converter:
e = Element()
if e.is_valid():
self._append(e)
return BASE_TEMPLATE % (
name,
"".join([e.dot(self.dnmap) for e in self.elements]),
)
return (BASE_TEMPLATE % (name, ''.join([e.dot(self.dnmap) for e in self.elements])))
BASE_TEMPLATE = """\
strict digraph "%s" {
@ -201,13 +191,13 @@ ENTRY_TEMPALTE = """\
"""
if __name__ == "__main__":
if __name__ == '__main__':
if len(sys.argv) > 2:
raise "Expected at most one argument."
raise 'Expected at most one argument.'
elif len(sys.argv) == 2:
name = sys.argv[1]
file = open(sys.argv[1], "r")
file = open(sys.argv[1], 'r')
else:
name = "<stdin>"
name = '<stdin>'
file = sys.stdin
print(Converter().parse(file, name))
print Converter().parse(file, name)

View file

@ -28,20 +28,18 @@
"cannot_open_file": "ليس بالإمكان فتح الملف {file} (السبب : {error})",
"cannot_write_file": "لا يمكن الكتابة في الملف {file} (السبب : {error})",
"unknown_error_reading_file": "طرأ هناك خطأ ما أثناء عملية قراءة الملف {file} (السبب: {error})",
"corrupted_json": "قراءة ملف JSON مُشوّهة مِن {ressource} (السبب : {error})",
"corrupted_json": "قراءة json مُشوّهة مِن {ressource} (السبب : {error})",
"error_writing_file": "طرأ هناك خطأ أثناء الكتابة في الملف {file}: {error}",
"error_removing": "خطأ أثناء عملية حذف {path}: {error}",
"error_changing_file_permissions": "خطأ أثناء عملية تعديل التصريحات لـ {path}: {error}",
"invalid_url": "فشل الاتصال بـ {url}… ربما تكون الخدمة معطلة ، أو أنك غير متصل بشكل صحيح بالإنترنت في IPv4 / IPv6.",
"invalid_url": "خطأ في عنوان الرابط {url} (هل هذا الموقع موجود حقًا ؟)",
"download_ssl_error": "خطأ في الاتصال الآمن عبر الـ SSL أثناء محاولة الربط بـ {url}",
"download_timeout": "{url} استغرق مدة طويلة جدا للإستجابة، فتوقّف.",
"download_unknown_error": "خطأ أثناء عملية تنزيل البيانات مِن {url} : {error}",
"download_bad_status_code": "{url} أعاد رمز الحالة {code}",
"corrupted_yaml": "قراءة مُشوّهة لملف YAML مِن {ressource} (السبب : {error})",
"corrupted_yaml": "قراءة مُشوّهة لنسق yaml مِن {ressource} (السبب : {error})",
"info": "معلومة:",
"warn_the_user_about_waiting_lock_again": "جارٍ الانتظار…",
"warn_the_user_that_lock_is_acquired": "لقد انتهى تنفيذ ذاك الأمر للتوّ ، جارٍ تنفيذ هذا الأمر",
"warn_the_user_about_waiting_lock": "هناك أمر لـ YunoHost قيد التشغيل حاليا. في انتظار انتهاء تنفيذه قبل تشغيل التالي",
"edit_text_question": "{}. تعديل هذا النص؟ [yN]: ",
"corrupted_toml": "قراءة مُشوّهة لملف TOML مِن {ressource} (السبب : {error})"
"warn_the_user_about_waiting_lock": "هناك أمر لـ YunoHost قيد التشغيل حاليا. في انتظار انتهاء تنفيذه قبل تشغيل التالي"
}

View file

@ -1,7 +1,7 @@
{
"argument_required": "Es requereix l'argument «{argument}»",
"argument_required": "Es requereix l'argument {argument}",
"authentication_required": "Es requereix autenticació",
"confirm": "Confirmar {prompt}",
"confirm": "Confirmar{prompt}",
"deprecated_command": "{prog}{command}és obsolet i es desinstal·larà en el futur",
"deprecated_command_alias": "{prog}{old}és obsolet i es desinstal·larà en el futur, utilitzeu {prog}{new}en el seu lloc",
"error": "Error:",
@ -33,7 +33,7 @@
"error_writing_file": "Error al escriure el fitxer {file}: {error}",
"error_removing": "Error al eliminar {path}: {error}",
"error_changing_file_permissions": "Error al canviar els permisos per {path}: {error}",
"invalid_url": "No s'ha pogut connectar a {url}… pot ser que el servei estigui caigut, o que no hi hagi connexió a Internet amb IPv4/IPv6.",
"invalid_url": "URL invàlid {url} (el lloc web existeix?)",
"download_ssl_error": "Error SSL al connectar amb {url}",
"download_timeout": "{url} ha tardat massa en respondre, s'ha deixat d'esperar.",
"download_unknown_error": "Error al baixar dades des de {url}: {error}",
@ -42,6 +42,5 @@
"corrupted_toml": "El fitxer TOML ha estat corromput en la lectura des de {ressource} (motiu: {error})",
"warn_the_user_about_waiting_lock": "Hi ha una altra ordre de YunoHost en execució, s'executarà aquesta ordre un cop l'anterior hagi acabat",
"warn_the_user_about_waiting_lock_again": "Encara en espera…",
"warn_the_user_that_lock_is_acquired": "L'altra ordre tot just ha acabat, ara s'executarà aquesta ordre",
"edit_text_question": "{}. Edita aquest text ? [yN]: "
"warn_the_user_that_lock_is_acquired": "L'altra ordre tot just ha acabat, ara s'executarà aquesta ordre"
}

View file

@ -34,13 +34,13 @@
"error_writing_file": "写入文件{file}失败:{error}",
"error_removing": "删除路径{path}失败:{error}",
"error_changing_file_permissions": "目录{path}权限修改失败:{error}",
"invalid_url": "{url} 连接失败… 可能是服务中断了或者你没有正确连接到IPv4/IPv6的互联网。",
"invalid_url": "URL{url}无效site是否存在",
"download_ssl_error": "连接{url}时发生SSL错误",
"download_timeout": "{url}响应超时,放弃。",
"download_unknown_error": "下载{url}失败:{error}",
"download_bad_status_code": "{url}返回状态码:{code}",
"warn_the_user_that_lock_is_acquired": "另一个命令刚刚完成,现在启动此命令",
"warn_the_user_about_waiting_lock_again": "仍在等待…",
"warn_the_user_about_waiting_lock_again": "还在等...",
"warn_the_user_about_waiting_lock": "目前正在运行另一个YunoHost命令我们在运行此命令之前等待它完成",
"corrupted_toml": "从{ressource}读取的TOML已损坏原因{error}",
"edit_text_question": "{}.编辑此文本?[yN]: "

View file

@ -2,7 +2,7 @@
"password": "Heslo",
"logged_out": "Jste odhlášen/a",
"warn_the_user_that_lock_is_acquired": "Předchozí operace dokončena, nyní spouštíme tuto",
"warn_the_user_about_waiting_lock_again": "Stále čekáme",
"warn_the_user_about_waiting_lock_again": "Stále čekáme...",
"warn_the_user_about_waiting_lock": "Jiná YunoHost operace právě probíhá, před spuštěním této čekáme na její dokončení",
"download_bad_status_code": "{url} vrátil stavový kód {code}",
"download_unknown_error": "Chyba při stahování dat z {url}: {error}",

View file

@ -32,13 +32,13 @@
"cannot_open_file": "Datei {file} konnte nicht geöffnet werden (Ursache: {error})",
"corrupted_yaml": "Beschädigtes YAML gelesen von {ressource} (reason: {error})",
"warn_the_user_that_lock_is_acquired": "Der andere Befehl wurde gerade abgeschlossen, starte jetzt diesen Befehl",
"warn_the_user_about_waiting_lock_again": "Immer noch wartend",
"warn_the_user_about_waiting_lock_again": "Immer noch wartend...",
"warn_the_user_about_waiting_lock": "Ein anderer YunoHost Befehl läuft gerade, wir warten bis er fertig ist, bevor dieser laufen kann",
"download_bad_status_code": "{url} lieferte folgende(n) Status Code(s) {code}",
"download_unknown_error": "Fehler beim Herunterladen von Daten von {url}: {error}",
"download_timeout": "{url} brauchte zu lange zum Antworten, hab aufgegeben.",
"download_ssl_error": "SSL Fehler beim Verbinden zu {url}",
"invalid_url": "Konnte keine Verbindung zu {url} herstellen vielleicht ist der Dienst ausgefallen, oder Sie sind nicht richtig mit dem Internet in IPv4/IPv6 verbunden.",
"invalid_url": "Konnte keine Verbindung zu {url} herstellen... vielleicht ist der Dienst ausgefallen, oder Sie sind nicht richtig mit dem Internet in IPv4/IPv6 verbunden.",
"error_changing_file_permissions": "Fehler beim Ändern der Berechtigungen für {path}: {error}",
"error_removing": "Fehler beim Entfernen {path}: {error}",
"error_writing_file": "Fehler beim Schreiben von Datei {file}: {error}",

View file

@ -36,7 +36,7 @@
"error_writing_file": "Error when writing file {file}: {error}",
"error_removing": "Error when removing {path}: {error}",
"error_changing_file_permissions": "Error when changing permissions for {path}: {error}",
"invalid_url": "Failed to connect to {url}... maybe the service is down, or you are not properly connected to the Internet in IPv4/IPv6.",
"invalid_url": "Failed to connect to {url} ... maybe the service is down, or you are not properly connected to the Internet in IPv4/IPv6.",
"download_ssl_error": "SSL error when connecting to {url}",
"download_timeout": "{url} took too long to answer, gave up.",
"download_unknown_error": "Error when downloading data from {url}: {error}",

View file

@ -1,7 +1,7 @@
{
"password": "Pasvorto",
"warn_the_user_that_lock_is_acquired": "La alia komando ĵus kompletigis, nun komencante ĉi tiun komandon",
"warn_the_user_about_waiting_lock_again": "Ankoraŭ atendanta",
"warn_the_user_about_waiting_lock_again": "Ankoraŭ atendanta...",
"warn_the_user_about_waiting_lock": "Alia komando de YunoHost funkcias ĝuste nun, ni atendas, ke ĝi finiĝos antaŭ ol funkcii ĉi tiu",
"download_bad_status_code": "{url} redonita statuskodo {code}",
"download_unknown_error": "Eraro dum elŝutado de datumoj de {url}: {error}",
@ -16,7 +16,7 @@
"corrupted_json": "Koruptita JSON legis de {ressource} (Kialo: {error})",
"unknown_error_reading_file": "Nekonata eraro dum provi legi dosieron {file} (kialo: {error})",
"cannot_write_file": "Ne povis skribi dosieron {file} (kialo: {error})",
"cannot_open_file": "Ne povis malfermi dosieron {file} (kialo: {error})",
"cannot_open_file": "Ne povis malfermi dosieron {file: s} (kialo: {error: s})",
"websocket_request_expected": "Atendis ret-peto",
"warning": "Averto:",
"values_mismatch": "Valoroj ne kongruas",

View file

@ -32,7 +32,7 @@
"error_writing_file": "Error al escribir el archivo {file}: {error}",
"error_removing": "Error al eliminar {path}: {error}",
"error_changing_file_permissions": "Error al cambiar los permisos para {path}: {error}",
"invalid_url": "Imposible de conectarse a {url} (¿ la URL esta correcta, existe este sitio, o internet esta accesible?).",
"invalid_url": "URL inválida {url} (¿Existe este sitio?).",
"download_ssl_error": "Error SSL al conectar con {url}",
"download_timeout": "{url} tardó demasiado en responder, abandono.",
"download_unknown_error": "Error al descargar datos desde {url} : {error}",
@ -41,7 +41,7 @@
"info": "Información:",
"corrupted_toml": "Lectura corrupta de TOML desde {ressource} (motivo: {error})",
"warn_the_user_that_lock_is_acquired": "La otra orden recién terminó, iniciando esta orden ahora",
"warn_the_user_about_waiting_lock_again": "Aún esperando",
"warn_the_user_about_waiting_lock_again": "Aún esperando...",
"warn_the_user_about_waiting_lock": "Otra orden de YunoHost se está ejecutando ahora, estamos esperando a que termine antes de ejecutar esta",
"edit_text_question": "{}. Editar este texto ? [sN]: "
}

View file

@ -1,6 +1,6 @@
{
"argument_required": "'{argument}' argumentua ezinbestekoa da",
"logged_out": "Saioa amaituta",
"logged_out": "Saioa itxita",
"password": "Pasahitza",
"authentication_required": "Autentifikazioa behar da",
"confirm": "{prompt} baieztatu",
@ -22,14 +22,14 @@
"download_ssl_error": "SSL errorea {url}-(e)ra konektatzean",
"corrupted_toml": "{ressource}-eko/go TOMLa kaltetuta dago (zergatia: {error})",
"warn_the_user_about_waiting_lock": "YunoHosten beste komando bat ari da exekutatzen, horrek amaitu arte gaude zain",
"warn_the_user_about_waiting_lock_again": "Zain...",
"warn_the_user_about_waiting_lock_again": "Zain",
"folder_exists": "Direktorioa existitzen da dagoeneko: '{path}'",
"instance_already_running": "YunoHosten eragiketa bat exekutatzen ari da dagoeneko. Itxaron amaitu arte beste eragiketa bat abiarazi baino lehen.",
"instance_already_running": "YunoHosten eragiketa bat exekutatzen ari da dagoeneko. Mesedez, itxaron amaitu arte beste eragiketa bat abiarazi baino lehen.",
"invalid_usage": "Erabilera okerra, idatzi --help aukerak ikusteko",
"pattern_not_match": "Ez dator ereduarekin bat",
"root_required": "Ezinbestekoa da 'root' izatea eragiketa hau exekutatzeko",
"server_already_running": "Zerbitzari bat martxan dago dagoeneko ataka horretan",
"unable_authenticate": "Ezin da autentifikatu",
"unable_authenticate": "Ezinezkoa izan da autentifikatzea",
"values_mismatch": "Balioak ez datoz bat",
"warning": "Adi:",
"cannot_open_file": "Ezinezkoa izan da {file} fitxategia irekitzea (zergatia: {error})",
@ -42,6 +42,6 @@
"error_writing_file": "Errorea {file} fitxategia idazterakoan: {error}",
"error_removing": "Errorea {path} ezabatzerakoan: {error}",
"download_bad_status_code": "{url} helbideak {code} egoera kodea agertu du",
"invalid_url": "{url}-(e)ra konektatzeak huts egin du... agian zerbitzua ez dago martxan, edo ez zaude IPv4/IPv6 bidez ondo konektatuta internetera.",
"invalid_url": "Ezinezkoa izan da {url}-(e)ra konektatzea… agian zerbitzua ez dago martxan, edo zeu ez zaude IPv4/IPv6 bidez ondo konektatuta internetera.",
"download_timeout": "{url}(e)k denbora gehiegi behar izan du erantzuteko, bertan behera utzi du zerbitzariak."
}

View file

@ -14,13 +14,13 @@
"argument_required": "استدلال '{argument}' ضروری است",
"password": "کلمه عبور",
"warn_the_user_that_lock_is_acquired": "فرمان دیگر به تازگی تکمیل شده است ، اکنون این دستور را شروع کنید",
"warn_the_user_about_waiting_lock_again": "هنوز در انتظار",
"warn_the_user_about_waiting_lock_again": "هنوز در انتظار...",
"warn_the_user_about_waiting_lock": "یکی دیگر از دستورات YunoHost در حال اجرا است ، ما منتظر هستیم تا قبل از اجرای این دستور به پایان برسد",
"download_bad_status_code": "{url} کد وضعیّت بازگشتی {code}",
"download_unknown_error": "خطا هنگام بارگیری داده ها از {url}: {error}",
"download_timeout": "پاسخ {url} خیلی طول کشید ، منصرف شو.",
"download_ssl_error": "خطای SSL هنگام اتصال به {url}",
"invalid_url": "اتصال به {url} انجام نشد شاید سرویس خاموش باشد یا در IPv4/IPv6 به درستی به اینترنت متصل نشده باشید.",
"invalid_url": "اتصال به {url} انجام نشد ... شاید سرویس خاموش باشد یا در IPv4/IPv6 به درستی به اینترنت متصل نشده باشید.",
"error_changing_file_permissions": "خطا هنگام تغییر مجوزهای {path}: {error}",
"error_removing": "خطا هنگام حذف {path}: {error}",
"error_writing_file": "خطا هنگام نوشتن فایل {file}: {error}",

View file

@ -4,11 +4,11 @@
"confirm": "Confirmez {prompt}",
"deprecated_command": "'{prog} {command}' est déprécié et sera bientôt supprimé",
"deprecated_command_alias": "'{prog} {old}' est déprécié et sera bientôt supprimé, utilisez '{prog} {new}' à la place",
"error": "Erreur:",
"error": "Erreur :",
"file_not_exist": "Le fichier '{path}' n'existe pas",
"folder_exists": "Le dossier existe déjà: '{path}'",
"folder_exists": "Le dossier existe déjà : '{path}'",
"instance_already_running": "Une instance est déjà en cours d'exécution, merci d'attendre sa fin avant d'en lancer une autre.",
"invalid_argument": "Argument '{argument}' incorrect: {error}",
"invalid_argument": "Argument '{argument}' incorrect : {error}",
"invalid_usage": "Utilisation erronée, utilisez --help pour accéder à l'aide",
"logged_in": "Connecté",
"logged_out": "Déconnecté",
@ -16,32 +16,32 @@
"operation_interrupted": "Opération interrompue",
"password": "Mot de passe",
"pattern_not_match": "Ne correspond pas au motif",
"root_required": "Vous devez avoir les droits d'administration pour exécuter cette action",
"root_required": "Vous devez être super-utilisateur pour exécuter cette action",
"server_already_running": "Un serveur est déjà en cours d'exécution sur ce port",
"success": "Succès!",
"success": "Succès !",
"unable_authenticate": "Impossible de vous authentifier",
"unknown_group": "Le groupe '{group}' est inconnu",
"unknown_user": "Le compte '{user}' est inconnu",
"unknown_user": "L'utilisateur '{user}' est inconnu",
"values_mismatch": "Les valeurs ne correspondent pas",
"warning": "Attention:",
"warning": "Attention :",
"websocket_request_expected": "Une requête WebSocket est attendue",
"cannot_open_file": "Impossible d'ouvrir le fichier {file} (raison: {error})",
"cannot_write_file": "Ne peut pas écrire le fichier {file} (raison: {error})",
"unknown_error_reading_file": "Erreur inconnue en essayant de lire le fichier {file} (raison:{error})",
"corrupted_json": "Fichier JSON corrompu en lecture depuis {ressource} (raison: {error})",
"error_writing_file": "Erreur en écrivant le fichier {file}: {error}",
"error_removing": "Erreur lors de la suppression {path}: {error}",
"error_changing_file_permissions": "Erreur lors de la modification des autorisations pour {path}: {error}",
"invalid_url": "Impossible de se connecter à {url}... peut-être que le service est en panne ou que vous n'êtes pas correctement connecté à Internet en IPv4/IPv6.",
"cannot_open_file": "Impossible d'ouvrir le fichier {file} (raison : {error})",
"cannot_write_file": "Ne peut pas écrire le fichier {file} (raison : {error})",
"unknown_error_reading_file": "Erreur inconnue en essayant de lire le fichier {file} (raison :{error})",
"corrupted_json": "Fichier JSON corrompu en lecture depuis {ressource} (raison : {error})",
"error_writing_file": "Erreur en écrivant le fichier {file} : {error}",
"error_removing": "Erreur lors de la suppression {path} : {error}",
"error_changing_file_permissions": "Erreur lors de la modification des autorisations pour {path} : {error}",
"invalid_url": "Impossible de se connecter à {url}... peut-être que le service est hors service/indisponible/interrompu, ou que vous n'êtes pas correctement connecté à Internet en IPv4/IPv6.",
"download_ssl_error": "Erreur SSL lors de la connexion à {url}",
"download_timeout": "{url} a pris trop de temps pour répondre: abandon.",
"download_unknown_error": "Erreur lors du téléchargement des données à partir de {url}: {error}",
"download_timeout": "{url} a pris trop de temps pour répondre : abandon.",
"download_unknown_error": "Erreur lors du téléchargement des données à partir de {url} : {error}",
"download_bad_status_code": "{url} renvoie le code d'état {code}",
"corrupted_yaml": "Fichier YAML corrompu en lecture depuis {ressource} (raison: {error})",
"info": "Info:",
"corrupted_toml": "Fichier TOML corrompu en lecture depuis {ressource} (raison: {error})",
"corrupted_yaml": "Fichier YAML corrompu en lecture depuis {ressource} (raison : {error})",
"info": "Info :",
"corrupted_toml": "Fichier TOML corrompu en lecture depuis {ressource} (raison : {error})",
"warn_the_user_about_waiting_lock": "Une autre commande YunoHost est actuellement en cours, nous attendons qu'elle se termine avant de démarrer celle là",
"warn_the_user_about_waiting_lock_again": "Toujours en attente...",
"warn_the_user_that_lock_is_acquired": "La commande précédente vient de se terminer, lancement de cette nouvelle commande",
"edit_text_question": "{}. Modifier ce texte? [yN]: "
"edit_text_question": "{}. Modifier ce texte ? [yN] : "
}

View file

@ -26,13 +26,13 @@
"not_logged_in": "Non iniciaches sesión",
"logged_in": "Sesión iniciada",
"warn_the_user_that_lock_is_acquired": "O outro comando rematou, agora executarase este",
"warn_the_user_about_waiting_lock_again": "Agardando",
"warn_the_user_about_waiting_lock_again": "Agardando...",
"warn_the_user_about_waiting_lock": "Estase executando outro comando de YunoHost neste intre, estamos agardando a que remate para executar este",
"download_bad_status_code": "{url} devolveu o código de estado {code}",
"download_unknown_error": "Erro ao descargar os datos desde {url}: {error}",
"download_timeout": "{url} está tardando en responder, deixámolo.",
"download_ssl_error": "Erro SSL ao conectar con {url}",
"invalid_url": "Fallou a conexión con {url}... pode que o servizo estea caído, ou que non teñas conexión a Internet con IPv4/IPv6.",
"invalid_url": "Fallou a conexión con {url} ... pode que o servizo esté caído, ou que non teñas conexión a Internet con IPv4/IPv6.",
"error_changing_file_permissions": "Erro ao cambiar os permisos de {path}: {error}",
"error_removing": "Erro ao eliminar {path}: {error}",
"error_writing_file": "Erro ao escribir o ficheiro {file}: {error}",

View file

@ -1 +0,0 @@
{}

View file

@ -1,47 +1,19 @@
{
"argument_required": "Argumen '{argument}' dibutuhkan",
"authentication_required": "Autentikasi dibutuhkan",
"authentication_required": "Otentikasi dibutuhkan",
"deprecated_command": "'{prog} {command}' sudah usang dan akan dihapus nanti",
"logged_out": "Berhasil keluar",
"password": "Kata sandi",
"deprecated_command_alias": "'{prog} {old}' sudah usang dan akan dihapus nanti, gunakan '{prog} {new}'",
"deprecated_command_alias": "'{prog} {old}' sudah usang dan akan dihapus nanti, gunakan '{prog} {new}' saja",
"info": "Informasi:",
"instance_already_running": "Sudah ada tindakan YunoHost yang sedang berjalan. Tunggu itu selesai sebelum menjalankan yang lain.",
"instance_already_running": "Sudah ada operasi YunoHost yang sedang berjalan. Tunggu itu selesai sebelum menjalankan yang lain.",
"logged_in": "Berhasil masuk",
"warning": "Peringatan:",
"cannot_open_file": "Tidak dapat membuka berkas {file} (alasan: {error})",
"error_removing": "Terjadi galat ketika menghapus {path}: {error}",
"error_removing": "Terjadi kesalahan ketika menghapus {path}: {error}",
"success": "Berhasil!",
"warn_the_user_about_waiting_lock": "Perintah YunoHost lain sedang berjalan saat ini, kami sedang menunggu itu selesai sebelum menjalankan yang ini",
"warn_the_user_about_waiting_lock_again": "Masih menunggu...",
"unable_authenticate": "Tidak dapat mengautentikasi",
"warn_the_user_that_lock_is_acquired": "Perintah yang tadi baru saja selesai, akan memulai perintah ini",
"server_already_running": "Sebuah peladen telah berjalan di porta tersebut",
"unknown_group": "Kelompok '{group}' tidak diketahui",
"unknown_user": "Pengguna '{user}' tidak diketahui",
"values_mismatch": "Nnilai berbeda",
"cannot_write_file": "Tidak dapat menyimpan berkas {file} (alasan: {error})",
"unknown_error_reading_file": "Galat yang tidak diketahui ketika membaca berkas {file} (alasan: {error})",
"invalid_url": "Gagal terhubung dengan {url}... mungkin layanan tersebut sedang turun, atau Anda tidak terhubung dengan benar ke internet di IPv4/IPv6.",
"download_timeout": "{url} memakan waktu yang lama untuk menjawab, menyerah.",
"download_unknown_error": "Galat ketika mengunduh data dari {url}: {error}",
"download_bad_status_code": "{url} menjawab dengan kode status {code}",
"confirm": "Konfirmasi {prompt}",
"edit_text_question": "{}. Sunting teks ini ? [yN]: ",
"error": "Galat:",
"file_not_exist": "Berkas tidak ada: '{path}'",
"folder_exists": "Berkas sudah ada: '{path}'",
"invalid_argument": "Argumen tidak valid '{argument}': {error}",
"invalid_usage": "Tidak valid, ikutkan --help untuk melihat bantuan",
"not_logged_in": "Anda belum masuk",
"operation_interrupted": "Tindakan terganggu",
"error_writing_file": "Galat ketika menyimpan berkas {file}: {error}",
"error_changing_file_permissions": "Galat ketika mengubah izin untuk {path}: {error}",
"download_ssl_error": "Galat SSL ketika menghubungi {url}",
"pattern_not_match": "Tidak cocok dengan pola",
"root_required": "Anda harus berada di root untuk melakukan tindakan ini",
"corrupted_yaml": "Pembacaan rusak untuk YAML {ressource} (alasan: {error})",
"corrupted_toml": "Pembacaan rusak untuk TOML {ressource} (alasan: {error})",
"corrupted_json": "Pembacaan rusak untuk JSON {ressource} (alasan: {error})",
"websocket_request_expected": "Mengharapkan permintaan WebSocket"
"unable_authenticate": "Tidak dapat mengotentikasi",
"warn_the_user_that_lock_is_acquired": "Perintah yang tadi baru saja selesai, akan memulai perintah ini"
}

View file

@ -33,14 +33,14 @@
"error_writing_file": "Errore durante la scrittura del file {file}: {error}",
"error_removing": "Errore durante la rimozione {path}: {error}",
"error_changing_file_permissions": "Errore durante il cambio di permessi per {path}: {error}",
"invalid_url": "Fallita connessione a {url} magari il servizio è down, o non sei connesso correttamente ad internet con IPv4/IPv6.",
"invalid_url": "Fallita connessione a {url}... magari il servizio è down, o non sei connesso correttamente ad internet con IPv4/IPv6.",
"download_ssl_error": "Errore SSL durante la connessione a {url}",
"download_timeout": "{url} ci ha messo troppo a rispondere, abbandonato.",
"download_unknown_error": "Errore durante il download di dati da {url} : {error}",
"download_bad_status_code": "{url} ha restituito il codice di stato {code}",
"info": "Info:",
"warn_the_user_that_lock_is_acquired": "L'altro comando è appena completato, ora avvio questo comando",
"warn_the_user_about_waiting_lock_again": "Sto ancora aspettando",
"warn_the_user_about_waiting_lock_again": "Sto ancora aspettando ...",
"warn_the_user_about_waiting_lock": "Un altro comando YunoHost è in esecuzione in questo momento, stiamo aspettando che finisca prima di eseguire questo",
"corrupted_toml": "TOML corrotto da {ressource} (motivo: {error})",
"edit_text_question": "{}. Modificare il testo? [yN]: "

View file

@ -1,47 +0,0 @@
{
"logged_out": "ログアウトしました",
"password": "パスワード",
"argument_required": "引数 '{argument}' が必要です",
"authentication_required": "認証が必要",
"confirm": "{prompt}の確認",
"deprecated_command": "'{prog} {command}' は非推奨であり、将来削除される予定です",
"deprecated_command_alias": "'{prog} {old}' は非推奨であり、今後削除される予定です。代わりに '{prog} {new}' を使用してください",
"edit_text_question": "{}.このテキストを編集しますか?[yN]: ",
"error": "エラー:",
"info": "インフォメーション:",
"download_unknown_error": "{url}からデータをダウンロードする際のエラー:{error}",
"download_bad_status_code": "{url}は状態コード {code} を返しました",
"warn_the_user_about_waiting_lock": "別のYunoHostコマンドが現在実行されているため、完了するのを待っています",
"warn_the_user_about_waiting_lock_again": "まだ待っています…",
"warn_the_user_that_lock_is_acquired": "他のコマンドが完了しました。このコマンドが開始されました",
"file_not_exist": "ファイルが存在しません: '{path}'",
"folder_exists": "フォルダは既に存在します: '{path}'",
"instance_already_running": "YunoHost 操作が既に実行されています。他の操作が完了するのを待ってください。",
"invalid_argument": "無効な引数 '{argument}': {error}",
"invalid_usage": "無効な使用法です。--help を渡してヘルプを表示します",
"logged_in": "ログイン済み",
"not_logged_in": "ログインしていません",
"operation_interrupted": "操作が中断されました",
"pattern_not_match": "パターンと一致しない",
"root_required": "このアクションを実行するには、rootである必要があります",
"server_already_running": "サーバーはそのポートで既に実行されています",
"success": "成功!",
"unable_authenticate": "認証できません",
"unknown_group": "不明な '{group}' グループ",
"unknown_user": "不明な '{user}' ユーザー",
"values_mismatch": "値が一致しない",
"warning": "警告:",
"websocket_request_expected": "WebSocket 要求が必要です",
"cannot_open_file": "ファイル{file}を開けませんでした(理由:{error})",
"cannot_write_file": "ファイル {file}を書き込めませんでした (理由: {error})",
"unknown_error_reading_file": "ファイル{file}を読み取ろうとしているときに不明なエラーが発生しました(理由:{error})",
"corrupted_json": "{ressource}から読み取られたJSONは破損していました(理由:{error})",
"corrupted_yaml": "破損した YAML が{ressource}から読み取られました (理由: {error})",
"corrupted_toml": "破損した TOML が{ressource}から読み取られました (理由: {error})",
"error_writing_file": "ファイル{file}書き込み時のエラー:{error}",
"error_removing": "{path}を削除するときのエラー:{error}",
"error_changing_file_permissions": "{path}のアクセス許可変更時のエラー: {error}",
"invalid_url": "{url}に接続できませんでした…サービスがダウンしているか、IPv4 / IPv6でインターネットに正しく接続されていない可能性があります。",
"download_ssl_error": "{url}への接続時のSSLエラー",
"download_timeout": "{url}は応答に時間がかかりすぎたため、あきらめました。"
}

View file

@ -1 +0,0 @@
{}

View file

@ -1 +0,0 @@
{}

View file

@ -7,7 +7,7 @@
"unknown_user": "Ukjent '{user}' bruker",
"unknown_group": "Ukjent '{group}' gruppe",
"unable_authenticate": "Kunne ikke identitetsbekrefte",
"success": "Vellykket!",
"success": "Vellykket.",
"operation_interrupted": "Operasjon forstyrret",
"not_logged_in": "Du er ikke innlogget",
"logged_in": "Innlogget",

View file

@ -2,10 +2,10 @@
"argument_required": "Argument {argument} is vereist",
"authentication_required": "Aanmelding vereist",
"confirm": "Bevestig {prompt}",
"error": "Fout:",
"error": "Fout",
"file_not_exist": "Bestand bestaat niet: '{path}'",
"folder_exists": "Deze map bestaat al: '{path}'",
"instance_already_running": "Er is al een bewerking bezig. Even geduld tot deze afgesloten is, alvorens een andere te starten.",
"instance_already_running": "Er is al een instantie actief, bedankt om te wachten tot deze afgesloten is alvorens een andere te starten.",
"invalid_argument": "Ongeldig argument '{argument}': {error}",
"invalid_usage": "Ongeldig gebruik, doe --help om de hulptekst te lezen",
"logged_in": "Ingelogd",
@ -28,20 +28,20 @@
"cannot_open_file": "Niet mogelijk om bestand {file} te openen (reden: {error})",
"cannot_write_file": "Niet gelukt om bestand {file} te schrijven (reden: {error})",
"unknown_error_reading_file": "Ongekende fout tijdens het lezen van bestand {file} (cause:{error})",
"corrupted_json": "Corrupte JSON gelezen van {ressource} (reden: {error})",
"corrupted_json": "Corrupte json gelezen van {ressource} (reden: {error})",
"error_writing_file": "Fout tijdens het schrijven van bestand {file}: {error}",
"error_removing": "Fout tijdens het verwijderen van {path}: {error}",
"error_changing_file_permissions": "Fout tijdens het veranderen van machtiging voor {path}: {error}",
"invalid_url": "Kon niet verbinden met {url}… misschien is de dienst uit de lucht, of ben je niet goed verbonden via IPv4 of IPv6.",
"invalid_url": "Ongeldige URL {url} (bestaat deze website?)",
"download_ssl_error": "SSL fout gedurende verbinding met {url}",
"download_timeout": "{url} neemt te veel tijd om te antwoorden, we geven het op.",
"download_unknown_error": "Fout tijdens het downloaden van data van {url}: {error}",
"download_bad_status_code": "{url} stuurt status code {code}",
"warn_the_user_that_lock_is_acquired": "De andere opdracht is zojuist voltooid; nu wordt nu deze opdracht gestart",
"warn_the_user_about_waiting_lock_again": "Nog steeds aan het wachten",
"warn_the_user_about_waiting_lock_again": "Nog steeds aan het wachten...",
"warn_the_user_about_waiting_lock": "Een ander YunoHost commando wordt uitgevoerd, we wachten tot het gedaan is alovrens dit te starten",
"corrupted_toml": "Ongeldige TOML werd gelezen van {ressource} (reason: {error})",
"corrupted_yaml": "Ongeldig YAML bestand op {ressource} (reden: {error})",
"corrupted_yaml": "Ongeldig YAML bestand op {ressource} (reason: {error})",
"info": "Ter info:",
"edit_text_question": "{}. Deze tekst bewerken ? [yN]: "
}

View file

@ -2,13 +2,13 @@
"logged_out": "Wylogowano",
"password": "Hasło",
"warn_the_user_that_lock_is_acquired": "Inne polecenie właśnie się zakończyło, teraz uruchamiam to polecenie",
"warn_the_user_about_waiting_lock_again": "Wciąż czekam",
"warn_the_user_about_waiting_lock_again": "Wciąż czekam...",
"warn_the_user_about_waiting_lock": "Kolejne polecenie YunoHost jest obecnie uruchomione, czekamy na jego zakończenie przed uruchomieniem tego",
"download_bad_status_code": "{url} zwrócił kod stanu {code}",
"download_unknown_error": "Błąd podczas pobierania danych z {url}: {error}",
"download_timeout": "{url} potrzebował zbyt dużo czasu na odpowiedź, rezygnacja.",
"download_ssl_error": "Błąd SSL podczas łączenia z {url}",
"invalid_url": "Nie udało się połączyć z {url} być może strona nie jest dostępna, lub nie jesteś prawidłowo połączony z Internetem po IPv4/IPv6.",
"invalid_url": "Nie udało się połączyć z {url}... być może strona nie jest dostępna, lub nie jesteś prawidłowo połączony z Internetem po IPv4/IPv6.",
"error_changing_file_permissions": "Błąd podczas zmiany uprawnień dla {path}: {error}",
"error_removing": "Błąd podczas usuwania {path}: {error}",
"error_writing_file": "Błąd podczas zapisywania pliku {file}: {error}",

View file

@ -39,7 +39,7 @@
"corrupted_json": "JSON corrompido lido do {ressource} (motivo: {error})",
"corrupted_yaml": "YAML corrompido lido do {ressource} (motivo: {error})",
"warn_the_user_that_lock_is_acquired": "O outro comando acabou de concluir, agora iniciando este comando",
"warn_the_user_about_waiting_lock_again": "Ainda esperando",
"warn_the_user_about_waiting_lock_again": "Ainda esperando...",
"warn_the_user_about_waiting_lock": "Outro comando YunoHost está sendo executado agora, estamos aguardando o término antes de executar este",
"corrupted_toml": "TOML corrompido lido em {ressource} (motivo: {error})",
"info": "Informações:",

View file

@ -1 +0,0 @@
{}

View file

@ -1,5 +1,5 @@
{
"argument_required": "Требуется аргумент «{argument}»",
"argument_required": "Требуется'{argument}' аргумент",
"authentication_required": "Требуется аутентификация",
"confirm": "Подтвердить {prompt}",
"deprecated_command": "'{prog} {command}' устарела и будет удалена",
@ -10,7 +10,7 @@
"invalid_argument": "Неправильный аргумент '{argument}': {error}",
"logged_in": "Вы вошли",
"logged_out": "Вы вышли из системы",
"not_logged_in": "Вы не вошли в систему",
"not_logged_in": "Вы не залогинились",
"operation_interrupted": "Действие прервано",
"password": "Пароль",
"pattern_not_match": "Не соответствует образцу",
@ -28,7 +28,7 @@
"corrupted_yaml": "Повреждённой YAML получен от {ressource} (причина: {error})",
"error_writing_file": "Ошибка при записи файла {file}: {error}",
"error_removing": "Ошибка при удалении {path}: {error}",
"invalid_url": "Не удалось подключиться к {url}... возможно этот сервис недоступен или вы не подключены к Интернету через IPv4/IPv6.",
"invalid_url": "Не удалось подключиться к {url} ... возможно этот сервис недоступен или вы не подключены к Интернету через IPv4/IPv6.",
"download_ssl_error": "Ошибка SSL при соединении с {url}",
"download_timeout": "Превышено время ожидания ответа от {url}.",
"download_unknown_error": "Ошибка при загрузке данных с {url} : {error}",

View file

@ -1,5 +1,5 @@
{
"warn_the_user_about_waiting_lock_again": "Väntar fortfarande…",
"warn_the_user_about_waiting_lock_again": "Väntar fortfarande …",
"download_bad_status_code": "{url} svarade med statuskod {code}",
"download_timeout": "Gav upp eftersom {url} tog för lång tid på sig att svara.",
"download_ssl_error": "Ett SSL-fel påträffades vid anslutning till {url}",

View file

@ -19,13 +19,13 @@
"warning": "Uyarı:",
"websocket_request_expected": "WebSocket isteği gerekli",
"warn_the_user_that_lock_is_acquired": "Diğer komut şimdi tamamlandı, şimdi bu komutu başlatıyor",
"warn_the_user_about_waiting_lock_again": "Hala bekliyor",
"warn_the_user_about_waiting_lock_again": "Hala bekliyor...",
"warn_the_user_about_waiting_lock": "Başka bir YunoHost komutu şu anda çalışıyor, bunu çalıştırmadan önce bitmesini bekliyoruz",
"download_bad_status_code": "{url} döndürülen durum kodu {code}",
"download_unknown_error": "{url} adresinden veri indirilirken hata oluştu: {error}",
"download_timeout": "{url} yanıtlaması çok uzun sürdü, pes etti.",
"download_ssl_error": "{url} ağına bağlanırken SSL hatası",
"invalid_url": "{url} bağlanılamadı URL çalışmıyor olabilir veya IPv4/IPv6 internete düzgün bir şekilde bağlı değilsiniz.",
"invalid_url": "{url} bağlanılamadı... URL çalışmıyor olabilir veya IPv4/IPv6 internete düzgün bir şekilde bağlı değilsiniz.",
"error_changing_file_permissions": "{path} için izinler değiştirilirken hata oluştu: {error}",
"error_removing": "{path} kaldırılırken hata oluştu: {error}",
"error_writing_file": "{file} dosyası yazılırken hata oluştu: {error}",

View file

@ -1,9 +1,9 @@
{
"password": "Пароль",
"logged_out": "Ви вийшли з системи",
"invalid_url": "Помилка з'єднання із {url} можливо, служба не працює, або ви неправильно під'єднані до Інтернету в IPv4/IPv6.",
"invalid_url": "Помилка з'єднання із {url}... можливо, служба не працює, або ви неправильно під'єднані до Інтернету в IPv4/IPv6.",
"warn_the_user_that_lock_is_acquired": "Інша команда щойно завершилася, тепер запускаємо цю команду",
"warn_the_user_about_waiting_lock_again": "Досі очікуємо",
"warn_the_user_about_waiting_lock_again": "Досі очікуємо...",
"warn_the_user_about_waiting_lock": "Зараз запускається ще одна команда YunoHost, ми чекаємо її завершення, перш ніж запустити цю",
"download_bad_status_code": "{url} повернув код стану {code}",
"download_unknown_error": "Помилка під час завантаження даних з {url}: {error}",

View file

@ -1,37 +0,0 @@
VERSION="11.2.1"
RELEASE="stable"
REPO=$(basename $(git rev-parse --show-toplevel))
REPO_URL=$(git remote get-url origin)
ME=$(git config --get user.name)
EMAIL=$(git config --get user.email)
LAST_RELEASE=$(git tag --list 'debian/11.*' --sort="v:refname" | tail -n 1)
echo "$REPO ($VERSION) $RELEASE; urgency=low"
echo ""
git log $LAST_RELEASE.. -n 10000 --first-parent --pretty=tformat:' - %b%s (%h)' \
| sed -E "s&Merge .*#([0-9]+).*\$& \([#\1]\(http://github.com/YunoHost/$REPO/pull/\1\)\)&g" \
| sed -E "/Co-authored-by: .* <.*>/d" \
| grep -v "Translations update from Weblate" \
| tac
TRANSLATIONS=$(git log $LAST_RELEASE... -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 $LAST_RELEASE... -n 10000 --pretty=format:"%an" \
| sort | uniq | grep -v "$ME" | grep -v 'yunohost-bot' | grep -vi '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 ""
# 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'

View file

@ -39,6 +39,7 @@ class classproperty:
class Moulinette:
_interface = None
def prompt(*args, **kwargs):

View file

@ -18,7 +18,7 @@ from moulinette.core import (
MoulinetteLock,
MoulinetteValidationError,
)
from moulinette.interfaces import BaseActionsMapParser
from moulinette.interfaces import BaseActionsMapParser, TO_RETURN_PROP
from moulinette.utils.log import start_action_logging
from moulinette.utils.filesystem import read_yaml
@ -107,6 +107,7 @@ class CommentParameter(_ExtraParameter):
class AskParameter(_ExtraParameter):
"""
Ask for the argument value if possible and needed.
@ -145,6 +146,7 @@ class AskParameter(_ExtraParameter):
class PasswordParameter(AskParameter):
"""
Ask for the password argument value if possible and needed.
@ -167,6 +169,7 @@ class PasswordParameter(AskParameter):
class PatternParameter(_ExtraParameter):
"""
Check if the argument value match a pattern.
@ -219,6 +222,7 @@ class PatternParameter(_ExtraParameter):
class RequiredParameter(_ExtraParameter):
"""
Check if a required argument is defined or not.
@ -258,6 +262,7 @@ extraparameters_list = [
class ExtraArgumentParser:
"""
Argument validator and parser for the extra parameters.
@ -369,6 +374,7 @@ class ExtraArgumentParser:
class ActionsMap:
"""Validate and process actions defined into an actions map
The actions map defines the features - and their usage - of an
@ -386,6 +392,7 @@ class ActionsMap:
"""
def __init__(self, actionsmap_yml, top_parser, load_only_category=None):
assert isinstance(top_parser, BaseActionsMapParser), (
"Invalid parser class '%s'" % top_parser.__class__.__name__
)
@ -401,6 +408,7 @@ class ActionsMap:
actionsmap_pkl = f"{actionsmap_yml_dir}/.{actionsmap_yml_file}.{actionsmap_yml_stat.st_size}-{actionsmap_yml_stat.st_mtime}.pkl"
def generate_cache():
logger.debug("generating cache for actions map")
# Read actions map from yaml file
@ -456,6 +464,7 @@ class ActionsMap:
@cache
def get_authenticator(self, auth_method):
if auth_method == "default":
auth_method = self.default_authentication
@ -475,6 +484,7 @@ class ActionsMap:
return mod.Authenticator()
def check_authentication_if_required(self, *args, **kwargs):
auth_method = self.parser.auth_method(*args, **kwargs)
if auth_method is None:
@ -505,7 +515,9 @@ class ActionsMap:
tid = arguments.pop("_tid")
arguments = self.extraparser.parse_args(tid, arguments)
want_to_take_lock = self.parser.want_to_take_lock(args, **kwargs)
# Return immediately if a value is defined
if TO_RETURN_PROP in arguments:
return arguments.get(TO_RETURN_PROP)
# Retrieve action information
if len(tid) == 4:
@ -529,7 +541,7 @@ class ActionsMap:
full_action_name = "{}.{}.{}".format(namespace, category, action)
# Lock the moulinette for the namespace
with MoulinetteLock(namespace, timeout, self.enable_lock and want_to_take_lock):
with MoulinetteLock(namespace, timeout, self.enable_lock):
start = time()
try:
mod = __import__(
@ -614,9 +626,13 @@ class ActionsMap:
self.enable_lock = _global.get("lock", True)
self.default_authentication = _global["authentication"][interface_type]
if top_parser.has_global_parser():
top_parser.add_global_arguments(_global["arguments"])
# category_name is stuff like "user", "domain", "hooks"...
# category_values is the values of this category (like actions)
for category_name, category_values in actionsmap.items():
actions = category_values.pop("actions", {})
subcategories = category_values.pop("subcategories", {})
@ -653,17 +669,10 @@ class ActionsMap:
if interface_type in authentication:
action_parser.authentication = authentication[interface_type]
# Disable the locking mechanism for all actions that are 'GET' actions on the api
routes = action_options.get("api")
routes = [routes] if isinstance(routes, str) else routes
if routes and all(route.startswith("GET ") for route in routes):
action_parser.want_to_take_lock = False
else:
action_parser.want_to_take_lock = True
# subcategory_name is like "cert" in "domain cert status"
# subcategory_values is the values of this subcategory (like actions)
for subcategory_name, subcategory_values in subcategories.items():
actions = subcategory_values.pop("actions")
# Get subcategory parser
@ -703,13 +712,5 @@ class ActionsMap:
if interface_type in authentication:
action_parser.authentication = authentication[interface_type]
# Disable the locking mechanism for all actions that are 'GET' actions on the api
routes = action_options.get("api")
routes = [routes] if isinstance(routes, str) else routes
if routes and all(route.startswith("GET ") for route in routes):
action_parser.want_to_take_lock = False
else:
action_parser.want_to_take_lock = True
logger.debug("building parser took %.3fs", time() - start)
return top_parser

View file

@ -11,6 +11,7 @@ logger = logging.getLogger("moulinette.authenticator")
class BaseAuthenticator:
"""Authenticator base representation
Each authenticators must implement an Authenticator class derived
@ -28,6 +29,7 @@ class BaseAuthenticator:
# Each authenticator classes must implement these methods.
def authenticate_credentials(self, credentials):
try:
# Attempt to authenticate
auth_info = self._authenticate_credentials(credentials) or {}

View file

@ -18,6 +18,7 @@ def during_unittests_run():
class Translator:
"""Internationalization class
Provide an internationalization mechanism based on JSON files to
@ -115,6 +116,7 @@ class Translator:
self.default_locale != self.locale
and key in self._translations.get(self.default_locale, {})
):
try:
return self._translations[self.default_locale][key].format(
*args, **kwargs
@ -172,6 +174,7 @@ class Translator:
class Moulinette18n:
"""Internationalization service for the moulinette
Manage internationalization and access to the proper keys translation
@ -243,6 +246,7 @@ class Moulinette18n:
class MoulinetteError(Exception):
http_code = 500
"""Moulinette base exception"""
@ -260,14 +264,17 @@ class MoulinetteError(Exception):
class MoulinetteValidationError(MoulinetteError):
http_code = 400
class MoulinetteAuthenticationError(MoulinetteError):
http_code = 401
class MoulinetteLock:
"""Locker for a moulinette instance
It provides a lock mechanism for a given moulinette instance. It can
@ -314,6 +321,7 @@ class MoulinetteLock:
logger.debug("acquiring lock...")
while True:
lock_pids = self._lock_PIDs()
if self._is_son_of(lock_pids):
@ -381,6 +389,7 @@ class MoulinetteLock:
raise MoulinetteError("root_required")
def _lock_PIDs(self):
if not os.path.isfile(self._lockfile):
return []

View file

@ -5,19 +5,25 @@ import logging
import argparse
import copy
import datetime
from collections import OrderedDict
from collections import deque, OrderedDict
from json.encoder import JSONEncoder
from typing import Optional
from moulinette import m18n
from moulinette.core import MoulinetteError
logger = logging.getLogger("moulinette.interface")
# FIXME : are these even used for anything useful ...
TO_RETURN_PROP = "_to_return"
CALLBACKS_PROP = "_callbacks"
# Base Class -----------------------------------------------------------
class BaseActionsMapParser:
"""Actions map's base Parser
Each interfaces must implement an ActionsMapParser class derived
@ -142,11 +148,97 @@ class BaseActionsMapParser:
"derived class '%s' must override this method" % self.__class__.__name__
)
# Arguments helpers
@staticmethod
def prepare_action_namespace(tid, namespace=None):
"""Prepare the namespace for a given action"""
# Validate tid and namespace
if not isinstance(tid, tuple) and (
namespace is None or not hasattr(namespace, TO_RETURN_PROP)
):
raise MoulinetteError("invalid_usage")
elif not tid:
tid = "_global"
# Prepare namespace
if namespace is None:
namespace = argparse.Namespace()
namespace._tid = tid
return namespace
# Argument parser ------------------------------------------------------
class _CallbackAction(argparse.Action):
def __init__(
self,
option_strings,
dest,
nargs=0,
callback={},
default=argparse.SUPPRESS,
help=None,
):
if not callback or "method" not in callback:
raise ValueError("callback must be provided with at least " "a method key")
super(_CallbackAction, self).__init__(
option_strings=option_strings,
dest=dest,
nargs=nargs,
default=default,
help=help,
)
self.callback_method = callback.get("method")
self.callback_kwargs = callback.get("kwargs", {})
self.callback_return = callback.get("return", False)
@property
def callback(self):
if not hasattr(self, "_callback"):
self._retrieve_callback()
return self._callback
def _retrieve_callback(self):
# Attempt to retrieve callback method
mod_name, func_name = (self.callback_method).rsplit(".", 1)
try:
mod = __import__(mod_name, globals=globals(), level=0, fromlist=[func_name])
func = getattr(mod, func_name)
except (AttributeError, ImportError):
import traceback
traceback.print_exc()
raise ValueError("unable to import method {}".format(self.callback_method))
self._callback = func
def __call__(self, parser, namespace, values, option_string=None):
parser.enqueue_callback(namespace, self, values)
if self.callback_return:
setattr(namespace, TO_RETURN_PROP, {})
def execute(self, namespace, values):
try:
# Execute callback and get returned value
value = self.callback(namespace, values, **self.callback_kwargs)
except Exception as e:
error_message = "cannot get value from callback method " "'{}': {}".format(
self.callback_method, e
)
logger.exception(error_message)
raise MoulinetteError(error_message, raw_msg=True)
else:
if value:
if self.callback_return:
setattr(namespace, TO_RETURN_PROP, value)
else:
setattr(namespace, self.dest, value)
class _ExtendedSubParsersAction(argparse._SubParsersAction):
"""Subparsers with extended properties for argparse
It provides the following additional properties at initialization,
@ -227,8 +319,35 @@ class ExtendedArgumentParser(argparse.ArgumentParser):
)
# Register additional actions
self.register("action", "callback", _CallbackAction)
self.register("action", "parsers", _ExtendedSubParsersAction)
def enqueue_callback(self, namespace, callback, values):
queue = self._get_callbacks_queue(namespace)
queue.append((callback, values))
def dequeue_callbacks(self, namespace):
queue = self._get_callbacks_queue(namespace, False)
for _i in range(len(queue)):
c, v = queue.popleft()
# FIXME: break dequeue if callback returns
c.execute(namespace, v)
try:
delattr(namespace, CALLBACKS_PROP)
except AttributeError:
pass
def _get_callbacks_queue(self, namespace, create=True):
try:
queue = getattr(namespace, CALLBACKS_PROP)
except AttributeError:
if create:
queue = deque()
setattr(namespace, CALLBACKS_PROP, queue)
else:
queue = list()
return queue
def add_arguments(
self, arguments, extraparser, format_arg_names=None, validate_extra=True
):
@ -281,9 +400,11 @@ class ExtendedArgumentParser(argparse.ArgumentParser):
# positionals, optionals and user-defined groups
for action_group in self._action_groups:
# Dirty hack to separate 'subcommands'
# into 'actions' and 'subcategories'
if action_group.title == "subcommands":
# Make a copy of the "action group actions"...
choice_actions = action_group._group_actions[0]._choices_actions
actions_subparser = copy.copy(action_group._group_actions[0])
@ -383,6 +504,7 @@ class PositionalsFirstHelpFormatter(argparse.HelpFormatter):
# wrap the usage parts if it's too long
text_width = self._width - self._current_indent
if len(prefix) + len(usage) > text_width:
# break usage into wrappable parts
part_regexp = r"\(.*?\)+|\[.*?\]+|\S+"
opt_usage = format(optionals, groups)
@ -446,6 +568,7 @@ class PositionalsFirstHelpFormatter(argparse.HelpFormatter):
class JSONExtendedEncoder(JSONEncoder):
"""Extended JSON encoder
Extend default JSON encoder to recognize more types and classes. It will
@ -458,6 +581,7 @@ class JSONExtendedEncoder(JSONEncoder):
"""
def default(self, o):
import pytz # Lazy loading, this takes like 3+ sec on a RPi2 ?!
"""Return a serializable object"""

View file

@ -66,12 +66,14 @@ def filter_csrf(callback):
class LogQueues(dict):
"""Map of session ids to queue."""
pass
class APIQueueHandler(logging.Handler):
"""
A handler class which store logging records into a queue, to be used
and retrieved from the API.
@ -84,6 +86,7 @@ class APIQueueHandler(logging.Handler):
self.actionsmap = None
def emit(self, record):
# Prevent triggering this function while moulinette
# is being initialized with --debug
if not self.actionsmap or len(request.cookies) == 0:
@ -107,6 +110,7 @@ class APIQueueHandler(logging.Handler):
class _HTTPArgumentParser:
"""Argument parser for HTTP requests
Object for parsing HTTP requests into Python objects. It is based
@ -231,6 +235,9 @@ class _HTTPArgumentParser:
return self._parser.parse_args(arg_strings, namespace)
def dequeue_callbacks(self, *args, **kwargs):
return self._parser.dequeue_callbacks(*args, **kwargs)
def _error(self, message):
raise MoulinetteValidationError(message, raw_msg=True)
@ -250,6 +257,7 @@ class _ActionsMapPlugin:
api = 2
def __init__(self, actionsmap, log_queues={}):
self.actionsmap = actionsmap
self.log_queues = log_queues
@ -288,7 +296,7 @@ class _ActionsMapPlugin:
)
# Append routes from the actions map
for m, p in self.actionsmap.parser.routes:
for (m, p) in self.actionsmap.parser.routes:
app.route(p, method=m, callback=self.process)
def apply(self, callback, context):
@ -371,6 +379,7 @@ class _ActionsMapPlugin:
# This is called before each time a route is going to be processed
def authenticate(self, authenticator):
try:
session_infos = authenticator.get_session_cookie()
except Exception:
@ -380,12 +389,13 @@ class _ActionsMapPlugin:
return session_infos
def logout(self):
profile = request.params.get("profile", self.actionsmap.default_authentication)
authenticator = self.actionsmap.get_authenticator(profile)
try:
authenticator.get_session_cookie()
except Exception:
except KeyError:
raise HTTPResponse(m18n.g("not_logged_in"), 401)
else:
# Delete cookie and clean the session
@ -461,6 +471,7 @@ class _ActionsMapPlugin:
else:
return format_for_response(ret)
finally:
# Clean upload directory
# FIXME do that in a better way
global UPLOAD_DIR
@ -484,6 +495,7 @@ class _ActionsMapPlugin:
queue.put(StopIteration)
def display(self, message, style="info"):
profile = request.params.get("profile", self.actionsmap.default_authentication)
authenticator = self.actionsmap.get_authenticator(profile)
s_id = authenticator.get_session_cookie(raise_if_no_session_exists=False)["id"]
@ -508,6 +520,7 @@ class _ActionsMapPlugin:
def moulinette_error_to_http_response(error):
content = error.content()
if isinstance(content, dict):
return HTTPResponse(
@ -544,6 +557,7 @@ def format_for_response(content):
class ActionsMapParser(BaseActionsMapParser):
"""Actions map's Parser for the API
Provide actions map parsing methods for a CLI usage. The parser for
@ -623,6 +637,7 @@ class ActionsMapParser(BaseActionsMapParser):
return parser
def auth_method(self, _, route):
try:
# Retrieve the tid for the route
_, parser = self._parsers[route]
@ -635,11 +650,6 @@ class ActionsMapParser(BaseActionsMapParser):
return parser.authentication
def want_to_take_lock(self, _, route):
_, parser = self._parsers[route]
return getattr(parser, "want_to_take_lock", True)
def parse_args(self, args, **kwargs):
"""Parse arguments
@ -661,6 +671,7 @@ class ActionsMapParser(BaseActionsMapParser):
# TODO: Catch errors?
ret = parser.parse_args(args, ret)
parser.dequeue_callbacks(ret)
return ret
# Private methods
@ -687,6 +698,7 @@ class ActionsMapParser(BaseActionsMapParser):
class Interface:
"""Application Programming Interface for the moulinette
Initialize a HTTP server which serves the API connected to a given
@ -703,6 +715,7 @@ class Interface:
type = "api"
def __init__(self, routes={}, actionsmap=None):
actionsmap = ActionsMap(actionsmap, ActionsMapParser())
# Attempt to retrieve log queues from an APIQueueHandler

View file

@ -208,6 +208,7 @@ def get_locale():
class TTYHandler(logging.StreamHandler):
"""TTY log handler
A handler class which prints logging records for a tty. The record is
@ -273,6 +274,7 @@ class TTYHandler(logging.StreamHandler):
class ActionsMapParser(BaseActionsMapParser):
"""Actions map's Parser for the CLI
Provide actions map parsing methods for a CLI usage. The parser for
@ -287,12 +289,13 @@ class ActionsMapParser(BaseActionsMapParser):
"""
def __init__(
self, parent=None, parser=None, subparser_kwargs=None, top_parser=None
self, parent=None, parser=None, subparser_kwargs=None, top_parser=None, **kwargs
):
super(ActionsMapParser, self).__init__(parent)
if subparser_kwargs is None:
subparser_kwargs = {"title": "categories", "required": False}
self._parser = parser or ExtendedArgumentParser()
self._subparsers = self._parser.add_subparsers(**subparser_kwargs)
self.global_parser = parent.global_parser if parent else None
@ -333,11 +336,7 @@ class ActionsMapParser(BaseActionsMapParser):
parser = self._subparsers.add_parser(
name, description=category_help, help=category_help, **kwargs
)
return self.__class__(
parent=self,
parser=parser,
subparser_kwargs={"title": "subcommands", "required": True},
)
return self.__class__(self, parser, {"title": "subcommands", "required": True})
def add_subcategory_parser(self, name, subcategory_help=None, **kwargs):
"""Add a parser for a subcategory
@ -356,11 +355,7 @@ class ActionsMapParser(BaseActionsMapParser):
help=subcategory_help,
**kwargs,
)
return self.__class__(
parent=self,
parser=parser,
subparser_kwargs={"title": "actions", "required": True},
)
return self.__class__(self, parser, {"title": "actions", "required": True})
def add_action_parser(
self,
@ -393,9 +388,35 @@ class ActionsMapParser(BaseActionsMapParser):
hide_in_help=hide_in_help,
)
def add_global_arguments(self, arguments):
for argument_name, argument_options in arguments.items():
# will adapt arguments name for cli or api context
names = self.format_arg_names(
str(argument_name), argument_options.pop("full", None)
)
self.global_parser.add_argument(*names, **argument_options)
def auth_method(self, args):
ret = self.parse_args(args)
tid = getattr(ret, "_tid", [])
# FIXME? idk .. this try/except is duplicated from parse_args below
# Just to be able to obtain the tid
try:
ret = self._parser.parse_args(args)
except SystemExit:
raise
except Exception as e:
error_message = "unable to parse arguments '{}' because: {}".format(
" ".join(args),
e,
)
logger.exception(error_message)
raise MoulinetteValidationError(error_message, raw_msg=True)
tid = getattr(ret, "_tid", None)
# Ugh that's for yunohost --version ...
if tid is None:
return None
# We go down in the subparser tree until we find the leaf
# corresponding to the tid with a defined authentication
@ -408,14 +429,11 @@ class ActionsMapParser(BaseActionsMapParser):
else:
_p = _p._actions[1]
if tid == []:
return None
raise MoulinetteError(f"Authentication undefined for {tid} ?", raw_msg=True)
def parse_args(self, args, **kwargs):
try:
return self._parser.parse_args(args)
ret = self._parser.parse_args(args)
except SystemExit:
raise
except Exception as e:
@ -425,25 +443,14 @@ class ActionsMapParser(BaseActionsMapParser):
)
logger.exception(error_message)
raise MoulinetteValidationError(error_message, raw_msg=True)
def want_to_take_lock(self, args):
ret = self.parse_args(args)
tid = getattr(ret, "_tid", [])
if len(tid) == 3:
_p = self._subparsers.choices[tid[1]]._actions[1].choices[tid[2]]
elif len(tid) == 4:
_p = (
self._subparsers.choices[tid[1]]
._actions[1]
.choices[tid[2]]
._actions[1]
.choices[tid[3]]
)
return getattr(_p, "want_to_take_lock", True)
else:
self.prepare_action_namespace(getattr(ret, "_tid", None), ret)
self._parser.dequeue_callbacks(ret)
return ret
class Interface:
"""Command-line Interface for the moulinette
Initialize an interface connected to the standard input/output
@ -463,6 +470,7 @@ class Interface:
actionsmap=None,
locales_dir=None,
):
# Set user locale
m18n.set_locale(get_locale())
@ -493,9 +501,6 @@ class Interface:
if output_as and output_as not in ["json", "plain", "none"]:
raise MoulinetteValidationError("invalid_usage")
if not args:
raise MoulinetteValidationError("invalid_usage")
try:
ret = self.actionsmap.process(args, timeout=timeout)
except (KeyboardInterrupt, EOFError):
@ -550,7 +555,9 @@ class Interface:
)
def _prompt(message):
if not is_multiline:
import prompt_toolkit
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.styles import Style

View file

@ -85,6 +85,7 @@ def getHandlersByClass(classinfo, limit=0):
class MoulinetteLogger(Logger):
"""Custom logger class
Extend base Logger class to provide the SUCCESS custom log level with
@ -172,6 +173,7 @@ def getActionLogger(name=None, logger=None, action_id=None):
class ActionFilter:
"""Extend log record for an optionnal action
Filter a given record and look for an `action_id` key. If it is not found

View file

@ -80,6 +80,7 @@ def call_async_output(args, callback, **kwargs):
p = subprocess.Popen(args, **kwargs)
while p.poll() is None:
while True:
try:
callback, message = log_queue.get(True, 1)
@ -200,6 +201,7 @@ def run_commands(cmds, callback=None, separate_stderr=False, shell=True, **kwarg
# Iterate over commands
error = 0
for cmd in cmds:
process = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=_stderr, shell=shell, **kwargs
)

View file

@ -4,7 +4,5 @@ ignore =
E128,
E731,
E722,
# Black formatter conflict
W503,
# Black formatter conflict
E203
W503 # Black formatter conflict
E203 # Black formatter conflict

View file

@ -60,7 +60,7 @@ setup(
license="AGPL",
packages=find_packages(exclude=["test"]),
data_files=[("/usr/share/moulinette/locales", locale_files)],
python_requires=">=3.7.0,<3.10",
python_requires=">=3.7.*, <3.10",
install_requires=install_deps,
tests_require=test_deps,
extras_require=extras,

View file

@ -7,6 +7,21 @@ _global:
authentication:
api: dummy
cli: dummy
arguments:
-v:
full: --version
help: Display Yoloswag versions
action: callback
callback:
method: test.src.testauth.yoloswag_version
return: true
-w:
full: --wersion
help: Not existing function
action: callback
callback:
method: test.src.testauth.not_existing_function
return: true
#############################
# Test Actions #

View file

@ -12,11 +12,13 @@ reference = json.loads(open(locale_folder + "en.json").read())
def fix_locale(locale_file):
this_locale = json.loads(open(locale_folder + locale_file).read())
fixed_stuff = 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 this_locale:
continue

View file

@ -76,6 +76,7 @@ def patch_lock(moulinette):
@pytest.fixture(scope="session", autouse=True)
def moulinette(tmp_path_factory):
import moulinette
import moulinette.core
from moulinette.utils.log import configure_logging
@ -103,6 +104,7 @@ def moulinette(tmp_path_factory):
@pytest.fixture
def moulinette_webapi(moulinette):
from webtest import TestApp
from webtest.app import CookiePolicy

View file

@ -2,6 +2,7 @@ import re
def reformat(lang, transformations):
locale = open(f"locales/{lang}.json").read()
for pattern, replace in transformations.items():
locale = re.compile(pattern).sub(replace, locale)
@ -24,8 +25,8 @@ godamn_spaces_of_hell = [
"\u2008",
"\u2009",
"\u200A",
# "\u202f",
# "\u202F",
"\u202f",
"\u202F",
"\u3000",
]

View file

@ -10,6 +10,7 @@ locale_files.remove("en.json")
reference = json.loads(open(locale_folder + "en.json").read())
for locale_file in locale_files:
print(locale_file)
this_locale = json.loads(
open(locale_folder + locale_file).read(), object_pairs_hook=OrderedDict

View file

@ -13,6 +13,7 @@ session_secret = random_ascii()
class Authenticator(BaseAuthenticator):
"""Dummy authenticator used for tests"""
name = "dummy"
@ -21,12 +22,14 @@ class Authenticator(BaseAuthenticator):
pass
def _authenticate_credentials(self, credentials=None):
if not credentials == self.name:
raise MoulinetteError("invalid_password", raw_msg=True)
return
def set_session_cookie(self, infos):
from bottle import response
assert isinstance(infos, dict)
@ -46,6 +49,7 @@ class Authenticator(BaseAuthenticator):
)
def get_session_cookie(self, raise_if_no_session_exists=True):
from bottle import request
try:
@ -64,6 +68,7 @@ class Authenticator(BaseAuthenticator):
return infos
def delete_session_cookie(self):
from bottle import response
response.set_cookie("moulitest", "", max_age=-1)

View file

@ -13,6 +13,7 @@ session_secret = random_ascii()
class Authenticator(BaseAuthenticator):
"""Dummy authenticator used for tests"""
name = "yoloswag"
@ -21,12 +22,14 @@ class Authenticator(BaseAuthenticator):
pass
def _authenticate_credentials(self, credentials=None):
if not credentials == self.name:
raise MoulinetteError("invalid_password", raw_msg=True)
return
def set_session_cookie(self, infos):
from bottle import response
assert isinstance(infos, dict)
@ -46,6 +49,7 @@ class Authenticator(BaseAuthenticator):
)
def get_session_cookie(self, raise_if_no_session_exists=True):
from bottle import request
try:
@ -64,6 +68,7 @@ class Authenticator(BaseAuthenticator):
return infos
def delete_session_cookie(self):
from bottle import response
response.set_cookie("moulitest", "", max_age=-1)

View file

@ -44,3 +44,7 @@ def testauth_with_extra_str_only(only_a_str):
def testauth_with_type_int(only_an_int):
return only_an_int
def yoloswag_version(*args, **kwargs):
return "666"

View file

@ -161,6 +161,7 @@ def test_required_paremeter_missing_value(iface, caplog):
def test_actions_map_unknown_authenticator(monkeypatch, tmp_path):
from moulinette.interfaces.api import ActionsMapParser
amap = ActionsMap("test/actionsmap/moulitest.yml", ActionsMapParser())

View file

@ -255,6 +255,25 @@ class TestAuthCLI:
assert "invalid_password" in str(exception)
def test_request_with_callback(self, moulinette_cli, capsys, mocker):
mocker.patch("os.isatty", return_value=True)
mocker.patch("prompt_toolkit.prompt", return_value="dummy")
moulinette_cli.run(["--version"], output_as="plain")
message = capsys.readouterr()
assert "666" in message.out
moulinette_cli.run(["-v"], output_as="plain")
message = capsys.readouterr()
assert "666" in message.out
with pytest.raises(MoulinetteError):
moulinette_cli.run(["--wersion"], output_as="plain")
message = capsys.readouterr()
assert "cannot get value from callback method" in message.err
def test_request_with_arg(self, moulinette_cli, capsys, mocker):
mocker.patch("os.isatty", return_value=True)
mocker.patch("prompt_toolkit.prompt", return_value="dummy")

View file

@ -330,6 +330,7 @@ def test_mkdir(tmp_path):
def test_mkdir_with_permission(tmp_path, mocker):
# This test only make sense when not being root
if os.getuid() == 0:
return

View file

@ -13,10 +13,12 @@ reference = json.loads(open(locale_folder + "en.json").read())
def find_inconsistencies(locale_file):
this_locale = json.loads(open(locale_folder + locale_file).read())
# 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 this_locale:
continue

View file

@ -11,6 +11,7 @@ import json
def find_expected_string_keys():
# Try to find :
# m18n.g( "foo"
# MoulinetteError("foo"
@ -68,6 +69,7 @@ def test_undefined_i18n_keys():
def test_unused_i18n_keys():
unused_keys = keys_defined.difference(expected_string_keys)
unused_keys = sorted(unused_keys)

View file

@ -66,6 +66,7 @@ def test_run_shell_kwargs():
def test_call_async_output(test_file):
mock_callback_stdout = mock.Mock()
mock_callback_stderr = mock.Mock()
@ -117,6 +118,7 @@ def test_call_async_output(test_file):
def test_call_async_output_kwargs(test_file, mocker):
mock_callback_stdout = mock.Mock()
mock_callback_stdinfo = mock.Mock()
mock_callback_stderr = mock.Mock()

View file

@ -13,10 +13,12 @@ reference = json.loads(open(locale_folder + "en.json").read())
def find_inconsistencies(locale_file):
this_locale = json.loads(open(locale_folder + locale_file).read())
# 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 this_locale:
continue