New Upstream Snapshot - ruby-clamp
Ready changes
Summary
Merged new upstream version: 1.3.2+git20220917.1.2bea305 (was: 1.1.1).
Resulting package
Built on 2022-12-30T03:59 (took 14m51s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-snapshots ruby-clamp
Lintian Result
Diff
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..b372c22
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,10 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+max_line_length = 120
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 0b36852..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-*.gem
-.bundle
-.rvmrc
-.yardoc
-doc
-pkg/*
-Gemfile.lock
-
\ No newline at end of file
diff --git a/.rspec b/.rspec
index 4e1e0d2..023c417 100644
--- a/.rspec
+++ b/.rspec
@@ -1 +1,2 @@
--color
+--warnings
diff --git a/.rubocop.yml b/.rubocop.yml
index aa0fe6b..80e7751 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,17 +1,42 @@
-Eval:
- Exclude:
- - "Rakefile"
+require:
+ - rubocop-performance
+ - rubocop-rake
+ - rubocop-rspec
+
+AllCops:
+ TargetRubyVersion: 2.5
+ NewCops: enable
+
+Layout/LineLength:
+ Max: 120
+
+Layout/EmptyLinesAroundBlockBody:
+ Enabled: false
+
+Layout/EmptyLinesAroundClassBody:
+ EnforcedStyle: empty_lines
+
+Layout/EmptyLinesAroundModuleBody:
+ Enabled: false
Metrics/AbcSize:
Enabled: false
-Metrics/LineLength:
- Max: 120
+Metrics/BlockLength:
+ Exclude:
+ - "spec/**/*"
Metrics/MethodLength:
Max: 30
-Style/AccessorMethodName:
+Naming/AccessorMethodName:
+ Enabled: false
+
+Naming/FileName:
+ Exclude:
+ - "bin/*"
+
+Naming/PredicateName:
Enabled: false
Style/ClassAndModuleChildren:
@@ -22,35 +47,19 @@ Style/ClassAndModuleChildren:
Style/Documentation:
Exclude:
- "lib/**/version.rb"
+ - "examples/*"
- "spec/**/*"
-Style/EmptyLinesAroundBlockBody:
- Enabled: false
-
-Style/EmptyLinesAroundClassBody:
- EnforcedStyle: empty_lines
-
-Style/EmptyLinesAroundModuleBody:
- Enabled: false
-
Style/Encoding:
- EnforcedStyle: when_needed
Enabled: true
-Style/FileName:
- Exclude:
- - "bin/*"
-
-Style/HashSyntax:
- EnforcedStyle: hash_rockets
-
Style/Lambda:
Enabled: false
Style/NumericLiterals:
Enabled: false
-Style/PredicateName:
+Style/StderrPuts:
Enabled: false
Style/StringLiterals:
@@ -58,3 +67,6 @@ Style/StringLiterals:
Style/WordArray:
Enabled: false
+
+RSpec/NestedGroups:
+ Enabled: false
diff --git a/.travis.yml b/.travis.yml
index 83aca4a..bb7cb35 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,6 @@
language: ruby
rvm:
- - 2.1.8
- - 2.2.4
- - 2.3.1
-before_install:
- - gem update --system
- - gem install bundler
+ - 2.5
+ - 2.6
+ - 2.7
+ - 3.0
diff --git a/CHANGES.md b/CHANGES.md
index 828a7da..6feedb0 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,29 @@
# Changelog
+## 1.3.2 (2020-08-20)
+
+* Fix Ruby warnings.
+
+## 1.3.1 (2019-07-11)
+
+* Choose a sensible column width in generated help, based on content.
+* Fix issue#99: extraneous parameter names in subcommand help.
+
+## 1.3.0 (2018-06-17)
+
+* Add `.execute` DSL method.
+* Append '(required)' to the description of required options.
+* Fix issue#75: don't generate `default_XXX` method unless a default is specified.
+* Fix issue#90: allow required options to be provided after subcommands.
+
+## 1.2.0 (2018-02-12)
+
+* Add option to `Clamp.allow_options_after_parameters`.
+
+## 1.1.2 (2017-02-12)
+
+* Improve usage help for commands with both parameters and subcommands.
+
## 1.1.1 (2016-10-19)
* Rename `.declare_attribute` back to `.define_accessors_for`.
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 0000000..23f528b
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1 @@
+* @mdub
diff --git a/Gemfile b/Gemfile
index 52237b2..2f01e4f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,15 +1,21 @@
+# frozen_string_literal: true
+
source "http://rubygems.org"
gemspec
group :development do
- gem "guard-rspec", "~> 4.6.5", :require => false
- gem "listen", "~> 3.0.2"
- gem "rake", "~> 10.4"
- gem "rubocop", "~> 0.43.0", :require => false
+ gem "guard-rspec", "~> 4.7", require: false
+ gem "highline"
+ gem "listen", "~> 3.0"
+ gem "pry-byebug", "~> 3.9"
+ gem "rake", "~> 13.0"
+ gem "rubocop", "~> 1.22.3", require: false
+ gem "rubocop-performance", "~> 1.11", require: false
+ gem "rubocop-rake", "~> 0.6.0", require: false
+ gem "rubocop-rspec", "~> 2.6.0", require: false
end
group :test do
- gem "rspec", "~> 3.5.0"
- gem "rr", "~> 1.2.0"
+ gem "rspec", "~> 3.7"
end
diff --git a/Guardfile b/Guardfile
index 3534307..67a4072 100644
--- a/Guardfile
+++ b/Guardfile
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
@@ -5,7 +7,7 @@
# directories %w(app lib config test spec features) \
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
-## Note: if you are using the `directories` clause above and you are not
+## NOTE: if you are using the `directories` clause above and you are not
## watching the project directory ('.'), then you will want to move
## the Guardfile to a watched dir and symlink it back, e.g.
#
@@ -15,7 +17,7 @@
#
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
-# Note: The cmd option is now required due to the increasing number of ways
+# NOTE: The cmd option is now required due to the increasing number of ways
# rspec may be run, below are examples of the most common uses.
# * bundler: 'bundle exec rspec'
# * bundler binstubs: 'bin/rspec'
@@ -24,7 +26,7 @@
# * zeus: 'zeus rspec' (requires the server to be started separately)
# * 'just' rspec: 'rspec'
-guard :rspec, :cmd => "bundle exec rspec" do
+guard :rspec, cmd: "bundle exec rspec" do
require "guard/rspec/dsl"
dsl = Guard::RSpec::Dsl.new(self)
diff --git a/README.md b/README.md
index 474ec81..1f7c05f 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-Clamp
-=====
+# Clamp
[![Gem Version](https://badge.fury.io/rb/clamp.png)](http://badge.fury.io/rb/clamp)
[![Build Status](https://secure.travis-ci.org/mdub/clamp.png?branch=master)](http://travis-ci.org/mdub/clamp)
@@ -8,8 +7,7 @@ Clamp
It handles boring stuff like parsing the command-line, and generating help, so you can get on with making your command actually do stuff.
-Not another one!
-----------------
+## Not another one!
Yeah, sorry. There are a bunch of existing command-line parsing libraries out there, and Clamp draws inspiration from a variety of sources, including [Thor], [optparse], and [Clip]. In the end, though, I wanted a slightly rounder wheel. (Although, Clamp has a _lot_ in common with Ara T. Howard's [main.rb]. Had I been aware of that project at the time, I might not have written Clamp.)
@@ -18,8 +16,7 @@ Yeah, sorry. There are a bunch of existing command-line parsing libraries out t
[Clip]: http://clip.rubyforge.org/
[main.rb]: https://github.com/ahoward/main
-Quick Start
------------
+## Quick Start
A typical Clamp script looks like this:
@@ -29,11 +26,11 @@ require 'clamp'
Clamp do
option "--loud", :flag, "say it loud"
- option ["-n", "--iterations"], "N", "say it N times", :default => 1 do |s|
+ option ["-n", "--iterations"], "N", "say it N times", default: 1 do |s|
Integer(s)
end
- parameter "WORDS ...", "the thing to say", :attribute_name => :words
+ parameter "WORDS ...", "the thing to say", attribute_name: :words
def execute
the_truth = words.join(" ")
@@ -54,11 +51,11 @@ require 'clamp'
class SpeakCommand < Clamp::Command
option "--loud", :flag, "say it loud"
- option ["-n", "--iterations"], "N", "say it N times", :default => 1 do |s|
+ option ["-n", "--iterations"], "N", "say it N times", default: 1 do |s|
Integer(s)
end
- parameter "WORDS ...", "the thing to say", :attribute_name => :words
+ parameter "WORDS ...", "the thing to say", attribute_name: :words
def execute
the_truth = words.join(" ")
@@ -79,8 +76,7 @@ There are more examples demonstrating various features of Clamp [on Github][exam
[examples]: https://github.com/mdub/clamp/tree/master/examples
-Declaring options
------------------
+## Declaring options
Options are declared using the `option` method. The three required arguments are:
@@ -105,7 +101,7 @@ end
If you don't like the inferred attribute name, you can override it:
```ruby
-option "--type", "TYPE", "type of widget", :attribute_name => :widget_type
+option "--type", "TYPE", "type of widget", attribute_name: :widget_type
# to avoid clobbering Object#type
```
@@ -140,7 +136,7 @@ Clamp will handle both "`--force`" and "`--no-force`" options, setting the value
Although "required option" is an oxymoron, Clamp lets you mark an option as required, and will verify that a value is provided:
```ruby
-option "--password", "PASSWORD", "the secret password", :required => true
+option "--password", "PASSWORD", "the secret password", required: true
```
Note that it makes no sense to mark a `:flag` option, or one with a `:default`, as `:required`.
@@ -150,7 +146,7 @@ Note that it makes no sense to mark a `:flag` option, or one with a `:default`,
Declaring an option "`:multivalued`" allows it to be specified multiple times on the command line.
```ruby
-option "--format", "FORMAT", "output format", :multivalued => true
+option "--format", "FORMAT", "output format", multivalued: true
```
The underlying attribute becomes an Array, and the suffix "`_list`" is appended to the default attribute name. In this case, an attribute called "`format_list`" would be generated (unless you override the default by specifying an `:attribute_name`).
@@ -160,12 +156,21 @@ The underlying attribute becomes an Array, and the suffix "`_list`" is appended
Declaring an option "`:hidden`" will cause it to be hidden from `--help` output.
```ruby
-option "--some-option", "VALUE", "Just a little option", :hidden => true
+option "--some-option", "VALUE", "Just a little option", hidden: true
```
+### Version option
-Declaring parameters
---------------------
+A common idiom is to have an option `--version` that outputs the command version and doesn't run any subcommands. This can be achieved by:
+
+```ruby
+option "--version", :flag, "Show version" do
+ puts MyGem::VERSION
+ exit(0)
+end
+```
+
+## Declaring parameters
Positional parameters can be declared using `parameter`, specifying
@@ -193,13 +198,12 @@ parameter "[TARGET_DIR]", "target directory"
Three dots at the end of a parameter name makes it "greedy" - it will consume all remaining command-line arguments. For example:
```ruby
-parameter "FILE ...", "input files", :attribute_name => :files
+parameter "FILE ...", "input files", attribute_name: :files
```
Like multivalued options, greedy parameters are backed by an Array attribute (named with a "`_list`" suffix, by default).
-Parsing and validation of options and parameters
-------------------------------------------------
+## Parsing and validation of options and parameters
When you `#run` a command, it will first attempt to `#parse` command-line arguments, and map them onto the declared options and parameters, before invoking your `#execute` method.
@@ -255,15 +259,15 @@ end
Default values can be specified for options, and optional parameters:
```ruby
-option "--flavour", "FLAVOUR", "ice-cream flavour", :default => "chocolate"
+option "--flavour", "FLAVOUR", "ice-cream flavour", default: "chocolate"
-parameter "[HOST]", "server host", :default => "localhost"
+parameter "[HOST]", "server host", default: "localhost"
```
For more advanced cases, you can also specify default values by defining a method called "`default_#{attribute_name}`":
```ruby
-option "--http-port", "PORT", "web-server port", :default => 9000
+option "--http-port", "PORT", "web-server port", default: 9000
option "--admin-port", "PORT", "admin port"
@@ -277,17 +281,30 @@ end
Options (and optional parameters) can also be associated with environment variables:
```ruby
-option "--port", "PORT", "the port to listen on", :environment_variable => "MYAPP_PORT" do |val|
+option "--port", "PORT", "the port to listen on", environment_variable: "MYAPP_PORT" do |val|
val.to_i
end
-parameter "[HOST]", "server address", :environment_variable => "MYAPP_HOST"
+parameter "[HOST]", "server address", environment_variable: "MYAPP_HOST"
```
Clamp will check the specified envariables in the absence of values supplied on the command line, before looking for a default value.
-Declaring Subcommands
----------------------
+### Allowing options after parameters
+
+By default, Clamp only recognises options _before_ positional parameters.
+
+Some other option-parsing libraries - notably [GNU `getopt(3)`](https://www.gnu.org/software/libc/manual/html_node/Using-Getopt.html) - allow option and parameter arguments to appear in any order on the command-line, e.g.
+
+ foobar --foo=bar something --fnord=snuffle another-thing
+
+If you want Clamp to allow options and parameters to be "interspersed" in this way, set:
+
+```ruby
+Clamp.allow_options_after_parameters = true
+```
+
+## Declaring Subcommands
Subcommand support helps you wrap a number of related commands into a single script (ala tools like "`git`"). Clamp will inspect the first command-line argument (after options are parsed), and delegate to the named subcommand.
@@ -378,8 +395,7 @@ Clamp do
end
```
-Getting help
-------------
+## Getting help
All Clamp commands support a "`--help`" option, which outputs brief usage documentation, based on those seemingly useless extra parameters that you had to pass to `option` and `parameter`.
@@ -397,27 +413,27 @@ Options:
-h, --help print help
```
-Localization
-------------
+## Localization
Clamp comes with support for overriding strings with custom translations. You can use localization library of your choice and override the strings at startup.
Example usage:
+
```ruby
require 'gettext'
Clamp.messages = {
- :too_many_arguments => _("too many arguments"),
- :option_required => _("option '%<option>s' is required"),
- :option_or_env_required => _("option '%<option>s' (or env %<env>s) is required"),
- :option_argument_error => _("option '%<switch>s': %<message>s")
+ too_many_arguments: _("too many arguments"),
+ option_required: _("option '%<option>s' is required"),
+ option_or_env_required: _("option '%<option>s' (or env %<env>s) is required"),
+ option_argument_error: _("option '%<switch>s': %<message>s")
# ...
}
```
+
See [messages.rb](https://github.com/mdub/clamp/blob/master/lib/clamp/messages.rb) for full list of available messages.
-License
--------
+## License
Copyright (C) 2011 [Mike Williams](mailto:mdub@dogbiscuit.org)
@@ -438,7 +454,6 @@ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-Contributing to Clamp
----------------------
+## Contributing to Clamp
Source-code for Clamp is [on Github](https://github.com/mdub/clamp).
diff --git a/Rakefile b/Rakefile
index 079fbc0..fec582a 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,12 +1,18 @@
+# frozen_string_literal: true
+
require "bundler"
Bundler::GemHelper.install_tasks
require "rspec/core/rake_task"
-task "default" => "spec"
-
RSpec::Core::RakeTask.new do |t|
t.pattern = "spec/**/*_spec.rb"
t.rspec_opts = ["--colour", "--format", "documentation"]
end
+
+require "rubocop/rake_task"
+
+RuboCop::RakeTask.new
+
+task "default" => ["spec", "rubocop"]
diff --git a/clamp.gemspec b/clamp.gemspec
index 2a4e677..bb59525 100644
--- a/clamp.gemspec
+++ b/clamp.gemspec
@@ -1,4 +1,6 @@
-$LOAD_PATH.push File.expand_path("../lib", __FILE__)
+# frozen_string_literal: true
+
+$LOAD_PATH.push File.expand_path("lib", __dir__)
require "clamp/version"
Gem::Specification.new do |s|
@@ -8,18 +10,19 @@ Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.authors = ["Mike Williams"]
s.email = "mdub@dogbiscuit.org"
- s.homepage = "http://github.com/mdub/clamp"
+ s.homepage = "https://github.com/mdub/clamp"
s.license = "MIT"
s.summary = "a minimal framework for command-line utilities"
- s.description = <<EOF
-Clamp provides an object-model for command-line utilities.
-It handles parsing of command-line options, and generation of usage help.
-EOF
+ s.description = <<-TEXT.gsub(/^\s+/, "")
+ Clamp provides an object-model for command-line utilities.
+ It handles parsing of command-line options, and generation of usage help.
+ TEXT
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.require_paths = ["lib"]
+ s.required_ruby_version = ">= 2.5", "< 4"
end
diff --git a/debian/changelog b/debian/changelog
index 17b517a..26c6d24 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,4 @@
-ruby-clamp (1.1.1-2) UNRELEASED; urgency=medium
+ruby-clamp (1.3.2+git20220917.1.2bea305-1) UNRELEASED; urgency=medium
[ Utkarsh Gupta ]
* Add salsa-ci.yml
@@ -15,8 +15,9 @@ ruby-clamp (1.1.1-2) UNRELEASED; urgency=medium
* Use canonical URL in Vcs-Git.
* Apply multi-arch hints.
+ ruby-clamp: Add :any qualifier for ruby dependency.
+ * New upstream snapshot.
- -- Utkarsh Gupta <guptautkarsh2102@gmail.com> Tue, 13 Aug 2019 03:45:14 +0530
+ -- Utkarsh Gupta <guptautkarsh2102@gmail.com> Fri, 30 Dec 2022 03:47:05 -0000
ruby-clamp (1.1.1-1) unstable; urgency=low
diff --git a/examples/admin b/examples/admin
index 2adea32..4237d0a 100755
--- a/examples/admin
+++ b/examples/admin
@@ -1,17 +1,18 @@
#! /usr/bin/env ruby
+# frozen_string_literal: true
-# An example of subcommands
+# An example of options and parameters
require "clamp"
Clamp do
- option "--timeout", "SECONDS", "connection timeout", :default => 5, :environment_variable => "MYAPP_TIMEOUT" do |x|
+ option "--timeout", "SECONDS", "connection timeout", default: 5, environment_variable: "MYAPP_TIMEOUT" do |x|
Integer(x)
end
parameter "HOST", "server address"
- parameter "[PORT]", "server port", :default => 80, :environment_variable => "MYAPP_PORT"
+ parameter "[PORT]", "server port", default: 80, environment_variable: "MYAPP_PORT"
def execute
puts "trying to connect to #{host} on port #{port} (waiting up to #{timeout} seconds)"
diff --git a/examples/defaulted b/examples/defaulted
index 7c66bcb..938fd0e 100755
--- a/examples/defaulted
+++ b/examples/defaulted
@@ -1,4 +1,5 @@
#! /usr/bin/env ruby
+# frozen_string_literal: true
# An example of default values and methods
@@ -8,10 +9,10 @@ require "highline"
Clamp do
option ["-U", "--user"], "USER", "user name",
- :environment_variable => "THE_USER",
- :default => "bob"
+ environment_variable: "THE_USER",
+ default: "bob"
option ["-P", "--password"], "PASSWORD", "password",
- :environment_variable => "THE_PASSWORD"
+ environment_variable: "THE_PASSWORD"
def execute
puts "User: #{user}, Password: #{password}"
diff --git a/examples/flipflop b/examples/flipflop
index e9c72a5..109ca92 100755
--- a/examples/flipflop
+++ b/examples/flipflop
@@ -1,6 +1,7 @@
#! /usr/bin/env ruby
+# frozen_string_literal: true
-# An example of subcommands
+# An example of subcommands (including a default subcommand)
require "clamp"
require "clamp/version"
diff --git a/examples/fubar b/examples/fubar
index 681d1c4..953bf43 100755
--- a/examples/fubar
+++ b/examples/fubar
@@ -1,6 +1,7 @@
#! /usr/bin/env ruby
+# frozen_string_literal: true
-# An example of subcommands
+# An example of nested subcommands
require "clamp"
diff --git a/examples/gitdown b/examples/gitdown
index d8d2d54..2530b2c 100755
--- a/examples/gitdown
+++ b/examples/gitdown
@@ -1,4 +1,5 @@
#! /usr/bin/env ruby
+# frozen_string_literal: true
# Demonstrate how subcommands can be declared as classes
@@ -25,7 +26,7 @@ module GitDown
class CloneCommand < AbstractCommand
parameter "REPOSITORY", "repository to clone"
- parameter "[DIR]", "working directory", :default => "."
+ parameter "[DIR]", "working directory", default: "."
def execute
say "cloning to #{dir}"
diff --git a/examples/scoop b/examples/scoop
old mode 100644
new mode 100755
index 4861587..6505c55
--- a/examples/scoop
+++ b/examples/scoop
@@ -1,4 +1,5 @@
#! /usr/bin/env ruby
+# frozen_string_literal: true
# An example of multi-valued options
@@ -7,8 +8,8 @@ require "clamp"
Clamp do
option ["-f", "--flavour"], "FLAVOUR", "flavour",
- :multivalued => true, :default => ["chocolate"],
- :attribute_name => :flavours
+ multivalued: true, default: ["chocolate"],
+ attribute_name: :flavours
def execute
puts "one #{flavours.join(' and ')} ice-cream"
diff --git a/examples/speak b/examples/speak
index aa8d7b3..9890207 100755
--- a/examples/speak
+++ b/examples/speak
@@ -1,4 +1,5 @@
#! /usr/bin/env ruby
+# frozen_string_literal: true
# A simple Clamp command, with options and parameters
@@ -11,11 +12,11 @@ Clamp do
)
option "--loud", :flag, "say it loud"
- option ["-n", "--iterations"], "N", "say it N times", :default => 1 do |s|
+ option ["-n", "--iterations"], "N", "say it N times", default: 1 do |s|
Integer(s)
end
- parameter "WORDS ...", "the thing to say", :attribute_name => :words
+ parameter "WORDS ...", "the thing to say", attribute_name: :words
def execute
the_truth = words.join(" ")
diff --git a/examples/subcommand_missing b/examples/subcommand_missing
index 25d4264..aef78a6 100755
--- a/examples/subcommand_missing
+++ b/examples/subcommand_missing
@@ -1,4 +1,7 @@
#! /usr/bin/env ruby
+# frozen_string_literal: true
+
+# Demonstrate subcommand_missing
require "clamp"
diff --git a/examples/word b/examples/word
new file mode 100755
index 0000000..ea60d6a
--- /dev/null
+++ b/examples/word
@@ -0,0 +1,28 @@
+#! /usr/bin/env ruby
+# frozen_string_literal: true
+
+# Demonstrate "restful" style; parameters precede subcommands
+
+require "clamp"
+
+Clamp do
+
+ banner %(
+ Word ops.
+ )
+
+ parameter "WORD", "the word in question"
+
+ subcommand "say", "Say it" do
+ def execute
+ puts word
+ end
+ end
+
+ subcommand "shout", "Say it loud" do
+ def execute
+ puts word.upcase
+ end
+ end
+
+end
diff --git a/lib/clamp.rb b/lib/clamp.rb
index ef6d3b1..c35fd6a 100644
--- a/lib/clamp.rb
+++ b/lib/clamp.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
require "clamp/version"
require "clamp/command"
-def Clamp(&block)
+def Clamp(&block) # rubocop:disable Naming/MethodName
Class.new(Clamp::Command, &block).run
end
diff --git a/lib/clamp/attribute/declaration.rb b/lib/clamp/attribute/declaration.rb
index c146c9d..ce0ab0d 100644
--- a/lib/clamp/attribute/declaration.rb
+++ b/lib/clamp/attribute/declaration.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
module Clamp
module Attribute
+ # Methods to generate attribute accessors.
+ #
module Declaration
protected
@@ -25,6 +29,8 @@ module Clamp
end
def define_default_for(attribute)
+ return false if attribute.default_value.nil?
+
define_method(attribute.default_method) do
attribute.default_value
end
diff --git a/lib/clamp/attribute/definition.rb b/lib/clamp/attribute/definition.rb
index de90048..b56d435 100644
--- a/lib/clamp/attribute/definition.rb
+++ b/lib/clamp/attribute/definition.rb
@@ -1,25 +1,28 @@
+# frozen_string_literal: true
+
require "clamp/attribute/instance"
module Clamp
module Attribute
+ # Represents an attribute of a Clamp::Command class.
+ #
class Definition
def initialize(options)
- if options.key?(:attribute_name)
- @attribute_name = options[:attribute_name].to_s
- end
+ @attribute_name = options[:attribute_name].to_s if options.key?(:attribute_name)
@default_value = options[:default] if options.key?(:default)
- if options.key?(:environment_variable)
- @environment_variable = options[:environment_variable]
- end
+ @environment_variable = options[:environment_variable] if options.key?(:environment_variable)
@hidden = options[:hidden] if options.key?(:hidden)
end
attr_reader :description, :environment_variable
def help_rhs
- description + default_description
+ rhs = description
+ comments = [required_indicator, default_description].compact
+ rhs += " (#{comments.join(', ')})" unless comments.empty?
+ rhs
end
def help
@@ -51,11 +54,11 @@ module Clamp
end
def required?
- @required
+ @required ||= false
end
def hidden?
- @hidden
+ @hidden ||= false
end
def attribute_name
@@ -74,6 +77,17 @@ module Clamp
Attribute::Instance.new(self, command)
end
+ def option_missing_message
+ if environment_variable
+ Clamp.message(:option_or_env_required,
+ option: switches.first,
+ env: environment_variable)
+ else
+ Clamp.message(:option_required,
+ option: switches.first)
+ end
+ end
+
private
def default_description
@@ -81,8 +95,10 @@ module Clamp
("$#{@environment_variable}" if defined?(@environment_variable)),
(@default_value.inspect if defined?(@default_value))
].compact
- return "" if default_sources.empty?
- " (default: " + default_sources.join(", or ") + ")"
+
+ return nil if default_sources.empty?
+
+ "#{Clamp.message(:default)}: " + default_sources.join(", #{Clamp.message(:or)} ")
end
end
diff --git a/lib/clamp/attribute/instance.rb b/lib/clamp/attribute/instance.rb
index 7669d16..f831f0b 100644
--- a/lib/clamp/attribute/instance.rb
+++ b/lib/clamp/attribute/instance.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
module Clamp
module Attribute
- # Represents an option/parameter of a Clamp::Command instance.
+ # Represents an attribute of a Clamp::Command instance.
#
class Instance
@@ -27,7 +29,7 @@ module Clamp
end
def default
- command.send(attribute.default_method)
+ command.send(attribute.default_method) if command.respond_to?(attribute.default_method, true)
end
# default implementation of read_method
@@ -60,19 +62,40 @@ module Clamp
end
end
+ def signal_usage_error(*args)
+ command.send(:signal_usage_error, *args)
+ end
+
def default_from_environment
return if self.defined?
return if attribute.environment_variable.nil?
return unless ENV.key?(attribute.environment_variable)
+
# Set the parameter value if it's environment variable is present
value = ENV[attribute.environment_variable]
begin
take(value)
rescue ArgumentError => e
- command.send(:signal_usage_error, Clamp.message(:env_argument_error, :env => attribute.environment_variable, :message => e.message))
+ signal_usage_error Clamp.message(:env_argument_error, env: attribute.environment_variable, message: e.message)
end
end
+ def unset?
+ if attribute.multivalued?
+ read.empty?
+ else
+ read.nil?
+ end
+ end
+
+ def missing?
+ attribute.required? && unset?
+ end
+
+ def verify_not_missing
+ signal_usage_error attribute.option_missing_message if missing?
+ end
+
end
end
diff --git a/lib/clamp/command.rb b/lib/clamp/command.rb
index 7c558fb..ff5d68e 100644
--- a/lib/clamp/command.rb
+++ b/lib/clamp/command.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "clamp/messages"
require "clamp/errors"
require "clamp/help"
@@ -48,6 +50,7 @@ module Clamp
parse_options
parse_parameters
parse_subcommand
+ verify_required_options_are_set
handle_remaining_arguments
end
@@ -81,7 +84,7 @@ module Clamp
#
# @ param [String] name subcommand_name
def subcommand_missing(name)
- signal_usage_error(Clamp.message(:no_such_subcommand, :name => name))
+ signal_usage_error(Clamp.message(:no_such_subcommand, name: name))
end
include Clamp::Option::Parsing
@@ -122,6 +125,11 @@ module Clamp
include Clamp::Subcommand::Declaration
include Help
+ # An alternative to "def execute"
+ def execute(&block)
+ define_method(:execute, &block)
+ end
+
# Create an instance of this command class, and run it.
#
# @param [String] invocation_path the path used to invoke the command
diff --git a/lib/clamp/errors.rb b/lib/clamp/errors.rb
index 9c5ccbb..8b3d257 100644
--- a/lib/clamp/errors.rb
+++ b/lib/clamp/errors.rb
@@ -1,8 +1,12 @@
+# frozen_string_literal: true
+
module Clamp
+ # raised to indicate invalid option/parameter declaration
class DeclarationError < StandardError
end
+ # abstract command runtime error
class RuntimeError < StandardError
def initialize(message, command)
@@ -14,10 +18,10 @@ module Clamp
end
- # raise to signal incorrect command usage
+ # raised to signal incorrect command usage
class UsageError < RuntimeError; end
- # raise to request usage help
+ # raised to request usage help
class HelpWanted < RuntimeError
def initialize(command)
@@ -26,7 +30,7 @@ module Clamp
end
- # raise to signal error during execution
+ # raised to signal error during execution
class ExecutionError < RuntimeError
def initialize(message, command, status = 1)
diff --git a/lib/clamp/help.rb b/lib/clamp/help.rb
index 775d4b1..b729435 100644
--- a/lib/clamp/help.rb
+++ b/lib/clamp/help.rb
@@ -1,8 +1,12 @@
+# frozen_string_literal: true
+
require "stringio"
require "clamp/messages"
module Clamp
+ # Command help generation.
+ #
module Help
def usage(usage)
@@ -10,7 +14,7 @@ module Clamp
@declared_usage_descriptions << usage
end
- attr_reader :declared_usage_descriptions
+ attr_reader :declared_usage_descriptions, :description
def description=(description)
@description = description.dup
@@ -25,8 +29,6 @@ module Clamp
self.description = description
end
- attr_reader :description
-
def derived_usage_description
parts = ["[OPTIONS]"]
parts += parameters.map(&:name)
@@ -41,47 +43,67 @@ module Clamp
help = builder
help.add_usage(invocation_path, usage_descriptions)
help.add_description(description)
- if has_parameters?
- help.add_list(Clamp.message(:parameters_heading), parameters)
- end
- if has_subcommands?
- help.add_list(Clamp.message(:subcommands_heading), recognised_subcommands)
- end
+ help.add_list(Clamp.message(:parameters_heading), parameters) if has_parameters?
+ help.add_list(Clamp.message(:subcommands_heading), recognised_subcommands) if has_subcommands?
help.add_list(Clamp.message(:options_heading), recognised_options)
help.string
end
+ # A builder for auto-generated help.
+ #
class Builder
def initialize
- @out = StringIO.new
+ @lines = []
end
def string
- @out.string
+ left_column_width = lines.grep(Array).map(&:first).map(&:size).max
+ StringIO.new.tap do |out|
+ lines.each do |line|
+ case line
+ when Array
+ line[0] = line[0].ljust(left_column_width)
+ line.unshift("")
+ out.puts(line.join(" "))
+ else
+ out.puts(line)
+ end
+ end
+ end.string
+ end
+
+ def line(text = "")
+ @lines << text
+ end
+
+ def row(lhs, rhs)
+ @lines << [lhs, rhs]
end
def add_usage(invocation_path, usage_descriptions)
- puts Clamp.message(:usage_heading) + ":"
+ line "#{Clamp.message(:usage_heading)}:"
usage_descriptions.each do |usage|
- puts " #{invocation_path} #{usage}".rstrip
+ line " #{invocation_path} #{usage}".rstrip
end
end
def add_description(description)
return unless description
- puts ""
- puts description.gsub(/^/, " ")
+
+ line
+ line description.gsub(/^/, " ")
end
- DETAIL_FORMAT = " %-29s %s".freeze
+ DETAIL_FORMAT = " %-29s %s"
def add_list(heading, items)
- puts "\n#{heading}:"
+ line
+ line "#{heading}:"
items.reject { |i| i.respond_to?(:hidden?) && i.hidden? }.each do |item|
label, description = item.help
description.each_line do |line|
- puts format(DETAIL_FORMAT, label, line)
+ row(label, line)
label = ""
end
end
@@ -89,9 +111,7 @@ module Clamp
private
- def puts(*args)
- @out.puts(*args)
- end
+ attr_accessor :lines
end
diff --git a/lib/clamp/messages.rb b/lib/clamp/messages.rb
index 74be72d..9641907 100644
--- a/lib/clamp/messages.rb
+++ b/lib/clamp/messages.rb
@@ -1,5 +1,9 @@
-module Clamp
+# frozen_string_literal: true
+module Clamp # :nodoc:
+
+ # Message lookup, to allow localization.
+ #
module Messages
def messages=(new_messages)
@@ -7,7 +11,10 @@ module Clamp
end
def message(key, options = {})
- format(messages.fetch(key), options)
+ string = messages.fetch(key)
+ return string if options.empty?
+
+ format string, options
end
def clear_messages!
@@ -17,19 +24,22 @@ module Clamp
private
DEFAULTS = {
- :too_many_arguments => "too many arguments",
- :option_required => "option '%<option>s' is required",
- :option_or_env_required => "option '%<option>s' (or env %<env>s) is required",
- :option_argument_error => "option '%<switch>s': %<message>s",
- :parameter_argument_error => "parameter '%<param>s': %<message>s",
- :env_argument_error => "$%<env>s: %<message>s",
- :unrecognised_option => "Unrecognised option '%<switch>s'",
- :no_such_subcommand => "No such sub-command '%<name>s'",
- :no_value_provided => "no value provided",
- :usage_heading => "Usage",
- :parameters_heading => "Parameters",
- :subcommands_heading => "Subcommands",
- :options_heading => "Options"
+ too_many_arguments: "too many arguments",
+ option_required: "option '%<option>s' is required",
+ option_or_env_required: "option '%<option>s' (or env %<env>s) is required",
+ option_argument_error: "option '%<switch>s': %<message>s",
+ parameter_argument_error: "parameter '%<param>s': %<message>s",
+ env_argument_error: "$%<env>s: %<message>s",
+ unrecognised_option: "Unrecognised option '%<switch>s'",
+ no_such_subcommand: "No such sub-command '%<name>s'",
+ no_value_provided: "no value provided",
+ default: "default",
+ or: "or",
+ required: "required",
+ usage_heading: "Usage",
+ parameters_heading: "Parameters",
+ subcommands_heading: "Subcommands",
+ options_heading: "Options"
}.freeze
def messages
diff --git a/lib/clamp/option/declaration.rb b/lib/clamp/option/declaration.rb
index 3b3833a..180aece 100644
--- a/lib/clamp/option/declaration.rb
+++ b/lib/clamp/option/declaration.rb
@@ -1,9 +1,13 @@
+# frozen_string_literal: true
+
require "clamp/attribute/declaration"
require "clamp/option/definition"
module Clamp
module Option
+ # Option declaration methods.
+ #
module Declaration
include Clamp::Attribute::Declaration
@@ -25,7 +29,7 @@ module Clamp
end
def recognised_options
- unless @implicit_options_declared
+ unless @implicit_options_declared ||= false
declare_implicit_help_option
@implicit_options_declared = true
end
@@ -36,6 +40,7 @@ module Clamp
def declare_implicit_help_option
return false if effective_options.find { |o| o.handles?("--help") }
+
help_switches = ["--help"]
help_switches.unshift("-h") unless effective_options.find { |o| o.handles?("-h") }
option help_switches, :flag, "print help" do
@@ -51,6 +56,7 @@ module Clamp
def options_declared_on(ancestor)
return [] unless ancestor.is_a?(Clamp::Option::Declaration)
+
ancestor.declared_options
end
diff --git a/lib/clamp/option/definition.rb b/lib/clamp/option/definition.rb
index 75c3fda..1c2a749 100644
--- a/lib/clamp/option/definition.rb
+++ b/lib/clamp/option/definition.rb
@@ -1,9 +1,13 @@
+# frozen_string_literal: true
+
require "clamp/attribute/definition"
require "clamp/truthy"
module Clamp
module Option
+ # Represents an option of a Clamp::Command class.
+ #
class Definition < Attribute::Definition
def initialize(switches, type, description, options = {})
@@ -12,7 +16,9 @@ module Clamp
@description = description
super(options)
@multivalued = options[:multivalued]
+
return unless options.key?(:required)
+
@required = options[:required]
# Do some light validation for conflicting settings.
raise ArgumentError, "Specifying a :default value with :required doesn't make sense" if options.key?(:default)
@@ -39,7 +45,7 @@ module Clamp
def read_method
if flag?
- super + "?"
+ "#{super}?"
else
super
end
@@ -59,7 +65,7 @@ module Clamp
def help_lhs
lhs = switches.join(", ")
- lhs += " " + type unless flag?
+ lhs += " #{type}" unless flag?
lhs
end
@@ -76,14 +82,17 @@ module Clamp
end
def infer_attribute_name
- unless long_switch
- raise Clamp::DeclarationError, "You must specify either a long-switch or an :attribute_value"
- end
+ raise Clamp::DeclarationError, "You must specify either a long-switch or an :attribute_value" unless long_switch
+
inferred_name = long_switch.sub(/^--(\[no-\])?/, "").tr("-", "_")
inferred_name += "_list" if multivalued?
inferred_name
end
+ def required_indicator
+ Clamp.message(:required) if required?
+ end
+
end
end
diff --git a/lib/clamp/option/parsing.rb b/lib/clamp/option/parsing.rb
index b854824..3b21fb0 100644
--- a/lib/clamp/option/parsing.rb
+++ b/lib/clamp/option/parsing.rb
@@ -1,6 +1,17 @@
-module Clamp
+# frozen_string_literal: true
+
+module Clamp # :nodoc:
+
+ class << self
+
+ attr_accessor :allow_options_after_parameters
+
+ end
+
module Option
+ # Option parsing methods.
+ #
module Parsing
protected
@@ -8,40 +19,56 @@ module Clamp
def parse_options
set_options_from_command_line
default_options_from_environment
- verify_required_options_are_set
end
private
def set_options_from_command_line
- while remaining_arguments.first && remaining_arguments.first.start_with?("-")
+ argument_buffer = []
+ argument_buffer_limit = self.class.parameter_buffer_limit
+ until remaining_arguments.empty?
+ unless remaining_arguments.first.start_with?("-")
+ break unless argument_buffer.size < argument_buffer_limit
+
+ argument_buffer << remaining_arguments.shift
+
+ next
+ end
switch = remaining_arguments.shift
+
break if switch == "--"
- case switch
- when /\A(-\w)(.+)\z/m # combined short options
- switch = Regexp.last_match(1)
- if find_option(switch).flag?
- remaining_arguments.unshift("-" + Regexp.last_match(2))
- else
- remaining_arguments.unshift(Regexp.last_match(2))
- end
- when /\A(--[^=]+)=(.*)\z/m
- switch = Regexp.last_match(1)
- remaining_arguments.unshift(Regexp.last_match(2))
- end
+ handle_switch(switch)
+ end
+ remaining_arguments.unshift(*argument_buffer)
+ end
- option = find_option(switch)
- value = option.extract_value(switch, remaining_arguments)
+ def handle_switch(switch)
+ switch = split_trailing_switches(switch)
+ option = find_option(switch)
+ value = option.extract_value(switch, remaining_arguments)
+ begin
+ option.of(self).take(value)
+ rescue ArgumentError => e
+ signal_usage_error Clamp.message(:option_argument_error, switch: switch, message: e.message)
+ end
+ end
- begin
- option.of(self).take(value)
- rescue ArgumentError => e
- signal_usage_error Clamp.message(:option_argument_error, :switch => switch, :message => e.message)
+ def split_trailing_switches(switch)
+ case switch
+ when /\A(-\w)(.+)\z/m # combined short options
+ switch = Regexp.last_match(1)
+ if find_option(switch).flag?
+ remaining_arguments.unshift("-#{Regexp.last_match(2)}")
+ else
+ remaining_arguments.unshift(Regexp.last_match(2))
end
-
+ when /\A(--[^=]+)=(.*)\z/m
+ switch = Regexp.last_match(1)
+ remaining_arguments.unshift(Regexp.last_match(2))
end
+ switch
end
def default_options_from_environment
@@ -52,26 +79,17 @@ module Clamp
def verify_required_options_are_set
self.class.recognised_options.each do |option|
- # If this option is required and the value is nil, there's an error.
- next unless option.required? && send(option.attribute_name).nil?
- if option.environment_variable
- message = Clamp.message(:option_or_env_required,
- :option => option.switches.first,
- :env => option.environment_variable)
- else
- message = Clamp.message(:option_required,
- :option => option.switches.first)
- end
- signal_usage_error message
+ option.of(self).verify_not_missing
end
end
def find_option(switch)
self.class.find_option(switch) ||
- signal_usage_error(Clamp.message(:unrecognised_option, :switch => switch))
+ signal_usage_error(Clamp.message(:unrecognised_option, switch: switch))
end
end
end
+
end
diff --git a/lib/clamp/parameter/declaration.rb b/lib/clamp/parameter/declaration.rb
index 3ad8ede..9c95881 100644
--- a/lib/clamp/parameter/declaration.rb
+++ b/lib/clamp/parameter/declaration.rb
@@ -1,9 +1,13 @@
+# frozen_string_literal: true
+
require "clamp/attribute/declaration"
require "clamp/parameter/definition"
module Clamp
module Parameter
+ # Parameter declaration methods.
+ #
module Declaration
include Clamp::Attribute::Declaration
@@ -23,16 +27,23 @@ module Clamp
end
end
- protected
-
def inheritable_parameters
superclass_inheritable_parameters + parameters.select(&:inheritable?)
end
+ def parameter_buffer_limit
+ return 0 unless Clamp.allow_options_after_parameters
+
+ return Float::INFINITY if inheritable_parameters.any?(&:multivalued?)
+
+ inheritable_parameters.size
+ end
+
private
def superclass_inheritable_parameters
return [] unless superclass.respond_to?(:inheritable_parameters, true)
+
superclass.inheritable_parameters
end
diff --git a/lib/clamp/parameter/definition.rb b/lib/clamp/parameter/definition.rb
index e48b41d..16837f0 100644
--- a/lib/clamp/parameter/definition.rb
+++ b/lib/clamp/parameter/definition.rb
@@ -1,8 +1,12 @@
+# frozen_string_literal: true
+
require "clamp/attribute/definition"
module Clamp
module Parameter
+ # Represents an parameter of a Clamp::Command class.
+ #
class Definition < Attribute::Definition
def initialize(name, description, options = {})
@@ -28,25 +32,30 @@ module Clamp
def consume(arguments)
raise ArgumentError, Clamp.message(:no_value_provided) if required? && arguments.empty?
+
arguments.shift(multivalued? ? arguments.length : 1)
end
private
- ELLIPSIS_SUFFIX = / \.\.\.$/
- OPTIONAL = /^\[(.*)\]/
+ ELLIPSIS_SUFFIX = / \.\.\.$/.freeze
+ OPTIONAL = /^\[(.*)\]/.freeze
- VALID_ATTRIBUTE_NAME = /^[a-z0-9_]+$/
+ VALID_ATTRIBUTE_NAME = /^[a-z0-9_]+$/.freeze
def infer_attribute_name
inferred_name = name.downcase.tr("-", "_").sub(ELLIPSIS_SUFFIX, "").sub(OPTIONAL) { Regexp.last_match(1) }
- unless inferred_name =~ VALID_ATTRIBUTE_NAME
- raise "cannot infer attribute_name from #{name.inspect}"
- end
+
+ raise "cannot infer attribute_name from #{name.inspect}" unless inferred_name.match? VALID_ATTRIBUTE_NAME
+
inferred_name += "_list" if multivalued?
inferred_name
end
+ def required_indicator
+ # implied by LHS
+ end
+
end
end
diff --git a/lib/clamp/parameter/parsing.rb b/lib/clamp/parameter/parsing.rb
index e36540f..842745b 100644
--- a/lib/clamp/parameter/parsing.rb
+++ b/lib/clamp/parameter/parsing.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
module Clamp
module Parameter
+ # Parameter parsing methods.
+ #
module Parsing
protected
@@ -14,13 +18,11 @@ module Clamp
def set_parameters_from_command_line
self.class.parameters.each do |parameter|
- begin
- parameter.consume(remaining_arguments).each do |value|
- parameter.of(self).take(value)
- end
- rescue ArgumentError => e
- signal_usage_error Clamp.message(:parameter_argument_error, :param => parameter.name, :message => e.message)
+ parameter.consume(remaining_arguments).each do |value|
+ parameter.of(self).take(value)
end
+ rescue ArgumentError => e
+ signal_usage_error Clamp.message(:parameter_argument_error, param: parameter.name, message: e.message)
end
end
diff --git a/lib/clamp/subcommand/declaration.rb b/lib/clamp/subcommand/declaration.rb
index 01f6306..2965840 100644
--- a/lib/clamp/subcommand/declaration.rb
+++ b/lib/clamp/subcommand/declaration.rb
@@ -1,9 +1,13 @@
+# frozen_string_literal: true
+
require "clamp/errors"
require "clamp/subcommand/definition"
module Clamp
module Subcommand
+ # Subcommand declaration methods.
+ #
module Declaration
def recognised_subcommands
@@ -27,8 +31,9 @@ module Clamp
def find_subcommand_class(*names)
names.inject(self) do |command_class, name|
return nil unless command_class
+
subcommand = command_class.find_subcommand(name)
- subcommand.subcommand_class if subcommand
+ subcommand&.subcommand_class
end
end
@@ -37,18 +42,17 @@ module Clamp
end
def default_subcommand=(name)
- if has_subcommands?
- raise Clamp::DeclarationError, "default_subcommand must be defined before subcommands"
- end
+ raise Clamp::DeclarationError, "default_subcommand must be defined before subcommands" if has_subcommands?
+
@default_subcommand = name
end
def default_subcommand(*args, &block)
if args.empty?
- @default_subcommand
+ @default_subcommand ||= false
else
$stderr.puts "WARNING: Clamp default_subcommand syntax has changed; check the README."
- $stderr.puts " (from #{caller.first})"
+ $stderr.puts " (from #{caller(1..1).first})"
self.default_subcommand = args.first
subcommand(*args, &block)
end
@@ -57,21 +61,21 @@ module Clamp
private
def declare_subcommand_parameters
- if @default_subcommand
+ if default_subcommand
parameter "[SUBCOMMAND]", "subcommand",
- :attribute_name => :subcommand_name,
- :default => @default_subcommand,
- :inheritable => false
+ attribute_name: :subcommand_name,
+ default: default_subcommand,
+ inheritable: false
else
parameter "SUBCOMMAND", "subcommand",
- :attribute_name => :subcommand_name,
- :required => false,
- :inheritable => false
+ attribute_name: :subcommand_name,
+ required: false,
+ inheritable: false
end
- remove_method :default_subcommand_name
+ remove_method :default_subcommand_name if method_defined?(:default_subcommand_name)
parameter "[ARG] ...", "subcommand arguments",
- :attribute_name => :subcommand_arguments,
- :inheritable => false
+ attribute_name: :subcommand_arguments,
+ inheritable: false
end
end
diff --git a/lib/clamp/subcommand/definition.rb b/lib/clamp/subcommand/definition.rb
index 2aa1415..30c3ee6 100644
--- a/lib/clamp/subcommand/definition.rb
+++ b/lib/clamp/subcommand/definition.rb
@@ -1,16 +1,15 @@
+# frozen_string_literal: true
+
module Clamp
module Subcommand
- Definition = Struct.new(:name, :description, :subcommand_class) do
+ Definition = Struct.new(:names, :description, :subcommand_class) do
def initialize(names, description, subcommand_class)
- @names = Array(names)
- @description = description
- @subcommand_class = subcommand_class
+ names = Array(names)
+ super
end
- attr_reader :names, :description, :subcommand_class
-
def is_called?(name)
names.member?(name)
end
diff --git a/lib/clamp/subcommand/execution.rb b/lib/clamp/subcommand/execution.rb
index 10541e5..6308787 100644
--- a/lib/clamp/subcommand/execution.rb
+++ b/lib/clamp/subcommand/execution.rb
@@ -1,6 +1,13 @@
+# frozen_string_literal: true
+
module Clamp
module Subcommand
+ # Support for subcommand execution.
+ #
+ # This module is mixed into command instances that have subcommands, overriding
+ # default behaviour in {Clamp::Command}.
+ #
module Execution
# override default Command behaviour
@@ -15,20 +22,31 @@ module Clamp
def instantiate_subcommand(name)
subcommand_class = find_subcommand_class(name)
- subcommand = subcommand_class.new("#{invocation_path} #{name}", context)
+ subcommand = subcommand_class.new(invocation_path_for(name), context)
self.class.inheritable_attributes.each do |attribute|
next unless attribute.of(self).defined?
+
attribute.of(subcommand).set(attribute.of(self).get)
end
subcommand
end
+ def invocation_path_for(name)
+ param_names = self.class.parameters.select(&:inheritable?).map(&:name)
+ [invocation_path, *param_names, name].join(" ")
+ end
+
def find_subcommand_class(name)
subcommand_def = self.class.find_subcommand(name)
return subcommand_def.subcommand_class if subcommand_def
+
subcommand_missing(name)
end
+ def verify_required_options_are_set
+ # not required
+ end
+
end
end
diff --git a/lib/clamp/subcommand/parsing.rb b/lib/clamp/subcommand/parsing.rb
index 4415956..fca5670 100644
--- a/lib/clamp/subcommand/parsing.rb
+++ b/lib/clamp/subcommand/parsing.rb
@@ -1,14 +1,19 @@
+# frozen_string_literal: true
+
require "clamp/subcommand/execution"
module Clamp
module Subcommand
+ # Subcommand parsing methods.
+ #
module Parsing
protected
def parse_subcommand
return false unless self.class.has_subcommands?
+
extend(Subcommand::Execution)
end
diff --git a/lib/clamp/truthy.rb b/lib/clamp/truthy.rb
index ac6b07c..23671ac 100644
--- a/lib/clamp/truthy.rb
+++ b/lib/clamp/truthy.rb
@@ -1,6 +1,8 @@
-module Clamp
+# frozen_string_literal: true
- TRUTHY_VALUES = %w(1 yes enable on true).freeze
+module Clamp # :nodoc:
+
+ TRUTHY_VALUES = %w[1 yes enable on true].freeze
def self.truthy?(arg)
TRUTHY_VALUES.include?(arg.to_s.downcase)
diff --git a/lib/clamp/version.rb b/lib/clamp/version.rb
index a2c7f18..4ac3ae1 100644
--- a/lib/clamp/version.rb
+++ b/lib/clamp/version.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Clamp
- VERSION = "1.1.1".freeze
+ VERSION = "1.3.2"
end
diff --git a/spec/clamp/command_group_spec.rb b/spec/clamp/command_group_spec.rb
index 73f6d16..00ca3c5 100644
--- a/spec/clamp/command_group_spec.rb
+++ b/spec/clamp/command_group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Clamp::Command do
@@ -27,17 +29,27 @@ describe Clamp::Command do
end
- it "delegates to sub-commands" do
+ describe "flip command" do
+ before do
+ command.run(["flip"])
+ end
- command.run(["flip"])
- expect(stdout).to match(/FLIPPED/)
+ it "delegates to sub-commands" do
+ expect(stdout).to match(/FLIPPED/)
+ end
+ end
- command.run(["flop"])
- expect(stdout).to match(/FLOPPED/)
+ describe "flop command" do
+ before do
+ command.run(["flop"])
+ end
+ it "delegates to sub-commands" do
+ expect(stdout).to match(/FLOPPED/)
+ end
end
- context "executed with no subcommand" do
+ context "when executed with no subcommand" do
it "triggers help" do
expect do
@@ -54,10 +66,7 @@ describe Clamp::Command do
end
it "lists subcommands" do
- help = command.help
- expect(help).to match(/Subcommands:/)
- expect(help).to match(/flip +flip it/)
- expect(help).to match(/flop +flop it/)
+ expect(command.help).to match(/Subcommands:\n +flip +flip it\n +flop +flop it/)
end
it "handles new lines in subcommand descriptions" do
@@ -93,13 +102,27 @@ describe Clamp::Command do
end
- it "responds to both aliases" do
+ describe "the first alias" do
+
+ before do
+ command.run(["say", "boo"])
+ end
+
+ it "responds to it" do
+ expect(stdout).to match(/boo/)
+ end
+
+ end
+
+ describe "the second alias" do
- command.run(["say", "boo"])
- expect(stdout).to match(/boo/)
+ before do
+ command.run(["talk", "jive"])
+ end
- command.run(["talk", "jive"])
- expect(stdout).to match(/jive/)
+ it "responds to it" do
+ expect(stdout).to match(/jive/)
+ end
end
@@ -122,8 +145,7 @@ describe Clamp::Command do
subcommand "bar", "Baaaa!" do
- def self.this_is_bar
- end
+ def self.this_is_bar; end
def execute
puts "FUBAR"
@@ -166,7 +188,7 @@ describe Clamp::Command do
end
- context "executed with no subcommand" do
+ context "when executed with no subcommand" do
it "invokes the default subcommand" do
command.run([])
@@ -191,7 +213,7 @@ describe Clamp::Command do
end
- context "executed with no subcommand" do
+ context "when executed with no subcommand" do
it "invokes the default subcommand" do
command.run([])
@@ -202,24 +224,26 @@ describe Clamp::Command do
end
- context "declaring a default subcommand after subcommands" do
+ context "when declaring a default subcommand after subcommands" do
- it "is not supported" do
+ let(:command) do
+ Class.new(Clamp::Command) do
- expect do
- Class.new(Clamp::Command) do
+ subcommand "status", "Show status" do
- subcommand "status", "Show status" do
+ def execute
+ puts "All good!"
+ end
- def execute
- puts "All good!"
- end
+ end
- end
+ end
+ end
- self.default_subcommand = "status"
+ it "is not supported" do
- end
+ expect do
+ command.default_subcommand = "status"
end.to raise_error(/default_subcommand must be defined before subcommands/)
end
@@ -250,12 +274,18 @@ describe Clamp::Command do
it "allows the parameter to be specified first" do
command.run(["dummy", "spit"])
- expect(stdout.strip).to eql "spat the dummy"
+ expect(stdout.strip).to eq "spat the dummy"
end
it "passes the parameter down the stack" do
command.run(["money", "say", "loud"])
- expect(stdout.strip).to eql "MONEY"
+ expect(stdout.strip).to eq "MONEY"
+ end
+
+ it "shows parameter in usage help" do
+ command.run(["stuff", "say", "loud", "--help"])
+ rescue Clamp::HelpWanted => e
+ expect(e.command.invocation_path).to eq "with THING say loud"
end
end
@@ -266,12 +296,12 @@ describe Clamp::Command do
speed_options = Module.new do
extend Clamp::Option::Declaration
- option "--speed", "SPEED", "how fast", :default => "slowly"
+ option "--speed", "SPEED", "how fast", default: "slowly"
end
Class.new(Clamp::Command) do
- option "--direction", "DIR", "which way", :default => "home"
+ option "--direction", "DIR", "which way", default: "home"
include speed_options
@@ -307,7 +337,7 @@ describe Clamp::Command do
end
it "has access to command context" do
- command = command_class.new("go", :motion => "wandering")
+ command = command_class.new("go", motion: "wandering")
command.run(["move"])
expect(stdout).to match(/wandering home/)
end
@@ -323,14 +353,13 @@ describe Clamp::Command do
end
subcommand "woohoohoo", "like weeheehee but with more o" do
- def execute
- end
+ def execute; end
end
end
it "only parses options once" do
command.run(["--json", '{"a":"b"}', "woohoohoo"])
- expect(stdout).to eql "parsing!"
+ expect(stdout).to eq "parsing!"
end
end
@@ -362,8 +391,7 @@ describe Clamp::Command do
end
end
- def execute
- end
+ def execute; end
end
end
@@ -371,23 +399,39 @@ describe Clamp::Command do
command_class.new("foo")
end
- it "should signal no such subcommand usage error" do
- expect { command.run(["foo"]) }.to raise_error(Clamp::UsageError) do |exception|
- expect(exception.message).to eq "No such sub-command 'foo'"
- end
+ it "signals no such subcommand usage error" do
+ expect { command.run(["foo"]) }.to raise_error(Clamp::UsageError, "No such sub-command 'foo'")
end
- it "should execute the subcommand missing method" do
+ it "executes the subcommand missing method" do
command.extend subcommand_missing
- expect { command.run(["foo"]) }.to raise_error(SystemExit)
- expect(stderr).to match(/there is no such thing/)
+ expect { command.run(["foo"]) }.to raise_error(SystemExit, /there is no such thing/)
end
- it "should use the subcommand class returned from subcommand_missing" do
+ it "uses the subcommand class returned from subcommand_missing" do
command.extend subcommand_missing_with_return
command.run(["foo"])
expect(stdout).to match(/known subcommand/)
end
+
+ end
+
+ context "with a subcommand and required options" do
+
+ given_command "movements" do
+ option "--direction", "N|S|E|W", "bearing", required: true
+ subcommand "hop", "Hop" do
+ def execute
+ puts "Hopping #{direction}"
+ end
+ end
+ end
+
+ it "allows options after the subcommand" do
+ command.run(%w[hop --direction south])
+ expect(stdout).to eq "Hopping south\n"
+ end
+
end
end
diff --git a/spec/clamp/option_module_spec.rb b/spec/clamp/command_option_module_spec.rb
similarity index 82%
rename from spec/clamp/option_module_spec.rb
rename to spec/clamp/command_option_module_spec.rb
index e932ad3..9614e37 100644
--- a/spec/clamp/option_module_spec.rb
+++ b/spec/clamp/command_option_module_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Clamp::Command do
@@ -10,7 +12,7 @@ describe Clamp::Command do
shared_options = Module.new do
extend Clamp::Option::Declaration
- option "--size", "SIZE", :default => 4
+ option "--size", "SIZE", default: 4
end
command_class = Class.new(Clamp::Command) do
@@ -29,7 +31,7 @@ describe Clamp::Command do
it "accepts options from included module" do
command.run(["--size", "42"])
- expect(stdout).to eql "size = 42\n"
+ expect(stdout).to eq "size = 42\n"
end
end
diff --git a/spec/clamp/command_option_reordering_spec.rb b/spec/clamp/command_option_reordering_spec.rb
new file mode 100644
index 0000000..8ce8559
--- /dev/null
+++ b/spec/clamp/command_option_reordering_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe Clamp::Command do
+
+ extend CommandFactory
+ include OutputCapture
+
+ context "with allow_options_after_parameters enabled" do
+
+ before do
+ Clamp.allow_options_after_parameters = true
+ end
+
+ after do
+ Clamp.allow_options_after_parameters = false
+ end
+
+ given_command("cmd") do
+
+ option ["-v", "--verbose"], :flag, "Be noisy"
+
+ subcommand "say", "Say something" do
+
+ option "--loud", :flag, "say it loud"
+
+ parameter "WORDS ...", "the thing to say", attribute_name: :words
+
+ def execute
+ message = words.join(" ")
+ message = message.upcase if loud?
+ message *= 3 if verbose?
+ $stdout.puts message
+ end
+
+ end
+
+ end
+
+ it "still works" do
+ command.run(%w[say foo])
+ expect(stdout).to eq "foo\n"
+ end
+
+ it "honours options after positional arguments" do
+ command.run(%w[say blah --verbose])
+ expect(stdout).to eq "blahblahblah\n"
+ end
+
+ it "honours options declared on subcommands" do
+ command.run(%w[say --loud blah])
+ expect(stdout).to eq "BLAH\n"
+ end
+
+ end
+
+end
diff --git a/spec/clamp/command_spec.rb b/spec/clamp/command_spec.rb
index 602aa29..5dd4c94 100644
--- a/spec/clamp/command_spec.rb
+++ b/spec/clamp/command_spec.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
require "spec_helper"
@@ -30,18 +31,35 @@ describe Clamp::Command do
end
it "executes the #execute method" do
- expect(stdout).to_not be_empty
+ expect(stdout).not_to be_empty
end
end
describe ".option" do
- it "declares option argument accessors" do
- command.class.option "--flavour", "FLAVOUR", "Flavour of the month"
- expect(command.flavour).to eql nil
- command.flavour = "chocolate"
- expect(command.flavour).to eql "chocolate"
+ context "when regular" do
+
+ before do
+ command.class.option "--flavour", "FLAVOUR", "Flavour of the month"
+ end
+
+ context "when not set" do
+ it "equals nil" do
+ expect(command.flavour).to be_nil
+ end
+ end
+
+ context "when set" do
+ before do
+ command.flavour = "chocolate"
+ end
+
+ it "equals the value" do
+ expect(command.flavour).to eq "chocolate"
+ end
+ end
+
end
context "with type :flag" do
@@ -50,9 +68,20 @@ describe Clamp::Command do
command.class.option "--verbose", :flag, "Be heartier"
end
- it "declares a predicate-style reader" do
- expect(command).to respond_to(:verbose?)
- expect(command).to_not respond_to(:verbose)
+ describe "predicate-style reader" do
+
+ it "exists" do
+ expect(command).to respond_to(:verbose?)
+ end
+
+ end
+
+ describe "regular reader" do
+
+ it "does not exist" do
+ expect(command).not_to respond_to(:verbose)
+ end
+
end
end
@@ -60,17 +89,24 @@ describe Clamp::Command do
context "with explicit :attribute_name" do
before do
- command.class.option "--foo", "FOO", "A foo", :attribute_name => :bar
+ command.class.option "--foo", "FOO", "A foo", attribute_name: :bar
end
it "uses the specified attribute_name name to name accessors" do
command.bar = "chocolate"
- expect(command.bar).to eql "chocolate"
+ expect(command.bar).to eq "chocolate"
+ end
+
+ describe "default reader" do
+ it "does not exist" do
+ expect(command).not_to respond_to(:foo)
+ end
end
- it "does not attempt to create the default accessors" do
- expect(command).to_not respond_to(:foo)
- expect(command).to_not respond_to(:foo=)
+ describe "default writer" do
+ it "does not exist" do
+ expect(command).not_to respond_to(:foo=)
+ end
end
end
@@ -87,7 +123,7 @@ describe Clamp::Command do
end
it "sets the specified default value" do
- expect(command.port).to eql 4321
+ expect(command.port).to eq 4321
end
end
@@ -95,11 +131,11 @@ describe Clamp::Command do
context "with :default value" do
before do
- command.class.option "--port", "PORT", "port to listen on", :default => 4321
+ command.class.option "--port", "PORT", "port to listen on", default: 4321
end
it "declares default method" do
- expect(command.default_port).to eql 4321
+ expect(command.default_port).to eq 4321
end
describe "#help" do
@@ -112,31 +148,49 @@ describe Clamp::Command do
end
+ context "without :default value" do
+
+ before do
+ command.class.option "--port", "PORT", "port to listen on"
+ end
+
+ it "does not declare default method" do
+ expect(command).not_to respond_to(:default_port)
+ end
+
+ end
+
context "with :multivalued" do
before do
- command.class.option "--flavour", "FLAVOUR", "flavour(s)", :multivalued => true, :attribute_name => :flavours
+ command.class.option "--flavour", "FLAVOUR", "flavour(s)", multivalued: true, attribute_name: :flavours
end
it "defaults to empty array" do
- expect(command.flavours).to eql []
+ expect(command.flavours).to be_empty
end
it "supports multiple values" do
- command.parse(%w(--flavour chocolate --flavour vanilla))
- expect(command.flavours).to eql %w(chocolate vanilla)
+ command.parse(%w[--flavour chocolate --flavour vanilla])
+ expect(command.flavours).to eq %w[chocolate vanilla]
end
it "generates a single-value appender method" do
command.append_to_flavours("mud")
command.append_to_flavours("pie")
- expect(command.flavours).to eql %w(mud pie)
+ expect(command.flavours).to eq %w[mud pie]
end
it "generates a multi-value setter method" do
command.append_to_flavours("replaceme")
- command.flavours = %w(mud pie)
- expect(command.flavours).to eql %w(mud pie)
+ command.flavours = %w[mud pie]
+ expect(command.flavours).to eq %w[mud pie]
+ end
+
+ it "does not require a value" do
+ expect do
+ command.parse([])
+ end.not_to raise_error
end
end
@@ -148,8 +202,8 @@ describe Clamp::Command do
before do
command.class.option "--port", "PORT", "port to listen on",
- :default => 4321,
- :environment_variable => "PORT",
+ default: 4321,
+ environment_variable: "PORT",
&:to_i
set_env("PORT", environment_value)
command.parse(args)
@@ -158,7 +212,7 @@ describe Clamp::Command do
context "when no environment variable is present" do
it "uses the default" do
- expect(command.port).to eql 4321
+ expect(command.port).to eq 4321
end
end
@@ -168,15 +222,15 @@ describe Clamp::Command do
let(:environment_value) { "12345" }
it "uses the environment variable" do
- expect(command.port).to eql 12_345
+ expect(command.port).to eq 12_345
end
- context "and a value is specified on the command-line" do
+ context "when a value is specified on the command-line" do
- let(:args) { %w(--port 1500) }
+ let(:args) { %w[--port 1500] }
it "uses command-line value" do
- expect(command.port).to eql 1500
+ expect(command.port).to eq 1500
end
end
@@ -198,7 +252,7 @@ describe Clamp::Command do
let(:environment_value) { nil }
before do
- command.class.option "--[no-]enable", :flag, "enable?", :default => false, :environment_variable => "ENABLE"
+ command.class.option "--[no-]enable", :flag, "enable?", default: false, environment_variable: "ENABLE"
set_env("ENABLE", environment_value)
command.parse([])
end
@@ -206,33 +260,33 @@ describe Clamp::Command do
context "when no environment variable is present" do
it "uses the default" do
- expect(command.enable?).to eql false
+ expect(command.enable?).to be false
end
end
- %w(1 yes enable on true).each do |truthy_value|
+ %w[1 yes enable on true].each do |truthy_value|
context "when environment variable is #{truthy_value.inspect}" do
let(:environment_value) { truthy_value }
it "sets the flag" do
- expect(command.enable?).to eql true
+ expect(command.enable?).to be true
end
end
end
- %w(0 no disable off false).each do |falsey_value|
+ %w[0 no disable off false].each do |falsey_value|
context "when environment variable is #{falsey_value.inspect}" do
let(:environment_value) { falsey_value }
it "clears the flag" do
- expect(command.enable?).to eql false
+ expect(command.enable?).to be false
end
end
@@ -244,7 +298,43 @@ describe Clamp::Command do
context "with :required" do
before do
- command.class.option "--port", "PORT", "port to listen on", :required => true
+ command.class.option "--port", "PORT", "port to listen on", required: true
+ end
+
+ describe "#help" do
+
+ it "marks it as required" do
+ expect(command.help).to include("port to listen on (required)")
+ end
+
+ end
+
+ context "when no value is provided" do
+
+ it "raises a UsageError" do
+ expect do
+ command.parse([])
+ end.to raise_error(Clamp::UsageError)
+ end
+
+ end
+
+ context "when a value is provided" do
+
+ it "does not raise an error" do
+ expect do
+ command.parse(["--port", "12345"])
+ end.not_to raise_error
+ end
+
+ end
+
+ end
+
+ context "with :required and :multivalued" do
+
+ before do
+ command.class.option "--port", "PORT", "port to listen on", required: true, multivalued: true
end
context "when no value is provided" do
@@ -277,12 +367,23 @@ describe Clamp::Command do
end
end
- it "uses the block to validate and convert the option argument" do
- expect do
- command.port = "blah"
- end.to raise_error(ArgumentError)
- command.port = "1234"
- expect(command.port).to eql 1234
+ context "when value is incorrect" do
+
+ it "raises an error" do
+ expect do
+ command.port = "blah"
+ end.to raise_error(ArgumentError)
+ end
+
+ end
+
+ context "when value is convertible" do
+
+ it "uses the block to validate and convert the option argument" do
+ command.port = "1234"
+ expect(command.port).to eq 1234
+ end
+
end
end
@@ -295,12 +396,12 @@ describe Clamp::Command do
command.class.option ["-f", "--flavour"], "FLAVOUR", "Flavour of the month"
command.class.option ["-c", "--color"], "COLOR", "Preferred hue"
command.class.option ["--scoops"], "N", "Number of scoops",
- :default => 1,
- :environment_variable => "DEFAULT_SCOOPS" do |arg|
+ default: 1,
+ environment_variable: "DEFAULT_SCOOPS" do |arg|
Integer(arg)
end
command.class.option ["-n", "--[no-]nuts"], :flag, "Nuts (or not)\nMay include nuts"
- command.class.parameter "[ARG] ...", "extra arguments", :attribute_name => :arguments
+ command.class.parameter "[ARG] ...", "extra arguments", attribute_name: :arguments
end
describe "#parse" do
@@ -309,7 +410,7 @@ describe Clamp::Command do
it "raises a UsageError" do
expect do
- command.parse(%w(--foo bar))
+ command.parse(%w[--foo bar])
end.to raise_error(Clamp::UsageError)
end
@@ -318,13 +419,25 @@ describe Clamp::Command do
context "with options" do
before do
- command.parse(%w(--flavour strawberry --nuts --color blue))
+ command.parse(%w[--flavour strawberry --nuts --color blue])
end
- it "maps the option values onto the command object" do
- expect(command.flavour).to eql "strawberry"
- expect(command.color).to eql "blue"
- expect(command.nuts?).to eql true
+ describe "flavour" do
+ it "maps the option value onto the command object" do
+ expect(command.flavour).to eq "strawberry"
+ end
+ end
+
+ describe "color" do
+ it "maps the option value onto the command object" do
+ expect(command.color).to eq "blue"
+ end
+ end
+
+ describe "nuts?" do
+ it "maps the option value onto the command object" do
+ expect(command.nuts?).to be true
+ end
end
end
@@ -332,12 +445,19 @@ describe Clamp::Command do
context "with short options" do
before do
- command.parse(%w(-f strawberry -c blue))
+ command.parse(%w[-f strawberry -c blue])
end
- it "recognises short options as aliases" do
- expect(command.flavour).to eql "strawberry"
- expect(command.color).to eql "blue"
+ describe "flavour" do
+ it "recognizes short options as aliases" do
+ expect(command.flavour).to eq "strawberry"
+ end
+ end
+
+ describe "color" do
+ it "recognizes short options as aliases" do
+ expect(command.color).to eq "blue"
+ end
end
end
@@ -345,11 +465,11 @@ describe Clamp::Command do
context "with a value appended to a short option" do
before do
- command.parse(%w(-fstrawberry))
+ command.parse(%w[-fstrawberry])
end
it "works as though the value were separated" do
- expect(command.flavour).to eql "strawberry"
+ expect(command.flavour).to eq "strawberry"
end
end
@@ -357,12 +477,19 @@ describe Clamp::Command do
context "with combined short options" do
before do
- command.parse(%w(-nf strawberry))
+ command.parse(%w[-nf strawberry])
end
- it "works as though the options were separate" do
- expect(command.flavour).to eql "strawberry"
- expect(command.nuts?).to eql true
+ describe "flavour" do
+ it "works as though the options were separate" do
+ expect(command.flavour).to eq "strawberry"
+ end
+ end
+
+ describe "nuts?" do
+ it "works as though the options were separate" do
+ expect(command.nuts?).to be true
+ end
end
end
@@ -370,12 +497,39 @@ describe Clamp::Command do
context "with option arguments attached using equals sign" do
before do
- command.parse(%w(--flavour=strawberry --color=blue))
+ command.parse(%w[--flavour=strawberry --color=blue])
end
- it "works as though the option arguments were separate" do
- expect(command.flavour).to eql "strawberry"
- expect(command.color).to eql "blue"
+ describe "flavour" do
+ it "maps the option value onto the command object" do
+ expect(command.flavour).to eq "strawberry"
+ end
+ end
+
+ describe "color" do
+ it "maps the option value onto the command object" do
+ expect(command.color).to eq "blue"
+ end
+ end
+
+ end
+
+ context "with option arguments that look like options" do
+
+ before do
+ command.parse(%w[--flavour=-dashing- --scoops -1])
+ end
+
+ describe "flavour" do
+ it "sets the options" do
+ expect(command.flavour).to eq("-dashing-")
+ end
+ end
+
+ describe "scoops" do
+ it "sets the options" do
+ expect(command.scoops).to eq(-1)
+ end
end
end
@@ -383,8 +537,8 @@ describe Clamp::Command do
context "with option-like things beyond the arguments" do
it "treats them as positional arguments" do
- command.parse(%w(a b c --flavour strawberry))
- expect(command.arguments).to eql %w(a b c --flavour strawberry)
+ command.parse(%w[a b c --flavour strawberry])
+ expect(command.arguments).to eq %w[a b c --flavour strawberry]
end
end
@@ -395,10 +549,22 @@ describe Clamp::Command do
command.parse(["foo\n--flavour=strawberry", "bar\n-cblue"])
end
- it "treats them as positional arguments" do
- expect(command.arguments).to eql ["foo\n--flavour=strawberry", "bar\n-cblue"]
- expect(command.flavour).to be_nil
- expect(command.color).to be_nil
+ describe "arguments" do
+ it "treats them as positional arguments" do
+ expect(command.arguments).to eq ["foo\n--flavour=strawberry", "bar\n-cblue"]
+ end
+ end
+
+ describe "flavour" do
+ it "treats them as positional arguments" do
+ expect(command.flavour).to be_nil
+ end
+ end
+
+ describe "color" do
+ it "treats them as positional arguments" do
+ expect(command.color).to be_nil
+ end
end
end
@@ -406,8 +572,8 @@ describe Clamp::Command do
context "with an option terminator" do
it "considers everything after the terminator to be an argument" do
- command.parse(%w(--color blue -- --flavour strawberry))
- expect(command.arguments).to eql %w(--flavour strawberry)
+ command.parse(%w[--color blue -- --flavour strawberry])
+ expect(command.arguments).to eq %w[--flavour strawberry]
end
end
@@ -415,7 +581,7 @@ describe Clamp::Command do
context "with --flag" do
before do
- command.parse(%w(--nuts))
+ command.parse(%w[--nuts])
end
it "sets the flag" do
@@ -428,7 +594,7 @@ describe Clamp::Command do
before do
command.nuts = true
- command.parse(%w(--no-nuts))
+ command.parse(%w[--no-nuts])
end
it "clears the flag" do
@@ -441,7 +607,7 @@ describe Clamp::Command do
it "requests help" do
expect do
- command.parse(%w(--help))
+ command.parse(%w[--help])
end.to raise_error(Clamp::HelpWanted)
end
@@ -451,7 +617,7 @@ describe Clamp::Command do
it "requests help" do
expect do
- command.parse(%w(-h))
+ command.parse(%w[-h])
end.to raise_error(Clamp::HelpWanted)
end
@@ -461,7 +627,7 @@ describe Clamp::Command do
it "signals a UsageError" do
expect do
- command.parse(%w(--scoops reginald))
+ command.parse(%w[--scoops reginald])
end.to raise_error(Clamp::UsageError, /^option '--scoops': invalid value for Integer/)
end
@@ -487,8 +653,10 @@ describe Clamp::Command do
end
it "includes option details" do
- expect(command.help).to match(/--flavour FLAVOUR +Flavour of the month/)
- expect(command.help).to match(/--color COLOR +Preferred hue/)
+ flavour_help = "-f, --flavour FLAVOUR +Flavour of the month"
+ color_help = "-c, --color COLOR +Preferred hue"
+
+ expect(command.help).to match(/#{flavour_help}\n +#{color_help}/)
end
it "handles new lines in option descriptions" do
@@ -505,16 +673,27 @@ describe Clamp::Command do
command.class.option ["--help"], :flag, "help wanted"
end
- it "does not generate implicit help option" do
- expect do
- command.parse(%w(--help))
- end.to_not raise_error
- expect(command.help?).to be true
+ describe "parsing" do
+ it "does not generate implicit help option" do
+ expect do
+ command.parse(%w[--help])
+ end.not_to raise_error
+ end
+ end
+
+ describe "help?" do
+ before do
+ command.parse(%w[--help])
+ end
+
+ it "generates custom help option" do
+ expect(command.help?).to be true
+ end
end
it "does not recognise -h" do
expect do
- command.parse(%w(-h))
+ command.parse(%w[-h])
end.to raise_error(Clamp::UsageError)
end
@@ -529,12 +708,12 @@ describe Clamp::Command do
end
it "does not map -h to help" do
- expect(command.help).to_not match(/-h[, ].*help/)
+ expect(command.help).not_to match(/-h[, ].*help/)
end
- it "still recognises --help" do
+ it "still recognizes --help" do
expect do
- command.parse(%w(--help))
+ command.parse(%w[--help])
end.to raise_error(Clamp::HelpWanted)
end
@@ -542,22 +721,39 @@ describe Clamp::Command do
describe ".parameter" do
- it "declares option argument accessors" do
- command.class.parameter "FLAVOUR", "flavour of the month"
- expect(command.flavour).to eql nil
- command.flavour = "chocolate"
- expect(command.flavour).to eql "chocolate"
+ context "when regular" do
+
+ before do
+ command.class.parameter "FLAVOUR", "flavour of the month"
+ end
+
+ context "when not set" do
+ it "equals nil" do
+ expect(command.flavour).to be_nil
+ end
+ end
+
+ context "when set" do
+ before do
+ command.flavour = "chocolate"
+ end
+
+ it "equals the value" do
+ expect(command.flavour).to eq "chocolate"
+ end
+ end
+
end
context "with explicit :attribute_name" do
before do
- command.class.parameter "FOO", "a foo", :attribute_name => :bar
+ command.class.parameter "FOO", "a foo", attribute_name: :bar
end
it "uses the specified attribute_name name to name accessors" do
command.bar = "chocolate"
- expect(command.bar).to eql "chocolate"
+ expect(command.bar).to eq "chocolate"
end
end
@@ -565,11 +761,11 @@ describe Clamp::Command do
context "with :default value" do
before do
- command.class.parameter "[ORIENTATION]", "direction", :default => "west"
+ command.class.parameter "[ORIENTATION]", "direction", default: "west"
end
it "sets the specified default value" do
- expect(command.orientation).to eql "west"
+ expect(command.orientation).to eq "west"
end
describe "#help" do
@@ -590,12 +786,23 @@ describe Clamp::Command do
end
end
- it "uses the block to validate and convert the argument" do
- expect do
- command.port = "blah"
- end.to raise_error(ArgumentError)
- command.port = "1234"
- expect(command.port).to eql 1234
+ context "when value is incorrect" do
+
+ it "raises an error" do
+ expect do
+ command.port = "blah"
+ end.to raise_error(ArgumentError)
+ end
+
+ end
+
+ context "when value is convertible" do
+
+ it "uses the block to validate and convert the argument" do
+ command.port = "1234"
+ expect(command.port).to eq 1234
+ end
+
end
end
@@ -607,51 +814,67 @@ describe Clamp::Command do
end
it "accepts multiple arguments" do
- command.parse(%w(X Y Z))
- expect(command.file_list).to eql %w(X Y Z)
+ command.parse(%w[X Y Z])
+ expect(command.file_list).to eq %w[X Y Z]
end
end
- context "optional, with ellipsis" do
+ context "when optional, with ellipsis" do
before do
command.class.parameter "[FILE] ...", "files"
- end
- it "defaults to an empty list" do
command.parse([])
- expect(command.default_file_list).to eql []
- expect(command.file_list).to eql []
end
- it "is mutable" do
- command.parse([])
- command.file_list << "treasure"
- expect(command.file_list).to eql ["treasure"]
+ describe "default_file_list" do
+
+ it "defaults to an empty list" do
+ expect(command.default_file_list).to be_empty
+ end
+
end
- end
+ describe "file_list" do
- context "with :environment_variable" do
+ it "defaults to an empty list" do
+ expect(command.file_list).to be_empty
+ end
- before do
- command.class.parameter "[FILE]", "a file", :environment_variable => "FILE",
- :default => "/dev/null"
end
- let(:args) { [] }
- let(:environment_value) { nil }
+ describe "mutation" do
+
+ before do
+ command.file_list << "treasure"
+ end
+
+ it "is mutable" do
+ expect(command.file_list).to eq ["treasure"]
+ end
+
+ end
+
+ end
+
+ context "with :environment_variable" do
before do
+ command.class.parameter "[FILE]", "a file", environment_variable: "FILE",
+ default: "/dev/null"
+
set_env("FILE", environment_value)
command.parse(args)
end
+ let(:args) { [] }
+ let(:environment_value) { nil }
+
context "when neither argument nor environment variable are present" do
it "uses the default" do
- expect(command.file).to eql "/dev/null"
+ expect(command.file).to eq "/dev/null"
end
end
@@ -663,7 +886,7 @@ describe Clamp::Command do
describe "and no argument is provided" do
it "uses the environment variable" do
- expect(command.file).to eql "/etc/motd"
+ expect(command.file).to eq "/etc/motd"
end
end
@@ -673,7 +896,7 @@ describe Clamp::Command do
let(:args) { ["/dev/null"] }
it "uses the argument" do
- expect(command.file).to eql "/dev/null"
+ expect(command.file).to eq "/dev/null"
end
end
@@ -715,7 +938,7 @@ describe Clamp::Command do
before do
command.class.parameter "X", "x\nxx"
command.class.parameter "Y", "y"
- command.class.parameter "[Z]", "z", :default => "ZZZ"
+ command.class.parameter "[Z]", "z", default: "ZZZ"
end
describe "#parse" do
@@ -726,10 +949,22 @@ describe Clamp::Command do
command.parse(["crash", "bang", "wallop"])
end
- it "maps arguments onto the command object" do
- expect(command.x).to eql "crash"
- expect(command.y).to eql "bang"
- expect(command.z).to eql "wallop"
+ describe "x" do
+ it "maps arguments onto the command object" do
+ expect(command.x).to eq "crash"
+ end
+ end
+
+ describe "y" do
+ it "maps arguments onto the command object" do
+ expect(command.y).to eq "bang"
+ end
+ end
+
+ describe "z" do
+ it "maps arguments onto the command object" do
+ expect(command.z).to eq "wallop"
+ end
end
end
@@ -746,22 +981,52 @@ describe Clamp::Command do
context "with optional argument omitted" do
- it "defaults the optional argument" do
+ before do
command.parse(["crash", "bang"])
- expect(command.x).to eql "crash"
- expect(command.y).to eql "bang"
- expect(command.z).to eql "ZZZ"
+ end
+
+ describe "x" do
+ it "defaults the optional argument" do
+ expect(command.x).to eq "crash"
+ end
+ end
+
+ describe "y" do
+ it "defaults the optional argument" do
+ expect(command.y).to eq "bang"
+ end
+ end
+
+ describe "z" do
+ it "defaults the optional argument" do
+ expect(command.z).to eq "ZZZ"
+ end
end
end
context "with multi-line arguments" do
- it "parses them correctly" do
+ before do
command.parse(["foo\nhi", "bar", "baz"])
- expect(command.x).to eql "foo\nhi"
- expect(command.y).to eql "bar"
- expect(command.z).to eql "baz"
+ end
+
+ describe "x" do
+ it "parses them correctly" do
+ expect(command.x).to eq "foo\nhi"
+ end
+ end
+
+ describe "y" do
+ it "parses them correctly" do
+ expect(command.y).to eq "bar"
+ end
+ end
+
+ describe "z" do
+ it "parses them correctly" do
+ expect(command.z).to eq "baz"
+ end
end
end
@@ -785,9 +1050,7 @@ describe Clamp::Command do
end
it "includes parameter details" do
- expect(command.help).to match(/X +x/)
- expect(command.help).to match(/Y +y/)
- expect(command.help).to match(/\[Z\] +z \(default: "ZZZ"\)/)
+ expect(command.help).to match(/X +x\n[\s\S]* +Y +y\n[\s\S]* +\[Z\] +z \(default: "ZZZ"\)/)
end
it "handles new lines in option descriptions" do
@@ -798,6 +1061,26 @@ describe Clamp::Command do
end
+ describe ".execute" do
+
+ before do
+
+ command_class.class_eval do
+ execute do
+ puts "using execute DSL"
+ end
+ end
+
+ command.run([])
+
+ end
+
+ it "provides an alternative way to declare execute method" do
+ expect(stdout).to eq("using execute DSL\n")
+ end
+
+ end
+
context "with explicit usage" do
given_command("blah") do
@@ -828,8 +1111,7 @@ describe Clamp::Command do
describe "#help" do
it "includes both potential usages" do
- expect(command.help).to include("put THIS HERE\n")
- expect(command.help).to include("put THAT THERE\n")
+ expect(command.help).to match(/put THIS HERE\n +put THAT THERE/)
end
end
@@ -840,20 +1122,19 @@ describe Clamp::Command do
given_command("punt") do
- banner <<-EOF
+ banner <<-TEXT
Punt is an example command. It doesn't do much, really.
The prefix at the beginning of this description should be normalised
to two spaces.
- EOF
+ TEXT
end
describe "#help" do
it "includes the banner" do
- expect(command.help).to match(/^ Punt is an example command/)
- expect(command.help).to match(/^ The prefix/)
+ expect(command.help).to match(/^ Punt is an example command.+\n^ \n^ The prefix/)
end
end
@@ -862,28 +1143,42 @@ describe Clamp::Command do
describe ".run" do
- it "creates a new Command instance and runs it" do
- command.class.class_eval do
- parameter "WORD ...", "words"
- def execute
- print word_list.inspect
+ context "when regular" do
+
+ before do
+
+ command.class.class_eval do
+ parameter "WORD ...", "words"
+ def execute
+ print word_list.inspect
+ end
end
+
+ end
+
+ it "creates a new Command instance and runs it" do
+ xyz = %w[x y z]
+ command.class.run("cmd", xyz)
+ expect(stdout).to eq xyz.inspect
end
- @xyz = %w(x y z)
- command.class.run("cmd", @xyz)
- expect(stdout).to eql @xyz.inspect
+
end
- context "invoked with a context hash" do
+ context "when invoked with a context hash" do
+
+ before do
- it "makes the context available within the command" do
command.class.class_eval do
def execute
print context[:foo]
end
end
- command.class.run("xyz", [], :foo => "bar")
- expect(stdout).to eql "bar"
+
+ end
+
+ it "makes the context available within the command" do
+ command.class.run("xyz", [], foo: "bar")
+ expect(stdout).to eq "bar"
end
end
@@ -894,24 +1189,19 @@ describe Clamp::Command do
command.class.class_eval do
def execute
- signal_error "Oh crap!", :status => 456
+ signal_error "Oh crap!", status: 456
end
end
- begin
- command.class.run("cmd", [])
- rescue SystemExit => e
- @system_exit = e
- end
-
- end
-
- it "outputs the error message" do
- expect(stderr).to include "ERROR: Oh crap!"
end
- it "exits with the specified status" do
- expect(@system_exit.status).to eql 456
+ it "outputs the error message and exits with the specified status" do
+ expect { command.class.run("cmd", []) }
+ .to raise_error(
+ an_instance_of(SystemExit).and(having_attributes(status: 456))
+ ).and output(<<~TEXT).to_stderr
+ ERROR: Oh crap!
+ TEXT
end
end
@@ -934,17 +1224,15 @@ describe Clamp::Command do
end
- it "outputs the error message" do
- expect(stderr).to include "ERROR: bad dog!"
- end
-
- it "outputs help" do
- expect(stderr).to include "See: 'cmd --help'"
- end
+ it "outputs the error message and help" do
+ expect { command.class.run("cmd", []) }
+ .to raise_error(
+ an_instance_of(SystemExit).and(having_attributes(status: 1))
+ ).and output(<<~TEXT).to_stderr
+ ERROR: bad dog!
- it "exits with a non-zero status" do
- expect(@system_exit).to_not be_nil
- expect(@system_exit.status).to eql 1
+ See: 'cmd --help'
+ TEXT
end
end
diff --git a/spec/clamp/help/builder_spec.rb b/spec/clamp/help/builder_spec.rb
new file mode 100644
index 0000000..a87ad37
--- /dev/null
+++ b/spec/clamp/help/builder_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe Clamp::Help::Builder do
+
+ subject(:builder) { described_class.new }
+
+ def output
+ builder.string
+ end
+
+ describe "#line" do
+
+ it "adds a line of text" do
+ builder.line("blah")
+ expect(output).to eq("blah\n")
+ end
+
+ end
+
+ describe "#row" do
+
+ it "adds two strings separated by spaces" do
+ builder.row("LHS", "RHS")
+ expect(output).to eq(" LHS RHS\n")
+ end
+
+ end
+
+ context "with multiple rows" do
+
+ before do
+
+ builder.row("foo", "bar")
+ builder.row("flibble", "blurk")
+ builder.row("x", "y")
+
+ end
+
+ let(:expected_output) do
+ [
+ " foo bar\n",
+ " flibble blurk\n",
+ " x y\n"
+ ]
+ end
+
+ it "arranges them in two columns" do
+ expect(output.lines).to eq expected_output
+ end
+
+ end
+
+ context "with a mixture of lines and rows" do
+
+ before do
+
+ builder.line("ABCDEFGHIJKLMNOP")
+ builder.row("flibble", "blurk")
+ builder.line("Another section heading")
+ builder.row("x", "y")
+
+ end
+
+ let(:expected_output) do
+ [
+ "ABCDEFGHIJKLMNOP\n",
+ " flibble blurk\n",
+ "Another section heading\n",
+ " x y\n"
+ ]
+ end
+
+ it "still arranges them in two columns" do
+ expect(output.lines).to eq expected_output
+ end
+
+ end
+
+end
diff --git a/spec/clamp/messages_spec.rb b/spec/clamp/messages_spec.rb
index a711251..ab857e8 100644
--- a/spec/clamp/messages_spec.rb
+++ b/spec/clamp/messages_spec.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
require "spec_helper"
@@ -6,8 +7,8 @@ describe Clamp::Messages do
describe "message" do
before do
Clamp.messages = {
- :too_many_arguments => "Way too many!",
- :custom_message => "Say %<what>s to %<whom>s"
+ too_many_arguments: "Way too many!",
+ custom_message: "Say %<what>s to %<whom>s"
}
end
@@ -16,28 +17,33 @@ describe Clamp::Messages do
end
it "allows setting custom messages" do
- expect(Clamp.message(:too_many_arguments)).to eql "Way too many!"
+ expect(Clamp.message(:too_many_arguments)).to eq "Way too many!"
end
it "fallbacks to a default message" do
- expect(Clamp.message(:no_value_provided)).to eql "no value provided"
+ expect(Clamp.message(:no_value_provided)).to eq "no value provided"
end
it "formats the message" do
- expect(Clamp.message(:custom_message, :what => "hello", :whom => "Clamp")).to eql "Say hello to Clamp"
+ expect(Clamp.message(:custom_message, what: "hello", whom: "Clamp")).to eq "Say hello to Clamp"
end
end
describe "clear_messages!" do
- it "clears messages to the defualt state" do
- default_msg = Clamp.message(:too_many_arguments).clone
+ let!(:default_msg) { Clamp.message(:too_many_arguments).clone }
+
+ before do
Clamp.messages = {
- :too_many_arguments => "Way too many!"
+ too_many_arguments: "Way too many!"
}
+
Clamp.clear_messages!
- expect(Clamp.message(:too_many_arguments)).to eql default_msg
+ end
+
+ it "clears messages to the defualt state" do
+ expect(Clamp.message(:too_many_arguments)).to eq default_msg
end
end
diff --git a/spec/clamp/option/definition_spec.rb b/spec/clamp/option/definition_spec.rb
index ce1e218..c5f7c9b 100644
--- a/spec/clamp/option/definition_spec.rb
+++ b/spec/clamp/option/definition_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Clamp::Option::Definition do
@@ -9,26 +11,26 @@ describe Clamp::Option::Definition do
end
it "has a long_switch" do
- expect(option.long_switch).to eql "--key-file"
+ expect(option.long_switch).to eq "--key-file"
end
it "has a type" do
- expect(option.type).to eql "FILE"
+ expect(option.type).to eq "FILE"
end
it "has a description" do
- expect(option.description).to eql "SSH identity"
+ expect(option.description).to eq "SSH identity"
end
describe "#attribute_name" do
it "is derived from the (long) switch" do
- expect(option.attribute_name).to eql "key_file"
+ expect(option.attribute_name).to eq "key_file"
end
it "can be overridden" do
- option = described_class.new("--key-file", "FILE", "SSH identity", :attribute_name => "ssh_identity")
- expect(option.attribute_name).to eql "ssh_identity"
+ option = described_class.new("--key-file", "FILE", "SSH identity", attribute_name: "ssh_identity")
+ expect(option.attribute_name).to eq "ssh_identity"
end
end
@@ -36,7 +38,7 @@ describe Clamp::Option::Definition do
describe "#write_method" do
it "is derived from the attribute_name" do
- expect(option.write_method).to eql "key_file="
+ expect(option.write_method).to eq "key_file="
end
end
@@ -45,12 +47,12 @@ describe Clamp::Option::Definition do
it "defaults to nil" do
option = described_class.new("-n", "N", "iterations")
- expect(option.default_value).to eql nil
+ expect(option.default_value).to be_nil
end
it "can be overridden" do
- option = described_class.new("-n", "N", "iterations", :default => 1)
- expect(option.default_value).to eql 1
+ option = described_class.new("-n", "N", "iterations", default: 1)
+ expect(option.default_value).to eq 1
end
end
@@ -58,14 +60,14 @@ describe Clamp::Option::Definition do
describe "#help" do
it "combines switch, type and description" do
- expect(option.help).to eql ["--key-file FILE", "SSH identity"]
+ expect(option.help).to eq ["--key-file FILE", "SSH identity"]
end
end
end
- context "flag" do
+ context "when flag" do
let(:option) do
described_class.new("--verbose", :flag, "Blah blah blah")
@@ -73,14 +75,28 @@ describe Clamp::Option::Definition do
describe "#default_conversion_block" do
- it "converts truthy values to true" do
- expect(option.default_conversion_block.call("true")).to eql true
- expect(option.default_conversion_block.call("yes")).to eql true
+ context "with 'true' value" do
+ it "converts to true" do
+ expect(option.default_conversion_block.call("true")).to be true
+ end
+ end
+
+ context "with 'yes' value" do
+ it "converts to true" do
+ expect(option.default_conversion_block.call("yes")).to be true
+ end
end
- it "converts falsey values to false" do
- expect(option.default_conversion_block.call("false")).to eql false
- expect(option.default_conversion_block.call("no")).to eql false
+ context "with 'false' value" do
+ it "converts to false" do
+ expect(option.default_conversion_block.call("false")).to be false
+ end
+ end
+
+ context "with 'no' value" do
+ it "converts to false" do
+ expect(option.default_conversion_block.call("no")).to be false
+ end
end
end
@@ -88,29 +104,43 @@ describe Clamp::Option::Definition do
describe "#help" do
it "excludes option argument" do
- expect(option.help).to eql ["--verbose", "Blah blah blah"]
+ expect(option.help).to eq ["--verbose", "Blah blah blah"]
end
end
end
- context "negatable flag" do
+ context "when negatable flag" do
let(:option) do
described_class.new("--[no-]force", :flag, "Force installation")
end
- it "handles both positive and negative forms" do
- expect(option.handles?("--force")).to be true
- expect(option.handles?("--no-force")).to be true
+ describe "positive form" do
+ it "handles this" do
+ expect(option.handles?("--force")).to be true
+ end
+ end
+
+ describe "negative form" do
+ it "handles this" do
+ expect(option.handles?("--no-force")).to be true
+ end
end
describe "#flag_value" do
- it "returns true for the positive variant" do
- expect(option.flag_value("--force")).to be true
- expect(option.flag_value("--no-force")).to be false
+ describe "positive variant" do
+ it "returns true" do
+ expect(option.flag_value("--force")).to be true
+ end
+ end
+
+ describe "negative variant" do
+ it "returns false" do
+ expect(option.flag_value("--no-force")).to be false
+ end
end
end
@@ -118,7 +148,7 @@ describe Clamp::Option::Definition do
describe "#attribute_name" do
it "is derived from the (long) switch" do
- expect(option.attribute_name).to eql "force"
+ expect(option.attribute_name).to eq "force"
end
end
@@ -131,15 +161,22 @@ describe Clamp::Option::Definition do
described_class.new(["-k", "--key-file"], "FILE", "SSH identity")
end
- it "handles both switches" do
- expect(option.handles?("--key-file")).to be true
- expect(option.handles?("-k")).to be true
+ describe "long switch" do
+ it "handles this" do
+ expect(option.handles?("--key-file")).to be true
+ end
+ end
+
+ describe "short switch" do
+ it "handles this" do
+ expect(option.handles?("-k")).to be true
+ end
end
describe "#help" do
it "includes both switches" do
- expect(option.help).to eql ["-k, --key-file FILE", "SSH identity"]
+ expect(option.help).to eq ["-k, --key-file FILE", "SSH identity"]
end
end
@@ -149,27 +186,43 @@ describe Clamp::Option::Definition do
context "with an associated environment variable" do
let(:option) do
- described_class.new("-x", "X", "mystery option", :environment_variable => "APP_X")
+ described_class.new("-x", "X", "mystery option", environment_variable: "APP_X")
end
describe "#help" do
it "describes environment variable" do
- expect(option.help).to eql ["-x X", "mystery option (default: $APP_X)"]
+ expect(option.help).to eq ["-x X", "mystery option (default: $APP_X)"]
end
end
- context "and a default value" do
+ context "with a default value" do
let(:option) do
- described_class.new("-x", "X", "mystery option", :environment_variable => "APP_X", :default => "xyz")
+ described_class.new("-x", "X", "mystery option", environment_variable: "APP_X", default: "xyz")
end
describe "#help" do
it "describes both environment variable and default" do
- expect(option.help).to eql ["-x X", %{mystery option (default: $APP_X, or "xyz")}]
+ expect(option.help).to eq ["-x X", %{mystery option (default: $APP_X, or "xyz")}]
+ end
+
+ end
+
+ end
+
+ context "when it is required" do
+
+ let(:option) do
+ described_class.new("-x", "X", "mystery option", environment_variable: "APP_X", required: true)
+ end
+
+ describe "#help" do
+
+ it "describes the environment variable as the default" do
+ expect(option.help).to eql ["-x X", %{mystery option (required, default: $APP_X)}]
end
end
@@ -178,10 +231,10 @@ describe Clamp::Option::Definition do
end
- context "multivalued" do
+ context "when multivalued" do
let(:option) do
- described_class.new(["-H", "--header"], "HEADER", "extra header", :multivalued => true)
+ described_class.new(["-H", "--header"], "HEADER", "extra header", multivalued: true)
end
it "is multivalued" do
@@ -191,12 +244,12 @@ describe Clamp::Option::Definition do
describe "#default_value" do
it "defaults to an empty Array" do
- expect(option.default_value).to eql []
+ expect(option.default_value).to be_empty
end
it "can be overridden" do
- option = described_class.new("-H", "HEADER", "extra header", :multivalued => true, :default => [1, 2, 3])
- expect(option.default_value).to eql [1, 2, 3]
+ option = described_class.new("-H", "HEADER", "extra header", multivalued: true, default: [1, 2, 3])
+ expect(option.default_value).to eq [1, 2, 3]
end
end
@@ -204,7 +257,7 @@ describe Clamp::Option::Definition do
describe "#attribute_name" do
it "gets a _list suffix" do
- expect(option.attribute_name).to eql "header_list"
+ expect(option.attribute_name).to eq "header_list"
end
end
@@ -212,7 +265,7 @@ describe Clamp::Option::Definition do
describe "#append_method" do
it "is derived from the attribute_name" do
- expect(option.append_method).to eql "append_to_header_list"
+ expect(option.append_method).to eq "append_to_header_list"
end
end
@@ -236,7 +289,7 @@ describe Clamp::Option::Definition do
it "includes help for each option exactly once" do
subcommand = command_class.send(:find_subcommand, "foo")
subcommand_help = subcommand.subcommand_class.help("")
- expect(subcommand_help.lines.grep(/--bar BAR/).count).to eql 1
+ expect(subcommand_help.lines.grep(/--bar BAR/).count).to eq 1
end
end
@@ -247,19 +300,20 @@ describe Clamp::Option::Definition do
it "rejects :default" do
expect do
described_class.new("--key-file", "FILE", "SSH identity",
- :required => true, :default => "hello")
+ required: true, default: "hello")
end.to raise_error(ArgumentError)
end
it "rejects :flag options" do
expect do
- described_class.new("--awesome", :flag, "Be awesome?", :required => true)
+ described_class.new("--awesome", :flag, "Be awesome?", required: true)
end.to raise_error(ArgumentError)
end
end
describe "a hidden option" do
- let(:option) { described_class.new("--unseen", :flag, "Something", :hidden => true) }
+ let(:option) { described_class.new("--unseen", :flag, "Something", hidden: true) }
+
it "is hidden" do
expect(option).to be_hidden
end
@@ -268,7 +322,7 @@ describe Clamp::Option::Definition do
describe "a hidden option in a command" do
let(:command_class) do
Class.new(Clamp::Command) do
- option "--unseen", :flag, "Something", :hidden => true
+ option "--unseen", :flag, "Something", hidden: true
def execute
# this space intentionally left blank
@@ -277,13 +331,13 @@ describe Clamp::Option::Definition do
end
it "is not shown in the help" do
- expect(command_class.help("foo")).not_to match /^ +--unseen +Something$/
+ expect(command_class.help("foo")).not_to match(/^ +--unseen +Something$/)
end
it "sets the expected accessor" do
command = command_class.new("foo")
command.run(["--unseen"])
- expect(command.unseen?).to be_truthy
+ expect(command).to be_unseen
end
end
end
diff --git a/spec/clamp/parameter/definition_spec.rb b/spec/clamp/parameter/definition_spec.rb
index fd5f8ea..b3b262d 100644
--- a/spec/clamp/parameter/definition_spec.rb
+++ b/spec/clamp/parameter/definition_spec.rb
@@ -1,53 +1,71 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Clamp::Parameter::Definition do
- context "normal" do
+ context "when regular" do
let(:parameter) do
described_class.new("COLOR", "hue of choice")
end
it "has a name" do
- expect(parameter.name).to eql "COLOR"
+ expect(parameter.name).to eq "COLOR"
end
it "has a description" do
- expect(parameter.description).to eql "hue of choice"
+ expect(parameter.description).to eq "hue of choice"
end
it "is single-valued" do
- expect(parameter).to_not be_multivalued
+ expect(parameter).not_to be_multivalued
end
describe "#attribute_name" do
it "is derived from the name" do
- expect(parameter.attribute_name).to eql "color"
+ expect(parameter.attribute_name).to eq "color"
end
it "can be overridden" do
- parameter = described_class.new("COLOR", "hue of choice", :attribute_name => "hue")
- expect(parameter.attribute_name).to eql "hue"
+ parameter = described_class.new("COLOR", "hue of choice", attribute_name: "hue")
+ expect(parameter.attribute_name).to eq "hue"
end
end
describe "#consume" do
- it "consumes one argument" do
- arguments = %w(a b c)
- expect(parameter.consume(arguments)).to eql ["a"]
- expect(arguments).to eql %w(b c)
+ subject(:consume) { parameter.consume(arguments) }
+
+ context "with arguments" do
+ let(:arguments) { %w[a b c] }
+
+ it "returns one consumed argument" do
+ expect(consume).to eq ["a"]
+ end
+
+ describe "arguments after consume" do
+
+ before do
+ consume
+ end
+
+ it "has only non-consumed" do
+ expect(arguments).to eq %w[b c]
+ end
+
+ end
+
end
- describe "with no arguments" do
+ context "without arguments" do
+
+ let(:arguments) { [] }
it "raises an Argument error" do
- arguments = []
- expect do
- parameter.consume(arguments)
- end.to raise_error(ArgumentError)
+ expect { consume }.to raise_error(ArgumentError)
end
end
@@ -56,37 +74,55 @@ describe Clamp::Parameter::Definition do
end
- context "optional (name in square brackets)" do
+ context "when optional (name in square brackets)" do
let(:parameter) do
described_class.new("[COLOR]", "hue of choice")
end
it "is single-valued" do
- expect(parameter).to_not be_multivalued
+ expect(parameter).not_to be_multivalued
end
describe "#attribute_name" do
it "omits the brackets" do
- expect(parameter.attribute_name).to eql "color"
+ expect(parameter.attribute_name).to eq "color"
end
end
describe "#consume" do
- it "consumes one argument" do
- arguments = %w(a b c)
- expect(parameter.consume(arguments)).to eql ["a"]
- expect(arguments).to eql %w(b c)
+ subject(:consume) { parameter.consume(arguments) }
+
+ context "with arguments" do
+
+ let(:arguments) { %w[a b c] }
+
+ it "returns one consumed argument" do
+ expect(consume).to eq ["a"]
+ end
+
+ describe "arguments after consume" do
+
+ before do
+ consume
+ end
+
+ it "has only non-consumed" do
+ expect(arguments).to eq %w[b c]
+ end
+
+ end
end
- describe "with no arguments" do
+ context "without arguments" do
+
+ let(:arguments) { [] }
it "consumes nothing" do
- arguments = []
- expect(parameter.consume(arguments)).to eql []
+ expect(consume).to be_empty
end
end
@@ -95,7 +131,7 @@ describe Clamp::Parameter::Definition do
end
- context "list (name followed by ellipsis)" do
+ context "when list (name followed by ellipsis)" do
let(:parameter) do
described_class.new("FILE ...", "files to process")
@@ -108,7 +144,7 @@ describe Clamp::Parameter::Definition do
describe "#attribute_name" do
it "gets a _list suffix" do
- expect(parameter.attribute_name).to eql "file_list"
+ expect(parameter.attribute_name).to eq "file_list"
end
end
@@ -116,26 +152,43 @@ describe Clamp::Parameter::Definition do
describe "#append_method" do
it "is derived from the attribute_name" do
- expect(parameter.append_method).to eql "append_to_file_list"
+ expect(parameter.append_method).to eq "append_to_file_list"
end
end
describe "#consume" do
- it "consumes all the remaining arguments" do
- arguments = %w(a b c)
- expect(parameter.consume(arguments)).to eql %w(a b c)
- expect(arguments).to eql []
+ subject(:consume) { parameter.consume(arguments) }
+
+ context "with arguments" do
+
+ let(:arguments) { %w[a b c] }
+
+ it "returns all the consumed arguments" do
+ expect(consume).to eq %w[a b c]
+ end
+
+ describe "arguments after consume" do
+
+ before do
+ consume
+ end
+
+ it "empty" do
+ expect(arguments).to be_empty
+ end
+
+ end
+
end
- describe "with no arguments" do
+ describe "without arguments" do
+
+ let(:arguments) { [] }
it "raises an Argument error" do
- arguments = []
- expect do
- parameter.consume(arguments)
- end.to raise_error(ArgumentError)
+ expect { consume }.to raise_error(ArgumentError)
end
end
@@ -145,13 +198,13 @@ describe Clamp::Parameter::Definition do
context "with a weird parameter name, and an explicit attribute_name" do
let(:parameter) do
- described_class.new("KEY=VALUE ...", "config-settings", :attribute_name => :config_settings)
+ described_class.new("KEY=VALUE ...", "config-settings", attribute_name: :config_settings)
end
describe "#attribute_name" do
it "is the specified one" do
- expect(parameter.attribute_name).to eql "config_settings"
+ expect(parameter.attribute_name).to eq "config_settings"
end
end
@@ -160,7 +213,7 @@ describe Clamp::Parameter::Definition do
end
- context "optional list" do
+ context "when optional list" do
let(:parameter) do
described_class.new("[FILES] ...", "files to process")
@@ -173,7 +226,7 @@ describe Clamp::Parameter::Definition do
describe "#attribute_name" do
it "gets a _list suffix" do
- expect(parameter.attribute_name).to eql "files_list"
+ expect(parameter.attribute_name).to eq "files_list"
end
end
@@ -181,7 +234,7 @@ describe Clamp::Parameter::Definition do
describe "#default_value" do
it "is an empty list" do
- expect(parameter.default_value).to eql []
+ expect(parameter.default_value).to be_empty
end
end
@@ -189,7 +242,7 @@ describe Clamp::Parameter::Definition do
describe "#help" do
it "does not include default" do
- expect(parameter.help_rhs).to_not include("default:")
+ expect(parameter.help_rhs).not_to include("default:")
end
end
@@ -197,13 +250,13 @@ describe Clamp::Parameter::Definition do
context "with specified default value" do
let(:parameter) do
- described_class.new("[FILES] ...", "files to process", :default => %w(a b c))
+ described_class.new("[FILES] ...", "files to process", default: %w[a b c])
end
describe "#default_value" do
it "is that specified" do
- expect(parameter.default_value).to eql %w(a b c)
+ expect(parameter.default_value).to eq %w[a b c]
end
end
@@ -218,17 +271,36 @@ describe Clamp::Parameter::Definition do
describe "#consume" do
- it "consumes all the remaining arguments" do
- arguments = %w(a b c)
- expect(parameter.consume(arguments)).to eql %w(a b c)
- expect(arguments).to eql []
+ subject(:consume) { parameter.consume(arguments) }
+
+ context "with arguments" do
+
+ let(:arguments) { %w[a b c] }
+
+ it "returns all the consumed arguments" do
+ expect(consume).to eq %w[a b c]
+ end
+
+ describe "arguments after consume" do
+
+ before do
+ consume
+ end
+
+ it "empty" do
+ expect(arguments).to be_empty
+ end
+
+ end
+
end
- context "with no arguments" do
+ context "without arguments" do
+
+ let(:arguments) { [] }
it "don't override defaults" do
- arguments = []
- expect(parameter.consume(arguments)).to eql []
+ expect(consume).to be_empty
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index e3a3a3b..b19672b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,10 +1,17 @@
+# frozen_string_literal: true
+
require "rspec"
require "clamp"
require "stringio"
+require "pry-byebug"
RSpec.configure do |config|
- config.mock_with :rr
+ config.around do |example|
+ example.run
+ rescue SystemExit => e
+ raise "Unexpected exit with status #{e.status}"
+ end
end