clamp 1.2.0.beta1 → 1.3.2
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 +5 -5
- data/.editorconfig +9 -0
- data/.gitignore +1 -1
- data/.rspec +1 -0
- data/.rubocop.yml +26 -19
- data/.travis.yml +3 -6
- data/CHANGES.md +17 -1
- data/Gemfile +8 -6
- data/Guardfile +3 -1
- data/README.md +36 -43
- data/Rakefile +8 -0
- data/clamp.gemspec +8 -6
- data/examples/admin +3 -2
- data/examples/defaulted +4 -3
- data/examples/flipflop +1 -0
- data/examples/fubar +1 -0
- data/examples/gitdown +2 -1
- data/examples/scoop +3 -2
- data/examples/speak +3 -2
- data/examples/subcommand_missing +1 -0
- data/examples/word +1 -0
- data/lib/clamp.rb +3 -1
- data/lib/clamp/attribute/declaration.rb +5 -0
- data/lib/clamp/attribute/definition.rb +25 -11
- data/lib/clamp/attribute/instance.rb +25 -3
- data/lib/clamp/command.rb +9 -1
- data/lib/clamp/errors.rb +7 -3
- data/lib/clamp/help.rb +38 -17
- data/lib/clamp/messages.rb +25 -15
- data/lib/clamp/option/declaration.rb +5 -1
- data/lib/clamp/option/definition.rb +9 -3
- data/lib/clamp/option/parsing.rb +38 -43
- data/lib/clamp/parameter/declaration.rb +4 -0
- data/lib/clamp/parameter/definition.rb +9 -3
- data/lib/clamp/parameter/parsing.rb +5 -1
- data/lib/clamp/subcommand/declaration.rb +17 -15
- data/lib/clamp/subcommand/definition.rb +5 -6
- data/lib/clamp/subcommand/execution.rb +12 -1
- data/lib/clamp/subcommand/parsing.rb +4 -0
- data/lib/clamp/truthy.rb +4 -2
- data/lib/clamp/version.rb +3 -1
- data/spec/clamp/command_group_spec.rb +29 -11
- data/spec/clamp/command_spec.rb +130 -48
- data/spec/clamp/help_spec.rb +63 -0
- data/spec/clamp/messages_spec.rb +5 -4
- data/spec/clamp/option/definition_spec.rb +13 -11
- data/spec/clamp/option_module_spec.rb +3 -1
- data/spec/clamp/option_reordering_spec.rb +6 -4
- data/spec/clamp/parameter/definition_spec.rb +14 -12
- data/spec/spec_helper.rb +3 -3
- metadata +9 -7
data/examples/admin
CHANGED
@@ -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", :
|
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", :
|
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)"
|
data/examples/defaulted
CHANGED
@@ -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
|
-
:
|
12
|
-
:
|
12
|
+
environment_variable: "THE_USER",
|
13
|
+
default: "bob"
|
13
14
|
option ["-P", "--password"], "PASSWORD", "password",
|
14
|
-
:
|
15
|
+
environment_variable: "THE_PASSWORD"
|
15
16
|
|
16
17
|
def execute
|
17
18
|
puts "User: #{user}, Password: #{password}"
|
data/examples/flipflop
CHANGED
data/examples/fubar
CHANGED
data/examples/gitdown
CHANGED
@@ -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", :
|
29
|
+
parameter "[DIR]", "working directory", default: "."
|
29
30
|
|
30
31
|
def execute
|
31
32
|
say "cloning to #{dir}"
|
data/examples/scoop
CHANGED
@@ -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
|
-
:
|
11
|
-
:
|
11
|
+
multivalued: true, default: ["chocolate"],
|
12
|
+
attribute_name: :flavours
|
12
13
|
|
13
14
|
def execute
|
14
15
|
puts "one #{flavours.join(' and ')} ice-cream"
|
data/examples/speak
CHANGED
@@ -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", :
|
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", :
|
19
|
+
parameter "WORDS ...", "the thing to say", attribute_name: :words
|
19
20
|
|
20
21
|
def execute
|
21
22
|
the_truth = words.join(" ")
|
data/examples/subcommand_missing
CHANGED
data/examples/word
CHANGED
data/lib/clamp.rb
CHANGED
@@ -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
|
-
|
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
|
85
|
-
"
|
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
|
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
|
-
|
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
|
data/lib/clamp/command.rb
CHANGED
@@ -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, :
|
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
|
data/lib/clamp/errors.rb
CHANGED
@@ -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
|
-
#
|
21
|
+
# raised to signal incorrect command usage
|
18
22
|
class UsageError < RuntimeError; end
|
19
23
|
|
20
|
-
#
|
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
|
-
#
|
33
|
+
# raised to signal error during execution
|
30
34
|
class ExecutionError < RuntimeError
|
31
35
|
|
32
36
|
def initialize(message, command, status = 1)
|
data/lib/clamp/help.rb
CHANGED
@@ -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
|
-
|
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
|
-
@
|
59
|
+
@lines = []
|
58
60
|
end
|
59
61
|
|
60
62
|
def string
|
61
|
-
|
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
|
-
|
87
|
+
line Clamp.message(:usage_heading) + ":"
|
66
88
|
usage_descriptions.each do |usage|
|
67
|
-
|
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
|
-
|
74
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
93
|
-
@out.puts(*args)
|
94
|
-
end
|
115
|
+
attr_accessor :lines
|
95
116
|
|
96
117
|
end
|
97
118
|
|