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
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'clin'
|
2
|
+
require 'clin/text'
|
3
|
+
|
4
|
+
# Table text builder
|
5
|
+
class Clin::Text
|
6
|
+
# Helper class to display tables
|
7
|
+
class Table
|
8
|
+
# Header row(Nil for no header)
|
9
|
+
attr_writer :header
|
10
|
+
|
11
|
+
# List of rows in the table
|
12
|
+
attr_accessor :rows
|
13
|
+
|
14
|
+
# Column delimiters
|
15
|
+
# Can be either:
|
16
|
+
# * 1 string: All the column delimiter will use this.
|
17
|
+
# * list of string: The delimiters will be this list of string.
|
18
|
+
# The size must be the column size - 1
|
19
|
+
attr_accessor :column_delimiters
|
20
|
+
|
21
|
+
# Global Row delimiter
|
22
|
+
# All the separator will default to this value
|
23
|
+
attr_accessor :row_delim
|
24
|
+
|
25
|
+
# Boolean if yes or no outside border shall be included
|
26
|
+
attr_accessor :border
|
27
|
+
|
28
|
+
# Column alignment. Can either be a global value(i.e. [Symbol])
|
29
|
+
# or a column specific with an array of [Symbol]
|
30
|
+
# * :left
|
31
|
+
# * :center
|
32
|
+
# * :right
|
33
|
+
attr_accessor :alignment
|
34
|
+
|
35
|
+
# If blank cell should be separated with the column separator.
|
36
|
+
attr_accessor :separate_blank
|
37
|
+
|
38
|
+
attr_accessor :column_length
|
39
|
+
|
40
|
+
# Create a new table
|
41
|
+
# @param col_delim [String] Set the column delimiter @see #column_delimiters
|
42
|
+
# @param row_delim [String] Set the row delimiter @see #row_delim
|
43
|
+
# @param border [String] Set border @see #border
|
44
|
+
# @param align [String] Set alignment @see #align
|
45
|
+
# @param separate_blank [Boolean] If the column separator should be added near blank cells
|
46
|
+
# @param block [Proc] Block with self passed as param.
|
47
|
+
def initialize(col_delim: ' | ', row_delim: '-',
|
48
|
+
border: true, align: :left, separate_blank: true, &block)
|
49
|
+
@rows = []
|
50
|
+
@header = nil
|
51
|
+
@column_length = {}
|
52
|
+
@column_delimiters = col_delim
|
53
|
+
@row_delim = row_delim
|
54
|
+
@border = border
|
55
|
+
@separate_blank = separate_blank
|
56
|
+
@alignment = align
|
57
|
+
block.call(self) if block_given?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Add a new row
|
61
|
+
# @param cells [Array<String>] Cells.
|
62
|
+
def row(*cells)
|
63
|
+
@rows << TableRow.new(self, cells)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set or get the header row.
|
67
|
+
# @param cells [Array<String>] Cells.
|
68
|
+
def header(*cells)
|
69
|
+
@header = TableRow.new(self, cells) if cells.any?
|
70
|
+
@header
|
71
|
+
end
|
72
|
+
|
73
|
+
# Add a separator row
|
74
|
+
# @param char [Char] Separator char. If nil will use the default row delimiter
|
75
|
+
def separator(char = nil)
|
76
|
+
@rows << TableSeparatorRow.new(self, char)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Set or get the the column alignment
|
80
|
+
# @param args [Array|Symbol] List of alignment.
|
81
|
+
# ```
|
82
|
+
# t.align :center # => All column will be centered
|
83
|
+
# t.align :left, :center, :right #=> First column will be align left, second center, ...
|
84
|
+
# t.align []:left, :center, :right] #=> Equivalent
|
85
|
+
# ```
|
86
|
+
# @see #alignment
|
87
|
+
def align(*args)
|
88
|
+
@alignment = sym_or_array(*args) if args.any?
|
89
|
+
@alignment
|
90
|
+
end
|
91
|
+
|
92
|
+
# Set a specific column delimiter for all the column
|
93
|
+
def column_delimiter(*args)
|
94
|
+
@column_delimiters = sym_or_array(*args)
|
95
|
+
end
|
96
|
+
|
97
|
+
def border?
|
98
|
+
@border
|
99
|
+
end
|
100
|
+
|
101
|
+
def separate_blank?
|
102
|
+
separate_blank
|
103
|
+
end
|
104
|
+
|
105
|
+
def vertical_border
|
106
|
+
'|'
|
107
|
+
end
|
108
|
+
|
109
|
+
# Build the text object for this table.
|
110
|
+
def to_text
|
111
|
+
t = Clin::Text.new
|
112
|
+
unless @header.nil?
|
113
|
+
t.line @header.to_s
|
114
|
+
t.line TableSeparatorRow.new(self).to_s
|
115
|
+
end
|
116
|
+
t.lines @rows.map(&:to_s)
|
117
|
+
add_border(t) if border?
|
118
|
+
t
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_s
|
122
|
+
to_text.to_s
|
123
|
+
end
|
124
|
+
|
125
|
+
def update_column_length(index, cell_length)
|
126
|
+
@column_length[index] ||= 0
|
127
|
+
@column_length[index] = [cell_length, @column_length[index]].max
|
128
|
+
end
|
129
|
+
|
130
|
+
def delimiter_at(index)
|
131
|
+
return '' if index >= @column_length.size - 1
|
132
|
+
if @column_delimiters.is_a? Array
|
133
|
+
@column_delimiters[index]
|
134
|
+
else
|
135
|
+
@column_delimiters
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
protected def sym_or_array(*args)
|
140
|
+
return args if args.empty?
|
141
|
+
args.flatten!
|
142
|
+
args.size == 1 ? args.first : args
|
143
|
+
end
|
144
|
+
|
145
|
+
# Add the top and bottom border to the lines
|
146
|
+
protected def add_border(text)
|
147
|
+
line = TableSeparatorRow.new(self, col_delimiter: false).to_s
|
148
|
+
text.prefix(line)
|
149
|
+
text.line line
|
150
|
+
text
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Table Row container class
|
155
|
+
class TableRow
|
156
|
+
include Enumerable
|
157
|
+
|
158
|
+
# List of cells in the row.
|
159
|
+
attr_accessor :cells
|
160
|
+
|
161
|
+
def initialize(table, cells)
|
162
|
+
@table = table
|
163
|
+
@cells = cells.flatten.each_with_index.map { |x, i| TableCell.new(table, i, x) }
|
164
|
+
end
|
165
|
+
|
166
|
+
def each(&block)
|
167
|
+
@table.column_length.size.times.each do |i|
|
168
|
+
block.call(@cells[i] || '')
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def border(text, separator = '')
|
173
|
+
return text unless @table.border?
|
174
|
+
@table.vertical_border + separator + text + separator + @table.vertical_border
|
175
|
+
end
|
176
|
+
|
177
|
+
# Get the delimiter to insert after the cell at +index+
|
178
|
+
# @param index [Integer] Cell index.
|
179
|
+
# Will return the corresponding delimiter and for the last cell will return ''
|
180
|
+
def delimiter_at(index)
|
181
|
+
delim = @table.delimiter_at(index)
|
182
|
+
if !@table.separate_blank? && (@cells[index].blank? || @cells[index + 1].blank?)
|
183
|
+
delim = ' ' * delim.size
|
184
|
+
end
|
185
|
+
delim
|
186
|
+
end
|
187
|
+
|
188
|
+
def to_s
|
189
|
+
out = ''
|
190
|
+
each_with_index do |cell, i|
|
191
|
+
out << cell.to_s
|
192
|
+
out << delimiter_at(i)
|
193
|
+
end
|
194
|
+
border(out, ' ')
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Table row that is filled with the same character
|
199
|
+
class TableSeparatorRow < TableRow
|
200
|
+
# Char used for separation.
|
201
|
+
attr_accessor :char
|
202
|
+
|
203
|
+
def initialize(table, char = nil, col_delimiter: true)
|
204
|
+
super(table, [])
|
205
|
+
@char = char || @table.row_delim
|
206
|
+
@include_column_delimiter = col_delimiter
|
207
|
+
end
|
208
|
+
|
209
|
+
def delimiter_at(index)
|
210
|
+
col_delim = super(index)
|
211
|
+
@include_column_delimiter ? col_delim : (@char * col_delim.size)
|
212
|
+
end
|
213
|
+
|
214
|
+
def to_s
|
215
|
+
out = ''
|
216
|
+
each_with_index do |_, i|
|
217
|
+
out << @char * @table.column_length[i]
|
218
|
+
out << delimiter_at(i)
|
219
|
+
end
|
220
|
+
border(out, @char)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Table cell container class
|
225
|
+
class TableCell
|
226
|
+
def initialize(table, index, value)
|
227
|
+
@table = table
|
228
|
+
@index = index
|
229
|
+
@value = value.to_s
|
230
|
+
@table.update_column_length(index, @value.length)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Get the length of the cell
|
234
|
+
# @return [Integer]
|
235
|
+
def length
|
236
|
+
@table.column_length[@index]
|
237
|
+
end
|
238
|
+
|
239
|
+
def blank?
|
240
|
+
@value.blank?
|
241
|
+
end
|
242
|
+
|
243
|
+
# Get the alignment for this cell
|
244
|
+
# @return [Symbol]
|
245
|
+
# Can be:
|
246
|
+
# * :left
|
247
|
+
# * :center
|
248
|
+
# * :right
|
249
|
+
def align
|
250
|
+
return @table.align if @table.align.is_a? Symbol
|
251
|
+
fail Clin::Error, 'Align must either be a symbol or an Array!' unless @table.align.is_a? Array
|
252
|
+
@table.align[@index]
|
253
|
+
end
|
254
|
+
|
255
|
+
# Convert the cell to_s using the length of the column and the column alignment
|
256
|
+
# @return [String]
|
257
|
+
def to_s
|
258
|
+
case align
|
259
|
+
when :left
|
260
|
+
@value.to_s.ljust(length)
|
261
|
+
when :right
|
262
|
+
@value.to_s.rjust(length)
|
263
|
+
when :center
|
264
|
+
@value.to_s.center(length)
|
265
|
+
else
|
266
|
+
fail Clin::Error, "Invalid align #{align}"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
data/lib/clin/text.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'clin'
|
2
|
+
|
3
|
+
# Text builder class.
|
4
|
+
# Text is designed for building dynamic text that need multiple lines
|
5
|
+
# Help message and documentation are easily built with this.
|
6
|
+
# ```
|
7
|
+
# text = Clin::Text.new do
|
8
|
+
# line 'Usage:'
|
9
|
+
# indent 2 do
|
10
|
+
# line 'display Message'
|
11
|
+
# line 'print Message'
|
12
|
+
# end
|
13
|
+
# line 'Description: '
|
14
|
+
# line 'This is a description', indent: 3
|
15
|
+
# blank
|
16
|
+
# line 'Examples:'
|
17
|
+
# indent ' -' do
|
18
|
+
# line 'display Message'
|
19
|
+
# line 'print Message'
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
# puts text # =>
|
23
|
+
# $ Usage:
|
24
|
+
# display Message
|
25
|
+
# print Message
|
26
|
+
# Description:
|
27
|
+
# This is a description
|
28
|
+
#
|
29
|
+
# Examples:
|
30
|
+
# -display Message
|
31
|
+
# -print Message
|
32
|
+
#
|
33
|
+
# ```
|
34
|
+
class Clin::Text
|
35
|
+
attr_accessor :_lines
|
36
|
+
|
37
|
+
# All the lines added to this text will be indented with this.
|
38
|
+
attr_accessor :global_indent
|
39
|
+
|
40
|
+
# List of block that listen for line added to the next builder.
|
41
|
+
attr_accessor :listeners
|
42
|
+
|
43
|
+
def initialize(indent: '', &block)
|
44
|
+
@_lines = []
|
45
|
+
@inital_indent = compute_indent(indent)
|
46
|
+
@global_indent = @inital_indent
|
47
|
+
@listeners = []
|
48
|
+
block.call(self) if block_given?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Add a new line
|
52
|
+
# @param text [String] line to add
|
53
|
+
# @param indent [String|Integer] Indent the line with x spaces or the given text
|
54
|
+
# ```
|
55
|
+
# line('Some line') #=> 'Some line'
|
56
|
+
# line('Some line', indent: 3) #=> ' Some line'
|
57
|
+
# line('Some line', indent: '- ') #=> '- Some line'
|
58
|
+
# ```
|
59
|
+
def line(text, indent: '')
|
60
|
+
l = process_line(text, indent: indent)
|
61
|
+
@_lines << l
|
62
|
+
broadcast(l)
|
63
|
+
l
|
64
|
+
end
|
65
|
+
|
66
|
+
# Add a line at the beginning of the text
|
67
|
+
# @param text [String] line to add
|
68
|
+
# @param indent [String|Integer] Indent the line with x spaces or the given text
|
69
|
+
def prefix(text, indent: '')
|
70
|
+
l = process_line(text, indent: indent)
|
71
|
+
@_lines.unshift l
|
72
|
+
l
|
73
|
+
end
|
74
|
+
|
75
|
+
# Add a blank line n times
|
76
|
+
# @param times [Integer] Number of times to add the line.
|
77
|
+
def blank(times = 1)
|
78
|
+
@_lines += [''] * times
|
79
|
+
times.times.each { broadcast('') }
|
80
|
+
end
|
81
|
+
|
82
|
+
# Add a list of string as lines or get the existing lines.
|
83
|
+
# @param array [Array<String>] List of lines to add.
|
84
|
+
# @param indent [String|Integer] Indent each line. @see #line
|
85
|
+
# @return list of lines
|
86
|
+
def lines(array = [], indent: '')
|
87
|
+
array.each do |l|
|
88
|
+
line(l, indent: indent)
|
89
|
+
end
|
90
|
+
@_lines
|
91
|
+
end
|
92
|
+
|
93
|
+
# Add the content of another Clin::Text object
|
94
|
+
# @param text [Clin::Text]
|
95
|
+
# @param indent [String|Integer] Indent the content
|
96
|
+
def text(text, indent: '')
|
97
|
+
lines(text._lines, indent: indent)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Indent all the content inside this block.
|
101
|
+
# @param indent [String|Integer] indent value
|
102
|
+
# @param block [Proc] Callback.
|
103
|
+
def indent(indent, &block)
|
104
|
+
previous_indent = @global_indent
|
105
|
+
@global_indent += compute_indent(indent)
|
106
|
+
block.call(self)
|
107
|
+
@global_indent = previous_indent
|
108
|
+
end
|
109
|
+
|
110
|
+
def table(indent: '', **options, &block)
|
111
|
+
text Clin::Text::Table.new(**options, &block).to_text, indent: indent
|
112
|
+
end
|
113
|
+
|
114
|
+
# Join the lines together to form the output
|
115
|
+
# @return [String]
|
116
|
+
def to_s
|
117
|
+
"#{@_lines.join("\n")}\n"
|
118
|
+
end
|
119
|
+
|
120
|
+
def process_line(text, indent: '')
|
121
|
+
indent = compute_indent(indent)
|
122
|
+
"#{global_indent}#{indent}#{text}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def on(&block)
|
126
|
+
@listeners << block
|
127
|
+
end
|
128
|
+
|
129
|
+
# Call the the listener with the newly added line
|
130
|
+
def broadcast(line)
|
131
|
+
@listeners.each do |block|
|
132
|
+
block.call(line)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def ==(other)
|
137
|
+
return to_s == other if other.is_a? String
|
138
|
+
return false unless other.is_a? Clin::Text
|
139
|
+
@_lines == other._lines && @global_indent == other.global_indent
|
140
|
+
end
|
141
|
+
|
142
|
+
alias_method :eql?, :==
|
143
|
+
|
144
|
+
# Process the indent.
|
145
|
+
# If the indent is an integer it will return +indent+ spaces
|
146
|
+
# @return [String.]
|
147
|
+
protected def compute_indent(indent)
|
148
|
+
indent.is_a?(Integer) ? ' ' * indent : indent
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
require 'clin/text/table'
|
data/lib/clin/version.rb
CHANGED
data/lib/clin.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'active_support'
|
2
2
|
require 'active_support/core_ext'
|
3
|
-
require 'optparse'
|
4
3
|
require 'readline'
|
5
4
|
require 'clin/version'
|
6
5
|
|
@@ -13,6 +12,9 @@ module Clin
|
|
13
12
|
# Set the command when comparing 2 files(Used in the shell)
|
14
13
|
attr_writer :diff_cmd
|
15
14
|
|
15
|
+
# If the line reader should use Readline(For autocomplete and history)
|
16
|
+
attr_writer :use_readline
|
17
|
+
|
16
18
|
def default_exe_name
|
17
19
|
'command'
|
18
20
|
end
|
@@ -26,11 +28,16 @@ module Clin
|
|
26
28
|
def diff_cmd
|
27
29
|
@diff_cmd ||= 'diff -u'
|
28
30
|
end
|
31
|
+
|
32
|
+
def use_readline?
|
33
|
+
@use_readline ||= !ENV['CLIN_NO_READLINE']
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
|
32
38
|
require 'clin/command_mixin'
|
33
39
|
require 'clin/command'
|
40
|
+
require 'clin/option_parser'
|
34
41
|
require 'clin/command_parser'
|
35
42
|
require 'clin/general_option'
|
36
43
|
require 'clin/command_dispatcher'
|
@@ -38,4 +45,6 @@ require 'clin/common/help_options'
|
|
38
45
|
require 'clin/errors'
|
39
46
|
require 'clin/option'
|
40
47
|
require 'clin/option_list'
|
48
|
+
require 'clin/text'
|
41
49
|
require 'clin/shell'
|
50
|
+
require 'clin/line_reader'
|
@@ -57,7 +57,7 @@ RSpec.describe Clin::CommandDispatcher do
|
|
57
57
|
|
58
58
|
context 'when first command return FixedArgumentError' do
|
59
59
|
before do
|
60
|
-
allow(cmd1).to receive(:parse) { fail Clin::
|
60
|
+
allow(cmd1).to receive(:parse) { fail Clin::RequiredArgumentError, :some }
|
61
61
|
subject.parse(args)
|
62
62
|
end
|
63
63
|
it { expect(cmd1).to have_received(:parse).with(args, fallback_help: false) }
|
@@ -43,25 +43,48 @@ RSpec.describe Clin::CommandMixin::Options do
|
|
43
43
|
it { expect(subject.general_options.values.first).to eq(true) }
|
44
44
|
end
|
45
45
|
|
46
|
-
describe '#
|
46
|
+
describe '#options' do
|
47
47
|
subject { new_subject }
|
48
|
-
let(:
|
49
|
-
let(:
|
50
|
-
let(:
|
51
|
-
let(:
|
52
|
-
let(:
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
48
|
+
let(:option1) { double(:option1) }
|
49
|
+
let(:option2) { double(:option2) }
|
50
|
+
let(:option3) { double(:option3) }
|
51
|
+
let(:option4) { double(:option4) }
|
52
|
+
let(:general_option1) do
|
53
|
+
opt = Class.new(Clin::GeneralOption)
|
54
|
+
opt.add_option option3
|
55
|
+
opt.add_option option4
|
56
|
+
opt
|
57
|
+
end
|
58
|
+
|
59
|
+
let(:general_option2) do
|
60
|
+
opt = Class.new(Clin::GeneralOption)
|
61
|
+
opt.general_option general_option1
|
62
|
+
opt
|
63
|
+
end
|
58
64
|
|
59
|
-
|
65
|
+
it 'get every options' do
|
66
|
+
subject.add_option option1
|
67
|
+
subject.add_option option2
|
68
|
+
expect(subject.options).to eq([option1, option2])
|
60
69
|
end
|
61
70
|
|
62
|
-
it
|
63
|
-
|
64
|
-
|
71
|
+
it 'get every general option options' do
|
72
|
+
subject.general_option general_option1
|
73
|
+
expect(subject.options).to eq([option3, option4])
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'get every options and general option options' do
|
77
|
+
subject.add_option option1
|
78
|
+
subject.add_option option2
|
79
|
+
subject.general_option general_option1
|
80
|
+
expect(subject.options).to eq([option1, option2, option3, option4])
|
81
|
+
end
|
65
82
|
|
83
|
+
it 'get nested general options' do
|
84
|
+
subject.add_option option1
|
85
|
+
subject.add_option option2
|
86
|
+
subject.general_option general_option2
|
87
|
+
expect(subject.options).to eq([option1, option2, option3, option4])
|
88
|
+
end
|
66
89
|
end
|
67
90
|
end
|
@@ -12,10 +12,15 @@ RSpec.describe Clin::CommandParser do
|
|
12
12
|
subject { Clin::CommandParser.new(@command, []) }
|
13
13
|
|
14
14
|
it 'raise argument when option value is missing' do
|
15
|
-
|
15
|
+
subject.parse_options(%w(--name))
|
16
|
+
expect(subject.valid?).to be false
|
17
|
+
expect(subject.errors.first).to be_a(Clin::MissingOptionArgumentError)
|
16
18
|
end
|
17
|
-
|
18
|
-
|
19
|
+
|
20
|
+
it 'add error when unknown option' do
|
21
|
+
subject.parse_options(%w(--other))
|
22
|
+
expect(subject.errors.size).to be 1
|
23
|
+
expect(subject.errors.first).to be_a(Clin::OptionError)
|
19
24
|
end
|
20
25
|
|
21
26
|
it { expect(subject.parse_options(%w(--name MyName))).to eq(name: 'MyName') }
|
@@ -25,7 +30,7 @@ RSpec.describe Clin::CommandParser do
|
|
25
30
|
|
26
31
|
it { expect(subject.parse_options(%w(-v))).to eq(verbose: true) }
|
27
32
|
|
28
|
-
it { expect(subject.parse_options(%w(--echo))).to eq(echo:
|
33
|
+
it { expect(subject.parse_options(%w(--echo))).to eq(echo: true) }
|
29
34
|
it { expect(subject.parse_options(%w(-e EchoThis))).to eq(echo: 'EchoThis') }
|
30
35
|
end
|
31
36
|
|
@@ -39,14 +44,21 @@ RSpec.describe Clin::CommandParser do
|
|
39
44
|
subject { Clin::CommandParser.new(@command, []) }
|
40
45
|
|
41
46
|
it 'raise argument when fixed in different' do
|
42
|
-
|
47
|
+
subject.parse_arguments(%w(other val opt))
|
48
|
+
expect(subject.valid?).to be false
|
49
|
+
expect(subject.errors.first).to be_a Clin::CommandLineError
|
43
50
|
end
|
51
|
+
|
44
52
|
it 'raise error when too few arguments' do
|
45
|
-
|
53
|
+
subject.parse_arguments(%w(fix))
|
54
|
+
expect(subject.valid?).to be false
|
55
|
+
expect(subject.errors.first).to be_a Clin::CommandLineError
|
46
56
|
end
|
57
|
+
|
47
58
|
it 'raise error when too much argument' do
|
48
|
-
|
49
|
-
|
59
|
+
subject.parse_arguments(%w(other val opt more))
|
60
|
+
expect(subject.valid?).to be false
|
61
|
+
expect(subject.errors.first).to be_a Clin::CommandLineError
|
50
62
|
end
|
51
63
|
|
52
64
|
it 'map arguments' do
|
@@ -58,43 +70,6 @@ RSpec.describe Clin::CommandParser do
|
|
58
70
|
end
|
59
71
|
end
|
60
72
|
|
61
|
-
describe '#skipped_options' do
|
62
|
-
def skipped_options(argv)
|
63
|
-
Clin::CommandParser.new(@command, argv).skipped_options
|
64
|
-
end
|
65
|
-
|
66
|
-
before :all do
|
67
|
-
@command = Class.new(Clin::Command)
|
68
|
-
@command.skip_options true
|
69
|
-
end
|
70
|
-
|
71
|
-
context 'when all options should be skipped' do
|
72
|
-
it { expect(skipped_options(%w(pos arg))).to eq([]) }
|
73
|
-
|
74
|
-
it { expect(skipped_options(%w(pos arg --ignore -t))).to eq(%w(--ignore -t)) }
|
75
|
-
|
76
|
-
it { expect(skipped_options(%w(pos arg --ignore value -t))).to eq(%w(--ignore value -t)) }
|
77
|
-
|
78
|
-
end
|
79
|
-
context 'when option are define they should not be skipped' do
|
80
|
-
before :all do
|
81
|
-
@command.flag_option :verbose, 'Verbose'
|
82
|
-
end
|
83
|
-
|
84
|
-
it { expect(skipped_options(%w(pos arg --ignore value -t -v))).to eq(%w(--ignore value -t)) }
|
85
|
-
|
86
|
-
it do
|
87
|
-
expect(skipped_options(%w(pos arg --verbose --ignore value -t)))
|
88
|
-
.to eq(%w(--ignore value -t))
|
89
|
-
end
|
90
|
-
|
91
|
-
it do
|
92
|
-
expect(skipped_options(%w(pos arg --ignore value --verbose -t)))
|
93
|
-
.to eq(%w(--ignore value -t))
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
73
|
describe '.handle_dispatch' do
|
99
74
|
let(:args) { [Faker::Lorem.word, Faker::Lorem.word] }
|
100
75
|
before :all do
|
@@ -132,12 +107,14 @@ RSpec.describe Clin::CommandParser do
|
|
132
107
|
context 'when using commands' do
|
133
108
|
let(:cmd1) { double(:command_mixin) }
|
134
109
|
let(:cmd2) { double(:command_mixin) }
|
110
|
+
let(:dispatcher) { double(:dispatcher, parse: true) }
|
135
111
|
before do
|
136
112
|
@command.dispatch :args, commands: [cmd1, cmd2]
|
137
|
-
allow_any_instance_of(Clin::CommandDispatcher).to receive(:initialize)
|
138
113
|
end
|
114
|
+
|
139
115
|
it 'call the command dispatcher with the right arguments' do
|
140
|
-
|
116
|
+
expect(Clin::CommandDispatcher)
|
117
|
+
.to receive(:new).once.with([cmd1, cmd2]).and_return(dispatcher)
|
141
118
|
subject.redispatch(remote: 'remote', args: args)
|
142
119
|
end
|
143
120
|
end
|
@@ -146,19 +123,18 @@ RSpec.describe Clin::CommandParser do
|
|
146
123
|
let(:new_message) { Faker::Lorem.sentence }
|
147
124
|
before do
|
148
125
|
@command.dispatch :args
|
149
|
-
allow_any_instance_of(Clin::CommandDispatcher).to receive(:
|
126
|
+
allow_any_instance_of(Clin::CommandDispatcher).to receive(:new)
|
150
127
|
allow_any_instance_of(Clin::CommandDispatcher).to receive(:parse) do
|
151
128
|
fail Clin::HelpError, 'Dispatcher error'
|
152
129
|
end
|
153
|
-
allow(@command).to receive(:
|
130
|
+
allow(@command).to receive(:help).and_return(new_message)
|
154
131
|
end
|
155
132
|
it do
|
156
133
|
expect { subject.redispatch(remote: 'remote', args: args) }
|
157
134
|
.to raise_error(Clin::HelpError)
|
158
135
|
end
|
159
136
|
it do
|
160
|
-
expect { subject.redispatch(remote: 'remote', args: args) }
|
161
|
-
.to raise_error(new_message)
|
137
|
+
expect { subject.redispatch(remote: 'remote', args: args) }.to raise_error(new_message)
|
162
138
|
end
|
163
139
|
end
|
164
140
|
end
|