From 900f462791c91eb7bb316ccbf826ab15f2949aaa Mon Sep 17 00:00:00 2001 From: axolotle Date: Sat, 22 Apr 2023 18:42:24 +0200 Subject: [PATCH] doc:form: complete with ljf comments found in app example --- doc/generate_options_doc.py | 70 ++++++++++++++++++++++++++++++------- src/utils/form.py | 64 +++++++++++++++++++-------------- 2 files changed, 95 insertions(+), 39 deletions(-) diff --git a/doc/generate_options_doc.py b/doc/generate_options_doc.py index fc6078370..356cea3a5 100644 --- a/doc/generate_options_doc.py +++ b/doc/generate_options_doc.py @@ -34,11 +34,6 @@ routes: # Doc auto-generated by [this script](https://github.com/YunoHost/yunohost/blob/{current_commit}/doc/generate_options_doc.py) on {today} (YunoHost version {version}) -# Options - -Options are fields declaration that renders as form items in the web-admin and prompts in cli. -They are used in app manifests to declare the before installation form and in config panels. - ## Glossary You may encounter some named types which are used for simplicity. @@ -60,7 +55,7 @@ You may encounter some named types which are used for simplicity. - operators availables: `==`, `!=`, `!`, `&&`, `||`, `+`, `-`, `*`, `/`, `%`, `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 (`None`) + - 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` @@ -74,6 +69,7 @@ You may encounter some named types which are used for simplicity. 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 should be between simple quote, not double. """ ) @@ -87,9 +83,7 @@ content = open(fname).read() tree = ast.parse(content) OptionClasses = [ - c - for c in tree.body - if isinstance(c, ast.ClassDef) and c.name.endswith("Option") + c for c in tree.body if isinstance(c, ast.ClassDef) and c.name.endswith("Option") ] OptionDocString = {} @@ -201,13 +195,65 @@ Lorem ipsum dolor et si qua met! Config panels only -`bind` allows us to alter the generic behavior of option's values which is: get from and set in `settings.yml`. +`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. -- alter the destination with setters +- 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`. diff --git a/src/utils/form.py b/src/utils/form.py index f030523d6..c17c2502d 100644 --- a/src/utils/form.py +++ b/src/utils/form.py @@ -299,10 +299,20 @@ class Pattern(BaseModel): class BaseOption(BaseModel): """ + Options are fields declaration that renders as form items, button, alerts or text in the web-admin and printed or prompted in CLI. + They are used in app manifests to declare the before installation form and in config panels. + + [Have a look at the app config panel doc](/packaging_apps_config_panels) for details about Panels and Sections. + + ---------------- + IMPORTANT: as for Panels and Sections you have to choose an id, but this one should be unique in all this document, even if the question is in an other panel. + + ---------------- + ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "string" # ask as `str` ask = "The text in english" @@ -346,14 +356,14 @@ class BaseOption(BaseModel): - [`group`](#option-group) - `ask`: `Translation` (default to the option's `id` if not defined): - text to display as the option's label for inputs or text to display for readonly options + - in config panels, questions are displayed on the left side and therefore have not much space to be rendered. Therefore, it is better to use a short question, and use the `help` property to provide additional details if necessary. - `visible` (optional): `bool` or `JSExpression` (default: `true`) - define if the option is diplayed/asked - if `false` and used alongside `readonly = true`, you get a context only value that can still be used in `JSExpression`s - `readonly` (optional): `bool` (default: `false`, forced to `true` for readonly types): - If `true` for input types: forbid mutation of its value - - `bind` (optional): `Binding` (default: `None`): - - (config panels only!) allow to choose where an option's is gathered/stored: - - if not specified, the value will be gathered/stored in the `settings.yml` + - `bind` (optional): `Binding`, config panels only! A powerful feature that let you configure how and where the setting will be read, validated and written + - if not specified, the value will be read/written in the app `settings.yml` - if `"null"`: - the value will not be stored at all (can still be used in context evaluations) - if in `scripts/config` there's a function named: @@ -440,7 +450,7 @@ class DisplayTextOption(BaseReadonlyOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "display_text" ask = "Simple text rendered as is." ``` @@ -456,7 +466,7 @@ class MarkdownOption(BaseReadonlyOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "display_text" ask = "Text **rendered** in markdown." ``` @@ -480,7 +490,7 @@ class AlertOption(BaseReadonlyOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "alert" ask = "The configuration seems to be manually modified..." style = "warning" @@ -521,7 +531,7 @@ class ButtonOption(BaseReadonlyOption): ##### Examples ```toml - [my_action_id] + [section.my_option_id] type = "button" ask = "Break the system" style = "danger" @@ -575,7 +585,7 @@ class BaseInputOption(BaseOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "string" # …any common props… + optional = false @@ -751,7 +761,7 @@ class StringOption(BaseStringOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "string" default = "E10" pattern.regexp = "^[A-F]\d\d$" @@ -774,7 +784,7 @@ class TextOption(BaseStringOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "text" default = "multi\\nline\\ncontent" ``` @@ -797,7 +807,7 @@ class PasswordOption(BaseInputOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "password" ``` ##### Properties @@ -849,7 +859,7 @@ class ColorOption(BaseInputOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "color" default = "#ff0" ``` @@ -895,7 +905,7 @@ class NumberOption(BaseInputOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "number" default = 100 min = 50 @@ -970,7 +980,7 @@ class BooleanOption(BaseInputOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "boolean" default = 1 yes = "agree" @@ -1098,7 +1108,7 @@ class DateOption(BaseInputOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "date" default = "2070-12-31" ``` @@ -1128,7 +1138,7 @@ class TimeOption(BaseInputOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "time" default = "12:26" ``` @@ -1161,7 +1171,7 @@ class EmailOption(BaseInputOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "email" default = "Abc.123@test-example.com" ``` @@ -1181,7 +1191,7 @@ class WebPathOption(BaseStringOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "path" default = "/" ``` @@ -1229,7 +1239,7 @@ class URLOption(BaseStringOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "url" default = "https://example.xn--zfr164b/@handle/" ``` @@ -1253,7 +1263,7 @@ class FileOption(BaseInputOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "file" accept = ".json" # bind the file to a location to save the file there @@ -1381,7 +1391,7 @@ class SelectOption(BaseChoicesOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "select" choices = ["one", "two", "three"] choices = "one,two,three" @@ -1409,7 +1419,7 @@ class TagsOption(BaseChoicesOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "tags" default = "word,another word" @@ -1514,7 +1524,7 @@ class DomainOption(BaseChoicesOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "domain" ``` ##### Properties @@ -1568,7 +1578,7 @@ class AppOption(BaseChoicesOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "app" filter = "is_webapp" ``` @@ -1621,7 +1631,7 @@ class UserOption(BaseChoicesOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "user" ``` ##### Properties @@ -1681,7 +1691,7 @@ class GroupOption(BaseChoicesOption): ##### Examples ```toml - [my_option_id] + [section.my_option_id] type = "group" default = "visitors" ```