public function complete_application() { $buf = new Buffer(); $buf->appendLines(array("# {$this->programName} zsh completion script generated by CLIFramework", "# Web: http://github.com/c9s/php-CLIFramework", "# THIS IS AN AUTO-GENERATED FILE, PLEASE DON'T MODIFY THIS FILE DIRECTLY.")); $metaName = '_' . $this->programName . 'meta'; $buf->append($this->commandmeta_function()); $buf->appendLines(array("{$this->compName}() {", "local curcontext=\$curcontext state line", "typeset -A opt_args", "local ret=1", $this->complete_with_subcommands($this->app), "return ret", "}", "compdef {$this->compName} {$this->bindName}")); return $buf->__toString(); }
public function generateCompleteFunction(Buffer $buf, CommandBase $cmd, $compPrefix) { $funcSuffix = command_signature_suffix($cmd); $buf->appendLine("{$compPrefix}_complete_{$funcSuffix} ()"); $buf->appendLine("{"); $buf->appendLine(local_bash_var('comp_prefix', $compPrefix)); $buf->append(' local cur words cword prev _get_comp_words_by_ref -n =: cur words cword prev local command_signature=$1 local command_index=$2 ((command_index++)) # Output application command alias mapping # aliases[ alias ] = command declare -A subcommand_alias # Define the command names declare -A subcommands declare -A subcommand_signs # option names defines the available options of this command declare -A options # options_require_value: defines the required completion type for each # option that requires a value. declare -A options_require_value '); $subcommands = $cmd->getCommands(); $subcommandAliasMap = array(); $subcommandDescMap = array(); $commandOptionMap = array(); $commandOptionRequireValueMap = array(); $commandSignMap = array(); foreach ($subcommands as $subcommand) { foreach ($subcommand->aliases() as $alias) { $subcommandAliasMap[$alias] = $subcommand->getName(); } $subcommandDescMap[$subcommand->getName()] = $subcommand->brief(); $commandSignMap[$subcommand->getName()] = command_signature_suffix($subcommand); } // Command signature is used for fetching meta information from the meta command. // And a command description map // // subcommands=(["add"]="command to add" ["commit"]="command to commit") // subcommand_alias=(["a"]="add" ["c"]="commit") // $buf->appendLine(set_bash_array('subcommands', $subcommandDescMap)); $buf->appendLine(set_bash_array('subcommand_alias', $subcommandAliasMap)); $buf->appendLine(set_bash_array('subcommand_signs', $commandSignMap)); // Generate the bash array for command options // // options=(["--debug"]=1 ["--verbose"]=1 ["--log-dir"]=1) // $options = $cmd->getOptionCollection(); foreach ($options as $option) { if ($option->short) { $commandOptionMap['-' . $option->short] = 1; } if ($option->long) { $commandOptionMap['--' . $option->long] = 1; } if ($option->required || $option->multiple) { if ($option->short) { $commandOptionRequireValueMap['-' . $option->short] = 1; } if ($option->long) { $commandOptionRequireValueMap['--' . $option->long] = 1; } } } $buf->appendLine(set_bash_array('options', $commandOptionMap)); // options_require_value=(["--log-dir"]="__complete_directory") $buf->appendLine(set_bash_array('options_require_value', $commandOptionRequireValueMap)); // local argument_min_length=0 $argInfos = $cmd->getArgInfoList(); // $buf->appendLine("local argument_min_length=" . count($argInfos)); $buf->appendLine(local_bash_var('argument_min_length', count($argInfos))); $buf->append(' # Get the command name chain of the current input, e.g. # # app asset install [arg1] [arg2] [arg3] # app commit add # # The subcommand dispatch should be done in the command complete function, # not in the root completion function. # We should pass the argument index to the complete function. # command_index=1 start from the first argument, not the application name # Find the command position local argument_index=0 local i local command local found_options=0 # echo "[DEBUG] command_index: [$command_signature] [$command_index]" while [ $command_index -lt $cword ]; do i="${words[command_index]}" case "$i" in # Ignore options --=*) found_options=1 ;; --*) found_options=1 ;; -*) found_options=1 ;; *) # looks like my command, that\'s break the loop and dispatch to the next complete function if [[ -n "$i" && -n "${subcommands[$i]}" ]] ; then command="$i" break elif [[ -n "$i" && -n "${subcommand_alias[$i]}" ]] ; then command="$i" break elif [[ $command_index -gt 1 ]] ; then # If the command is not found, check if the previous argument is an option expecting a value # or it is an argument # the previous argument (might be) p="${words[command_index-1]}" # not an option value, push to the argument list if [[ -z "${options_require_value[$p]}" ]] ; then # echo "[DEBUG] argument_index++ because of [$i]" ((argument_index++)) fi fi ;; esac ((command_index++)) done '); $buf->append(' # If the first command name is not found, we do complete... if [[ -z "$command" ]] ; then case "$cur" in # If the current argument $cur looks like an option, then we should complete -*) __mycomp "${!options[*]}" return ;; *) # The argument here can be an option value. e.g. --output-dir /tmp # The the previous one... if [[ -n "$prev" && -n "${options_require_value[$prev]}" ]] ; then # TODO: local complete_type="${options_require_value[$prev]"} '); $buf->appendLine(' __complete_meta "$command_signature" "opt" "${prev##*(-)}" "valid-values"'); $buf->appendLine(' return'); $buf->appendLine(' fi'); $buf->appendLine(' # If the command requires at least $argument_min_length to run, we check the argument'); if (count($argInfos) > 0) { $buf->appendLine('if [[ $argument_min_length > 0 ]] ; then'); // $buf->appendLine('echo argument_index: [$argument_index]'); // $buf->appendLine('echo cur: [$cur]'); // expand the argument case $buf->appendLine(' case $argument_index in'); foreach ($argInfos as $index => $a) { // TODO: when $a->multiple is enabled, we will use "*" for the case pattern. $pattern = $index; if ($a->multiple) { $pattern = '*'; } $buf->appendLine(" {$pattern})"); // $buf->appendLine('echo argument_index matched: [$argument_index]'); if ($a->validValues || $a->suggestions) { $values = array(); if ($a->validValues) { if (is_callable($a->validValues)) { $buf->appendLine(" __complete_meta \"\$command_signature\" \"arg\" {$index} \"valid-values\""); $buf->appendLine(' return'); } elseif ($values = $a->getValidValues()) { $buf->appendLine(' COMPREPLY=( $(compgen -W "' . join("\n", $values) . '" -- $cur) )'); $buf->appendLine(' return'); } } elseif ($a->suggestions) { if (is_callable($a->suggestions)) { $buf->appendLine(" __complete_meta \"\$command_signature\" \"arg\" {$index} \"suggestions\""); $buf->appendLine(' return'); } elseif ($values = $a->getSuggestions()) { $buf->appendLine(' COMPREPLY=( $(compgen -W "' . join("\n", $values) . '" -- $cur) )'); $buf->appendLine(' return'); } } } elseif (in_array($a->isa, array('file', 'path', 'dir'))) { $compopt = ''; switch ($a->isa) { case "file": $compopt .= ' -A file'; // $buf->appendLine('COMPREPLY=($(compgen -A file -- $cur))'); break; case "path": $compopt .= ' -A file'; break; case "command": $compopt .= ' -A command'; break; case "user": $compopt .= ' -A user'; break; case "service": $compopt .= ' -A service'; break; case "hostname": $compopt .= ' -A hostname'; break; case "job": $compopt .= ' -A job'; break; case "dir": case "directory": $compopt .= ' -A directory'; break; } // If the glob is specified, bash does not support for both -A with -G if ($a->glob) { $compopt = " -G \"{$a->glob}\""; } $buf->appendLine("COMPREPLY=(\$(compgen {$compopt} -- \$cur))"); $buf->appendLine("return"); } $buf->appendLine(" ;;"); } $buf->appendLine(' esac'); $buf->appendLine(' fi'); } $buf->append(' # If there is no argument support, then user is supposed to give a subcommand name or an option __mycomp "${!options[*]} ${!subcommands[*]} ${!subcommand_alias[*]}" return ;; esac '); // Dispatch $buf->append(' else # We just found the first command, we are going to dispatch the completion handler to the next level... # Rewrite command alias to command name to get the correct response if [[ -n "${subcommand_alias[$command]}" ]] ; then command="${subcommand_alias[$command]}" fi if [[ -n "${subcommand_signs[$command]}" ]] ; then local suffix="${subcommand_signs[$command]}" local completion_func="${comp_prefix}_complete_${suffix//-/_}" # Declare the completion function name and dispatch rest arguments to the complete function command_signature="${command_signature}.${command}" declare -f $completion_func >/dev/null && \\ $completion_func $command_signature $command_index && return else echo "Command \'$command\' not found" fi fi '); // Epilog $buf->appendLine("};"); }