clamp 1.2.0.beta1 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +9 -0
  3. data/.gitignore +1 -1
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +26 -19
  6. data/.travis.yml +3 -6
  7. data/CHANGES.md +17 -1
  8. data/Gemfile +8 -6
  9. data/Guardfile +3 -1
  10. data/README.md +36 -43
  11. data/Rakefile +8 -0
  12. data/clamp.gemspec +8 -6
  13. data/examples/admin +3 -2
  14. data/examples/defaulted +4 -3
  15. data/examples/flipflop +1 -0
  16. data/examples/fubar +1 -0
  17. data/examples/gitdown +2 -1
  18. data/examples/scoop +3 -2
  19. data/examples/speak +3 -2
  20. data/examples/subcommand_missing +1 -0
  21. data/examples/word +1 -0
  22. data/lib/clamp.rb +3 -1
  23. data/lib/clamp/attribute/declaration.rb +5 -0
  24. data/lib/clamp/attribute/definition.rb +25 -11
  25. data/lib/clamp/attribute/instance.rb +25 -3
  26. data/lib/clamp/command.rb +9 -1
  27. data/lib/clamp/errors.rb +7 -3
  28. data/lib/clamp/help.rb +38 -17
  29. data/lib/clamp/messages.rb +25 -15
  30. data/lib/clamp/option/declaration.rb +5 -1
  31. data/lib/clamp/option/definition.rb +9 -3
  32. data/lib/clamp/option/parsing.rb +38 -43
  33. data/lib/clamp/parameter/declaration.rb +4 -0
  34. data/lib/clamp/parameter/definition.rb +9 -3
  35. data/lib/clamp/parameter/parsing.rb +5 -1
  36. data/lib/clamp/subcommand/declaration.rb +17 -15
  37. data/lib/clamp/subcommand/definition.rb +5 -6
  38. data/lib/clamp/subcommand/execution.rb +12 -1
  39. data/lib/clamp/subcommand/parsing.rb +4 -0
  40. data/lib/clamp/truthy.rb +4 -2
  41. data/lib/clamp/version.rb +3 -1
  42. data/spec/clamp/command_group_spec.rb +29 -11
  43. data/spec/clamp/command_spec.rb +130 -48
  44. data/spec/clamp/help_spec.rb +63 -0
  45. data/spec/clamp/messages_spec.rb +5 -4
  46. data/spec/clamp/option/definition_spec.rb +13 -11
  47. data/spec/clamp/option_module_spec.rb +3 -1
  48. data/spec/clamp/option_reordering_spec.rb +6 -4
  49. data/spec/clamp/parameter/definition_spec.rb +14 -12
  50. data/spec/spec_helper.rb +3 -3
  51. metadata +9 -7
@@ -1,5 +1,9 @@
1
- module Clamp
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)
@@ -7,7 +11,10 @@ module Clamp
7
11
  end
8
12
 
9
13
  def message(key, options = {})
10
- format(messages.fetch(key), options)
14
+ string = messages.fetch(key)
15
+ return string if options.empty?
16
+
17
+ format string, options
11
18
  end
12
19
 
13
20
  def clear_messages!
@@ -17,19 +24,22 @@ module Clamp
17
24
  private
18
25
 
19
26
  DEFAULTS = {
20
- :too_many_arguments => "too many arguments",
21
- :option_required => "option '%<option>s' is required",
22
- :option_or_env_required => "option '%<option>s' (or env %<env>s) is required",
23
- :option_argument_error => "option '%<switch>s': %<message>s",
24
- :parameter_argument_error => "parameter '%<param>s': %<message>s",
25
- :env_argument_error => "$%<env>s: %<message>s",
26
- :unrecognised_option => "Unrecognised option '%<switch>s'",
27
- :no_such_subcommand => "No such sub-command '%<name>s'",
28
- :no_value_provided => "no value provided",
29
- :usage_heading => "Usage",
30
- :parameters_heading => "Parameters",
31
- :subcommands_heading => "Subcommands",
32
- :options_heading => "Options"
27
+ too_many_arguments: "too many arguments",
28
+ option_required: "option '%<option>s' is required",
29
+ option_or_env_required: "option '%<option>s' (or env %<env>s) is required",
30
+ option_argument_error: "option '%<switch>s': %<message>s",
31
+ parameter_argument_error: "parameter '%<param>s': %<message>s",
32
+ env_argument_error: "$%<env>s: %<message>s",
33
+ unrecognised_option: "Unrecognised option '%<switch>s'",
34
+ no_such_subcommand: "No such sub-command '%<name>s'",
35
+ no_value_provided: "no value provided",
36
+ default: "default",
37
+ or: "or",
38
+ required: "required",
39
+ usage_heading: "Usage",
40
+ parameters_heading: "Parameters",
41
+ subcommands_heading: "Subcommands",
42
+ options_heading: "Options"
33
43
  }.freeze
34
44
 
35
45
  def messages
@@ -1,9 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "clamp/attribute/declaration"
2
4
  require "clamp/option/definition"
3
5
 
4
6
  module Clamp
5
7
  module Option
6
8
 
9
+ # Option declaration methods.
10
+ #
7
11
  module Declaration
8
12
 
9
13
  include Clamp::Attribute::Declaration
@@ -25,7 +29,7 @@ module Clamp
25
29
  end
26
30
 
27
31
  def recognised_options
