clir 0.22.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.
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