class Clamp::Completion::FishGenerator
Generates fish shell completion scripts.
Public Class Methods
new(command_class, executable_name)
click to toggle source
# File lib/clamp/completion/fish_generator.rb, line 9 def initialize(command_class, executable_name) @command_class = command_class @executable_name = executable_name end
Public Instance Methods
generate()
click to toggle source
# File lib/clamp/completion/fish_generator.rb, line 14 def generate lines = [] lines << "# Fish completions for #{@executable_name}" lines << "# Generated by Clamp" lines << "" helpers = [subcmd_args_function] # Track visible option switches at each depth, so we can identify # which options are new at each level. Depth-first walk order means # the entry at depth N-1 is always the current node's parent. switches_at_depth = [] Completion.walk_command_tree(@command_class) do |cmd, path, has_children| generate_option_completions(lines, cmd, path, switches_at_depth) next unless has_children # Subcommand names need an exclusive condition (only at this level). child_names = cmd.recognised_subcommands.flat_map(&:names) exclusive_cond = condition_for(path, child_names) subcmd_cond = subcommand_condition(cmd, path, exclusive_cond, helpers) generate_subcommand_completions(lines, cmd, subcmd_cond) lines << "" end "#{helpers.join("\n\n")}\n\n#{lines.join("\n")}\n" end
Private Instance Methods
argparse_optspecs(command_class)
click to toggle source
# File lib/clamp/completion/fish_generator.rb, line 123 def argparse_optspecs(command_class) command_class.recognised_options.flat_map do |option| Completion.argparse_specs_for(option) end end
completion_function()
click to toggle source
# File lib/clamp/completion/fish_generator.rb, line 95 def completion_function "_clamp_complete_#{@executable_name}" end
condition_for(path, child_names)
click to toggle source
Condition that matches this level only (excludes child subcommands).
# File lib/clamp/completion/fish_generator.rb, line 85 def condition_for(path, child_names) if path.empty? "not #{completion_function}_subcmd_args >/dev/null" else parts = path.map { |sub| "#{completion_function}_seen_subcommand_from #{sub.names.join(' ')}" } parts << "not #{completion_function}_seen_subcommand_from #{child_names.join(' ')}" if child_names.any? parts.join("; and ") end end
escape(str)
click to toggle source
# File lib/clamp/completion/fish_generator.rb, line 147 def escape(str) str.gsub("'", "\\\\'") end
generate_option_completions(lines, cmd, path, switches_at_depth)
click to toggle source
Emit only options new at this level (not inherited from parent). Use an inclusive condition so they also apply to child subcommands.
# File lib/clamp/completion/fish_generator.rb, line 63 def generate_option_completions(lines, cmd, path, switches_at_depth) depth = path.length visible = Completion.visible_options(cmd) parent_switches = depth.positive? ? switches_at_depth[depth - 1] : nil new_options = if parent_switches visible.reject { |o| parent_switches.include?(o.switches) } else visible end option_cond = depth.positive? ? inclusive_condition_for(path) : nil new_options.each do |option| lines << option_completion(option, option_cond) end switches_at_depth[depth] = Set.new(visible.map(&:switches)) end
generate_subcommand_completions(lines, cmd, condition)
click to toggle source
# File lib/clamp/completion/fish_generator.rb, line 52 def generate_subcommand_completions(lines, cmd, condition) cmd.recognised_subcommands.each do |sub| sub.names.each do |name| lines << "complete -c #{@executable_name} -f -n '#{condition}' -a #{name} " \ "-d '#{escape(sub.description)}'" end end end
inclusive_condition_for(path)
click to toggle source
Condition that matches this level AND all child subcommands.
# File lib/clamp/completion/fish_generator.rb, line 80 def inclusive_condition_for(path) path.map { |sub| "#{completion_function}_seen_subcommand_from #{sub.names.join(' ')}" }.join("; and ") end
option_completion(option, condition = nil)
click to toggle source
# File lib/clamp/completion/fish_generator.rb, line 129 def option_completion(option, condition = nil) parts = ["complete -c #{@executable_name} -f"] parts << "-n '#{condition}'" if condition Completion.expanded_switches(option).each do |switch| parts << if switch.start_with?("--") "-l #{switch.sub(/^--/, '')}" else "-s #{switch.sub(/^-/, '')}" end end parts << "-r" unless option.flag? parts << "-d '#{escape(option.description)}'" parts.join(" ") end
subcmd_args_function()
click to toggle source
# File lib/clamp/completion/fish_generator.rb, line 99 def subcmd_args_function optspecs = argparse_optspecs(@command_class) lines = [ "function #{completion_function}_subcmd_args", " set -l tokens (commandline -opc)", " set -e tokens[1]", " argparse -si #{optspecs.map { |s| "'#{s}'" }.join(' ')} -- $tokens 2>/dev/null", " or return 1", " test (count $argv) -gt 0", " and printf '%s\\n' $argv", "end", "", "function #{completion_function}_seen_subcommand_from", " for p in (#{completion_function}_subcmd_args)", " if contains -- $p $argv", " return 0", " end", " end", " return 1", "end" ] lines.join("\n") end
subcommand_condition(cmd, path, condition, helpers)
click to toggle source
# File lib/clamp/completion/fish_generator.rb, line 43 def subcommand_condition(cmd, path, condition, helpers) param_count = Completion.required_parameter_count(cmd) return condition unless param_count.positive? helper = ParamsSatisfiedHelper.new(@executable_name, path, param_count) helpers << helper.to_s "#{condition}; and #{helper.function_name}" end