Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions readme_generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,3 @@ Add the webhook.service to systemd config, then start it:
```bash
systemctl start the_webhook_service
```

## Translation

It's based on Babel integrated into jinja2 : <https://babel.pocoo.org/en/latest/>

```bash
source venv/bin/activate

# Extract the english sentences from the code, needed if you modified it
pybabel extract --ignore-dirs venv -F babel.cfg -o messages.pot .

# If working on a new locale: initialize it: (in this example: fr)
pybabel init -i messages.pot -d translations -l fr
# Otherwise, update the existing .po:
pybabel update -i messages.pot -d translations
# To update only a specific language: (in this example: fr)
pybabel update -i messages.pot -d translations -l fr

# ... translate stuff in translations/<lang>/LC_MESSAGES/messages.po
# re-run the 'update' command to let Babel properly format the text
# then compile:
pybabel compile -d translations
```
56 changes: 56 additions & 0 deletions readme_generator/README.md.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{% if manifest.id == "example" -%}
# Packaging an app, starting from this example

- (Alternatively to this small tutorial, consider checking out <https://appgenerator.yunohost.org> !)
- Copy this app before working on it, using the ['Use this template'](https://github.com/new?template_name=example_ynh&template_owner=YunoHost) button on the Github repo
- Edit the `manifest.toml` with app specific info
- Edit the `install`, `upgrade`, `remove`, `backup` and `restore` scripts, and any relevant conf files in `conf/`
- Using the [script helpers documentation](https://yunohost.org/packaging_apps_helpers)
- Edit the `change_url` and `config` scripts too, or remove them if you have no use of them
- Add a `LICENSE` file for the package.
- NB: this `LICENSE` file is not meant to necessarily be the same LICENSE as the upstream app - it is only the LICENSE you want this package's code to published with and you can choose it freely! (If you don't know which to choose, we recommend [the AGPL-3](https://www.gnu.org/licenses/agpl-3.0.txt))
- Edit files under the `doc/` directory ([see the page about documenting packages](https://yunohost.org/packaging_app_doc))
- The `README.md` files are to be automatically generated by <https://github.com/YunoHost/apps_tools/blob/main/readme_generator>

---
{% endif -%}

<!--
N.B.: This README was automatically generated by <https://github.com/YunoHost/apps_tools/blob/main/readme_generator>
It shall NOT be edited by hand.
-->

<h1>
<img src="https://raw.githubusercontent.com/YunoHost/apps/master/logos/{{manifest.id}}.png" width="32px" alt="Logo of {{manifest.name}}">
{{ manifest.name }}, packaged for YunoHost
</h1>

{{ manifest.description['en'] }}

{% if manifest.upstream.website %}[![🌐 Official app website](https://img.shields.io/badge/Official_app_website-darkgreen?style=for-the-badge)]({{manifest.upstream.website}})
{% endif %}{% if manifest.upstream.demo %}[![App Demo](https://img.shields.io/badge/App_Demo-blue?style=for-the-badge)]({{manifest.upstream.demo}})
{% endif %}![Version: {{ manifest.version}}](https://img.shields.io/badge/Version-{{manifest.version|replace("-", "--")}}-rgba(0,150,0,1)?style=for-the-badge)

<div align="center">
<a href="https://apps.yunohost.org/app/{{manifest.id}}"><img height="100px" src="https://github.com/YunoHost/yunohost-artwork/raw/refs/heads/main/badges/neopossum-badges/badge_more_info_on_the_appstore.svg"/></a>
<a href="https://github.com/YunoHost-Apps/{{manifest.id}}_ynh/issues"><img height="100px" src="https://github.com/YunoHost/yunohost-artwork/raw/refs/heads/main/badges/neopossum-badges/badge_report_an_issue.svg"/></a>
</div>

## 📦 Developer info

🛠️ Upstream {{manifest.name}} repository: <{{manifest.upstream.code}}>

Pull request are welcome and should target the [`testing` branch](https://github.com/YunoHost-Apps/{{manifest.id}}_ynh/tree/testing).

The `testing` branch can be tested using:
```
# fresh install:
sudo yunohost app install https://github.com/YunoHost-Apps/{{manifest.id}}_ynh/tree/testing

# upgrade an existing install:
sudo yunohost app upgrade {{manifest.id}} -u https://github.com/YunoHost-Apps/{{manifest.id}}_ynh/tree/testing
```

### 📚 App packaging documentation

Please see <https://doc.yunohost.org/packaging_apps> for more information.
1 change: 0 additions & 1 deletion readme_generator/babel.cfg

This file was deleted.

187 changes: 13 additions & 174 deletions readme_generator/make_readme.py
Original file line number Diff line number Diff line change
@@ -1,195 +1,37 @@
#! /usr/bin/env python3

import sys
import os
import argparse
import json
from pathlib import Path
from copy import deepcopy

from typing import Dict, Optional, List, Tuple

import toml
from jinja2 import Environment, FileSystemLoader
from babel.support import Translations
from babel.messages.pofile import PoFileParser
from langcodes import Language

# add apps/tools to sys.path
sys.path.insert(0, str(Path(__file__).parent.parent))

from appslib import get_apps_repo

README_GEN_DIR = Path(__file__).resolve().parent

TRANSLATIONS_DIR = README_GEN_DIR / "translations"


def value_for_lang(values: Dict, lang: str):
if not isinstance(values, dict):
return values
if lang in values:
return values[lang]
elif "en" in values:
return values["en"]
else:
return list(values.values())[0]


def generate_READMEs(app_path: Path, apps_repo_path: Path):
def generate_READMEs(app_path: Path):
if not app_path.exists():
raise Exception("App path provided doesn't exists ?!")

if (app_path / "manifest.json").exists():
manifest = json.load(open(app_path / "manifest.json"))
else:
manifest = toml.load(open(app_path / "manifest.toml"))

upstream = manifest.get("upstream", {})

catalog = toml.load((apps_repo_path / "apps.toml").open(encoding="utf-8"))
from_catalog = catalog.get(manifest["id"], {})

antifeatures_list = toml.load(
(apps_repo_path / "antifeatures.toml").open(encoding="utf-8")
)

if not upstream and not (app_path / "doc" / "DISCLAIMER.md").exists():
print(
"There's no 'upstream' key in the manifest, and doc/DISCLAIMER.md doesn't exists - therefore assuming that we shall not auto-update the README.md for this app yet."
)
return

poparser = PoFileParser({})
poparser.parse((README_GEN_DIR / "messages.pot").open(encoding="utf-8"))

# we only want to translate a README if all strings are translatables so we
# do this loop to detect which language provides a full translation
fully_translated_langs: List[str] = []
for available_translations in os.listdir(TRANSLATIONS_DIR):
translations = Translations.load(TRANSLATIONS_DIR, available_translations)

is_fully_translated = True
for sentence in poparser.catalog:
# ignore empty strings
if not sentence.strip():
continue

if sentence not in translations._catalog:
print(f"The sentence: {repr(sentence)} is not in the target catalog")
is_fully_translated = False
break
manifest = toml.load(open(app_path / "manifest.toml"))

if not translations._catalog[sentence]:
print(f"The sentence: '{repr(sentence)}' is not translated")
is_fully_translated = False
break

if is_fully_translated:
fully_translated_langs.append(available_translations)
else:
print(
"WARNING: skip generating translated README for "
f"{Language(available_translations).language_name()} ({available_translations}) "
"because it is not fully translated yet."
)

fully_translated_langs.sort()
print(
f"Available languages for translation: {', '.join(fully_translated_langs) if fully_translated_langs else []}"
env = Environment(
loader=FileSystemLoader(README_GEN_DIR),
)
template = env.get_template("README.md.j2")

screenshots: List[str] = []

screenshots_dir = app_path / "doc" / "screenshots"
if screenshots_dir.exists():
for entry in sorted(screenshots_dir.iterdir()):
# only pick files (no folder) on the root of 'screenshots'
if not entry.is_file():
continue
# ignore '.gitkeep' or any file whose name begins with a dot
if entry.name.startswith("."):
continue
screenshots.append(str(entry.relative_to(app_path)))

def generate_single_README(lang_suffix: str, lang: str):
env = Environment(
loader=FileSystemLoader(README_GEN_DIR / "templates"),
extensions=["jinja2.ext.i18n"],
)
translations = Translations.load(TRANSLATIONS_DIR, [lang])
env.install_gettext_translations(translations)

template = env.get_template("README.md.j2")

if (app_path / "doc" / f"DESCRIPTION{lang_suffix}.md").exists():
description = (
app_path / "doc" / f"DESCRIPTION{lang_suffix}.md"
).read_text()
# Fallback to english if maintainer too lazy to translate the description
elif (app_path / "doc" / "DESCRIPTION.md").exists():
description = (app_path / "doc" / "DESCRIPTION.md").read_text()
else:
description = None

disclaimer: Optional[str]
if (app_path / "doc" / f"DISCLAIMER{lang_suffix}.md").exists():
disclaimer = (app_path / "doc" / f"DISCLAIMER{lang_suffix}.md").read_text()
# Fallback to english if maintainer too lazy to translate the disclaimer idk
elif (app_path / "doc" / "DISCLAIMER.md").exists():
disclaimer = (app_path / "doc" / "DISCLAIMER.md").read_text()
else:
disclaimer = None
out: str = template.render(manifest=manifest)
(app_path / "README.md").write_text(out)

# TODO: Add url to the documentation... and actually create that documentation :D
antifeatures = {
a: deepcopy(antifeatures_list[a])
for a in from_catalog.get("antifeatures", [])
}
for k, v in antifeatures.items():
antifeatures[k]["title"] = value_for_lang(v["title"], lang)
if manifest.get("antifeatures", {}).get(k, None):
antifeatures[k]["description"] = value_for_lang(
manifest.get("antifeatures", {}).get(k, None), lang
)
else:
antifeatures[k]["description"] = value_for_lang(
antifeatures[k]["description"], lang
)

out: str = template.render(
lang=lang,
upstream=upstream,
description=description,
screenshots=screenshots,
disclaimer=disclaimer,
antifeatures=antifeatures,
manifest=manifest,
)
(app_path / f"README{lang_suffix}.md").write_text(out)

generate_single_README("", "en")

for lang in fully_translated_langs:
generate_single_README("_" + lang, lang)

links_to_other_READMEs = []
for language in fully_translated_langs:
translations = Translations.load(TRANSLATIONS_DIR, [language])
language_name_in_itself = Language.get(language).autonym()
links_to_other_READMEs.append(
(
f"README_{language}.md",
translations.gettext("Read the README in %(language)s")
% {"language": language_name_in_itself},
)
)

env = Environment(loader=FileSystemLoader(README_GEN_DIR / "templates"))
out: str = env.get_template("ALL_README.md.j2").render(
links_to_other_READMEs=links_to_other_READMEs
)
(app_path / "ALL_README.md").write_text(out)
# Delete legacy READMEs
for legacy_README in app_path.glob("README_*.md"):
legacy_README.unlink()
if (app_path / "ALL_README.md").exists():
(app_path / "ALL_README.md").unlink()


def main():
Expand All @@ -199,12 +41,9 @@ def main():
parser.add_argument(
"app_path", type=Path, help="Path to the app to generate/update READMEs for"
)
get_apps_repo.add_args(parser)
args = parser.parse_args()

apps_path = get_apps_repo.from_args(args)

generate_READMEs(args.app_path, apps_path)
generate_READMEs(args.app_path)


if __name__ == "__main__":
Expand Down
Loading