Example #1
0
 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();
 }
Example #2
0
    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("};");
    }