4 namespace Stecman\Component\Symfony\Console\BashCompletion;
6 final class HookFactory
11 * These are shell-specific scripts that pass required information from that shell's
12 * completion system to the interface of the completion command in this module.
14 * The following placeholders are replaced with their value at runtime:
16 * %%function_name%% - name of the generated shell function run for completion
17 * %%program_name%% - command name completion will be enabled for
18 * %%program_path%% - path to program the completion is for/generated by
19 * %%completion_command%% - command to be run to compute completions
21 * NOTE: Comments are stripped out by HookFactory::stripComments as eval reads
22 * input as a single line, causing it to break if comments are included.
23 * While comments work using `... | source /dev/stdin`, existing installations
24 * are likely using eval as it's been part of the instructions for a while.
28 protected static $hooks = array(
31 # BASH completion for %%program_path%%
32 function %%function_name%% {
34 # Copy BASH's completion variables to the ones the completion command expects
35 # These line up exactly as the library was originally designed for BASH
36 local CMDLINE_CONTENTS="$COMP_LINE"
37 local CMDLINE_CURSOR_INDEX="$COMP_POINT"
38 local CMDLINE_WORDBREAKS="$COMP_WORDBREAKS";
40 export CMDLINE_CONTENTS CMDLINE_CURSOR_INDEX CMDLINE_WORDBREAKS
44 RESULT="$(%%completion_command%% </dev/null)";
47 local cur mail_check_backup;
49 mail_check_backup=$MAILCHECK;
52 _get_comp_words_by_ref -n : cur;
54 # Check if shell provided path completion is requested
55 # @see Completion\ShellPathCompletion
56 if [ $STATUS -eq 200 ]; then
60 # Bail out if PHP didn't exit cleanly
61 elif [ $STATUS -ne 0 ]; then
66 COMPREPLY=(`compgen -W "$RESULT" -- $cur`);
68 __ltrim_colon_completions "$cur";
70 MAILCHECK=mail_check_backup;
73 if [ "$(type -t _get_comp_words_by_ref)" == "function" ]; then
74 complete -F %%function_name%% "%%program_name%%";
76 >&2 echo "Completion was not registered for %%program_name%%:";
77 >&2 echo "The 'bash-completion' package is required but doesn't appear to be installed.";
83 # ZSH completion for %%program_path%%
84 function %%function_name%% {
85 local -x CMDLINE_CONTENTS="$words"
86 local -x CMDLINE_CURSOR_INDEX
87 (( CMDLINE_CURSOR_INDEX = ${#${(j. .)words[1,CURRENT]}} ))
90 RESULT=("${(@f)$( %%completion_command%% )}")
93 # Check if shell provided path completion is requested
94 # @see Completion\ShellPathCompletion
95 if [ $STATUS -eq 200 ]; then
99 # Bail out if PHP didn't exit cleanly
100 elif [ $STATUS -ne 0 ]; then
108 compdef %%function_name%% "%%program_name%%";
113 * Return the names of shells that have hooks
117 public static function getShellTypes()
119 return array_keys(self::$hooks);
123 * Return a completion hook for the specified shell type
125 * @param string $type - a key from self::$hooks
126 * @param string $programPath
127 * @param string $programName
128 * @param bool $multiple
132 public function generateHook($type, $programPath, $programName = null, $multiple = false)
134 if (!isset(self::$hooks[$type])) {
135 throw new \RuntimeException(sprintf(
136 "Cannot generate hook for unknown shell type '%s'. Available hooks are: %s",
138 implode(', ', self::getShellTypes())
142 // Use the program path if an alias/name is not given
143 $programName = $programName ?: $programPath;
146 $completionCommand = '$1 _completion';
148 $completionCommand = $programPath . ' _completion';
156 '%%completion_command%%',
159 $this->generateFunctionName($programPath, $programName),
164 $this->stripComments(self::$hooks[$type])
169 * Generate a function name that is unlikely to conflict with other generated function names in the same shell
171 protected function generateFunctionName($programPath, $programName)
175 $this->sanitiseForFunctionName(basename($programName)),
176 substr(md5($programPath), 0, 16)
182 * Make a string safe for use as a shell function name
184 * @param string $name
187 protected function sanitiseForFunctionName($name)
189 $name = str_replace('-', '_', $name);
190 return preg_replace('/[^A-Za-z0-9_]+/', '', $name);
194 * Strip '#' style comments from a string
196 * BASH's eval doesn't work with comments as it removes line breaks, so comments have to be stripped out
197 * for this method of sourcing the hook to work. Eval seems to be the most reliable method of getting a
198 * hook into a shell, so while it would be nice to render comments, this stripping is required for now.
200 * @param string $script
203 protected function stripComments($script)
205 return preg_replace('/(^\s*\#.*$)/m', '', $script);