clir 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/clir/Table.rb ADDED
@@ -0,0 +1,369 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+ =begin
4
+
5
+ Class Clir::Table
6
+ -----------------
7
+ Construction d'une table en console
8
+
9
+ Contrairement à Labelizor qui ne construit des tables qu'à
10
+ deux colonnes, avec <<< libelle, valeur >>>, cette classe permet
11
+ de construire des affichages à plusieurs colonnes.
12
+
13
+
14
+ @usage
15
+
16
+ # Pour définir la table
17
+ tb = CLITable.new(
18
+ colonnes_totaux: {3 => :euros},
19
+ # On comptera le total de la 3e colonnes et l'on transformera
20
+ # toutes les valeurs en euros (avec la méthode :€)
21
+ header: ["TITRE", "NOMBRE", "REDEVANCE"],
22
+ gutter: 3,
23
+ align: {right: [3]}
24
+ # La 3e colonne sera alignée à droite (sur le '€' ici)
25
+ )
26
+
27
+ # Pour ajouter une ligne
28
+ tb << [titre, nombre, redevance]
29
+
30
+ # Pour l'afficher
31
+ tb.display
32
+
33
+ =end
34
+ module Clir
35
+ class Table
36
+ attr_reader :params
37
+
38
+ attr_reader :column_count
39
+
40
+ ##
41
+ # Instanciation
42
+ #
43
+ # @param params {Hash}
44
+ #
45
+ # Définition générale de la table.
46
+ #
47
+ # :colonnes_totaux {Hash} Table des indices (1-start) des
48
+ # colonnes dont il faut faire la somme.
49
+ # Avec en clé l'indice réel de la colonne et
50
+ # en valeur soit nil pour un nombre normal,
51
+ # soit :euros pour une somme financière.
52
+ # Si cette donnée est définie, une nouvelle
53
+ # ligne sera ajoutée au bout du tableau avec
54
+ # la somme de cette colonne.
55
+ #
56
+ # :header {Array} Entête, nom de chaque colonne. On peut
57
+ # utiliser les retours de chariot pour faire deux
58
+ # lignes.
59
+ # :gutter {Integer} Goutière entre chaque colonne. 4 par défaut
60
+ # :indent {Integer} Indentation initiale, en nombre d'espaces
61
+ #
62
+ #
63
+ # :char_separator {String} La caractère pour faire les lignes
64
+ # horizontales de séparation. Une étoile par
65
+ # défaut.
66
+ #
67
+ # @option params [Hash] :align Définition de l'alignement dans les colonnes.
68
+ # En clé l'indice 1-start de la colonne, en
69
+ # valeur une valeur parmi :right, :left et :center
70
+ # @option params [Array|Hash|Integer] :max_widths
71
+ # Max width for columns. If it's a integer, it's
72
+ # the max width for each column.
73
+ # If it's an Array, it's the definition of each
74
+ # column in order. For example, [4, 5, 6] means
75
+ # 4 signs for the first column, 5 for the second
76
+ # one and 6 for the third column. Columns without
77
+ # max widths must have nil value.
78
+ # If it's a Hash, the key is the column index
79
+ # (1-start) and the value is the max lenght in
80
+ # signs.
81
+ #
82
+ def initialize(params = nil)
83
+ @params = params || {}
84
+ @lines = []
85
+ add_header_lines
86
+ end
87
+
88
+ def display
89
+ #
90
+ # On définit l'alignement de chaque colonne
91
+ #
92
+ define_colonnes_align
93
+
94
+ #
95
+ # On fait les totaux des colonnes désignées
96
+ #
97
+ formate_cells_totaux unless colonnes_totaux.nil?
98
+
99
+ #
100
+ # On mesure les largeurs des colonnes
101
+ #
102
+ calc_column_widths
103
+
104
+ #
105
+ # On ajoute une séparation à la toute fin
106
+ #
107
+ add(separation)
108
+
109
+ #
110
+ # Séparation toujours
111
+ #
112
+ puts "\n\n"
113
+
114
+ #
115
+ # Écriture du titre (if any)
116
+ #
117
+ traite_titre if @params[:title]
118
+
119
+ #
120
+ # Boucle sur chaque ligne d'entête
121
+ #
122
+ @header_lines.each do |cols|
123
+ traite_colonnes( cols, is_header = true )
124
+ end
125
+ #
126
+ # Boucle sur chaque ligne de données
127
+ #
128
+ @lines.each do |cols|
129
+ traite_colonnes( cols, is_header = false)
130
+ end
131
+ puts "\n\n"
132
+ end
133
+
134
+
135
+ def traite_titre
136
+ puts "#{indent}#{char_separator} #{params[:title].upcase} #{char_separator}\n#{indent}#{separation}"
137
+ end
138
+
139
+ def traite_colonnes(cols, for_header = false)
140
+ line =
141
+ case cols
142
+ when Array
143
+ ' ' + cols.collect.with_index do |col, idx|
144
+ alignment = for_header ? :ljust : colonne_aligns[idx]
145
+ col = col.to_s
146
+ if col.length > @column_widths[idx]
147
+ col[0...(@column_widths[idx] - 1)] + '…'
148
+ else
149
+ col.to_s.send(alignment, @column_widths[idx])
150
+ end
151
+ end.join(gutter) + ' '
152
+ when :separation then separation
153
+ when String then cols
154
+ end
155
+ #
156
+ # Écriture de la ligne
157
+ #
158
+ puts indent + line
159
+ end
160
+
161
+ def add(ary)
162
+ @lines << ary
163
+ end
164
+ alias :<< :add
165
+
166
+ # Building header
167
+ #
168
+ # On part toujours du principe qu'il y a deux lignes, et si
169
+ # l'une est vide, on n'en met qu'une seule.
170
+ def add_header_lines
171
+ @header_lines = []
172
+ return if params[:header].nil?
173
+ two_lines = false
174
+ line1 = []
175
+ line2 = []
176
+ params[:header].each do |lib|
177
+ lib1, lib2 =
178
+ if lib.match?("\n")
179
+ two_lines = true
180
+ lib.split("\n")
181
+ else
182
+ ['', lib]
183
+ end
184
+ line1 << lib1
185
+ line2 << lib2
186
+ end
187
+ @column_count = line2.count
188
+ @header_lines << line1 if two_lines
189
+ @header_lines << line2
190
+ @header_lines << :separation
191
+ end
192
+
193
+ def table_width
194
+ @table_width ||= @column_widths.sum + (gutter_width * (column_count - 1))
195
+ end
196
+
197
+ def separation
198
+ @separation ||= char_separator * (table_width + 4)
199
+ end
200
+
201
+ def char_separator
202
+ @char_separator ||= params[:char_separator] || '*'
203
+ end
204
+
205
+ def gutter
206
+ @gutter ||= ' ' * gutter_width
207
+ end
208
+
209
+ def gutter_width
210
+ @gutter_width ||= params[:gutter] || 4
211
+ end
212
+
213
+ def indent
214
+ @indent ||= ' ' * (params[:indent] || 2)
215
+ end
216
+
217
+ def align
218
+ @align ||= params[:align]
219
+ end
220
+
221
+ def max_widths
222
+ @max_widths ||= params[:max_widths]
223
+ end
224
+
225
+ def colonnes_totaux
226
+ @colonnes_totaux ||= params[:colonnes_totaux]
227
+ end
228
+
229
+ # Alignement des colonnes
230
+ def colonne_aligns
231
+ @colonne_aligns
232
+ end
233
+
234
+
235
+ private
236
+
237
+ def define_colonnes_align
238
+ #
239
+ # Par défaut tous les colonnes sont alignés à gauche
240
+ #
241
+ @colonne_aligns = Array.new(column_count, :ljust)
242
+ return if align.nil?
243
+ align.each do |indice_colonne, alignment|
244
+
245
+ @colonne_aligns[indice_colonne - 1] = case alignment
246
+ when :right then :rjust
247
+ when :left then :ljust
248
+ when :center then :cjust
249
+ end
250
+ end
251
+ end
252
+
253
+ #
254
+ # Note : requis avant de compter la largeur des colonnes, car
255
+ # les totaux peuvent changer la donne
256
+ #
257
+ def formate_cells_totaux
258
+ #
259
+ # On fait la ligne de total
260
+ #
261
+ make_totaux_line
262
+ #
263
+ # On formate toutes les valeurs. Car elles ont été données
264
+ # en nombre (x) et non pas en euros (€(x)) pour pouvoir calcu-
265
+ # ler les totaux.
266
+ #
267
+ @lines.each do |cols|
268
+ next unless cols.is_a?(Array)
269
+ colonnes_totaux.each do |idx, type|
270
+ real_idx = idx - 1
271
+ case type
272
+ when NilClass
273
+ # Rien à faire
274
+ when Symbol
275
+ cols[real_idx] = send(type, cols[real_idx])
276
+ when Proc
277
+ cols[real_idx] = type.call(cols[real_idx])
278
+ else
279
+ cols[real_idx] = "#{cols[real_idx]} #{type}"
280
+ end
281
+ end
282
+ end
283
+
284
+ end
285
+
286
+ # Calc of the column width. Either defined by widthest value or
287
+ # header, either by max_widths option parameter.
288
+ #
289
+ def calc_column_widths
290
+ #
291
+ # Pour collecter les largeurs de colonnes
292
+ #
293
+ @column_widths = Array.new(column_count, 0)
294
+ #
295
+ # Pour savoir si les largeurs maximales sont définies et
296
+ # prendre les valeurs.
297
+ # @rappel : les largeurs peuvent être définies par un integer
298
+ # un Hash ou un Array.
299
+ #
300
+ maxes = case max_widths
301
+ when Integer
302
+ Array.new(column_count, max_widths)
303
+ when Hash
304
+ cw = Array.new(column_count, nil)
305
+ max_widths.each do |idx_col, val|
306
+ cw[idx_col - 1] = val
307
+ end
308
+ cw
309
+ when Array
310
+ max_widths # must be all defined
311
+ else
312
+ Array.new(column_count, nil)
313
+ end
314
+ #
315
+ # Les largeurs de colonne seraient-elles déjà toutes définies ?
316
+ #
317
+ zero_found = false
318
+ maxes.each do |width|
319
+ zero_found = true and break if width == 0 || width.nil?
320
+ end
321
+ unless zero_found
322
+ @column_widths = maxes
323
+ return # fini
324
+ end
325
+
326
+ #
327
+ # Boucles sur chaque ligne
328
+ #
329
+ (@header_lines + @lines).each do |cols|
330
+ next unless cols.is_a?(Array)
331
+ cols.each_with_index do |col, col_idx|
332
+ if maxes[col_idx]
333
+ #
334
+ # Si la largeur max est définie pour cette colonne
335
+ # @note
336
+ # Pour le moment, sera répété pour chaque ligne…
337
+ #
338
+ @column_widths[col_idx] = max_widths[col_idx]
339
+ else
340
+ #
341
+ # S'il faut prendre la plus large colonne
342
+ #
343
+ len = col.to_s.length
344
+ @column_widths[col_idx] = len if len > @column_widths[col_idx]
345
+ end
346
+ end
347
+ end
348
+ end
349
+
350
+ def make_totaux_line
351
+ totaux_line = Array.new(column_count, '')
352
+ max_index = colonnes_totaux.keys.min
353
+ totaux_line[max_index - 2] = 'TOTAUX' unless max_index == 1
354
+ colonnes_totaux.each do |idx, type|
355
+ totaux_line[idx - 1] = 0
356
+ end
357
+ @lines.each do |cols|
358
+ colonnes_totaux.each do |idx, type|
359
+ real_idx = idx - 1
360
+ # puts "totaux_line[real_idx] = #{totaux_line[real_idx].inspect}"
361
+ # puts "cols[real_idx] = #{cols[real_idx].inspect}:#{cols[real_idx].class}"
362
+ totaux_line[real_idx] += cols[real_idx].to_i
363
+ end
364
+ end
365
+ add(:separation)
366
+ add(totaux_line)
367
+ end
368
+ end #/class Table
369
+ end #/module Clir
@@ -0,0 +1,42 @@
1
+ =begin
2
+
3
+ Console method
4
+
5
+ =end
6
+
7
+ # To wach (empty) the console
8
+ def clear
9
+ # puts "\n" # pour certaines méthodes
10
+ STDOUT.write "\n\033c"
11
+ end
12
+
13
+ # Write +str+ at column +column+ and line +line+
14
+ def write_at(str, line, column)
15
+ msg = "\e[#{line};#{column}H#{str}"
16
+ if test?
17
+ puts msg
18
+ else
19
+ STDOUT.write msg
20
+ end
21
+ end
22
+
23
+
24
+ # @return column count of the console
25
+ def console_width
26
+ `tput cols`.strip.to_i
27
+ end
28
+
29
+ # @return lines count of the console
30
+ def console_height
31
+ `tput lines`.strip.to_i
32
+ end
33
+
34
+ # Use 'less' command to display +texte+
35
+ def less(texte)
36
+ if debug?
37
+ puts texte
38
+ else
39
+ exec "echo \"#{texte.gsub(/\"/,'\\"')}\" | less -r"
40
+ end
41
+ end
42
+
@@ -0,0 +1,68 @@
1
+ # --- Helpers pour console ---
2
+
3
+ ##
4
+ # Pour "labeliser" une table de label <> valeur
5
+ #
6
+ # @param ary {Array}
7
+ # Liste des valeurs, sous la forme
8
+ # [
9
+ # ['<label>', '<valeur>'[ :couleur|options]],
10
+ # ['<label>', '<valeur>'[ :couleur|options]],
11
+ # etc,
12
+ # ]
13
+ # Produira :
14
+ # label valeur
15
+ # label valeur
16
+ # etc.
17
+ #
18
+ # Le 3e paramètre de chaque item peut définir SOIT la couleur
19
+ # comme un symbole (par exemple :vert), SOIT une table qui
20
+ # peut contenir :
21
+ # color: La couleur à appliquer
22
+ # detail: Un texte à ajouter après la valeur (en gris)
23
+ #
24
+ # @param params {Hash|Nil}
25
+ #
26
+ # Paramètres définissant la table à obtenir avec :
27
+ #
28
+ # :indent Identation avant le label (soit un nombre
29
+ # d'espace soit l'indentation elle-même)
30
+ # :gutter Taille (en espace) de la gouttière entre les
31
+ # libellés et les valeurs (4 par défaut)
32
+ # :label_width Pour forcer la taille des libellés. Sinon,
33
+ # elle sera calculée d'après le plus grand
34
+ # label.
35
+ def labelize(ary, params = nil)
36
+ #
37
+ # On peut traiter une simple ligne ou un tableau
38
+ #
39
+ ary = [ary] unless ary.is_a?(Array)
40
+ #
41
+ # Les paramètres à appliquer
42
+ #
43
+ params ||= {}
44
+ params.key?(:gutter) || params.merge!(gutter: 4)
45
+ params.key?(:label_width) || begin
46
+ params.merge!(label_width: ary.max { |a,b| a[0].length <=> b[0].length }[0].length + params[:gutter] )
47
+ end
48
+ (params.key?(:indent) && params[:indent]) || params.merge!(indent:'')
49
+ params[:indent] = ' '*params[:indent] if params[:indent].is_a?(Integer)
50
+ #
51
+ # On construit la ligne ou chaque ligne du tableau
52
+ #
53
+ ary.collect do |lib, val, options|
54
+ params_line = params.dup
55
+ case options
56
+ when Symbol then params_line.merge!(color: options)
57
+ when Hash then params_line.merge!(options)
58
+ end
59
+ params[:indent] + label_value_line(lib, val, params_line)
60
+ end.join("\n")
61
+ end
62
+ def label_value_line(label, value, params = nil)
63
+ str = "#{label.to_s.ljust(params[:label_width])}#{value}"
64
+ str = str.send(params[:color]) unless params[:color].nil?
65
+ str = str + ' ' + params[:detail].gris if params[:detail]
66
+ return str
67
+ end
68
+
@@ -0,0 +1,48 @@
1
+
2
+
3
+ def verbose?
4
+ Clir::State.verbose?
5
+ end
6
+
7
+ def debug?
8
+ Clir::State.debug?
9
+ end
10
+
11
+ def test?
12
+ Clir::State.test?
13
+ end
14
+
15
+ def help?
16
+ Clir::State.help?
17
+ end
18
+
19
+ module Clir
20
+ class State
21
+ class << self
22
+
23
+ def verbose?
24
+ :TRUE == @__isverbose ||= true_or_false(CLI.options[:verbose] === true)
25
+ end
26
+
27
+ def debug?
28
+ :TRUE == @__debugon ||= true_or_false(CLI.options[:debug] === true)
29
+ end
30
+
31
+ def test?
32
+ :TRUE == @__teston ||= true_or_false((CLI.options[:test]||CLI.options[:tests]) === true || ENV['CLI_TEST'] == 'true' || ENV['CLI_TESTS'] == 'true' || File.exist?(CLI::MARKER_TESTS_FILE))
33
+ end
34
+
35
+ def help?
36
+ :TRUE == @__helpon ||= true_or_false(CLI.options[:help] === true || ['help','aide'].include?(CLI.main_command))
37
+ end
38
+
39
+ def reset
40
+ @__isverbose = nil
41
+ @__debugon = nil
42
+ @__teston = nil
43
+ @__helpon = nil
44
+ end
45
+
46
+ end #/<< self
47
+ end #/class State
48
+ end #/module Clir
@@ -0,0 +1,57 @@
1
+ # require 'fileutils'
2
+
3
+ ##
4
+ # Require all ruby file deep in folder +dossier+
5
+ #
6
+ def require_folder(dossier)
7
+ Dir["#{dossier}/**/*.rb"].each{|m|require(m)}
8
+ end
9
+
10
+ ##
11
+ # Like 'mkdir -p' command
12
+ #
13
+ def mkdir(pth)
14
+ FileUtils.mkdir_p(pth)
15
+ return pth
16
+ end
17
+ alias :mkdir_p :mkdir
18
+
19
+
20
+ ##
21
+ # To round a number
22
+ #
23
+ def round(n, decim = 2)
24
+ r = n.to_f.round(decim)
25
+ r.to_f == r.to_i ? r.to_i : r.to_f
26
+ end
27
+
28
+ ##
29
+ # To copy in the clipboard
30
+ #
31
+ def clip(ca, silent = false)
32
+ `printf "#{ca.gsub(/"/, '\\"').strip}" | pbcopy`
33
+ silent || puts("\n(“#{ca}“ copié dans le presse-papier)".gris)
34
+ end
35
+
36
+ ##
37
+ # To delete (unlink) folder of file if it exists
38
+ #
39
+ # @return TRUE if file existed (and have been deleted).
40
+ #
41
+ def delete_if_exist(pth)
42
+ if File.exist?(pth)
43
+ if File.directory?(pth)
44
+ FileUtils.rm_rf(pth)
45
+ else
46
+ File.delete(pth)
47
+ end
48
+ return not(File.exist?(pth))
49
+ else
50
+ false
51
+ end
52
+ end
53
+
54
+ def true_or_false(value)
55
+ value ? :TRUE : :FALSE
56
+ end
57
+
@@ -0,0 +1,50 @@
1
+
2
+
3
+
4
+ # @return [String] La valeur exprimée en euro
5
+ # @example
6
+ # €("12") # => '12 €'
7
+ # €("12", true) # => '12.00 €'
8
+ # €(12.5698) # => '12.57 €'
9
+ # @param [String|Integer|Float] Number to return as euro value
10
+ def €(value, with_cents = false)
11
+ if value.is_a?(Integer)
12
+ "#{value}#{".00" if with_cents} €"
13
+ elsif value.to_f.to_s != value.to_s
14
+ raise "Bad value. #{value.inspect} can't be converted to euros."
15
+ else
16
+ n, d = value.to_f.round(2).to_s.split('.')
17
+ d = d.ljust(2,'0')
18
+ "#{n}.#{d} €"
19
+ end
20
+ end
21
+
22
+ # @return [String] Value +value+ as pourcentage
23
+ #
24
+ # @example
25
+ # pcent(12) # => "12 %"
26
+ # pcent(12, 3) # => "12.000 %"
27
+ # pcent(12.436, 1) # => "12.4 %"
28
+ # pcent(12.436, true) # => "12.4 %"
29
+ # pcent(12.436, 2) # => "12.44 %"
30
+ #
31
+ # @param value [Integer|String|Float] The value to convert
32
+ # @param with_cents [TrueClass|Integer] Decimal value. If true,
33
+ # 1 decimal is used ("10.1 %")
34
+ #
35
+ def pcent(value, with_cents = nil)
36
+ if value.is_a?(Integer)
37
+ value = value.to_s
38
+ elsif value == value.to_i.to_s
39
+ value = value.to_i.to_s
40
+ elsif value.to_f.to_s != value.to_s
41
+ raise "Bad value. #{value.inspect}:#{value.class} can't be converted to pourcentage."
42
+ end
43
+ with_cents = 1 if with_cents == true
44
+ if with_cents
45
+ n, d = value.to_f.round(with_cents).to_s.split('.')
46
+ d = d.ljust(with_cents,'0')
47
+ value = "#{n}.#{d}"
48
+ end
49
+ "#{value} %"
50
+ end
@@ -0,0 +1,3 @@
1
+ module Clir
2
+ VERSION = "0.22.0"
3
+ end
data/lib/clir.rb ADDED
@@ -0,0 +1,36 @@
1
+ =begin
2
+
3
+ @usage
4
+
5
+ ~~~ruby
6
+ require 'clir'
7
+ CLI.init
8
+ ...
9
+ ~~~
10
+
11
+ =end
12
+ require 'fileutils'
13
+ require "clir/version"
14
+ require 'clir/String.ext'
15
+ require 'clir/CLI.mod'
16
+ require 'clir/Array.ext'
17
+ require 'clir/Integer.ext'
18
+ require 'clir/Symbol.ext'
19
+ require 'clir/File_extension'
20
+ require 'clir/CSV_extension'
21
+ require 'clir/Config.cls'
22
+ require 'clir/utils_methods'
23
+ require 'clir/utils_numbers'
24
+ require 'clir/Date_utils'
25
+ require 'clir/helpers_methods'
26
+ require 'clir/state_methods'
27
+ require 'clir/console_methods'
28
+ require 'clir/TTY-Prompt.cls'
29
+ require 'clir/Replayer.cls'
30
+ require 'clir/Table'
31
+ require 'clir/Labelizor'
32
+
33
+ module Clir
34
+ class Error < StandardError; end
35
+ # Your code goes here...
36
+ end