clamp 1.2.1 → 1.3.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +12 -4
- data/.travis.yml +4 -6
- data/CHANGES.md +7 -0
- data/Gemfile +8 -6
- data/Guardfile +3 -1
- data/Rakefile +8 -0
- data/clamp.gemspec +8 -6
- data/examples/admin +3 -2
- data/examples/defaulted +4 -3
- data/examples/flipflop +1 -0
- data/examples/fubar +1 -0
- data/examples/gitdown +2 -1
- data/examples/scoop +3 -2
- data/examples/speak +3 -2
- data/examples/subcommand_missing +1 -0
- data/examples/word +1 -0
- data/lib/clamp.rb +3 -1
- data/lib/clamp/attribute/declaration.rb +5 -0
- data/lib/clamp/attribute/definition.rb +15 -12
- data/lib/clamp/attribute/instance.rb +5 -3
- data/lib/clamp/command.rb +9 -1
- data/lib/clamp/errors.rb +7 -3
- data/lib/clamp/help.rb +8 -6
- data/lib/clamp/messages.rb +21 -14
- data/lib/clamp/option/declaration.rb +4 -0
- data/lib/clamp/option/definition.rb +9 -3
- data/lib/clamp/option/parsing.rb +37 -32
- data/lib/clamp/parameter/declaration.rb +4 -0
- data/lib/clamp/parameter/definition.rb +9 -3
- data/lib/clamp/parameter/parsing.rb +5 -1
- data/lib/clamp/subcommand/declaration.rb +15 -13
- data/lib/clamp/subcommand/definition.rb +2 -0
- data/lib/clamp/subcommand/execution.rb +11 -0
- data/lib/clamp/subcommand/parsing.rb +4 -0
- data/lib/clamp/truthy.rb +4 -2
- data/lib/clamp/version.rb +3 -1
- data/spec/clamp/command_group_spec.rb +27 -9
- data/spec/clamp/command_spec.rb +84 -49
- data/spec/clamp/messages_spec.rb +5 -4
- data/spec/clamp/option/definition_spec.rb +13 -11
- data/spec/clamp/option_module_spec.rb +3 -1
- data/spec/clamp/option_reordering_spec.rb +6 -4
- data/spec/clamp/parameter/definition_spec.rb +14 -12
- data/spec/spec_helper.rb +3 -3
- metadata +4 -4
data/lib/clamp/errors.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Clamp
|
2
4
|
|
5
|
+
# raised to indicate invalid option/parameter declaration
|
3
6
|
class DeclarationError < StandardError
|
4
7
|
end
|
5
8
|
|
9
|
+
# abstract command runtime error
|
6
10
|
class RuntimeError < StandardError
|
7
11
|
|
8
12
|
def initialize(message, command)
|
@@ -14,10 +18,10 @@ module Clamp
|
|
14
18
|
|
15
19
|
end
|
16
20
|
|
17
|
-
#
|
21
|
+
# raised to signal incorrect command usage
|
18
22
|
class UsageError < RuntimeError; end
|
19
23
|
|
20
|
-
#
|
24
|
+
# raised to request usage help
|
21
25
|
class HelpWanted < RuntimeError
|
22
26
|
|
23
27
|
def initialize(command)
|
@@ -26,7 +30,7 @@ module Clamp
|
|
26
30
|
|
27
31
|
end
|
28
32
|
|
29
|
-
#
|
33
|
+
# raised to signal error during execution
|
30
34
|
class ExecutionError < RuntimeError
|
31
35
|
|
32
36
|
def initialize(message, command, status = 1)
|
data/lib/clamp/help.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "stringio"
|
2
4
|
require "clamp/messages"
|
3
5
|
|
4
6
|
module Clamp
|
5
7
|
|
8
|
+
# Command help generation.
|
9
|
+
#
|
6
10
|
module Help
|
7
11
|
|
8
12
|
def usage(usage)
|
@@ -41,16 +45,14 @@ module Clamp
|
|
41
45
|
help = builder
|
42
46
|
help.add_usage(invocation_path, usage_descriptions)
|
43
47
|
help.add_description(description)
|
44
|
-
if has_parameters?
|
45
|
-
|
46
|
-
end
|
47
|
-
if has_subcommands?
|
48
|
-
help.add_list(Clamp.message(:subcommands_heading), recognised_subcommands)
|
49
|
-
end
|
48
|
+
help.add_list(Clamp.message(:parameters_heading), parameters) if has_parameters?
|
49
|
+
help.add_list(Clamp.message(:subcommands_heading), recognised_subcommands) if has_subcommands?
|
50
50
|
help.add_list(Clamp.message(:options_heading), recognised_options)
|
51
51
|
help.string
|
52
52
|
end
|
53
53
|
|
54
|
+
# A builder for auto-generated help.
|
55
|
+
#
|
54
56
|
class Builder
|
55
57
|
|
56
58
|
def initialize
|
data/lib/clamp/messages.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Clamp #:nodoc:
|
4
|
+
|
5
|
+
# Message lookup, to allow localization.
|
6
|
+
#
|
3
7
|
module Messages
|
4
8
|
|
5
9
|
def messages=(new_messages)
|
@@ -17,19 +21,22 @@ module Clamp
|
|
17
21
|
private
|
18
22
|
|
19
23
|
DEFAULTS = {
|
20
|
-
:
|
21
|
-
:
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:
|
25
|
-
:
|
26
|
-
:
|
27
|
-
:
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
:
|
24
|
+
too_many_arguments: "too many arguments",
|
25
|
+
option_required: "option '%<option>s' is required",
|
26
|
+
option_or_env_required: "option '%<option>s' (or env %<env>s) is required",
|
27
|
+
option_argument_error: "option '%<switch>s': %<message>s",
|
28
|
+
parameter_argument_error: "parameter '%<param>s': %<message>s",
|
29
|
+
env_argument_error: "$%<env>s: %<message>s",
|
30
|
+
unrecognised_option: "Unrecognised option '%<switch>s'",
|
31
|
+
no_such_subcommand: "No such sub-command '%<name>s'",
|
32
|
+
no_value_provided: "no value provided",
|
33
|
+
default: "default",
|
34
|
+
or: "or",
|
35
|
+
required: "required",
|
36
|
+
usage_heading: "Usage",
|
37
|
+
parameters_heading: "Parameters",
|
38
|
+
subcommands_heading: "Subcommands",
|
39
|
+
options_heading: "Options"
|
33
40
|
}.freeze
|
34
41
|
|
35
42
|
def messages
|
@@ -1,9 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "clamp/attribute/definition"
|
2
4
|
require "clamp/truthy"
|
3
5
|
|
4
6
|
module Clamp
|
5
7
|
module Option
|
6
8
|
|
9
|
+
# Represents an option of a Clamp::Command class.
|
10
|
+
#
|
7
11
|
class Definition < Attribute::Definition
|
8
12
|
|
9
13
|
def initialize(switches, type, description, options = {})
|
@@ -76,14 +80,16 @@ module Clamp
|
|
76
80
|
end
|
77
81
|
|
78
82
|
def infer_attribute_name
|
79
|
-
unless long_switch
|
80
|
-
raise Clamp::DeclarationError, "You must specify either a long-switch or an :attribute_value"
|
81
|
-
end
|
83
|
+
raise Clamp::DeclarationError, "You must specify either a long-switch or an :attribute_value" unless long_switch
|
82
84
|
inferred_name = long_switch.sub(/^--(\[no-\])?/, "").tr("-", "_")
|
83
85
|
inferred_name += "_list" if multivalued?
|
84
86
|
inferred_name
|
85
87
|
end
|
86
88
|
|
89
|
+
def required_indicator
|
90
|
+
Clamp.message(:required) if required?
|
91
|
+
end
|
92
|
+
|
87
93
|
end
|
88
94
|
|
89
95
|
end
|
data/lib/clamp/option/parsing.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Clamp #:nodoc:
|
2
4
|
|
3
5
|
class << self
|
6
|
+
|
4
7
|
attr_accessor :allow_options_after_parameters
|
8
|
+
|
5
9
|
end
|
6
10
|
|
7
11
|
module Option
|
8
12
|
|
13
|
+
# Option parsing methods.
|
14
|
+
#
|
9
15
|
module Parsing
|
10
16
|
|
11
17
|
protected
|
@@ -13,7 +19,6 @@ module Clamp
|
|
13
19
|
def parse_options
|
14
20
|
set_options_from_command_line
|
15
21
|
default_options_from_environment
|
16
|
-
verify_required_options_are_set
|
17
22
|
end
|
18
23
|
|
19
24
|
private
|
@@ -22,43 +27,43 @@ module Clamp
|
|
22
27
|
argument_buffer = []
|
23
28
|
argument_buffer_limit = self.class.parameter_buffer_limit
|
24
29
|
until remaining_arguments.empty?
|
25
|
-
|
26
30
|
unless remaining_arguments.first.start_with?("-")
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
else
|
31
|
-
break
|
32
|
-
end
|
31
|
+
break unless argument_buffer.size < argument_buffer_limit
|
32
|
+
argument_buffer << remaining_arguments.shift
|
33
|
+
next
|
33
34
|
end
|
34
|
-
|
35
35
|
switch = remaining_arguments.shift
|
36
36
|
break if switch == "--"
|
37
|
+
handle_switch(switch)
|
38
|
+
end
|
39
|
+
remaining_arguments.unshift(*argument_buffer)
|
40
|
+
end
|
37
41
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
remaining_arguments.unshift(Regexp.last_match(2))
|
49
|
-
end
|
50
|
-
|
51
|
-
option = find_option(switch)
|
52
|
-
value = option.extract_value(switch, remaining_arguments)
|
42
|
+
def handle_switch(switch)
|
43
|
+
switch = split_trailing_switches(switch)
|
44
|
+
option = find_option(switch)
|
45
|
+
value = option.extract_value(switch, remaining_arguments)
|
46
|
+
begin
|
47
|
+
option.of(self).take(value)
|
48
|
+
rescue ArgumentError => e
|
49
|
+
signal_usage_error Clamp.message(:option_argument_error, switch: switch, message: e.message)
|
50
|
+
end
|
51
|
+
end
|
53
52
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
def split_trailing_switches(switch)
|
54
|
+
case switch
|
55
|
+
when /\A(-\w)(.+)\z/m # combined short options
|
56
|
+
switch = Regexp.last_match(1)
|
57
|
+
if find_option(switch).flag?
|
58
|
+
remaining_arguments.unshift("-" + Regexp.last_match(2))
|
59
|
+
else
|
60
|
+
remaining_arguments.unshift(Regexp.last_match(2))
|
58
61
|
end
|
59
|
-
|
62
|
+
when /\A(--[^=]+)=(.*)\z/m
|
63
|
+
switch = Regexp.last_match(1)
|
64
|
+
remaining_arguments.unshift(Regexp.last_match(2))
|
60
65
|
end
|
61
|
-
|
66
|
+
switch
|
62
67
|
end
|
63
68
|
|
64
69
|
def default_options_from_environment
|
@@ -75,7 +80,7 @@ module Clamp
|
|
75
80
|
|
76
81
|
def find_option(switch)
|
77
82
|
self.class.find_option(switch) ||
|
78
|
-
signal_usage_error(Clamp.message(:unrecognised_option, :
|
83
|
+
signal_usage_error(Clamp.message(:unrecognised_option, switch: switch))
|
79
84
|
end
|
80
85
|
|
81
86
|
end
|
@@ -1,8 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "clamp/attribute/definition"
|
2
4
|
|
3
5
|
module Clamp
|
4
6
|
module Parameter
|
5
7
|
|
8
|
+
# Represents an parameter of a Clamp::Command class.
|
9
|
+
#
|
6
10
|
class Definition < Attribute::Definition
|
7
11
|
|
8
12
|
def initialize(name, description, options = {})
|
@@ -40,13 +44,15 @@ module Clamp
|
|
40
44
|
|
41
45
|
def infer_attribute_name
|
42
46
|
inferred_name = name.downcase.tr("-", "_").sub(ELLIPSIS_SUFFIX, "").sub(OPTIONAL) { Regexp.last_match(1) }
|
43
|
-
unless inferred_name =~ VALID_ATTRIBUTE_NAME
|
44
|
-
raise "cannot infer attribute_name from #{name.inspect}"
|
45
|
-
end
|
47
|
+
raise "cannot infer attribute_name from #{name.inspect}" unless inferred_name =~ VALID_ATTRIBUTE_NAME
|
46
48
|
inferred_name += "_list" if multivalued?
|
47
49
|
inferred_name
|
48
50
|
end
|
49
51
|
|
52
|
+
def required_indicator
|
53
|
+
# implied by LHS
|
54
|
+
end
|
55
|
+
|
50
56
|
end
|
51
57
|
|
52
58
|
end
|
@@ -1,6 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Clamp
|
2
4
|
module Parameter
|
3
5
|
|
6
|
+
# Parameter parsing methods.
|
7
|
+
#
|
4
8
|
module Parsing
|
5
9
|
|
6
10
|
protected
|
@@ -19,7 +23,7 @@ module Clamp
|
|
19
23
|
parameter.of(self).take(value)
|
20
24
|
end
|
21
25
|
rescue ArgumentError => e
|
22
|
-
signal_usage_error Clamp.message(:parameter_argument_error, :
|
26
|
+
signal_usage_error Clamp.message(:parameter_argument_error, param: parameter.name, message: e.message)
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
@@ -1,9 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "clamp/errors"
|
2
4
|
require "clamp/subcommand/definition"
|
3
5
|
|
4
6
|
module Clamp
|
5
7
|
module Subcommand
|
6
8
|
|
9
|
+
# Subcommand declaration methods.
|
10
|
+
#
|
7
11
|
module Declaration
|
8
12
|
|
9
13
|
def recognised_subcommands
|
@@ -37,9 +41,7 @@ module Clamp
|
|
37
41
|
end
|
38
42
|
|
39
43
|
def default_subcommand=(name)
|
40
|
-
if has_subcommands?
|
41
|
-
raise Clamp::DeclarationError, "default_subcommand must be defined before subcommands"
|
42
|
-
end
|
44
|
+
raise Clamp::DeclarationError, "default_subcommand must be defined before subcommands" if has_subcommands?
|
43
45
|
@default_subcommand = name
|
44
46
|
end
|
45
47
|
|
@@ -48,7 +50,7 @@ module Clamp
|
|
48
50
|
@default_subcommand
|
49
51
|
else
|
50
52
|
$stderr.puts "WARNING: Clamp default_subcommand syntax has changed; check the README."
|
51
|
-
$stderr.puts " (from #{caller.first})"
|
53
|
+
$stderr.puts " (from #{caller(1..1).first})"
|
52
54
|
self.default_subcommand = args.first
|
53
55
|
subcommand(*args, &block)
|
54
56
|
end
|
@@ -59,19 +61,19 @@ module Clamp
|
|
59
61
|
def declare_subcommand_parameters
|
60
62
|
if @default_subcommand
|
61
63
|
parameter "[SUBCOMMAND]", "subcommand",
|
62
|
-
:
|
63
|
-
:
|
64
|
-
:
|
64
|
+
attribute_name: :subcommand_name,
|
65
|
+
default: @default_subcommand,
|
66
|
+
inheritable: false
|
65
67
|
else
|
66
68
|
parameter "SUBCOMMAND", "subcommand",
|
67
|
-
:
|
68
|
-
:
|
69
|
-
:
|
69
|
+
attribute_name: :subcommand_name,
|
70
|
+
required: false,
|
71
|
+
inheritable: false
|
70
72
|
end
|
71
|
-
remove_method :default_subcommand_name
|
73
|
+
remove_method :default_subcommand_name if method_defined?(:default_subcommand_name)
|
72
74
|
parameter "[ARG] ...", "subcommand arguments",
|
73
|
-
:
|
74
|
-
:
|
75
|
+
attribute_name: :subcommand_arguments,
|
76
|
+
inheritable: false
|
75
77
|
end
|
76
78
|
|
77
79
|
end
|
@@ -1,6 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Clamp
|
2
4
|
module Subcommand
|
3
5
|
|
6
|
+
# Support for subcommand execution.
|
7
|
+
#
|
8
|
+
# This module is mixed into command instances that have subcommands, overriding
|
9
|
+
# default behaviour in {Clamp::Command}.
|
10
|
+
#
|
4
11
|
module Execution
|
5
12
|
|
6
13
|
# override default Command behaviour
|
@@ -34,6 +41,10 @@ module Clamp
|
|
34
41
|
subcommand_missing(name)
|
35
42
|
end
|
36
43
|
|
44
|
+
def verify_required_options_are_set
|
45
|
+
# not required
|
46
|
+
end
|
47
|
+
|
37
48
|
end
|
38
49
|
|
39
50
|
end
|
data/lib/clamp/truthy.rb
CHANGED