1 module commandr.completion.bash; 2 3 import commandr.program; 4 import commandr.option; 5 import std.algorithm : map, filter; 6 import std.array : Appender, join; 7 import std..string : format; 8 import std.range : empty, chain; 9 10 11 /** 12 * Creates bash completion script. 13 * 14 * Creates completion script for specified program, returning the script contents. 15 * You need to create it once, and save the script in directory like 16 * `/etc/bash_completion.d/` during installation. 17 * 18 * Params: 19 * program - Program to create completion script. 20 * 21 * Returns: 22 * Generated completion script contents. 23 * 24 * Examples: 25 * --- 26 * import std.file : write; 27 * import std.string : format; 28 * import commandr; 29 * 30 * auto prog = new Program("test"); 31 * std.file.write("%s.bash".format(prog.binaryName), createBashCompletionScript(prog)); 32 * --- 33 */ 34 string createBashCompletionScript(Program program) { 35 Appender!string builder; 36 37 builder ~= "#!/usr/bin/env bash\n"; 38 builder ~= "# This file is autogenerated. DO NOT EDIT.\n"; 39 40 builder ~= ` 41 __get_args() { 42 local max opts args name arg i count 43 max=$1; shift 44 opts=$1; shift 45 args=($@) 46 let i=0 47 let count=0 48 49 while [ $i -le ${#args[@]} ]; do 50 arg=${args[i]} 51 name="${arg%=*}" 52 53 if [[ $name = -* ]]; then 54 if [[ " $opts " = *"$name"* ]]; then 55 if ! [[ $name = *"="* ]]; then 56 let i+=1 57 fi 58 fi 59 else 60 let count+=1 61 echo $arg 62 fi 63 let i+=1 64 65 [ $count -ge $max ] && break 66 done 67 68 return $i 69 } 70 71 __function_exists() { 72 declare -f -F $1 > /dev/null 73 return $? 74 } 75 76 `; 77 // works by creating completion functions for commands recursively 78 completionFunc(program, builder); 79 80 builder ~= "complete -F _%s_completion_main %s\n".format(program.binaryName, program.binaryName); 81 82 return builder.data; 83 } 84 85 private void completionFunc(Command command, Appender!string builder) { 86 foreach(command; command.commands) { 87 completionFunc(command, builder); 88 } 89 90 auto argumentCount = command.arguments.length; 91 auto commands = command.commands.keys.join(" "); 92 93 auto shorts = command.abbrevations.map!(s => "-" ~ s); 94 auto longs = command.fullNames.map!(l => "--" ~ l); 95 96 auto options = command.options 97 .map!(o => [o.abbrev ? "-" ~ o.abbrev : null, o.full ? "--" ~ o.full : null]) 98 .join().filter!`a && a.length`.join(" "); 99 100 builder ~= "_%s_completion() {\n".format(command.chain.join("_")); 101 builder ~= " local args target\n\n"; 102 103 builder ~= " __args=( $(__get_args %s \"%s\" \"${COMP_WORDS[@]:__args_start}\") )\n" 104 .format(argumentCount + command.commands.length ? 1 : 0, options); 105 builder ~= " args=$?\n\n"; 106 107 builder ~= " if [ $COMP_CWORD -lt $(( $__args_start + $args )) ]; then\n"; 108 builder ~= " if [[ \"$curr\" = -* ]]; then\n"; 109 builder ~= " COMPREPLY=( $(compgen -W \"%s\" -- \"$curr\") )\n".format(chain(shorts, longs).join(" ")); 110 111 if (command.commands.length > 0) { 112 builder ~= " elif [ ${#__args[@]} -ge %s ]; then\n".format(command.arguments.length); 113 builder ~= " COMPREPLY=( $(compgen -W \"%s\" -- \"$curr\") )\n".format(commands); 114 builder ~= " fi\n"; 115 116 builder ~= " elif [ ${#__args[@]} -gt 0 ] && [[ \" %s \" = *\" ${__args[@]: -1:1} \"* ]]; then\n".format(commands); 117 builder ~= " target=\"_%s_${__args[@]: -1:1}_completion\"\n".format(command.chain.join("_")); 118 builder ~= " let __args_start+=$args\n"; 119 builder ~= " __function_exists $target && $target\n"; 120 } 121 else { 122 builder ~= " fi\n"; 123 } 124 builder ~= " fi\n"; 125 builder ~= "}\n\n"; 126 } 127 128 private void completionFunc(Program program, Appender!string builder) { 129 foreach(command; program.commands) { 130 completionFunc(command, builder); 131 } 132 133 completionFunc(cast(Command)program, builder); 134 135 builder ~= "_%s_completion_main() {\n".format(program.binaryName); 136 builder ~= " COMPREPLY=()\n"; 137 builder ~= " __args_start=1\n"; 138 builder ~= " curr=${COMP_WORDS[COMP_CWORD]}\n"; 139 builder ~= " prev=${COMP_WORDS[COMP_CWORD-1]}\n\n"; 140 builder ~= " _%s_completion\n".format(program.binaryName); 141 142 builder ~= " unset __args __args_start curr prev\n"; 143 builder ~= "}\n\n"; 144 }