ame 0.1.1 → 1.0.1
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.
- 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
|