ame 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +4 -0
- data/Rakefile +9 -0
- data/lib/ame.rb +26 -0
- data/lib/ame/argument.rb +56 -0
- data/lib/ame/arguments.rb +65 -0
- data/lib/ame/class.rb +117 -0
- data/lib/ame/help.rb +5 -0
- data/lib/ame/help/console.rb +96 -0
- data/lib/ame/method.rb +94 -0
- data/lib/ame/methods.rb +30 -0
- data/lib/ame/option.rb +50 -0
- data/lib/ame/options.rb +102 -0
- data/lib/ame/root.rb +57 -0
- data/lib/ame/splat.rb +12 -0
- data/lib/ame/types.rb +29 -0
- data/lib/ame/types/array.rb +16 -0
- data/lib/ame/types/boolean.rb +16 -0
- data/lib/ame/types/integer.rb +11 -0
- data/lib/ame/types/string.rb +9 -0
- data/lib/ame/version.rb +5 -0
- data/test/ame/types/array.rb +13 -0
- data/test/ame/types/boolean.rb +17 -0
- data/test/ame/types/integer.rb +7 -0
- data/test/ame/types/string.rb +6 -0
- data/test/unit/ame/argument.rb +66 -0
- data/test/unit/ame/arguments.rb +106 -0
- data/test/unit/ame/help/console.rb +163 -0
- data/test/unit/ame/method.rb +40 -0
- data/test/unit/ame/methods.rb +10 -0
- data/test/unit/ame/option.rb +75 -0
- data/test/unit/ame/options.rb +136 -0
- data/test/unit/ame/root.rb +15 -0
- data/test/unit/ame/splat.rb +11 -0
- metadata +150 -0
data/lib/ame/methods.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Ame::Methods
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@methods = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def <<(method)
|
11
|
+
@methods[method.name] = method
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](name)
|
16
|
+
@methods[name.to_sym] or
|
17
|
+
raise Ame::UnrecognizedMethod, 'unrecognized method: %s' % name
|
18
|
+
end
|
19
|
+
|
20
|
+
def include?(name)
|
21
|
+
@methods.include? name.to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
def each
|
25
|
+
@methods.each_value do |method|
|
26
|
+
yield method
|
27
|
+
end
|
28
|
+
self
|
29
|
+
end
|
30
|
+
end
|
data/lib/ame/option.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Ame::Option < Ame::Argument
|
4
|
+
def initialize(name, description, options = {}, &validate)
|
5
|
+
is_boolean_type = [TrueClass, FalseClass].include? options[:type]
|
6
|
+
options[:default] = false unless options.include? :default or options.include? :type
|
7
|
+
if is_boolean_type and not options.include? :default
|
8
|
+
options[:default] = options[:type] == FalseClass
|
9
|
+
end
|
10
|
+
is_boolean = [true, false].include? options[:default]
|
11
|
+
raise ArgumentError,
|
12
|
+
'optional arguments to options are only allowed for booleans' if
|
13
|
+
options[:optional] and not (is_boolean_type or is_boolean)
|
14
|
+
options[:optional] = is_boolean
|
15
|
+
raise ArgumentError,
|
16
|
+
'boolean options cannot have argument descriptions' if
|
17
|
+
is_boolean and options[:argument]
|
18
|
+
@argument_name = is_boolean ? "" : (options[:argument] || name).to_s
|
19
|
+
@aliases = Array(options[:alias]) + Array(options[:aliases])
|
20
|
+
@ignored = options[:ignore]
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
(name.to_s.length > 1 ? '--%s' : '-%s') % name
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :argument_name, :aliases
|
29
|
+
|
30
|
+
def short
|
31
|
+
[name, *aliases].find{ |a| a.to_s.length == 1 }
|
32
|
+
end
|
33
|
+
|
34
|
+
def long
|
35
|
+
[name, *aliases].find{ |a| a.to_s.length > 1 }
|
36
|
+
end
|
37
|
+
|
38
|
+
def ignored?
|
39
|
+
@ignored
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def set_default(value, type)
|
45
|
+
saved_optional, @optional = @optional, true
|
46
|
+
super
|
47
|
+
ensure
|
48
|
+
@optional = saved_optional
|
49
|
+
end
|
50
|
+
end
|
data/lib/ame/options.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Ame::Options
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@options = {}
|
8
|
+
@ordered = []
|
9
|
+
@options_must_precede_arguments = ENV.include? 'POSIXLY_CORRECT'
|
10
|
+
end
|
11
|
+
|
12
|
+
def option(name, description, options = {}, &block)
|
13
|
+
option = Ame::Option.new(name, description, options, &block)
|
14
|
+
self[option.name] = option
|
15
|
+
option.aliases.each do |a|
|
16
|
+
self[a] = option
|
17
|
+
end
|
18
|
+
@ordered << option
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def options_must_precede_arguments
|
23
|
+
@options_must_precede_arguments = true
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def process(arguments)
|
28
|
+
process!(defaults, arguments.dup)
|
29
|
+
end
|
30
|
+
|
31
|
+
def each
|
32
|
+
@ordered.each do |option|
|
33
|
+
yield option
|
34
|
+
end
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def include?(name)
|
39
|
+
@options.include? name.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def []=(name, option)
|
45
|
+
raise ArgumentError,
|
46
|
+
'option already defined: %s' % name if include? name
|
47
|
+
@options[name.to_s] = option
|
48
|
+
end
|
49
|
+
|
50
|
+
def [](name)
|
51
|
+
@options[name.to_s.sub(/^-+/, "")] or
|
52
|
+
raise Ame::UnrecognizedOption, 'unrecognized option: %s' % name
|
53
|
+
end
|
54
|
+
|
55
|
+
def defaults
|
56
|
+
@ordered.reduce({}){ |d, o| d[o.name] = o.default; d }
|
57
|
+
end
|
58
|
+
|
59
|
+
def process!(results, arguments)
|
60
|
+
remainder = []
|
61
|
+
until arguments.empty?
|
62
|
+
case first = arguments.shift
|
63
|
+
when '--'
|
64
|
+
break
|
65
|
+
when /^-([^=-]{2,})$/
|
66
|
+
process_combined results, arguments, $1
|
67
|
+
when /^(--[^=]+|-[^-])(?:=(.*))?$/
|
68
|
+
process1 results, arguments, self[$1], $2
|
69
|
+
else
|
70
|
+
remainder << first
|
71
|
+
break if @options_must_precede_arguments
|
72
|
+
end
|
73
|
+
end
|
74
|
+
[results.reject{ |n, _| self[n].ignored? }, remainder.concat(arguments)]
|
75
|
+
end
|
76
|
+
|
77
|
+
def process_combined(results, arguments, combined)
|
78
|
+
combined.each_char.with_index do |c, i|
|
79
|
+
option = self['-' + c]
|
80
|
+
if option.optional?
|
81
|
+
process1 results, [], option, nil
|
82
|
+
elsif i == combined.length - 1
|
83
|
+
process1 results, arguments, option, nil
|
84
|
+
else
|
85
|
+
process1 results, [], option, combined[i+1..-1]
|
86
|
+
break
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def process1(results, arguments, option, arg)
|
92
|
+
results[option.name] = option.process(results, [], argument(arguments, option, arg))
|
93
|
+
end
|
94
|
+
|
95
|
+
def argument(arguments, option, argument)
|
96
|
+
case
|
97
|
+
when argument then argument
|
98
|
+
when option.optional? then (!option.default).to_s
|
99
|
+
else arguments.shift
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/ame/root.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Ame::Root < Ame::Class
|
4
|
+
class << self
|
5
|
+
def basename
|
6
|
+
''
|
7
|
+
end
|
8
|
+
|
9
|
+
def process(method = File.basename($0), arguments = ARGV)
|
10
|
+
new.process(method, arguments)
|
11
|
+
rescue => e
|
12
|
+
help_for_error method, e
|
13
|
+
end
|
14
|
+
|
15
|
+
def help(help = nil)
|
16
|
+
return @help = help if help
|
17
|
+
@help ||= Ame::Help::Console.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def help_for_dispatch(method, subclass)
|
21
|
+
help.for_dispatch method, subclass
|
22
|
+
end
|
23
|
+
|
24
|
+
def help_for_method(method)
|
25
|
+
help.for_method method
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def help_for_error(method, error)
|
31
|
+
help.for_error method, error
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_added(name)
|
35
|
+
m = method
|
36
|
+
option :version, 'Display version information', :ignore => true do
|
37
|
+
help.version self, m
|
38
|
+
throw Ame::AbortAllProcessing
|
39
|
+
end unless method.options.include? :version
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def process(name, arguments = [])
|
45
|
+
catch Ame::AbortAllProcessing do
|
46
|
+
super
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def call(name, arguments = nil, options = nil)
|
52
|
+
catch Ame::AbortAllProcessing do
|
53
|
+
super
|
54
|
+
end
|
55
|
+
self
|
56
|
+
end
|
57
|
+
end
|
data/lib/ame/splat.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Ame::Splat < Ame::Argument
|
4
|
+
def arity
|
5
|
+
-1
|
6
|
+
end
|
7
|
+
|
8
|
+
def process(options, processed, arguments)
|
9
|
+
super options, processed, nil if required? and arguments.empty?
|
10
|
+
arguments.map{ |argument| super(options, processed, argument) }.tap{ arguments.clear }
|
11
|
+
end
|
12
|
+
end
|
data/lib/ame/types.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Ame::Types
|
4
|
+
class << self
|
5
|
+
def register(type, *classes)
|
6
|
+
classes.each do |c|
|
7
|
+
types[c] = type
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](class_or_value)
|
12
|
+
type = types[class_or_value] and return type
|
13
|
+
pair = types.find{ |c, t| class_or_value.is_a? c } and return pair.last
|
14
|
+
class_or_value.respond_to? :parse and return class_or_value
|
15
|
+
raise ArgumentError, 'unknown type: %p' % class_or_value
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def types
|
21
|
+
@types ||= {}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
autoload :Array, 'ame/types/array'
|
26
|
+
require 'ame/types/boolean'
|
27
|
+
require 'ame/types/integer'
|
28
|
+
require 'ame/types/string'
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Ame::Types::Boolean
|
4
|
+
Ame::Types.register self, TrueClass, FalseClass
|
5
|
+
|
6
|
+
def self.parse(value)
|
7
|
+
case value
|
8
|
+
when 'true', 'yes', 'on'
|
9
|
+
true
|
10
|
+
when 'false', 'no', 'off'
|
11
|
+
false
|
12
|
+
else
|
13
|
+
raise Ame::MalformedArgument, 'not a boolean: %s' % value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/ame/version.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
Expectations do
|
4
|
+
expect Ame::Types::Array do Ame::Types::Array[String] end
|
5
|
+
expect [''] do Ame::Types::Array[String].parse('') end
|
6
|
+
expect [1] do Ame::Types::Array[Integer].parse('1') end
|
7
|
+
expect [1, 2, 3] do
|
8
|
+
ary = Ame::Types::Array[Integer]
|
9
|
+
ary.parse('1')
|
10
|
+
ary.parse('2')
|
11
|
+
ary.parse('3')
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
Expectations do
|
4
|
+
expect TrueClass do Ame::Types::Boolean.parse('true') end
|
5
|
+
expect TrueClass do Ame::Types::Boolean.parse('yes') end
|
6
|
+
expect TrueClass do Ame::Types::Boolean.parse('on') end
|
7
|
+
expect Ame::MalformedArgument do Ame::Types::Boolean.parse('') end
|
8
|
+
expect Ame::MalformedArgument do Ame::Types::Boolean.parse('1') end
|
9
|
+
expect Ame::MalformedArgument do Ame::Types::Boolean.parse('junk') end
|
10
|
+
|
11
|
+
expect FalseClass do Ame::Types::Boolean.parse('false') end
|
12
|
+
expect FalseClass do Ame::Types::Boolean.parse('no') end
|
13
|
+
expect FalseClass do Ame::Types::Boolean.parse('off') end
|
14
|
+
expect Ame::MalformedArgument do Ame::Types::Boolean.parse('') end
|
15
|
+
expect Ame::MalformedArgument do Ame::Types::Boolean.parse('0') end
|
16
|
+
expect Ame::MalformedArgument do Ame::Types::Boolean.parse('junk') end
|
17
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
Expectations do
|
4
|
+
expect :a do Ame::Argument.new(:a, 'd').name end
|
5
|
+
|
6
|
+
expect 'd' do Ame::Argument.new(:a, 'd').description end
|
7
|
+
|
8
|
+
expect 1 do Ame::Argument.new(:a, 'd').arity end
|
9
|
+
|
10
|
+
expect Ame::Argument.new(:a, 'd').not.to.be.optional?
|
11
|
+
expect Ame::Argument.new(:a, 'd', :optional => true).to.be.optional?
|
12
|
+
|
13
|
+
expect Ame::Argument.new(:a, 'd').to.be.required?
|
14
|
+
expect Ame::Argument.new(:a, 'd', :optional => true).not.to.be.required?
|
15
|
+
|
16
|
+
expect 'A' do Ame::Argument.new(:a, 'd').to_s end
|
17
|
+
|
18
|
+
expect ArgumentError do Ame::Argument.new(:a, 'd', :default => 1) end
|
19
|
+
expect 1 do Ame::Argument.new(:a, 'd', :optional => true, :default => 1).default end
|
20
|
+
|
21
|
+
expect ArgumentError do Ame::Argument.new(:a, 'd', :optional => true, :type => String, :default => 1) end
|
22
|
+
expect 1 do Ame::Argument.new(:a, 'd', :optional => true, :type => Integer, :default => 1).default end
|
23
|
+
expect ArgumentError do Ame::Argument.new(:a, 'd', :optional => true, :type => TrueClass, :default => false) end
|
24
|
+
|
25
|
+
expect Ame::MissingArgument do Ame::Argument.new(:a, 'd').process({}, [], nil) end
|
26
|
+
expect nil do Ame::Argument.new(:a, 'd', :optional => true).process({}, [], nil) end
|
27
|
+
expect 'default' do Ame::Argument.new(:a, 'd', :optional => true, :default => 'default').process({}, [], nil) end
|
28
|
+
expect 'string' do Ame::Argument.new(:a, 'd').process({}, [], 'string') end
|
29
|
+
|
30
|
+
expect 1 do Ame::Argument.new(:a, 'd', :type => Integer).process({}, [], '1') end
|
31
|
+
expect 2 do Ame::Argument.new(:a, 'd', :optional => true, :default => 1).process({}, [], '2') end
|
32
|
+
expect Ame::MalformedArgument.new('A: not an integer: junk') do
|
33
|
+
Ame::Argument.new(:a, 'd', :type => Integer).process({}, [], 'junk')
|
34
|
+
end
|
35
|
+
|
36
|
+
expect TrueClass do Ame::Argument.new(:a, 'd', :type => TrueClass).process({}, [], 'true') end
|
37
|
+
expect TrueClass do Ame::Argument.new(:a, 'd', :optional => true, :default => true).process({}, [], nil) end
|
38
|
+
expect Ame::MalformedArgument.new('A: not a boolean: junk') do
|
39
|
+
Ame::Argument.new(:a, 'd', :type => TrueClass).process({}, [], 'junk')
|
40
|
+
end
|
41
|
+
|
42
|
+
expect FalseClass do Ame::Argument.new(:a, 'd', :type => FalseClass).process({}, [], 'false') end
|
43
|
+
expect FalseClass do Ame::Argument.new(:a, 'd', :optional => true, :default => false).process({}, [], nil) end
|
44
|
+
expect Ame::MalformedArgument.new('A: not a boolean: junk') do
|
45
|
+
Ame::Argument.new(:a, 'd', :type => FalseClass).process({}, [], 'junk')
|
46
|
+
end
|
47
|
+
|
48
|
+
expect :a => 1 do
|
49
|
+
options = nil
|
50
|
+
Ame::Argument.new(:a, 'd', :type => Integer){ |o, p, a| options = o }.process({:a => 1}, [1], '2')
|
51
|
+
options
|
52
|
+
end
|
53
|
+
expect [1] do
|
54
|
+
processed = nil
|
55
|
+
Ame::Argument.new(:a, 'd', :type => Integer){ |o, p, a| processed = p }.process({:a => 1}, [1], '2')
|
56
|
+
processed
|
57
|
+
end
|
58
|
+
expect 2 do
|
59
|
+
argument = nil
|
60
|
+
Ame::Argument.new(:a, 'd', :type => Integer){ |o, p, a| argument = a }.process({:a => 1}, [1], '2')
|
61
|
+
argument
|
62
|
+
end
|
63
|
+
expect 3 do
|
64
|
+
Ame::Argument.new(:a, 'd', :type => Integer){ |o, p, a| 3 }.process({:a => 1}, [1], '2')
|
65
|
+
end
|
66
|
+
end
|