02.09.2009Автоподстановка задач rake в терминале с помощью ruby
Введение
Периодически натыкаюсь на хвалебные отзывы о zsh и о совершенной необходимости перехода с bash на него всему прогрессивному человечеству. В качестве демонстрации удобства демонстрируют лёгкость создания скриптов автоподстановки, например для задач rake. Неужели из-за этого следует сменить оболочку терминала?
Задача
Найти решение для автоподстановки задач rake с помощью bash несложно. Для этого используется complete. Огромное число примеров можно посмотреть у себя же в системе в /etc/bash_completion.
При ближайшем рассмотрении выяснилось, что complete может использовать не только функцию, но и команду. То есть отдельный запускаемый скрипт, который может быть написан и на руби в том числе.
Можно ли из этого извлечь пользу?
Решение
Итак, чтобы подключить наш скрипт, нужно добавить в файл ~/.bashrc такую строчку.
complete -C ~/.bash/autocomplete/rake_complete -o default rake
Для начала положим по адресу ~/.bash/autocomplete/rake_complete элементарную реализацию автоподстановки, которая будет запускать rake и фильтровать результат.
Следует знать два момента:
- Введенная строка, после которой была нажата табуляция, находится в ENV["COMP_LINE"].
- В задачах могут попасться пространства имён. Поэтому из окончательного результата нужно убрать часть введенной строки, содержащей двоеточие.
Элементарная реализация выглядит следующим образом:
#!/usr/bin/env ruby
class RakeComplete
def initialize(cmd)
@command = cmd[ /\s(.+)$/, 1 ] || ""
end
def search
`rake -T`.split("\n")[1..-1].map{ |l| l.split(/\s+/)[1] }.select{ |cmd| cmd[0, @command.length] == @command }.map{ |cmd| cmd.gsub(cmd_before_column, "") }
end
private
def cmd_before_column
@command[ /.+\:/ ] || ""
end
end
puts RakeComplete.new(ENV["COMP_LINE"]).search
exit 0
Вроде бы всё понятно: из результата вывода rake -T , отбросив паразитную первую строчку, вытаскиваем только названия задач, подбираем те, начало который совпадает с введенным названием задачи, выводим массив, предварительно удалив у элементов введённую до двоеточия часть. Но уж больно медленно работает. Конечно же, встает вопрос о кешировании.
Кеширование
Приведу сразу результат:
#!/usr/bin/env ruby
class RakeComplete
CACHE_NAME = ".rake_complete~"
def initialize(cmd)
@command = cmd[ /\s(.+)$/, 1 ] || ""
end
def search
exit 0 if rake_file.nil?
selected_tasks.map do |cmd|
cmd.gsub(cmd_before_column, "")
end
end
private
def cmd_before_column
@command[ /.+\:/ ] || ""
end
def rake_file
["Rakefile", "Rakefile.rb", "rakefile", "rakefile.rb"].detect do |name|
File.file?(File.join(Dir.pwd, name))
end
end
def cache_file
File.join(Dir.pwd, CACHE_NAME)
end
def generate_tasks
tasks = `rake -T`.split("\n")[1..-1].map{ |l| l.split(/\s+/)[1] }
File.open(cache_file, "w") do |f|
tasks.each do |task|
f.puts task
end
end
tasks
end
def cached_tasks
File.read(cache_file).split("\n")
end
def cache_valid?
File.exist?(cache_file) && (File.mtime(cache_file) >= File.mtime(rake_file))
end
def tasks
cache_valid? ? cached_tasks : generate_tasks
end
def selected_tasks
tasks.select do |cmd|
cmd[0, @command.length] == @command
end
end
end
puts RakeComplete.new(ENV["COMP_LINE"]).search
exit 0
Для самостоятельного изучения
Думаю, теперь не составит труда написать подобное для задач capistrano.