ame 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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