Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: ea8d6288a0
Fetching contributors…

Cannot retrieve contributors at this time

724 lines (617 sloc) 17.159 kB
module Main
class Parameter
class Error < StandardError
include Softspoken
fattr 'wrapped'
end
class Arity < Error; end
class NotGiven < Arity; end
class InValid < Error; end
class NoneSuch < Error; end
class AmbigousOption < Error; end
class NeedlessArgument < Error; end
class MissingArgument < Error; end
class InvalidOption < Error; end
class << self
def wrapped_error w
e = Error.new "(#{ w.message } (#{ w.class }))"
e.wrapped = w
e.set_backtrace(w.backtrace || [])
e
end
def wrap_errors
begin
yield
rescue => e
raise wrapped_error(e)
end
end
Types = [ Parameter ]
def inherited other
Types << other
end
def sym
@sym ||= name.split(%r/::/).last.downcase.to_sym
end
def class_for(type)
sym = type.to_s.downcase.to_sym
c = Types.detect{|t| t.sym == sym}
raise ArgumentError, type.inspect unless c
c
end
def create(type, main, *a, &b)
c = class_for(type)
obj = c.allocate
obj.type = c.sym
obj.main = main
obj.instance_eval{ initialize(*a, &b) }
obj
end
end
fattr 'main'
fattr 'type'
fattr 'names'
fattr 'abbreviations'
fattr 'argument'
fattr 'given'
fattr 'cast'
fattr 'validate'
fattr 'description'
fattr 'synopsis'
fattr('values'){ [] }
fattr('defaults'){ [] }
fattr('examples'){ [] }
fattr 'arity' => 1
fattr 'required' => false
fattr 'error_handler_before'
fattr 'error_handler_instead'
fattr 'error_handler_after'
def initialize(name, *names, &block)
@names = Cast.list_of_string(name, *names)
@names.map! do |name|
if name =~ %r/^-+/
name.gsub! %r/^-+/, ''
end
if name =~ %r/=.*$/
argument( name =~ %r/=\s*\[.*$/ ? :optional : :required )
name.gsub! %r/=.*$/, ''
end
name
end
@names = @names.sort_by{|name| name.size}.reverse
@names[1..-1].each do |name|
raise ArgumentError, "only one long name allowed (#{ @names.inspect })" if
name.size > 1
end
DSL.evaluate(self, &block) if block
sanity_check!
end
def sanity_check!
raise Arity, "#{ name } with arity -1 (zero or more args) cannot be required" if(arity == -1 and required?)
end
def name
names.first
end
def default(*values)
defaults(values) unless values.empty?
defaults.first
end
def default=(value)
default(value)
end
def typename
prefix = '--' if type.to_s =~ %r/option/
"#{ type }(#{ prefix }#{ name })"
end
def add_value value
given true
values << value
end
def value
values.first
end
def argument_none?
argument.nil?
end
def argument_required?
argument and
argument.to_s.downcase.to_sym == :required
end
def argument_optional?
argument and
argument.to_s.downcase.to_sym == :optional
end
def optional?
not required?
end
def optional= bool
self.required !bool
end
=begin
def setup!
return false unless given?
adding_handlers do
check_arity
apply_casting
check_validation
end
true
end
=end
def setup!
adding_handlers do
check_arity
apply_casting
check_validation
end
end
def check_arity
return true if not given? and optional?
ex = values.size == 0 ? NotGiven : Arity
(raise ex, "#{ typename })" if values.size.zero? and argument_required?) unless arity == -1
if arity >= 0
min = arity
sign = ''
else
min = arity.abs - 1
sign = '-'
end
arity = min
if values.size < arity
if argument_optional?
raise ex, "#{ typename }) #{ values.size }/#{ sign }#{ arity }" if(values.size < arity and values.size > 0)
elsif argument_required? or argument_none?
raise ex, "#{ typename }) #{ values.size }/#{ sign }#{ arity }" if(values.size < arity)
end
end
end
def apply_casting
if cast?
op = cast.respond_to?('call') ? cast : Cast[cast]
case op.arity
when -1
replacement = Parameter.wrap_errors{ op.call(*values) }
values.replace(replacement)
else
values.map! do |val|
Parameter.wrap_errors{ op.call val }
end
end
end
end
def check_validation
if validate?
values.each do |value|
validate[value] or
raise InValid, "invalid: #{ typename }=#{ value.inspect }"
end
end
end
def add_handlers e
esc =
class << e
self
end
this = self
%w[ before instead after ].each do |which|
getter = "error_handler_#{ which }"
query = "error_handler_#{ which }?"
if send(query)
handler = send getter
esc.module_eval do
define_method(getter) do |main|
main.instance_eval_block self, &handler
end
end
end
end
end
def adding_handlers
begin
yield
rescue Exception => e
add_handlers e
raise
end
end
def remove
main.parameters.delete(self)
end
alias_method('remove!', 'remove')
alias_method('ignore', 'remove')
alias_method('ignore!', 'ignore')
class Argument < Parameter
fattr 'required' => true
fattr 'synopsis' do
label = name
op = required ? "->" : "~>"
value = defaults.size > 0 ? "#{ name }=#{ defaults.join ',' }" : name
value = "#{ cast }(#{ value })" if(cast and not cast.respond_to?(:call))
"#{ label } (#{ arity } #{ op } #{ value })"
end
end
class Option < Parameter
fattr 'required' => false
fattr 'arity' => 0
fattr 'synopsis' do
long, *short = names
value = cast || name
rhs = argument ? (argument == :required ? "=#{ name }" : "=[#{ name }]") : nil
label = ["--#{ long }#{ rhs }", short.map{|s| "-#{ s }"}].flatten.join(", ")
unless argument_none?
op = required ? "->" : "~>"
value = defaults.size > 0 ? "#{ name }=#{ defaults.join ',' }" : name
value = "#{ cast }(#{ value })" if(cast and not cast.respond_to?(:call))
"#{ label } (#{ arity } #{ op } #{ value })"
else
label
end
end
end
class Keyword < Parameter
fattr 'required' => false
fattr 'argument' => :required
fattr 'synopsis' do
label = "#{ name }=#{ name }"
op = required ? "->" : "~>"
value = defaults.size > 0 ? "#{ name }=#{ defaults.join ',' }" : name
value = "#{ cast }(#{ value })" if(cast and not cast.respond_to?(:call))
"#{ label } (#{ arity } #{ op } #{ value })"
end
end
class Environment < Parameter
fattr 'argument' => :required
fattr 'synopsis' do
label = "env[#{ name }]=#{ name }"
op = required ? "->" : "~>"
value = defaults.size > 0 ? "#{ name }=#{ defaults.join ',' }" : name
value = "#{ cast }(#{ value })" if(cast and not cast.respond_to?(:call))
"#{ label } (#{ arity } #{ op } #{ value })"
end
end
class List < ::Array
fattr :main
fattr :argv
fattr :env
def parse main
@main, @argv, @env = main, main.argv, main.env
ignore, stop = [], argv.index('--')
if stop
ignore = argv[stop .. -1]
(argv.size - stop).times{ argv.pop }
end
argv.push "--#{ argv.shift }" if argv.first == 'help'
parse_options argv
return 'help' if detect{|p| p.name.to_s == 'help' and p.given?}
parse_keywords argv
parse_arguments argv
parse_environment env
defaults!
validate!
argv.push(*ignore[1..-1]) unless ignore.empty?
return self
ensure
@main, @argv, @env = nil
end
def parse_options argv, params = nil
params ||= options
spec, h, s = [], {}, {}
params.each do |p|
head, *tail = p.names
long = "--#{ head }"
shorts = tail.map{|t| "-#{ t }"}
type =
if p.argument_required? then GetoptLong::REQUIRED_ARGUMENT
elsif p.argument_optional? then GetoptLong::OPTIONAL_ARGUMENT
else GetoptLong::NO_ARGUMENT
end
a = [ long, shorts, type ].flatten
spec << a
h[long] = p
s[long] = a
end
begin
GetoptLong.new(argv, *spec).each do |long, value|
value =
case s[long].last
when GetoptLong::NO_ARGUMENT
value.empty? ? true : value
when GetoptLong::OPTIONAL_ARGUMENT
value.empty? ? true : value
when GetoptLong::REQUIRED_ARGUMENT
value
end
p = h[long]
p.add_value value
end
rescue GetoptLong::AmbigousOption, GetoptLong::NeedlessArgument,
GetoptLong::MissingArgument, GetoptLong::InvalidOption => e
c = Parameter.const_get e.class.name.split(/::/).last
ex = c.new e.message
ex.set_backtrace e.message
ex.extend Softspoken
raise ex
end
=begin
params.each do |p|
p.setup!
end
=end
end
def parse_arguments argv, params=nil
params ||= select{|p| p.type == :argument}
params.each do |p|
if p.arity >= 0
p.arity.times do
break if argv.empty?
value = argv.shift
p.add_value value
end
else
arity = p.arity.abs - 1
arity.times do
break if argv.empty?
value = argv.shift
p.add_value value
end
argv.size.times do
value = argv.shift
p.add_value value
end
end
end
=begin
params.each do |p|
p.setup!
end
=end
end
def parse_keywords argv, params=nil
params ||= select{|p| p.type == :keyword}
replacements = {}
params.each do |p|
names = p.names
name = names.sort_by{|n| [n.size,n]}.last
kre = %r/^\s*(#{ names.join '|' })\s*=/
opt = "--#{ name }"
i = -1
argv.each_with_index do |a, idx|
i += 1
b = argv[idx + 1]
s = "#{ a }#{ b }"
m, key, *ignored = kre.match(s).to_a
if m
replacements[i] ||= a.gsub %r/^\s*#{ key }/, opt
next
end
=begin
abbrev = name.index(key) == 0
if abbrev
replacements[i] ||= a.gsub %r/^\s*#{ key }/, opt
end
=end
end
end
replacements.each do |i, r|
argv[i] = r
end
parse_options argv, params
end
def parse_environment env, params=nil
params ||= select{|p| p.type == :environment}
params.each do |p|
names = p.names
name = names.first
value = env[name]
next unless value
p.add_value value
end
=begin
params.each do |p|
p.setup!
end
=end
end
def defaults!
each do |p|
if(p.defaults? and (not p.given?))
p.defaults.each do |default|
p.values << (default.respond_to?('to_proc') ? main.instance_eval(&default) : default) # so as NOT to set 'given?'
end
end
end
end
def validate!
each do |p|
#p.adding_handlers do
#next if p.arity == -1
#raise NotGiven, "#{ p.typename } not given" if(p.required? and (not p.given?))
#end
p.setup!
end
end
[:parameter, :option, :argument, :keyword, :environment].each do |m|
define_method("#{ m }s"){ select{|p| p.type == m or m == :parameter} }
define_method("has_#{ m }?") do |name, *names|
catch :has do
names = Cast.list_of_string name, *names
send("#{ m }s").each do |param|
common = Cast.list_of_string(param.names) & names
throw :has, true unless common.empty?
end
false
end
end
end
def delete name, *names
name, *names = name.names if Parameter === name
names = Cast.list_of_string name, *names
keep = []
each do |param|
common = Cast.list_of_string(param.names) & names
keep << param if common.empty?
end
replace keep
end
def <<(*a)
delete(*a)
super
end
def [](*index)
first = index.first
if(index.size == 1 and (first.is_a?(String) or first.is_a?(Symbol)))
first = first.to_s
return detect{|param| param.name == first}
end
return super
end
end
class DSL
def self.evaluate param, &block
new(param).instance_eval(&block)
end
attr 'param'
def initialize param
@param = param
end
def fattr a = nil, &block
name = param.name
a ||= name
b = fattr_block_for name, &block
@param.main.module_eval{ fattr(*a, &b) }
end
alias_method 'attribute', 'fattr'
def fattr_block_for name, &block
block ||= lambda{|param| [0,1].include?(param.arity) ? param.value : param.values }
lambda{ block.call self.param[name] }
end
def attr(*a, &b)
fattr(*a, &b)
end
def example *list
list.flatten.compact.each do |elem|
param.examples << elem.to_s
end
end
alias_method "examples", "example"
def type *sym
sym.size == 0 ? param.type : (param.type = sym.first)
end
def type?
param.type?
end
def synopsis *arg
arg.size == 0 ? param.synopsis : (param.synopsis arg.first)
end
def argument arg
param.argument arg
end
def argument_required bool = true
if bool
param.argument :required
else
param.argument false
end
end
def argument_required?
param.argument_required?
end
def argument_optional bool = true
if bool
param.argument :optional
else
param.argument false
end
end
def argument_optional?
param.argument_optional?
end
def required bool = true
param.required = bool
end
def required?
param.required?
end
def optional bool = true
if bool
param.required !bool
else
param.required bool
end
end
def optional?
param.optional?
end
def cast sym=nil, &b
param.cast = sym || b
end
def cast?
param.cast?
end
def validate sym=nil, &b
param.validate = sym || b
end
def validate?
param.validate?
end
def description s
param.description = s.to_s
end
def description?
param.description?
end
alias_method 'desc', 'description'
def default *values, &block
if block.nil? and values.empty?
raise ArgumentError, 'no default'
end
unless values.empty?
param.defaults.push(*values)
end
unless block.nil?
param.defaults.push block
end
param.defaults
end
alias_method 'defaults', 'default'
def defaults?
param.defaults?
end
def arity value
raise Arity if value.nil?
value = -1 if value.to_s == '*'
value = Integer value
raise Arity if value.zero?
param.arity = value
if param.arity == -1
optional true
end
value
end
def arity?
param.arity?
end
def error which = :instead, &block
param.send "error_handler_#{ which }=", block
end
end
class Table < ::Array
def initialize
super()
self.fields = []
extend BoundsCheck
end
def to_options
(hash = self.to_hash ).keys.each { |key| hash[key] = hash[key].value }
return hash
end
module BoundsCheck
def [] *a, &b
p = super
ensure
raise NoneSuch, a.join(',') unless p
end
end
end
end
end
Jump to Line
Something went wrong with that request. Please try again.