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,4 +1,5 @@
1
1
  #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # An example of options and parameters
4
5
 
@@ -6,12 +7,12 @@ require "clamp"
6
7
 
7
8
  Clamp do
8
9
 
9
- option "--timeout", "SECONDS", "connection timeout", :default => 5, :environment_variable => "MYAPP_TIMEOUT" do |x|
10
+ option "--timeout", "SECONDS", "connection timeout", default: 5, environment_variable: "MYAPP_TIMEOUT" do |x|
10
11
  Integer(x)
11
12
  end
12
13
 
13
14
  parameter "HOST", "server address"
14
- parameter "[PORT]", "server port", :default => 80, :environment_variable => "MYAPP_PORT"
15
+ parameter "[PORT]", "server port", default: 80, environment_variable: "MYAPP_PORT"
15
16
 
16
17
  def execute
17
18
  puts "trying to connect to #{host} on port #{port} (waiting up to #{timeout} seconds)"
@@ -1,4 +1,5 @@
1
1
  #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # An example of default values and methods
4
5
 
@@ -8,10 +9,10 @@ require "highline"
8
9
  Clamp do
9
10
 
10
11
  option ["-U", "--user"], "USER", "user name",
11
- :environment_variable => "THE_USER",
12
- :default => "bob"
12
+ environment_variable: "THE_USER",
13
+ default: "bob"
13
14
  option ["-P", "--password"], "PASSWORD", "password",
14
- :environment_variable => "THE_PASSWORD"
15
+ environment_variable: "THE_PASSWORD"
15
16
 
16
17
  def execute
17
18
  puts "User: #{user}, Password: #{password}"
@@ -1,4 +1,5 @@
1
1
  #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # An example of subcommands (including a default subcommand)
4
5
 
@@ -1,4 +1,5 @@
1
1
  #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # An example of nested subcommands
4
5
 
@@ -1,4 +1,5 @@
1
1
  #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # Demonstrate how subcommands can be declared as classes
4
5
 
@@ -25,7 +26,7 @@ module GitDown
25
26
  class CloneCommand < AbstractCommand
26
27
 
27
28
  parameter "REPOSITORY", "repository to clone"
28
- parameter "[DIR]", "working directory", :default => "."
29
+ parameter "[DIR]", "working directory", default: "."
29
30
 
30
31
  def execute
31
32
  say "cloning to #{dir}"
@@ -1,4 +1,5 @@
1
1
  #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # An example of multi-valued options
4
5
 
@@ -7,8 +8,8 @@ require "clamp"
7
8
  Clamp do
8
9
 
9
10
  option ["-f", "--flavour"], "FLAVOUR", "flavour",
10
- :multivalued => true, :default => ["chocolate"],
11
- :attribute_name => :flavours
11
+ multivalued: true, default: ["chocolate"],
12
+ attribute_name: :flavours
12
13
 
13
14
  def execute
14
15
  puts "one #{flavours.join(' and ')} ice-cream"
@@ -1,4 +1,5 @@
1
1
  #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # A simple Clamp command, with options and parameters
4
5
 
@@ -11,11 +12,11 @@ Clamp do
11
12
  )
12
13
 
13
14
  option "--loud", :flag, "say it loud"
14
- option ["-n", "--iterations"], "N", "say it N times", :default => 1 do |s|
15
+ option ["-n", "--iterations"], "N", "say it N times", default: 1 do |s|
15
16
  Integer(s)
16
17
  end
17
18
 
18
- parameter "WORDS ...", "the thing to say", :attribute_name => :words
19
+ parameter "WORDS ...", "the thing to say", attribute_name: :words
19
20
 
20
21
  def execute
21
22
  the_truth = words.join(" ")
@@ -1,4 +1,5 @@
1
1
  #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # Demonstrate subcommand_missing
4
5
 
@@ -1,4 +1,5 @@
1
1
  #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # Demonstrate "restful" style; parameters precede subcommands
4
5
 
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "clamp/version"
2
4
 
3
5
  require "clamp/command"
4
6
 
5
- def Clamp(&block)
7
+ def Clamp(&block) # rubocop:disable Naming/MethodName
6
8
  Class.new(Clamp::Command, &block).run
