clin 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/timcolonel/clin.svg?branch=master)](https://travis-ci.org/timcolonel/clin)
|
3
|
+
[![Coverage Status](https://coveralls.io/repos/timcolonel/clin/badge.svg?branch=master)](https://coveralls.io/r/timcolonel/clin?branch=master)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/timcolonel/clin/badges/gpa.svg)](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
|