-
-
Save danielsdeleo/1488254 to your computer and use it in GitHub Desktop.
A wrapper for Chef's `knife` that saves you typing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
#= k | |
#== Requirements | |
# I install Chef with rubygems. May/may not work with other install methods. | |
# | |
#== INSTALL | |
# copy this to ~/bin/k | |
# | |
#== USE | |
# Type `k` instead of `knife` | |
# | |
# You can abbreviate commands using the first letter of each command, or a | |
# fuzzy matching substring from each word. The only restriction is that you | |
# have to join several "sub-words" with a hyphen. For example: | |
# * knife cookbook upload => k cb-up | |
# * knife node list => k nl | |
# * knife role from file => k rff | |
# * knife status => k st | |
# | |
#== Disclaimer | |
# This code is a collection of complete hacks. Any updates to core knife are probably going to break it. | |
require 'rubygems' | |
require 'chef/application/knife' | |
module K | |
class CommandSelector | |
attr_reader :cmd_in | |
attr_reader :possibilities | |
def initialize(cmd_in, possibilities) | |
@cmd_in, @possibilities = cmd_in, possibilities | |
end | |
def pick | |
first_letters = cmd_in.map {|word| word[0]} | |
possible_matches = [] | |
possibilities.each do |cmd| | |
# using the first letters grouped together? | |
# "rff" => "role from file" | |
# or, using a few letters from each command? | |
# "cb up" => "cookbook upload" | |
next unless cmd_in.size == 1 or cmd_in.size == cmd.size | |
first_letter_match = true | |
first_letters.each_with_index do |letter, word_index| | |
if word = cmd[word_index] | |
first_letter_match &&= (word[0] == letter) | |
end | |
end | |
next unless first_letter_match | |
# try to match each letter in the cmd to the first letter of a command word | |
if cmd_in.size == 1 | |
reject = false | |
0.upto(cmd_in[0].size) do |i| | |
if word = cmd[i] | |
reject ||= (cmd_in[0][i] != word[0]) | |
end | |
end | |
next if reject | |
end | |
reject = false | |
# make sure that every letter in cmd_in exists in the candidate command | |
if cmd_in.size == 1 | |
#could have "st" => "status" or "rff" => "role from file" | |
cmd_in[0].each_char do |c| | |
reject ||= (!cmd.any? {|word| word.index(c)}) | |
end | |
else | |
cmd_in.each_with_index do |abbrev, i| | |
abbrev.each_char do |c| | |
reject ||= (!cmd[i].index(c)) | |
end | |
end | |
end | |
next if reject | |
possible_matches << cmd | |
end | |
possible_matches | |
end | |
end | |
class App < Chef::Application::Knife | |
def self.options=(new_opts) | |
superclass.options = new_opts | |
end | |
def self.options | |
superclass.options | |
end | |
def run | |
Mixlib::Log::Formatter.show_time = false | |
validate_and_parse_options | |
quiet_traps | |
Controller.run(ARGV, options) | |
exit 0 | |
end | |
end | |
class Controller < Chef::Knife | |
# This will hide us from help output. | |
category 'deprecated' | |
def self.subcommand_class_from(args) | |
# BEGIN COPYPASTA :( | |
command_words = args.select {|arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ } | |
subcommand_class = nil | |
while ( !subcommand_class ) && ( !command_words.empty? ) | |
snake_case_class_name = command_words.join("_") | |
unless subcommand_class = subcommands[snake_case_class_name] | |
command_words.pop | |
end | |
end | |
# see if we got the command as e.g., knife node-list | |
subcommand_class ||= subcommands[args.first.gsub('-', '_')] | |
# END COPYPASTA | |
subcommand_class ||= begin | |
picker = CommandSelector.new(args.first.split('-'), subcommands.keys.map {|cmd| cmd.split('_')} ) | |
matching_cmds = picker.pick | |
#puts "found match(es) #{matching_cmds.map {|c| c.join(' ')}.inspect} for args #{args}" | |
if matching_cmds.size > 1 | |
cmd_normal_names = matching_cmds.map {|cmd| cmd.join(' ')} | |
ui.fatal "Several commands matched '#{args}':\n#{matching_cmds.join("\n")}" | |
exit 11 | |
elsif matching_cmds.empty? | |
nil | |
else | |
# Trick knife into thinking the original arguments were what it normally wants. | |
#puts "old arg list: #{args.inspect}" | |
args.delete_at(0) | |
args.replace(matching_cmds.first + args) | |
#puts "new arg list: #{args.inspect}" | |
subcommands[matching_cmds.first.join('_')] | |
end | |
end | |
# THIS LINE ALSO COPYPASTA | |
subcommand_class || subcommand_not_found!(args) | |
end | |
end | |
end | |
K::App.new.run | |
__END__ | |
COMMANDS_AS_ARRAYS = [["bootstrap"], | |
["client", "bulk", "delete"], | |
["client", "create"], | |
["client", "delete"], | |
["client", "edit"], | |
["client", "list"], | |
["client", "reregister"], | |
["client", "show"], | |
["configure"], | |
["configure", "client"], | |
["cookbook", "bulk", "delete"], | |
["cookbook", "create"], | |
["cookbook", "delete"], | |
["cookbook", "download"], | |
["cookbook", "list"], | |
["cookbook", "metadata"], | |
["cookbook", "metadata", "from", "file"], | |
["cookbook", "show"], | |
["cookbook", "site", "download"], | |
["cookbook", "site", "install"], | |
["cookbook", "site", "list"], | |
["cookbook", "site", "search"], | |
["cookbook", "site", "share"], | |
["cookbook", "site", "show"], | |
["cookbook", "site", "unshare"], | |
["cookbook", "site", "vendor"], | |
["cookbook", "test"], | |
["cookbook", "upload"], | |
["data", "bag", "create"], | |
["data", "bag", "delete"], | |
["data", "bag", "edit"], | |
["data", "bag", "from", "file"], | |
["data", "bag", "list"], | |
["data", "bag", "show"], | |
["environment", "create"], | |
["environment", "delete"], | |
["environment", "edit"], | |
["environment", "from", "file"], | |
["environment", "list"], | |
["environment", "show"], | |
["exec"], | |
["help"], | |
["index", "rebuild"], | |
["node", "bulk", "delete"], | |
["node", "create"], | |
["node", "delete"], | |
["node", "edit"], | |
["node", "from", "file"], | |
["node", "list"], | |
["node", "run", "list", "add"], | |
["node", "run", "list", "remove"], | |
["node", "show"], | |
["recipe", "list"], | |
["role", "bulk", "delete"], | |
["role", "create"], | |
["role", "delete"], | |
["role", "edit"], | |
["role", "from", "file"], | |
["role", "list"], | |
["role", "show"], | |
["search"], | |
["ssh"], | |
["status"], | |
["tag", "create"], | |
["tag", "delete"], | |
["tag", "list"], | |
["ec2", "instance", "data"], | |
["ec2", "server", "create"], | |
["ec2", "server", "delete"], | |
["ec2", "server", "list"], | |
["deploy"], | |
["edit", "env"], | |
["grep"], | |
["push", "env"], | |
["set", "rev"], | |
["show", "rev"]] | |
TEST_CASES = { | |
%w[cb up] => %w[cookbook upload], | |
%w[rff] => %w[role from file], | |
%w[ne] => %w[node edit], | |
%w[st] => %w[status], | |
%w[dbff] => %w[data bag from file] | |
} | |
#require 'pp' | |
#TEST_CASES.each do |input, expected| | |
#pp input => K::CommandSelector.new(input).pick | |
#end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment