ame 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|