This commit is contained in:
getzze 2024-05-17 21:49:47 +02:00 committed by GitHub
commit 6dc80e6984
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1027 additions and 25 deletions

1
debian/install vendored
View file

@ -6,5 +6,6 @@ conf/* /usr/share/yunohost/conf/
locales/* /usr/share/yunohost/locales/
doc/yunohost.8.gz /usr/share/man/man8/
doc/bash_completion.d/* /etc/bash_completion.d/
doc/yunohost_zsh_completion /usr/share/zsh/vendor-completions/_yunohost
conf/metronome/modules/* /usr/lib/metronome/modules/
src/* /usr/lib/python3/dist-packages/yunohost/

1
debian/rules vendored
View file

@ -7,4 +7,5 @@
override_dh_auto_build:
# Generate bash completion file
python3 doc/generate_bash_completion.py
python3 doc/generate_zsh_completion.py
python3 doc/generate_manpages.py --gzip --output doc/yunohost.8.gz

View file

@ -0,0 +1,730 @@
#!/usr/bin/python3
"""
INSTALL:
This script creates a `yunohost_zsh_completion` file in the same folder as this script.
To install, copy (and rename) the created file to:
- (Debian) `/usr/share/zsh/vendor-completions/_yunohost`
- (Fedora) `/usr/share/zsh/site-functions/_yunohost`
- (other distribution) `/usr/local/share/zsh/site-functions/_yunohost`
DOCS:
- https://github.com/zsh-users/zsh/blob/master/Etc/completion-style-guide
- http://zsh.sourceforge.net/Doc/Release/Completion-System.html#Completion-System
or `man zshcompsys`
- http://zsh.sourceforge.net/Guide/zshguide06.html
Misc:
- http://zsh.sourceforge.net/Doc/Release/Parameters.html#Array-Parameters
TODO:
- use the extra:required:True pattern (similar to `nargs`?)
- Have the global options listed in a separate category
- Allow multiple arguments per option
(e.g.: `yunohost user info alice --fields uid cn mail`)
- In `yunohost.yml`, consider merging:
- metavar
- pattern
- autocomplete
- Make use of `type`, maybe using `_guard`
- Use `pattern`, maybe with `_guard`. This seems hard though, as ZSH has
its own globbing language...
Link about this globbing system:
http://zsh.sourceforge.net/Doc/Release/Expansion.html#Filename-Generation
NOTES:
- Command for debugging zsh: `unfunction _yunohost; autoload -U _yunohost`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Optimization:
- caching mecanism: invalidate the cache afer some commands? Hard, the
cache is local to user
- implement a zstyle switch, to change the cache validity period?
AUTHORS:
- buzuck (Fol)
- kayou
- getzze
"""
import os
import re
import yaml
THIS_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
ACTIONSMAP_FILE = THIS_SCRIPT_DIR + "/../share/actionsmap.yml"
ZSH_COMPLETION_FILE = THIS_SCRIPT_DIR + "/yunohost_zsh_completion"
# This dict will contain the completion function data
COMPLETION_FUNCTIONS = {}
def main():
yunohost_map = yaml.safe_load(open(ACTIONSMAP_FILE, "r"))
output = CONST_HEADER
#
# Creation of the entry function of the completion script
output += entry_point(yunohost_map)
#
# The main loop, going through all (sub)commands
for key, value in yunohost_map.items():
output += make_category(key, value)
#
# Building the auxilliary completion functions, they have been added
# to this dict during the main loop
output += build_completion_functions(COMPLETION_FUNCTIONS)
output += CONST_END_OF_FILE
with open(ZSH_COMPLETION_FILE, "w") as _yunohost:
_yunohost.write(output)
def entry_point(yunohost_map: dict) -> str:
"""
Provides the entry point of the completion script: a function called _yunohost, along with the routing function.
:param dict yunohost_map: The while YAML
:return str: The resulting string
"""
categories = ""
# Remember the amount of spaces to get a human readable completion file
spaces = re.search(r"\n(\s*)YNH_GLOBAL_OPTIONS", ENTRY_POINT).group(1)
if "_global" in yunohost_map and "arguments" in yunohost_map["_global"]:
global_arguments = make_argument_list(yunohost_map["_global"], spaces)
else:
global_arguments = "\n".join(
"{}{}".format(spaces, global_arg) for global_arg in GLOBAL_ARGUMENTS
)
# Remove spaces and newlines before and after
global_arguments = global_arguments.strip()
# Remember the amount of spaces to get a human readable completion file
spaces = re.search(r"\n(\s*)YNH_COMMANDS_DESCRIPTION", ENTRY_POINT).group(1)
#
# Going through the main ynh commands
for category, content in yunohost_map.items():
# We only consider a command an item that has a "category_help" field
if "category_help" not in content:
continue
# Creation of the category line, with the right amount of spaces
categories += "{}'{}:{}'\n".format(
spaces, category, _escape(content["category_help"])
)
# Remove spaces and newlines before and after
categories = categories.strip()
return ENTRY_POINT.replace("YNH_GLOBAL_OPTIONS", global_arguments).replace(
"YNH_COMMANDS_DESCRIPTION", categories
)
def make_category(category_name: str, category_map: dict) -> str:
"""
Generates the main function of a given category.
Reminder:
yunohost monitor info --cpu --ram
^ ^ ^ ^
(script) | category | action | parameters
:param str category_name: The name of the category
:param dict category_map: The mapping associated to the category
:return str: The entry point of the given category
"""
# No need to go further if a category does not have an `action` field
if "actions" not in category_map:
return ""
output = TEMPLATES["command"]
subcategories = ""
# Memorizing the spaces, to get a human readable file
spaces = re.search(r"\n(\s*)YNH_ACTIONS_DESCRIPTION", output).group(1)
# First, complete the main action map
for action, action_details in category_map["actions"].items():
# Empty element failsafe
if action_details.get("hide_in_help", False) or (
"action_help" not in action_details and "arguments" not in action_details
):
continue
# Adding the new action to the map of the category
new_action = "'{}:{}'".format(action, _escape(action_details["action_help"]))
output = output.replace(
"YNH_ACTIONS_DESCRIPTION",
"{} \\\n{}YNH_ACTIONS_DESCRIPTION".format(new_action, spaces),
)
# Generation of this action completion function
output += make_action(action, action_details).replace("COMMAND", category_name)
# Removing the remaining tag, uneeded now
output = re.sub(r"\n(\s*)YNH_ACTIONS_DESCRIPTION", "", output)
#
# Going through the subcategories if any
#
if "subcategories" in category_map:
# Getting the template, and saving the spaces
subcategories = TEMPLATES["subcategory"]
spaces = re.search(
r"\n(\s*)YNH_SUBCACTEGORIES_DESCRIPTION", subcategories
).group(1)
# Looping through the subcategories
for subcategory, subcategory_details in category_map["subcategories"].items():
# Append new subcategory
new_subcategory = "'{}:{}'".format(
subcategory, _escape(subcategory_details["subcategory_help"])
)
subcategories = subcategories.replace(
"YNH_SUBCACTEGORIES_DESCRIPTION",
"{}\\\n{}YNH_SUBCACTEGORIES_DESCRIPTION".format(
new_subcategory, spaces
),
)
# Creation of the subcategory
output += make_category(
category_name + "_" + subcategory, subcategory_details
)
# Removing the remaining tag, uneeded now
subcategories = re.sub(
r"\n(\s*)YNH_SUBCACTEGORIES_DESCRIPTION", "", subcategories
)
# Adding the subcategories to the final output. `subcategories` may be
# empty, if no subcategory exists for the current command.
return output.replace("YNH_SUBCATEGORY", subcategories).replace(
"COMMAND", category_name
)
def make_action(action_name: str, action_map: dict) -> str:
"""
Generates the completion function for a given action
Reminder:
yunohost monitor info --cpu --ram
^ ^ ^ ^
(script) | category | action | parameters
:param str action_name: The name of the action
:param dict action_map: The mapping associated to the action
:return str: The entry point of the given category
"""
# Return immediately if no argument is expected for this action
if "arguments" not in action_map:
return TEMPLATES["action_without_arguments"].replace("ACTION", action_name)
action = TEMPLATES["action"]
# Memorizing the spaces, to get a human readable file
spaces = re.search(r"\n(\s*)YNH_ACTION", action).group(1)
# Creation of the arguments list
args = make_argument_list(action_map, spaces)
# Insertion of the action's name
return action.replace("YNH_ACTION", args).replace("ACTION", action_name)
def make_argument_list(action_map: dict, spaces: str = 4 * " ") -> str:
"""
Builds the actions list.
:param dict action_map: The list of possible arguments
:param str spaces: [optional] The amount of spaces used for the indentation
:return str: The arguments list, ready to use
"""
action_list = "YNH_ACTION"
# This is a counter, in case of position dependent paremeters (the ones not
# beginning with a `-`)
position = 0
# Early return if no arguments key found
if "arguments" not in action_map:
return ""
for argument_name, argument_details in action_map["arguments"].items():
#
# Initializing the argument dict to make sure all fields are defined
# - id: identifier (`-n` or `--name`). If none (e.g. `ynh app install
# APP_NAME`), this field is the arguments position or cardinality (from
# `nargs`)
# - excludes: usually the argument itself. Only used for optional args
# - desc: the argument description
# - completion: the completion function name
#
arg = {"excludes": "", "spec": "", "desc": "", "mess": "", "action": ""}
#
# Forcing to str, as the yaml parser inteprets numbers as integers
# (eg.: `firewall allow... -4`)
argument_name = str(argument_name)
#
# Check if the argument should be hidden (API only)
#
if (
argument_details.get("extra", {})
.get("autocomplete", {})
.get("hide_in_help", False)
):
continue
#
# Generation of the completion hints
#
arg["action"] = make_argument_completion(argument_details)
#
# A parameter not beginning with `-` is considered mandatory.
if argument_name[0] != "-":
position += 1
# This parameter may be used more than once, else we use the position counter
if argument_details.get("nargs", "") in ["+", "*"]:
if argument_details["nargs"] == "+":
arg["spec"] = "'{{{},*}}'".format(str(position))
else: # argument_details["nargs"] == "*":
arg["spec"] = "*"
else:
arg["spec"] = str(position)
arg["mess"] = argument_details.get("help", argument_name)
#
# If defined, add the default value as a hint
if "default" in argument_details:
arg["mess"] += " (default: {})".format(argument_details["default"])
# Escape special character in the description
arg["mess"] = _escape(arg["mess"])
# ----
# NOTE: a double colon marks for an optional argument:
# '::Username to update:__ynh_user_list'
# ----
placeholder = "'{}{}{}:{}:{}'"
#
# This is an optional parameter, beginning with a `-`
else:
# `full` is the extended form of the argument (e.g.: -n is short for --number)
if "full" in argument_details:
full_name = argument_details["full"]
arg["mess"] = str(full_name).lstrip("--")
arg["spec"] = "'{{{},{}}}'".format(argument_name, full_name)
arg["excludes"] = "({} {})".format(argument_name, full_name)
else:
arg["mess"] = str(argument_name).lstrip("--")
arg["spec"] = argument_name
# Escape special character in the description
arg["mess"] = _escape(arg["mess"])
# The description of the parameter
# Getting the `help` field if any, else simply by using it's name
arg["desc"] = "[{}]".format(argument_details.get("help", arg["mess"]))
has_action = True
# Add a pattern field to match multiple arguments
if argument_details.get("nargs", "") in ["+", "*"]:
if arg["excludes"]:
# suppose that `arg["excludes"] = (-f --foo)`
arg["excludes"] = "(* " + arg["excludes"][1:]
else:
arg["excludes"] = "(*)"
arg["mess"] = "*:" + arg["mess"]
has_action = True
# Options without arguments should skip the message and action fields
elif argument_details.get("action", "").startswith("store_"):
has_action = False
# Place holder for the parameters
if has_action:
placeholder = "'{}{}{}:{}:{}'"
else:
placeholder = "'{}{}{}'"
#
# Putting it all together
# Escape special character in the description
arg["desc"] = _escape(arg["desc"])
action_list = action_list.replace(
"YNH_ACTION",
"{} \\\n{}YNH_ACTION".format(placeholder, spaces).format(
arg["excludes"], arg["spec"], arg["desc"], arg["mess"], arg["action"]
),
)
# Removing the extra tag and backslash,
return re.sub(r"\s*\\\n(\s*)YNH_ACTION", "", action_list)
def make_argument_completion(argument_details: dict) -> str:
"""
Finds the completion function for the given argument, if defined.
:param dict argument_details: The mapping of the argument
:return str: The name of the completion function, or an empty string
"""
# `COMPLETION_FUNCTIONS` hold the elements needed to generate it. The
# actual creation of this function will be done by build_completion_functions(),
# called near the end of this script.
# `choices` and `autocomplete` should not be present at the same time (`choices` takes precedence)
#
# A list of choices is defined
if "choices" in argument_details:
return "({})".format(" ".join(argument_details["choices"]))
#
# An autocompletion function is defined
if "extra" in argument_details and "autocomplete" in argument_details["extra"]:
autocomplete = argument_details["extra"]["autocomplete"]
#
# This is a combinaision of YunoHost and jq commands
#
if "ynh_selector" in autocomplete and "jq_selector" in autocomplete:
# Create this function's name
function_name = _remove_special_chars(
"__ynh_" + _norm_name(autocomplete["ynh_selector"])
)
#
# Add it to the function's dict, if it has not been created yet
if function_name not in COMPLETION_FUNCTIONS:
# First, build the shell command that returns the completions
call = "sudo yunohost {} --output-as json | jq -cr '{}'".format(
autocomplete["ynh_selector"], autocomplete["jq_selector"]
)
# If a cache is needed, wrap the call in the caching function
if "use_cache" in autocomplete:
call = "__get_ynh_cache 'YNH_{}' \"{}\"".format(
_norm_name(autocomplete["ynh_selector"]), call
)
# Lastly, save the content
COMPLETION_FUNCTIONS[function_name] = {"shell_call": call}
return function_name
#
# The autocompletion is done by a grep
#
elif "shell_call" in autocomplete:
# Create this function's name
function_name = _remove_special_chars(
"__ynh_" + _norm_name(autocomplete["shell_call"])
)
#
# Add it to the function's dict, if it has not been created yet
if function_name not in COMPLETION_FUNCTIONS:
# First, build the shell command that returns the completions
call = autocomplete["shell_call"]
# If a cache is needed, wrap the call in the caching function
# Note: not tested with grep, only with YunoHost's commands
if "use_cache" in autocomplete:
call = "__get_ynh_cache 'YNH_{}' \"{}\"".format(
_remove_special_chars(autocomplete["shell_call"]), call
)
# Lastly, save the content
COMPLETION_FUNCTIONS[function_name] = {"shell_call": call}
return function_name
#
# This is a combinaision of two other completion functions
#
elif "aggregate" in autocomplete:
# Create this function's name
function = "__ynh"
for subcall in autocomplete["aggregate"]:
function += "_" + _norm_name(subcall["ynh_selector"])
# If this function is yet undefined, create it
if function not in COMPLETION_FUNCTIONS:
aggregation = ""
for subcall in autocomplete["aggregate"]:
aggregation += "\n'{}:{}:{}' \\".format(
subcall["name"],
subcall["name"],
_norm_name("__ynh_" + subcall["ynh_selector"]),
)
# Saving the result, and removing the extra backslash
COMPLETION_FUNCTIONS[function] = {"aggregated": aggregation}
return function
#
# The autocompletion is done by a ZSH function
#
elif "zsh_completion" in autocomplete:
return autocomplete["zsh_completion"]
return ""
def build_completion_functions(functions: dict) -> str:
"""
Generates the custom completion functions for YunoHost, from the "metavar"
object of the YAML mapping.
:param dict metavars: The "metavar" object from the YAML
:return str: All custom completion functions, beginning by "__ynh_"
"""
output = ""
#
# This basically consists in associating the type of the completion
# function to it's template
for function, completion in functions.items():
if "aggregated" in completion:
output += (
TEMPLATES["completion_function_aggregate"]
.replace("FUNCTION", function)
.replace("AGGREGATED", completion["aggregated"])
)
else:
output += (
TEMPLATES["completion_shell_call"]
.replace("FUNCTION", function)
.replace("SHELL_CALL", completion["shell_call"])
)
return output
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
#
# Utility functions, mainly string manipulation
#
def _norm_name(string: str) -> str:
"""
Normalizies a string to make it look like a function name:
- lowercase
- spaces replaced by underscores
- no dashs
:param str string: the string to norm.
:return str: The normed string
"""
return (
string.lower()
.replace(" ", "_")
.replace("-", "")
.replace("/", "_")
.replace(".", "_")
)
def _escape(string: str) -> str:
r"""
Escapes any special character:
- single quotes (') are put in a separate double quoted string ('"'"')
- colons (:) and other characters are preceded by a backslash (\:)
:param str string: The string to escape
:return str: `string` escaped
"""
return string.replace("'", "'\"'\"'").replace(":", r"\:")
def _remove_special_chars(string: str) -> str:
"""
Removes any character with a special meaning in ZSH, such as `$`, `{` ...
:param str string: The string to clean
:return str: `string` cleaned
"""
# NOTE: this list may not be comprehensive and should be extended if needed
return re.sub(r'[- =\^+:\?\'"$(){}\[\]/\\\\]', "", string).replace(".", "")
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
#
# The ZSH templates are defined below
#
GLOBAL_ARGUMENTS = [
r"""'(-h --help)'{-h,--help}'[Show help message and exit]'""",
r"""'--output-as[Output result in another format]:output:(json plain none)'""",
r"""'--debug[Log and print debug messages]'""",
r"""'--quiet[Don't produce any output]'""",
r"""'--version[Display YunoHost packages versions (alias to `yunohost tools versions`)]'""",
r"""'--timeout[Number of seconds before this command will timeout because it can't acquire the lock (meaning that another command is currently running), by default there is no timeout and the command will wait until it can get the lock]'""",
]
CONST_HEADER = r"""#compdef yunohost
# -----------------------------------------------------------------------------
# Description
# -----------
# Completion script for yunohost, automatically generated from the action map
# decribed by `yunohost.yml`
# -----------------------------------------------------------------------------
local state line curcontext
# For debug purposes only
__log() {
echo $@ >> '/tmp/zsh-completion.log'
}
# First argument: The name of the completion list
# 2nd argument: The command to get it
# (( $+functions[__get_ynh_cache] )) ||
function __get_ynh_cache() {
# Checking a global cache policy is defined,
# and linkage to ynh-cache-policy
local update_policy completion_items
zstyle -s ":completion:${curcontext}:" cache-policy update_policy
if [[ -z "$update_policy" ]]; then
zstyle ":completion:${curcontext}:" cache-policy __yunohost_cache_policy
fi
# If the cache is invalid (too old), regenerate it
if _cache_invalid $1 || ! _retrieve_cache $1; then
completion_items=(`eval $2`)
_store_cache $1 completion_items
else
_retrieve_cache $1
fi
echo $completion_items
}
# (( $+functions[__yunohost_cache_policy] )) ||
__yunohost_cache_policy(){
local cache_file="$1"
# Rebuild if the yunohost executable is newer than cache
[[ "${commands[yunohost]}" -nt "${cache_file}" ]] && return
# Rebuild if cache is more than a week old
local -a oldp
# oldp=( "$1"(mM+1) ) # month
# oldp=( "$1"(Nm+7) ) # 1 week
oldp=( "$1"(Nmd+1) ) # 1 day
(( $#oldp )) && return
return 1
}
#
# Routing function, used to go through $words and find the correct subfunction
# (Suggestions welcome to improve that design... =/ )
# (( $+functions[__jump] )) ||
function __jump() {
local cmd
# Remember the subcommand name
if (( ${#@} == 0 )); then
local cmd=${words[2]}
else
cmd=$1 # < no more used?
fi
# Set the context for the subcommand
ynhcommand="${ynhcommand}_${cmd}"
# Narrow the range of words we are looking at to exclude `yunohost`
(( CURRENT-- ))
shift words
# Run the completion for the subcommand
if ! _call_function ret ${ynhcommand#:*:}; then
_default && ret=0
fi
return ret
}
"""
# --------------- Main entry point -----------------------------------
ENTRY_POINT = r"""
# (( $+functions[_yunohost] )) ||
function _yunohost() {
local curcontext="${curcontext}" state line ret=1
local mode
# `ynhcommand` is where `__jump` builds the name of the completion function
ynhcommand='_yunohost'
typeset -ag common_options; common_options=(
YNH_GLOBAL_OPTIONS
)
if (( CURRENT > 2 )); then
__jump
else
local -a yunohost_categories; yunohost_categories=(
YNH_COMMANDS_DESCRIPTION
)
_describe -V -t yunohost-commands 'yunohost category' yunohost_categories "$@"
fi
_arguments -s -C $common_options
# unset common_option
}
"""
CONST_END_OF_FILE = r"""
_yunohost "$@"
"""
TEMPLATES = {
"command": r"""
#-----------------------------------------
# COMMAND
#-----------------------------------------
# (( $+functions[_yunohost_COMMAND] )) ||
function _yunohost_COMMAND() {
if (( CURRENT > 2 )); then
__jump
else
local -a yunohost_COMMAND; yunohost_COMMAND=(
YNH_ACTIONS_DESCRIPTION
)
_describe -V -t yunohost-COMMAND 'yunohost COMMAND category' yunohost_COMMAND "$@"
YNH_SUBCATEGORY
fi
}
""",
# --------------------------------------------------------------------
"subcategory": r"""
local -a yunohost_COMMAND_subcategories; yunohost_COMMAND_subcategories=(
YNH_SUBCACTEGORIES_DESCRIPTION
)
_describe -V -t yunohost-COMMAND-subcategories 'yunohost COMMAND subcategories' yunohost_COMMAND_subcategories "$@"
""",
# --------------------------------------------------------------------
# Note: The common_options are not added until we find a way to have them
# in a separate category. It is too confusing to have them mixed with
# the command's options.
# Memo:
# YNH_ACTION \
# $common_options
#
"action": r"""
# (( $+functions[_yunohost_COMMAND_ACTION] )) ||
function _yunohost_COMMAND_ACTION() {
_arguments -s -C \
YNH_ACTION
}
""",
# --------------------------------------------------------------------
"action_without_arguments": r"""
# (( $+functions[_yunohost_COMMAND_ACTION] )) ||
function _yunohost_COMMAND_ACTION() { }
""",
# --------------------------------------------------------------------
"completion_shell_call": r"""
# (( $+functions[FUNCTION] )) ||
function FUNCTION() {
compadd "$@" -- ${(@)$(SHELL_CALL)}
}
""",
# --------------------------------------------------------------------
# The newline is included in `AGGREGATED`
"completion_function_aggregate": r"""
# (( $+functions[FUNCTION] )) ||
function FUNCTION() {
_alternative \AGGREGATED
}
""",
}
if __name__ == "__main__":
main()

View file

@ -37,6 +37,32 @@ _global:
authentication:
api: ldap_admin
cli: null
arguments:
-h:
full: --help
help: Show this help message and exit
--version:
help: Display YunoHost packages versions
action: callback
callback:
method: yunohost.utils.packages.ynh_packages_version
return: true
--output-as:
help: Output result in another format
choices:
- json
- plain
- none
--debug:
help: Log and print debug messages
action: store_true
--quiet:
help: Don't produce any output
action: store_true
--timeout:
help: Number of seconds before this command will timeout because it can't acquire the lock (meaning that another command is currently running), by default there is no timeout and the command will wait until it can get the lock
type: int
#############################
# User #
@ -53,6 +79,16 @@ user:
--fields:
help: fields to fetch (username, fullname, mail, mail-alias, mail-forward, mailbox-quota, groups, shell, home-path)
nargs: "+"
choices:
- username
- fullname
- mail
- mail-alias
- mail-forward
- mailbox-quota
- groups
- shell
- home-path
### user_create()
create:
@ -107,6 +143,10 @@ user:
pattern: &pattern_domain
- !!str ^([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$
- "pattern_domain"
autocomplete: &domains_list
ynh_selector: domain list
jq_selector: '.domains[]'
use_cache: false
-q:
full: --mailbox-quota
help: Mailbox size quota
@ -131,6 +171,10 @@ user:
help: Username to delete
extra:
pattern: *pattern_username
autocomplete: &users_list
ynh_selector: user list --fields username
jq_selector: '.users[].username'
use_cache: false
--purge:
help: Purge user's home and mail directories
action: store_true
@ -142,6 +186,8 @@ user:
arguments:
username:
help: Username to update
extra:
autocomplete: *users_list
-F:
full: --fullname
help: The full name of the user. For example 'Camille Dupont'
@ -212,6 +258,8 @@ user:
arguments:
username:
help: Username or email to get information
extra:
autocomplete: *users_list
### user_export()
export:
@ -226,6 +274,9 @@ user:
csvfile:
help: "CSV file with columns username, firstname, lastname, password, mail, mailbox-quota, mail-alias, mail-forward, groups (separated by coma)"
type: open
extra:
autocomplete:
zsh_completion: _files
-u:
full: --update
help: Update all existing users contained in the CSV file (by default existing users are ignored)
@ -279,6 +330,10 @@ user:
help: Name of the group to be deleted
extra:
pattern: *pattern_groupname
autocomplete: &user_groups_list
ynh_selector: user group list -s
jq_selector: '.groups[]'
use_cache: false
### user_group_info()
info:
@ -288,7 +343,8 @@ user:
groupname:
help: Name of the group to fetch info about
extra:
pattern: *pattern_username
pattern: *pattern_groupname
autocomplete: *user_groups_list
### user_group_add()
add:
@ -299,12 +355,14 @@ user:
help: Name of the group to add user(s) to
extra:
pattern: *pattern_groupname
autocomplete: *user_groups_list
usernames:
help: User(s) to add in the group
nargs: "*"
metavar: USERNAME
extra:
pattern: *pattern_username
autocomplete: *users_list
### user_group_remove()
remove:
@ -315,12 +373,14 @@ user:
help: Name of the group to remove user(s) from
extra:
pattern: *pattern_groupname
autocomplete: *user_groups_list
usernames:
help: User(s) to remove from the group
nargs: "*"
metavar: USERNAME
extra:
pattern: *pattern_username
autocomplete: *users_list
add-mailalias:
action_help: Add mail aliases to group
@ -330,6 +390,7 @@ user:
help: Name of the group to add user(s) to
extra:
pattern: *pattern_groupname
autocomplete: *user_groups_list
aliases:
help: Mail aliases to add
nargs: "+"
@ -344,13 +405,13 @@ user:
help: Name of the group to add user(s) to
extra:
pattern: *pattern_groupname
autocomplete: *user_groups_list
aliases:
help: Mail aliases to remove
nargs: "+"
metavar: MAIL
permission:
subcategory_help: Manage permissions
actions:
@ -363,6 +424,11 @@ user:
apps:
help: Apps to list permission for (all by default)
nargs: "*"
extra:
autocomplete: &apps_list
ynh_selector: app list
jq_selector: '.apps[].id'
use_cache: false
-s:
full: --short
help: Only list permission names
@ -379,6 +445,11 @@ user:
arguments:
permission:
help: Name of the permission to fetch info about (use "yunohost user permission list" and "yunohost user permission -f" to see all the current permissions)
extra:
autocomplete: &permissions_list
ynh_selector: user permission list
jq_selector: '.permissions | keys[]'
use_cache: false
### user_permission_update()
update:
@ -387,6 +458,8 @@ user:
arguments:
permission:
help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) (use "yunohost user permission list" and "yunohost user permission -f" to see all the current permissions)
extra:
autocomplete: *permissions_list
-l:
full: --label
help: Label for this permission. This label will be shown on the SSO and in the admin
@ -394,8 +467,8 @@ user:
full: --show_tile
help: Define if a tile will be shown in the SSO
choices:
- 'True'
- 'False'
- "True"
- "False"
## user_permission_add()
add:
@ -404,12 +477,18 @@ user:
arguments:
permission:
help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) (use "yunohost user permission list" and "yunohost user permission -f" to see all the current permissions)
extra:
autocomplete: *permissions_list
names:
help: Group or usernames to grant this permission to
nargs: "*"
metavar: GROUP_OR_USER
extra:
pattern: *pattern_username
autocomplete: &user_and_groups_list
ynh_selector: user group list
jq_selector: '[(.groups | keys), ([.groups[] | select(.members).members[]] | unique)] | add[]'
use_cache: false
## user_permission_remove()
remove:
@ -418,12 +497,15 @@ user:
arguments:
permission:
help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) (use "yunohost user permission list" and "yunohost user permission -f" to see all the current permissions)
extra:
autocomplete: *permissions_list
names:
help: Group or usernames to revoke this permission to
nargs: "*"
metavar: GROUP_OR_USER
extra:
pattern: *pattern_username
autocomplete: *user_and_groups_list
## user_permission_reset()
reset:
@ -432,6 +514,8 @@ user:
arguments:
permission:
help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) (use "yunohost user permission list" and "yunohost user permission -f" to see all the current permissions)
extra:
autocomplete: *permissions_list
ssh:
subcategory_help: Manage ssh access
@ -446,6 +530,7 @@ user:
help: Username of the user
extra:
pattern: *pattern_username
autocomplete: *users_list
### user_ssh_keys_add()
add-key:
@ -456,6 +541,7 @@ user:
help: Username of the user
extra:
pattern: *pattern_username
autocomplete: *users_list
key:
help: The key to be added
-c:
@ -471,6 +557,7 @@ user:
help: Username of the user
extra:
pattern: *pattern_username
autocomplete: *users_list
key:
help: The key to be removed
@ -505,6 +592,7 @@ domain:
help: Domain to check
extra:
pattern: *pattern_domain
autocomplete: *domains_list
### domain_add()
add:
@ -535,6 +623,7 @@ domain:
help: Domain to delete
extra:
pattern: *pattern_domain
autocomplete: *domains_list
-r:
full: --remove-apps
help: Remove apps installed on the domain
@ -564,6 +653,7 @@ domain:
help: Target domain
extra:
pattern: *pattern_domain
autocomplete: *domains_list
### domain_maindomain()
main-domain:
@ -577,6 +667,7 @@ domain:
help: Change the current main domain
extra:
pattern: *pattern_domain
autocomplete: *domains_list
### certificate_status()
cert-status:
@ -586,6 +677,8 @@ domain:
domain_list:
help: Domains to check
nargs: "*"
extra:
autocomplete: *domains_list
--full:
help: Show more details
action: store_true
@ -598,6 +691,8 @@ domain:
domain_list:
help: Domains for which to install the certificates
nargs: "*"
extra:
autocomplete: *domains_list
--force:
help: Install even if current certificate is not self-signed
action: store_true
@ -616,6 +711,8 @@ domain:
domain_list:
help: Domains for which to renew the certificates
nargs: "*"
extra:
autocomplete: *domains_list
--force:
help: Ignore the validity threshold (15 days)
action: store_true
@ -628,7 +725,7 @@ domain:
### domain_url_available()
url-available:
hide_in_help: True
hide_in_help: true
action_help: Check availability of a web path
api: GET /domain/<domain>/urlavailable
arguments:
@ -636,18 +733,21 @@ domain:
help: The domain for the web path (e.g. your.domain.tld)
extra:
pattern: *pattern_domain
autocomplete: *domains_list
path:
help: The path to check (e.g. /coffee)
### domain_action_run()
action-run:
hide_in_help: True
hide_in_help: true
action_help: Run domain action
api: PUT /domain/<domain>/actions/<action>
arguments:
domain:
help: Domain name
extra:
autocomplete: *domains_list
action:
help: action id
-a:
@ -666,6 +766,7 @@ domain:
help: Domain to subscribe to the DynDNS service
extra:
pattern: *pattern_domain
autocomplete: *domains_list
-p:
full: --recovery-password
nargs: "?"
@ -682,6 +783,7 @@ domain:
help: Domain to unsubscribe from the DynDNS service
extra:
pattern: *pattern_domain
autocomplete: *domains_list
required: True
-p:
full: --recovery-password
@ -699,6 +801,7 @@ domain:
help: Domain to set recovery password for
extra:
pattern: *pattern_domain
autocomplete: *domains_list
required: True
-p:
full: --recovery-password
@ -719,6 +822,8 @@ domain:
arguments:
domain:
help: Domain name
extra:
autocomplete: *domains_list
key:
help: A specific panel, section or a question identifier
nargs: '?'
@ -738,6 +843,8 @@ domain:
arguments:
domain:
help: Domain name
extra:
autocomplete: *domains_list
key:
help: The question or form key
nargs: '?'
@ -762,6 +869,7 @@ domain:
help: Target domain
extra:
pattern: *pattern_domain
autocomplete: *domains_list
### domain_dns_push()
push:
@ -772,6 +880,7 @@ domain:
help: Domain name to push DNS conf for
extra:
pattern: *pattern_domain
autocomplete: *domains_list
-d:
full: --dry-run
help: Only display what's to be pushed
@ -794,6 +903,9 @@ domain:
domain_list:
help: Domains to check
nargs: "*"
extra:
autocomplete: *domains_list
--full:
help: Show more details
action: store_true
@ -806,6 +918,8 @@ domain:
domain_list:
help: Domains for which to install the certificates
nargs: "*"
extra:
autocomplete: *domains_list
--force:
help: Install even if current certificate is not self-signed
action: store_true
@ -824,6 +938,8 @@ domain:
domain_list:
help: Domains for which to renew the certificates
nargs: "*"
extra:
autocomplete: *domains_list
--force:
help: Ignore the validity threshold (30 days)
action: store_true
@ -873,10 +989,18 @@ app:
arguments:
app:
help: Name, local path or git URL of the app to fetch the manifest of
extra:
autocomplete: &apps_catalog_list
ynh_selector: app catalog
jq_selector: '.apps | keys[]'
use_cache: true
-s:
full: --with-screenshot
help: Also return a base64 screenshot if any (API only)
action: store_true
extra:
autocomplete:
hide_in_help: true
### app_list()
list:
@ -899,6 +1023,8 @@ app:
arguments:
app:
help: Specific app ID
extra:
autocomplete: *apps_list
-f:
full: --full
help: Display all details, including the app manifest and various other infos
@ -912,6 +1038,8 @@ app:
-a:
full: --app
help: Specific app to map
extra:
autocomplete: *apps_list
-r:
full: --raw
help: Return complete dict
@ -921,6 +1049,7 @@ app:
help: Allowed app map for a user
extra:
pattern: *pattern_username
autocomplete: *users_list
### app_install()
install:
@ -929,6 +1058,8 @@ app:
arguments:
app:
help: Name, local path or git URL of the app to install
extra:
autocomplete: *apps_catalog_list
-l:
full: --label
help: Custom name for the app
@ -951,6 +1082,8 @@ app:
arguments:
app:
help: App to remove
extra:
autocomplete: *apps_list
-p:
full: --purge
help: Also remove all application data
@ -964,6 +1097,8 @@ app:
app:
help: App(s) to upgrade (default all)
nargs: "*"
extra:
autocomplete: *apps_list
-u:
full: --url
help: Git url to fetch for upgrade
@ -990,6 +1125,8 @@ app:
arguments:
app:
help: Target app instance name
extra:
autocomplete: *apps_list
-d:
full: --domain
help: New app domain on which the application will be moved
@ -997,6 +1134,7 @@ app:
ask: ask_new_domain
pattern: *pattern_domain
required: True
autocomplete: *domains_list
-p:
full: --path
help: New path at which the application will be moved
@ -1011,6 +1149,8 @@ app:
arguments:
app:
help: App ID
extra:
autocomplete: *apps_list
key:
help: Key to get/set
-v:
@ -1029,31 +1169,41 @@ app:
arguments:
app:
help: App ID
extra:
autocomplete: *apps_list
### app_register_url()
register-url:
hide_in_help: True
hide_in_help: true
action_help: Book/register a web path for a given app
arguments:
app:
help: App which will use the web path
extra:
autocomplete: *apps_list
domain:
help: The domain on which the app should be registered (e.g. your.domain.tld)
extra:
autocomplete: *domains_list
path:
help: The path to be registered (e.g. /coffee)
### app_makedefault()
makedefault:
hide_in_help: True
hide_in_help: true
action_help: Redirect domain root to an app
api: PUT /apps/<app>/default
arguments:
app:
help: App name to put on domain root
extra:
autocomplete: *apps_list
-d:
full: --domain
help: Specific domain to put app on (the app domain by default)
extra:
autocomplete: *domains_list
-u:
full: --undo
help: Undo redirection
@ -1061,14 +1211,19 @@ app:
### app_dismiss_notification
dismiss-notification:
hide_in_help: True
hide_in_help: true
action_help: Dismiss post_install or post_upgrade notification
api: PUT /apps/<app>/dismiss_notification/<name>
arguments:
app:
help: App ID to dismiss notification for
extra:
autocomplete: *apps_list
name:
help: Notification name, either post_install or post_upgrade
choices:
- post_install
- post_upgrade
### app_ssowatconf()
ssowatconf:
@ -1081,6 +1236,8 @@ app:
arguments:
app:
help: App ID
extra:
autocomplete: *apps_list
new_label:
help: New app label
@ -1097,6 +1254,8 @@ app:
arguments:
app:
help: App name
extra:
autocomplete: *apps_list
### app_action_run()
run:
@ -1105,6 +1264,8 @@ app:
arguments:
app:
help: App name
extra:
autocomplete: *apps_list
action:
help: action id
-a:
@ -1124,6 +1285,8 @@ app:
arguments:
app:
help: App name
extra:
autocomplete: *apps_list
key:
help: A specific panel, section or a question identifier
nargs: '?'
@ -1143,6 +1306,8 @@ app:
arguments:
app:
help: App name
extra:
autocomplete: *apps_list
key:
help: The question or panel key
nargs: '?'
@ -1191,6 +1356,8 @@ backup:
--apps:
help: List of application names to backup (or all if none given)
nargs: "*"
extra:
autocomplete: *apps_list
--dry-run:
help: "'Simulate' the backup and return the size details per item to backup"
action: store_true
@ -1202,12 +1369,19 @@ backup:
arguments:
name:
help: Name of the local backup archive
extra:
autocomplete: &backups_list
ynh_selector: backup list
jq_selector: '.archives[]'
use_cache: false
--system:
help: List of system parts to restore (or all if none is given)
nargs: "*"
--apps:
help: List of application names to restore (or all if none is given)
nargs: "*"
extra:
autocomplete: *apps_list
--force:
help: Force restauration on an already installed system
action: store_true
@ -1233,6 +1407,8 @@ backup:
arguments:
name:
help: Name of the local backup archive
extra:
autocomplete: *backups_list
-d:
full: --with-details
help: Show additional backup information
@ -1244,7 +1420,7 @@ backup:
### backup_download()
download:
hide_in_help: True
hide_in_help: true
action_help: (API only) Request to download the file
api: GET /backups/<name>/download
arguments:
@ -1260,6 +1436,7 @@ backup:
help: Name of the archive to delete
extra:
pattern: *pattern_backup_archive_name
autocomplete: *backups_list
#############################
@ -1286,6 +1463,11 @@ settings:
arguments:
key:
help: Settings key
extra:
autocomplete: &settings_list
ynh_selector: settings list
jq_selector: '. | keys[]'
use_cache: false
-f:
full: --full
help: Display all details (meant to be used by the API)
@ -1303,6 +1485,8 @@ settings:
key:
help: The question or form key
nargs: '?'
extra:
autocomplete: *settings_list
-v:
full: --value
help: new value
@ -1322,6 +1506,8 @@ settings:
arguments:
key:
help: Settings key
extra:
autocomplete: *settings_list
#############################
# Service #
@ -1343,6 +1529,9 @@ service:
full: --log
help: Absolute path to log file to display
nargs: "+"
extra:
autocomplete:
zsh_completion: _files
--test_status:
help: Specify a custom bash command to check the status of the service. Note that it only makes sense to specify this if the corresponding systemd service does not return the proper information already.
--test_conf:
@ -1363,6 +1552,11 @@ service:
arguments:
name:
help: Service name to remove
extra:
autocomplete: &services_list
ynh_selector: service status
jq_selector: '. | keys[]'
use_cache: false
### service_start()
start:
@ -1373,6 +1567,8 @@ service:
help: Service name to start
nargs: "+"
metavar: NAME
extra:
autocomplete: *services_list
### service_stop()
stop:
@ -1383,6 +1579,8 @@ service:
help: Service name to stop
nargs: "+"
metavar: NAME
extra:
autocomplete: *services_list
### service_reload()
reload:
@ -1392,6 +1590,8 @@ service:
help: Service name to reload
nargs: "+"
metavar: NAME
extra:
autocomplete: *services_list
### service_restart()
restart:
@ -1402,6 +1602,8 @@ service:
help: Service name to restart
nargs: "+"
metavar: NAME
extra:
autocomplete: *services_list
### service_reload_or_restart()
reload_or_restart:
@ -1411,6 +1613,8 @@ service:
help: Service name to reload or restart
nargs: "+"
metavar: NAME
extra:
autocomplete: *services_list
### service_enable()
enable:
@ -1421,6 +1625,8 @@ service:
help: Service name to enable
nargs: "+"
metavar: NAME
extra:
autocomplete: *services_list
### service_disable()
disable:
@ -1431,6 +1637,8 @@ service:
help: Service name to disable
nargs: "+"
metavar: NAME
extra:
autocomplete: *services_list
### service_status()
status:
@ -1443,6 +1651,8 @@ service:
help: Service name to show
nargs: "*"
metavar: NAME
extra:
autocomplete: *services_list
### service_log()
log:
@ -1451,6 +1661,8 @@ service:
arguments:
name:
help: Service name to log
extra:
autocomplete: *services_list
-n:
full: --number
help: Number of lines to display
@ -1596,6 +1808,7 @@ dyndns:
help: Full domain to subscribe with ( deprecated, use 'yunohost domain dyndns subscribe' instead )
extra:
pattern: *pattern_domain
autocomplete: *domains_list
-p:
full: --recovery-password
nargs: "?"
@ -1613,6 +1826,7 @@ dyndns:
help: Full domain to update
extra:
pattern: *pattern_domain
autocomplete: *domains_list
-f:
full: --force
help: Force the update (for debugging only)
@ -1668,6 +1882,7 @@ tools:
ask: ask_main_domain
pattern: *pattern_domain
required: True
autocomplete: *domains_list
-u:
full: --username
help: Username for the first (admin) user. For example 'camille'
@ -1675,6 +1890,7 @@ tools:
ask: ask_admin_username
pattern: *pattern_username
required: True
autocomplete: *users_list
-F:
full: --fullname
help: The full name for the first (admin) user. For example 'Camille Dupont'
@ -1826,6 +2042,11 @@ tools:
targets:
help: Migrations to run (all pendings by default)
nargs: "*"
extra:
autocomplete: &migrations_list
ynh_selector: tools migrations list
jq_selector: '.migrations[].id'
use_cache: false
--skip:
help: Skip specified migrations (to be used only if you know what you are doing)
action: store_true
@ -1857,8 +2078,13 @@ hook:
arguments:
app:
help: App to link with
extra:
autocomplete: *apps_list
file:
help: Script to add
extra:
autocomplete:
zsh_completion: _files
### hook_remove()
remove:
@ -1866,14 +2092,35 @@ hook:
arguments:
app:
help: Scripts related to app will be removed
extra:
autocomplete: *apps_list
### hook_info()
info:
hide_in_help: True
hide_in_help: true
action_help: Get information about a given hook
arguments:
action:
help: Action name
choices: &hook_action_choices
- post_user_create
- post_user_delete
- post_user_update
- post_app_addaccess
- post_app_removeaccess
- post_domain_add
- post_domain_remove
- post_cert_update
- custom_dns_rules
- post_app_change_url
- post_app_upgrade
- post_app_install
- post_app_remove
- backup
- restore
- backup_method
- post_iptable_rules
- conf_regen
name:
help: Hook name
@ -1884,6 +2131,7 @@ hook:
arguments:
action:
help: Action name
choices: *hook_action_choices
-l:
full: --list-by
help: Property to list hook by
@ -1899,11 +2147,12 @@ hook:
### hook_callback()
callback:
hide_in_help: True
hide_in_help: true
action_help: Execute all scripts binded to an action
arguments:
action:
help: Action name
choices: *hook_action_choices
-n:
full: --hooks
help: List of hooks names to execute
@ -1922,11 +2171,15 @@ hook:
### hook_exec()
exec:
hide_in_help: True
hide_in_help: true
action_help: Execute hook from a file with arguments
arguments:
path:
help: Path of the script to execute
extra:
autocomplete:
zsh_completion: _files
-a:
full: --args
help: Ordered list of arguments to pass to the script
@ -1977,6 +2230,11 @@ log:
arguments:
path:
help: Log file which to display the content
extra:
autocomplete: &logs_list
ynh_selector: log list
jq_selector: '.operation[].name'
use_cache: false
-n:
full: --number
help: Number of lines to display
@ -2001,6 +2259,8 @@ log:
arguments:
path:
help: Log file to share
extra:
autocomplete: *logs_list
#############################
@ -2021,6 +2281,12 @@ diagnosis:
categories:
help: Diagnosis categories to display (all by default)
nargs: "*"
extra:
autocomplete: &diagnosis_list
ynh_selector: diagnosis list
jq_selector: '.categories[]'
use_cache: false
--full:
help: Display additional information
action: store_true
@ -2040,6 +2306,8 @@ diagnosis:
arguments:
category:
help: Diagnosis category to fetch results from
extra:
autocomplete: *diagnosis_list
item:
help: "List of criteria describing the test. Must correspond exactly to the 'meta' infos in 'yunohost diagnosis show'"
metavar: CRITERIA
@ -2052,6 +2320,8 @@ diagnosis:
categories:
help: Diagnosis categories to run (all by default)
nargs: "*"
extra:
autocomplete: *diagnosis_list
--force:
help: Ignore the cached report even if it is still 'fresh'
action: store_true