lib/sickle.rb in sickle-0.0.1 vs lib/sickle.rb in sickle-0.1.0

- old
+ new

@@ -1,5 +1,254 @@ require "sickle/version" +require 'optparse' module Sickle - # Your code goes here... + class << self + def push_desc(desc) + @__desc = desc + end + + def pop_desc + d = @__desc + @__desc = nil + d + end + + def push_option(name, opts) + @__options ||= {} + @__options[name] = Option.new(name, opts) + end + + def pop_options + o = @__options || {} + @__options = {} + o + end + + def push_namespace(n) + namespace << n + end + + def pop_namespace + namespace.pop + end + + def namespace + @__namespace ||= [] + end + end + + module Runner + def self.included(base) + base.extend(Sickle::ClassMethods) + + if base.is_a?(Class) + base.send(:include, Sickle::Help) + base.method_added(:help) + end + end + + def options + @__options ||= {} + end + end + + class Command + attr_accessor :meth, :name, :desc, :options + + def initialize(meth, name, desc, options) + @meth, @name, @desc, @options = meth, name, desc, options + end + end + + class Option + attr_accessor :name, :opts, :default + + def initialize(name, opts) + @name, @opts = name, opts + + @default = opts[:default] || false + + if @default == true || @default == false + @type = :boolean + else + @type = @default.class.to_s.downcase.to_sym + end + end + + def register(parser, results) + if @type == :boolean + parser.on("--#{@name}", opts[:desc]) do + results[@name] = true + end + else + parser.on("--#{@name} #{@name.upcase}") do |v| + results[@name] = coerce(v) + end + end + end + + def coerce(value) + case @default + when Fixnum + value.to_i + when Float + value.to_f + else + value + end + end + + end + + module Help + def help(command = nil) + if command + __display_help_for_command(command) + else + __display_help + end + end + + def __display_help_for_command(name) + if cmd = self.class.__commands[name] + puts "USAGE:" + u, _ = __display_command_usage(name, cmd) + puts " #{$0} #{u}" + puts + puts "DESCRIPTION:" + puts cmd.desc.split("\n").map {|e| " #{e}"}.join("\n") + puts + unless cmd.options.empty? + puts "OPTIONS:" + cmd.options.each do |_, opt| + puts __display_option(opt) + end + end + + __display_global_options + else + puts "\e[31mCommand '#{name}' not found\e[0m" + end + end + + def __display_command_usage(name, command) + params = command.meth.parameters.map do |(r, p)| + r == :req ? p.upcase : "[#{p.upcase}]" + end + + ["#{name} #{params.join(" ")}", command] + end + + def __display_help + puts "USAGE:" + puts " #{$0} COMMAND [ARG1, ARG2, ...] [OPTIONS]" + puts + + puts "TASKS:" + cmds = self.class.__commands.sort.map do |name, command| + __display_command_usage(name, command) + end + max = cmds.map {|a| a[0].length }.max + cmds.each do |(cmd, c)| + desc = c.desc ? "# #{c.desc}" : "" + puts " #{cmd.ljust(max)} #{desc}" + end + + __display_global_options + end + + def __display_global_options + unless self.class.__global_options.empty? + puts + puts "GLOBAL OPTIONS:" + self.class.__global_options.sort.each do |name, opt| + puts __display_option(opt) + end + end + end + + def __display_option(opt) + " --#{opt.name} (default: #{opt.default})" + end + end + + module ClassMethods + def included(base) + __commands.each do |name, command| + name = (Sickle.namespace + [name]).join(":") + base.__commands[name] = command + end + end + + def desc(label) + Sickle.push_desc(label) + end + + def global_option(name, opts = {}) + __global_options[name.to_s] = Option.new(name, opts) + end + + def option(name, opts = {}) + Sickle.push_option(name, opts) + end + + def include_modules(hash) + hash.each do |key, value| + Sickle.push_namespace(key) + send(:include, value) + Sickle.pop_namespace + end + end + + def __commands + @__commands ||= {} + end + + def __global_options + @__global_options ||= {} + end + + def run(argv) + # puts "ARGV: #{argv.inspect}" + + if command_name = argv.shift + if command = __commands[command_name] + all = __global_options.values + command.options.values + + results = {} + args = OptionParser.new do |parser| + all.each do |option| + option.register(parser, results) + end + end.parse!(argv) + + all.each do |o| + results[o.name] ||= o.default + end + + # puts "args: #{args.inspect}" + # puts "results: #{results.inspect}" + + + obj = self.new + obj.instance_variable_set(:@__options, results) + command.meth.bind(obj).call(*args) + else + puts "\e[31mCommand '#{command_name}' not found\e[0m" + puts + run(["help"]) + end + else + run(["help"]) + end + end + + + + def method_added(a) + meth = instance_method(a) + __commands[a.to_s] = Command.new(meth, a, Sickle.pop_desc, Sickle.pop_options) + end + end end +