28
- unless @implicit_options_declared
32
+ unless @implicit_options_declared ||= false
29
33
  declare_implicit_help_option
30
34
  @implicit_options_declared = true
31
35
  end
@@ -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
@@ -1,11 +1,17 @@
1
- module Clamp
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
- if argument_buffer.size < argument_buffer_limit
28
- argument_buffer << remaining_arguments.shift
29
- next
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
- case switch
39
- when /\A(-\w)(.+)\z/m # combined short options
40
- switch = Regexp.last_match(1)
41
- if find_option(switch).flag?
42
- remaining_arguments.unshift("-" + Regexp.last_match(2))
43
- else
44
- remaining_arguments.unshift(Regexp.last_match(2))
45
- end
46
- when /\A(--[^=]+)=(.*)\z/m
47
- switch = Regexp.last_match(1)
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
- begin
55
- option.of(self).take(value)
56
- rescue ArgumentError => e
57
- signal_usage_error Clamp.message(:option_argument_error, :switch => switch, :message => e.message)
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
- remaining_arguments.unshift(*argument_buffer)
66
+ switch
62
67
  end
63
68
 
64
69
  def default_options_from_environment
@@ -69,23 +74,13 @@ module Clamp
69
74
 
70
75
  def verify_required_options_are_set
71
76
  self.class.recognised_options.each do |option|
72
- # If this option is required and the value is nil, there's an error.
73
- next unless option.required? && send(option.attribute_name).nil?
74
- if option.environment_variable
75
- message = Clamp.message(:option_or_env_required,
76
- :option => option.switches.first,
77
- :env => option.environment_variable)
78
- else
79
- message = Clamp.message(:option_required,
80
- :option => option.switches.first)
81
- end
82
- signal_usage_error message
77
+ option.of(self).verify_not_missing
83
78
  end
84
79
  end
85
80
 
86
81
  def find_option(switch)
87
82
  self.class.find_option(switch) ||
88
- signal_usage_error(Clamp.message(:unrecognised_option, :switch => switch))
83
+ signal_usage_error(Clamp.message(:unrecognised_option, switch: switch))
89
84
  end
90
85
 
91
86
  end
@@ -1,9 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "clamp/attribute/declaration"
2
4
  require "clamp/parameter/definition"
3
5
 
4
6
  module Clamp
5
7
  module Parameter
6
8
 
9
+ # Parameter declaration methods.
10
+ #
7
11
  module Declaration
8
12
 
9
13
  include Clamp::Attribute::Declaration
@@ -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, :param => parameter.name, :message => e.message)
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,18 +41,16 @@ 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
 
46
48
  def default_subcommand(*args, &block)
47
49
  if args.empty?
48
- @default_subcommand
50
+ @default_subcommand ||= false
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
@@ -57,21 +59,21 @@ module Clamp
57
59
  private
58
60
 
59
61
  def declare_subcommand_parameters
60
- if @default_subcommand
62
+ if default_subcommand
61
63
  parameter "[SUBCOMMAND]", "subcommand",
62
- :attribute_name => :subcommand_name,
63
- :default => @default_subcommand,
64
- :inheritable => false
64
+ attribute_name: :subcommand_name,
65
+ default: default_subcommand,
66
+ inheritable: false
65
67
  else
66
68
  parameter "SUBCOMMAND", "subcommand",
67
- :attribute_name => :subcommand_name,
68
- :required => false,
69
- :inheritable => false
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
- :attribute_name => :subcommand_arguments,
74
- :inheritable => false
75
+ attribute_name: :subcommand_arguments,
76
+ inheritable: false
75
77
  end
76
78
 
77
79
  end
@@ -1,16 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Clamp
2
4
  module Subcommand
3
5
 
4
- Definition = Struct.new(:name, :description, :subcommand_class) do
6
+ Definition = Struct.new(:names, :description, :subcommand_class) do
5
7
 
6
8
  def initialize(names, description, subcommand_class)
7
- @names = Array(names)
8
- @description = description
9
- @subcommand_class = subcommand_class
9
+ names = Array(names)
10
+ super
10
11
  end
11
12
 
12
- attr_reader :names, :description, :subcommand_class
13
-
14
13
  def is_called?(name)
15
14
  names.member?(name)
16
15
  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
@@ -24,7 +31,7 @@ module Clamp
24
31
  end
25
32
 
26
33
  def invocation_path_for(name)
27
- param_names = self.class.inheritable_parameters.map(&:name)
34
+ param_names = self.class.parameters.select(&:inheritable?).map(&:name)
28
35
  [invocation_path, *param_names, name].join(" ")
29
36
  end
30
37
 
@@ -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
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "clamp/subcommand/execution"
2
4
 
3
5
  module Clamp
4
6
  module Subcommand
5
7
 
8
+ # Subcommand parsing methods.
9
+ #
6
10
  module Parsing
7
11
 
8
12
  protected
@@ -1,6 +1,8 @@
1
- module Clamp
1
+ # frozen_string_literal: true
2
2
 
3
- TRUTHY_VALUES = %w(1 yes enable on true).freeze
3
+ module Clamp #:nodoc:
4
+
5
+ TRUTHY_VALUES = %w[1 yes enable on true].freeze
4
6
 
5
7
  def self.truthy?(arg)
6
8
  TRUTHY_VALUES.include?(arg.to_s.downcase)