ame 0.1.1 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README +541 -3
- data/Rakefile +15 -6
- data/lib/ame-1.0.rb +31 -0
- data/lib/ame-1.0/argument.rb +63 -0
- data/lib/ame-1.0/arguments.rb +44 -0
- data/lib/ame-1.0/arguments/complete.rb +37 -0
- data/lib/ame-1.0/arguments/optional.rb +34 -0
- data/lib/ame-1.0/arguments/undefined.rb +71 -0
- data/lib/ame-1.0/class.rb +436 -0
- data/lib/ame-1.0/flag.rb +101 -0
- data/lib/{ame → ame-1.0}/help.rb +1 -1
- data/lib/ame-1.0/help/delegate.rb +19 -0
- data/lib/ame-1.0/help/terminal.rb +132 -0
- data/lib/ame-1.0/method.rb +75 -0
- data/lib/ame-1.0/method/undefined.rb +184 -0
- data/lib/ame-1.0/methods.rb +40 -0
- data/lib/ame-1.0/multioption.rb +36 -0
- data/lib/ame-1.0/option.rb +37 -0
- data/lib/ame-1.0/optional.rb +31 -0
- data/lib/ame-1.0/options.rb +68 -0
- data/lib/ame-1.0/options/undefined.rb +114 -0
- data/lib/ame-1.0/root.rb +174 -0
- data/lib/ame-1.0/splat.rb +16 -0
- data/lib/ame-1.0/splus.rb +22 -0
- data/lib/ame-1.0/switch.rb +39 -0
- data/lib/ame-1.0/types.rb +60 -0
- data/lib/ame-1.0/types/boolean.rb +13 -0
- data/lib/ame-1.0/types/enumeration.rb +40 -0
- data/lib/ame-1.0/types/float.rb +11 -0
- data/lib/{ame → ame-1.0}/types/integer.rb +3 -3
- data/lib/{ame → ame-1.0}/types/string.rb +2 -2
- data/lib/ame-1.0/types/symbol.rb +9 -0
- data/lib/ame-1.0/version.rb +62 -0
- data/test/unit/ame-1.0.rb +4 -0
- data/test/unit/ame-1.0/argument.rb +46 -0
- data/test/unit/ame-1.0/arguments.rb +63 -0
- data/test/unit/ame-1.0/arguments/complete.rb +4 -0
- data/test/unit/ame-1.0/arguments/optional.rb +4 -0
- data/test/unit/ame-1.0/arguments/undefined.rb +63 -0
- data/test/unit/ame-1.0/class.rb +4 -0
- data/test/unit/ame-1.0/flag.rb +31 -0
- data/test/unit/ame-1.0/help.rb +4 -0
- data/test/unit/ame-1.0/help/delegate.rb +4 -0
- data/test/unit/{ame/help/console.rb → ame-1.0/help/terminal.rb} +34 -23
- data/test/unit/ame-1.0/method.rb +4 -0
- data/test/unit/ame-1.0/method/undefined.rb +33 -0
- data/test/unit/ame-1.0/methods.rb +9 -0
- data/test/unit/ame-1.0/multioption.rb +4 -0
- data/test/unit/ame-1.0/option.rb +11 -0
- data/test/unit/ame-1.0/optional.rb +9 -0
- data/test/unit/ame-1.0/options.rb +149 -0
- data/test/unit/ame-1.0/options/undefined.rb +33 -0
- data/test/unit/ame-1.0/root.rb +4 -0
- data/test/unit/ame-1.0/splat.rb +9 -0
- data/test/unit/ame-1.0/splus.rb +4 -0
- data/test/unit/ame-1.0/switch.rb +15 -0
- data/test/unit/ame-1.0/types.rb +4 -0
- data/test/{ame → unit/ame-1.0}/types/boolean.rb +0 -0
- data/test/unit/ame-1.0/types/enumeration.rb +4 -0
- data/test/unit/ame-1.0/types/float.rb +7 -0
- data/test/{ame → unit/ame-1.0}/types/integer.rb +0 -0
- data/test/{ame → unit/ame-1.0}/types/string.rb +0 -0
- data/test/unit/ame-1.0/types/symbol.rb +5 -0
- data/test/unit/ame-1.0/version.rb +4 -0
- metadata +690 -60
- data/lib/ame.rb +0 -26
- data/lib/ame/argument.rb +0 -56
- data/lib/ame/arguments.rb +0 -65
- data/lib/ame/class.rb +0 -117
- data/lib/ame/help/console.rb +0 -96
- data/lib/ame/method.rb +0 -94
- data/lib/ame/methods.rb +0 -30
- data/lib/ame/option.rb +0 -50
- data/lib/ame/options.rb +0 -102
- data/lib/ame/root.rb +0 -57
- data/lib/ame/splat.rb +0 -12
- data/lib/ame/types.rb +0 -29
- data/lib/ame/types/array.rb +0 -16
- data/lib/ame/types/boolean.rb +0 -16
- data/lib/ame/version.rb +0 -5
- data/test/ame/types/array.rb +0 -13
- data/test/unit/ame/argument.rb +0 -66
- data/test/unit/ame/arguments.rb +0 -106
- data/test/unit/ame/method.rb +0 -40
- data/test/unit/ame/methods.rb +0 -10
- data/test/unit/ame/option.rb +0 -75
- data/test/unit/ame/options.rb +0 -136
- data/test/unit/ame/root.rb +0 -15
- data/test/unit/ame/splat.rb +0 -11
data/Rakefile
CHANGED
@@ -1,9 +1,18 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'yard'
|
3
|
+
require 'inventory-rake-1.0'
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
load File.expand_path('../lib/ame-1.0/version.rb', __FILE__)
|
6
|
+
|
7
|
+
Inventory::Rake::Tasks.define Ame::Version
|
8
|
+
|
9
|
+
Inventory::Rake::Tasks.unless_installing_dependencies do
|
10
|
+
require 'lookout-rake-3.0'
|
11
|
+
Lookout::Rake::Tasks::Test.new
|
12
|
+
|
13
|
+
require 'inventory-rake-tasks-yard-1.0'
|
14
|
+
Inventory::Rake::Tasks::YARD.new do |t|
|
15
|
+
t.options += %w'--plugin yard-heuristics-1.0'
|
16
|
+
t.globals[:source_code_url] = 'https://github.com/now/%s/blob/v%s/%%s#L%%d' % [t.inventory.package, t.inventory]
|
17
|
+
end
|
18
|
+
end
|
data/lib/ame-1.0.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# Namespace for Ame.
|
4
|
+
# @api developer/user
|
5
|
+
module Ame
|
6
|
+
# Value thrown to abort processing of a {Root.process} or {Root.call}.
|
7
|
+
AbortAllProcessing = :AmeAbortAllProcessing
|
8
|
+
|
9
|
+
# Value thrown to abort processing of a {Class.process} or {Class.call}.
|
10
|
+
AbortProcessing = :AmeAbortProcessing
|
11
|
+
|
12
|
+
Error = Class.new(StandardError)
|
13
|
+
|
14
|
+
# Error raised when a dispatch is invoked on a method that doesn’t exist.
|
15
|
+
UnrecognizedMethod = Class.new(Error)
|
16
|
+
|
17
|
+
# Error raised when an argument can’t be parsed into the desired type.
|
18
|
+
MalformedArgument = Class.new(Error)
|
19
|
+
|
20
|
+
# Error raised when a required argument is missing.
|
21
|
+
MissingArgument = Class.new(Error)
|
22
|
+
|
23
|
+
# Error raised when too many arguments have been given.
|
24
|
+
SuperfluousArgument = Class.new(Error)
|
25
|
+
|
26
|
+
# Error raised when an unrecognized option has been given.
|
27
|
+
UnrecognizedOption = Class.new(Error)
|
28
|
+
|
29
|
+
load File.expand_path('../ame-1.0/version.rb', __FILE__)
|
30
|
+
Version.load
|
31
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# Represents an argument to a {Method}. It has a {#name} and a {#description}.
|
4
|
+
# It will be called upon to process an argument before the method this argument
|
5
|
+
# is associated with gets called.
|
6
|
+
# @api developer
|
7
|
+
class Ame::Argument
|
8
|
+
# @api internal
|
9
|
+
# @param [String] name
|
10
|
+
# @param [::Class] type
|
11
|
+
# @param [String] description
|
12
|
+
# @yield [?]
|
13
|
+
# @yieldparam [Hash<String, Object>] options
|
14
|
+
# @yieldparam [Array<String>] processed
|
15
|
+
# @yieldparam [Object] argument
|
16
|
+
# @raise [ArgumentError] If TYPE isn’t one that Ame knows how to parse
|
17
|
+
def initialize(name, type, description, &validate)
|
18
|
+
@name, @description, @validate = name, description, validate || proc{ |_, _, a| a }
|
19
|
+
@type = Ame::Types[[type, String].reject(&:nil?).first]
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String] The name of the receiver
|
23
|
+
attr_reader :name
|
24
|
+
|
25
|
+
# @return [String] The description of the receiver
|
26
|
+
attr_reader :description
|
27
|
+
|
28
|
+
# @return [String] The upcasing of the {#name} of the receiver
|
29
|
+
def to_s
|
30
|
+
@to_s ||= name.upcase
|
31
|
+
end
|
32
|
+
|
33
|
+
# Invokes the optional block passed to the receiver when it was created for
|
34
|
+
# additional validation and parsing after optionally parsing one or more of
|
35
|
+
# the ARGUMENTS passed to it (see subclasses’ {#parse} methods for their
|
36
|
+
# behaviour).
|
37
|
+
# @api internal
|
38
|
+
# @param [Hash<String, Object>] options
|
39
|
+
# @param [Array<String>] processed
|
40
|
+
# @param [Array<String>] arguments
|
41
|
+
# @raise [MissingArgument] If a required argument is missing
|
42
|
+
# @raise [MalformedArgument] If an argument can’t be parsed
|
43
|
+
# @return [Object]
|
44
|
+
def process(options, processed, arguments)
|
45
|
+
@validate.call(options, processed, parse(arguments))
|
46
|
+
rescue Ame::MalformedArgument, ArgumentError, TypeError => e
|
47
|
+
raise Ame::MalformedArgument, '%s: %s' % [self, e]
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Returns the parsed value of the result of ARGUMENTS#shift
|
53
|
+
# Should be overridden by subclasses that want different behaviour for
|
54
|
+
# missing arguments.
|
55
|
+
# @api internal
|
56
|
+
# @param [Array<String>] arguments
|
57
|
+
# @return [Object] The parsed value of the result of ARGUMENTS#shift
|
58
|
+
# @raise [MissingArgument] If ARGUMENTS#empty?
|
59
|
+
# @raise [MalformedArgument] If ARGUMENTS#shift is non-nil and can’t be parsed
|
60
|
+
def parse(arguments)
|
61
|
+
@type.parse(arguments.shift || raise(Ame::MissingArgument, 'missing argument: %s' % self))
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# The arguments to a method in its {Method defined state}. Does the processing
|
4
|
+
# of arguments to the method and also enumerates {#each} of the arguments to
|
5
|
+
# the method for, for example, help output.
|
6
|
+
# @api developer
|
7
|
+
class Ame::Arguments
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def initialize(arguments)
|
11
|
+
@arguments = arguments
|
12
|
+
end
|
13
|
+
|
14
|
+
# @api internal
|
15
|
+
# @param [Hash<String, Object>] options
|
16
|
+
# @param [Array<String>] arguments
|
17
|
+
# @raise [SuperfluousArgument] If more arguments than required/optional have
|
18
|
+
# been given
|
19
|
+
# @raise (see Argument#process)
|
20
|
+
# @return [Array<Object>] The {Argument#process}ed arguments
|
21
|
+
def process(options, arguments)
|
22
|
+
unprocessed = arguments.dup
|
23
|
+
reduce([]){ |processed, argument|
|
24
|
+
processed << argument.process(options, processed, unprocessed)
|
25
|
+
}.tap{
|
26
|
+
raise Ame::SuperfluousArgument,
|
27
|
+
'superfluous arguments: %s' % unprocessed.join(' ') unless unprocessed.empty?
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
# @overload
|
32
|
+
# Enumerates the arguments.
|
33
|
+
#
|
34
|
+
# @yieldparam [Argument] argument
|
35
|
+
# @overload
|
36
|
+
# @return [Enumerator<Argument>] An Enumerator over the arguments
|
37
|
+
def each
|
38
|
+
return enum_for(__method__) unless block_given?
|
39
|
+
@arguments.each do |argument|
|
40
|
+
yield argument
|
41
|
+
end
|
42
|
+
self
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# The arguments to a method in its {Method::Undefined undefined state} where a
|
4
|
+
# splat or splus has been added and thus no further arguments are allowed.
|
5
|
+
# @api internal
|
6
|
+
class Ame::Arguments::Complete < Ame::Arguments::Undefined
|
7
|
+
def initialize(arguments, splat_command, splat)
|
8
|
+
@arguments, @splat_command, @splat = arguments + [splat], splat_command, splat
|
9
|
+
end
|
10
|
+
|
11
|
+
# @raise [ArgumentError] If a splat or splus argument has been defined
|
12
|
+
def argument(name, type, description, &validate)
|
13
|
+
error 'argument', name
|
14
|
+
end
|
15
|
+
|
16
|
+
# @raise [ArgumentError] If a splat or splus argument has been defined
|
17
|
+
def optional(name, default, description, &validate)
|
18
|
+
error 'optional', name
|
19
|
+
end
|
20
|
+
|
21
|
+
# @raise [ArgumentError] If a splat or splus argument has been defined
|
22
|
+
def splat(name, type, description, &validate)
|
23
|
+
error 'splat', name
|
24
|
+
end
|
25
|
+
|
26
|
+
# @raise [ArgumentError] If a splat or splus argument has been defined
|
27
|
+
def splus(name, type, description, &validate)
|
28
|
+
error 'splus', name
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def error(command, name)
|
34
|
+
raise ArgumentError, "%s '%s', … may not follow %s '%s', …" %
|
35
|
+
[command, name, @splat_command, @splat.name]
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# The arguments to a method in its {Method::Undefined undefined state} where at
|
4
|
+
# least one optional argument has been defined and only further
|
5
|
+
# {Optional#optional #optional} or {Undefined#splat #splat} arguments are
|
6
|
+
# allowed.
|
7
|
+
# @api internal
|
8
|
+
class Ame::Arguments::Optional < Ame::Arguments::Undefined
|
9
|
+
def initialize(arguments, optional)
|
10
|
+
@arguments, @optional = arguments + [optional], optional
|
11
|
+
end
|
12
|
+
|
13
|
+
# @raise [ArgumentError] If an optional argument has been defined
|
14
|
+
def argument(name, type, description, &validate)
|
15
|
+
raise ArgumentError,
|
16
|
+
"argument '%s', … may not follow optional '%s', …" % [name, @optional.name]
|
17
|
+
end
|
18
|
+
|
19
|
+
# @raise [ArgumentError] If an optional argument has been defined
|
20
|
+
def splus(name, default, description, &validate)
|
21
|
+
raise ArgumentError,
|
22
|
+
"splus '%s', … may not follow optional '%s', …" % [name, @optional.name]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adds a new {Optional} argument to the receiver.
|
26
|
+
# @param (see Undefined#optional)
|
27
|
+
# @yield (see Undefined#optional)
|
28
|
+
# @yieldparam (see Undefined#optional)
|
29
|
+
# @raise (see Undefined#optional)
|
30
|
+
# @return [self]
|
31
|
+
def optional(name, default, description, &validate)
|
32
|
+
self << Ame::Optional.new(name, default, description, &validate)
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# The arguments to a method in its {Method::Undefined undefined state} before
|
4
|
+
# any optional or splat/splus arguments have been defined. When an {#optional}
|
5
|
+
# argument has been added, it’ll enter an {Optional} state where only
|
6
|
+
# additional {#optional} and {#splat} arguments are allowed. When a {#splat}
|
7
|
+
# or {#splus} has been added, it’ll enter a {Complete} state where no
|
8
|
+
# additional arguments are allowed.
|
9
|
+
# @api internal
|
10
|
+
class Ame::Arguments::Undefined
|
11
|
+
def initialize
|
12
|
+
@arguments = []
|
13
|
+
end
|
14
|
+
|
15
|
+
# Adds a new {Argument} to the receiver.
|
16
|
+
# @param (see Argument#initialize)
|
17
|
+
# @yield (see Argument#initialize)
|
18
|
+
# @yieldparam (see Argument#initialize)
|
19
|
+
# @raise (see Argument#initialize)
|
20
|
+
# @return [self]
|
21
|
+
def argument(name, type, description, &validate)
|
22
|
+
self << Ame::Argument.new(name, type, description, &validate)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adds a new {Optional} argument to the receiver.
|
26
|
+
# @param (see Ame::Optional#initialize)
|
27
|
+
# @yield (see Ame::Optional#initialize)
|
28
|
+
# @yieldparam (see Ame::Optional#initialize)
|
29
|
+
# @raise (see Ame::Optional#initialize)
|
30
|
+
# @return [Optional]
|
31
|
+
def optional(name, default, description, &validate)
|
32
|
+
Ame::Arguments::Optional.new(@arguments, Ame::Optional.new(name, default, description, &validate))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Adds a new {Splat} to the receiver.
|
36
|
+
# @param (see Argument#initialize)
|
37
|
+
# @yield (see Argument#initialize)
|
38
|
+
# @yieldparam (see Argument#initialize)
|
39
|
+
# @raise (see Argument#initialize)
|
40
|
+
# @return [Complete]
|
41
|
+
def splat(name, type, description, &validate)
|
42
|
+
Ame::Arguments::Complete.new(@arguments, 'splat', Ame::Splat.new(name, type, description, &validate))
|
43
|
+
end
|
44
|
+
|
45
|
+
# Adds a new {Splus} (required splat) to the receiver.
|
46
|
+
# @param (see Argument#initialize)
|
47
|
+
# @yield (see Argument#initialize)
|
48
|
+
# @yieldparam (see Argument#initialize)
|
49
|
+
# @raise (see Argument#initialize)
|
50
|
+
# @return [Complete]
|
51
|
+
def splus(name, type, description, &validate)
|
52
|
+
Ame::Arguments::Complete.new(@arguments, 'splus', Ame::Splus.new(name, type, description, &validate))
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return True if now arguments have been added to the receiver
|
56
|
+
def empty?
|
57
|
+
@arguments.empty?
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Arguments] The defined version of the receiver
|
61
|
+
def define
|
62
|
+
Ame::Arguments.new(@arguments)
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
def <<(argument)
|
68
|
+
@arguments << argument
|
69
|
+
self
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,436 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# The superclass of a Ruby class that wants to be able to be invoked from the
|
4
|
+
# command line (or with any list of String options and arguments). Subclassed
|
5
|
+
# by {Root}, which should be used as the root of any command-line processing
|
6
|
+
# interface. See {Root} for an example.
|
7
|
+
class Ame::Class
|
8
|
+
class << self
|
9
|
+
# Process ARGUMENTS as a list of options and arguments, then call METHOD
|
10
|
+
# with the results of this processing on a new instance of the receiver.
|
11
|
+
# This method catches {AbortProcessing}. Options are arguments that begin
|
12
|
+
# with `-` or `--`. If {.options_must_precede_arguments} has been called
|
13
|
+
# on the receiver, then options must precede arguments. Either way, a `--`
|
14
|
+
# argument will always end the processing of options and begin processing
|
15
|
+
# of arguments instead.
|
16
|
+
# @param [#to_sym] method
|
17
|
+
# @param [Array<String>] arguments
|
18
|
+
# @raise (see Method#process)
|
19
|
+
# @raise [UnrecognizedMethod] If the method argument to a dispatch isn’t a
|
20
|
+
# known method
|
21
|
+
# @return [self]
|
22
|
+
def process(method, arguments = [])
|
23
|
+
catch Ame::AbortProcessing do
|
24
|
+
methods[method].process new, arguments
|
25
|
+
end
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Call METHOD with ARGUMENTS and OPTIONS on a new instance of the receiver.
|
30
|
+
# This method catches {AbortProcessing}. Options are arguments that begin
|
31
|
+
# with `-` or `--`. If {.options_must_precede_arguments} has been called
|
32
|
+
# on the receiver, then options must precede arguments. Either way, a `--`
|
33
|
+
# argument will always end the processing of options and begin processing
|
34
|
+
# of arguments instead.
|
35
|
+
# @param [#to_sym] method
|
36
|
+
# @param [Array] arguments
|
37
|
+
# @param [Hash<String, Object>] options
|
38
|
+
# @raise (see Method#call)
|
39
|
+
# @raise [UnrecognizedMethod] If the method argument to a dispatch isn’t a
|
40
|
+
# known method
|
41
|
+
# @return [self]
|
42
|
+
def call(method, arguments = nil, options = nil)
|
43
|
+
catch Ame::AbortProcessing do
|
44
|
+
methods[method].call new, arguments, options
|
45
|
+
end
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
# Sets the DESCRIPTION of the method about to be defined, or returns it if
|
50
|
+
# DESCRIPTION is nil. The description is used in help output and similar
|
51
|
+
# circumstances. A description can only be given to a public method, as
|
52
|
+
# only public methods can be used as Ame methods.
|
53
|
+
# @param [String, nil] description
|
54
|
+
# @return [String]
|
55
|
+
# @example Set The Description of the Format-patch Method
|
56
|
+
# class Git::CLI::Git::FormatPatch < Ame::Class
|
57
|
+
# description 'Prepare patches for e-mail submission'
|
58
|
+
# def format_patch
|
59
|
+
def description(description = nil)
|
60
|
+
return method.description(description) if description
|
61
|
+
defined?(@description) ? @description : ''
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# Forces options to the method about to be defined to precede any
|
67
|
+
# arguments, lest they be seen as arguments. If not given, the behaviour
|
68
|
+
# will depend on whether `ENV['POSIXLY_CORRECT']` has been set or not.
|
69
|
+
# @return [self]
|
70
|
+
def options_must_precede_arguments
|
71
|
+
method.options_must_precede_arguments
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
# Defines SHORT and/or LONG as a boolean flag with DEFAULT and DESCRIPTION.
|
76
|
+
# An optional block will be used for any validation or further processing
|
77
|
+
# of the parsed value of the argument, where OPTIONS are the options
|
78
|
+
# processed so far and their values and VALUE is the parsed value itself or
|
79
|
+
# the inverse of DEFAULT, if no argument was passed. An argument isn’t
|
80
|
+
# required, but if one is to be explicitly given, it must be passed as
|
81
|
+
# `-SHORT=ARGUMENT` or `--LONG=ARGUMENT` and may be given as any of “true”,
|
82
|
+
# “yes”, “on” or “false”, “no”, “off”. Multiple short flags may be
|
83
|
+
# juxtaposed as `-abc`.
|
84
|
+
# @param (see Method::Undefined#flag)
|
85
|
+
# @yield (see Method::Undefined#flag)
|
86
|
+
# @yieldparam (see Method::Undefined#flag)
|
87
|
+
# @raise (see Method::Undefined#flag)
|
88
|
+
# @return [self]
|
89
|
+
# @example A Couple of Flags
|
90
|
+
# class Git::CLI::Git::FormatPatch < Ame::Class
|
91
|
+
# flag ?n, 'numbered', false, 'Name output in [PATCH n/m] format'
|
92
|
+
# flag ?N, 'no-numbered', nil, 'Name output in [PATCH] format' do |opt|
|
93
|
+
# opt['numbered'] = false
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
def flag(short, long, default, description, &validate)
|
97
|
+
method.flag short, long, default, description, &validate
|
98
|
+
self
|
99
|
+
rescue; $!.set_backtrace(caller[1..-1]); raise
|
100
|
+
end
|
101
|
+
|
102
|
+
# Defines SHORT and/or LONG as a boolean toggle with DEFAULT and
|
103
|
+
# DESCRIPTION. A toggle acts like a flag, but also supports `--no-LONG`
|
104
|
+
# for explicitly specifying that the inverse of the inverse of the default
|
105
|
+
# should be used. An optional block will be used for any validation or
|
106
|
+
# further processing of the parsed value of the argument, where OPTIONS are
|
107
|
+
# the options processed so far and their values and VALUE is the parsed
|
108
|
+
# value itself or the inverse of DEFAULT, if no argument was passed. An
|
109
|
+
# argument isn’t required, but if one is to be explicitly given, it must be
|
110
|
+
# passed as `-SHORT=ARGUMENT` or `--LONG=ARGUMENT` and may be given as any
|
111
|
+
# of “true”, “yes”, “on” or “false”, “no”, “off”. Multiple short toggles
|
112
|
+
# may be juxtaposed as `-abc`.
|
113
|
+
# @param (see Method::Undefined#toggle)
|
114
|
+
# @yield (see Method::Undefined#toggle)
|
115
|
+
# @yieldparam (see Method::Undefined#toggle)
|
116
|
+
# @raise (see Method::Undefined#toggle)
|
117
|
+
# @return [self]
|
118
|
+
# @example A Toggle
|
119
|
+
# class Git::CLI::Git::FormatPatch < Ame::Class
|
120
|
+
# toggle ?s, 'signoff', false,
|
121
|
+
# 'Add Signed-off-by: line to the commit message'
|
122
|
+
# end
|
123
|
+
def toggle(short, long, default, description, &validate)
|
124
|
+
method.toggle short, long, default, description, &validate
|
125
|
+
self
|
126
|
+
rescue; $!.set_backtrace(caller[1..-1]); raise
|
127
|
+
end
|
128
|
+
|
129
|
+
# Defines SHORT and/or LONG as a switch with DEFAULT taking ARGUMENT with
|
130
|
+
# ARGUMENT_DEFAULT and DESCRIPTION. A switch acts like an option, but the
|
131
|
+
# argument is optional and defaults to ARGUMENT_DEFAULT. An optional block
|
132
|
+
# will be used for any validation or further processing of the parsed value
|
133
|
+
# of the argument, where OPTIONS are the options processed so far and their
|
134
|
+
# values and VALUE is the parsed value itself or ARGUMENT_DEFAULT, if no
|
135
|
+
# argument was passed. An argument must be passed as `-SHORT=ARG`, or
|
136
|
+
# `--LONG=ARG`. Multiple short switches may be juxtaposed as `-abc`. The
|
137
|
+
# type of the argument is determined by the type of ARGUMENT_DEFAULT, or
|
138
|
+
# the type of DEFAULT if ARGUMENT_DEFAULT is nil. If both are nil, String
|
139
|
+
# will be used. Also, if ARGUMENT_DEFAULT’s type responds to \#default,
|
140
|
+
# the result of invoking it will be used as the default value of the
|
141
|
+
# argument. See {Types::Enumeration} for an example of a type that abuses
|
142
|
+
# this fact.
|
143
|
+
# @param (see Method::Undefined#switch)
|
144
|
+
# @yield (see Method::Undefined#switch)
|
145
|
+
# @yieldparam (see Method::Undefined#switch)
|
146
|
+
# @raise (see Method::Undefined#switch)
|
147
|
+
# @return [self]
|
148
|
+
# @example A Switch Using An Enumeration
|
149
|
+
# class Git::CLI::Git::FormatPatch < Ame::Class
|
150
|
+
# switch '', 'thread', 'STYLE', nil,
|
151
|
+
# Ame::Types::Enumeration[:shallow, :deep],
|
152
|
+
# 'Controls addition of In-Reply-To and References headers'
|
153
|
+
# flag '', 'no-thread', nil,
|
154
|
+
# 'Disables addition of In-Reply-To and Reference headers' do |o, _|
|
155
|
+
# o.delete 'thread'
|
156
|
+
# end
|
157
|
+
# end
|
158
|
+
def switch(short, long, argument, default, argument_default, description, &validate)
|
159
|
+
method.switch short, long, argument, default, argument_default, description, &validate
|
160
|
+
self
|
161
|
+
rescue; $!.set_backtrace(caller[1..-1]); raise
|
162
|
+
end
|
163
|
+
|
164
|
+
# Defines SHORT and/or LONG as an option with DEFAULT and DESCRIPTION. An
|
165
|
+
# option always takes an argument. An optional block will be used for any
|
166
|
+
# validation or further processing of the parsed value of the argument,
|
167
|
+
# where OPTIONS are the options processed so far and their values and VALUE
|
168
|
+
# is the parsed value itself. An argument may be passed as
|
169
|
+
# <code>-<em>short</em>ARG</code>, `-SHORT=ARG`, or `--LONG=ARG` or as
|
170
|
+
# `-SHORT ARG` or `--LONG ARG`. Multiple short options can thus not be
|
171
|
+
# juxtaposed, as anything following the short option will be used as an
|
172
|
+
# argument. The type of the argument is determined by the type of DEFAULT,
|
173
|
+
# or String if it’s nil.
|
174
|
+
# @param (see Method::Undefined#option)
|
175
|
+
# @yield (see Method::Undefined#option)
|
176
|
+
# @yieldparam (see Method::Undefined#option)
|
177
|
+
# @raise (see Method::Undefined#option)
|
178
|
+
# @return [self]
|
179
|
+
# @example An Option
|
180
|
+
# class Git::CLI::Git::FormatPatch < Ame::Class
|
181
|
+
# option '', 'start-number', 'N', 1,
|
182
|
+
# 'Start numbering the patches at N instead of 1'
|
183
|
+
# end
|
184
|
+
def option(short, long, argument, default, description, &validate)
|
185
|
+
method.option short, long, argument, default, description, &validate
|
186
|
+
self
|
187
|
+
rescue; $!.set_backtrace(caller[1..-1]); raise
|
188
|
+
end
|
189
|
+
|
190
|
+
# Defines SHORT and/or LONG as a multi-option that takes arguments of TYPE
|
191
|
+
# and has DESCRIPTION. A multi-option always takes an argument and may be
|
192
|
+
# given any number of times. Each parsed value of each argument will be
|
193
|
+
# added to an Array that’s stored in the OPTIONS Hash instead of the usual
|
194
|
+
# atom types used for the other forms of options. An optional block will
|
195
|
+
# be used for any validation or further processing of the parsed value of
|
196
|
+
# the argument, where OPTIONS are the options processed so far and their
|
197
|
+
# values and VALUE is the parsed value itself. An argument may be passed as
|
198
|
+
# <code>-<em>short</em>ARG</code>, `-SHORT=ARG`, or `--LONG=ARG` or as
|
199
|
+
# `-SHORT ARG` or `--long ARG`. Multiple short options can thus not be
|
200
|
+
# juxtaposed, as anything following the short option will be used as an
|
201
|
+
# argument.
|
202
|
+
# @param (see Method::Undefined#multioption)
|
203
|
+
# @yield (see Method::Undefined#multioption)
|
204
|
+
# @yieldparam (see Method::Undefined#multioption)
|
205
|
+
# @raise (see Method::Undefined#multioption)
|
206
|
+
# @return [self]
|
207
|
+
# @example An Option
|
208
|
+
# class Git::CLI::Git::FormatPatch < Ame::Class
|
209
|
+
# multioption '', 'to', 'address', String,
|
210
|
+
# 'Add a To: header to the email headers'
|
211
|
+
# end
|
212
|
+
def multioption(short, long, argument, type, description, &validate)
|
213
|
+
method.multioption short, long, argument, type, description, &validate
|
214
|
+
self
|
215
|
+
rescue; $!.set_backtrace(caller[1..-1]); raise
|
216
|
+
end
|
217
|
+
|
218
|
+
# Defines argument NAME of TYPE with DESCRIPTION. An optional block will
|
219
|
+
# be used for any validation or further processing of the parsed value of
|
220
|
+
# the argument, where OPTIONS are the options processed so far and their
|
221
|
+
# values, PROCESSED are the values of the arguments processed so far, and
|
222
|
+
# VALUE is the parsed value itself.
|
223
|
+
# @param (see Method::Undefined#argument)
|
224
|
+
# @yield (see Method::Undefined#argument)
|
225
|
+
# @yieldparam (see Method::Undefined#argument)
|
226
|
+
# @raise (see Method::Undefined#argument)
|
227
|
+
# @return [self]
|
228
|
+
# @example An Argument
|
229
|
+
# class Git::CLI::Git::Annotate < Ame::Class
|
230
|
+
# argument 'FILE', String, 'File to annotate'
|
231
|
+
# end
|
232
|
+
def argument(name, type, description, &validate)
|
233
|
+
method.argument name, type, description, &validate
|
234
|
+
self
|
235
|
+
rescue; $!.set_backtrace(caller[1..-1]); raise
|
236
|
+
end
|
237
|
+
|
238
|
+
# Defines optional argument NAME with DEFAULT and DESCRIPTION. An optional
|
239
|
+
# block will be used for any validation or further processing of the parsed
|
240
|
+
# value of the argument, where OPTIONS are the options processed so far and
|
241
|
+
# their values, PROCESSED are the values of the arguments processed so far,
|
242
|
+
# and VALUE is the parsed value itself or DEFAULT, if no argument was
|
243
|
+
# given.
|
244
|
+
# @param (see Method::Undefined#optional)
|
245
|
+
# @yield (see Method::Undefined#optional)
|
246
|
+
# @yieldparam (see Method::Undefined#optional)
|
247
|
+
# @raise (see Method::Undefined#optional)
|
248
|
+
# @return [self]
|
249
|
+
# @example An Optional Argument
|
250
|
+
# class Git::CLI::Git::FormatPatch < Ame::Class
|
251
|
+
# optional 'SINCE', String, 'Generate patches for commits after SINCE'
|
252
|
+
# end
|
253
|
+
def optional(name, default, description, &validate)
|
254
|
+
method.optional name, default, description, &validate
|
255
|
+
self
|
256
|
+
rescue; $!.set_backtrace(caller[1..-1]); raise
|
257
|
+
end
|
258
|
+
|
259
|
+
# Defines splat argument NAME of TYPE with DESCRIPTION. A splat argument
|
260
|
+
# may be given zero or more times. An optional block will be used for any
|
261
|
+
# validation or further processing, where OPTIONS are the options processed
|
262
|
+
# so far and their values, PROCESSED are the values of the arguments
|
263
|
+
# processed so far, and ARGUMENT is the parsed value itself.
|
264
|
+
# @param (see Method::Undefined#splat)
|
265
|
+
# @yield (see Method::Undefined#splat)
|
266
|
+
# @yieldparam (see Method::Undefined#splat)
|
267
|
+
# @raise (see Method::Undefined#splat)
|
268
|
+
# @return [self]
|
269
|
+
# @example A Splat Argument
|
270
|
+
# class Git::CLI::Git::Add < Ame::Class
|
271
|
+
# splat 'PATHSPEC', String, 'Files to add content from'
|
272
|
+
# end
|
273
|
+
def splat(name, type, description, &validate)
|
274
|
+
method.splat name, type, description, &validate
|
275
|
+
self
|
276
|
+
rescue; $!.set_backtrace(caller[1..-1]); raise
|
277
|
+
end
|
278
|
+
|
279
|
+
# Defines required splat argument NAME of TYPE with DESCRIPTION. An
|
280
|
+
# optional block will be used for any validation or further processing,
|
281
|
+
# where OPTIONS are the options processed so far and their values,
|
282
|
+
# PROCESSED are the values of the arguments processed so far, and ARGUMENT
|
283
|
+
# is the parsed value itself.
|
284
|
+
# @param (see Method::Undefined#splus)
|
285
|
+
# @yield (see Method::Undefined#splus)
|
286
|
+
# @yieldparam (see Method::Undefined#splus)
|
287
|
+
# @raise (see Method::Undefined#splus)
|
288
|
+
# @return [self]
|
289
|
+
# @example A Splus Argument
|
290
|
+
# class Git::CLI::Git::CheckAttr < Ame::Class
|
291
|
+
# splus 'PATHNAME', String, 'Files to list attributes of'
|
292
|
+
# end
|
293
|
+
def splus(name, type, description, &validate)
|
294
|
+
method.splus name, type, description, &validate
|
295
|
+
self
|
296
|
+
rescue; $!.set_backtrace(caller[1..-1]); raise
|
297
|
+
end
|
298
|
+
|
299
|
+
# Sets up a dispatch method to KLASS. A dispatch method delegates
|
300
|
+
# processing to another class based on the first argument passed to it.
|
301
|
+
# This is useful if you want to support sub-commands in a simple manner.
|
302
|
+
# The description of KLASS will be used as the description of the dispatch
|
303
|
+
# method.
|
304
|
+
# @param [::Class] klass
|
305
|
+
# @param [Hash<String, Object>] options
|
306
|
+
# @option options [String] :default The default method to run; if given,
|
307
|
+
# method argument will be optional
|
308
|
+
# @raise [ArgumentError] If any arguments have been defined on the method
|
309
|
+
# @return [self]
|
310
|
+
# @example A Git-like Command-line Interface
|
311
|
+
# class Git::CLI::Git < Ame::Class
|
312
|
+
# description 'The stupid content tracker'
|
313
|
+
# def initialize; end
|
314
|
+
#
|
315
|
+
# dispatch Remote
|
316
|
+
# end
|
317
|
+
# class Git::CLI::Git::Remote < Ame::Class
|
318
|
+
# description 'Manage set of remote repositories'
|
319
|
+
# def initialize; end
|
320
|
+
#
|
321
|
+
# description 'Adds a remote named NAME for the repository at URL'
|
322
|
+
# argument :name, 'Name of the remote to add'
|
323
|
+
# argument :url, 'URL to the repository of the remote to add'
|
324
|
+
# def add(name, url)
|
325
|
+
# …
|
326
|
+
# end
|
327
|
+
# end
|
328
|
+
def dispatch(klass, options = {})
|
329
|
+
klass.parent = self
|
330
|
+
description klass.description
|
331
|
+
options_must_precede_arguments
|
332
|
+
flag '', 'help', nil, 'Display help for this method' do
|
333
|
+
help.dispatch methods[klass.basename], klass
|
334
|
+
throw Ame::AbortAllProcessing
|
335
|
+
end unless method.option? :help
|
336
|
+
method.arguments? and
|
337
|
+
raise ArgumentError,
|
338
|
+
'arguments may not be defined for a dispatch: %s' % klass
|
339
|
+
if options[:default]
|
340
|
+
optional 'method', options[:default], 'Method to run'
|
341
|
+
else
|
342
|
+
argument 'method', String, 'Method to run'
|
343
|
+
end
|
344
|
+
splat 'arguments', String, 'Arguments to pass to METHOD'
|
345
|
+
define_method Ame::Method.ruby_name(klass.basename) do |method, arguments|
|
346
|
+
klass.process method, arguments
|
347
|
+
end
|
348
|
+
self
|
349
|
+
rescue; $!.set_backtrace(caller[1..-1]); raise
|
350
|
+
end
|
351
|
+
|
352
|
+
# @api developer
|
353
|
+
# @return [Method::Undefined] The undefined method about to be defined
|
354
|
+
def method
|
355
|
+
@method ||= Ame::Method::Undefined.new(self)
|
356
|
+
end
|
357
|
+
|
358
|
+
protected
|
359
|
+
|
360
|
+
# @api developer
|
361
|
+
# @return [Class] The parent of the receiver
|
362
|
+
attr_accessor :parent
|
363
|
+
|
364
|
+
public
|
365
|
+
|
366
|
+
# Sets the HELP object to use for displaying usage information, or returns
|
367
|
+
# it if HELP is nil. The default is to delegate the request to the
|
368
|
+
# {.parent}.
|
369
|
+
# @api developer
|
370
|
+
# @param [#method, #dispatch, #error, #version] help
|
371
|
+
# @return [#method, #dispatch, #error, #version]
|
372
|
+
def help(help = nil)
|
373
|
+
return @help = help if help
|
374
|
+
@help ||= Ame::Help::Delegate.new(parent.help)
|
375
|
+
end
|
376
|
+
|
377
|
+
# Sets or returns, depending on if BASENAME is nil or not, the basename
|
378
|
+
# of the receiver. The basename is the downcased last component of the
|
379
|
+
# double-colon-separated name of the class with all camel-cased sub-words
|
380
|
+
# separated by dashes.
|
381
|
+
# @api developer
|
382
|
+
# @example Basename of A::B::CToTheD
|
383
|
+
# class A::B::CToTheD < Ame::Class; end
|
384
|
+
# A::B::CToTheD.basename # ⇒ "c-to-the-d"
|
385
|
+
# @param [String, nil] basename
|
386
|
+
# @return [String]
|
387
|
+
def basename(basename = nil)
|
388
|
+
@basename = basename if basename
|
389
|
+
return @basename if defined? @basename
|
390
|
+
name.split('::').last.scan(/[[:upper:]][[:lower:]]*/).join('-').downcase
|
391
|
+
end
|
392
|
+
|
393
|
+
# @api developer
|
394
|
+
# @return [String] The full name of the space-separated concatenation of
|
395
|
+
# the basenames of the receiver and its {.parent}s
|
396
|
+
# @example Fullname of A::B::CToTheD
|
397
|
+
# class A::B::CToTheD < Ame::Class; end
|
398
|
+
# A::B::CToTheD.fullname # ⇒ "a b c-to-the-d"
|
399
|
+
def fullname
|
400
|
+
[].tap{ |names|
|
401
|
+
klass = self
|
402
|
+
until klass.nil? or klass.basename.empty?
|
403
|
+
names << klass.basename
|
404
|
+
klass = klass.parent
|
405
|
+
end
|
406
|
+
}.reverse.join(' ')
|
407
|
+
end
|
408
|
+
|
409
|
+
# @api developer
|
410
|
+
# @return [Methods] The methods defined on the receiver
|
411
|
+
def methods
|
412
|
+
@methods ||= Ame::Methods.new
|
413
|
+
end
|
414
|
+
|
415
|
+
private
|
416
|
+
|
417
|
+
# Defines the previously undefined {.method} now that it’s been added to
|
418
|
+
# the class.
|
419
|
+
# @api internal
|
420
|
+
# @param [Symbol] ruby_name
|
421
|
+
# @raise [ArgumentError] If RUBY_NAME is the name of a non-public method
|
422
|
+
# that’s being defined
|
423
|
+
# @return [self]
|
424
|
+
def method_added(ruby_name)
|
425
|
+
if ruby_name == :initialize
|
426
|
+
@description = method.define(ruby_name).description
|
427
|
+
elsif public_method_defined? ruby_name
|
428
|
+
methods << method.define(ruby_name)
|
429
|
+
elsif method.valid?
|
430
|
+
raise ArgumentError, 'non-public method cannot be used by Ame: %s' % ruby_name, caller
|
431
|
+
end
|
432
|
+
@method = nil
|
433
|
+
self
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|