This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
*.gem
|
||||
*.rbc
|
||||
.bundle
|
||||
.config
|
||||
.yardoc
|
||||
Gemfile.lock
|
||||
InstalledFiles
|
||||
_yardoc
|
||||
coverage
|
||||
doc/
|
||||
lib/bundler/man
|
||||
pkg
|
||||
rdoc
|
||||
spec/reports
|
||||
test/tmp
|
||||
test/version_tmp
|
||||
tmp
|
||||
vendor/bundle
|
||||
@@ -0,0 +1,2 @@
|
||||
--color
|
||||
--format progress
|
||||
@@ -0,0 +1,10 @@
|
||||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
require: rubocop-jekyll
|
||||
inherit_gem:
|
||||
rubocop-jekyll: .rubocop.yml
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.4
|
||||
Exclude:
|
||||
- vendor/**/*
|
||||
@@ -0,0 +1,79 @@
|
||||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config`
|
||||
# on 2020-01-18 15:09:24 +0100 using RuboCop version 0.71.0.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 2
|
||||
Jekyll/NoPAllowed:
|
||||
Exclude:
|
||||
- 'examples/help_dialogue.rb'
|
||||
|
||||
# Offense count: 2
|
||||
Jekyll/NoPutsAllowed:
|
||||
Exclude:
|
||||
- 'lib/mercenary/command.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# Configuration parameters: AllowSafeAssignment.
|
||||
Lint/AssignmentInCondition:
|
||||
Exclude:
|
||||
- 'lib/mercenary/command.rb'
|
||||
- 'lib/mercenary/option.rb'
|
||||
- 'lib/mercenary/presenter.rb'
|
||||
|
||||
# Offense count: 2
|
||||
Lint/DuplicateMethods:
|
||||
Exclude:
|
||||
- 'lib/mercenary/command.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: AllowComments.
|
||||
Lint/HandleExceptions:
|
||||
Exclude:
|
||||
- 'lib/mercenary/option.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Lint/UselessAssignment:
|
||||
Exclude:
|
||||
- 'lib/mercenary/presenter.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Metrics/AbcSize:
|
||||
Max: 25
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: CountComments, ExcludedMethods.
|
||||
# ExcludedMethods: refine
|
||||
Metrics/BlockLength:
|
||||
Max: 28
|
||||
|
||||
# Offense count: 6
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
||||
# URISchemes: http, https
|
||||
Metrics/LineLength:
|
||||
Max: 319
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: CountComments, ExcludedMethods.
|
||||
Metrics/MethodLength:
|
||||
Max: 24
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist, MethodDefinitionMacros.
|
||||
# NamePrefix: is_, has_, have_
|
||||
# NamePrefixBlacklist: is_, has_, have_
|
||||
# NameWhitelist: is_a?
|
||||
# MethodDefinitionMacros: define_method, define_singleton_method
|
||||
Naming/PredicateName:
|
||||
Exclude:
|
||||
- 'spec/**/*'
|
||||
- 'lib/mercenary/command.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Style/MissingRespondToMissing:
|
||||
Exclude:
|
||||
- 'lib/mercenary/presenter.rb'
|
||||
@@ -0,0 +1,15 @@
|
||||
language: ruby
|
||||
cache: bundler
|
||||
rvm:
|
||||
- 2.4
|
||||
- 2.6
|
||||
|
||||
before_script: bundle update
|
||||
script: script/cibuild
|
||||
|
||||
notifications:
|
||||
email:
|
||||
recipients:
|
||||
- mercenary@jekyllrb.com
|
||||
on_success: change
|
||||
on_failure: change
|
||||
@@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source "https://rubygems.org"
|
||||
|
||||
# Specify your gem's dependencies in mercenary.gemspec
|
||||
gemspec
|
||||
@@ -0,0 +1,127 @@
|
||||
## 0.4.0 / 2020-01-18
|
||||
|
||||
### Major Enhancements
|
||||
|
||||
* Drop Ruby 2.3 support
|
||||
|
||||
### Minor Enhancements
|
||||
|
||||
* Remove parent command's flags from subcommand usage (#44)
|
||||
|
||||
### Development Fixes
|
||||
|
||||
* Adopt Jekyll's rubocop config for consistency
|
||||
|
||||
### Documentation
|
||||
|
||||
* fixes the readme (#52)
|
||||
|
||||
## 0.3.6 / 2016-04-07
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Presenter: Options should include those from parent command (#42)
|
||||
|
||||
## 0.3.5 / 2014-11-12
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Capture `OptionsParser::InvalidOption` and show a nice error message (#38)
|
||||
* Absolute paths for requires and autoloads (#39)
|
||||
|
||||
### Development Fixes
|
||||
|
||||
* Bump to RSpec 3 (#40)
|
||||
|
||||
## 0.3.4 / 2014-07-11
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use option object as key in the command's `@map` hash (#35)
|
||||
|
||||
## 0.3.3 / 2014-05-07
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* The `--version` flag should not exit with code 1, but instead code 0. (#33)
|
||||
|
||||
## 0.3.2 / 2014-03-18
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Remove duplicate commands from help output; show aliases w/command names (#29)
|
||||
|
||||
## 0.3.1 / 2014-02-21
|
||||
|
||||
### Minor Enhancements
|
||||
|
||||
* Add `-t/--trace` to list of options in help message (#19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `Mercenary::Option` now accepts return values in the form of Class constants (#22)
|
||||
|
||||
## 0.3.0 / 2014-02-20
|
||||
|
||||
### Major Enhancements
|
||||
|
||||
* Officially drop 1.8.7 support (#14)
|
||||
* Allow Commands to set their own versions (#17)
|
||||
* Show subcommands, options and usage in help and attach to all commands (#18)
|
||||
* Add `-t, --trace` to allow full exception backtrace to print, otherwise print just the error message (#19)
|
||||
|
||||
### Minor Enhancements
|
||||
|
||||
* Logging state is maintained throughout process (#12)
|
||||
* Tidy up Command#logger output (#21)
|
||||
|
||||
### Development Fixes
|
||||
|
||||
* Added specs for `Program` (#13)
|
||||
|
||||
## 0.2.1 / 2013-12-25
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Added missing comma to fix '-v' and '--version' options (#9)
|
||||
|
||||
## 0.2.0 / 2013-11-30
|
||||
|
||||
### Major Enhancements
|
||||
|
||||
* Add `Command#default_command` to specify a default command if none is given by the user at runtime (#7)
|
||||
|
||||
### Minor Enhancements
|
||||
|
||||
* Add `Command#execute` to execute the actions of a command (#6)
|
||||
|
||||
### Development Fixes
|
||||
|
||||
* Add standard GitHub bootstrap and cibuild scripts to `script/` (#2)
|
||||
|
||||
## 0.1.0 / 2013-11-08
|
||||
|
||||
### Major Enhancements
|
||||
|
||||
* It works!
|
||||
|
||||
### Minor Enhancements
|
||||
|
||||
* Add a logger to `Command`
|
||||
* Add `--version` switch to all programs
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix `Command#syntax` and `Command#description`'s handing of setting vs getting
|
||||
* Fix load path problem in `lib/mercenary.rb`
|
||||
|
||||
### Development Fixes
|
||||
|
||||
* Add TomDoc to everything
|
||||
* Add a couple starter specs
|
||||
* Add TravisCI badge
|
||||
* Add Travis configuration
|
||||
|
||||
## 0.0.1 / 2013-11-06
|
||||
|
||||
* Birthday!
|
||||
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2013-present Parker Moore and the mercenary contributors
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 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.
|
||||
@@ -0,0 +1,249 @@
|
||||
# Mercenary
|
||||
|
||||
Lightweight and flexible library for writing command-line apps in Ruby.
|
||||
|
||||
[](https://travis-ci.org/jekyll/mercenary)
|
||||
|
||||
## Installation
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
|
||||
gem 'mercenary'
|
||||
|
||||
And then execute:
|
||||
|
||||
$ bundle
|
||||
|
||||
Or install it yourself as:
|
||||
|
||||
$ gem install mercenary
|
||||
|
||||
**Note: Mercenary may not work with Ruby < 1.9.3.**
|
||||
|
||||
## Usage
|
||||
|
||||
Creating programs and commands with Mercenary is easy:
|
||||
|
||||
```ruby
|
||||
Mercenary.program(:jekyll) do |p|
|
||||
p.version Jekyll::VERSION
|
||||
p.description 'Jekyll is a blog-aware, static site generator in Ruby'
|
||||
p.syntax "jekyll <subcommand> [options]"
|
||||
|
||||
p.command(:new) do |c|
|
||||
c.syntax "new PATH" # do not include the program name or super commands
|
||||
c.description "Creates a new Jekyll site scaffold in PATH"
|
||||
c.option 'blank', '--blank', 'Initialize the new site without any content.'
|
||||
|
||||
c.action do |args, options|
|
||||
Jekyll::Commands::New.process(args, blank: options['blank'])
|
||||
end
|
||||
end
|
||||
|
||||
p.command(:build) do |c|
|
||||
c.syntax "build [options]"
|
||||
c.description "Builds your Jekyll site"
|
||||
|
||||
c.option 'safe', '--safe', 'Run in safe mode'
|
||||
c.option 'source', '--source DIR', 'From where to collect the source files'
|
||||
c.option 'destination', '--dest DIR', 'To where the compiled files should be written'
|
||||
|
||||
c.action do |_, options|
|
||||
Jekyll::Commands::Build.process(options)
|
||||
end
|
||||
end
|
||||
|
||||
# Bring in command bundled in external gem
|
||||
begin
|
||||
require "jekyll-import"
|
||||
JekyllImport.init_with_program(p)
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
p.default_command(:build)
|
||||
end
|
||||
```
|
||||
|
||||
All commands have the following default options:
|
||||
|
||||
- `-h/--help` - show a help message
|
||||
- `-v/--version` - show the program version
|
||||
- `-t/--trace` - show the full backtrace when an error occurs
|
||||
|
||||
## API
|
||||
|
||||
### `Mercenary`
|
||||
|
||||
#### `.program`
|
||||
|
||||
Creates and executes a program. Accepts two arguments:
|
||||
|
||||
- `name` - program name as a Symbol
|
||||
- `block` - the specification for the program, passed the program instance as an
|
||||
argument.
|
||||
|
||||
Example is above, under the heading [Usage](#usage).
|
||||
|
||||
### `Program`
|
||||
|
||||
`Program` is a subclass of `Command`, so it has all of the methods documented
|
||||
below as well as those for `Command`.
|
||||
|
||||
#### `#config`
|
||||
|
||||
Fetches the program configuration hash.
|
||||
|
||||
### `Command`
|
||||
|
||||
#### `#new`
|
||||
|
||||
Create a new command. Accepts one argument:
|
||||
|
||||
- `name` - the name of your command, as a symbol
|
||||
|
||||
#### `#version`
|
||||
|
||||
Sets or gets the version of the command. Accepts an optional argument:
|
||||
|
||||
- `version` - (optional) the version to set for the command. If present, this
|
||||
becomes the new version for the command and persists.
|
||||
|
||||
#### `#syntax`
|
||||
|
||||
Sets or gets the syntax of the command. Built on parent syntaxes if a parent
|
||||
exists. Accepts one optional argument:
|
||||
|
||||
- `syntax` - (optional) the syntax to set for the command. Will inherit from the
|
||||
parent commands or program. Usually in the form of
|
||||
`"command_name <SUBCOMMAND> [OPTIONS]"`
|
||||
|
||||
When a parent command exists, say `supercommand`, with syntax set as
|
||||
`supercommand <SUBCOMMAND> [OPTIONS]`, the syntax of the command in question
|
||||
will be `supercommand command_name <SUBCOMMAND> [OPTIONS]` with both
|
||||
`<SUBCOMMAND>` and `[OPTIONS]` stripped out. Any text between `<` and `>` or
|
||||
between `[` and `]` will be stripped from parent command syntaxes. The purpose
|
||||
of this chaining is to reduce redundancy.
|
||||
|
||||
#### `#description`
|
||||
|
||||
Sets or gets the description of the command. Accepts one optional argument:
|
||||
|
||||
- `desc` - (optional) the description to set for the command. If
|
||||
provided, will override any previous description set for the command.
|
||||
|
||||
#### `#default_command`
|
||||
|
||||
Sets or gets the default subcommand of the command to execute in the event no
|
||||
subcommand is passed during execution. Accepts one optional argument:
|
||||
|
||||
- `command_name` - (optional) the `Symbol` name of the subcommand to be
|
||||
executed. Raises an `ArgumentError` if the subcommand doesn't exist.
|
||||
Overwrites previously-set default commands.
|
||||
|
||||
#### `#option`
|
||||
|
||||
Adds a new option to the command. Accepts many arguments:
|
||||
|
||||
- `config_key` - the configuration key that the value of this option maps to.
|
||||
- `*options` - all the options, globbed, to be passed to `OptionParser`, namely the
|
||||
switches and the option description. Usually in the format
|
||||
`"-s", "--switch", "Sets the 'switch' flag"`.
|
||||
|
||||
Valid option calls:
|
||||
|
||||
```ruby
|
||||
cmd.option 'config_key', '-c', 'Sets the "config" flag'
|
||||
cmd.option 'config_key', '--config', 'Sets the "config" flag'
|
||||
cmd.option 'config_key', '-c', '--config', 'Sets the "config" flag.'
|
||||
cmd.option 'config_key', '-c FILE', '--config FILE', 'The config file.'
|
||||
cmd.option 'config_key', '-c FILE1[,FILE2[,FILE3...]]', '--config FILE1[,FILE2[,FILE3...]]', Array, 'The config files.'
|
||||
```
|
||||
|
||||
Notice that you can specify either a short switch, a long switch, or both. If
|
||||
you want to accept an argument, you have to specify it in the switch strings.
|
||||
The class of the argument defaults to `String`, but you can optionally set a
|
||||
different class to create, e.g. `Array`, if you are expecting a particular class
|
||||
in your code from this option's value. The description is also optional, but
|
||||
it's highly recommended to include a description.
|
||||
|
||||
#### `#alias`
|
||||
|
||||
Specifies an alias for this command such that the alias may be used in place of
|
||||
the command during execution. Accepts one argument:
|
||||
|
||||
- `cmd_name` - the alias name for this command as a `Symbol`
|
||||
|
||||
Example:
|
||||
|
||||
```ruby
|
||||
cmd.alias(:my_alias)
|
||||
# Now `cmd` is now also executable via "my_alias"
|
||||
```
|
||||
|
||||
#### `#action`
|
||||
|
||||
Specifies a block to be executed in the event the command is specified at
|
||||
runtime. The block is given two arguments:
|
||||
|
||||
- `args` - the non-switch arguments given from the command-line
|
||||
- `options` - the options hash built via the switches passed
|
||||
|
||||
**Note that actions are additive**, meaning any new call to `#action` will
|
||||
result in another action to be executed at runtime. Actions will be executed in
|
||||
the order they are specified in.
|
||||
|
||||
Example:
|
||||
|
||||
```ruby
|
||||
cmd.action do |args, options|
|
||||
# do something!
|
||||
end
|
||||
```
|
||||
|
||||
#### `#logger`
|
||||
|
||||
Access the logger for this command. Useful for outputting information to STDOUT.
|
||||
Accepts one optional argument:
|
||||
|
||||
- `level` - (optional) the severity threshold at which to begin logging. Uses
|
||||
Ruby's built-in
|
||||
[`Logger`](http://www.ruby-doc.org/stdlib-2.1.0/libdoc/logger/rdoc/Logger.html)
|
||||
levels.
|
||||
|
||||
Log level defaults to `Logger::INFO`.
|
||||
|
||||
Examples:
|
||||
|
||||
```ruby
|
||||
cmd.logger(Logger::DEBUG)
|
||||
cmd.logger.debug "My debug message."
|
||||
cmd.logger.info "My informative message."
|
||||
cmd.logger.warn "ACHTUNG!!"
|
||||
cmd.logger.error "Something terrible has happened."
|
||||
cmd.logger.fatal "I can't continue doing what I'm doing."
|
||||
```
|
||||
|
||||
#### `#command`
|
||||
|
||||
Creates a new subcommand for the current command. Accepts two arguments:
|
||||
|
||||
- `cmd_name` - the command name, as a Symbol
|
||||
- `block` - the specification of the subcommand in a block
|
||||
|
||||
Example:
|
||||
|
||||
```ruby
|
||||
my_command.command(:my_subcommand) do |subcmd|
|
||||
subcmd.description 'My subcommand'
|
||||
subcmd.syntax 'my_subcommand [OPTIONS]'
|
||||
# ...
|
||||
end
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||
4. Push to the branch (`git push origin my-new-feature`)
|
||||
5. Create new Pull Request
|
||||
@@ -0,0 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/gem_tasks"
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH.unshift File.join(__dir__, "..", "lib")
|
||||
|
||||
require "mercenary"
|
||||
|
||||
# This example sets the logging mode of mercenary to
|
||||
# debug. Logging messages from "p.logger.debug" will
|
||||
# be output to STDOUT.
|
||||
|
||||
Mercenary.program(:help_dialogue) do |p|
|
||||
p.version "2.0.1"
|
||||
p.description "An example of the help dialogue in Mercenary"
|
||||
p.syntax "help_dialogue <subcommand>"
|
||||
|
||||
p.command(:some_subcommand) do |c|
|
||||
c.version "1.4.2"
|
||||
c.syntax "some_subcommand <subcommand> [options]"
|
||||
c.description "Some subcommand to do something"
|
||||
c.option "an_option", "-o", "--option", "Some option"
|
||||
c.alias(:blah)
|
||||
|
||||
c.command(:yet_another_sub) do |f|
|
||||
f.syntax "yet_another_sub [options]"
|
||||
f.description "Do amazing things"
|
||||
f.option "blah", "-b", "--blah", "Trigger blah flag"
|
||||
f.option "heh", "-H ARG", "--heh ARG", "Give a heh"
|
||||
|
||||
f.action do |args, options|
|
||||
print "Args: "
|
||||
p args
|
||||
print "Opts: "
|
||||
p options
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
p.command(:another_subcommand) do |c|
|
||||
c.syntax "another_subcommand <subcommand> [options]"
|
||||
c.description "Another subcommand to do something different."
|
||||
c.option "an_option", "-O", "--option", "Some option"
|
||||
c.option "another_options", "--pluginzzz", "Set where the plugins should be found from"
|
||||
end
|
||||
end
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH.unshift File.join(__dir__, "..", "lib")
|
||||
|
||||
require "mercenary"
|
||||
|
||||
# This example sets the logging mode of mercenary to
|
||||
# debug. Logging messages from "p.logger.debug" will
|
||||
# be output to STDOUT.
|
||||
|
||||
Mercenary.program(:logger_output) do |p|
|
||||
p.version "5.2.6"
|
||||
p.description "An example of turning on logging for Mercenary."
|
||||
p.syntax "logger_output"
|
||||
|
||||
p.logger.info "The default log level is INFO. So this will output."
|
||||
p.logger.debug "Since DEBUG is below INFO, this will not output."
|
||||
|
||||
p.logger(Logger::DEBUG)
|
||||
p.logger.debug "Logger level now set to DEBUG. So everything will output."
|
||||
|
||||
p.logger.debug "Example of DEBUG level message."
|
||||
p.logger.info "Example of INFO level message."
|
||||
p.logger.warn "Example of WARN level message."
|
||||
p.logger.error "Example of ERROR level message."
|
||||
p.logger.fatal "Example of FATAL level message."
|
||||
p.logger.unknown "Example of UNKNOWN level message."
|
||||
|
||||
p.action do |_args, _options|
|
||||
p.logger(Logger::INFO)
|
||||
p.logger.debug "Logger level back to INFO. This line will not output."
|
||||
p.logger.info "This INFO message will output."
|
||||
end
|
||||
end
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH.unshift File.join(__dir__, "..", "lib")
|
||||
|
||||
require "mercenary"
|
||||
|
||||
# This example sets the logging mode of mercenary to
|
||||
# debug. Logging messages from "p.logger.debug" will
|
||||
# be output to STDOUT.
|
||||
|
||||
Mercenary.program(:trace) do |p|
|
||||
p.version "2.0.1"
|
||||
p.description "An example of traces in Mercenary"
|
||||
p.syntax "trace <subcommand>"
|
||||
|
||||
p.action do |_, _|
|
||||
raise ArgumentError, "YOU DID SOMETHING TERRIBLE YOU BUFFOON"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require File.expand_path("mercenary/version", __dir__)
|
||||
require "optparse"
|
||||
require "logger"
|
||||
|
||||
module Mercenary
|
||||
autoload :Command, File.expand_path("mercenary/command", __dir__)
|
||||
autoload :Option, File.expand_path("mercenary/option", __dir__)
|
||||
autoload :Presenter, File.expand_path("mercenary/presenter", __dir__)
|
||||
autoload :Program, File.expand_path("mercenary/program", __dir__)
|
||||
|
||||
# Public: Instantiate a new program and execute.
|
||||
#
|
||||
# name - the name of your program
|
||||
#
|
||||
# Returns nothing.
|
||||
def self.program(name)
|
||||
program = Program.new(name)
|
||||
yield program
|
||||
program.go(ARGV)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,281 @@
|
||||
# frozen_string_literal: false
|
||||
|
||||
module Mercenary
|
||||
class Command
|
||||
attr_reader :name
|
||||
attr_reader :description
|
||||
attr_reader :syntax
|
||||
attr_accessor :options
|
||||
attr_accessor :commands
|
||||
attr_accessor :actions
|
||||
attr_reader :map
|
||||
attr_accessor :parent
|
||||
attr_reader :trace
|
||||
attr_reader :aliases
|
||||
|
||||
# Public: Creates a new Command
|
||||
#
|
||||
# name - the name of the command
|
||||
# parent - (optional) the instancce of Mercenary::Command which you wish to
|
||||
# be the parent of this command
|
||||
#
|
||||
# Returns nothing
|
||||
def initialize(name, parent = nil)
|
||||
@name = name
|
||||
@options = []
|
||||
@commands = {}
|
||||
@actions = []
|
||||
@map = {}
|
||||
@parent = parent
|
||||
@trace = false
|
||||
@aliases = []
|
||||
end
|
||||
|
||||
# Public: Sets or gets the command version
|
||||
#
|
||||
# version - the command version (optional)
|
||||
#
|
||||
# Returns the version and sets it if an argument is non-nil
|
||||
def version(version = nil)
|
||||
@version = version if version
|
||||
@version
|
||||
end
|
||||
|
||||
# Public: Sets or gets the syntax string
|
||||
#
|
||||
# syntax - the string which describes this command's usage syntax (optional)
|
||||
#
|
||||
# Returns the syntax string and sets it if an argument is present
|
||||
def syntax(syntax = nil)
|
||||
@syntax = syntax if syntax
|
||||
syntax_list = []
|
||||
syntax_list << parent.syntax.to_s.gsub(%r!<[\w\s-]+>!, "").gsub(%r!\[[\w\s-]+\]!, "").strip if parent
|
||||
syntax_list << (@syntax || name.to_s)
|
||||
syntax_list.join(" ")
|
||||
end
|
||||
|
||||
# Public: Sets or gets the command description
|
||||
#
|
||||
# description - the description of what the command does (optional)
|
||||
#
|
||||
# Returns the description and sets it if an argument is present
|
||||
def description(desc = nil)
|
||||
@description = desc if desc
|
||||
@description
|
||||
end
|
||||
|
||||
# Public: Sets the default command
|
||||
#
|
||||
# command_name - the command name to be executed in the event no args are
|
||||
# present
|
||||
#
|
||||
# Returns the default command if there is one, `nil` otherwise
|
||||
def default_command(command_name = nil)
|
||||
if command_name
|
||||
if commands.key?(command_name)
|
||||
@default_command = commands[command_name] if command_name
|
||||
@default_command
|
||||
else
|
||||
raise ArgumentError, "'#{command_name}' couldn't be found in this command's list of commands."
|
||||
end
|
||||
else
|
||||
@default_command
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Adds an option switch
|
||||
#
|
||||
# sym - the variable key which is used to identify the value of the switch
|
||||
# at runtime in the options hash
|
||||
#
|
||||
# Returns nothing
|
||||
def option(sym, *options)
|
||||
new_option = Option.new(sym, options)
|
||||
@options << new_option
|
||||
@map[new_option] = sym
|
||||
end
|
||||
|
||||
# Public: Adds a subcommand
|
||||
#
|
||||
# cmd_name - the name of the command
|
||||
# block - a block accepting the new instance of Mercenary::Command to be
|
||||
# modified (optional)
|
||||
#
|
||||
# Returns nothing
|
||||
def command(cmd_name)
|
||||
cmd = Command.new(cmd_name, self)
|
||||
yield cmd
|
||||
@commands[cmd_name] = cmd
|
||||
end
|
||||
|
||||
# Public: Add an alias for this command's name to be attached to the parent
|
||||
#
|
||||
# cmd_name - the name of the alias
|
||||
#
|
||||
# Returns nothing
|
||||
def alias(cmd_name)
|
||||
logger.debug "adding alias to parent for self: '#{cmd_name}'"
|
||||
aliases << cmd_name
|
||||
@parent.commands[cmd_name] = self
|
||||
end
|
||||
|
||||
# Public: Add an action Proc to be executed at runtime
|
||||
#
|
||||
# block - the Proc to be executed at runtime
|
||||
#
|
||||
# Returns nothing
|
||||
def action(&block)
|
||||
@actions << block
|
||||
end
|
||||
|
||||
# Public: Fetch a Logger (stdlib)
|
||||
#
|
||||
# level - the logger level (a Logger constant, see docs for more info)
|
||||
#
|
||||
# Returns the instance of Logger
|
||||
|
||||
def logger(level = nil)
|
||||
unless @logger
|
||||
@logger = Logger.new(STDOUT)
|
||||
@logger.level = level || Logger::INFO
|
||||
@logger.formatter = proc do |severity, _datetime, _progname, msg|
|
||||
"#{identity} | " << "#{severity.downcase.capitalize}:".ljust(7) << " #{msg}\n"
|
||||
end
|
||||
end
|
||||
|
||||
@logger.level = level unless level.nil?
|
||||
@logger
|
||||
end
|
||||
|
||||
# Public: Run the command
|
||||
#
|
||||
# argv - an array of string args
|
||||
# opts - the instance of OptionParser
|
||||
# config - the output config hash
|
||||
#
|
||||
# Returns the command to be executed
|
||||
def go(argv, opts, config)
|
||||
opts.banner = "Usage: #{syntax}"
|
||||
process_options(opts, config)
|
||||
add_default_options(opts)
|
||||
|
||||
if argv[0] && cmd = commands[argv[0].to_sym]
|
||||
logger.debug "Found subcommand '#{cmd.name}'"
|
||||
argv.shift
|
||||
cmd.go(argv, opts, config)
|
||||
else
|
||||
logger.debug "No additional command found, time to exec"
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Add this command's options to OptionParser and set a default
|
||||
# action of setting the value of the option to the inputted hash
|
||||
#
|
||||
# opts - instance of OptionParser
|
||||
# config - the Hash in which the option values should be placed
|
||||
#
|
||||
# Returns nothing
|
||||
def process_options(opts, config)
|
||||
options.each do |option|
|
||||
opts.on(*option.for_option_parser) do |x|
|
||||
config[map[option]] = x
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Add version and help options to the command
|
||||
#
|
||||
# opts - instance of OptionParser
|
||||
#
|
||||
# Returns nothing
|
||||
def add_default_options(opts)
|
||||
option "show_help", "-h", "--help", "Show this message"
|
||||
option "show_version", "-v", "--version", "Print the name and version"
|
||||
option "show_backtrace", "-t", "--trace", "Show the full backtrace when an error occurs"
|
||||
opts.on("-v", "--version", "Print the version") do
|
||||
puts "#{name} #{version}"
|
||||
exit(0)
|
||||
end
|
||||
|
||||
opts.on("-t", "--trace", "Show full backtrace if an error occurs") do
|
||||
@trace = true
|
||||
end
|
||||
|
||||
opts.on_tail("-h", "--help", "Show this message") do
|
||||
puts self
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Execute all actions given the inputted args and options
|
||||
#
|
||||
# argv - (optional) command-line args (sans opts)
|
||||
# config - (optional) the Hash configuration of string key to value
|
||||
#
|
||||
# Returns nothing
|
||||
def execute(argv = [], config = {})
|
||||
if actions.empty? && !default_command.nil?
|
||||
default_command.execute
|
||||
else
|
||||
actions.each { |a| a.call(argv, config) }
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Check if this command has a subcommand
|
||||
#
|
||||
# sub_command - the name of the subcommand
|
||||
#
|
||||
# Returns true if this command is the parent of a command of name
|
||||
# 'sub_command' and false otherwise
|
||||
def has_command?(sub_command)
|
||||
commands.key?(sub_command)
|
||||
end
|
||||
|
||||
# Public: Identify this command
|
||||
#
|
||||
# Returns a string which identifies this command
|
||||
def ident
|
||||
"<Command name=#{identity}>"
|
||||
end
|
||||
|
||||
# Public: Get the full identity (name & version) of this command
|
||||
#
|
||||
# Returns a string containing the name and version if it exists
|
||||
def identity
|
||||
"#{full_name} #{version}".strip
|
||||
end
|
||||
|
||||
# Public: Get the name of the current command plus that of
|
||||
# its parent commands
|
||||
#
|
||||
# Returns the full name of the command
|
||||
def full_name
|
||||
the_name = []
|
||||
the_name << parent.full_name if parent&.full_name
|
||||
the_name << name
|
||||
the_name.join(" ")
|
||||
end
|
||||
|
||||
# Public: Return all the names and aliases for this command.
|
||||
#
|
||||
# Returns a comma-separated String list of the name followed by its aliases
|
||||
def names_and_aliases
|
||||
([name.to_s] + aliases).compact.join(", ")
|
||||
end
|
||||
|
||||
# Public: Build a string containing a summary of the command
|
||||
#
|
||||
# Returns a one-line summary of the command.
|
||||
def summarize
|
||||
" #{names_and_aliases.ljust(20)} #{description}"
|
||||
end
|
||||
|
||||
# Public: Build a string containing the command name, options and any subcommands
|
||||
#
|
||||
# Returns the string identifying this command, its options and its subcommands
|
||||
def to_s
|
||||
Presenter.new(self).print_command
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,89 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Mercenary
|
||||
class Option
|
||||
attr_reader :config_key, :description, :short, :long, :return_type
|
||||
|
||||
# Public: Create a new Option
|
||||
#
|
||||
# config_key - the key in the config hash to which the value of this option
|
||||
# will map
|
||||
# info - an array containing first the switches, then an optional
|
||||
# return type (e.g. Array), then a description of the option
|
||||
#
|
||||
# Returns nothing
|
||||
def initialize(config_key, info)
|
||||
@config_key = config_key
|
||||
while arg = info.shift
|
||||
begin
|
||||
@return_type = Object.const_get(arg.to_s)
|
||||
next
|
||||
rescue NameError
|
||||
end
|
||||
if arg.start_with?("-")
|
||||
if arg.start_with?("--")
|
||||
@long = arg
|
||||
else
|
||||
@short = arg
|
||||
end
|
||||
next
|
||||
end
|
||||
@description = arg
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Fetch the array containing the info OptionParser is interested in
|
||||
#
|
||||
# Returns the array which OptionParser#on wants
|
||||
def for_option_parser
|
||||
[short, long, return_type, description].flatten.reject { |o| o.to_s.empty? }
|
||||
end
|
||||
|
||||
# Public: Build a string representation of this option including the
|
||||
# switches and description
|
||||
#
|
||||
# Returns a string representation of this option
|
||||
def to_s
|
||||
"#{formatted_switches} #{description}"
|
||||
end
|
||||
|
||||
# Public: Build a beautifully-formatted string representation of the switches
|
||||
#
|
||||
# Returns a formatted string representation of the switches
|
||||
def formatted_switches
|
||||
[
|
||||
switches.first.rjust(10),
|
||||
switches.last.ljust(13),
|
||||
].join(", ").gsub(%r! , !, " ").gsub(%r!, !, " ")
|
||||
end
|
||||
|
||||
# Public: Hash based on the hash value of instance variables
|
||||
#
|
||||
# Returns a Fixnum which is unique to this Option based on the instance variables
|
||||
def hash
|
||||
instance_variables.map do |var|
|
||||
instance_variable_get(var).hash
|
||||
end.reduce(:^)
|
||||
end
|
||||
|
||||
# Public: Check equivalence of two Options based on equivalence of their
|
||||
# instance variables
|
||||
#
|
||||
# Returns true if all the instance variables are equal, false otherwise
|
||||
def eql?(other)
|
||||
return false unless self.class.eql?(other.class)
|
||||
|
||||
instance_variables.map do |var|
|
||||
instance_variable_get(var).eql?(other.instance_variable_get(var))
|
||||
end.all?
|
||||
end
|
||||
|
||||
# Public: Fetch an array of switches, including the short and long versions
|
||||
#
|
||||
# Returns an array of two strings. An empty string represents no switch in
|
||||
# that position.
|
||||
def switches
|
||||
[short, long].map(&:to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,103 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Mercenary
|
||||
class Presenter
|
||||
attr_accessor :command
|
||||
|
||||
# Public: Make a new Presenter
|
||||
#
|
||||
# command - a Mercenary::Command to present
|
||||
#
|
||||
# Returns nothing
|
||||
def initialize(command)
|
||||
@command = command
|
||||
end
|
||||
|
||||
# Public: Builds a string representation of the command usage
|
||||
#
|
||||
# Returns the string representation of the command usage
|
||||
def usage_presentation
|
||||
" #{command.syntax}"
|
||||
end
|
||||
|
||||
# Public: Builds a string representation of the options
|
||||
#
|
||||
# Returns the string representation of the options
|
||||
def options_presentation
|
||||
return nil unless command_options_presentation || parent_command_options_presentation
|
||||
|
||||
[command_options_presentation, parent_command_options_presentation].compact.join("\n")
|
||||
end
|
||||
|
||||
def command_options_presentation
|
||||
return nil if command.options.empty?
|
||||
|
||||
options = command.options
|
||||
options -= command.parent.options if command.parent
|
||||
options.map(&:to_s).join("\n")
|
||||
end
|
||||
|
||||
# Public: Builds a string representation of the options for parent
|
||||
# commands
|
||||
#
|
||||
# Returns the string representation of the options for parent commands
|
||||
def parent_command_options_presentation
|
||||
return nil unless command.parent
|
||||
|
||||
Presenter.new(command.parent).options_presentation
|
||||
end
|
||||
|
||||
# Public: Builds a string representation of the subcommands
|
||||
#
|
||||
# Returns the string representation of the subcommands
|
||||
def subcommands_presentation
|
||||
return nil if command.commands.empty?
|
||||
|
||||
command.commands.values.uniq.map(&:summarize).join("\n")
|
||||
end
|
||||
|
||||
# Public: Builds the command header, including the command identity and description
|
||||
#
|
||||
# Returns the command header as a String
|
||||
def command_header
|
||||
header = command.identity.to_s
|
||||
header << " -- #{command.description}" if command.description
|
||||
header
|
||||
end
|
||||
|
||||
# Public: Builds a string representation of the whole command
|
||||
#
|
||||
# Returns the string representation of the whole command
|
||||
def command_presentation
|
||||
msg = []
|
||||
msg << command_header
|
||||
msg << "Usage:"
|
||||
msg << usage_presentation
|
||||
|
||||
if opts = options_presentation
|
||||
msg << "Options:\n#{opts}"
|
||||
end
|
||||
if subcommands = subcommands_presentation
|
||||
msg << "Subcommands:\n#{subcommands_presentation}"
|
||||
end
|
||||
msg.join("\n\n")
|
||||
end
|
||||
|
||||
# Public: Turn a print_* into a *_presentation or freak out
|
||||
#
|
||||
# meth - the method being called
|
||||
# args - an array of arguments passed to the missing method
|
||||
# block - the block passed to the missing method
|
||||
#
|
||||
# Returns the value of whatever function is called
|
||||
def method_missing(meth, *args, &block)
|
||||
if meth.to_s =~ %r!^print_(.+)$!
|
||||
send("#{Regexp.last_match(1).downcase}_presentation")
|
||||
else
|
||||
# You *must* call super if you don't handle the method,
|
||||
# otherwise you'll mess up Ruby's method lookup.
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Mercenary
|
||||
class Program < Command
|
||||
attr_reader :optparse
|
||||
attr_reader :config
|
||||
|
||||
# Public: Creates a new Program
|
||||
#
|
||||
# name - the name of the program
|
||||
#
|
||||
# Returns nothing
|
||||
def initialize(name)
|
||||
@config = {}
|
||||
super(name)
|
||||
end
|
||||
|
||||
# Public: Run the program
|
||||
#
|
||||
# argv - an array of string args (usually ARGV)
|
||||
#
|
||||
# Returns nothing
|
||||
def go(argv)
|
||||
logger.debug("Using args passed in: #{argv.inspect}")
|
||||
|
||||
cmd = nil
|
||||
|
||||
@optparse = OptionParser.new do |opts|
|
||||
cmd = super(argv, opts, @config)
|
||||
end
|
||||
|
||||
begin
|
||||
@optparse.parse!(argv)
|
||||
rescue OptionParser::InvalidOption => e
|
||||
logger.error "Whoops, we can't understand your command."
|
||||
logger.error e.message.to_s
|
||||
logger.error "Run your command again with the --help switch to see available options."
|
||||
abort
|
||||
end
|
||||
|
||||
logger.debug("Parsed config: #{@config.inspect}")
|
||||
|
||||
begin
|
||||
cmd.execute(argv, @config)
|
||||
rescue StandardError => e
|
||||
if cmd.trace
|
||||
raise e
|
||||
else
|
||||
logger.error e.message
|
||||
abort
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Mercenary
|
||||
VERSION = "0.4.0"
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
lib = File.expand_path("lib", __dir__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require "mercenary/version"
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "mercenary"
|
||||
spec.version = Mercenary::VERSION
|
||||
spec.authors = ["Tom Preston-Werner", "Parker Moore"]
|
||||
spec.email = ["tom@mojombo.com", "parkrmoore@gmail.com"]
|
||||
spec.description = "Lightweight and flexible library for writing command-line apps in Ruby."
|
||||
spec.summary = "Lightweight and flexible library for writing command-line apps in Ruby."
|
||||
spec.homepage = "https://github.com/jekyll/mercenary"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
||||
spec.executables = spec.files.grep(%r!^bin/!) { |f| File.basename(f) }
|
||||
spec.test_files = spec.files.grep(%r!^(test|spec|features)/!)
|
||||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.required_ruby_version = ">= 2.4.0"
|
||||
|
||||
spec.add_development_dependency "bundler"
|
||||
spec.add_development_dependency "rake"
|
||||
spec.add_development_dependency "rspec", "~> 3.0"
|
||||
spec.add_development_dependency "rubocop-jekyll", "~> 0.10.0"
|
||||
end
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#! /bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "Time to get set up."
|
||||
bundle install
|
||||
echo "Boom."
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#! /bin/sh
|
||||
|
||||
set -ex
|
||||
|
||||
bundle exec rspec
|
||||
script/fmt
|
||||
script/examples
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#! /bin/bash
|
||||
|
||||
irb -r./lib/mercenary.rb
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#! /bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
function run () {
|
||||
echo "+ ruby ./examples/$@"
|
||||
ruby -e "puts '=' * 79"
|
||||
ruby ./examples/$@
|
||||
ruby -e "puts '=' * 79"
|
||||
}
|
||||
|
||||
run logging.rb
|
||||
run logging.rb -v
|
||||
run help_dialogue.rb -h
|
||||
run help_dialogue.rb some_subcommand -h
|
||||
run help_dialogue.rb another_subcommand -h
|
||||
run help_dialogue.rb some_subcommand yet_another_sub -h
|
||||
run help_dialogue.rb some_subcommand yet_another_sub -b
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Rubocop $(bundle exec rubocop --version)"
|
||||
bundle exec rubocop -S -D -E $@
|
||||
@@ -0,0 +1,98 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
describe(Mercenary::Command) do
|
||||
context "a basic command" do
|
||||
let(:command) { Mercenary::Command.new(:my_name) }
|
||||
let(:parent) { Mercenary::Command.new(:my_parent) }
|
||||
let(:with_sub) do
|
||||
c = Mercenary::Command.new(:i_have_subcommand)
|
||||
add_sub.call(c)
|
||||
c
|
||||
end
|
||||
let(:command_with_parent) do
|
||||
Mercenary::Command.new(
|
||||
:i_have_parent,
|
||||
parent
|
||||
)
|
||||
end
|
||||
let(:add_sub) do
|
||||
proc do |c|
|
||||
c.command(:sub_command) { |p| }
|
||||
end
|
||||
end
|
||||
|
||||
it "can be created with just a name" do
|
||||
expect(command.name).to eql(:my_name)
|
||||
end
|
||||
|
||||
it "can hold a parent command" do
|
||||
expect(command_with_parent.parent).to eql(parent)
|
||||
end
|
||||
|
||||
it "can create subcommands" do
|
||||
expect(add_sub.call(command)).to be_a(Mercenary::Command)
|
||||
expect(add_sub.call(command).parent).to eq(command)
|
||||
end
|
||||
|
||||
it "can set its version" do
|
||||
version = "1.4.2"
|
||||
command.version version
|
||||
expect(command.version).to eq(version)
|
||||
end
|
||||
|
||||
it "can set its syntax" do
|
||||
syntax_string = "my_name [options]"
|
||||
cmd = described_class.new(:my_name)
|
||||
cmd.syntax syntax_string
|
||||
expect(cmd.syntax).to eq(syntax_string)
|
||||
end
|
||||
|
||||
it "can set its description" do
|
||||
desc = "run all the things"
|
||||
command.description desc
|
||||
expect(command.description).to eq(desc)
|
||||
end
|
||||
|
||||
it "can set its options" do
|
||||
name = "show_drafts"
|
||||
opts = ["--drafts", "Render posts in the _drafts folder"]
|
||||
option = Mercenary::Option.new(name, opts)
|
||||
command.option name, *opts
|
||||
expect(command.options).to eql([option])
|
||||
expect(command.map.values).to include(name)
|
||||
end
|
||||
|
||||
it "knows its full name" do
|
||||
expect(command_with_parent.full_name).to eql("my_parent i_have_parent")
|
||||
end
|
||||
|
||||
it "knows its identity" do
|
||||
command_with_parent.version "1.8.7"
|
||||
expect(command_with_parent.identity).to eql("my_parent i_have_parent 1.8.7")
|
||||
end
|
||||
|
||||
it "raises an ArgumentError if I specify a default_command that isn't there" do
|
||||
c = command # some weird NameError with the block below?
|
||||
expect { c.default_command(:nope) }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "sets the default_command" do
|
||||
expect(with_sub.default_command(:sub_command).name).to eq(:sub_command)
|
||||
end
|
||||
|
||||
context "with an alias" do
|
||||
before(:each) do
|
||||
command_with_parent.alias(:an_alias)
|
||||
end
|
||||
it "shows the alias in the summary" do
|
||||
expect(command_with_parent.summarize).to eql(" i_have_parent, an_alias ")
|
||||
end
|
||||
|
||||
it "its names_and_aliases method reports both the name and alias" do
|
||||
expect(command_with_parent.names_and_aliases).to eql("i_have_parent, an_alias")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
describe(Mercenary::Option) do
|
||||
let(:config_key) { "largo" }
|
||||
let(:description) { "This is a description" }
|
||||
let(:switches) { ["-l", "--largo"] }
|
||||
let(:option) { described_class.new(config_key, [switches, description].flatten.reject(&:nil?)) }
|
||||
|
||||
it "knows its config key" do
|
||||
expect(option.config_key).to eql(config_key)
|
||||
end
|
||||
|
||||
it "knows its description" do
|
||||
expect(option.description).to eql(description)
|
||||
end
|
||||
|
||||
it "knows its switches" do
|
||||
expect(option.switches).to eql(switches)
|
||||
end
|
||||
|
||||
it "knows how to present itself" do
|
||||
expect(option.to_s).to eql(" -l, --largo #{description}")
|
||||
end
|
||||
|
||||
it "has an OptionParser representation" do
|
||||
expect(option.for_option_parser).to eql([switches, description].flatten)
|
||||
end
|
||||
|
||||
it "compares itself with other options well" do
|
||||
new_option = described_class.new(config_key, ["-l", "--largo", description])
|
||||
expect(option.eql?(new_option)).to be(true)
|
||||
expect(option.hash.eql?(new_option.hash)).to be(true)
|
||||
end
|
||||
|
||||
it "has a custom #hash" do
|
||||
expect(option.hash.to_s).to match(%r!\d+!)
|
||||
end
|
||||
|
||||
context "with just the long switch" do
|
||||
let(:switches) { ["--largo"] }
|
||||
|
||||
it "adds an empty string in place of the short switch" do
|
||||
expect(option.switches).to eql(["", "--largo"])
|
||||
end
|
||||
|
||||
it "sets its description properly" do
|
||||
expect(option.description).to eql(description)
|
||||
end
|
||||
|
||||
it "knows how to present the switch" do
|
||||
expect(option.formatted_switches).to eql(" --largo ")
|
||||
end
|
||||
end
|
||||
|
||||
context "with just the short switch" do
|
||||
let(:switches) { ["-l"] }
|
||||
|
||||
it "adds an empty string in place of the long switch" do
|
||||
expect(option.switches).to eql(["-l", ""])
|
||||
end
|
||||
|
||||
it "sets its description properly" do
|
||||
expect(option.description).to eql(description)
|
||||
end
|
||||
|
||||
it "knows how to present the switch" do
|
||||
expect(option.formatted_switches).to eql(" -l ")
|
||||
end
|
||||
end
|
||||
|
||||
context "without a description" do
|
||||
let(:description) { nil }
|
||||
|
||||
it "knows there is no description" do
|
||||
expect(option.description).to be(nil)
|
||||
end
|
||||
|
||||
it "knows both inputs are switches" do
|
||||
expect(option.switches).to eql(switches)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
describe(Mercenary::Presenter) do
|
||||
let(:supercommand) { Mercenary::Command.new(:script_name) }
|
||||
let(:command) { Mercenary::Command.new(:subcommand, supercommand) }
|
||||
let(:presenter) { described_class.new(command) }
|
||||
|
||||
before(:each) do
|
||||
supercommand.option "version", "-v", "--version", "Show version"
|
||||
supercommand.option "help", "-h", "--help", "Help!"
|
||||
|
||||
command.version "1.4.2"
|
||||
command.description "Do all the things."
|
||||
command.option "help", "-h", "--help", "Help!"
|
||||
command.option "one", "-1", "--one", "The first option"
|
||||
command.option "two", "-2", "--two", "The second option"
|
||||
command.alias :cmd
|
||||
supercommand.commands[command.name] = command
|
||||
end
|
||||
|
||||
it "knows how to present the command" do
|
||||
expect(presenter.command_presentation).to eql("script_name subcommand 1.4.2 -- Do all the things.\n\nUsage:\n\n script_name subcommand\n\nOptions:\n -1, --one The first option\n -2, --two The second option\n -v, --version Show version\n -h, --help Help!")
|
||||
end
|
||||
|
||||
it "knows how to present the subcommands, without duplicates for aliases" do
|
||||
expect(described_class.new(supercommand).subcommands_presentation).to eql(" subcommand, cmd Do all the things.")
|
||||
end
|
||||
|
||||
it "knows how to present the usage" do
|
||||
expect(presenter.usage_presentation).to eql(" script_name subcommand")
|
||||
end
|
||||
|
||||
it "knows how to present the options" do
|
||||
expect(presenter.options_presentation).to eql(" -1, --one The first option\n -2, --two The second option\n -v, --version Show version\n -h, --help Help!")
|
||||
end
|
||||
|
||||
it "allows you to say print_* instead of *_presentation" do
|
||||
expect(presenter.print_usage).to eql(presenter.usage_presentation)
|
||||
expect(presenter.print_subcommands).to eql(presenter.subcommands_presentation)
|
||||
expect(presenter.print_options).to eql(presenter.options_presentation)
|
||||
expect(presenter.print_command).to eql(presenter.command_presentation)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
describe(Mercenary::Program) do
|
||||
context "a basic program" do
|
||||
let(:program) { Mercenary::Program.new(:my_name) }
|
||||
|
||||
it "can be created with just a name" do
|
||||
expect(program.name).to eql(:my_name)
|
||||
end
|
||||
|
||||
it "can set its version" do
|
||||
version = Mercenary::VERSION
|
||||
program.version version
|
||||
expect(program.version).to eq(version)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
lib = File.expand_path("../lib", __dir__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require "mercenary"
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.run_all_when_everything_filtered = true
|
||||
config.filter_run :focus
|
||||
|
||||
# Run specs in random order to surface order dependencies. If you find an
|
||||
# order dependency and want to debug it, you can fix the order by providing
|
||||
# the seed, which is printed after each run.
|
||||
# --seed 1234
|
||||
config.order = "random"
|
||||
end
|
||||
Reference in New Issue
Block a user