Skip to content

Instantly share code, notes, and snippets.

@ajvargo
Created April 22, 2019 17:28
Show Gist options
  • Save ajvargo/55bc5ea907375681162fe6906f8d8d5d to your computer and use it in GitHub Desktop.
Save ajvargo/55bc5ea907375681162fe6906f8d8d5d to your computer and use it in GitHub Desktop.
Files for autocomplete talk

Goal

  • Introduce bash completion
  • look at basic options
  • build a custom completion

What’s out there

complete -p
complete -F _minimal
complete -F _fcoemon_options fcoemon
complete -o filenames -F _yu_yumdownloader yumdownloader
complete -o filenames -F _yu_builddep yum-builddep.py
...

299 on my home laptop

Setup

Register a completion strategy for a command.

Strategies all return lists of completions.

Setup Example: Function

complete -F _foo foo

Setup Example: Command

complete -C foo_comp foo

Setup Example: Actions

complete -A setopt set 

Setup Example: Options

complete -o filenames -o nospace -F _pass pass

Options

complete [-abcdefgjksuv] [-o comp-option] [-DEI] [-A action] [-G globpat]
[-W wordlist] [-F function] [-C command] [-X filterpat]
[-P prefix] [-S suffix] name [name …]
complete -pr [-DEI] [name …]

Example: SSH

complete -o default -o nospace -W \
  "$(awk '/^Host / {print $2}' < $HOME/.ssh/config)" scp sftp ssh;

More Complicated

Let’s build up a version for bk

We’ll use a dummy version called baker

baker

def pr cmd, options
  puts "'#{cmd} #{options.join(' ')}' was called"
end

cmd, *options = ARGV
case cmd
when 'up'
  pr cmd, options
when 'down'
  pr cmd, options
when 'runcmd'
 #....
else
  raise "Unknown cmd: #{cmd}"
end

version 1

#!/usr/bin/env ruby

# complete -r baker; complete -C ./c1.rb baker

COMMANDS = %w(up down runcmd ps pull version)

puts COMMANDS

exit 0

version 2

class BakerCompleter
  COMMANDS = %w(up down runcmd ps pull version)

  def self.completions(word)
    if word.empty?
      COMMANDS
    else
      COMMANDS.select do |c|
        c.match? word
      end
    end
  end
end

# baker <?>
full_cmd = ENV["COMP_LINE"].split
current_word = full_cmd.last.to_s.strip

puts BakerCompleter.completions(current_word)
exit 0

version 3

version 3

links

https://github.com/scop/bash-completion https://devmanual.gentoo.org/tasks-reference/completion/index.html https://debian-administration.org/article/317/An_introduction_to_bash_completion_part_2 https://blog.cykerway.com/posts/2016/12/23/how-to-write-bash-completion.html https://www.linuxjournal.com/content/more-using-bash-complete-command https://spin.atomicobject.com/2016/02/14/bash-programmable-completion/

#!/usr/bin/env ruby
def pr cmd, options
puts "'#{cmd} #{options.join(' ')}' was called"
end
cmd, *options = ARGV
case cmd
when 'up'
pr cmd, options
when 'down'
pr cmd, options
when 'runcmd'
pr cmd, options
when 'ps'
pr cmd, options
when 'pull'
pr cmd, options
when 'version'
pr cmd, options
else
raise "Unknown cmd: #{cmd}"
end
#!/usr/bin/env ruby
# complete -r baker; complete -C c1.rb baker
COMMANDS = %w(up down runcmd ps pull version)
puts COMMANDS
exit 0
#!/usr/bin/env ruby
# complete -r baker; complete -C c2.rb baker
class BakerCompleter
COMMANDS = %w(up down runcmd ps pull version)
def self.completions(word)
if word.empty?
COMMANDS
else
COMMANDS.select do |c|
c.match? word
end
end
end
end
# baker <?>
full_cmd = ENV["COMP_LINE"].split
full_cmd.shift # drop bakerq
current_word = full_cmd.last.to_s.strip
puts BakerCompleter.completions(current_word)
exit 0
#!/usr/bin/env ruby
# complete -r baker; complete -C c3.rb baker
class BakerCompleter
COMMANDS = %w(up down runcmd ps pull version)
APPS = %w(lello pince valve nile)
OPTS = {
'up' => ['--no-migrations'],
'down' => ['--no-rm'],
'runcmd' => ["--command", "--no-build"]
}
def initialize previous, current
@previous, @current = previous, current
end
def completions
return COMMANDS if @current.nil? && @previous.nil?
return nil if @current == 'version'
if @previous.empty? && COMMANDS.include?(@current)
return APPS
end
if COMMANDS.include?(@previous)
if @current.match("-")
matches(OPTS[@previous], @current)
else
matches(APPS, @current)
end
else
matches(COMMANDS, @current)
end
end
private
def matches(haystack, needle)
haystack.select{|c| c.match? needle }
end
end
args = ENV["COMP_LINE"].split.compact
args.shift # drop baker
current = args.pop.to_s.strip
previous = args.pop.to_s.strip
puts BakerCompleter.new(previous, current).completions
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment