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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -4
  3. data/.travis.yml +4 -6
  4. data/CHANGES.md +7 -0
  5. data/Gemfile +8 -6
  6. data/Guardfile +3 -1
  7. data/Rakefile +8 -0
  8. data/clamp.gemspec +8 -6
  9. data/examples/admin +3 -2
  10. data/examples/defaulted +4 -3
  11. data/examples/flipflop +1 -0
  12. data/examples/fubar +1 -0
  13. data/examples/gitdown +2 -1
  14. data/examples/scoop +3 -2
  15. data/examples/speak +3 -2
  16. data/examples/subcommand_missing +1 -0
  17. data/examples/word +1 -0
  18. data/lib/clamp.rb +3 -1
  19. data/lib/clamp/attribute/declaration.rb +5 -0
  20. data/lib/clamp/attribute/definition.rb +15 -12
  21. data/lib/clamp/attribute/instance.rb +5 -3
  22. data/lib/clamp/command.rb +9 -1
  23. data/lib/clamp/errors.rb +7 -3
  24. data/lib/clamp/help.rb +8 -6
  25. data/lib/clamp/messages.rb +21 -14
  26. data/lib/clamp/option/declaration.rb +4 -0
  27. data/lib/clamp/option/definition.rb +9 -3
  28. data/lib/clamp/option/parsing.rb +37 -32
  29. data/lib/clamp/parameter/declaration.rb +4 -0
  30. data/lib/clamp/parameter/definition.rb +9 -3
  31. data/lib/clamp/parameter/parsing.rb +5 -1
  32. data/lib/clamp/subcommand/declaration.rb +15 -13
  33. data/lib/clamp/subcommand/definition.rb +2 -0
  34. data/lib/clamp/subcommand/execution.rb +11 -0
  35. data/lib/clamp/subcommand/parsing.rb +4 -0
  36. data/lib/clamp/truthy.rb +4 -2
  37. data/lib/clamp/version.rb +3 -1
  38. data/spec/clamp/command_group_spec.rb +27 -9
  39. data/spec/clamp/command_spec.rb +84 -49
  40. data/spec/clamp/messages_spec.rb +5 -4
  41. data/spec/clamp/option/definition_spec.rb +13 -11
  42. data/spec/clamp/option_module_spec.rb +3 -1
  43. data/spec/clamp/option_reordering_spec.rb +6 -4
  44. data/spec/clamp/parameter/definition_spec.rb +14 -12
  45. data/spec/spec_helper.rb +3 -3
  46. metadata +4 -4
@@ -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
- # raise to signal incorrect command usage
21
+ # raised to signal incorrect command usage
18
22
  class UsageError < RuntimeError; end
19
23
 
20
- # raise to request usage help
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
- # raise to signal error during execution
33
+ # raised to signal error during execution
30
34
  class ExecutionError < RuntimeError
31
35
 
32
36
  def initialize(message, command, status = 1)
@@ -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
- help.add_list(Clamp.message(:parameters_heading), parameters)
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
@@ -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)
@@ -17,19 +21,22 @@ module Clamp
17
21
  private
18
22
 
19
23
  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"
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/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
@@ -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
@@ -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, :switch => switch))
83
+ signal_usage_error(Clamp.message(:unrecognised_option, switch: switch))
79
84
  end
80
85
 
81
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,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
- :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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Clamp
2
4
  module Subcommand
3
5
 
@@ -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
@@ -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)