clamp 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +60 -0
- data/.travis.yml +3 -5
- data/CHANGES.md +6 -0
- data/Gemfile +3 -2
- data/Guardfile +1 -1
- data/README.md +14 -1
- data/Rakefile +2 -2
- data/clamp.gemspec +3 -4
- data/examples/defaulted +1 -1
- data/examples/scoop +2 -2
- data/examples/speak +2 -4
- data/examples/subcommand_missing +18 -0
- data/lib/clamp.rb +2 -2
- data/lib/clamp/attribute/declaration.rb +3 -1
- data/lib/clamp/attribute/definition.rb +6 -12
- data/lib/clamp/attribute/instance.rb +1 -1
- data/lib/clamp/command.rb +33 -35
- data/lib/clamp/help.rb +11 -12
- data/lib/clamp/messages.rb +5 -28
- data/lib/clamp/option/declaration.rb +20 -19
- data/lib/clamp/option/definition.rb +11 -18
- data/lib/clamp/option/parsing.rb +27 -18
- data/lib/clamp/parameter/declaration.rb +16 -3
- data/lib/clamp/parameter/definition.rb +7 -2
- data/lib/clamp/parameter/parsing.rb +8 -1
- data/lib/clamp/subcommand/declaration.rb +28 -25
- data/lib/clamp/subcommand/definition.rb +1 -1
- data/lib/clamp/subcommand/execution.rb +7 -7
- data/lib/clamp/subcommand/parsing.rb +2 -2
- data/lib/clamp/truthy.rb +1 -1
- data/lib/clamp/version.rb +1 -1
- data/spec/clamp/command_group_spec.rb +89 -23
- data/spec/clamp/command_spec.rb +20 -21
- data/spec/clamp/messages_spec.rb +1 -1
- data/spec/clamp/option/definition_spec.rb +5 -5
- data/spec/clamp/option_module_spec.rb +1 -1
- data/spec/clamp/parameter/definition_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -3
- metadata +5 -3
data/lib/clamp/help.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "stringio"
|
2
|
+
require "clamp/messages"
|
3
3
|
|
4
4
|
module Clamp
|
5
5
|
|
@@ -15,8 +15,8 @@ module Clamp
|
|
15
15
|
def description=(description)
|
16
16
|
@description = description.dup
|
17
17
|
if @description =~ /^\A\n*( +)/
|
18
|
-
indent =
|
19
|
-
@description.gsub!(/^#{indent}/,
|
18
|
+
indent = Regexp.last_match(1)
|
19
|
+
@description.gsub!(/^#{indent}/, "")
|
20
20
|
end
|
21
21
|
@description.strip!
|
22
22
|
end
|
@@ -29,7 +29,7 @@ module Clamp
|
|
29
29
|
|
30
30
|
def derived_usage_description
|
31
31
|
parts = ["[OPTIONS]"]
|
32
|
-
parts += parameters.map
|
32
|
+
parts += parameters.map(&:name)
|
33
33
|
parts.join(" ")
|
34
34
|
end
|
35
35
|
|
@@ -69,21 +69,20 @@ module Clamp
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def add_description(description)
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
72
|
+
return unless description
|
73
|
+
puts ""
|
74
|
+
puts description.gsub(/^/, " ")
|
76
75
|
end
|
77
76
|
|
78
|
-
DETAIL_FORMAT = " %-29s %s"
|
77
|
+
DETAIL_FORMAT = " %-29s %s".freeze
|
79
78
|
|
80
79
|
def add_list(heading, items)
|
81
80
|
puts "\n#{heading}:"
|
82
81
|
items.reject { |i| i.respond_to?(:hidden?) && i.hidden? }.each do |item|
|
83
82
|
label, description = item.help
|
84
83
|
description.each_line do |line|
|
85
|
-
puts DETAIL_FORMAT
|
86
|
-
label =
|
84
|
+
puts format(DETAIL_FORMAT, label, line)
|
85
|
+
label = ""
|
87
86
|
end
|
88
87
|
end
|
89
88
|
end
|
data/lib/clamp/messages.rb
CHANGED
@@ -6,8 +6,8 @@ module Clamp
|
|
6
6
|
messages.merge!(new_messages)
|
7
7
|
end
|
8
8
|
|
9
|
-
def message(key, options={})
|
10
|
-
|
9
|
+
def message(key, options = {})
|
10
|
+
format(messages.fetch(key), options)
|
11
11
|
end
|
12
12
|
|
13
13
|
def clear_messages!
|
@@ -30,38 +30,15 @@ module Clamp
|
|
30
30
|
:parameters_heading => "Parameters",
|
31
31
|
:subcommands_heading => "Subcommands",
|
32
32
|
:options_heading => "Options"
|
33
|
-
}
|
33
|
+
}.freeze
|
34
34
|
|
35
35
|
def messages
|
36
|
-
unless defined?(@messages)
|
37
|
-
init_default_messages
|
38
|
-
end
|
36
|
+
init_default_messages unless defined?(@messages)
|
39
37
|
@messages
|
40
38
|
end
|
41
39
|
|
42
40
|
def init_default_messages
|
43
|
-
@messages = DEFAULTS.
|
44
|
-
end
|
45
|
-
|
46
|
-
begin
|
47
|
-
|
48
|
-
("%{foo}" % {:foo => "bar"}) # test Ruby 1.9 string interpolation
|
49
|
-
|
50
|
-
def format_string(format, params = {})
|
51
|
-
format % params
|
52
|
-
end
|
53
|
-
|
54
|
-
rescue ArgumentError
|
55
|
-
|
56
|
-
# string formatting for ruby 1.8
|
57
|
-
def format_string(format, params = {})
|
58
|
-
array_params = format.scan(/%[<{]([^>}]*)[>}]/).collect do |name|
|
59
|
-
name = name[0]
|
60
|
-
params[name.to_s] || params[name.to_sym]
|
61
|
-
end
|
62
|
-
format.gsub(/%[<]([^>]*)[>]/, '%').gsub(/%[{]([^}]*)[}]/, '%s') % array_params
|
63
|
-
end
|
64
|
-
|
41
|
+
@messages = DEFAULTS.dup
|
65
42
|
end
|
66
43
|
|
67
44
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "clamp/attribute/declaration"
|
2
|
+
require "clamp/option/definition"
|
3
3
|
|
4
4
|
module Clamp
|
5
5
|
module Option
|
@@ -10,9 +10,9 @@ module Clamp
|
|
10
10
|
|
11
11
|
def option(switches, type, description, opts = {}, &block)
|
12
12
|
Option::Definition.new(switches, type, description, opts).tap do |option|
|
13
|
-
declared_options << option
|
14
13
|
block ||= option.default_conversion_block
|
15
|
-
|
14
|
+
declare_attribute(option, &block)
|
15
|
+
declared_options << option
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -25,34 +25,35 @@ module Clamp
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def recognised_options
|
28
|
-
|
28
|
+
unless @implicit_options_declared
|
29
|
+
declare_implicit_help_option
|
30
|
+
@implicit_options_declared = true
|
31
|
+
end
|
29
32
|
effective_options
|
30
33
|
end
|
31
34
|
|
32
35
|
private
|
33
36
|
|
34
|
-
def
|
35
|
-
return
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
request_help
|
41
|
-
end
|
37
|
+
def declare_implicit_help_option
|
38
|
+
return false if effective_options.find { |o| o.handles?("--help") }
|
39
|
+
help_switches = ["--help"]
|
40
|
+
help_switches.unshift("-h") unless effective_options.find { |o| o.handles?("-h") }
|
41
|
+
option help_switches, :flag, "print help" do
|
42
|
+
request_help
|
42
43
|
end
|
43
|
-
@implicit_options_declared = true
|
44
44
|
end
|
45
45
|
|
46
46
|
def effective_options
|
47
47
|
ancestors.inject([]) do |options, ancestor|
|
48
|
-
|
49
|
-
options + ancestor.declared_options
|
50
|
-
else
|
51
|
-
options
|
52
|
-
end
|
48
|
+
options + options_declared_on(ancestor)
|
53
49
|
end
|
54
50
|
end
|
55
51
|
|
52
|
+
def options_declared_on(ancestor)
|
53
|
+
return [] unless ancestor.is_a?(Clamp::Option::Declaration)
|
54
|
+
ancestor.declared_options
|
55
|
+
end
|
56
|
+
|
56
57
|
end
|
57
58
|
|
58
59
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "clamp/attribute/definition"
|
2
|
+
require "clamp/truthy"
|
3
3
|
|
4
4
|
module Clamp
|
5
5
|
module Option
|
@@ -12,16 +12,11 @@ module Clamp
|
|
12
12
|
@description = description
|
13
13
|
super(options)
|
14
14
|
@multivalued = options[:multivalued]
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
if type == :flag
|
22
|
-
raise ArgumentError, "A required flag (boolean) doesn't make sense."
|
23
|
-
end
|
24
|
-
end
|
15
|
+
return unless options.key?(:required)
|
16
|
+
@required = options[:required]
|
17
|
+
# Do some light validation for conflicting settings.
|
18
|
+
raise ArgumentError, "Specifying a :default value with :required doesn't make sense" if options.key?(:default)
|
19
|
+
raise ArgumentError, "A required flag (boolean) doesn't make sense." if type == :flag
|
25
20
|
end
|
26
21
|
|
27
22
|
attr_reader :switches, :type
|
@@ -39,7 +34,7 @@ module Clamp
|
|
39
34
|
end
|
40
35
|
|
41
36
|
def flag_value(switch)
|
42
|
-
!(switch =~ /^--no-(.*)/ && switches.member?("--\[no-\]#{
|
37
|
+
!(switch =~ /^--no-(.*)/ && switches.member?("--\[no-\]#{Regexp.last_match(1)}"))
|
43
38
|
end
|
44
39
|
|
45
40
|
def read_method
|
@@ -59,9 +54,7 @@ module Clamp
|
|
59
54
|
end
|
60
55
|
|
61
56
|
def default_conversion_block
|
62
|
-
if flag?
|
63
|
-
Clamp.method(:truthy?)
|
64
|
-
end
|
57
|
+
Clamp.method(:truthy?) if flag?
|
65
58
|
end
|
66
59
|
|
67
60
|
def help_lhs
|
@@ -75,7 +68,7 @@ module Clamp
|
|
75
68
|
def recognised_switches
|
76
69
|
switches.map do |switch|
|
77
70
|
if switch =~ /^--\[no-\](.*)/
|
78
|
-
["--#{
|
71
|
+
["--#{Regexp.last_match(1)}", "--no-#{Regexp.last_match(1)}"]
|
79
72
|
else
|
80
73
|
switch
|
81
74
|
end
|
@@ -86,7 +79,7 @@ module Clamp
|
|
86
79
|
unless long_switch
|
87
80
|
raise Clamp::DeclarationError, "You must specify either a long-switch or an :attribute_value"
|
88
81
|
end
|
89
|
-
inferred_name = long_switch.sub(/^--(\[no-\])?/,
|
82
|
+
inferred_name = long_switch.sub(/^--(\[no-\])?/, "").tr("-", "_")
|
90
83
|
inferred_name += "_list" if multivalued?
|
91
84
|
inferred_name
|
92
85
|
end
|
data/lib/clamp/option/parsing.rb
CHANGED
@@ -6,23 +6,30 @@ module Clamp
|
|
6
6
|
protected
|
7
7
|
|
8
8
|
def parse_options
|
9
|
+
set_options_from_command_line
|
10
|
+
default_options_from_environment
|
11
|
+
verify_required_options_are_set
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
9
15
|
|
10
|
-
|
16
|
+
def set_options_from_command_line
|
17
|
+
while remaining_arguments.first && remaining_arguments.first.start_with?("-")
|
11
18
|
|
12
19
|
switch = remaining_arguments.shift
|
13
20
|
break if switch == "--"
|
14
21
|
|
15
22
|
case switch
|
16
23
|
when /\A(-\w)(.+)\z/m # combined short options
|
17
|
-
switch =
|
24
|
+
switch = Regexp.last_match(1)
|
18
25
|
if find_option(switch).flag?
|
19
|
-
remaining_arguments.unshift("-" +
|
26
|
+
remaining_arguments.unshift("-" + Regexp.last_match(2))
|
20
27
|
else
|
21
|
-
remaining_arguments.unshift(
|
28
|
+
remaining_arguments.unshift(Regexp.last_match(2))
|
22
29
|
end
|
23
30
|
when /\A(--[^=]+)=(.*)\z/m
|
24
|
-
switch =
|
25
|
-
remaining_arguments.unshift(
|
31
|
+
switch = Regexp.last_match(1)
|
32
|
+
remaining_arguments.unshift(Regexp.last_match(2))
|
26
33
|
end
|
27
34
|
|
28
35
|
option = find_option(switch)
|
@@ -35,31 +42,33 @@ module Clamp
|
|
35
42
|
end
|
36
43
|
|
37
44
|
end
|
45
|
+
end
|
38
46
|
|
39
|
-
|
47
|
+
def default_options_from_environment
|
40
48
|
self.class.recognised_options.each do |option|
|
41
49
|
option.of(self).default_from_environment
|
42
50
|
end
|
51
|
+
end
|
43
52
|
|
44
|
-
|
53
|
+
def verify_required_options_are_set
|
45
54
|
self.class.recognised_options.each do |option|
|
46
55
|
# If this option is required and the value is nil, there's an error.
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
56
|
+
next unless option.required? && send(option.attribute_name).nil?
|
57
|
+
if option.environment_variable
|
58
|
+
message = Clamp.message(:option_or_env_required,
|
59
|
+
:option => option.switches.first,
|
60
|
+
:env => option.environment_variable)
|
61
|
+
else
|
62
|
+
message = Clamp.message(:option_required,
|
63
|
+
:option => option.switches.first)
|
54
64
|
end
|
65
|
+
signal_usage_error message
|
55
66
|
end
|
56
67
|
end
|
57
68
|
|
58
|
-
private
|
59
|
-
|
60
69
|
def find_option(switch)
|
61
70
|
self.class.find_option(switch) ||
|
62
|
-
|
71
|
+
signal_usage_error(Clamp.message(:unrecognised_option, :switch => switch))
|
63
72
|
end
|
64
73
|
|
65
74
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "clamp/attribute/declaration"
|
2
|
+
require "clamp/parameter/definition"
|
3
3
|
|
4
4
|
module Clamp
|
5
5
|
module Parameter
|
@@ -18,11 +18,24 @@ module Clamp
|
|
18
18
|
|
19
19
|
def parameter(name, description, options = {}, &block)
|
20
20
|
Parameter::Definition.new(name, description, options).tap do |parameter|
|
21
|
+
declare_attribute(parameter, &block)
|
21
22
|
parameters << parameter
|
22
|
-
define_accessors_for(parameter, &block)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
protected
|
27
|
+
|
28
|
+
def inheritable_parameters
|
29
|
+
superclass_inheritable_parameters + parameters.select(&:inheritable?)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def superclass_inheritable_parameters
|
35
|
+
return [] unless superclass.respond_to?(:inheritable_parameters, true)
|
36
|
+
superclass.inheritable_parameters
|
37
|
+
end
|
38
|
+
|
26
39
|
end
|
27
40
|
|
28
41
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "clamp/attribute/definition"
|
2
2
|
|
3
3
|
module Clamp
|
4
4
|
module Parameter
|
@@ -13,10 +13,15 @@ module Clamp
|
|
13
13
|
@required = options.fetch(:required) do
|
14
14
|
(@name !~ OPTIONAL)
|
15
15
|
end
|
16
|
+
@inheritable = options.fetch(:inheritable, true)
|
16
17
|
end
|
17
18
|
|
18
19
|
attr_reader :name
|
19
20
|
|
21
|
+
def inheritable?
|
22
|
+
@inheritable
|
23
|
+
end
|
24
|
+
|
20
25
|
def help_lhs
|
21
26
|
name
|
22
27
|
end
|
@@ -34,7 +39,7 @@ module Clamp
|
|
34
39
|
VALID_ATTRIBUTE_NAME = /^[a-z0-9_]+$/
|
35
40
|
|
36
41
|
def infer_attribute_name
|
37
|
-
inferred_name = name.downcase.tr(
|
42
|
+
inferred_name = name.downcase.tr("-", "_").sub(ELLIPSIS_SUFFIX, "").sub(OPTIONAL) { Regexp.last_match(1) }
|
38
43
|
unless inferred_name =~ VALID_ATTRIBUTE_NAME
|
39
44
|
raise "cannot infer attribute_name from #{name.inspect}"
|
40
45
|
end
|
@@ -6,7 +6,13 @@ module Clamp
|
|
6
6
|
protected
|
7
7
|
|
8
8
|
def parse_parameters
|
9
|
+
set_parameters_from_command_line
|
10
|
+
default_parameters_from_environment
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
9
14
|
|
15
|
+
def set_parameters_from_command_line
|
10
16
|
self.class.parameters.each do |parameter|
|
11
17
|
begin
|
12
18
|
parameter.consume(remaining_arguments).each do |value|
|
@@ -16,11 +22,12 @@ module Clamp
|
|
16
22
|
signal_usage_error Clamp.message(:parameter_argument_error, :param => parameter.name, :message => e.message)
|
17
23
|
end
|
18
24
|
end
|
25
|
+
end
|
19
26
|
|
27
|
+
def default_parameters_from_environment
|
20
28
|
self.class.parameters.each do |parameter|
|
21
29
|
parameter.of(self).default_from_environment
|
22
30
|
end
|
23
|
-
|
24
31
|
end
|
25
32
|
|
26
33
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "clamp/errors"
|
2
|
+
require "clamp/subcommand/definition"
|
3
3
|
|
4
4
|
module Clamp
|
5
5
|
module Subcommand
|
@@ -11,19 +11,8 @@ module Clamp
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def subcommand(name, description, subcommand_class = self, &block)
|
14
|
-
|
15
|
-
|
16
|
-
parameter "[SUBCOMMAND]", "subcommand", :attribute_name => :subcommand_name, :default => @default_subcommand
|
17
|
-
else
|
18
|
-
parameter "SUBCOMMAND", "subcommand", :attribute_name => :subcommand_name, :required => false
|
19
|
-
end
|
20
|
-
remove_method :default_subcommand_name
|
21
|
-
parameter "[ARG] ...", "subcommand arguments", :attribute_name => :subcommand_arguments
|
22
|
-
end
|
23
|
-
if block
|
24
|
-
# generate a anonymous sub-class
|
25
|
-
subcommand_class = Class.new(subcommand_class, &block)
|
26
|
-
end
|
14
|
+
subcommand_class = Class.new(subcommand_class, &block) if block
|
15
|
+
declare_subcommand_parameters unless has_subcommands?
|
27
16
|
recognised_subcommands << Subcommand::Definition.new(name, description, subcommand_class)
|
28
17
|
end
|
29
18
|
|
@@ -37,20 +26,14 @@ module Clamp
|
|
37
26
|
|
38
27
|
def find_subcommand_class(*names)
|
39
28
|
names.inject(self) do |command_class, name|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
44
|
-
end
|
29
|
+
return nil unless command_class
|
30
|
+
subcommand = command_class.find_subcommand(name)
|
31
|
+
subcommand.subcommand_class if subcommand
|
45
32
|
end
|
46
33
|
end
|
47
34
|
|
48
|
-
def parameters_before_subcommand
|
49
|
-
parameters.take_while { |p| p != @subcommand_parameter }
|
50
|
-
end
|
51
|
-
|
52
35
|
def inheritable_attributes
|
53
|
-
recognised_options +
|
36
|
+
recognised_options + inheritable_parameters
|
54
37
|
end
|
55
38
|
|
56
39
|
def default_subcommand=(name)
|
@@ -71,6 +54,26 @@ module Clamp
|
|
71
54
|
end
|
72
55
|
end
|
73
56
|
|
57
|
+
private
|
58
|
+
|
59
|
+
def declare_subcommand_parameters
|
60
|
+
if @default_subcommand
|
61
|
+
parameter "[SUBCOMMAND]", "subcommand",
|
62
|
+
:attribute_name => :subcommand_name,
|
63
|
+
:default => @default_subcommand,
|
64
|
+
:inheritable => false
|
65
|
+
else
|
66
|
+
parameter "SUBCOMMAND", "subcommand",
|
67
|
+
:attribute_name => :subcommand_name,
|
68
|
+
:required => false,
|
69
|
+
:inheritable => false
|
70
|
+
end
|
71
|
+
remove_method :default_subcommand_name
|
72
|
+
parameter "[ARG] ...", "subcommand arguments",
|
73
|
+
:attribute_name => :subcommand_arguments,
|
74
|
+
:inheritable => false
|
75
|
+
end
|
76
|
+
|
74
77
|
end
|
75
78
|
|
76
79
|
end
|