command line option handling DSL implemented using Doodle
regular expression for ISO date
expect an on/off flag, e.g. -b
# File lib/doodle/app.rb, line 190 def boolean(*args, &block) args = [{ :using => Option, :default => false, :arity => 0}, *args] da = option(*args, &block) da.instance_eval do kind FalseClass, TrueClass from NilClass do false end from Numeric do |n| n == 0 ? false : true end from String do |s| case s when "on", "true", "yes", "1" true when "off", "false", "no", "0" false else raise Doodle::ValidationError, "unknown value for boolean: #{s}" end end end end
date: -d 2008-09-28
# File lib/doodle/app.rb, line 226 def date(*args, &block) args = [{ :using => Option, :kind => Date }, *args] da = option(*args, &block) da.instance_eval do from String do |s| Date.parse(s) end end end
expect a filename - set :existing => true to specify that the file must exist
filename :input, :existing => true, :flag => "i", :doc => "input file name"
# File lib/doodle/app.rb, line 171 def filename(*args, &block) args = [{ :using => Filename, :kind => String }, *args ] da = option(*args, &block) da.instance_eval do if da.existing must "exist" do |s| File.exist?(s) end end if da.expand from String do |s| File.expand_path(s) end end end end
defines the help text displayed when option —help passed
# File lib/doodle/app.rb, line 444 def help_text format_block = proc {|key, flag, doc, required, kind| sprintf(" %-3s %-14s %-10s %s %s", flag, key, kind, doc, required ? '(REQUIRED)' : '') } required, options = help_attributes.partition{ |a| a[3]} [ self.doc, "\n", self.usage ? ["Usage: " + self.usage, "\n"] : [], required.size > 0 ? ["Required args:", required.map(&format_block), "\n"] : [], options.size > 0 ? ["Options:", options.map(&format_block), "\n"] : [], (self.examples && self.examples.size > 0) ? "Examples:\n" + " " + self.examples.join("\n ") : [], ] end
whole number, e.g. -n 10
# File lib/doodle/app.rb, line 215 def integer(*args, &block) args = [{ :using => Option, :kind => Integer }, *args] da = option(*args, &block) da.instance_eval do from String do |s| s =~ /^\d+$/ or raise "must be a whole number" s.to_i end end end
the generic option - can be any type
# File lib/doodle/app.rb, line 104 def option(*args, &block) #p [:option, args, :optional, optional?] key_values, args = args.partition{ |x| x.kind_of?(Hash)} key_values = key_values.inject({ }){ |hash, kv| hash.merge(kv)} errors = [] # handle optional/required flipflop if optional? required = { :default => nil } if key_values.delete(:required) if key_values.key?(:optional) errors << "Can't specify both :required and :optional" end if key_values.key?(:default) errors << "Can't specify both :required and :default" end required = { } elsif key_values.delete(:optional) && !key_values.key?(:default) required = { :default => nil } end else key_values.delete(:required) required = { } end args = [{ :using => Option }.merge(required).merge(key_values), *args] da = has(*args, &block) if errors.size > 0 raise ArgumentError, "#{da.name}: #{errors.join(', ')}", [caller[-1]] end da.instance_eval do #p [:checking_values, values, values.class] if da.values.kind_of?(Range) must "be in range #{da.values}" do |s| da.values.include?(s) end elsif da.values.respond_to?(:size) && da.values.size > 0 must "be one of #{da.values.join(', ')}" do |s| da.values.include?(s) end end if da.match must "match pattern #{da.match.inspect}" do |s| #p [:matching, s, da.match.inspect] s.to_s =~ da.match end end end da end
(Not documented)
# File lib/doodle/app.rb, line 92 def optional @optional = true end
(Not documented)
# File lib/doodle/app.rb, line 95 def optional? instance_variable_defined?("@optional") ? @optional : false end
(Not documented)
# File lib/doodle/app.rb, line 89 def required @optional = false end
(Not documented)
# File lib/doodle/app.rb, line 98 def required_args doodle.attributes.select{ |k, v| v.required?}.map{ |k,v| v} end
call App.run to start your application (calls instance.run)
# File lib/doodle/app.rb, line 272 def run(argv = ARGV) begin # cheating if argv.include?('-h') or argv.include?('--help') puts help_text else app = from_argv(argv) app.run end rescue Exception => e if exit_status == 0 exit_status 1 end puts "\nERROR: #{e}" ensure exit(exit_status) end end
use this to include ‘standard’ flags: help (-h, —help), verbose (-v, —verbose) and debug (-d, —debug)
# File lib/doodle/app.rb, line 261 def std_flags # FIXME: this is bogus m = method(:help_text) boolean :help, :flag => "h", :doc => "display this help" boolean :verbose, :flag => "v", :doc => "verbose output" boolean :debug, :flag => "D", :doc => "turn on debugging" end
expect a string
# File lib/doodle/app.rb, line 155 def string(*args, &block) args = [{ :using => Option, :kind => String }, *args] da = option(*args, &block) end
expect a symbol (and convert from String)
# File lib/doodle/app.rb, line 160 def symbol(*args, &block) args = [{ :using => Option, :kind => Symbol }, *args] da = option(*args, &block) da.instance_eval do from String do |s| s.to_sym end end end
replace the full directory path with ./ where appropriate
# File lib/doodle/app.rb, line 35 def self.tidy_dir(path) path.to_s.gsub(Regexp.new("^#{ Regexp.escape(Dir.pwd) }/"), './') end
time: -d 2008-09-28T18:00:00
# File lib/doodle/app.rb, line 236 def time(*args, &block) args = [{ :using => Option, :kind => Time }, *args] da = option(*args, &block) da.instance_eval do from String do |s| Time.parse(s) end end end
utcdate: -d 2008-09-28T21:41:29Z
# File lib/doodle/app.rb, line 247 def utcdate(*args, &block) args = [{ :using => Option, :kind => Time }, *args] da = option(*args, &block) da.instance_eval do from String do |s| if s !~ RX_ISODATE raise ArgumentError, "date must be in ISO format (YYYY-MM-DDTHH:MM:SS, e.g. #{Time.now.utc.xmlschema})" end Time.parse(s) end end end
helpers
# File lib/doodle/app.rb, line 294 def flag_to_attribute(flag) a = doodle.attributes.select do |key, attr| (key.to_s == flag.to_s) || (attr.respond_to?(:flag) && attr.flag.to_s == flag.to_s) end if a.size == 0 raise ArgumentError, "Unknown option: #{flag}" elsif a.size > 1 #raise ArgumentError, "More than one option matches: #{flag}" end a.first end
(Not documented)
# File lib/doodle/app.rb, line 395 def format_doc(attr) #p [:doc, attr.doc] doc = attr.doc if doc.kind_of?(Doodle::DeferredBlock) doc = doc.block end if doc.kind_of?(Proc) doc = attr.instance_eval(&doc) end if doc.respond_to?(:call) doc = doc.call end doc = doc.to_s if attr.respond_to?(:values) && (attr.values.kind_of?(Range) || attr.values.size > 0) doc = "#{doc} [#{format_values(attr.values)}]" end doc end
(Not documented)
# File lib/doodle/app.rb, line 413 def format_kind(kind) if (kind & [TrueClass, FalseClass, NilClass]).size > 0 "Boolean" else kind.map{ |k| k.to_s }.join(', ') end.upcase end
(Not documented)
# File lib/doodle/app.rb, line 385 def format_values(values) case values when Array values.map{ |s| s.to_s }.join(', ') when Range values.inspect else values.inspect end end
(Not documented)
# File lib/doodle/app.rb, line 379 def from_argv(argv) params, args = params_args(argv) args << params #pp [:args, args] new(*args) end
(Not documented)
# File lib/doodle/app.rb, line 420 def help_attributes options, args = doodle.attributes.partition { |key, attr| attr.respond_to?(:flag)} args = args.map { |key, attr| [ '', '', format_doc(attr), attr.required?, format_kind(attr.kind), ] } options = options.map { |key, attr| [ "--#{key}", attr.flag.to_s.size > 0 ? "-#{attr.flag}," : '', format_doc(attr), attr.required?, format_kind(attr.kind), ] } args + options end
(Not documented)
# File lib/doodle/app.rb, line 305 def key_value(arg, argv) value = nil if arg[0] == ?- # got flag #p [:a, 1, arg] if arg[1] == ?- # got --flag # --key value key = arg[2..-1] #p [:a, 2, arg, key] if key == "" key = "--" end else key = arg[1].chr #p [:a, 4, key] if arg[2] # -kvalue value = arg[2..-1] #p [:a, 5, key, value] end end pkey, attr = flag_to_attribute(key) if pkey.nil? raise Exception, "Internal error: #{key} does not match attribute" end #p [:flag_to_attribute, key, value, pkey, attr] if value.nil? if attr.arity == 0 value = true else #p [:args, 5, :getting_args, attr.arity] value = [] 1.upto(attr.arity) do a = argv.shift break if a.nil? #p [:a, 6, key, value] if a =~ /^-/ # got a switch - break? (what should happen here?) #p [:a, 7, key, value] argv.unshift a break else value << a end end if attr.arity == 1 value = *value end end end if key.size == 1 #p [:finding, key] #p [:found, pkey, attr] key = pkey.to_s end end [key, value] end
(Not documented)
# File lib/doodle/app.rb, line 364 def params_args(argv) argv = argv.dup params = { } args = [] while arg = argv.shift key, value = key_value(arg, argv) if key.nil? args << arg else #p [:setting, key, value] params[key] = value end end [params, args] end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.