clin 0.3.0 → 0.4.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 +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
|
-
[](http://rubygems.org/gems/clin)
|
|
3
|
+
[](https://travis-ci.org/timcolonel/clin)
|
|
4
|
+
[](https://codeclimate.com/github/timcolonel/clin/coverage)
|
|
5
|
+
[](https://codeclimate.com/github/timcolonel/clin)
|
|
6
|
+
[](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
|