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.
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+ class Ame::Types::Array
4
+ class << self
5
+ alias_method :[], :new
6
+ end
7
+
8
+ def initialize(type)
9
+ @type = Ame::Types[type]
10
+ @contents = []
11
+ end
12
+
13
+ def parse(value)
14
+ @contents << @type.parse(value)
15
+ end
16
+ 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
@@ -0,0 +1,11 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Ame::Types::Integer
4
+ Ame::Types.register self, Integer
5
+
6
+ def self.parse(value)
7
+ Integer(value)
8
+ rescue ArgumentError
9
+ raise Ame::MalformedArgument, 'not an integer: %s' % value
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Ame::Types::String
4
+ Ame::Types.register self, String
5
+
6
+ def self.parse(value)
7
+ value
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Ame
4
+ Version = '0.1.0'
5
+ end
@@ -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,7 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ Expectations do
4
+ expect 1 do Ame::Types::Integer.parse('1') end
5
+ expect Ame::MalformedArgument do Ame::Types::Integer.parse('') end
6
+ expect Ame::MalformedArgument do Ame::Types::Integer.parse('junk') end
7
+ end
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ Expectations do
4
+ expect '' do Ame::Types::String.parse('') end
5
+ expect 'junk' do Ame::Types::String.parse('junk') end
6
+ 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