clir 0.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +229 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +44 -0
- data/Manual/Manuel_fr.md +222 -0
- data/Manual/Manuel_fr.pdf +0 -0
- data/README.md +63 -0
- data/REFLEXIONS.md +8 -0
- data/Rakefile +10 -0
- data/TODO.md +9 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/clir.gemspec +40 -0
- data/lib/clir/Array.ext.rb +9 -0
- data/lib/clir/CLI.mod.rb +237 -0
- data/lib/clir/CSV_extension.rb +177 -0
- data/lib/clir/Config.cls.rb +25 -0
- data/lib/clir/Date_utils.rb +161 -0
- data/lib/clir/File_extension.rb +127 -0
- data/lib/clir/Integer.ext.rb +43 -0
- data/lib/clir/Labelizor.rb +231 -0
- data/lib/clir/Replayer.cls.rb +90 -0
- data/lib/clir/String.ext.rb +304 -0
- data/lib/clir/Symbol.ext.rb +20 -0
- data/lib/clir/TTY-Prompt.cls.rb +415 -0
- data/lib/clir/Table.rb +369 -0
- data/lib/clir/console_methods.rb +42 -0
- data/lib/clir/helpers_methods.rb +68 -0
- data/lib/clir/state_methods.rb +48 -0
- data/lib/clir/utils_methods.rb +57 -0
- data/lib/clir/utils_numbers.rb +50 -0
- data/lib/clir/version.rb +3 -0
- data/lib/clir.rb +36 -0
- metadata +136 -0
@@ -0,0 +1,415 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
Class CliTTYPrompt
|
4
|
+
|
5
|
+
It exposes +Q+ class (with methods) so you can use:
|
6
|
+
|
7
|
+
`Q.select("Select some vegetables", ...)`
|
8
|
+
|
9
|
+
This class aims two goals:
|
10
|
+
|
11
|
+
* make tests easier with CliTest
|
12
|
+
* enable the "redo" command (with '_')
|
13
|
+
|
14
|
+
# To toggle interactive/inputs mode during the tests (mainly)
|
15
|
+
TTY::MyPrompt.set_mode_interactive
|
16
|
+
TTY::MyPrompt.unset_mode_interactive
|
17
|
+
|
18
|
+
=end
|
19
|
+
require 'tty-prompt'
|
20
|
+
require 'json'
|
21
|
+
|
22
|
+
module TTY
|
23
|
+
class MyPrompt < Prompt
|
24
|
+
|
25
|
+
MARKER_TTY_FILE = File.expand_path(File.join('.','.TTY_MARKER_FILE'))
|
26
|
+
|
27
|
+
class << self
|
28
|
+
|
29
|
+
# @prop :interactive or :inputs
|
30
|
+
attr_accessor :mode
|
31
|
+
|
32
|
+
# Methods to switch hardly in interactive mode during tests
|
33
|
+
def set_mode_interactive
|
34
|
+
Object.send(:remove_const, 'Q')
|
35
|
+
Object.const_set('Q', new)
|
36
|
+
Q.init(mode_interactive = true)
|
37
|
+
File.write(MARKER_TTY_FILE,"#{Time.now}::true")
|
38
|
+
end
|
39
|
+
def unset_mode_interactive
|
40
|
+
Object.send(:remove_const, 'Q')
|
41
|
+
Object.const_set('Q', new)
|
42
|
+
File.delete(MARKER_TTY_FILE) if File.exist?(MARKER_TTY_FILE)
|
43
|
+
Q.init(mode_interactive = false)
|
44
|
+
end
|
45
|
+
alias :set_mode_inputs :unset_mode_interactive
|
46
|
+
|
47
|
+
end #/<< self
|
48
|
+
|
49
|
+
################### INSTANCE ###################
|
50
|
+
|
51
|
+
|
52
|
+
##
|
53
|
+
# Init Q instance
|
54
|
+
#
|
55
|
+
def init(mode_interactive = nil)
|
56
|
+
if File.exist?(MARKER_TTY_FILE)
|
57
|
+
_, mode_interactive = File.read(MARKER_TTY_FILE).split('::')
|
58
|
+
mode_interactive = eval(mode_interactive)
|
59
|
+
include_methods_by_mode(mode_interactive)
|
60
|
+
elsif mode_interactive === nil
|
61
|
+
toggle_mode
|
62
|
+
else
|
63
|
+
include_methods_by_mode(mode_interactive)
|
64
|
+
end
|
65
|
+
@inputs = nil # for testing
|
66
|
+
end
|
67
|
+
|
68
|
+
# Méthode qui fait basculer du mode normal au mode test et
|
69
|
+
# inversement.
|
70
|
+
def toggle_mode
|
71
|
+
include_methods_by_mode(not(CLI::Replayer.on? || test? ))
|
72
|
+
end
|
73
|
+
|
74
|
+
def include_methods_by_mode(interactive_mode)
|
75
|
+
if interactive_mode
|
76
|
+
#
|
77
|
+
# Usual methods
|
78
|
+
# (overwrite tests method if any)
|
79
|
+
#
|
80
|
+
self.extend ReplayedTTYMethods
|
81
|
+
else
|
82
|
+
#
|
83
|
+
# Use Inputs methods instead of usual methods
|
84
|
+
# (overwrite them)
|
85
|
+
#
|
86
|
+
self.extend InputsTTYMethods
|
87
|
+
end
|
88
|
+
@mode = interactive_mode ? :interactive : :inputs
|
89
|
+
self.class.mode = @mode
|
90
|
+
end
|
91
|
+
|
92
|
+
# Sadly, for select, Tty-prompt requires the :name value for the
|
93
|
+
# default value (:default) in methods. This method can return
|
94
|
+
# :name valeur from :value
|
95
|
+
#
|
96
|
+
# @param choices_list {Array} Choices list for select method
|
97
|
+
# @param default_value {Any} The default value
|
98
|
+
#
|
99
|
+
# @return nil or :name of the choice.
|
100
|
+
def default_name_for_value(choices_list, default_value)
|
101
|
+
choices_list.each do |dchoix|
|
102
|
+
if dchoix[:value] == default_value
|
103
|
+
return dchoix[:name]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
return nil
|
107
|
+
end
|
108
|
+
|
109
|
+
end #/class MyPrompt
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Regular methods with replay capabilities
|
114
|
+
#
|
115
|
+
module ReplayedTTYMethods
|
116
|
+
class ReplayedPrompt < TTY::Prompt
|
117
|
+
# def select(*)
|
118
|
+
# eval(code_super(CLI::Replayer.on?))
|
119
|
+
# end
|
120
|
+
|
121
|
+
##
|
122
|
+
# @return method code to evaluate super (if replayer is
|
123
|
+
# off) or to get input (if replayer is on)
|
124
|
+
def code_super(on)
|
125
|
+
CODE_SUPER_OR_GET_INPUT % {truth: on ? 'true' : 'false'}
|
126
|
+
end
|
127
|
+
CODE_SUPER_OR_GET_INPUT = <<~RUBY
|
128
|
+
if %{truth}
|
129
|
+
CLI::Replayer.get_input
|
130
|
+
else
|
131
|
+
super.tap do |result|
|
132
|
+
CLI::Replayer.add_input(result)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
RUBY
|
136
|
+
|
137
|
+
end #/class ReplayedPrompt
|
138
|
+
end #/module ReplayedTTYMethods
|
139
|
+
|
140
|
+
##
|
141
|
+
# Tests methods for TTY::Prompt
|
142
|
+
#
|
143
|
+
# Each method of TTY::Prompt owns its own method in this
|
144
|
+
# module so it can respond to Q.<method> and return the 'inputs'
|
145
|
+
# defined in CLITests for user interaction.
|
146
|
+
#
|
147
|
+
module InputsTTYMethods
|
148
|
+
|
149
|
+
##
|
150
|
+
# Error class raised when there's no more input values to
|
151
|
+
# bring back.
|
152
|
+
#
|
153
|
+
class CLiTestNoMoreValuesError < StandardError; end
|
154
|
+
|
155
|
+
##
|
156
|
+
# In test mode, return fake-user inputs (keyboard inputs)
|
157
|
+
def inputs
|
158
|
+
@inputs ||= begin
|
159
|
+
if ENV['CLI_TEST_INPUTS']
|
160
|
+
JSON.parse ENV['CLI_TEST_INPUTS']
|
161
|
+
elsif CLI::Replayer.inputs
|
162
|
+
CLI::Replayer.inputs
|
163
|
+
else
|
164
|
+
[]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# @return next fake-user input or raise a error if no more
|
171
|
+
# value to return.
|
172
|
+
#
|
173
|
+
def next_input
|
174
|
+
if inputs.any?
|
175
|
+
inputs.shift
|
176
|
+
else
|
177
|
+
raise CLiTestNoMoreValuesError
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def response_of(type, *args, &block)
|
182
|
+
Responder.new(self, type, *args, &block).response
|
183
|
+
end
|
184
|
+
|
185
|
+
def ask(*args, &block)
|
186
|
+
response_of('ask', *args, &block)
|
187
|
+
end
|
188
|
+
def yes?(*args, &block)
|
189
|
+
response_of('yes', *args, &block)
|
190
|
+
end
|
191
|
+
def no?(*args, &block)
|
192
|
+
response_of('no', *args, &block)
|
193
|
+
end
|
194
|
+
def multiline(*args, &block)
|
195
|
+
response_of('multiline', *args, &block)
|
196
|
+
end
|
197
|
+
def select(*args, &block)
|
198
|
+
response_of('select', *args, &block)
|
199
|
+
end
|
200
|
+
def multi_select(*args, &block)
|
201
|
+
response_of('multi_select', *args, &block)
|
202
|
+
end
|
203
|
+
def slider(*args, &block)
|
204
|
+
response_of('slider', *args, &block)
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
# --- Class InputsTTYMethods::Responder ---
|
209
|
+
|
210
|
+
class Responder
|
211
|
+
|
212
|
+
attr_reader :prompt, :type, :args
|
213
|
+
|
214
|
+
##
|
215
|
+
# The input for this responder
|
216
|
+
attr_reader :input
|
217
|
+
|
218
|
+
def initialize(prompt, type, *args, &block)
|
219
|
+
@prompt = prompt
|
220
|
+
@type = type
|
221
|
+
@args = args
|
222
|
+
instance_eval(&block) if block_given?
|
223
|
+
end
|
224
|
+
|
225
|
+
##
|
226
|
+
# Main method to evaluate the respond to give
|
227
|
+
# (the respond that user would have given)
|
228
|
+
#
|
229
|
+
# @return the fake-user input transformed (for example, if no
|
230
|
+
# value has been given, return the default value)
|
231
|
+
def response
|
232
|
+
@input = prompt.next_input
|
233
|
+
if treat_special_input_values
|
234
|
+
self.send(tty_method)
|
235
|
+
end
|
236
|
+
return input
|
237
|
+
end
|
238
|
+
|
239
|
+
# --- Special treatment of input values ---
|
240
|
+
|
241
|
+
##
|
242
|
+
# @return false if no more treatment (no send to tty_method
|
243
|
+
# below)
|
244
|
+
#
|
245
|
+
def treat_special_input_values
|
246
|
+
case input.to_s.upcase
|
247
|
+
when /CTRL[ _\-]C/, 'EXIT', '^C' then exit 0
|
248
|
+
when 'DEFAULT', 'DÉFAUT'
|
249
|
+
@input = default_value
|
250
|
+
return false
|
251
|
+
end
|
252
|
+
return true
|
253
|
+
end
|
254
|
+
|
255
|
+
# --- Twin TTY::Prompt methods ---
|
256
|
+
# --- They treat input value as required ---
|
257
|
+
|
258
|
+
def __ask
|
259
|
+
# nothing to do (even default value is treated above)
|
260
|
+
end
|
261
|
+
|
262
|
+
def __multiline
|
263
|
+
case input
|
264
|
+
when /CTRL[ _\-]D/, '^D' then @input = ''
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def __select
|
269
|
+
return unless input.is_a?(Hash)
|
270
|
+
@input =
|
271
|
+
if input.key?('name')
|
272
|
+
find_in_choices(/^#{input['name']}$/i).first
|
273
|
+
elsif input.key?('rname')
|
274
|
+
find_in_choices(/#{input['rname']}/).first
|
275
|
+
elsif input.key?('item') || input.key?('index')
|
276
|
+
choices[(input['item']||input['index']) - 1][:value]
|
277
|
+
else
|
278
|
+
input
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def __multi_select
|
283
|
+
return unless input.is_a?(Hash)
|
284
|
+
@input =
|
285
|
+
if input.key?('names')
|
286
|
+
find_in_choices(/^(#{input['names'].join('|')})$/i)
|
287
|
+
elsif input.key?('items') || input.key?('index')
|
288
|
+
(input['items']||input['index']).map { |n| choices[n.to_i - 1][:value] }
|
289
|
+
elsif input.key?('rname')
|
290
|
+
find_in_choices(/#{input['rname']}/i)
|
291
|
+
elsif input.key?('rnames')
|
292
|
+
find_in_choices(/(#{input['rnames'].join('|')})/i)
|
293
|
+
else
|
294
|
+
input
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def __yes
|
299
|
+
@input = ['o','y','true',"\n",'1','oui','yes'].include?(input.to_s.downcase)
|
300
|
+
end
|
301
|
+
def __no
|
302
|
+
self.__yes
|
303
|
+
@input = !@input
|
304
|
+
end
|
305
|
+
|
306
|
+
def __slider
|
307
|
+
@input = input.to_i
|
308
|
+
end
|
309
|
+
|
310
|
+
# --- Usefull methods ---
|
311
|
+
|
312
|
+
# @return all values that match +search+ in choices
|
313
|
+
def find_in_choices(search)
|
314
|
+
@choices.select do |choix|
|
315
|
+
choix[:name].match?(search)
|
316
|
+
end.map do |choix|
|
317
|
+
choix[:value]
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# @return self Twin TTY method
|
322
|
+
# p.e. '__ask' ou '__select'
|
323
|
+
def tty_method
|
324
|
+
@tty_method ||= "__#{type}".to_sym
|
325
|
+
end
|
326
|
+
|
327
|
+
# --- Default value ---
|
328
|
+
|
329
|
+
##
|
330
|
+
# @return the default value
|
331
|
+
def default_value
|
332
|
+
if defined?(@default)
|
333
|
+
return @default
|
334
|
+
elsif args[-1].is_a?(Hash)
|
335
|
+
args.last[:default]
|
336
|
+
else
|
337
|
+
nil
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
# --- Les méthodes communes qui permettent de définir
|
342
|
+
# le répondeur ---
|
343
|
+
|
344
|
+
##
|
345
|
+
# To define and get select choices
|
346
|
+
#
|
347
|
+
def choices(vals = nil)
|
348
|
+
if vals.nil?
|
349
|
+
return @choices ||= []
|
350
|
+
else
|
351
|
+
@choices ||= []
|
352
|
+
@choices += vals
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
##
|
357
|
+
# For select or multi-select, to add a choice
|
358
|
+
#
|
359
|
+
def choice menu, value = nil, options = nil
|
360
|
+
@choices ||= []
|
361
|
+
@choices << {name:menu, value:value||menu, options:options}
|
362
|
+
end
|
363
|
+
|
364
|
+
##
|
365
|
+
# To define the number of items displayed with a
|
366
|
+
# select or multiselect
|
367
|
+
#
|
368
|
+
def per_page(*args)
|
369
|
+
# Rien à faire ici
|
370
|
+
end
|
371
|
+
|
372
|
+
##
|
373
|
+
# To define the default value
|
374
|
+
#
|
375
|
+
def default(value)
|
376
|
+
@default = value
|
377
|
+
end
|
378
|
+
|
379
|
+
##
|
380
|
+
# For real TTYPrompt compatibility
|
381
|
+
def enum(*args)
|
382
|
+
# Ne rien faire
|
383
|
+
end
|
384
|
+
|
385
|
+
##
|
386
|
+
# For real TTYPrompt compatibility
|
387
|
+
def help(str)
|
388
|
+
# Ne rien faire
|
389
|
+
end
|
390
|
+
|
391
|
+
##
|
392
|
+
# For real TTYPrompt compatibility
|
393
|
+
def validate(*arg, &block)
|
394
|
+
# Ne rien faire
|
395
|
+
# TODO Plus tard on pourra vérifier les validations aussi
|
396
|
+
end
|
397
|
+
|
398
|
+
##
|
399
|
+
# For real TTYPrompt compatibility
|
400
|
+
def convert(*arg, &block)
|
401
|
+
# Ne rien faire
|
402
|
+
if block_given?
|
403
|
+
# TODO Plus tard on pourra vérifier les conversions aussi
|
404
|
+
# Mais attention : c'est pas forcément donné par block
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
|
409
|
+
end #/class Responder
|
410
|
+
|
411
|
+
end #/module InputsTTYMethods
|
412
|
+
|
413
|
+
Q = TTY::MyPrompt.new(symbols: {radio_on:"☒", radio_off:"☐"})
|
414
|
+
# Q = TTY::Prompt.new(symbols: {radio_on:"☒", radio_off:"☐"})
|
415
|
+
|