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