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
data/lib/clir/CLI.mod.rb
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
A command line is composed by:
|
4
|
+
|
5
|
+
- app command (for example: 'rake')
|
6
|
+
- main command (main_command): firts element not leading with '-'
|
7
|
+
dans without '=' (for example: 'build' in 'rake build')
|
8
|
+
- options: elements that lead with '-' or '--'
|
9
|
+
- params : elments that contain '=' (key=value pairs)
|
10
|
+
- components: all other elements
|
11
|
+
|
12
|
+
We can get this elments with:
|
13
|
+
|
14
|
+
CLI.main_command
|
15
|
+
CLI.options[:<key>]
|
16
|
+
CLI.option(:key)
|
17
|
+
CLI.params[:<key>]
|
18
|
+
CLI.components[<index>]
|
19
|
+
|
20
|
+
App can define its own short options (to long options) with:
|
21
|
+
|
22
|
+
CLI.set_options_table({short: long, short: long...})
|
23
|
+
|
24
|
+
Note : il faut obligatoirement mettre la version courte (souvent
|
25
|
+
une seule lettre) en Symbol :
|
26
|
+
CLI.set_options_table({e: :edition})
|
27
|
+
|
28
|
+
|
29
|
+
=end
|
30
|
+
module CLI
|
31
|
+
MARKER_TESTS_FILE = File.expand_path(File.join('.','.MARKER_TESTS'))
|
32
|
+
class << self
|
33
|
+
|
34
|
+
##
|
35
|
+
# First class method
|
36
|
+
# (call it at start-up)
|
37
|
+
#
|
38
|
+
def init
|
39
|
+
parse(ARGV)
|
40
|
+
Q.init if Q.respond_to?(:init)
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# @return command line options
|
45
|
+
# @options is set while parse method. If it is not set, we
|
46
|
+
# CLI.init first.
|
47
|
+
#
|
48
|
+
def options
|
49
|
+
defined?(@options) || self.init
|
50
|
+
@options
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return option of key +key+
|
54
|
+
def option(key)
|
55
|
+
options[key.to_sym]
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# @main command (after command name)
|
60
|
+
def main_command
|
61
|
+
defined?(@main_command) || self.init
|
62
|
+
@main_command
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# @return command line parameters
|
67
|
+
#
|
68
|
+
def params
|
69
|
+
defined?(@params) || self.init
|
70
|
+
@params
|
71
|
+
end
|
72
|
+
|
73
|
+
def param(key)
|
74
|
+
@params[key.to_sym]
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# @return command line components
|
79
|
+
# 'components' are elements of command line that are not options
|
80
|
+
# (with leading '-'), that are not parameters (key=value paire) and
|
81
|
+
# that are not main_command
|
82
|
+
#
|
83
|
+
def components
|
84
|
+
defined?(@components) || self.init
|
85
|
+
@components
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
##
|
90
|
+
# Command name
|
91
|
+
#
|
92
|
+
# Don't confuse with 'main command' which is the very first
|
93
|
+
# argument in command line
|
94
|
+
#
|
95
|
+
def command_name
|
96
|
+
@command_name ||= begin
|
97
|
+
File.basename($PROGRAM_NAME,File.extname($PROGRAM_NAME))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Main method which parse command line to get:
|
103
|
+
# - main command
|
104
|
+
# - options (leadings with -/--)
|
105
|
+
# - parameters (key=value pairs)
|
106
|
+
#
|
107
|
+
def parse(argv)
|
108
|
+
argv = argv.split(' ') if argv.is_a?(String)
|
109
|
+
if replay_it?(argv)
|
110
|
+
#
|
111
|
+
# Replay last command (if unable)
|
112
|
+
#
|
113
|
+
puts "Je dois apprendre à replayer la commande précédente".jaune
|
114
|
+
puts "Pour ça, je dois enregistrer les inputs précédents.".jaune
|
115
|
+
else
|
116
|
+
#
|
117
|
+
# Regular run
|
118
|
+
#
|
119
|
+
reset
|
120
|
+
@raw_command_line = ([command_name]+argv).join(' ')
|
121
|
+
argv.each do |arg|
|
122
|
+
if arg.start_with?('--')
|
123
|
+
arg, val = key_and_value_in(arg[2..-1])
|
124
|
+
@options.merge!(arg.to_sym => val)
|
125
|
+
elsif arg.start_with?('-')
|
126
|
+
arg, val = key_and_value_in(arg[1..-1])
|
127
|
+
arg = long_option_for(arg)
|
128
|
+
@options.merge!(arg.to_sym => val)
|
129
|
+
elsif arg.match?('.=.')
|
130
|
+
key, val = key_and_value_in(arg)
|
131
|
+
@params.merge!(key.to_sym => val)
|
132
|
+
elsif @main_command.nil?
|
133
|
+
@main_command = arg
|
134
|
+
else
|
135
|
+
@components << arg
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def set_options_table(table)
|
142
|
+
@_app_options_table = table
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# For Replayer, return data
|
147
|
+
def get_command_line_data
|
148
|
+
{
|
149
|
+
raw_command_line: @raw_command_line,
|
150
|
+
command_name: command_name,
|
151
|
+
main_command: main_command,
|
152
|
+
components: components,
|
153
|
+
options: options,
|
154
|
+
params: params,
|
155
|
+
table_short2long_options: table_short2long_options
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
def set_command_line_data(data)
|
160
|
+
data.each do |k, v| instance_variable_set("@#{k}", v) end
|
161
|
+
end
|
162
|
+
|
163
|
+
# --- Tests Methods ---
|
164
|
+
|
165
|
+
def set_tests_on_with_marker
|
166
|
+
File.write(MARKER_TESTS_FILE, "#{Time.now}")
|
167
|
+
end
|
168
|
+
|
169
|
+
def unset_tests_on_with_marker
|
170
|
+
if File.exist?(MARKER_TESTS_FILE)
|
171
|
+
File.delete(MARKER_TESTS_FILE)
|
172
|
+
else
|
173
|
+
puts "Weirdly, the test marker file (CLI::MARKER_TESTS_FILE) doesn't exist…".rouge
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def reset
|
181
|
+
@main_command = nil
|
182
|
+
@components = []
|
183
|
+
@options = {}
|
184
|
+
@params = {}
|
185
|
+
Clir::State.reset
|
186
|
+
CLI::Replayer.init_for_recording
|
187
|
+
@table_short2long_options = nil
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# @return the long option for +short+
|
192
|
+
#
|
193
|
+
def long_option_for(short)
|
194
|
+
short = short.to_sym
|
195
|
+
if table_short2long_options.key?(short)
|
196
|
+
table_short2long_options[short]
|
197
|
+
else
|
198
|
+
short
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# @return conversion table from short option (pe 'v') to
|
204
|
+
# long option (pe 'verbose').
|
205
|
+
# App can define its own table in CLI.set_options_table
|
206
|
+
def table_short2long_options
|
207
|
+
@table_short2long_options ||= begin
|
208
|
+
{
|
209
|
+
:h => :help,
|
210
|
+
:q => :quiet,
|
211
|
+
:v => :verbose,
|
212
|
+
:x => :debug,
|
213
|
+
}.merge(app_options_table)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# @return the app table of options conversions
|
219
|
+
def app_options_table
|
220
|
+
@_app_options_table ||= {}
|
221
|
+
end
|
222
|
+
|
223
|
+
##
|
224
|
+
# @return [key, value] in +foo+ if +foo+ contains '='
|
225
|
+
# [foo, default] otherwise
|
226
|
+
def key_and_value_in(foo, default = true)
|
227
|
+
foo.match?('=') ? foo.split('=') : [foo, default]
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# @return TRUE if replay character is used and only
|
232
|
+
# replay character
|
233
|
+
def replay_it?(argv)
|
234
|
+
argv.count == 1 && argv[0] == ::Config[:replay_character]
|
235
|
+
end
|
236
|
+
end #/<< self CLI
|
237
|
+
end #/module CLI
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'csv'
|
2
|
+
class CSV
|
3
|
+
class << self
|
4
|
+
|
5
|
+
# Pour pouvoir tester l'entête dans les tests
|
6
|
+
attr_reader :headers_for_test
|
7
|
+
|
8
|
+
# Lecture d'un fichier CSV à partir de la fin
|
9
|
+
#
|
10
|
+
# Les arguments sont les mêmes que pour readlines
|
11
|
+
#
|
12
|
+
# Pour rappel :
|
13
|
+
# +options+ peut contenir
|
14
|
+
# :headers À true, on tient compte des entêtes et on
|
15
|
+
# retourne des CSV::Row. Sinon, on retourne une
|
16
|
+
# Array simple.
|
17
|
+
# :headers_converters
|
18
|
+
# Convertisseur pour les noms des colonnes.
|
19
|
+
# :downcase ou :symbol
|
20
|
+
# :converters
|
21
|
+
# Liste de convertisseurs pour les données.
|
22
|
+
# Principalement :numeric, :date
|
23
|
+
# :col_sep
|
24
|
+
# Séparateur de colonne. Par défaut une virgule.
|
25
|
+
#
|
26
|
+
def readlines_backward(path, **options, &block)
|
27
|
+
|
28
|
+
options ||= {}
|
29
|
+
|
30
|
+
file = File.new(path)
|
31
|
+
size = File.size(file)
|
32
|
+
|
33
|
+
if size < ( 1 << 16 )
|
34
|
+
return readlines_backward_in_small_file(path, **options, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Si options[:headers] est true, il faut récupérer la première
|
39
|
+
# ligne et la transformer en entête
|
40
|
+
#
|
41
|
+
if options[:headers]
|
42
|
+
begin
|
43
|
+
fileh = File.open(path,'r')
|
44
|
+
header = fileh.gets.chomp
|
45
|
+
ensure
|
46
|
+
fileh.close
|
47
|
+
end
|
48
|
+
table = CSV.parse(header, **options)
|
49
|
+
headers = table.headers
|
50
|
+
@headers_for_test = headers # pour les tests
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
if block_given?
|
55
|
+
#
|
56
|
+
# Les options pour CSV.parse
|
57
|
+
# On garde seulement les convertisseurs de données et on met
|
58
|
+
# toujours headers à false (puisque c'est seulement la ligne
|
59
|
+
# de données qui sera parser)
|
60
|
+
#
|
61
|
+
# line_csv_options = {headers:false, converters: options[:converters]}
|
62
|
+
line_csv_options = options
|
63
|
+
#
|
64
|
+
# Avec un bloc fourni, on va lire ligne par ligne en partant
|
65
|
+
# de la fin.
|
66
|
+
#
|
67
|
+
buffer_size = 10000 # disons que c'est la longueur maximale d'une ligne
|
68
|
+
|
69
|
+
#
|
70
|
+
# On se positionne de la fin - la longueur du tampo dans le
|
71
|
+
# fichier à lire
|
72
|
+
#
|
73
|
+
file.seek(-buffer_size, IO::SEEK_END)
|
74
|
+
#
|
75
|
+
# Le tampon courant (il contient tout jusqu'à ce qu'il y ait
|
76
|
+
# au moins une ligne)
|
77
|
+
#
|
78
|
+
buffer = ""
|
79
|
+
#
|
80
|
+
# On boucle tant qu'on n'interrompt pas (pour le moment)
|
81
|
+
#
|
82
|
+
# QUESTIONS
|
83
|
+
# 1. Comment repère-t-on la fin ? En comparant la position
|
84
|
+
# actuelle du pointeur avec 0 ?
|
85
|
+
# 2. Comment met-on fin à la recherche (c'est presque la
|
86
|
+
# même question)
|
87
|
+
while true
|
88
|
+
#
|
89
|
+
# On lit la longueur du tampon en l'ajoutant à ce qu'on a
|
90
|
+
# déjà lu ou ce qui reste.
|
91
|
+
# Celui pourra contenir une ou plusieurs lignes, la première
|
92
|
+
# pourra être tronquée
|
93
|
+
#
|
94
|
+
buffer = file.read(buffer_size) + buffer
|
95
|
+
#
|
96
|
+
# Nombre de lignes
|
97
|
+
# (utile ?)
|
98
|
+
nombre_lignes = buffer.count("\n")
|
99
|
+
#
|
100
|
+
# On traite les lignes du buffer (en gardant ce qui dépasse)
|
101
|
+
#
|
102
|
+
if nombre_lignes > 0
|
103
|
+
# puts "Position dans le fichier : #{file.pos}".bleu
|
104
|
+
# puts "Nombre de lignes : #{nombre_lignes}".bleu
|
105
|
+
lines = buffer.split("\n").reverse
|
106
|
+
#
|
107
|
+
# On laisse la dernière ligne, certainement incomplète, dans
|
108
|
+
# le tampon. Elle sera ajoutée à la fin de ce qui précède
|
109
|
+
#
|
110
|
+
buffer = lines.pop
|
111
|
+
#
|
112
|
+
# Boucle sur les lignes
|
113
|
+
#
|
114
|
+
# @note : un break, dans le &block, interrompra la boucle
|
115
|
+
#
|
116
|
+
lines.each do |line|
|
117
|
+
line = line.chomp
|
118
|
+
line = "#{header}\n#{line}\n" if options[:headers]
|
119
|
+
# puts "line parsée : #{line.inspect}".bleu
|
120
|
+
line_csv = CSV.parse(line, **line_csv_options)
|
121
|
+
# puts "line_csv: #{line_csv.inspect}::#{line_csv.class}".orange
|
122
|
+
yield line_csv[0]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
#
|
126
|
+
# On remonte de deux fois la longueur du tampon. Une fois pour
|
127
|
+
# revenir au point de départ, une fois pour remonter à la
|
128
|
+
# portion suivante, à partir de la position courante, évide-
|
129
|
+
# ment
|
130
|
+
#
|
131
|
+
new_pos = file.pos - 2 * buffer_size
|
132
|
+
if new_pos < 0
|
133
|
+
file.seek(new_pos)
|
134
|
+
else
|
135
|
+
file.seek(- 2 * buffer_size, IO::SEEK_CUR)
|
136
|
+
end
|
137
|
+
#
|
138
|
+
# Si on se trouve à 0, on doit s'arrêter
|
139
|
+
#
|
140
|
+
break if file.pos <= 0
|
141
|
+
# puts "Nouvelle position dans le fichier : #{file.pos}".bleu
|
142
|
+
end
|
143
|
+
else
|
144
|
+
#
|
145
|
+
# Sans bloc fourni, on renvoie tout le code du fichier
|
146
|
+
#
|
147
|
+
# À vos risques et périls
|
148
|
+
# self.readlines(path, **options).to_a.reverse
|
149
|
+
self.foreach(path, **options).reverse
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
alias :readlines_backwards :readlines_backward
|
154
|
+
alias :foreach_backward :readlines_backward
|
155
|
+
alias :foreach_backwards :readlines_backward
|
156
|
+
|
157
|
+
# Lecture à l'envers dans un petit fichier
|
158
|
+
#
|
159
|
+
def readlines_backward_in_small_file(path, **options, &block)
|
160
|
+
if block_given?
|
161
|
+
self.foreach(path, **options).reverse_each do |row|
|
162
|
+
yield row
|
163
|
+
end
|
164
|
+
# liste2reverse = []
|
165
|
+
# self.readlines(path, **options).each { |row| liste2reverse << row }
|
166
|
+
# liste2reverse.reverse.each do |row|
|
167
|
+
# yield row
|
168
|
+
# end
|
169
|
+
else
|
170
|
+
# Lecture toute simple de la table
|
171
|
+
# return self.readlines(path, **options).to_a.reverse
|
172
|
+
return self.foreach(path, **options).reverse
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end #/<< self class CSV
|
177
|
+
end #/class CSV
|
@@ -0,0 +1,25 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
Pour le moment, on met la configuration comme ça, en dur, mais
|
4
|
+
plus tard on pourra la modifier pour chaque application.
|
5
|
+
|
6
|
+
=end
|
7
|
+
module Clir
|
8
|
+
class Configuration
|
9
|
+
|
10
|
+
def [](key)
|
11
|
+
data[key]
|
12
|
+
end
|
13
|
+
|
14
|
+
# TODO
|
15
|
+
def data
|
16
|
+
{
|
17
|
+
replay_character: '_'
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
end #/class Configuration
|
22
|
+
end #/module CLIR
|
23
|
+
|
24
|
+
# Expose outside
|
25
|
+
Config = Clir::Configuration.new
|
@@ -0,0 +1,161 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
Usefull methods for date & time
|
4
|
+
|
5
|
+
@author: Philippe Perret <philippe.perret@yahoo.fr>
|
6
|
+
|
7
|
+
=end
|
8
|
+
require 'date'
|
9
|
+
|
10
|
+
MOIS = {
|
11
|
+
1 => {court: 'jan', long: 'janvier'},
|
12
|
+
2 => {court: 'fév', long: 'février'},
|
13
|
+
3 => {court: 'mars', long: 'mars'},
|
14
|
+
4 => {court: 'avr', long: 'avril'},
|
15
|
+
5 => {court: 'mai', long: 'mai'},
|
16
|
+
6 => {court: 'juin', long: 'juin'},
|
17
|
+
7 => {court: 'juil', long: 'juillet'},
|
18
|
+
8 => {court: 'aout', long: 'aout'},
|
19
|
+
9 => {court: 'sept', long: 'septembre'},
|
20
|
+
10 => {court: 'oct', long: 'octobre'},
|
21
|
+
11 => {court: 'nov', long: 'novembre'},
|
22
|
+
12 => {court: 'déc', long: 'décembre'}
|
23
|
+
}
|
24
|
+
|
25
|
+
DAYNAMES = [
|
26
|
+
'Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'
|
27
|
+
]
|
28
|
+
|
29
|
+
|
30
|
+
# @return [String] Une date formatée avec le moins verbal
|
31
|
+
#
|
32
|
+
# @param [Time|Date|NIl] La date. Si non fournie, on prend maintenant
|
33
|
+
# @param [Hash] options Les options de formatage
|
34
|
+
# @option lenght [Symbol] :court (pour le mois court)
|
35
|
+
def human_date(ladate = nil, **options)
|
36
|
+
ladate ||= Time.now
|
37
|
+
options.key?(:length) || options.merge!(length: :long)
|
38
|
+
lemois = MOIS[ladate.month][options[:length]]
|
39
|
+
lemois = "#{lemois}." if options[:length] == :court
|
40
|
+
"#{ladate.day} #{lemois} #{ladate.year}"
|
41
|
+
end
|
42
|
+
alias :date_humaine :human_date
|
43
|
+
|
44
|
+
|
45
|
+
# @return A date for a file, now
|
46
|
+
# @example
|
47
|
+
# date_for_file # => "2022-12-14"
|
48
|
+
# date_for_file(nil, true) # => "2022-12-14-23-11"
|
49
|
+
def date_for_file(time = nil, with_hour = false, del = '-')
|
50
|
+
time ||= Time.now
|
51
|
+
fmt = "%Y#{del}%m#{del}%d"
|
52
|
+
fmt = "#{fmt}#{del}%H#{del}%M" if with_hour
|
53
|
+
time.strftime(fmt)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @reçoit une date et la retourne sous la forme "YYYY-MM-DD"
|
57
|
+
def ymd(time = nil, delimitor = '-')
|
58
|
+
time ||= Time.now
|
59
|
+
time.strftime("%Y#{delimitor}%m#{delimitor}%d")
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# @return Date corresponding to +foo+
|
64
|
+
# @param [String|Integer|Time|Date] foo
|
65
|
+
# - [Time] Return itself
|
66
|
+
# - [Date] Return itself.to_time
|
67
|
+
# - [String] JJ/MM/AAAA or 'YYYY/MM/DD'
|
68
|
+
# - [Integer] Number of seconds
|
69
|
+
# @note
|
70
|
+
# Alias :time_from (more semanticaly correct)
|
71
|
+
#
|
72
|
+
def date_from(foo)
|
73
|
+
case foo
|
74
|
+
when Time then foo
|
75
|
+
when Date then foo.to_time
|
76
|
+
when Integer then Time.at(foo)
|
77
|
+
when String
|
78
|
+
a, b, c = foo.split('/')
|
79
|
+
if c.length == 4
|
80
|
+
Time.new(c.to_i, b.to_i, a.to_i)
|
81
|
+
else
|
82
|
+
Time.new(a.to_i, b.to_i, c.to_i)
|
83
|
+
end
|
84
|
+
else
|
85
|
+
raise "Unable to transform #{foo.inspect} to Time."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
alias :time_from :date_from
|
89
|
+
|
90
|
+
##
|
91
|
+
# Formate de date as JJ MM AAAA (or MM JJ AAAA in english)
|
92
|
+
# @param {Time} date
|
93
|
+
# @param [Hash] options table:
|
94
|
+
# @option options [Boolean] :verbal If true, the format will be "month the day-th etc."
|
95
|
+
# @option options [Boolean] :no_time If true, only day, without time
|
96
|
+
# @option options [Boolean] :seconds If true, add seconds with time
|
97
|
+
# @option options [Boolean] :update_format If true, the format is updated. Otherwise, the last format is used for all next date
|
98
|
+
# @option options [Boolean] :sentence If true, on met "le ... à ...."
|
99
|
+
#
|
100
|
+
def formate_date(date, options = nil)
|
101
|
+
options ||= {}
|
102
|
+
@last_format = nil if options[:update_format] || options[:template]
|
103
|
+
@last_format ||= begin
|
104
|
+
as_verbal = options[:verbal]||options[:sentence]
|
105
|
+
if options[:template]
|
106
|
+
options[:template]
|
107
|
+
else
|
108
|
+
fmt = []
|
109
|
+
fmt << 'le ' if options[:sentence]
|
110
|
+
if as_verbal
|
111
|
+
forday = date.day == 1 ? '1er' : '%-d'
|
112
|
+
fmt << "#{forday} #{MOIS[date.month][:long]} %Y"
|
113
|
+
else
|
114
|
+
fmt << '%d %m %Y'
|
115
|
+
end
|
116
|
+
delh = options[:sentence] ? 'à' : '-'
|
117
|
+
unless options[:no_time]
|
118
|
+
fmt << (as_verbal ? " à %H h %M" : " #{delh} %H:%M")
|
119
|
+
end
|
120
|
+
if options[:seconds]
|
121
|
+
fmt << (as_verbal ? ' mn et %S s' : ':%S' )
|
122
|
+
end
|
123
|
+
fmt.join('')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
date.strftime(@last_format)
|
127
|
+
end
|
128
|
+
|
129
|
+
class Time
|
130
|
+
|
131
|
+
# @return [String] French date with separator
|
132
|
+
# @example
|
133
|
+
# Time.now.jj_mm_yyyy # => "28/12/2022"
|
134
|
+
# @param separator [String] Separator to use (default: '/')
|
135
|
+
def jj_mm_aaaa(separator = '/')
|
136
|
+
self.strftime(['%d','%m','%Y'].join(separator))
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [String] English date with separator
|
140
|
+
# @example
|
141
|
+
# Time.now.mm_dd_yyyy # => "12/28/2022"
|
142
|
+
#
|
143
|
+
# @param separator [String] Separator to use (default: '/')
|
144
|
+
def mm_dd_yyyy(separator = '/')
|
145
|
+
self.strftime(['%m','%d','%Y'].join(separator))
|
146
|
+
end
|
147
|
+
|
148
|
+
end #/class Time
|
149
|
+
|
150
|
+
class Integer
|
151
|
+
|
152
|
+
def ago
|
153
|
+
(Time.now - self).mm_dd_yyyy
|
154
|
+
end
|
155
|
+
|
156
|
+
end #/class Integer
|
157
|
+
|
158
|
+
def ilya(laps, options = nil)
|
159
|
+
options ||= {}
|
160
|
+
(Time.now - laps).jj_mm_aaaa(options[:separator] || '/')
|
161
|
+
end
|