Skip to content

Commit 7c334fd

Browse files
authored
Merge pull request YunoHost#2571 from YunoHost/move_scripts
Move here doc generator scripts from the yunohost repository
2 parents 21403aa + ae3eb8e commit 7c334fd

9 files changed

+747
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
!/docker-compose.yml
88
!/dev/plugins
99
!/markdownlint-rules-grav-pages
10+
!/scripts
File renamed without changes.

scripts/forms_doc_generate.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (c) 2024 YunoHost Contributors
4+
#
5+
# This file is part of YunoHost (see https://yunohost.org)
6+
#
7+
# This program is free software: you can redistribute it and/or modify
8+
# it under the terms of the GNU Affero General Public License as
9+
# published by the Free Software Foundation, either version 3 of the
10+
# License, or (at your option) any later version.
11+
#
12+
# This program is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU Affero General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Affero General Public License
18+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
#
20+
21+
import argparse
22+
import ast
23+
import datetime
24+
import subprocess
25+
from pathlib import Path
26+
27+
from jinja2 import Template
28+
29+
30+
TEMPLATE_FILE = Path(__file__).resolve().parent / "forms_doc_template.md.j2"
31+
32+
def get_current_commit(docdir: Path) -> str:
33+
p = subprocess.Popen(
34+
["git", "rev-parse", "--verify", "HEAD"],
35+
cwd=docdir,
36+
stdout=subprocess.PIPE,
37+
stderr=subprocess.STDOUT,
38+
)
39+
stdout, stderr = p.communicate()
40+
41+
current_commit = stdout.strip().decode("utf-8")
42+
return current_commit
43+
44+
45+
def get_changelog_version(srcdir: Path) -> str:
46+
changelog_file = srcdir / "debian" / "changelog"
47+
version = changelog_file.open("r").readline().split()[1].strip("()")
48+
return version
49+
50+
51+
##############################################################################
52+
53+
54+
def dict_key_first(dict: dict, key) -> dict:
55+
value = dict.pop(key)
56+
return {key: value, **dict}
57+
58+
59+
def list_config_panel(srcdir: Path) -> dict[str, str]:
60+
configpanel_file = srcdir / "src" / "utils" / "configpanel.py"
61+
62+
# NB: This magic is because we want to be able to run this script outside of a YunoHost context,
63+
# in which we cant really 'import' the file because it will trigger a bunch of moulinette/yunohost imports...
64+
tree = ast.parse(configpanel_file.read_text())
65+
66+
classes: dict[str, str] = {}
67+
68+
for cl in reversed(tree.body):
69+
if isinstance(cl, ast.ClassDef):
70+
if cl.name in ["SectionModel", "PanelModel", "ConfigPanelModel"]:
71+
docstring = ast.get_docstring(cl)
72+
assert isinstance(docstring, str)
73+
classes[cl.name.replace("Model", "")] = docstring
74+
75+
return classes
76+
77+
78+
def list_form_options(srcdir: Path) -> dict[str, str]:
79+
configpanel_file = srcdir / "src" / "utils" / "form.py"
80+
81+
# NB: This magic is because we want to be able to run this script outside of a YunoHost context,
82+
# in which we cant really 'import' the file because it will trigger a bunch of moulinette/yunohost imports...
83+
tree = ast.parse(configpanel_file.read_text())
84+
85+
options: dict[str, str] = {}
86+
87+
for cl in tree.body:
88+
if not isinstance(cl, ast.ClassDef):
89+
continue
90+
if not cl.name.endswith("Option"):
91+
continue
92+
if not isinstance(cl.body[0], ast.Expr):
93+
continue
94+
95+
assert isinstance(cl.body[1], ast.AnnAssign)
96+
assert isinstance(cl.body[1].target, ast.Name)
97+
assert isinstance(cl.bases[0], ast.Name)
98+
99+
# Determine the title of the section
100+
if cl.name == "BaseOption":
101+
name = "Common properties"
102+
103+
elif cl.name == "BaseInputOption":
104+
name = "Common inputs properties"
105+
106+
else:
107+
assert cl.body[1].target.id == "type"
108+
assert isinstance(cl.body[1].value, ast.Attribute)
109+
option_type = cl.body[1].value.attr
110+
111+
if option_type == "display_text":
112+
# display_text is kind of legacy x_x
113+
continue
114+
115+
name = f"`{option_type}`"
116+
117+
if cl.bases:
118+
base_type = cl.bases[0].id.replace("Option", "")
119+
base_type = base_type.replace("Base", "").lower()
120+
name += f" ({base_type})"
121+
122+
docstring = ast.get_docstring(cl)
123+
if docstring:
124+
options[name] = docstring
125+
126+
# Dirty hack to have "Common properties" as first and "Common inputs properties" as 2nd in list
127+
options = dict_key_first(options, "Common inputs properties")
128+
options = dict_key_first(options, "Common properties")
129+
return options
130+
131+
132+
def main() -> None:
133+
parser = argparse.ArgumentParser()
134+
parser.add_argument("--input", "-i", type=Path, required=True, help="Input yunohost source directory")
135+
parser.add_argument("--output", "-o", type=Path, required=True)
136+
args = parser.parse_args()
137+
138+
config_panel = list_config_panel(args.input)
139+
options = list_form_options(args.input)
140+
version = get_changelog_version(args.input)
141+
commit = get_current_commit(Path(__file__).parent)
142+
143+
template = Template(TEMPLATE_FILE.read_text())
144+
template.globals["now"] = datetime.datetime.utcnow
145+
146+
result = template.render(
147+
configpanel=config_panel,
148+
options=options,
149+
date=datetime.datetime.now().strftime("%d/%m/%Y"),
150+
version=version,
151+
current_commit=commit,
152+
)
153+
154+
args.output.write_text(result)
155+
156+
157+
if __name__ == "__main__":
158+
main()