7
9
  end
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Clamp
2
4
  module Attribute
3
5
 
6
+ # Methods to generate attribute accessors.
7
+ #
4
8
  module Declaration
5
9
 
6
10
  protected
@@ -25,6 +29,7 @@ module Clamp
25
29
  end
26
30
 
27
31
  def define_default_for(attribute)
32
+ return false if attribute.default_value.nil?
28
33
  define_method(attribute.default_method) do
29
34
  attribute.default_value
30
35
  end
@@ -1,25 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "clamp/attribute/instance"
2
4
 
3
5
  module Clamp
4
6
  module Attribute
5
7
 
8
+ # Represents an attribute of a Clamp::Command class.
9
+ #
6
10
  class Definition
7
11
 
8
12
  def initialize(options)
9
- if options.key?(:attribute_name)
10
- @attribute_name = options[:attribute_name].to_s
11
- end
13
+ @attribute_name = options[:attribute_name].to_s if options.key?(:attribute_name)
12
14
  @default_value = options[:default] if options.key?(:default)
13
- if options.key?(:environment_variable)
14
- @environment_variable = options[:environment_variable]
15
- end
15
+ @environment_variable = options[:environment_variable] if options.key?(:environment_variable)
16
16
  @hidden = options[:hidden] if options.key?(:hidden)
17
17
  end
18
18
 
19
19
  attr_reader :description, :environment_variable
20
20
 
21
21
  def help_rhs
22
- description + default_description
22
+ rhs = description
23
+ comments = required_indicator || default_description
24
+ rhs += " (#{comments})" if comments
25
+ rhs
23
26
  end
24
27
 
25
28
  def help
@@ -51,11 +54,11 @@ module Clamp
51
54
  end
52
55
 
53
56
  def required?
54
- @required
57
+ @required ||= false
55
58
  end
56
59
 
57
60
  def hidden?
58
- @hidden
61
+ @hidden ||= false
59
62
  end
60
63
 
61
64
  def attribute_name
@@ -74,6 +77,17 @@ module Clamp
74
77
  Attribute::Instance.new(self, command)
75
78
  end
76
79
 
80
+ def option_missing_message
81
+ if environment_variable
82
+ Clamp.message(:option_or_env_required,
83
+ option: switches.first,
84
+ env: environment_variable)
85
+ else
86
+ Clamp.message(:option_required,
87
+ option: switches.first)
88
+ end
89
+ end
90
+
77
91
  private
78
92
 
79
93
  def default_description
@@ -81,8 +95,8 @@ module Clamp
81
95
  ("$#{@environment_variable}" if defined?(@environment_variable)),
82
96
  (@default_value.inspect if defined?(@default_value))
83
97
  ].compact
84
- return "" if default_sources.empty?
85
- " (default: " + default_sources.join(", or ") + ")"
98
+ return nil if default_sources.empty?
99
+ "#{Clamp.message(:default)}: " + default_sources.join(", #{Clamp.message(:or)} ")
86
100
  end
87
101
 
88
102
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Clamp
2
4
  module Attribute
3
5
 
4
- # Represents an option/parameter of a Clamp::Command instance.
6
+ # Represents an attribute of a Clamp::Command instance.
5
7
  #
6
8
  class Instance
7
9
 
@@ -27,7 +29,7 @@ module Clamp
27
29
  end
28
30
 
29
31
  def default
30
- command.send(attribute.default_method)
32
+ command.send(attribute.default_method) if command.respond_to?(attribute.default_method, true)
31
33
  end
32
34
 
33
35
  # default implementation of read_method
@@ -60,6 +62,10 @@ module Clamp
60
62
  end
61
63
  end
62
64
 
65
+ def signal_usage_error(*args)
66
+ command.send(:signal_usage_error, *args)
67
+ end
68
+
63
69
  def default_from_environment
64
70
  return if self.defined?
65
71
  return if attribute.environment_variable.nil?
@@ -69,10 +75,26 @@ module Clamp
69
75
  begin
70
76
  take(value)
71
77
  rescue ArgumentError => e
