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,127 @@
|
|
1
|
+
class File
|
2
|
+
|
3
|
+
# @return +nline+ last lines of +path+ file without loading the
|
4
|
+
# whole content.
|
5
|
+
#
|
6
|
+
def self.tail(path, nlines)
|
7
|
+
file = open(path, "r")
|
8
|
+
buff = 512
|
9
|
+
line_count = 0
|
10
|
+
# nlines += 1 # to get the last one
|
11
|
+
file.seek(0, IO::SEEK_END)
|
12
|
+
|
13
|
+
offset = file.pos # start at the end
|
14
|
+
|
15
|
+
while nlines > line_count && offset > 0
|
16
|
+
to_read = if (offset - buff) < 0
|
17
|
+
offset
|
18
|
+
else
|
19
|
+
buff
|
20
|
+
end
|
21
|
+
|
22
|
+
file.seek(offset - to_read)
|
23
|
+
data = file.read(to_read)
|
24
|
+
|
25
|
+
data.reverse.each_char do |c|
|
26
|
+
if line_count > nlines
|
27
|
+
offset += 1
|
28
|
+
break
|
29
|
+
end
|
30
|
+
offset -= 1
|
31
|
+
if c == "\n"
|
32
|
+
line_count += 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
file.seek(offset)
|
38
|
+
data = file.read.strip
|
39
|
+
ensure
|
40
|
+
file.close
|
41
|
+
end
|
42
|
+
|
43
|
+
# def self.readlines_backward(path, &block)
|
44
|
+
# #
|
45
|
+
# # Si le fichier n'est pas trop gros, on le lit à l'envers
|
46
|
+
# # en le chargeant.
|
47
|
+
# #
|
48
|
+
# if File.size(File.new(path)) < ( 1 << 16 )
|
49
|
+
# self.readlines(path).reverse.each do |line|
|
50
|
+
# yield line
|
51
|
+
# end
|
52
|
+
# return
|
53
|
+
# end
|
54
|
+
|
55
|
+
# #
|
56
|
+
# # Pour un gros fichier
|
57
|
+
# #
|
58
|
+
|
59
|
+
# # TODO ON POURRAIT METTRE UN BUFFER MOINS GRAND POUR NE PAS
|
60
|
+
# # AVOIR DE TROP NOMBREUSES LIGNES EN MÊME TEMPS
|
61
|
+
# tail_buf_length = 1 << 16 # 65000 et quelques
|
62
|
+
# file = File.new(path)
|
63
|
+
|
64
|
+
# file.seek(-tail_buf_length,IO::SEEK_END)
|
65
|
+
# out = ""
|
66
|
+
# count = 0
|
67
|
+
# while count <= n # TODO C'EST CETTE BOUCLE QU'IL FAUT DÉVELOPPER JUSQU'EN HAUT
|
68
|
+
# buf = file.read( tail_buf_length )
|
69
|
+
# count += buf.count("\n")
|
70
|
+
# out += buf
|
71
|
+
# # 2 * since the pointer is a the end , of the previous iteration
|
72
|
+
# file.seek(2 * -tail_buf_length,IO::SEEK_CUR)
|
73
|
+
# end
|
74
|
+
# # TODO : IL FAUT S'Y PRENDRE MIEUX QUE ÇA POUR TOUT LIRE
|
75
|
+
# # CAR MAINTENANT +n+ N'EST PLUS LE NOMBRE DE LIGNES DONNÉES EN
|
76
|
+
# # ARGUMENT DE LA MÉTHODE tail ORIGINALE
|
77
|
+
# out.split("\n")[-n..-1].each do |line|
|
78
|
+
# yield line
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
|
82
|
+
# @return les +n+ dernières lignes d'un fichier
|
83
|
+
def tail_lines(n)
|
84
|
+
return [] if n < 1
|
85
|
+
if File.size(self) < ( 1 << 16 )
|
86
|
+
tail_buf_length = File.size(self)
|
87
|
+
return self.readlines.reverse[0..n-1]
|
88
|
+
else
|
89
|
+
tail_buf_length = 1 << 16
|
90
|
+
end
|
91
|
+
self.seek(-tail_buf_length,IO::SEEK_END)
|
92
|
+
out = ""
|
93
|
+
count = 0
|
94
|
+
while count <= n
|
95
|
+
buf = self.read( tail_buf_length )
|
96
|
+
count += buf.count("\n")
|
97
|
+
out += buf
|
98
|
+
# 2 * since the pointer is a the end , of the previous iteration
|
99
|
+
self.seek(2 * -tail_buf_length,IO::SEEK_CUR)
|
100
|
+
end
|
101
|
+
return out.split("\n")[-n..-1]
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
# @return [String] the affix's file, i.e. the name without the
|
107
|
+
# extension.
|
108
|
+
#
|
109
|
+
# @example
|
110
|
+
# File.affix("myfile.ext") => "myfile"
|
111
|
+
# File.affix("pth/to/myfile.ext") => "myfile"
|
112
|
+
def self.affix(path)
|
113
|
+
File.basename(path,File.extname(path))
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Add +code+ to file at +fpath+
|
118
|
+
#
|
119
|
+
def self.append(fpath, code)
|
120
|
+
begin
|
121
|
+
open(fpath,'a') { |f| f.write(code) }
|
122
|
+
rescue ENOENT
|
123
|
+
raise "Folder #{File.dirname(fpath)} doesn't exist. Unable to write #{fpath} file."
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Integer
|
2
|
+
|
3
|
+
# @return TRUE if self is inside +ary+ or has key self
|
4
|
+
# @param ary {Range|Array|Hash}
|
5
|
+
def in?(ary)
|
6
|
+
case ary
|
7
|
+
when Array, Range
|
8
|
+
ary.include?(self)
|
9
|
+
when Hash
|
10
|
+
ary.key?(self)
|
11
|
+
else
|
12
|
+
raise "Integer#in? waits for a Array, an Hash or a Range. Given: #{ary.class}."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def semaine
|
17
|
+
self * 7 * 3600 * 24
|
18
|
+
end
|
19
|
+
alias :semaines :semaine
|
20
|
+
alias :week :semaine
|
21
|
+
alias :weeks :semaine
|
22
|
+
|
23
|
+
# For 4.jours / 4.days
|
24
|
+
def jour
|
25
|
+
self * 3600 * 24
|
26
|
+
end
|
27
|
+
alias :jours :jour
|
28
|
+
alias :day :jour
|
29
|
+
alias :days :jour
|
30
|
+
|
31
|
+
def heure
|
32
|
+
self * 3600
|
33
|
+
end
|
34
|
+
alias :heures :heure
|
35
|
+
alias :hour :heure
|
36
|
+
alias :hours :heure
|
37
|
+
|
38
|
+
def minute
|
39
|
+
self * 60
|
40
|
+
end
|
41
|
+
alias :minutes :minute
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
=begin
|
3
|
+
|
4
|
+
Pour simplifier l'affichage de table à la console.
|
5
|
+
|
6
|
+
@usage
|
7
|
+
|
8
|
+
t = Labelizor.new(<params>)
|
9
|
+
|
10
|
+
t.w "<label>", "<valeur>", <options>
|
11
|
+
...
|
12
|
+
|
13
|
+
t.display # puts
|
14
|
+
|
15
|
+
NOTES
|
16
|
+
=====
|
17
|
+
Il y a des options très pratiques comme la propriété :if qui
|
18
|
+
permet de n'écrire la ligne que si la condition est vraie. Ça
|
19
|
+
permet de ne pas avoir d'identation dans la définition de la
|
20
|
+
table.
|
21
|
+
Au lieu de :
|
22
|
+
|
23
|
+
t.w "Mon label", "Ma valeur"
|
24
|
+
if cest_vrai
|
25
|
+
t.w "Autre label", "Autre valeur"
|
26
|
+
end
|
27
|
+
|
28
|
+
… on utilisera :
|
29
|
+
|
30
|
+
t.w "Mon label", "Ma valeur"
|
31
|
+
t.w "Autre label", "Autre valeur", if:cest_vrai
|
32
|
+
|
33
|
+
=end
|
34
|
+
|
35
|
+
class Labelizor
|
36
|
+
attr_reader :lines
|
37
|
+
attr_reader :config
|
38
|
+
#
|
39
|
+
# @param params {Hash|Nil}
|
40
|
+
# :titre Le titre à donné au tableau
|
41
|
+
# :titre_color Couleur à appliquer (bleu par défaut)
|
42
|
+
# :separator le sépateur, en général une espace mais peut être
|
43
|
+
# aussi un point
|
44
|
+
# :delimitor_size {Integer} La longueur par défaut d'une
|
45
|
+
# ligne délimitant les données.
|
46
|
+
# On peut définir la longueur d'une ligne à la
|
47
|
+
# volée avec le 2e paramètres : t.w(:delimitor, 20)
|
48
|
+
# :delimitor_char {String} Caractère utilisé pour le délimiteur
|
49
|
+
# Par défaut, un signe '='
|
50
|
+
# :gutter {Integer} La largeur de la gouttière (4 par défaut)
|
51
|
+
# :indentation {Integer} Nombre d'espace en indentation (2 par
|
52
|
+
# defaut)
|
53
|
+
# :selectable {Boolean} Si true, l'instance rajoute des numéros
|
54
|
+
# à chaque item et permet à la fin d'en choisir
|
55
|
+
# un : value = Labelizor#display
|
56
|
+
#
|
57
|
+
def initialize(params = nil)
|
58
|
+
defaultize_config(params)
|
59
|
+
reset
|
60
|
+
end
|
61
|
+
def defaultize_config(config)
|
62
|
+
@config = config || {}
|
63
|
+
@config.key?(:gutter) || @config.merge!(gutter: 4)
|
64
|
+
@config.key?(:indentation) || @config.merge!(indentation: 2)
|
65
|
+
@config.key?(:separator) || @config.merge!(separator: ' ')
|
66
|
+
@config.key?(:delimitor_size) || @config.merge!(delimitor_size: nil)
|
67
|
+
@config.key?(:delimitor_char) || @config.merge!(delimitor_char: '=')
|
68
|
+
@config.key?(:title_delimitor) || @config.merge!(title_delimitor: '*')
|
69
|
+
@config.key?(:delimitor_color) || @config.merge!(delimitor_color: nil)
|
70
|
+
@config.key?(:titre_color) || @config.merge!(titre_color: :bleu)
|
71
|
+
end
|
72
|
+
#
|
73
|
+
# Pour écrire dans la table
|
74
|
+
#
|
75
|
+
# @param label {String}
|
76
|
+
# Le label. Certaines valeurs spéciales peuvent être utilisées
|
77
|
+
# comme :delimitor (la longueur sera celle définie en second
|
78
|
+
# argument ou celle définie par défaut)
|
79
|
+
# @param value {Any}
|
80
|
+
# La valeur à afficher à droite du label.
|
81
|
+
# @param params {Hash}
|
82
|
+
# Les paramètres à appliquer. C'est là que la méthode prend
|
83
|
+
# tout son sens.
|
84
|
+
# Les valeurs définies peuvent être :
|
85
|
+
# :color {Symbol} La méthode de couleur (p.e. :vert)
|
86
|
+
# :if On n'affiche la ligne que si la valeur est true
|
87
|
+
#
|
88
|
+
def w(label, value = nil, params = nil)
|
89
|
+
params ||= {}
|
90
|
+
return if params.key?(:if) && !params[:if]
|
91
|
+
#
|
92
|
+
# Traitement de valeur de +label+ spéciales
|
93
|
+
#
|
94
|
+
case label
|
95
|
+
when :titre, :title
|
96
|
+
value = "\n#{value}\n#{'-'*value.length}"
|
97
|
+
add_line [:titre, value, params]
|
98
|
+
when :delimitor
|
99
|
+
params.merge!(color: delimitor_color) unless params.key?(:color)
|
100
|
+
add_line([:delimitor, value, params])
|
101
|
+
else
|
102
|
+
add_line([label || '', value || '', params])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
alias :<< :w
|
106
|
+
|
107
|
+
def reset
|
108
|
+
@label_width = nil
|
109
|
+
@lines = [] # pour se servir encore de l'instance
|
110
|
+
@indent = nil
|
111
|
+
@values = [] # pour selectable
|
112
|
+
end
|
113
|
+
|
114
|
+
def add_line(dline)
|
115
|
+
#
|
116
|
+
# Si c'est une table "sélectionnable", il faut ajouter des
|
117
|
+
# index pour choisir la valeur
|
118
|
+
#
|
119
|
+
if selectable?
|
120
|
+
@values << dline[1].freeze
|
121
|
+
dline[0] = "#{@values.count.to_s.rjust(3)}. #{dline[0]}"
|
122
|
+
end
|
123
|
+
#
|
124
|
+
# Ajout de la ligne
|
125
|
+
#
|
126
|
+
@lines << dline
|
127
|
+
end
|
128
|
+
#
|
129
|
+
# Méthode pour afficher la table
|
130
|
+
def display
|
131
|
+
puts "\n"
|
132
|
+
#
|
133
|
+
# Écriture du titre (if any)
|
134
|
+
#
|
135
|
+
if @config[:titre]
|
136
|
+
sep = @config[:title_delimitor] * (@config[:titre].length + 2)
|
137
|
+
titre = ("#{indent + sep}\n#{indent + ' ' + @config[:titre]}\n#{indent + sep}")
|
138
|
+
titre = titre.send(@config[:titre_color]) unless config[:titre_color].nil?
|
139
|
+
puts titre
|
140
|
+
end
|
141
|
+
puts "\n"
|
142
|
+
#
|
143
|
+
# Écriture des lignes
|
144
|
+
#
|
145
|
+
lines.each do |lab, val, params|
|
146
|
+
case lab
|
147
|
+
when :titre then lab, val = [val,'']
|
148
|
+
when :delimitor
|
149
|
+
lab = delimitor_char * delimitor_size(val)
|
150
|
+
val = nil
|
151
|
+
else
|
152
|
+
lab = "#{lab} ".ljust(label_width - 2, config[:separator])
|
153
|
+
end
|
154
|
+
str = lab + ' ' + formate_value(val, params)
|
155
|
+
str = str.send(params[:color]) if params[:color]
|
156
|
+
idt = indent(params)
|
157
|
+
str = idt + str.split("\n").join("\n#{idt}")
|
158
|
+
puts str
|
159
|
+
end
|
160
|
+
puts "\n\n"
|
161
|
+
if selectable?
|
162
|
+
wait_for_item_chosen
|
163
|
+
end
|
164
|
+
reset
|
165
|
+
end
|
166
|
+
alias :flush :display
|
167
|
+
|
168
|
+
def wait_for_item_chosen
|
169
|
+
choix = nil
|
170
|
+
nombre_choix = @values.count
|
171
|
+
puts "\n"
|
172
|
+
while choix.nil?
|
173
|
+
STDOUT.write("\rMémoriser : ")
|
174
|
+
choix = STDIN.getch.to_i
|
175
|
+
if choix > 0 && choix <= nombre_choix
|
176
|
+
break
|
177
|
+
else
|
178
|
+
STDOUT.write "\r #{choix} n'est pas compris entre 1 et #{nombre_choix}.".rouge
|
179
|
+
choix = nil
|
180
|
+
end
|
181
|
+
end
|
182
|
+
STDOUT.write("\r"+' '*80)
|
183
|
+
clip @values[choix.to_i - 1]
|
184
|
+
puts "\n\n"
|
185
|
+
end
|
186
|
+
|
187
|
+
def delimitor_color
|
188
|
+
@delimitor_color ||= config[:delimitor_color]
|
189
|
+
end
|
190
|
+
def delimitor_size(value = nil)
|
191
|
+
return value unless value.nil?
|
192
|
+
@delimitor_size ||= config[:delimitor_size] || (label_width - 1)
|
193
|
+
end
|
194
|
+
def delimitor_char
|
195
|
+
@delimitor_char || config[:delimitor_char]
|
196
|
+
end
|
197
|
+
|
198
|
+
def indent(params = {})
|
199
|
+
@indent ||= ' ' * config[:indentation]
|
200
|
+
case params[:indent]
|
201
|
+
when 0 then ''
|
202
|
+
when nil then @indent
|
203
|
+
when Integer then @indent + ' ' * params[:indent]
|
204
|
+
when String then @indent + params[:indent]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
#
|
208
|
+
# Formater la valeur en fonction des paramètres
|
209
|
+
#
|
210
|
+
def formate_value(val, params)
|
211
|
+
return '' if val.nil?
|
212
|
+
val = €(val) if params[:euros]
|
213
|
+
val = formate_date(val) if params[:date]
|
214
|
+
return val.to_s
|
215
|
+
end
|
216
|
+
#
|
217
|
+
# Calcul de la longueur du label
|
218
|
+
#
|
219
|
+
def label_width
|
220
|
+
@label_width ||= begin
|
221
|
+
maxw = 0; @lines.each do |dline|
|
222
|
+
labl = dline[0].length
|
223
|
+
maxw = labl if labl > maxw
|
224
|
+
end;maxw + config[:gutter]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def selectable?
|
229
|
+
:TRUE == @isselectable ||= true_or_false(@config[:selectable] == true)
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module CLI
|
2
|
+
class Replayer
|
3
|
+
class << self
|
4
|
+
|
5
|
+
attr_reader :ison
|
6
|
+
|
7
|
+
##
|
8
|
+
# When the command is not replayed (ie not '_'), CLI initialize
|
9
|
+
# the recordings of inputs
|
10
|
+
#
|
11
|
+
def init_for_recording
|
12
|
+
@inputs = []
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# @return true if replayer is running
|
17
|
+
# (note : it's running when CLI.main_command is '_')
|
18
|
+
#
|
19
|
+
def on?
|
20
|
+
:TRUE == ison
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Start running replayer (with '_' main command)
|
25
|
+
def start
|
26
|
+
@ison = replayable? ? :TRUE : :FALSE
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_input
|
30
|
+
@inputs.pop
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Add a {Any} input value (a Hash, a string, an object, etc.)
|
35
|
+
def add_input(input)
|
36
|
+
@inputs << input
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# @return last inputs for the same command.
|
41
|
+
# Use 'get_input' to get the input in order with 'pop'
|
42
|
+
def inputs
|
43
|
+
@inputs ||= begin
|
44
|
+
replayable? || raise('Can’t load inputs values: this command is not replayable.')
|
45
|
+
data[:inputs].reverse
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Save inputs at the end of the script
|
51
|
+
def save
|
52
|
+
table_replay = {
|
53
|
+
inputs: inputs,
|
54
|
+
data: CLI::get_command_line_data
|
55
|
+
}
|
56
|
+
File.write(last_command_file, Marshal.dump(table_replay))
|
57
|
+
end
|
58
|
+
|
59
|
+
def data
|
60
|
+
@data ||= begin
|
61
|
+
load.tap do |table|
|
62
|
+
CLI.set_command_line_data(table[:data])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def load
|
68
|
+
Marshal.load(last_command_file)
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# @return TRUE if there is a replayable command
|
73
|
+
# Note: it's a replayable command if it exists a
|
74
|
+
# '.<app>_lastcommand' file in current folder
|
75
|
+
def replayable?
|
76
|
+
File.exist?(last_command_file(CLI.command_name))
|
77
|
+
end
|
78
|
+
|
79
|
+
def last_command_file(command_name = nil)
|
80
|
+
command_name ||= CLI.command_name
|
81
|
+
File.join(commands_folder_path, command_name)
|
82
|
+
end
|
83
|
+
|
84
|
+
def commands_folder_path
|
85
|
+
@commands_folder_path ||= mkdir(File.join(ENV["HOME"] || ENV["USERPROFILE"],'.chir_last_commands'))
|
86
|
+
end
|
87
|
+
|
88
|
+
end #/<< self class Replayer
|
89
|
+
end #/class Replayer
|
90
|
+
end #/module CLI
|