diff --git a/dconfig-center/common/helper.hpp b/dconfig-center/common/helper.hpp index 96ca4a5..3d79b8d 100644 --- a/dconfig-center/common/helper.hpp +++ b/dconfig-center/common/helper.hpp @@ -131,6 +131,24 @@ static ResourceList resourcesForAllApp(const QString &localPrefix = QString()) return result.values(); } +// Return every resource that can be opened with appid. Generic resources are +// valid for every application and are the only resources available when the +// appid is empty. +static ResourceList availableResourcesForApp(const QString &appid, const QString &localPrefix = QString()) +{ + QSet result; + if (!appid.isEmpty()) { + const auto resources = resourcesForApp(appid, localPrefix); + for (const auto &resource : resources) + result.insert(resource); + } + + const auto genericResources = resourcesForAllApp(localPrefix); + for (const auto &resource : genericResources) + result.insert(resource); + return result.values(); +} + static SubpathList subpathsForResource(const AppId &appid, const ResourceId &resourceId, const QString &localPrefix = QString()) { SubpathList result; diff --git a/dconfig-center/dde-dconfig/completion/dde-dconfig.bash b/dconfig-center/dde-dconfig/completion/dde-dconfig.bash index d703917..073bf6a 100644 --- a/dconfig-center/dde-dconfig/completion/dde-dconfig.bash +++ b/dconfig-center/dde-dconfig/completion/dde-dconfig.bash @@ -1,71 +1,329 @@ #!/bin/bash -# SPDX-FileCopyrightText: 2024 Uniontech Software Technology Co.,Ltd. +# SPDX-FileCopyrightText: 2024 - 2026 Uniontech Software Technology Co.,Ltd. # # SPDX-License-Identifier: LGPL-3.0-or-later -_dde_dconfig() { - local cur prev words cword - _init_completion || return +_dde_dconfig_read_option() +{ + local option=$1 i word + _dde_dconfig_option_set=0 + _dde_dconfig_option_value= - local opts=("-a -r -k") + for ((i = 1; i < cword; ++i)); do + word=${words[i]} + if [[ $word == "$option" ]]; then + _dde_dconfig_option_set=1 + if ((i + 1 < cword)); then + _dde_dconfig_option_value=${words[i + 1]} + fi + elif [[ $word == "$option="* ]]; then + _dde_dconfig_option_set=1 + _dde_dconfig_option_value=${word#*=} + fi + done - case $prev in - 'dde-dconfig') - COMPREPLY=($(compgen -W 'help list set get reset gui watch' -- $cur)) - return - ;; - 'list' | 'set' | 'reset' | 'watch') - local result=($(dde-dconfig list)) - COMPREPLY=($(compgen -W "${result[*]}" -- $cur)) - return + # zsh and some bash-completion versions can retain the source quotes for + # an explicitly empty word. They mean the documented empty appid/value. + if [[ $_dde_dconfig_option_value == '""' || $_dde_dconfig_option_value == "''" ]]; then + _dde_dconfig_option_value= + fi +} + +_dde_dconfig_find_command() +{ + local i word skip_next=0 + _dde_dconfig_command= + for ((i = 1; i < cword; ++i)); do + word=${words[i]} + if ((skip_next)); then + skip_next=0 + continue + fi + case $word in + -u | -a | -r | -s | -k | -v | -p | -m | -l) skip_next=1 ;; + -u=* | -a=* | -r=* | -s=* | -k=* | -v=* | -p=* | -m=* | -l=* | -h | --help) ;; + list | --list) _dde_dconfig_command=list ;; + get | --get) _dde_dconfig_command=get ;; + set | --set) _dde_dconfig_command=set ;; + reset | --reset) _dde_dconfig_command=reset ;; + watch | --watch) _dde_dconfig_command=watch ;; + gui | --gui) _dde_dconfig_command=gui ;; + esac + [[ -n $_dde_dconfig_command ]] && return + done +} + +_dde_dconfig_find_positionals() +{ + local i word skip_next=0 command_seen=0 + _dde_dconfig_positionals=() + + for ((i = 1; i < cword; ++i)); do + word=${words[i]} + if ((skip_next)); then + skip_next=0 + continue + fi + case $word in + -u | -a | -r | -s | -k | -v | -p | -m | -l) + skip_next=1 + continue + ;; + -u=* | -a=* | -r=* | -s=* | -k=* | -v=* | -p=* | -m=* | -l=* | -h | --help) + continue + ;; + --list | --get | --set | --reset | --watch | --gui) + command_seen=1 + continue + ;; + list | get | set | reset | watch | gui) + if ((!command_seen)); then + command_seen=1 + continue + fi + ;; + -*) + continue + ;; + esac + + if ((command_seen)); then + if [[ $word == '""' || $word == "''" ]]; then + word= + fi + _dde_dconfig_positionals+=("$word") + fi + done + _dde_dconfig_positional_count=${#_dde_dconfig_positionals[@]} +} + +_dde_dconfig_context() +{ + _dde_dconfig_find_positionals + + _dde_dconfig_read_option -a + _dde_dconfig_appid_option_set=$_dde_dconfig_option_set + _dde_dconfig_appid_set=$_dde_dconfig_option_set + _dde_dconfig_appid=$_dde_dconfig_option_value + if ((!_dde_dconfig_appid_set && _dde_dconfig_positional_count > 0)); then + _dde_dconfig_appid_set=1 + _dde_dconfig_appid=${_dde_dconfig_positionals[0]} + fi + + _dde_dconfig_read_option -r + _dde_dconfig_resource_option_set=$_dde_dconfig_option_set + _dde_dconfig_resource_set=$_dde_dconfig_option_set + _dde_dconfig_resource=$_dde_dconfig_option_value + if ((!_dde_dconfig_resource_set && _dde_dconfig_appid_set)); then + _dde_dconfig_resource_set=1 + _dde_dconfig_resource=$_dde_dconfig_appid + fi + + _dde_dconfig_read_option -k + _dde_dconfig_key_option_set=$_dde_dconfig_option_set + _dde_dconfig_key_set=$_dde_dconfig_option_set + _dde_dconfig_key=$_dde_dconfig_option_value + local key_positional_index=1 + ((_dde_dconfig_appid_option_set)) && key_positional_index=0 + if ((!_dde_dconfig_key_set && _dde_dconfig_positional_count > key_positional_index)); then + _dde_dconfig_key_set=1 + _dde_dconfig_key=${_dde_dconfig_positionals[key_positional_index]} + fi +} + +_dde_dconfig_add_matches() +{ + local insert_prefix=$1 typed=$2 candidate + shift 2 + for candidate in "$@"; do + [[ $candidate == "$typed"* ]] && COMPREPLY+=("${insert_prefix}${candidate}") + done +} + +_dde_dconfig_complete_subpath() +{ + local typed=$1 + # A subpath is runtime-defined. Complete only its required leading slash; + # do not enumerate or validate any content below it. + [[ / == "$typed"* ]] && COMPREPLY+=(/) + compopt -o nospace 2>/dev/null +} + +_dde_dconfig_complete_resource_option() +{ + # Do not synthesize an option=value word. Complete the option first, then + # let the normal `-r VALUE` state complete the resource on the next Tab. + _dde_dconfig_add_matches '' "$cur" -r +} + +_dde_dconfig_complete_appids() +{ + local insert_prefix=$1 typed=$2 line + local -a candidates=("''") + while IFS= read -r line; do + [[ -n $line ]] && candidates+=("$line") + done < <(command dde-dconfig list 2>/dev/null) + _dde_dconfig_add_matches "$insert_prefix" "$typed" "${candidates[@]}" +} + +_dde_dconfig_complete_resources() +{ + local insert_prefix=$1 typed=$2 line + local -a candidates=() command_line=(dde-dconfig list -a '') + _dde_dconfig_context + if ((_dde_dconfig_appid_set)); then + command_line=(dde-dconfig list -a "$_dde_dconfig_appid") + fi + while IFS= read -r line; do + [[ -n $line ]] && candidates+=("$line") + done < <(command "${command_line[@]}" 2>/dev/null) + _dde_dconfig_add_matches "$insert_prefix" "$typed" "${candidates[@]}" +} + +_dde_dconfig_complete_keys() +{ + local insert_prefix=$1 typed=$2 line + local -a candidates=() command_line + _dde_dconfig_context + ((_dde_dconfig_appid_set)) || return + ((_dde_dconfig_resource_set)) || return + [[ -n $_dde_dconfig_resource ]] || return + + command_line=(dde-dconfig --get -a "$_dde_dconfig_appid" -r "$_dde_dconfig_resource") + # Subpath is dynamic. Key completion intentionally queries the resource + # schema without trying to enumerate or validate the current -s value. + while IFS= read -r line; do + [[ -n $line ]] && candidates+=("$line") + done < <(command "${command_line[@]}" 2>/dev/null) + _dde_dconfig_add_matches "$insert_prefix" "$typed" "${candidates[@]}" +} + +_dde_dconfig_option_used() +{ + local option=$1 word i + # The current word is only a prefix being completed, not an option that + # has already been consumed. This matches the Zsh context scan. + for ((i = 1; i < cword; ++i)); do + word=${words[i]} + [[ $word == "$option" || $word == "$option="* ]] && return 0 + done + return 1 +} + +_dde_dconfig_complete_options() +{ + local option + local -a options=() + case $_dde_dconfig_command in + list) options=(-h --help -a -r -p) ;; + get) options=(-h --help -u -a -r -s -k -p -m -l) ;; + set) options=(-h --help -u -a -r -s -k -v -p) ;; + reset | watch) options=(-h --help -u -a -r -s -k -p) ;; + gui) options=(-h --help) ;; + *) options=(-h --help -u -a -r -s -k -v -p -m -l) ;; + esac + + for option in "${options[@]}"; do + if ! _dde_dconfig_option_used "$option" && [[ $option == "$cur"* ]]; then + COMPREPLY+=("$option") + fi + done +} + +_dde_dconfig_complete_next() +{ + _dde_dconfig_context + + case $_dde_dconfig_command in + list) + if ((!_dde_dconfig_appid_set)); then + _dde_dconfig_complete_appids '' "$cur" + elif ((!_dde_dconfig_resource_option_set)); then + _dde_dconfig_complete_resource_option + else + _dde_dconfig_complete_options + fi ;; - 'get') - if [[ $cur == [-]* ]]; then - COMPREPLY=($(compgen -W "${opts}" -- ${cur})) - return + get | set | reset | watch) + if ((!_dde_dconfig_appid_set)); then + _dde_dconfig_complete_appids '' "$cur" + elif ((!_dde_dconfig_key_set)); then + if [[ -n $_dde_dconfig_resource ]]; then + # The CLI accepts the next positional argument as Key both after a + # positional appid and when appid was supplied with -a. + _dde_dconfig_complete_keys '' "$cur" + elif ((!_dde_dconfig_resource_option_set)); then + # An empty positional appid denotes generic configuration and has + # no implicit resource. Offer -r as the next step. + _dde_dconfig_complete_resource_option + else + _dde_dconfig_complete_options + fi + else + _dde_dconfig_complete_options fi - local result=($(dde-dconfig list)) - COMPREPLY=($(compgen -W "${result[*]}" -- $cur)) - return ;; - '-a') - local result=($(dde-dconfig list)) - COMPREPLY=($(compgen -W "${result[*]}" -- $cur)) + *) + _dde_dconfig_complete_options + ;; + esac +} + +_dde_dconfig() +{ + local cur prev words cword + # Keep --option=value as one word. Otherwise bash-completion splits it + # around `=`, and the value can be mistaken for a positional argument. + _init_completion -n = || return + COMPREPLY=() + _dde_dconfig_find_command + + case $cur in + -a=*) _dde_dconfig_complete_appids '' "${cur#*=}"; return ;; + -r=*) _dde_dconfig_complete_resources '' "${cur#*=}"; return ;; + -k=*) _dde_dconfig_complete_keys '' "${cur#*=}"; return ;; + -s=*) _dde_dconfig_complete_subpath "${cur#*=}"; return ;; + -u=* | -v=* | -l=*) return ;; + -p=*) + cur=${cur#*=} + _filedir -d return ;; - '-r') - local a=${COMP_WORDS[COMP_CWORD - 2]} - local result=($(dde-dconfig list -a $a)) - COMPREPLY=($(compgen -W "${result[*]}" -- $cur)) + -m=*) + _dde_dconfig_add_matches '' "${cur#*=}" value name description visibility permissions version isDefaultValue return ;; - '-k') - local a=${COMP_WORDS[COMP_CWORD - 4]} - local r=${COMP_WORDS[COMP_CWORD - 2]} - local result=($(dde-dconfig --get -a $a -r $r)) - COMPREPLY=($(compgen -W "${result[*]}" -- $cur)) + esac + + case $prev in + -a) _dde_dconfig_complete_appids '' "$cur"; return ;; + -r) _dde_dconfig_complete_resources '' "$cur"; return ;; + -k) _dde_dconfig_complete_keys '' "$cur"; return ;; + -s) _dde_dconfig_complete_subpath "$cur"; return ;; + -u | -v | -l) return ;; + -p) _filedir -d; return ;; + -m) + _dde_dconfig_add_matches '' "$cur" value name description visibility permissions version isDefaultValue return ;; esac - if [[ $cur == [-]* ]]; then - COMPREPLY=($(compgen -W "${opts}" -- ${cur})) + if [[ $cur == -* ]]; then + if [[ -z $_dde_dconfig_command ]]; then + _dde_dconfig_add_matches '' "$cur" -h --help -u -a -r -s -k -v -p -m -l --list --get --set --reset --watch --gui + else + _dde_dconfig_complete_options + fi + return fi - if ((${#COMPREPLY[@]} != 0)); then - # this removes any options from the list of completions that have - # already been specified somewhere on the command line, as long as - # these options can only be used once. - local -A onlyonce=([-a]=1 [-r]=1 [-k]=1) - local j - for i in "${words[@]}"; do - [[ $i && -v onlyonce["$i"] ]] || continue - for j in "${!COMPREPLY[@]}"; do - [[ ${COMPREPLY[j]} == "$i" ]] && unset 'COMPREPLY[j]' - done - done + if [[ -z $_dde_dconfig_command ]]; then + _dde_dconfig_add_matches '' "$cur" help list get set reset watch gui + return fi -} && - complete -F _dde_dconfig dde-dconfig + _dde_dconfig_complete_next +} + +complete -F _dde_dconfig dde-dconfig diff --git a/dconfig-center/dde-dconfig/completion/dde-dconfig.zsh b/dconfig-center/dde-dconfig/completion/dde-dconfig.zsh index a10d31c..9fea042 100644 --- a/dconfig-center/dde-dconfig/completion/dde-dconfig.zsh +++ b/dconfig-center/dde-dconfig/completion/dde-dconfig.zsh @@ -1,60 +1,459 @@ #compdef dde-dconfig -# SPDX-FileCopyrightText: 2024 Uniontech Software Technology Co.,Ltd. +# SPDX-FileCopyrightText: 2024 - 2026 Uniontech Software Technology Co.,Ltd. # # SPDX-License-Identifier: LGPL-3.0-or-later -_dde-dconfig() { - local ret=1 +_dde_dconfig_zsh_read_option() +{ + local option=$1 word + local -i i + _dde_dconfig_zsh_option_set=0 + _dde_dconfig_zsh_option_value= + + for ((i = 2; i < CURRENT; ++i)); do + word=${words[i]} + if [[ $word == $option ]]; then + _dde_dconfig_zsh_option_set=1 + if ((i + 1 < CURRENT)); then + _dde_dconfig_zsh_option_value=${words[i + 1]} + fi + elif [[ $word == ${option}=* ]]; then + _dde_dconfig_zsh_option_set=1 + _dde_dconfig_zsh_option_value=${word#*=} + fi + done + + if [[ $_dde_dconfig_zsh_option_value == '""' || $_dde_dconfig_zsh_option_value == "''" ]]; then + _dde_dconfig_zsh_option_value= + fi +} + +_dde_dconfig_zsh_find_command() +{ + local word + local -i i skip_next=0 + _dde_dconfig_zsh_command= + _dde_dconfig_zsh_command_token= + for ((i = 2; i < CURRENT; ++i)); do + word=${words[i]} + if ((skip_next)); then + skip_next=0 + continue + fi + case $word in + -u | -a | -r | -s | -k | -v | -p | -m | -l) skip_next=1 ;; + -u=* | -a=* | -r=* | -s=* | -k=* | -v=* | -p=* | -m=* | -l=* | -h | --help) ;; + list | --list) _dde_dconfig_zsh_command=list ;; + get | --get) _dde_dconfig_zsh_command=get ;; + set | --set) _dde_dconfig_zsh_command=set ;; + reset | --reset) _dde_dconfig_zsh_command=reset ;; + watch | --watch) _dde_dconfig_zsh_command=watch ;; + gui | --gui) _dde_dconfig_zsh_command=gui ;; + esac + if [[ -n $_dde_dconfig_zsh_command ]]; then + _dde_dconfig_zsh_command_token=$word + return + fi + done +} + +_dde_dconfig_zsh_find_positionals() +{ + local word + local -i i skip_next=0 command_seen=0 + _dde_dconfig_zsh_positionals=() + + for ((i = 2; i < CURRENT; ++i)); do + word=${words[i]} + if ((skip_next)); then + skip_next=0 + continue + fi + case $word in + -u | -a | -r | -s | -k | -v | -p | -m | -l) + skip_next=1 + continue + ;; + -u=* | -a=* | -r=* | -s=* | -k=* | -v=* | -p=* | -m=* | -l=* | -h | --help) + continue + ;; + --list | --get | --set | --reset | --watch | --gui) + command_seen=1 + continue + ;; + list | get | set | reset | watch | gui) + if ((!command_seen)); then + command_seen=1 + continue + fi + ;; + -*) + continue + ;; + esac + + if ((command_seen)); then + if [[ $word == '""' || $word == "''" ]]; then + word= + fi + _dde_dconfig_zsh_positionals+=("$word") + fi + done + _dde_dconfig_zsh_positional_count=${#_dde_dconfig_zsh_positionals[@]} +} + +_dde_dconfig_zsh_context() +{ + _dde_dconfig_zsh_find_positionals + + _dde_dconfig_zsh_read_option -a + _dde_dconfig_zsh_appid_option_set=$_dde_dconfig_zsh_option_set + _dde_dconfig_zsh_appid_set=$_dde_dconfig_zsh_option_set + _dde_dconfig_zsh_appid=$_dde_dconfig_zsh_option_value + if ((!_dde_dconfig_zsh_appid_set && _dde_dconfig_zsh_positional_count > 0)); then + _dde_dconfig_zsh_appid_set=1 + _dde_dconfig_zsh_appid=${_dde_dconfig_zsh_positionals[1]} + fi + + _dde_dconfig_zsh_read_option -r + _dde_dconfig_zsh_resource_option_set=$_dde_dconfig_zsh_option_set + _dde_dconfig_zsh_resource_set=$_dde_dconfig_zsh_option_set + _dde_dconfig_zsh_resource=$_dde_dconfig_zsh_option_value + if ((!_dde_dconfig_zsh_resource_set && _dde_dconfig_zsh_appid_set)); then + _dde_dconfig_zsh_resource_set=1 + _dde_dconfig_zsh_resource=$_dde_dconfig_zsh_appid + fi + + _dde_dconfig_zsh_read_option -k + _dde_dconfig_zsh_key_option_set=$_dde_dconfig_zsh_option_set + _dde_dconfig_zsh_key_set=$_dde_dconfig_zsh_option_set + _dde_dconfig_zsh_key=$_dde_dconfig_zsh_option_value + local -i key_positional_index=2 + ((_dde_dconfig_zsh_appid_option_set)) && key_positional_index=1 + if ((!_dde_dconfig_zsh_key_set && _dde_dconfig_zsh_positional_count >= key_positional_index)); then + _dde_dconfig_zsh_key_set=1 + _dde_dconfig_zsh_key=${_dde_dconfig_zsh_positionals[key_positional_index]} + fi +} + +_dde_dconfig_zsh_complete_subpath() +{ + # A subpath is runtime-defined. Complete only its required leading slash; + # an empty suffix keeps the cursor directly after `/` for further input. + compadd -S '' -- / +} + +_dde_dconfig_zsh_complete_resource_option() +{ + # Keep the generated command line consistent with Bash: complete `-r` + # first and complete its value in the normal `-r VALUE` state. + compadd -- -r +} + +_dde_dconfig_zsh_complete_appids() +{ + local -a result + result=("${(@f)$(command dde-dconfig list 2>/dev/null)}") + result=(${result:#}) + # -Q inserts shell quotes rather than escaping them, so accepting this + # candidate passes a real empty string to dde-dconfig. + compadd -Q -- "''" + ((${#result[@]})) && compadd -a result +} + +_dde_dconfig_zsh_complete_resources() +{ + local insert_prefix=$1 + local -a result command_line + _dde_dconfig_zsh_context + command_line=(dde-dconfig list -a '') + if ((_dde_dconfig_zsh_appid_set)); then + command_line=(dde-dconfig list -a "$_dde_dconfig_zsh_appid") + fi + result=("${(@f)$(command "${command_line[@]}" 2>/dev/null)}") + result=(${result:#}) + if ((${#result[@]})); then + if [[ -n $insert_prefix ]]; then + compadd -P "$insert_prefix" -a result + else + compadd -a result + fi + fi +} + +_dde_dconfig_zsh_option_used() +{ + local option=$1 word + local -i i + for ((i = 2; i < CURRENT; ++i)); do + word=${words[i]} + [[ $word == $option || $word == ${option}=* ]] && return 0 + done + return 1 +} + +_dde_dconfig_zsh_complete_options() +{ + # When an option prefix is already being typed, _arguments supplies the + # same command-specific candidates. Avoid adding a second described copy. + [[ $PREFIX == -* ]] && return 1 + + local entry option + local -a candidates=() supported=() + case $_dde_dconfig_zsh_command in + list) + supported=( + '-h:display help information' '--help:display help information' + '-a:指定应用Id,空字符串表示应用无关配置' + '-r:指定配置Id(resource)' '-p:指定工作目录前缀' + ) + ;; + get) + supported=( + '-h:display help information' '--help:display help information' + '-u:指定用户uid' '-a:指定应用Id,空字符串表示应用无关配置' + '-r:指定配置Id(resource)' '-s:指定动态子路径(subpath)' + '-k:指定配置项Key值' '-p:指定工作目录前缀' + '-m:指定get查询的方法' '-l:指定配置项语言' + ) + ;; + set) + supported=( + '-h:display help information' '--help:display help information' + '-u:指定用户uid' '-a:指定应用Id,空字符串表示应用无关配置' + '-r:指定配置Id(resource)' '-s:指定动态子路径(subpath)' + '-k:指定配置项Key值' '-v:指定set命令的新值' + '-p:指定工作目录前缀' + ) + ;; + reset | watch) + supported=( + '-h:display help information' '--help:display help information' + '-u:指定用户uid' '-a:指定应用Id,空字符串表示应用无关配置' + '-r:指定配置Id(resource)' '-s:指定动态子路径(subpath)' + '-k:指定配置项Key值' '-p:指定工作目录前缀' + ) + ;; + gui) + supported=('-h:display help information' '--help:display help information') + ;; + *) + supported=( + '-h:display help information' '--help:display help information' + '-u:指定用户uid' '-a:指定应用Id,空字符串表示应用无关配置' + '-r:指定配置Id(resource)' '-s:指定动态子路径(subpath)' + '-k:指定配置项Key值' '-v:指定set命令的新值' + '-p:指定工作目录前缀' '-m:指定get查询的方法' + '-l:指定配置项语言' + ) + ;; + esac + + for entry in "${supported[@]}"; do + option=${entry%%:*} + _dde_dconfig_zsh_option_used "$option" || candidates+=("$entry") + done + ((${#candidates[@]})) || return 1 + _describe -t options 'options' candidates +} + +_dde_dconfig_zsh_complete_keys() +{ + local insert_prefix=$1 + local -a result command_line + _dde_dconfig_zsh_context + ((_dde_dconfig_zsh_appid_set)) || return 1 + ((_dde_dconfig_zsh_resource_set)) || return 1 + [[ -n $_dde_dconfig_zsh_resource ]] || return 1 + + command_line=(dde-dconfig --get -a "$_dde_dconfig_zsh_appid" -r "$_dde_dconfig_zsh_resource") + # Subpath is dynamic. Key completion intentionally queries the resource + # schema without trying to enumerate or validate the current -s value. + result=("${(@f)$(command "${command_line[@]}" 2>/dev/null)}") + result=(${result:#}) + if ((${#result[@]})); then + if [[ -n $insert_prefix ]]; then + compadd -P "$insert_prefix" -a result + else + compadd -a result + fi + fi +} + +_dde_dconfig_zsh_complete_next() +{ + _dde_dconfig_zsh_context + + case $_dde_dconfig_zsh_command in + list) + if ((!_dde_dconfig_zsh_appid_set)); then + _dde_dconfig_zsh_complete_appids + elif ((!_dde_dconfig_zsh_resource_option_set)); then + _dde_dconfig_zsh_complete_resource_option + else + _dde_dconfig_zsh_complete_options + fi + ;; + get | set | reset | watch) + if ((!_dde_dconfig_zsh_appid_set)); then + _dde_dconfig_zsh_complete_appids + elif ((!_dde_dconfig_zsh_key_set)); then + if [[ -n $_dde_dconfig_zsh_resource ]]; then + # The CLI accepts the next positional argument as Key both after a + # positional appid and when appid was supplied with -a. + _dde_dconfig_zsh_complete_keys + elif ((!_dde_dconfig_zsh_resource_option_set)); then + _dde_dconfig_zsh_complete_resource_option + else + _dde_dconfig_zsh_complete_options + fi + else + _dde_dconfig_zsh_complete_options + fi + ;; + *) + _dde_dconfig_zsh_complete_options + ;; + esac +} + +_dde-dconfig() +{ + local ret=1 state local -a commands=( - 'list:list模式,列出可配置的应用Id,配置Id,及子目录' + 'list:list模式,列出可配置的应用Id和配置Id' 'get:get模式,用于查询指定配置项的信息' 'set:set模式,用于设置配置项的值' - 'reset:reset模式,用于重置配置项的值,此会清除对应的缓存值' + 'reset:reset模式,用于重置配置项的值' 'watch:watch模式,用于监听配置项的更改' - 'gui:gui模式,用于启动GUI工具,需要安装对应的GUI工具dde-dconfig-editor' + 'gui:gui模式,用于启动dde-dconfig-editor' ) - - _arguments -C -s -S -n \ - '(* -)'{-v,--version}"[display version information]: :->full" \ - '(- 1 *)'{-h,--help}'[display usage information]: :->full' \ - '1:cmd:->cmds' \ - '*:: :->args' && ret=0 - - local last_index=$((${#words[@]}-1)) - local prev=${words[$last_index]} - case "$state" in - cmds) - _describe -t commands 'commands' commands - ;; - args) - case $prev in - 'list' | 'set' | 'get' | 'reset' | 'watch' | '-a') - local result=($(dde-dconfig list)) - compadd -a result - ret=0 + _dde_dconfig_zsh_find_command + local -a argument_options=( + '(-h --help)-h[display help information]' + '(-h --help)--help[display help information]' + ) + case $_dde_dconfig_zsh_command in + list) + argument_options+=( + '(-a)-a+[application id; empty means generic configuration]:appid:->appid' + '(-r)-r+[configuration resource id]:resource:->resource' + '(-p)-p+[working prefix directory]:directory:->directory' + ) ;; - '-r') - local a=$words[$(($last_index-1))] - local result=($(dde-dconfig list -a $a)) - compadd -a result - ret=0 + get) + argument_options+=( + '(-u)-u+[operate configuration for uid]:uid:->uid' + '(-a)-a+[application id; empty means generic configuration]:appid:->appid' + '(-r)-r+[configuration resource id]:resource:->resource' + '(-s)-s+[dynamic configuration subpath]:subpath:->subpath' + '(-k)-k+[configuration key]:key:->key' + '(-p)-p+[working prefix directory]:directory:->directory' + '(-m)-m+[query method]:method:->method' + '(-l)-l+[configuration language]:language:->language' + ) ;; - '-k') - local a=$words[$(($last_index-3))] - local r=$words[$(($last_index-1))] - local result=($(dde-dconfig --get -a $a -r $r)) - compadd -a result - ret=0 + set) + argument_options+=( + '(-u)-u+[operate configuration for uid]:uid:->uid' + '(-a)-a+[application id; empty means generic configuration]:appid:->appid' + '(-r)-r+[configuration resource id]:resource:->resource' + '(-s)-s+[dynamic configuration subpath]:subpath:->subpath' + '(-k)-k+[configuration key]:key:->key' + '(-v)-v+[new value for set]:value:->value' + '(-p)-p+[working prefix directory]:directory:->directory' + ) + ;; + reset | watch) + argument_options+=( + '(-u)-u+[operate configuration for uid]:uid:->uid' + '(-a)-a+[application id; empty means generic configuration]:appid:->appid' + '(-r)-r+[configuration resource id]:resource:->resource' + '(-s)-s+[dynamic configuration subpath]:subpath:->subpath' + '(-k)-k+[configuration key]:key:->key' + '(-p)-p+[working prefix directory]:directory:->directory' + ) + ;; + gui) ;; *) - local result=('-a' '-k' '-r') - compadd -a result - ret=0 + argument_options+=( + '(-u)-u+[operate configuration for uid]:uid:->uid' + '(-a)-a+[application id; empty means generic configuration]:appid:->appid' + '(-r)-r+[configuration resource id]:resource:->resource' + '(-s)-s+[dynamic configuration subpath]:subpath:->subpath' + '(-k)-k+[configuration key]:key:->key' + '(-v)-v+[new value for set]:value:->value' + '(-p)-p+[working prefix directory]:directory:->directory' + '(-m)-m+[query method]:method:->method' + '(-l)-l+[configuration language]:language:->language' + '--list[list mode]' '--get[get mode]' '--set[set mode]' + '--reset[reset mode]' '--watch[watch mode]' + '--gui[start dde-dconfig-editor]' + ) ;; + esac + + # A hidden long command is itself an option. Keep its spec so _arguments can + # parse the remaining words, while the self-exclusion prevents suggesting it + # again. Positional commands do not need any hidden command specs. + if [[ $_dde_dconfig_zsh_command_token == --* ]]; then + case $_dde_dconfig_zsh_command in + list) argument_options+=('(--list)--list[list mode]') ;; + get) argument_options+=('(--get)--get[get mode]') ;; + set) argument_options+=('(--set)--set[set mode]') ;; + reset) argument_options+=('(--reset)--reset[reset mode]') ;; + watch) argument_options+=('(--watch)--watch[watch mode]') ;; + gui) argument_options+=('(--gui)--gui[start dde-dconfig-editor]') ;; esac - ;; - *) ;; + fi + + # The rest argument uses a single colon so `words` retains the full + # command line for the context scanner below. + _arguments -C -s -S -n \ + "${argument_options[@]}" \ + '1:command:->commands' \ + '*:argument:->args' && ret=0 + + _dde_dconfig_zsh_find_command + # With an attached short-option value (`-r=value`), _arguments leaves + # the leading `=` in PREFIX. Move it to IPREFIX so candidates match the + # actual value while preserving the option text on insertion. + [[ $PREFIX == '='* ]] && compset -p 1 + + case $state in + commands) + if [[ -n $_dde_dconfig_zsh_command ]]; then + _dde_dconfig_zsh_complete_next && ret=0 + else + _describe -t commands 'commands' commands && ret=0 + fi + ;; + appid) + _dde_dconfig_zsh_complete_appids && ret=0 + ;; + resource) + _dde_dconfig_zsh_complete_resources && ret=0 + ;; + subpath) + _dde_dconfig_zsh_complete_subpath && ret=0 + ;; + key) + _dde_dconfig_zsh_complete_keys && ret=0 + ;; + directory) + _directories && ret=0 + ;; + method) + compadd -- value name description visibility permissions version isDefaultValue && ret=0 + ;; + uid | value | language) + _message "enter $state" && ret=0 + ;; + args) + _dde_dconfig_zsh_complete_next && ret=0 + ;; esac return ret diff --git a/dconfig-center/dde-dconfig/main.cpp b/dconfig-center/dde-dconfig/main.cpp index 5f1e97b..013524c 100644 --- a/dconfig-center/dde-dconfig/main.cpp +++ b/dconfig-center/dde-dconfig/main.cpp @@ -59,31 +59,45 @@ class CommandManager { subpathid = parser.value(subpathOption); key = fetchKey(); } - // fallback to positionalArgument as key + // Fall back to a positional key. When -a supplies the appid, the first + // argument after the command is already the key; otherwise it follows the + // positional appid. inline QString fetchKey() const { if (parser.isSet(keyOption)) return parser.value(keyOption); - if (parser.positionalArguments().size() > 2) { - return parser.positionalArguments().at(2); - } + + const auto positions = parser.positionalArguments(); + const int keyIndex = parser.isSet(appidOption) ? 1 : 2; + if (positions.size() > keyIndex) + return positions.at(keyIndex); return QString(); } inline bool isSetKey() const { - return parser.isSet(keyOption) || parser.positionalArguments().size() > 2; + if (parser.isSet(keyOption)) + return true; + const int keyIndex = parser.isSet(appidOption) ? 1 : 2; + return parser.positionalArguments().size() > keyIndex; } // fallback to positionalArgument as appid inline QString fetchAppid() const { - if (parser.isSet(appidOption)) - return parser.value(appidOption); - if (parser.positionalArguments().size() > 1) { - return parser.positionalArguments().at(1); + QString value; + if (parser.isSet(appidOption)) { + value = parser.value(appidOption); + } else if (parser.positionalArguments().size() > 1) { + value = parser.positionalArguments().at(1); } - return QString(); + + // Some completion engines retain the quotes of an explicitly empty + // argument in their words array. Accept those representations as the + // documented empty appid used for application-independent configs. + if (value == QStringLiteral("\"\"") || value == QStringLiteral("''")) + return QString(); + return value; } inline bool isSetAppid() const @@ -139,7 +153,7 @@ int CommandManager::listCommand() { // list命令,查看app、resource、subpath if (isSetAppid()) { - if (!existAppid(appid)) { + if (!appid.isEmpty() && !existAppid(appid)) { outpuSTDError(QString("not exist appid:%1").arg(appid)); return 1; } @@ -154,7 +168,7 @@ int CommandManager::listCommand() outpuSTD(item); } } else { - auto resources = resourcesForApp(appid); + const auto resources = availableResourcesForApp(appid); for (auto item : resources) { outpuSTD(item); } @@ -188,7 +202,8 @@ int CommandManager::getCommand() "description", "visibility", "permissions", - "version"}; + "version", + "isDefaultValue"}; for (auto item : methods) { outpuSTD(item); } @@ -404,7 +419,7 @@ int main(int argc, char *argv[]) parser.addOption(subpathOption); QCommandLineOption keyOption("k", QCoreApplication::translate("main", "configure item's key.\n" - "three positional argument as key if not the option"), "key", QString()); + "next positional argument after appid as key if not the option"), "key", QString()); parser.addOption(keyOption); QCommandLineOption valueOption("v", QCoreApplication::translate("main", "new value to set configure item."), "value", QString()); diff --git a/dconfig-center/docs/DtkConfig.zh_CN.md b/dconfig-center/docs/DtkConfig.zh_CN.md index 33a9978..8221f1a 100644 --- a/dconfig-center/docs/DtkConfig.zh_CN.md +++ b/dconfig-center/docs/DtkConfig.zh_CN.md @@ -280,7 +280,7 @@ sudo journalctl -u dde-dconfig-daemon.service -f -b ## 内置工具 ### dde-dconfig -dde-config 是一个命令行工具,主要用于通过命令行的方式,浏览和设置应用的配置项,可根据`dde-dconfig --help`获取帮助信息。 +dde-dconfig 是一个命令行工具,主要用于通过命令行的方式,浏览和设置应用的配置项,可根据`dde-dconfig --help`获取帮助信息。 以下以应用名为`dconfig-example`,配置id为 `example`, 配置项名称为 `key1`示例: - 设置配置项 @@ -295,6 +295,30 @@ dde-dconfig --get -a dconfig-example -r example -k key1 ``` bash dde-dconfig --list -a dconfig-example ``` +- 浏览和读取应用无关配置(空 appid): +``` bash +dde-dconfig --list -a "" +dde-dconfig --get -a "" -r example -k key1 +``` +- 使用动态 subpath: +``` bash +dde-dconfig --get -a dconfig-example -r example -s /runtime/instance -k key1 +``` + `subpath` 是运行时路径,并且必须以 `/` 开头。为方便输入,`-s` 的值为空时, + 命令补全只会补上起始的 `/`;`/` 后面的动态路径不会被枚举或校验,也不要求它 + 对应已安装的固定目录。补全 Key 时只根据 appid 和 resource 查询配置结构,不会把 + `subpath` 传给补全查询。 + + 命令和位置参数也支持逐级补全。例如 `get` 后补全 appid,位置 appid 后补全 Key; + Bash 和 Zsh 使用相同的补全步骤。对于应用无关配置,输入 `dde-dconfig get ''`(或 + `dde-dconfig get -a ''`)后会先补全 `-r`,再次按 Tab 补全 resource 的值,生成 `-r `,而不会擅自生成 + `-r=`;只有用户已经输入 `-r=` 时才保留等号形式并补全它的值。选择资源后 + 继续补全 Key。使用 `-a` 指定 appid 时,下一个位置参数同样可以直接作为 Key, + 补全不会额外插入 `-k=`: +``` bash +dde-dconfig get '' -r example key1 +dde-dconfig get -a dconfig-example -r example key1 +``` - 启动 dde-dconfig-editor ``` bash dde-dconfig --gui @@ -312,6 +336,5 @@ sudo apt install dde-dconfig-editor - OEM: 导出override配置文件 - config language: 切换配置项描述语言,默认为英文 - -***应用列表是根据$DSG_DATA_DIRS/configs或者默认的/usr/share/dsg/configs中的子目录名来获取的,所以对于自定义的appid,应用无关配置无法通过此工具设置。*** - +应用列表根据 `$DSG_DATA_DIRS/configs`(默认 `/usr/share/dsg/configs`)中的子目录名生成; +应用无关配置不属于任何应用目录,使用显式空 appid(`-a ""`)访问。 diff --git a/dconfig-center/example/dde-dconfig.sh b/dconfig-center/example/dde-dconfig.sh index 0d3ce6b..1c905cc 100755 --- a/dconfig-center/example/dde-dconfig.sh +++ b/dconfig-center/example/dde-dconfig.sh @@ -14,8 +14,8 @@ dde-dconfig list # list all resource for the appid. dde-dconfig list dconfig-example -# list all matched common resource. -dde-dconfig list -r="" +# list all application-independent resources with an explicitly empty appid. +dde-dconfig list -a "" #list all subpath for the resource. dde-dconfig list dconfig-example -r=example @@ -25,6 +25,7 @@ dde-dconfig list dconfig-example -r=example dde-dconfig get # query all keys for the resource, which `subpath` is '/a'. +# Subpath is dynamic and is passed through without completion-time validation. dde-dconfig get dconfig-example -r=example -s=/a # query name for the key, which `language` is 'zh_CN'.