scripts/forms_doc_template.md.j2

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---
2+
title: Technical details for config panel structure and form option types
3+
template: docs
4+
taxonomy:
5+
category: docs
6+
routes:
7+
default: '/dev/forms'
8+
---
9+
10+
Doc auto-generated by [this script](https://github.com/YunoHost/doc/blob/{{ current_commit }}/scripts/forms_doc_generate.py) on {{date}} (YunoHost version {{version}})
11+
12+
## Glossary
13+
14+
You may encounter some named types which are used for simplicity.
15+
16+
- `Translation`: a translated property
17+
- used for properties: `ask`, `help` and `Pattern.error`
18+
- a `dict` with locales as keys and translations as values:
19+
20+
```toml
21+
ask.en = "The text in english"
22+
ask.fr = "Le texte en français"
23+
```
24+
25+
It is not currently possible for translators to translate those string in weblate.
26+
27+
- a single `str` for a single english default string
28+
29+
```toml
30+
help = "The text in english"
31+
```
32+
33+
- `JSExpression`: a `str` JS expression to be evaluated to `true` or `false`:
34+
- used for properties: `visible` and `enabled`
35+
- operators availables: `==`, `!=`, `>`, `>=`, `<`, `<=`, `!`, `&&`, `||`, `+`, `-`, `*`, `/`, `%` and `match()`
36+
- `Binding`: bind a value to a file/property/variable/getter/setter/validator
37+
- save the value in `settings.yaml` when not defined
38+
- nothing at all with `"null"`
39+
- a custom getter/setter/validator with `"null"` + a function starting with `get__`, `set__`, `validate__` in `scripts/config`
40+
- a variable/property in a file with `:__FINALPATH__/my_file.php`
41+
- a whole file with `__FINALPATH__/my_file.php`
42+
- `Pattern`: a `dict` with a regex to match the value against and an error message
43+
44+
```toml
45+
pattern.regexp = '^[A-F]\d\d$'
46+
pattern.error = "Provide a room number such as F12: one uppercase and 2 numbers"
47+
# or with translated error
48+
pattern.error.en = "Provide a room number such as F12: one uppercase and 2 numbers"
49+
pattern.error.fr = "Entrez un numéro de salle comme F12: une lettre majuscule et deux chiffres."
50+
```
51+
52+
- IMPORTANT: your `pattern.regexp` should be between simple quote, not double.
53+
54+
## Configuration panel structure
55+
{% for name, docstring in configpanel.items() %}
56+
### {{name}}
57+
58+
{{docstring}}
59+
{% if not loop.last %}
60+
---
61+
{% endif %}
62+
{%- endfor %}
63+
---
64+
65+
## List of all option types
66+
{% for name, docstring in options.items() %}
67+
### {{name}}
68+
69+
{{docstring}}
70+
{%- if "#### Properties" not in docstring %}
71+
72+
#### Properties
73+
74+
- [common properties](#common-properties)
75+
{%- endif %}
76+
{% if not loop.last %}
77+
---
78+
{% endif %}
79+
{%- endfor %}

scripts/generate_docs.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
set -Eeuo pipefail
3+
set -x
4+
5+
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6+
7+
SRCDIR=$1
8+
9+
DOCDIR=$(dirname "$SCRIPT_DIR")
10+
11+
"$SCRIPT_DIR/helpers_doc_generate.py" 2 -i "$SRCDIR" -o "$DOCDIR/pages/06.contribute/10.packaging_apps/20.scripts/10.helpers/packaging_app_scripts_helpers.md"
12+
"$SCRIPT_DIR/helpers_doc_generate.py" 2.1 -i "$SRCDIR" -o "$DOCDIR/pages/06.contribute/10.packaging_apps/20.scripts/12.helpers21/packaging_app_scripts_helpers_v21.md"
13+
"$SCRIPT_DIR/resources_doc_generate.py" -i "$SRCDIR" -o "$DOCDIR/pages/06.contribute/10.packaging_apps/10.manifest/10.appresources/packaging_app_manifest_resources.md"
14+
"$SCRIPT_DIR/forms_doc_generate.py" -i "$SRCDIR" -o "$DOCDIR/pages/06.contribute/15.dev/03.forms/forms.md"

0 commit comments

Comments
 (0)