72
- command.send(:signal_usage_error, Clamp.message(:env_argument_error, :env => attribute.environment_variable, :message => e.message))
78
+ signal_usage_error Clamp.message(:env_argument_error, env: attribute.environment_variable, message: e.message)
79
+ end
80
+ end
81
+
82
+ def unset?
83
+ if attribute.multivalued?
84
+ read.empty?
85
+ else
86
+ read.nil?
73
87
  end
74
88
  end
75
89
 
90
+ def missing?
91
+ attribute.required? && unset?
92
+ end
93
+
94
+ def verify_not_missing
95
+ signal_usage_error attribute.option_missing_message if missing?
96
+ end
97
+
76
98
  end
77
99
 
78
100
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "clamp/messages"
2
4
  require "clamp/errors"
3
5
  require "clamp/help"
@@ -48,6 +50,7 @@ module Clamp
48
50
  parse_options
49
51
  parse_parameters
50
52
  parse_subcommand
53
+ verify_required_options_are_set
51
54
  handle_remaining_arguments
52
55
  end
53
56
 
@@ -81,7 +84,7 @@ module Clamp
81
84
  #
82
85
  # @ param [String] name subcommand_name
83
86
  def subcommand_missing(name)
84
- signal_usage_error(Clamp.message(:no_such_subcommand, :name => name))
87
+ signal_usage_error(Clamp.message(:no_such_subcommand, name: name))
85
88
  end
86
89
 
87
90
  include Clamp::Option::Parsing
@@ -122,6 +125,11 @@ module Clamp
122
125
  include Clamp::Subcommand::Declaration
123
126
  include Help
124
127
 
128
+ # An alternative to "def execute"
129
+ def execute(&block)
130
+ define_method(:execute, &block)
131
+ end
132
+
125
133
  # Create an instance of this command class, and run it.
126
134
  #
127
135
  # @param [String] invocation_path the path used to invoke the command
@@ -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,47 +45,66 @@ 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
57
- @out = StringIO.new
59
+ @lines = []
58
60
  end
59
61
 
60
62
  def string
61
- @out.string
63
+ left_column_width = lines.grep(Array).map(&:first).map(&:size).max
64
+ StringIO.new.tap do |out|
65
+ lines.each do |line|
66
+ case line
67
+ when Array
68
+ line[0] = line[0].ljust(left_column_width)
69
+ line.unshift("")
70
+ out.puts(line.join(" "))
71
+ else
72
+ out.puts(line)
73
+ end
74
+ end
75
+ end.string
76
+ end
77
+
78
+ def line(text = "")
79
+ @lines << text
80
+ end
81
+
82
+ def row(lhs, rhs)
83
+ @lines << [lhs, rhs]
62
84
  end
63
85
 
64
86
  def add_usage(invocation_path, usage_descriptions)
65
- puts Clamp.message(:usage_heading) + ":"
87
+ line Clamp.message(:usage_heading) + ":"
66
88
  usage_descriptions.each do |usage|
67
- puts " #{invocation_path} #{usage}".rstrip
89
+ line " #{invocation_path} #{usage}".rstrip
68
90
  end
69
91
  end
70
92
 
71
93
  def add_description(description)
72
94
  return unless description
73
- puts ""
74
- puts description.gsub(/^/, " ")
95
+ line
96
+ line description.gsub(/^/, " ")
75
97
  end
76
98
 
77
99
  DETAIL_FORMAT = " %-29s %s".freeze
78
100
 
79
101
  def add_list(heading, items)
80
- puts "\n#{heading}:"
102
+ line
103
+ line "#{heading}:"
81
104
  items.reject { |i| i.respond_to?(:hidden?) && i.hidden? }.each do |item|
82
105
  label, description = item.help
83
106
  description.each_line do |line|
84
- puts format(DETAIL_FORMAT, label, line)
107
+ row(label, line)
85
108
  label = ""
86
109
  end
87
110
  end
@@ -89,9 +112,7 @@ module Clamp
89
112
 
90
113
  private
91
114
 
92
- def puts(*args)
93
- @out.puts(*args)
94
- end
115
+ attr_accessor :lines
95
116
 
96
117
  end
97
118