32 cmd.add_option(
"<shell>", shell_,
"Shell type: bash, zsh, or fish")->required();
37 if (shell_ ==
"bash") {
38 output_bash_completion();
40 else if (shell_ ==
"zsh") {
41 output_zsh_completion();
43 else if (shell_ ==
"fish") {
44 output_fish_completion();
47 std::cerr <<
"Unknown shell type: " << shell_ <<
"\n";
48 std::cerr <<
"Supported shells: bash, zsh, fish\n";
49 throw CLI::RuntimeError{1};
54 static constexpr auto kCommandName =
"completion";
55 static constexpr auto kCommandDesc =
"Generate shell completion scripts.";
56 static constexpr auto kCommandGroup =
"UTILITIES";
59 void output_bash_completion()
61 std::cout <<
"# Bash completion for porytiles\n";
62 std::cout <<
"# Add to ~/.bashrc or ~/.bash_completion:\n";
63 std::cout <<
"# eval \"$(porytiles completion bash)\"\n";
67 std::cout <<
"# Helper function to extract --project-root or -C from command line\n";
68 std::cout <<
"_porytiles_get_project_root() {\n";
69 std::cout <<
" local i\n";
70 std::cout <<
" for ((i=1; i < ${#COMP_WORDS[@]}; i++)); do\n";
71 std::cout <<
" case \"${COMP_WORDS[i]}\" in\n";
72 std::cout <<
" -C|--project-root)\n";
73 std::cout <<
" if [[ $((i+1)) -lt ${#COMP_WORDS[@]} ]]; then\n";
74 std::cout <<
" echo \"${COMP_WORDS[$((i+1))]}\"\n";
75 std::cout <<
" return\n";
78 std::cout <<
" esac\n";
79 std::cout <<
" done\n";
80 std::cout <<
" echo \".\"\n";
85 std::cout <<
"# Helper function to get tileset completions\n";
86 std::cout <<
"_porytiles_complete_tilesets() {\n";
87 std::cout <<
" local filter=\"$1\"\n";
88 std::cout <<
" local project_root\n";
89 std::cout <<
" project_root=$(_porytiles_get_project_root)\n";
90 std::cout <<
" porytiles list-tilesets -C \"$project_root\" --filter \"$filter\" --prefix \"$cur\" "
95 std::cout <<
"_porytiles_completions() {\n";
96 std::cout <<
" local cur prev opts subcommand\n";
97 std::cout <<
" COMPREPLY=()\n";
98 std::cout <<
" cur=\"${COMP_WORDS[COMP_CWORD]}\"\n";
99 std::cout <<
" prev=\"${COMP_WORDS[COMP_CWORD-1]}\"\n";
103 std::cout <<
" # Find the subcommand\n";
104 std::cout <<
" subcommand=\"\"\n";
105 std::cout <<
" for ((i=1; i < COMP_CWORD; i++)); do\n";
106 std::cout <<
" case \"${COMP_WORDS[i]}\" in\n";
107 std::cout <<
" compile-tileset|create-tileset|decompile-tileset|dump-tileset-config|"
108 "import-tileset|list-tilesets|completion)\n";
109 std::cout <<
" subcommand=\"${COMP_WORDS[i]}\"\n";
110 std::cout <<
" break\n";
111 std::cout <<
" ;;\n";
112 std::cout <<
" esac\n";
113 std::cout <<
" done\n";
116 std::cout <<
" # Main commands\n";
117 std::cout <<
" local commands=\"compile-tileset create-tileset decompile-tileset dump-tileset-config "
118 "import-tileset list-tilesets completion\"\n";
120 std::cout <<
" # Config options\n";
121 std::cout <<
" local config_opts=\"";
124 for (
const auto &opt : meta) {
125 std::cout <<
"--" << opt.long_name <<
" ";
126 if (opt.has_negation) {
127 std::cout <<
"--no-" << opt.long_name <<
" ";
135 std::cout <<
" # If no subcommand yet, complete commands\n";
136 std::cout <<
" if [[ -z \"$subcommand\" ]]; then\n";
137 std::cout <<
" if [[ ${cur} == -* ]]; then\n";
138 std::cout <<
" COMPREPLY=( $(compgen -W \"-C --project-root\" -- ${cur}) )\n";
139 std::cout <<
" else\n";
140 std::cout <<
" COMPREPLY=( $(compgen -W \"${commands}\" -- ${cur}) )\n";
141 std::cout <<
" fi\n";
142 std::cout <<
" return 0\n";
143 std::cout <<
" fi\n";
146 std::cout <<
" case \"${prev}\" in\n";
149 for (
const auto &opt : meta) {
150 if (!opt.choices.empty()) {
151 std::cout <<
" --" << opt.long_name <<
")\n";
152 std::cout <<
" COMPREPLY=( $(compgen -W \"";
153 for (std::size_t i = 0; i < opt.choices.size(); ++i) {
157 std::cout << opt.choices[i];
159 std::cout <<
"\" -- ${cur}) )\n";
160 std::cout <<
" return 0\n";
161 std::cout <<
" ;;\n";
165 std::cout <<
" -C|--project-root)\n";
166 std::cout <<
" # Complete directories\n";
167 std::cout <<
" COMPREPLY=( $(compgen -d -- ${cur}) )\n";
168 std::cout <<
" return 0\n";
169 std::cout <<
" ;;\n";
170 std::cout <<
" *)\n";
171 std::cout <<
" ;;\n";
172 std::cout <<
" esac\n";
176 std::cout <<
" if [[ ${cur} == -* ]]; then\n";
177 std::cout <<
" local extra_opts=\"\"\n";
178 std::cout <<
" if [[ \"$subcommand\" == \"create-tileset\" ]]; then\n";
179 std::cout <<
" extra_opts=\"--secondary\"\n";
180 std::cout <<
" fi\n";
181 std::cout <<
" COMPREPLY=( $(compgen -W \"${config_opts} ${extra_opts}\" -- ${cur}) )\n";
182 std::cout <<
" return 0\n";
183 std::cout <<
" fi\n";
187 std::cout <<
" # Complete tileset names based on subcommand\n";
188 std::cout <<
" case \"$subcommand\" in\n";
189 std::cout <<
" compile-tileset|decompile-tileset)\n";
190 std::cout <<
" # Only managed tilesets\n";
191 std::cout <<
" COMPREPLY=( $(_porytiles_complete_tilesets managed) )\n";
192 std::cout <<
" ;;\n";
193 std::cout <<
" dump-tileset-config)\n";
194 std::cout <<
" # All tilesets (config can be dumped for any)\n";
195 std::cout <<
" COMPREPLY=( $(_porytiles_complete_tilesets all) )\n";
196 std::cout <<
" ;;\n";
197 std::cout <<
" import-tileset)\n";
198 std::cout <<
" # Only unmanaged tilesets\n";
199 std::cout <<
" COMPREPLY=( $(_porytiles_complete_tilesets unmanaged) )\n";
200 std::cout <<
" ;;\n";
201 std::cout <<
" create-tileset)\n";
202 std::cout <<
" # No completion - user provides new name\n";
203 std::cout <<
" ;;\n";
204 std::cout <<
" list-tilesets)\n";
205 std::cout <<
" # Complete --filter values\n";
206 std::cout <<
" if [[ \"${prev}\" == \"--filter\" ]]; then\n";
207 std::cout <<
" COMPREPLY=( $(compgen -W \"all managed unmanaged\" -- ${cur}) )\n";
208 std::cout <<
" fi\n";
209 std::cout <<
" ;;\n";
210 std::cout <<
" completion)\n";
211 std::cout <<
" COMPREPLY=( $(compgen -W \"bash zsh fish\" -- ${cur}) )\n";
212 std::cout <<
" ;;\n";
213 std::cout <<
" esac\n";
215 std::cout <<
" return 0\n";
218 std::cout <<
"complete -F _porytiles_completions porytiles\n";
221 void output_zsh_completion()
223 std::cout <<
"#compdef porytiles\n";
224 std::cout <<
"# Zsh completion for porytiles\n";
225 std::cout <<
"# Add to ~/.zshrc or place in a directory in your $fpath:\n";
226 std::cout <<
"# eval \"$(porytiles completion zsh)\"\n";
230 std::cout <<
"# Helper function to extract --project-root or -C from command line\n";
231 std::cout <<
"_porytiles_get_project_root() {\n";
232 std::cout <<
" local i\n";
233 std::cout <<
" for ((i=1; i <= ${#words[@]}; i++)); do\n";
234 std::cout <<
" case \"${words[i]}\" in\n";
235 std::cout <<
" -C|--project-root)\n";
236 std::cout <<
" if [[ $((i+1)) -le ${#words[@]} ]]; then\n";
237 std::cout <<
" echo \"${words[$((i+1))]}\"\n";
238 std::cout <<
" return\n";
239 std::cout <<
" fi\n";
240 std::cout <<
" ;;\n";
241 std::cout <<
" esac\n";
242 std::cout <<
" done\n";
243 std::cout <<
" echo \".\"\n";
248 std::cout <<
"# Helper function to complete tileset names\n";
249 std::cout <<
"_porytiles_complete_tilesets() {\n";
250 std::cout <<
" local filter=\"$1\"\n";
251 std::cout <<
" local project_root\n";
252 std::cout <<
" project_root=$(_porytiles_get_project_root)\n";
253 std::cout <<
" local -a tilesets\n";
254 std::cout <<
" tilesets=(${(f)\"$(porytiles list-tilesets -C \"$project_root\" --filter \"$filter\" "
255 "2>/dev/null)\"})\n";
256 std::cout <<
" _describe 'tileset' tilesets\n";
260 std::cout <<
"_porytiles() {\n";
261 std::cout <<
" local state line\n";
262 std::cout <<
" local -a commands\n";
263 std::cout <<
" commands=(\n";
264 std::cout <<
" 'compile-tileset:Compile a tileset'\n";
265 std::cout <<
" 'create-tileset:Create a new tileset'\n";
266 std::cout <<
" 'decompile-tileset:Decompile a tileset'\n";
267 std::cout <<
" 'dump-tileset-config:Dump the full configuration provenance chain for a tileset'\n";
268 std::cout <<
" 'import-tileset:Import a pre-existing tileset'\n";
269 std::cout <<
" 'list-tilesets:List tileset names in the project'\n";
270 std::cout <<
" 'completion:Generate shell completion scripts'\n";
273 std::cout <<
" local -a config_opts\n";
274 std::cout <<
" config_opts=(\n";
275 std::cout <<
" '-C[Set project root directory]:directory:_files -/'\n";
276 std::cout <<
" '--project-root[Set project root directory]:directory:_files -/'\n";
279 for (
const auto &opt : meta) {
280 std::cout <<
" '--" << opt.long_name <<
"[" << opt.description <<
"]";
281 if (!opt.choices.empty()) {
282 std::cout <<
":choice:(";
283 for (std::size_t i = 0; i < opt.choices.size(); ++i) {
287 std::cout << opt.choices[i];
292 if (opt.has_negation) {
293 std::cout <<
" '--no-" << opt.long_name <<
"[Disable " << opt.description <<
"]'\n";
299 std::cout <<
" _arguments -C \\\n";
300 std::cout <<
" '-C[Set project root directory]:directory:_files -/' \\\n";
301 std::cout <<
" '--project-root[Set project root directory]:directory:_files -/' \\\n";
302 std::cout <<
" '1:command:->command' \\\n";
303 std::cout <<
" '*::arg:->args'\n";
305 std::cout <<
" case \"$state\" in\n";
306 std::cout <<
" command)\n";
307 std::cout <<
" _describe 'command' commands\n";
308 std::cout <<
" ;;\n";
309 std::cout <<
" args)\n";
310 std::cout <<
" case \"$line[1]\" in\n";
311 std::cout <<
" compile-tileset|decompile-tileset)\n";
312 std::cout <<
" _arguments \\\n";
313 std::cout <<
" $config_opts \\\n";
314 std::cout <<
" '1:tileset:->tileset_managed'\n";
316 <<
" [[ \"$state\" == tileset_managed ]] && _porytiles_complete_tilesets managed\n";
317 std::cout <<
" ;;\n";
318 std::cout <<
" dump-tileset-config)\n";
319 std::cout <<
" _arguments \\\n";
320 std::cout <<
" $config_opts \\\n";
321 std::cout <<
" '1:tileset:->tileset_all'\n";
322 std::cout <<
" [[ \"$state\" == tileset_all ]] && _porytiles_complete_tilesets all\n";
323 std::cout <<
" ;;\n";
324 std::cout <<
" import-tileset)\n";
325 std::cout <<
" _arguments \\\n";
326 std::cout <<
" $config_opts \\\n";
327 std::cout <<
" '1:tileset:->tileset_unmanaged'\n";
329 <<
" [[ \"$state\" == tileset_unmanaged ]] && _porytiles_complete_tilesets unmanaged\n";
330 std::cout <<
" ;;\n";
331 std::cout <<
" create-tileset)\n";
332 std::cout <<
" _arguments \\\n";
333 std::cout <<
" $config_opts \\\n";
334 std::cout <<
" '--secondary[Create a secondary tileset instead of a primary]' \\\n";
335 std::cout <<
" '1:tileset name:'\n";
336 std::cout <<
" ;;\n";
337 std::cout <<
" list-tilesets)\n";
338 std::cout <<
" _arguments \\\n";
339 std::cout <<
" '-C[Set project root directory]:directory:_files -/' \\\n";
340 std::cout <<
" '--project-root[Set project root directory]:directory:_files -/' \\\n";
341 std::cout <<
" '--filter[Filter mode]:filter:(all managed unmanaged)' \\\n";
342 std::cout <<
" '--prefix[Only show tilesets starting with this prefix]:prefix:'\n";
343 std::cout <<
" ;;\n";
344 std::cout <<
" completion)\n";
345 std::cout <<
" _arguments '1:shell:(bash zsh fish)'\n";
346 std::cout <<
" ;;\n";
347 std::cout <<
" *)\n";
348 std::cout <<
" _values 'config options' $config_opts\n";
349 std::cout <<
" ;;\n";
350 std::cout <<
" esac\n";
351 std::cout <<
" ;;\n";
352 std::cout <<
" esac\n";
355 std::cout <<
"compdef _porytiles porytiles\n";
358 void output_fish_completion()
360 std::cout <<
"# Fish completion for porytiles\n";
361 std::cout <<
"# Save to completions directory (eval does not work reliably in fish):\n";
362 std::cout <<
"# porytiles completion fish > ~/.config/fish/completions/porytiles.fish\n";
363 std::cout <<
"# Then restart fish or run: source ~/.config/fish/completions/porytiles.fish\n";
365 std::cout <<
"# Erase any existing completions for porytiles\n";
366 std::cout <<
"complete -e -c porytiles\n";
368 std::cout <<
"# Disable file completions globally for porytiles\n";
369 std::cout <<
"complete -c porytiles -f\n";
373 std::cout <<
"# Helper function to extract --project-root or -C from command line\n";
374 std::cout <<
"function __porytiles_get_project_root\n";
375 std::cout <<
" set -l tokens (commandline -opc)\n";
376 std::cout <<
" for i in (seq 1 (count $tokens))\n";
377 std::cout <<
" switch $tokens[$i]\n";
378 std::cout <<
" case -C --project-root\n";
379 std::cout <<
" set -l next (math $i + 1)\n";
380 std::cout <<
" if test $next -le (count $tokens)\n";
381 std::cout <<
" echo $tokens[$next]\n";
382 std::cout <<
" return\n";
383 std::cout <<
" end\n";
384 std::cout <<
" end\n";
385 std::cout <<
" end\n";
386 std::cout <<
" echo \".\"\n";
387 std::cout <<
"end\n";
391 std::cout <<
"# Helper function to complete tileset names\n";
392 std::cout <<
"function __porytiles_complete_tilesets\n";
393 std::cout <<
" set -l filter $argv[1]\n";
394 std::cout <<
" set -l project_root (__porytiles_get_project_root)\n";
395 std::cout <<
" porytiles list-tilesets -C \"$project_root\" --filter \"$filter\" 2>/dev/null\n";
396 std::cout <<
"end\n";
400 std::cout <<
"# Helper functions for detecting current subcommand\n";
401 std::cout <<
"function __porytiles_needs_subcommand\n";
402 std::cout <<
" set -l cmd (commandline -opc)\n";
403 std::cout <<
" for word in $cmd[2..-1]\n";
404 std::cout <<
" switch $word\n";
405 std::cout <<
" case compile-tileset create-tileset decompile-tileset dump-tileset-config "
406 "import-tileset list-tilesets completion\n";
407 std::cout <<
" return 1\n";
408 std::cout <<
" end\n";
409 std::cout <<
" end\n";
410 std::cout <<
" return 0\n";
411 std::cout <<
"end\n";
414 std::cout <<
"function __porytiles_using_subcommand\n";
415 std::cout <<
" set -l cmd (commandline -opc)\n";
416 std::cout <<
" for word in $cmd[2..-1]\n";
417 std::cout <<
" if test \"$word\" = \"$argv[1]\"\n";
418 std::cout <<
" return 0\n";
419 std::cout <<
" end\n";
420 std::cout <<
" end\n";
421 std::cout <<
" return 1\n";
422 std::cout <<
"end\n";
426 std::cout <<
"# Commands\n";
427 std::cout <<
"complete -c porytiles -f -n __porytiles_needs_subcommand -a compile-tileset -d 'Compile a "
429 std::cout <<
"complete -c porytiles -f -n __porytiles_needs_subcommand -a create-tileset -d 'Create a new "
431 std::cout <<
"complete -c porytiles -f -n __porytiles_needs_subcommand -a decompile-tileset -d 'Decompile a "
433 std::cout <<
"complete -c porytiles -f -n __porytiles_needs_subcommand -a dump-tileset-config -d 'Dump the "
434 "full configuration provenance chain for a tileset'\n";
435 std::cout <<
"complete -c porytiles -f -n __porytiles_needs_subcommand -a import-tileset -d 'Import a "
436 "pre-existing tileset'\n";
437 std::cout <<
"complete -c porytiles -f -n __porytiles_needs_subcommand -a list-tilesets -d 'List tileset "
438 "names in the project'\n";
439 std::cout <<
"complete -c porytiles -f -n __porytiles_needs_subcommand -a completion -d 'Generate shell "
440 "completion scripts'\n";
444 std::cout <<
"# Global options\n";
445 std::cout <<
"complete -c porytiles -f -s C -l project-root -d 'Set project root directory' -xa '(__fish_"
446 "complete_directories)'\n";
450 std::cout <<
"# Tileset name completions\n";
451 std::cout <<
"complete -c porytiles -f -n '__porytiles_using_subcommand compile-tileset' -a "
452 "'(__porytiles_complete_tilesets managed)'\n";
453 std::cout <<
"complete -c porytiles -f -n '__porytiles_using_subcommand decompile-tileset' -a "
454 "'(__porytiles_complete_tilesets managed)'\n";
455 std::cout <<
"complete -c porytiles -f -n '__porytiles_using_subcommand dump-tileset-config' -a "
456 "'(__porytiles_complete_tilesets all)'\n";
457 std::cout <<
"complete -c porytiles -f -n '__porytiles_using_subcommand import-tileset' -a "
458 "'(__porytiles_complete_tilesets unmanaged)'\n";
462 std::cout <<
"# Options for create-tileset subcommand\n";
463 std::cout <<
"complete -c porytiles -f -n '__porytiles_using_subcommand create-tileset' -l secondary -d "
464 "'Create a secondary tileset instead of a primary'\n";
468 std::cout <<
"# Options for list-tilesets subcommand\n";
469 std::cout <<
"complete -c porytiles -f -n '__porytiles_using_subcommand list-tilesets' -l filter -d "
470 "'Filter mode' -xa 'all managed unmanaged'\n";
471 std::cout <<
"complete -c porytiles -f -n '__porytiles_using_subcommand list-tilesets' -l prefix -d "
472 "'Only show tilesets starting with this prefix'\n";
476 std::cout <<
"# Shell type completion for 'completion' subcommand\n";
477 std::cout <<
"complete -c porytiles -f -n '__porytiles_using_subcommand completion' -a 'bash zsh fish'\n";
481 std::cout <<
"# Config options\n";
484 for (
const auto &opt : meta) {
485 std::cout <<
"complete -c porytiles -f -l " << opt.long_name <<
" -d '" << opt.description <<
"'";
486 if (!opt.choices.empty()) {
487 std::cout <<
" -xa '";
488 for (std::size_t i = 0; i < opt.choices.size(); ++i) {
492 std::cout << opt.choices[i];
497 if (opt.has_negation) {
498 std::cout <<
"complete -c porytiles -f -l no-" << opt.long_name <<
" -d 'Disable " << opt.description
Command is an abstract class that provides basic command functionality for the Porytiles CLI driver.
CLI::App & get_app() const
Command that outputs shell completion scripts.
CompletionCommand(CLI::App &parent_app)
std::vector< CliOptionMeta > get_cli_option_metadata()
Get all CLI option metadata for completion generation.