clin 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.lint-ci.yml +2 -0
- data/.simplecov +5 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +11 -0
- data/README.md +5 -4
- data/benchmarks/bench.rb +21 -0
- data/benchmarks/text_bench.rb +78 -0
- data/clin.gemspec +2 -1
- data/examples/reusable_options.rb +19 -0
- data/examples/simple.rb +8 -3
- data/examples/test.rb +5 -5
- data/examples/text_builder.rb +40 -0
- data/lib/clin/argument.rb +19 -2
- data/lib/clin/command_mixin/core.rb +13 -18
- data/lib/clin/command_mixin/options.rb +37 -26
- data/lib/clin/command_parser.rb +46 -57
- data/lib/clin/common/help_options.rb +1 -0
- data/lib/clin/errors.rb +50 -4
- data/lib/clin/line_reader/basic.rb +38 -0
- data/lib/clin/line_reader/readline.rb +53 -0
- data/lib/clin/line_reader.rb +16 -0
- data/lib/clin/option.rb +24 -11
- data/lib/clin/option_parser.rb +159 -0
- data/lib/clin/shell.rb +36 -15
- data/lib/clin/shell_interaction/choose.rb +19 -11
- data/lib/clin/shell_interaction/file_conflict.rb +4 -1
- data/lib/clin/shell_interaction/select.rb +44 -0
- data/lib/clin/shell_interaction.rb +1 -0
- data/lib/clin/text/table.rb +270 -0
- data/lib/clin/text.rb +152 -0
- data/lib/clin/version.rb +1 -1
- data/lib/clin.rb +10 -1
- data/spec/clin/command_dispacher_spec.rb +1 -1
- data/spec/clin/command_mixin/options_spec.rb +38 -15
- data/spec/clin/command_parser_spec.rb +27 -51
- data/spec/clin/line_reader/basic_spec.rb +54 -0
- data/spec/clin/line_reader/readline_spec.rb +64 -0
- data/spec/clin/line_reader_spec.rb +17 -0
- data/spec/clin/option_parser_spec.rb +217 -0
- data/spec/clin/option_spec.rb +5 -7
- data/spec/clin/shell_interaction/choose_spec.rb +30 -0
- data/spec/clin/shell_interaction/file_interaction_spec.rb +18 -0
- data/spec/clin/shell_interaction/select_spec.rb +96 -0
- data/spec/clin/shell_spec.rb +42 -0
- data/spec/clin/text/table_cell_spec.rb +72 -0
- data/spec/clin/text/table_row_spec.rb +74 -0
- data/spec/clin/text/table_separator_row_spec.rb +82 -0
- data/spec/clin/text/table_spec.rb +259 -0
- data/spec/clin/text_spec.rb +158 -0
- data/spec/examples/list_option_spec.rb +6 -2
- data/spec/examples/reusable_options_spec.rb +21 -0
- data/spec/examples/simple_spec.rb +9 -9
- data/spec/spec_helper.rb +3 -2
- metadata +54 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 398c6c91b940d2efda70f9879e2743ae5c16c33c
|
4
|
+
data.tar.gz: b36c46118f12f7322ced5a53fe519f0402a45a19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9759baa328947f0dbf9247f0e96997f6e060c28bb35dbd98b18daa3aff46b928f595fb80fa34b9fd7072fad59cd1b186540fdadb81abed0e7b5a3114ebfec28f
|
7
|
+
data.tar.gz: 5fcd4a5a2a05a86488d65b62737e21d87b1c8878a0c4306f6b855c78319bc0cede1d0b2de75df313913607991f695a794be703838373596e558410462725778f
|
data/.lint-ci.yml
ADDED
data/.simplecov
ADDED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## 0.4.0
|
2
|
+
Features:
|
3
|
+
- Command line parsing is now done internally, i.e. removed optparse (#6).
|
4
|
+
- Added a text builder interface.
|
5
|
+
- Added a table builder interface.
|
6
|
+
- Shell interact with text builder interface
|
7
|
+
- Shell#say, Shell#indent, Shell#password
|
8
|
+
|
9
|
+
Bug fix:
|
10
|
+
- OptParse was hijacking the -v --version.
|
11
|
+
|
1
12
|
## 0.3.0
|
2
13
|
Features:
|
3
14
|
- Added a shell class for any user interaction.
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# Clin
|
2
|
-
[![
|
3
|
-
[![
|
4
|
-
[![Code Climate](https://
|
5
|
-
[![
|
2
|
+
[![Gem](https://img.shields.io/gem/v/clin.svg?style=flat-square)](http://rubygems.org/gems/clin)
|
3
|
+
[![Build Status](https://img.shields.io/travis/timcolonel/clin/master.svg?style=flat-square)](https://travis-ci.org/timcolonel/clin)
|
4
|
+
[![Code Climate](https://img.shields.io/codeclimate/coverage/github/timcolonel/clin.svg?style=flat-square)](https://codeclimate.com/github/timcolonel/clin/coverage)
|
5
|
+
[![Test Coverage](https://img.shields.io/codeclimate/github/timcolonel/clin.svg?style=flat-square)](https://codeclimate.com/github/timcolonel/clin)
|
6
|
+
[![Inline docs](http://inch-ci.org/github/timcolonel/clin.svg?branch=master&style=flat-square)](http://inch-ci.org/github/timcolonel/clin)
|
6
7
|
|
7
8
|
Clin is Command Line Interface library that provide an clean api for complex command configuration.
|
8
9
|
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
|
data/benchmarks/bench.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
$LOAD_PATH.push File.expand_path('../..', __FILE__)
|
3
|
+
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
4
|
+
require 'clin'
|
5
|
+
|
6
|
+
Benchmark.ips do |x|
|
7
|
+
x.report('simple') do
|
8
|
+
require 'examples/simple'
|
9
|
+
SimpleCommand.parse('display Some -e "Even More"')
|
10
|
+
end
|
11
|
+
|
12
|
+
x.report('auto_option') do
|
13
|
+
require 'examples/auto_option'
|
14
|
+
AutoOptionCommand.parse('--eko="Lorem ipsum"')
|
15
|
+
end
|
16
|
+
|
17
|
+
x.report('nested_dispatcher') do
|
18
|
+
require 'examples/nested_dispatcher'
|
19
|
+
DispatchCommand.parse('you display Some --verbose -e More --times 3')
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
$LOAD_PATH.push File.expand_path('../..', __FILE__)
|
3
|
+
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
4
|
+
require 'clin'
|
5
|
+
|
6
|
+
values = ['Some value 1', 'Some value 2']
|
7
|
+
Benchmark.ips do |x|
|
8
|
+
x.report('Normal <<') do
|
9
|
+
t = ''
|
10
|
+
t << 'Usage:' << "\n"
|
11
|
+
t << ' display Message' << "\n"
|
12
|
+
t << ' print Message' << "\n"
|
13
|
+
t << ' --echo' << "\n"
|
14
|
+
t << ' --verbose' << "\n"
|
15
|
+
t << 'Description: ' << "\n"
|
16
|
+
t << ' This is a description' << "\n"
|
17
|
+
t << '' << "\n"
|
18
|
+
t << 'Examples:' << "\n"
|
19
|
+
t << 'Examples:' << "\n"
|
20
|
+
t << ' -display Message' << "\n"
|
21
|
+
t << ' -print Message' << "\n"
|
22
|
+
t << 'Values: ' << "\n"
|
23
|
+
values.each do |val|
|
24
|
+
t << "***#{val}" << "\n"
|
25
|
+
end
|
26
|
+
t = 'You have an error!' + "\n" << t
|
27
|
+
end
|
28
|
+
|
29
|
+
x.report('Normal +=') do
|
30
|
+
t = ''
|
31
|
+
t += 'Usage:' + "\n"
|
32
|
+
t += ' display Message' + "\n"
|
33
|
+
t += ' print Message' + "\n"
|
34
|
+
t += ' --echo' + "\n"
|
35
|
+
t += ' --verbose' + "\n"
|
36
|
+
t += 'Description: ' + "\n"
|
37
|
+
t += ' This is a description' + "\n"
|
38
|
+
t += '' + "\n"
|
39
|
+
t += 'Examples:' + "\n"
|
40
|
+
t += 'Examples:' + "\n"
|
41
|
+
t += ' -display Message' + "\n"
|
42
|
+
t += ' -print Message' + "\n"
|
43
|
+
t += 'Values: ' + "\n"
|
44
|
+
values.each do |val|
|
45
|
+
t += "***#{val}" + "\n"
|
46
|
+
end
|
47
|
+
t = 'You have an error!' + "\n" + t
|
48
|
+
end
|
49
|
+
|
50
|
+
x.report('Text builder') do
|
51
|
+
Clin::Text.new do |t|
|
52
|
+
t.line 'Usage:'
|
53
|
+
t.indent ' ' do
|
54
|
+
t.line 'display Message'
|
55
|
+
t.line 'print Message'
|
56
|
+
|
57
|
+
t.indent ' ' do
|
58
|
+
t.line '--echo'
|
59
|
+
t.line '--verbose'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
t.line 'Description: '
|
63
|
+
t.line 'This is a description', indent: ' '
|
64
|
+
t.blank
|
65
|
+
t.line 'Examples:'
|
66
|
+
t.indent ' -' do
|
67
|
+
t.line 'display Message'
|
68
|
+
t.line 'print Message'
|
69
|
+
end
|
70
|
+
t.line 'Values: '
|
71
|
+
t.lines values, indent: '*** '
|
72
|
+
|
73
|
+
t.prefix 'You have an error!'
|
74
|
+
end.to_s
|
75
|
+
end
|
76
|
+
|
77
|
+
x.compare!
|
78
|
+
end
|
data/clin.gemspec
CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_development_dependency 'bundler', '~> 1.7'
|
23
23
|
spec.add_development_dependency 'rake', '~> 10.0'
|
24
24
|
spec.add_development_dependency 'rspec', '>= 3.0'
|
25
|
-
spec.add_development_dependency '
|
25
|
+
spec.add_development_dependency 'codeclimate-test-reporter'
|
26
26
|
spec.add_development_dependency 'faker'
|
27
|
+
spec.add_development_dependency 'benchmark-ips'
|
27
28
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'clin'
|
3
|
+
|
4
|
+
# Reusable option definition
|
5
|
+
class SourceOptions < Clin::GeneralOption
|
6
|
+
option :source, 'Set the source'
|
7
|
+
end
|
8
|
+
|
9
|
+
# Command Using reusable option
|
10
|
+
class ReusableOptionCommand < Clin::Command
|
11
|
+
flag_option :verbose, 'Verbose'
|
12
|
+
option :echo, 'Echo'
|
13
|
+
|
14
|
+
general_option SourceOptions
|
15
|
+
|
16
|
+
def run
|
17
|
+
puts params
|
18
|
+
end
|
19
|
+
end
|
data/examples/simple.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
2
2
|
require 'clin'
|
3
|
-
require 'clin'
|
4
3
|
|
5
4
|
# Simple command Example
|
6
5
|
class SimpleCommand < Clin::Command
|
@@ -9,6 +8,8 @@ class SimpleCommand < Clin::Command
|
|
9
8
|
option :echo, 'Echo some text'
|
10
9
|
general_option Clin::HelpOptions
|
11
10
|
|
11
|
+
description 'Simple command that print stuff!'
|
12
|
+
|
12
13
|
def run
|
13
14
|
puts @params[:message]
|
14
15
|
puts @params[:echo]
|
@@ -16,5 +17,9 @@ class SimpleCommand < Clin::Command
|
|
16
17
|
end
|
17
18
|
|
18
19
|
# Run example:
|
19
|
-
# SimpleCommand.parse('display "My Message"
|
20
|
-
|
20
|
+
# SimpleCommand.parse('display "My Message" -e SOME').run
|
21
|
+
begin
|
22
|
+
SimpleCommand.parse('').run
|
23
|
+
rescue Clin::HelpError => e
|
24
|
+
puts e
|
25
|
+
end
|
data/examples/test.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
2
2
|
require 'clin'
|
3
|
-
|
3
|
+
require 'io/console'
|
4
4
|
|
5
5
|
shell = Clin::Shell.new
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
100.times.each do |i|
|
7
|
+
shell.say "# #{i}", indent: i
|
8
|
+
sleep(0.5)
|
9
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'clin'
|
3
|
+
values = ['Some value 1', 'Some value 2']
|
4
|
+
text = Clin::Text.new do |t|
|
5
|
+
t.line 'Usage:'
|
6
|
+
t.indent 2 do
|
7
|
+
t.line 'display Message'
|
8
|
+
t.line 'print Message'
|
9
|
+
|
10
|
+
t.indent 3 do
|
11
|
+
t.line '--echo'
|
12
|
+
t.line '--verbose'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
t.line 'Description: '
|
16
|
+
t.line 'This is a description', indent: 3
|
17
|
+
t.blank
|
18
|
+
t.line 'Examples:'
|
19
|
+
t.indent ' -' do
|
20
|
+
t.line 'display Message'
|
21
|
+
t.line 'print Message'
|
22
|
+
end
|
23
|
+
t.line 'Values: '
|
24
|
+
t.lines values, indent: '*** '
|
25
|
+
end
|
26
|
+
puts text
|
27
|
+
|
28
|
+
table = Clin::Text::Table.new(border: true) do |t|
|
29
|
+
t.align :right, :center, :left
|
30
|
+
# t.column_delimiter ' - ', ' # '
|
31
|
+
t.header %w(First Last Email)
|
32
|
+
|
33
|
+
t.row %w(Timothee Guerin timothee.guerin@outlook.com)
|
34
|
+
t.row %w(Some Guy Some.Guy@outlook.com)
|
35
|
+
|
36
|
+
t.separator
|
37
|
+
|
38
|
+
t.row %w(VeryLongFirstName Guy Some.Other@outlook.com)
|
39
|
+
end
|
40
|
+
puts table
|
data/lib/clin/argument.rb
CHANGED
@@ -2,12 +2,29 @@ require 'clin'
|
|
2
2
|
|
3
3
|
# Command line positional argument(not option)
|
4
4
|
class Clin::Argument
|
5
|
+
# Original name specified in the command
|
5
6
|
attr_accessor :original
|
7
|
+
|
8
|
+
# If the argument is optional
|
6
9
|
attr_accessor :optional
|
10
|
+
|
11
|
+
# If the argument accept multiple values
|
7
12
|
attr_accessor :multiple
|
13
|
+
|
14
|
+
# If the argument is a fixed argument(User value need to match the name)
|
8
15
|
attr_accessor :variable
|
16
|
+
|
17
|
+
# The name extracted without the brackets and arrows.
|
18
|
+
# This will be the key in the params when initializing a command
|
9
19
|
attr_accessor :name
|
10
20
|
|
21
|
+
# Create a new argument from string
|
22
|
+
# +argument+ will be used to deduce the name, if it's fixed, optional, accept multiple values
|
23
|
+
# If the argument is a simple string(e.g. install) then it will be a fixed argument
|
24
|
+
# For the argument to accept variable values it must be surrounded with <> (e.g. <command>)
|
25
|
+
# For the argument to be optional it must be surrounded with [] (e.g. [<value>])
|
26
|
+
# For the argument to accept multiple value it must be suffixed with ... (e.g. <commands>...)
|
27
|
+
# @param argument [String] argument Value
|
11
28
|
def initialize(argument)
|
12
29
|
@original = argument
|
13
30
|
@optional = false
|
@@ -63,7 +80,7 @@ class Clin::Argument
|
|
63
80
|
def ensure_fixed(args)
|
64
81
|
[*args].each do |arg|
|
65
82
|
next if arg == @name
|
66
|
-
fail Clin::
|
83
|
+
fail Clin::RequiredArgumentError, @name, arg
|
67
84
|
end
|
68
85
|
end
|
69
86
|
|
@@ -77,7 +94,7 @@ class Clin::Argument
|
|
77
94
|
if @variable
|
78
95
|
fail Clin::MissingArgumentError, @name
|
79
96
|
else
|
80
|
-
fail Clin::
|
97
|
+
fail Clin::RequiredArgumentError, @name
|
81
98
|
end
|
82
99
|
end
|
83
100
|
|
@@ -121,24 +121,6 @@ module Clin::CommandMixin::Core
|
|
121
121
|
@_default_priority + @_priority
|
122
122
|
end
|
123
123
|
|
124
|
-
# Build the Option Parser object
|
125
|
-
# Used to parse the option
|
126
|
-
# Useful for regenerating the help as well.
|
127
|
-
def option_parser(out = {})
|
128
|
-
OptionParser.new do |opts|
|
129
|
-
opts.banner = banner
|
130
|
-
opts.separator ''
|
131
|
-
opts.separator 'Options:'
|
132
|
-
register_options(opts, out)
|
133
|
-
dispatch_doc(opts)
|
134
|
-
unless @description.blank?
|
135
|
-
opts.separator "\nDescription:"
|
136
|
-
opts.separator @description
|
137
|
-
end
|
138
|
-
opts.separator ''
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
124
|
def default_commands
|
143
125
|
subcommands.sort_by(&:priority).reverse
|
144
126
|
end
|
@@ -148,5 +130,18 @@ module Clin::CommandMixin::Core
|
|
148
130
|
def subcommands
|
149
131
|
subclasses.reject(&:abstract?)
|
150
132
|
end
|
133
|
+
|
134
|
+
def help
|
135
|
+
Clin::Text.new do |t|
|
136
|
+
t.line banner
|
137
|
+
t.blank
|
138
|
+
t.line 'Options:'
|
139
|
+
t.text option_help, indent: 2
|
140
|
+
unless description.blank?
|
141
|
+
t.line 'Description:'
|
142
|
+
t.line description, indent: 2
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
151
146
|
end
|
152
147
|
end
|
@@ -6,38 +6,22 @@ module Clin::CommandMixin::Options
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
included do
|
9
|
-
self.
|
9
|
+
self.specific_options = []
|
10
10
|
self.general_options = {}
|
11
11
|
# Trigger when a class inherit this class
|
12
12
|
# It will clone attributes that need inheritance
|
13
13
|
# @param subclass [Clin::Command]
|
14
14
|
def self.inherited(subclass)
|
15
|
-
subclass.
|
15
|
+
subclass.specific_options = @specific_options.clone
|
16
16
|
subclass.general_options = @general_options.clone
|
17
17
|
super
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
module ClassMethods # :nodoc:
|
22
|
-
attr_accessor :
|
22
|
+
attr_accessor :specific_options
|
23
23
|
attr_accessor :general_options
|
24
24
|
|
25
|
-
# Add an option
|
26
|
-
# @param args list of arguments.
|
27
|
-
# * First argument must be the name if no block is given.
|
28
|
-
# It will set automatically read the value into the hash with +name+ as key
|
29
|
-
# * The remaining arguments are OptionsParser#on arguments
|
30
|
-
# ```
|
31
|
-
# option :require, '-r', '--require [LIBRARY]', 'Require the library'
|
32
|
-
# option '-h', '--helper', 'Show the help' do
|
33
|
-
# puts opts
|
34
|
-
# exit
|
35
|
-
# end
|
36
|
-
# ```
|
37
|
-
def opt_option(*args, &block)
|
38
|
-
add_option Clin::Option.new(*args, &block)
|
39
|
-
end
|
40
|
-
|
41
25
|
# Add an option.
|
42
26
|
# Helper method that just create a new Clin::Option with the argument then call add_option
|
43
27
|
# ```
|
@@ -85,7 +69,7 @@ module Clin::CommandMixin::Options
|
|
85
69
|
# @param option [Clin::Option] option to add.
|
86
70
|
def add_option(option)
|
87
71
|
# Need to use += instead of << otherwise the parent class will also be changed
|
88
|
-
@
|
72
|
+
@specific_options << option
|
89
73
|
end
|
90
74
|
|
91
75
|
# Add a general option
|
@@ -105,16 +89,17 @@ module Clin::CommandMixin::Options
|
|
105
89
|
|
106
90
|
# To be called inside OptionParser block
|
107
91
|
# Extract the option in the command line using the OptionParser and map it to the out map.
|
108
|
-
# @
|
109
|
-
|
110
|
-
|
111
|
-
@
|
112
|
-
option.
|
92
|
+
# @return [Hash] Where the options shall be extracted
|
93
|
+
def option_defaults
|
94
|
+
out = {}
|
95
|
+
@specific_options.each do |option|
|
96
|
+
option.load_default(out)
|
113
97
|
end
|
114
98
|
|
115
99
|
@general_options.each do |_cls, option|
|
116
|
-
option.class.
|
100
|
+
out.merge! option.class.option_defaults
|
117
101
|
end
|
102
|
+
out
|
118
103
|
end
|
119
104
|
|
120
105
|
# Call #execute on each of the general options.
|
@@ -130,5 +115,31 @@ module Clin::CommandMixin::Options
|
|
130
115
|
gopts.execute(options)
|
131
116
|
end
|
132
117
|
end
|
118
|
+
|
119
|
+
# Return all options
|
120
|
+
def options
|
121
|
+
specific_options + general_options.keys.map(&:options).flatten
|
122
|
+
end
|
123
|
+
|
124
|
+
def find_option(value)
|
125
|
+
find_option_by(name: value)
|
126
|
+
end
|
127
|
+
|
128
|
+
def find_option_by(hash)
|
129
|
+
key, value = hash.first
|
130
|
+
options.find { |x| x.send(key) == value }
|
131
|
+
end
|
132
|
+
|
133
|
+
def option_help
|
134
|
+
Clin::Text.new do |t|
|
135
|
+
options.each do |option|
|
136
|
+
t.line option.banner
|
137
|
+
end
|
138
|
+
|
139
|
+
general_options.each do |cls, _|
|
140
|
+
t.text cls.option_help
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
133
144
|
end
|
134
145
|
end
|
data/lib/clin/command_parser.rb
CHANGED
@@ -2,6 +2,9 @@ require 'clin'
|
|
2
2
|
|
3
3
|
# Command parser
|
4
4
|
class Clin::CommandParser
|
5
|
+
# List of errors that have occurred during the parsing
|
6
|
+
attr_reader :errors
|
7
|
+
|
5
8
|
# Create the command parser
|
6
9
|
# @param command_cls [Class<Clin::Command>] Command that must be matched
|
7
10
|
# @param argv [Array<String>] List of CL arguments
|
@@ -11,75 +14,59 @@ class Clin::CommandParser
|
|
11
14
|
argv = Shellwords.split(argv) if argv.is_a? String
|
12
15
|
@argv = argv
|
13
16
|
@fallback_help = fallback_help
|
17
|
+
@options = {}
|
18
|
+
@arguments = {}
|
19
|
+
@errors = []
|
20
|
+
@skipped_options = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def params
|
24
|
+
out = @options.merge(@arguments)
|
25
|
+
out[:skipped_options] = @skipped_options if @command.skip_options?
|
26
|
+
out
|
27
|
+
end
|
28
|
+
|
29
|
+
def init_defaults
|
30
|
+
@options = @command.option_defaults
|
14
31
|
end
|
15
32
|
|
16
33
|
# Parse the command line.
|
17
34
|
def parse
|
18
35
|
argv = @argv.clone
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
options.merge! parse_options(argv)
|
23
|
-
rescue Clin::OptionError => e
|
24
|
-
error = e
|
25
|
-
end
|
26
|
-
begin
|
27
|
-
options.merge! parse_arguments(argv)
|
28
|
-
rescue Clin::ArgumentError => e
|
29
|
-
raise e unless @fallback_help
|
30
|
-
error = e
|
31
|
-
end
|
36
|
+
init_defaults
|
37
|
+
parse_options(argv)
|
38
|
+
parse_arguments(argv)
|
32
39
|
|
33
|
-
return redispatch(
|
34
|
-
obj = @command.new(
|
35
|
-
|
40
|
+
return redispatch(params) if @command.redispatch?
|
41
|
+
obj = @command.new(params)
|
42
|
+
validate!
|
36
43
|
obj
|
37
44
|
end
|
38
45
|
|
39
|
-
# Parse the options in the argv.
|
40
|
-
# @return [Array] the list of argv that are not options(positional arguments)
|
41
46
|
def parse_options(argv)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
rescue OptionParser::InvalidOption => e
|
49
|
-
raise Clin::OptionError, e.to_s
|
50
|
-
end
|
51
|
-
out[:skipped_options] = skipped if @command.skip_options?
|
52
|
-
out
|
47
|
+
parser = Clin::OptionParser.new(@command, argv)
|
48
|
+
@options.merge! parser.parse
|
49
|
+
@skipped_options = parser.skipped_options
|
50
|
+
@errors += parser.errors
|
51
|
+
argv.replace(parser.arguments)
|
52
|
+
@options
|
53
53
|
end
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
return [] unless @command.skip_options?
|
58
|
-
argv = @argv.dup
|
59
|
-
skipped = []
|
60
|
-
parser = @command.option_parser
|
61
|
-
loop do
|
62
|
-
begin
|
63
|
-
parser.parse!(argv)
|
64
|
-
break
|
65
|
-
rescue OptionParser::InvalidOption => e
|
66
|
-
skipped << e.to_s.sub(/invalid option:\s+/, '')
|
67
|
-
next if argv.empty? || argv.first.start_with?('-')
|
68
|
-
skipped << argv.shift
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
skipped
|
55
|
+
def add_error(err)
|
56
|
+
@errors << err
|
73
57
|
end
|
74
58
|
|
75
59
|
# Parse the argument. The options must have been strip out first.
|
76
60
|
def parse_arguments(argv)
|
77
|
-
out = {}
|
78
61
|
@command.args.each do |arg|
|
79
62
|
value, argv = arg.parse(argv)
|
80
|
-
|
63
|
+
|
64
|
+
@arguments[arg.name.to_sym] = value
|
81
65
|
end
|
82
|
-
|
66
|
+
@arguments.delete_if { |_, v| v.nil? }
|
67
|
+
@arguments
|
68
|
+
rescue Clin::ArgumentError => e
|
69
|
+
add_error e
|
83
70
|
end
|
84
71
|
|
85
72
|
# Method called after the argument have been parsed and before creating the command
|
@@ -91,7 +78,7 @@ class Clin::CommandParser
|
|
91
78
|
begin
|
92
79
|
dispatcher.parse(redispatch_arguments(params))
|
93
80
|
rescue Clin::HelpError
|
94
|
-
raise Clin::HelpError, @command
|
81
|
+
raise Clin::HelpError, @command
|
95
82
|
end
|
96
83
|
end
|
97
84
|
|
@@ -105,11 +92,13 @@ class Clin::CommandParser
|
|
105
92
|
args
|
106
93
|
end
|
107
94
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
95
|
+
def valid?
|
96
|
+
@errors.empty?
|
97
|
+
end
|
98
|
+
|
99
|
+
def validate!
|
100
|
+
return if valid?
|
101
|
+
fail Clin::HelpError, @command if @fallback_help
|
102
|
+
fail @errors.sort_by { |e| e.class.severity }.last
|
114
103
|
end
|
115
104
|
end
|