mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
411 lines
12 KiB
Python
411 lines
12 KiB
Python
import ast
|
|
import datetime
|
|
import subprocess
|
|
|
|
version = open("../debian/changelog").readlines()[0].split()[1].strip("()")
|
|
today = datetime.datetime.now().strftime("%d/%m/%Y")
|
|
|
|
|
|
def get_current_commit():
|
|
p = subprocess.Popen(
|
|
"git rev-parse --verify HEAD",
|
|
shell=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
)
|
|
stdout, stderr = p.communicate()
|
|
|
|
current_commit = stdout.strip().decode("utf-8")
|
|
return current_commit
|
|
|
|
|
|
current_commit = get_current_commit()
|
|
|
|
|
|
print(
|
|
f"""---
|
|
title: Options
|
|
template: docs
|
|
taxonomy:
|
|
category: docs
|
|
routes:
|
|
default: '/dev/forms'
|
|
---
|
|
|
|
Doc auto-generated by [this script](https://github.com/YunoHost/yunohost/blob/{current_commit}/doc/generate_options_doc.py) on {today} (YunoHost version {version})
|
|
|
|
## Glossary
|
|
|
|
You may encounter some named types which are used for simplicity.
|
|
|
|
- `Translation`: a translated property
|
|
- used for properties: `ask`, `help` and `Pattern.error`
|
|
- a `dict` with locales as keys and translations as values:
|
|
```toml
|
|
ask.en = "The text in english"
|
|
ask.fr = "Le texte en français"
|
|
```
|
|
It is not currently possible for translators to translate those string in weblate.
|
|
- a single `str` for a single english default string
|
|
```toml
|
|
help = "The text in english"
|
|
```
|
|
- `JSExpression`: a `str` JS expression to be evaluated to `true` or `false`:
|
|
- used for properties: `visible` and `enabled`
|
|
- operators availables: `==`, `!=`, `>`, `>=`, `<`, `<=`, `!`, `&&`, `||`, `+`, `-`, `*`, `/`, `%` and `match()`
|
|
- [examples available in the advanced section](#advanced-use-cases)
|
|
- `Binding`: bind a value to a file/property/variable/getter/setter/validator
|
|
- save the value in `settings.yaml` when not defined
|
|
- nothing at all with `"null"`
|
|
- a custom getter/setter/validator with `"null"` + a function starting with `get__`, `set__`, `validate__` in `scripts/config`
|
|
- a variable/property in a file with `:__FINALPATH__/my_file.php`
|
|
- a whole file with `__FINALPATH__/my_file.php`
|
|
- [examples available in the advanced section](#bind)
|
|
- `Pattern`: a `dict` with a regex to match the value against and an error message
|
|
```toml
|
|
pattern.regexp = '^[A-F]\d\d$'
|
|
pattern.error = "Provide a room like F12: one uppercase and 2 numbers"
|
|
# or with translated error
|
|
pattern.error.en = "Provide a room like F12: one uppercase and 2 numbers"
|
|
pattern.error.fr = "Entrez un numéro de salle comme F12: une lettre majuscule et deux chiffres."
|
|
```
|
|
- IMPORTANT: your `pattern.regexp` should be between simple quote, not double.
|
|
|
|
"""
|
|
)
|
|
|
|
|
|
fname = "../src/utils/form.py"
|
|
content = open(fname).read()
|
|
|
|
# NB: This magic is because we want to be able to run this script outside of a YunoHost context,
|
|
# in which we cant really 'import' the file because it will trigger a bunch of moulinette/yunohost imports...
|
|
tree = ast.parse(content)
|
|
|
|
OptionClasses = [
|
|
c for c in tree.body if isinstance(c, ast.ClassDef) and c.name.endswith("Option")
|
|
]
|
|
|
|
OptionDocString = {}
|
|
|
|
for c in OptionClasses:
|
|
if not isinstance(c.body[0], ast.Expr):
|
|
continue
|
|
option_type = None
|
|
|
|
if c.name in {"BaseOption", "BaseInputOption"}:
|
|
option_type = c.name
|
|
elif c.body[1].target.id == "type":
|
|
option_type = c.body[1].value.attr
|
|
|
|
docstring = ast.get_docstring(c)
|
|
if docstring:
|
|
if "##### Properties" not in docstring:
|
|
docstring += """
|
|
##### Properties
|
|
|
|
- [common properties](#common-option-properties)
|
|
"""
|
|
OptionDocString[option_type] = docstring
|
|
|
|
for option_type, doc in OptionDocString.items():
|
|
print("")
|
|
if option_type == "BaseOption":
|
|
print("## Common Option properties")
|
|
elif option_type == "BaseInputOption":
|
|
print("## Common Inputs properties")
|
|
elif option_type == "display_text":
|
|
print("----------------")
|
|
print("## Readonly Options")
|
|
print(f"### Option `{option_type}`")
|
|
elif option_type == "string":
|
|
print("----------------")
|
|
print("## Input Options")
|
|
print(f"### Option `{option_type}`")
|
|
else:
|
|
print(f"### Option `{option_type}`")
|
|
print("")
|
|
print(doc)
|
|
print("")
|
|
|
|
print(
|
|
"""
|
|
----------------
|
|
|
|
## Advanced use cases
|
|
|
|
### `visible` & `enabled` expression evaluation
|
|
|
|
Sometimes we may want to conditionaly display a message or prompt for a value, for this we have the `visible` prop.
|
|
And we may want to allow a user to trigger an action only if some condition are met, for this we have the `enabled` prop.
|
|
|
|
Expressions are evaluated against a context containing previous values of the current section's options. This quite limited current design exists because on the web-admin or on the CLI we cannot guarantee that a value will be present in the form if the user queried only a single panel/section/option.
|
|
In the case of an action, the user will be shown or asked for each of the options of the section in which the button is present.
|
|
|
|
The expression has to be written in javascript (this has been designed for the web-admin first and is converted to python on the fly on the cli).
|
|
|
|
Available operators are: `==`, `!=`, `>`, `>=`, `<`, `<=`, `!`, `&&`, `||`, `+`, `-`, `*`, `/`, `%` and `match()`.
|
|
|
|
##### Examples
|
|
|
|
```toml
|
|
# simple "my_option_id" is thruthy/falsy
|
|
visible = "my_option_id"
|
|
visible = "!my_option_id"
|
|
# misc
|
|
visible = "my_value >= 10"
|
|
visible = "-(my_value + 1) < 0"
|
|
visible = "!!my_value || my_other_value"
|
|
```
|
|
For a more complete set of examples, [check the tests at the end of the file](https://github.com/YunoHost/yunohost/blob/dev/src/tests/test_questions.py).
|
|
|
|
##### match()
|
|
|
|
For more complex evaluation we can use regex matching.
|
|
|
|
```toml
|
|
[my_string]
|
|
default = "Lorem ipsum dolor et si qua met!"
|
|
|
|
[my_boolean]
|
|
type = "boolean"
|
|
visible = "my_string && match(my_string, '^Lorem [ia]psumE?')"
|
|
```
|
|
|
|
Match the content of a file.
|
|
|
|
```toml
|
|
[my_file]
|
|
type = "file"
|
|
accept = ".txt"
|
|
bind = "/etc/random/lorem.txt"
|
|
|
|
[my_boolean]
|
|
type = "boolean"
|
|
visible = "my_file && match(my_file, '^Lorem [ia]psumE?')"
|
|
```
|
|
|
|
with a file with content like:
|
|
```txt
|
|
Lorem ipsum dolor et si qua met!
|
|
```
|
|
|
|
|
|
### `bind`
|
|
|
|
Config panels only
|
|
|
|
`bind` allows us to alter the generic behavior of option's values which is: get from and set in the app `settings.yml`.
|
|
|
|
We can:
|
|
- alter the source the value comes from with getters or binds to file.
|
|
- alter the destination with setters or binds to file.
|
|
- parse/validate the value before destination with validators
|
|
|
|
----------------
|
|
IMPORTANT: with the exception of `bind = "null"` options, options ids should almost **always** correspond to an app setting initialized / reused during install/upgrade.
|
|
Not doing so may result in inconsistencies between the config panel mechanism and the use of ynh_add_config
|
|
|
|
----------------
|
|
|
|
##### bind to file
|
|
|
|
You can bind a `text` or directly a `file` to a specific file by using `bind = "FILEPATH`.
|
|
|
|
```toml
|
|
[panel.section.config_file]
|
|
type = "file"
|
|
bind = "__FINALPATH__/config.ini"
|
|
```
|
|
|
|
```toml
|
|
[panel.section.config_content]
|
|
type = "text"
|
|
bind = "__FINALPATH__/config.ini"
|
|
default = "key: 'value'"
|
|
```
|
|
|
|
##### bind a value inside a file
|
|
|
|
Settings usually correspond to key/values in actual app configurations. Hence, a more useful mode is to have `bind = ":FILENAME"` with a colon `:` before. In that case, YunoHost will automagically find a line with `KEY=VALUE` in `FILENAME` (with the adequate separator between `KEY` and `VALUE`).
|
|
|
|
YunoHost will then use this value for the read/get operation. During write/set operations, YunoHost will overwrite the value in **both** FILENAME and in the app's settings.yml
|
|
|
|
Configuration file format supported: `yaml`, `toml`, `json`, `ini`, `env`, `php`, `python`.
|
|
The feature probably works with others formats, but should be tested carefully.
|
|
|
|
Note that this feature only works with relatively simple cases such as `KEY: VALUE`, but won't properly work with complex data structures like multilin array/lists or dictionnaries.
|
|
It also doesn't work with XML format, custom config function call, php define(), …
|
|
|
|
|
|
```toml
|
|
[panel.section.config_value]
|
|
# Do not use `file` for this since we only want to insert/save a value
|
|
type = "string"
|
|
bind = ":__FINALPATH__/config.ini"
|
|
default = ""
|
|
```
|
|
|
|
By default, `bind = ":FILENAME"` will use the option id as `KEY` but the option id may sometime not be the exact same `KEY` name in the configuration file.
|
|
For example, [In pepettes app](https://github.com/YunoHost-Apps/pepettes_ynh/blob/5cc2d3ffd6529cc7356ff93af92dbb6785c3ab9a/conf/settings.py##L11), the python variable is `name` and not `project_name`. In that case, the key name can be specified before the colon `:`.
|
|
|
|
```toml
|
|
[panel.section.project_name]
|
|
bind = "name:__FINALPATH__/config.ini"
|
|
```
|
|
|
|
##### Getters
|
|
|
|
Define an option's custom getter in a bash script `script/config`.
|
|
It has to be named after an option's id prepended by `get__`.
|
|
|
|
To display a custom alert message for example. We setup a base option in `config_panel.toml`.
|
|
|
|
```toml
|
|
[panel.section.alert]
|
|
type = "alert"
|
|
# bind to "null" to inform there's something in `scripts/config`
|
|
bind = "null"
|
|
# `ask` & `style` will be injected by a custom getter
|
|
```
|
|
|
|
Then add a custom getter that output yaml, every properties defined here will override any property previously declared.
|
|
|
|
```bash
|
|
get__alert() {
|
|
if [ "$whatever" ]; then
|
|
cat << EOF
|
|
style: success
|
|
ask: Your VPN is running :)
|
|
EOF
|
|
else
|
|
cat << EOF
|
|
style: danger
|
|
ask: Your VPN is down
|
|
EOF
|
|
fi
|
|
}
|
|
```
|
|
|
|
Or to inject a custom value:
|
|
|
|
```toml
|
|
[panel.section.my_hidden_value]
|
|
type = "number"
|
|
bind = "null"
|
|
# option will act as an hidden variable that can be used in context evaluation
|
|
# (ie: `visible` or `enabled`)
|
|
readonly = true
|
|
visible = false
|
|
# `default` injected by a custom getter
|
|
```
|
|
|
|
```bash
|
|
get__my_hidden_value() {
|
|
if [ "$whatever" ]; then
|
|
# if only a value is needed
|
|
echo "10"
|
|
else
|
|
# or if we need to override some other props
|
|
# (use `default` or `value` to inject the value)
|
|
cat << EOF
|
|
ask: Here's a number
|
|
visible: true
|
|
default: 0
|
|
EOF
|
|
fi
|
|
}
|
|
```
|
|
|
|
##### Setters
|
|
|
|
Define an option's custom setter in a bash script `script/config`.
|
|
It has to be named after an option's id prepended by `set__`.
|
|
|
|
```toml
|
|
[panel.section.my_value]
|
|
type = "string"
|
|
bind = "null"
|
|
ask = "gimme complex string"
|
|
```
|
|
|
|
```bash
|
|
set__my_value() {
|
|
if [ -n "$my_value" ]; then
|
|
# split the string into multiple elements or idk
|
|
fi
|
|
# To save the value or modified value as a setting:
|
|
ynh_app_setting_set --app=$app --key=my_value --value="$my_value"
|
|
}
|
|
```
|
|
|
|
##### Validators
|
|
|
|
Define an option's custom validator in a bash script `script/config`.
|
|
It has to be named after an option's id prepended by `validate__`.
|
|
|
|
Validators allows us to return custom error messages depending on the value.
|
|
|
|
```toml
|
|
[panel.section.my_value]
|
|
type = "string"
|
|
bind = "null"
|
|
ask = "Gimme a long string"
|
|
default = "too short"
|
|
```
|
|
|
|
```bash
|
|
validate__my_value() {
|
|
if [[ "${#my_value}" -lt 12 ]]; then echo 'Too short!'; fi
|
|
}
|
|
```
|
|
|
|
##### Actions
|
|
|
|
Define an option's action in a bash script `script/config`.
|
|
It has to be named after a `button`'s id prepended by `run__`.
|
|
|
|
```toml
|
|
[panel.section.my_action]
|
|
type = "button"
|
|
# no need to set `bind` to "null" it is its hard default
|
|
ask = "Run action"
|
|
```
|
|
|
|
```bash
|
|
run__my_action() {
|
|
ynh_print_info "Running 'my_action'..."
|
|
}
|
|
```
|
|
|
|
A more advanced example could look like:
|
|
|
|
```toml
|
|
[panel.my_action_section]
|
|
name = "Action section"
|
|
[panel.my_action_section.my_repo]
|
|
type = "url"
|
|
bind = "null" # value will not be saved as a setting
|
|
ask = "gimme a repo link"
|
|
|
|
[panel.my_action_section.my_repo_name]
|
|
type = "string"
|
|
bind = "null" # value will not be saved as a setting
|
|
ask = "gimme a custom folder name"
|
|
|
|
[panel.my_action_section.my_action]
|
|
type = "button"
|
|
ask = "Clone the repo"
|
|
# enabled the button only if the above values is defined
|
|
enabled = "my_repo && my_repo_name"
|
|
```
|
|
|
|
```bash
|
|
run__my_action() {
|
|
ynh_print_info "Cloning '$my_repo'..."
|
|
cd /tmp
|
|
git clone "$my_repo" "$my_repo_name"
|
|
}
|
|
```
|
|
"""
|
|
)
|