lib/duse/cli/command.rb
require 'highline'
require 'duse/cli/cli_config'
require 'duse/cli/parser'
module Duse
module CLI
class Command
attr_reader :output, :input
attr_accessor :arguments, :force_interactive
extend Parser
HighLine.color_scheme = HighLine::ColorScheme.new do |cs|
cs[:command] = [ :bold ]
cs[:error] = [ :red ]
cs[:important] = [ :bold, :underline ]
cs[:success] = [ :green ]
cs[:info] = [ :yellow ]
cs[:debug] = [ :magenta ]
end
on('-h', '--help', 'Display help') do |c, _|
c.say c.help
exit
end
def initialize(options = {})
self.output = $stdout
self.input = $stdin
self.arguments ||= []
options.each do |key, value|
public_send("#{key}=", value) if respond_to? "#{key}="
end
@arguments ||= []
end
def parse(args)
arguments.concat(parser.parse(args))
rescue OptionParser::ParseError => e
error e.message
end
def execute
check_arity(method(:run), *arguments)
setup
run *arguments
rescue Interrupt
say "\naborted!"
rescue Faraday::SSLError
error "\nSSL connection could not be verified. Aborting...You should " \
"check what the hell is going on here."
end
def setup
end
def output=(io)
@terminal = nil
@output = io
end
def input=(io)
@terminal = nil
@input = io
end
def terminal
@terminal ||= HighLine.new(input, output)
end
# ignore Command since its not supposed to be executed
@@abstract ||= [Command]
def self.abstract?
@@abstract.include? self
end
def self.abstract
@@abstract << self
end
def self.skip(*names)
names.each { |n| define_method(n) {} }
end
def self.super_command=(command_class)
@super_command = command_class
end
def self.has_super_command?
!@super_command.nil?
end
def self.super_command
@super_command
end
def self.subcommand(command)
return nil if command.nil?
return subcommands.select { |sc| sc.command_name == command }.first if command.is_a? String
command.super_command = self
subcommands << command
end
def self.subcommands
@subcommands ||= []
end
def self.has_subcommands?
!@subcommands.empty?
end
def config
CLI.config
end
def warn(message)
write_to($stderr) do
say color(message, :error)
yield if block_given?
end
end
def error(message, &block)
warn(message, &block)
exit 1
end
def success(line)
say color(line, :success) if interactive?
end
def write_to(io)
io_was, self.output = output, io
yield
ensure
self.output = io_was if io_was
end
def color(line, style)
return line.to_s unless interactive?
terminal.color(line || '???', Array(style).map(&:to_sym))
end
def interactive?(io = output)
return io.tty? if force_interactive.nil?
force_interactive
end
def say(data, format = nil, style = nil)
terminal.say format(data, format, style)
end
def self.full_command
name[/[^:]*$/].split(/(?=[A-Z])/).map(&:downcase).join(' ')
end
def full_command
self.class.full_command
end
def self.command_name
return full_command.sub(super_command.full_command, '').strip.sub(' ', '-') if has_super_command?
full_command
end
def command_name
self.class.command_name
end
def self.description(description = nil)
@description = description if description
@description ||= ""
end
def help(info = "")
return help_subcommands unless self.class.subcommands.empty?
parser.banner = usage
self.class.description.sub(/./) { |c| c.upcase } + ".\n\n" + info + parser.to_s
end
def help_subcommands
result = "#{self.class.description}\n\n"
result << "Usage: duse #{full_command} COMMAND ...\n\nAvailable commands:\n\n"
self.class.subcommands.each { |command_class| result << "\t#{color(command_class.command_name, :command).ljust(22)} #{color(command_class.description, :info)}\n" }
result << "\nrun `duse help #{full_command} COMMAND` for more infos"
result
end
def usage
"Usage: " << color(usage_for(full_command, :run), :command)
end
def usage_for(prefix, method)
usage = "duse #{prefix}"
method = method(method)
if method.respond_to? :parameters
method.parameters.each do |type, name|
name = name.upcase
name = "[#{name}]" if type == :opt
name = "[#{name}..]" if type == :rest
usage << " #{name}"
end
elsif method.arity != 0
usage << " ..."
end
usage << " [OPTIONS]"
end
def check_arity(method, *args)
return unless method.respond_to? :parameters
method.parameters.each do |type, name|
return if type == :rest
wrong_args("few") unless args.shift or type == :opt or type == :block
end
wrong_args("many") if args.any?
end
def wrong_args(quantity)
error "too #{quantity} arguments" do
say help
end
end
def format(data, format = nil, style = nil)
style ||= :important
data = format % color(data, style) if format and interactive?
data = data.gsub(/<\[\[/, '<%=').gsub(/\]\]>/, '%>')
data.encode! 'utf-8' if data.respond_to? :encode!
data
end
end
end
end