clin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.codeclimate.yml +5 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +19 -0
- data/.travis.yml +14 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +98 -0
- data/Rakefile +10 -0
- data/clin.gemspec +27 -0
- data/examples/dispatcher.rb +76 -0
- data/examples/nested_dispatcher.rb +91 -0
- data/examples/optional_argument.rb +24 -0
- data/examples/simple.rb +39 -0
- data/examples/test.rb +2 -0
- data/lib/clin/argument.rb +90 -0
- data/lib/clin/command.rb +195 -0
- data/lib/clin/command_dispatcher.rb +48 -0
- data/lib/clin/command_options_mixin.rb +88 -0
- data/lib/clin/common/help_options.rb +25 -0
- data/lib/clin/errors.rb +31 -0
- data/lib/clin/general_option.rb +15 -0
- data/lib/clin/option.rb +102 -0
- data/lib/clin/version.rb +4 -0
- data/lib/clin.rb +29 -0
- data/spec/cli/argument_spec.rb +117 -0
- data/spec/cli/command_dispacher_spec.rb +84 -0
- data/spec/cli/command_options_mixin_spec.rb +64 -0
- data/spec/cli/command_spec.rb +225 -0
- data/spec/cli/common/help_options_spec.rb +29 -0
- data/spec/cli/option_spec.rb +132 -0
- data/spec/errors_spec.rb +8 -0
- data/spec/examples/simple_spec.rb +31 -0
- data/spec/spec_helper.rb +28 -0
- metadata +171 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 98bdce921d4896c86ed482d8db5cf87a74b2795a
|
4
|
+
data.tar.gz: e025be0b4c3c5fd36f49437e21abfc1f00ef1a0c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8a70c2a7c6e53bf8eb781b37841dc24052e8396a2b6a2e8d73d6ea9dc90525fe1e03bf2437f43aefdeaf6aef648b210d7c79fec2fd8d7a31734c5ab21da7d9c1
|
7
|
+
data.tar.gz: ef60cf35207752817b52bae1514c55f5db9c12e5f87ae322a17469b9039b6a37470327c6bb4e4d43d37f024c5ae5acd283ec200090c6bad5f6705739086edd49
|
data/.codeclimate.yml
ADDED
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Metrics/LineLength:
|
2
|
+
Max: 100
|
3
|
+
|
4
|
+
Style/ClassAndModuleChildren:
|
5
|
+
Enabled: false
|
6
|
+
|
7
|
+
Style/SpaceInsideHashLiteralBraces:
|
8
|
+
EnforcedStyle: no_space
|
9
|
+
|
10
|
+
Metrics/MethodLength:
|
11
|
+
Description: Avoid methods longer than 25 lines of code.
|
12
|
+
Enabled: true
|
13
|
+
CountComments: false
|
14
|
+
Max: 25
|
15
|
+
|
16
|
+
|
17
|
+
AllCops:
|
18
|
+
Exclude:
|
19
|
+
- 'spec/**/*'
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Timothee Guerin
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# Clin
|
2
|
+
[](https://travis-ci.org/timcolonel/clin)
|
3
|
+
[](https://coveralls.io/r/timcolonel/clin?branch=master)
|
4
|
+
[](https://codeclimate.com/github/timcolonel/clin)
|
5
|
+
|
6
|
+
Clin is Command Line Interface library that provide an clean api for complex command configuration.
|
7
|
+
The way Clin is design allow a command defined by the user to be called via the command line as well as directly in the code without any additional configuration
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'clin'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install clin
|
23
|
+
|
24
|
+
Then add the following in you ruby script.
|
25
|
+
```ruby
|
26
|
+
require 'clin'
|
27
|
+
```
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
The [examples](examples/) folder contains various use case of Clin.
|
31
|
+
|
32
|
+
### Define a command
|
33
|
+
To define a command you must create a new class that inherit `Clin::Command`:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class DisplayCommand < Clin::Command
|
37
|
+
def run
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
#### Specify arguments
|
44
|
+
To specify what argument your command takes use the `.arguments` method.
|
45
|
+
Clin will then automatically extract the arguments when parsing and pass them when creating the object.
|
46
|
+
You can after access the arguments with @params
|
47
|
+
```ruby
|
48
|
+
class DisplayCommand < Clin::Command
|
49
|
+
arguments 'display <message>'
|
50
|
+
|
51
|
+
def run
|
52
|
+
puts "Display message: #{params[:message}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
Then calling `DisplayCommand.parse('display "Hello World!"').run` will print `Display message: Hello World!`
|
58
|
+
|
59
|
+
#### Specify options
|
60
|
+
You can also specify options using the `.option` method.
|
61
|
+
```ruby
|
62
|
+
class DisplayCommand < Clin::Command
|
63
|
+
arguments 'display <message>'
|
64
|
+
option :times, 'Display the message n times'
|
65
|
+
|
66
|
+
def run
|
67
|
+
params[:times] ||= 1
|
68
|
+
params[:times].times.each do
|
69
|
+
puts "Display message: #{params[:message}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
### Dispatch to the right command
|
76
|
+
For complex command line interface you might need several different commands(e.g. git add, git commit,etc.)
|
77
|
+
Define each command as shown previously then use the CommandDispatcher to choose the right command.
|
78
|
+
By default the dispatcher is going to try all the loaded commands(All subclasses of Clin::Commands)
|
79
|
+
but it can be filter.
|
80
|
+
```ruby
|
81
|
+
# Suppose Git::Add and Git::Commit are Clin::Command.
|
82
|
+
Clin::CommandDispatcher.parse('commit -m "initial commit") #=> Git::Commit<params: {message: "initial commit"}>
|
83
|
+
```
|
84
|
+
|
85
|
+
To limit filter the usage:
|
86
|
+
```ruby
|
87
|
+
# Suppose Git::Add, Git::Commit, Git::Push are Clin::Command.
|
88
|
+
dispatcher = Clin::CommandDispatcher.new(Git::Commit, Git::Push)
|
89
|
+
dispatcher.parse('commit add -A") #=> Will show the help as no command match.
|
90
|
+
dispatcher.parse('commit -m "initial commit") #=> Git::Commit<params: {message: "initial commit"}>
|
91
|
+
```
|
92
|
+
## Contributing
|
93
|
+
|
94
|
+
1. Fork it ( https://github.com/timcolonel/clin/fork )
|
95
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
96
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
97
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
98
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/clin.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'clin/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'clin'
|
8
|
+
spec.version = Clin::VERSION
|
9
|
+
spec.authors = ['Timothee Guerin']
|
10
|
+
spec.email = ['timothee.guerin@outlook.com']
|
11
|
+
spec.summary = 'Clin provide an advance way to define complex command line interface.'
|
12
|
+
spec.description = ''
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'activesupport', '>=4.0'
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
spec.add_development_dependency 'rspec'
|
25
|
+
spec.add_development_dependency 'coveralls'
|
26
|
+
spec.add_development_dependency 'faker'
|
27
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'clin'
|
3
|
+
|
4
|
+
# Simple command Example
|
5
|
+
class DisplayCommand < Clin::Command
|
6
|
+
arguments 'display <message>'
|
7
|
+
|
8
|
+
general_option Clin::HelpOptions
|
9
|
+
|
10
|
+
self.description = 'Display the given message'
|
11
|
+
|
12
|
+
def run
|
13
|
+
puts "Display: '#{params[:message]}'"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Simple command Example
|
18
|
+
class PrintCommand < Clin::Command
|
19
|
+
arguments 'print <message>'
|
20
|
+
|
21
|
+
general_option Clin::HelpOptions
|
22
|
+
|
23
|
+
self.description = 'Print the given message'
|
24
|
+
|
25
|
+
def run
|
26
|
+
puts "Print: '#{params[:message]}'"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Clin::CommandDispatcher.parse('display "My Message"').run
|
31
|
+
puts
|
32
|
+
puts '=' * 60
|
33
|
+
puts
|
34
|
+
Clin::CommandDispatcher.parse('print "My Message"').run
|
35
|
+
puts
|
36
|
+
puts '=' * 60
|
37
|
+
puts
|
38
|
+
begin
|
39
|
+
Clin::CommandDispatcher.parse('display -h').run
|
40
|
+
rescue Clin::CommandLineError => e
|
41
|
+
puts e
|
42
|
+
end
|
43
|
+
puts
|
44
|
+
puts '=' * 60
|
45
|
+
puts
|
46
|
+
begin
|
47
|
+
Clin::CommandDispatcher.parse('-h')
|
48
|
+
rescue Clin::CommandLineError => e
|
49
|
+
puts e
|
50
|
+
end
|
51
|
+
|
52
|
+
# Output:
|
53
|
+
#
|
54
|
+
# $ ruby dispatcher.rb
|
55
|
+
# Display: 'My Message'
|
56
|
+
#
|
57
|
+
# ============================================================
|
58
|
+
#
|
59
|
+
# Print: 'My Message'
|
60
|
+
#
|
61
|
+
# ============================================================
|
62
|
+
#
|
63
|
+
# Usage: command display <message> [Options]
|
64
|
+
#
|
65
|
+
# Options:
|
66
|
+
# -h, --help Show the help.
|
67
|
+
#
|
68
|
+
# Description:
|
69
|
+
# Display the given message
|
70
|
+
#
|
71
|
+
#
|
72
|
+
# ============================================================
|
73
|
+
#
|
74
|
+
# Usage:
|
75
|
+
# command display <message> [Options]
|
76
|
+
# command print <message> [Options]
|
@@ -0,0 +1,91 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'clin'
|
3
|
+
|
4
|
+
# Simple dispatch Example
|
5
|
+
class DispatchCommand < Clin::Command
|
6
|
+
arguments 'you <args>...'
|
7
|
+
dispatch :args, prefix: 'you'
|
8
|
+
general_option Clin::HelpOptions
|
9
|
+
|
10
|
+
self.description = 'YOU print the given message'
|
11
|
+
|
12
|
+
def run
|
13
|
+
puts 'Should not be called'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Simple command Example
|
18
|
+
class DisplayCommand < DispatchCommand
|
19
|
+
arguments 'you display <message>'
|
20
|
+
|
21
|
+
general_option Clin::HelpOptions
|
22
|
+
|
23
|
+
self.description = 'Display the given message'
|
24
|
+
|
25
|
+
def run
|
26
|
+
puts "I Display: '#{params[:message]}'"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Simple command Example
|
31
|
+
class PrintCommand < DispatchCommand
|
32
|
+
arguments 'you print <message>'
|
33
|
+
|
34
|
+
general_option Clin::HelpOptions
|
35
|
+
|
36
|
+
self.description = 'Print the given message'
|
37
|
+
|
38
|
+
def run
|
39
|
+
puts "I Print: '#{params[:message]}'"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
Clin::CommandDispatcher.parse('you display "My Message"').run
|
45
|
+
puts
|
46
|
+
puts '=' * 60
|
47
|
+
puts
|
48
|
+
Clin::CommandDispatcher.parse('you print "My Message"').run
|
49
|
+
puts
|
50
|
+
puts '=' * 60
|
51
|
+
puts
|
52
|
+
begin
|
53
|
+
Clin::CommandDispatcher.parse('you -h').run
|
54
|
+
rescue Clin::CommandLineError => e
|
55
|
+
puts e
|
56
|
+
end
|
57
|
+
puts
|
58
|
+
puts '=' * 60
|
59
|
+
puts
|
60
|
+
begin
|
61
|
+
Clin::CommandDispatcher.parse('-h')
|
62
|
+
rescue Clin::CommandLineError => e
|
63
|
+
puts e
|
64
|
+
end
|
65
|
+
|
66
|
+
# Output:
|
67
|
+
#
|
68
|
+
# $ ruby dispatcher.rb
|
69
|
+
# I Display: 'My Message'
|
70
|
+
#
|
71
|
+
# ============================================================
|
72
|
+
#
|
73
|
+
# I Print: 'My Message'
|
74
|
+
#
|
75
|
+
# ============================================================
|
76
|
+
#
|
77
|
+
# Usage: command you <args>... [Options]
|
78
|
+
#
|
79
|
+
# Options:
|
80
|
+
# -h, --help Show the help.
|
81
|
+
#
|
82
|
+
# Description:
|
83
|
+
# YOU print the given message
|
84
|
+
#
|
85
|
+
#
|
86
|
+
# ============================================================
|
87
|
+
#
|
88
|
+
# Usage:
|
89
|
+
# command you <args>... [Options]
|
90
|
+
# command you display <message> [Options]
|
91
|
+
# command you print <message> [Options]
|
@@ -0,0 +1,24 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'clin'
|
3
|
+
|
4
|
+
# Simple command Example
|
5
|
+
class OptionalArgumentCommand < Clin::Command
|
6
|
+
arguments 'display [<message>]'
|
7
|
+
|
8
|
+
def run
|
9
|
+
puts params.fetch(:message, 'No message given')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
OptionalArgumentCommand.parse('display "My Message"')
|
14
|
+
puts
|
15
|
+
puts '=' * 60
|
16
|
+
puts
|
17
|
+
OptionalArgumentCommand.parse('display')
|
18
|
+
|
19
|
+
# $ ruby optional_argument.rb
|
20
|
+
# My Message
|
21
|
+
#
|
22
|
+
# ============================================================
|
23
|
+
#
|
24
|
+
# No message given
|
data/examples/simple.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'clin'
|
3
|
+
require 'clin'
|
4
|
+
|
5
|
+
# Simple command Example
|
6
|
+
class SimpleCommand < Clin::Command
|
7
|
+
arguments 'display <message>'
|
8
|
+
|
9
|
+
option :echo, 'Echo some text'
|
10
|
+
general_option Clin::HelpOptions
|
11
|
+
|
12
|
+
def run
|
13
|
+
puts @params[:message]
|
14
|
+
puts @params[:echo]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
if __FILE__== $0
|
19
|
+
SimpleCommand.parse('display "My Message" --echo SOME').run
|
20
|
+
puts
|
21
|
+
puts '=' * 60
|
22
|
+
puts
|
23
|
+
begin
|
24
|
+
SimpleCommand.parse('').run
|
25
|
+
rescue Clin::HelpError => e
|
26
|
+
puts e
|
27
|
+
end
|
28
|
+
end
|
29
|
+
# $ ruby simple.rb
|
30
|
+
# My Message
|
31
|
+
# SOME
|
32
|
+
#
|
33
|
+
# ============================================================
|
34
|
+
#
|
35
|
+
# Usage: command display <message> [Options]
|
36
|
+
#
|
37
|
+
# Options:
|
38
|
+
# -e, --echo ECHO Echo some text
|
39
|
+
# -h, --help Show the help.
|
data/examples/test.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'clin'
|
2
|
+
|
3
|
+
# Command line positional argument(not option)
|
4
|
+
class Clin::Argument
|
5
|
+
attr_accessor :original
|
6
|
+
attr_accessor :optional
|
7
|
+
attr_accessor :multiple
|
8
|
+
attr_accessor :variable
|
9
|
+
attr_accessor :name
|
10
|
+
|
11
|
+
def initialize(argument)
|
12
|
+
@original = argument
|
13
|
+
@optional = false
|
14
|
+
@multiple = false
|
15
|
+
@variable = false
|
16
|
+
argument = check_optional(argument)
|
17
|
+
argument = check_multiple(argument)
|
18
|
+
@name = check_variable(argument)
|
19
|
+
end
|
20
|
+
|
21
|
+
def check_optional(argument)
|
22
|
+
if check_between(argument, '[', ']')
|
23
|
+
@optional = true
|
24
|
+
return argument[1...-1]
|
25
|
+
end
|
26
|
+
argument
|
27
|
+
end
|
28
|
+
|
29
|
+
def check_multiple(argument)
|
30
|
+
if argument.end_with? '...'
|
31
|
+
@multiple = true
|
32
|
+
return argument[0...-3]
|
33
|
+
end
|
34
|
+
argument
|
35
|
+
end
|
36
|
+
|
37
|
+
def check_variable(argument)
|
38
|
+
if check_between(argument, '<', '>')
|
39
|
+
@variable = true
|
40
|
+
return argument[1...-1]
|
41
|
+
end
|
42
|
+
argument
|
43
|
+
end
|
44
|
+
|
45
|
+
# Given a list of arguments extract the list of arguments that are matched
|
46
|
+
def parse(argv)
|
47
|
+
return handle_empty if argv.empty?
|
48
|
+
if @multiple
|
49
|
+
ensure_name(argv) unless @variable
|
50
|
+
[argv, []]
|
51
|
+
else
|
52
|
+
ensure_name(argv[0]) unless @variable
|
53
|
+
[argv[0], argv[1..-1]]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def ensure_name(args)
|
60
|
+
[*args].each do |arg|
|
61
|
+
if arg != @name
|
62
|
+
fail Clin::FixedArgumentError, @name, arg
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Call when the argv is empty.
|
68
|
+
# Will return nil, [] if the argument is optional
|
69
|
+
# Will fail otherwise:
|
70
|
+
# * MissingArgumentError if the argument is a variable(e.g. <arg>)
|
71
|
+
# * FixedArgumentError if the argument is fixed(e.g. display)
|
72
|
+
def handle_empty
|
73
|
+
return nil, [] if optional
|
74
|
+
if @variable
|
75
|
+
fail Clin::MissingArgumentError, @name
|
76
|
+
else
|
77
|
+
fail Clin::FixedArgumentError, @name
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def check_between(argument, start_char, end_char)
|
82
|
+
if argument[0] == start_char
|
83
|
+
if argument[-1] != end_char
|
84
|
+
fail Clin::Error, "Argument format error! Cannot start with #{start_char} and not end with #{end_char}"
|
85
|
+
end
|
86
|
+
return true
|
87
|
+
end
|
88
|
+
false
|
89
|
+
end
|
90
|
+
end
|