immosquare-yaml 0.1.28 → 1.0.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.
- checksums.yaml +4 -4
- data/lib/immosquare-yaml/version.rb +1 -1
- data/lib/immosquare-yaml.rb +195 -637
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3a23201af8649660878ead589649938df652d4c3b4eccd8b5d2463643e7d0b79
|
|
4
|
+
data.tar.gz: 4ee0383267d4a9f5e718d0189395f043a67d663abb43409f5b1f35782e76768e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c349896c32e18ce5fe66b8c00cc33f7e55828ddc48a4dd4e69977ea1cad46940c06186ac043574b514ed8d36357233f7442a4749c2215bca7e8c80878883a4c7
|
|
7
|
+
data.tar.gz: 2c0a872feb19356fca0895f00c1bc49eeba7bb6a40dab31e9710fd3a6b062d4e35b2cb0bee7b8953cc86bc0dcdab68bd77feb7eba9435b514d11a62ff030c5fe
|
data/lib/immosquare-yaml.rb
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
require "English"
|
|
2
1
|
require "psych"
|
|
3
|
-
require "date"
|
|
4
2
|
require "fileutils"
|
|
5
3
|
require "immosquare-extensions"
|
|
6
4
|
require_relative "immosquare-yaml/configuration"
|
|
@@ -8,19 +6,27 @@ require_relative "immosquare-yaml/shared_methods"
|
|
|
8
6
|
require_relative "immosquare-yaml/railtie" if defined?(Rails)
|
|
9
7
|
|
|
10
8
|
##============================================================##
|
|
11
|
-
##
|
|
12
|
-
##
|
|
13
|
-
##
|
|
14
|
-
##
|
|
9
|
+
## ImmosquareYaml — post-processeur Psych dédié aux fichiers
|
|
10
|
+
## de traduction (locales Rails).
|
|
11
|
+
##
|
|
12
|
+
## Trois responsabilités :
|
|
13
|
+
## - parse(file) : YAML → Hash, en s'appuyant sur l'AST Psych
|
|
14
|
+
## - dump(hash) : Hash → YAML formaté (quotes minimales,
|
|
15
|
+
## blocs littéraux, emojis décodés)
|
|
16
|
+
## - clean(file) : parse + tri par clé + dump → écrit
|
|
17
|
+
##
|
|
18
|
+
## La gem résout cinq problèmes que Psych seul ne traite pas :
|
|
19
|
+
## 1. Norway problem (yes/no/on/off lus comme String)
|
|
20
|
+
## 2. Tri déterministe par clé
|
|
21
|
+
## 3. Préservation des blocs littéraux (|, |-)
|
|
22
|
+
## 4. Quotes minimales pour la lisibilité
|
|
23
|
+
## 5. Décodage des escapes \U0001F600 → emoji
|
|
15
24
|
##============================================================##
|
|
16
25
|
module ImmosquareYaml
|
|
17
26
|
extend SharedMethods
|
|
18
27
|
|
|
19
28
|
class << self
|
|
20
29
|
|
|
21
|
-
##============================================================##
|
|
22
|
-
## Gem configuration
|
|
23
|
-
##============================================================##
|
|
24
30
|
attr_writer :configuration
|
|
25
31
|
|
|
26
32
|
def configuration
|
|
@@ -32,67 +38,27 @@ module ImmosquareYaml
|
|
|
32
38
|
end
|
|
33
39
|
|
|
34
40
|
##============================================================##
|
|
35
|
-
##
|
|
36
|
-
##
|
|
37
|
-
##
|
|
38
|
-
## to a YAML format.
|
|
39
|
-
##
|
|
40
|
-
## Params:
|
|
41
|
-
## +file_path+:: Path to the YAML file that needs to be cleaned.
|
|
42
|
-
## +options+:: A hash of options where :sort controls whether the output should be sorted (default is true).
|
|
43
|
-
##
|
|
44
|
-
## Returns:
|
|
45
|
-
## Boolean indicating the success (true) or failure (false) of the operation.
|
|
41
|
+
## clean(file_path, sort: true, output: file_path)
|
|
42
|
+
## Charge le fichier, le re-écrit propre et trié.
|
|
43
|
+
## Retourne true / false selon le succès.
|
|
46
44
|
##============================================================##
|
|
47
45
|
def clean(file_path, **options)
|
|
48
|
-
##============================================================##
|
|
49
|
-
## Default options
|
|
50
|
-
##============================================================##
|
|
51
46
|
options = {
|
|
52
47
|
:sort => true,
|
|
53
48
|
:output => file_path
|
|
54
49
|
}.merge(options)
|
|
55
50
|
|
|
56
51
|
begin
|
|
57
|
-
output_file_path = nil
|
|
58
52
|
raise("File not found") if !File.exist?(file_path)
|
|
59
53
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
##============================================================##
|
|
63
|
-
output_file_path = options[:output]
|
|
64
|
-
|
|
65
|
-
##============================================================##
|
|
66
|
-
## Backup original content for restoration after parsing if necessary
|
|
67
|
-
##============================================================##
|
|
68
|
-
original_content = File.read(file_path) if output_file_path != file_path
|
|
69
|
-
|
|
70
|
-
##============================================================##
|
|
71
|
-
## The cleaning procedure is initialized with a comprehensive clean, transforming
|
|
72
|
-
## the YAML content to a hash to facilitate optional sorting, before
|
|
73
|
-
## rewriting it to the YAML file in its cleaned and optionally sorted state.
|
|
74
|
-
##============================================================##
|
|
75
|
-
clean_yml(file_path)
|
|
76
|
-
parsed_yml = parse(file_path)
|
|
77
|
-
parsed_yml = parsed_yml.sort_by_key
|
|
78
|
-
parsed_yml = dump(parsed_yml)
|
|
79
|
-
|
|
80
|
-
##============================================================##
|
|
81
|
-
## Restore original content if necessary
|
|
82
|
-
##============================================================##
|
|
83
|
-
File.write(file_path, original_content) if output_file_path != file_path
|
|
54
|
+
parsed_yml = parse(file_path, :sort => options[:sort])
|
|
55
|
+
return false if parsed_yml == false
|
|
84
56
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
FileUtils.mkdir_p(File.dirname(output_file_path))
|
|
89
|
-
File.write(output_file_path, parsed_yml)
|
|
57
|
+
output = dump(parsed_yml)
|
|
58
|
+
FileUtils.mkdir_p(File.dirname(options[:output]))
|
|
59
|
+
File.write(options[:output], output)
|
|
90
60
|
true
|
|
91
61
|
rescue StandardError => e
|
|
92
|
-
##============================================================##
|
|
93
|
-
## Restore original content if necessary
|
|
94
|
-
##============================================================##
|
|
95
|
-
File.write(file_path, original_content) if output_file_path != file_path && !original_content.nil?
|
|
96
62
|
puts(e.message)
|
|
97
63
|
puts(e.backtrace)
|
|
98
64
|
false
|
|
@@ -100,55 +66,39 @@ module ImmosquareYaml
|
|
|
100
66
|
end
|
|
101
67
|
|
|
102
68
|
##============================================================##
|
|
103
|
-
##
|
|
104
|
-
##
|
|
105
|
-
##
|
|
106
|
-
## It operates under the assumption that the file is properly structured.
|
|
69
|
+
## parse(file_path, sort: true)
|
|
70
|
+
## Lit un fichier YAML et retourne un Hash Ruby.
|
|
71
|
+
## Hash trié par clé par défaut.
|
|
107
72
|
##
|
|
108
|
-
##
|
|
109
|
-
##
|
|
110
|
-
##
|
|
111
|
-
##
|
|
112
|
-
##
|
|
113
|
-
##
|
|
73
|
+
## Implémentation : on parcourt l'AST Psych plutôt que d'appeler
|
|
74
|
+
## Psych.load. Cela permet de :
|
|
75
|
+
## - distinguer un scalaire plain "yes" d'un bool true
|
|
76
|
+
## - garder les valeurs problématiques (Norway) en String
|
|
77
|
+
## - décoder nous-mêmes les escapes \U... pour les blocs
|
|
78
|
+
## littéraux qui ne sont pas désescapés par Psych
|
|
114
79
|
##============================================================##
|
|
115
80
|
def parse(file_path, **options)
|
|
116
81
|
options = {:sort => true}.merge(options)
|
|
117
82
|
|
|
118
83
|
begin
|
|
119
|
-
original_content = nil
|
|
120
84
|
raise("File not found") if !File.exist?(file_path)
|
|
121
85
|
|
|
122
86
|
##============================================================##
|
|
123
|
-
##
|
|
87
|
+
## Psych.parse_file retourne un Document. Si le fichier est
|
|
88
|
+
## vide ou ne contient que des commentaires, root est nil.
|
|
124
89
|
##============================================================##
|
|
125
|
-
|
|
90
|
+
doc = Psych.parse_file(file_path)
|
|
91
|
+
return {} if !doc || doc.root.nil?
|
|
126
92
|
|
|
127
|
-
|
|
128
|
-
## clean the file
|
|
129
|
-
##============================================================##
|
|
130
|
-
clean_yml(file_path)
|
|
131
|
-
|
|
132
|
-
##============================================================##
|
|
133
|
-
## parse the file & sort if necessary
|
|
134
|
-
##============================================================##
|
|
135
|
-
parsed_xml = parse_xml(file_path)
|
|
136
|
-
parsed_xml = parsed_xml.sort_by_key if options[:sort]
|
|
137
|
-
|
|
138
|
-
##============================================================##
|
|
139
|
-
## Restore original content
|
|
140
|
-
##============================================================##
|
|
141
|
-
File.write(file_path, original_content) if !original_content.nil?
|
|
93
|
+
result = node_to_value(doc.root, {})
|
|
142
94
|
|
|
143
95
|
##============================================================##
|
|
144
|
-
##
|
|
96
|
+
## On accepte tous les types racine (Hash, Array, scalaire),
|
|
97
|
+
## mais on ne trie que si la racine est un Hash.
|
|
145
98
|
##============================================================##
|
|
146
|
-
|
|
99
|
+
result = result.sort_by_key if options[:sort] && result.is_a?(Hash)
|
|
100
|
+
result
|
|
147
101
|
rescue StandardError => e
|
|
148
|
-
##============================================================##
|
|
149
|
-
## Restore original content
|
|
150
|
-
##============================================================##
|
|
151
|
-
File.write(file_path, original_content) if !original_content.nil?
|
|
152
102
|
puts(e.message)
|
|
153
103
|
puts(e.backtrace)
|
|
154
104
|
false
|
|
@@ -156,26 +106,28 @@ module ImmosquareYaml
|
|
|
156
106
|
end
|
|
157
107
|
|
|
158
108
|
##============================================================##
|
|
159
|
-
##
|
|
160
|
-
##
|
|
161
|
-
##
|
|
162
|
-
##
|
|
163
|
-
##
|
|
164
|
-
##
|
|
165
|
-
## Params:
|
|
166
|
-
## +hash+:: The input hash to be converted into a YAML representation.
|
|
167
|
-
## +lines+:: An array to hold the constructed lines (default is an empty array).
|
|
168
|
-
## +indent+:: The current indentation level (default is 0).
|
|
169
|
-
##
|
|
170
|
-
## Returns:
|
|
171
|
-
## A string representing the YAML representation of the input hash.
|
|
109
|
+
## dump(hash) → String YAML
|
|
110
|
+
## Sérialise un Hash en YAML avec nos règles de formatage :
|
|
111
|
+
## - clés "yes/no/on/..." re-quotées
|
|
112
|
+
## - valeurs plain quand c'est sûr, sinon doublequotées
|
|
113
|
+
## - chaînes multi-lignes en bloc littéral | ou |-
|
|
114
|
+
## - arrays imbriqués délégués à Psych.dump puis indentés
|
|
172
115
|
##============================================================##
|
|
173
|
-
def dump(hash
|
|
116
|
+
def dump(hash)
|
|
117
|
+
render_hash(hash, [], 0)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
##============================================================##
|
|
125
|
+
## Rendu récursif d'un Hash. Les paramètres lines et indent
|
|
126
|
+
## sont des accumulateurs internes — exposés dans la signature
|
|
127
|
+
## privée uniquement.
|
|
128
|
+
##============================================================##
|
|
129
|
+
def render_hash(hash, lines, indent)
|
|
174
130
|
hash.each do |key, value|
|
|
175
|
-
##============================================================##
|
|
176
|
-
## Preparing the key with the proper indentation before identifying
|
|
177
|
-
## the type of the value to handle it appropriately in the YAML representation.
|
|
178
|
-
##============================================================##
|
|
179
131
|
line = "#{SPACE * indent}#{clean_key(key)}:"
|
|
180
132
|
|
|
181
133
|
case value
|
|
@@ -184,10 +136,9 @@ module ImmosquareYaml
|
|
|
184
136
|
when String
|
|
185
137
|
if value.include?(NEWLINE) || value.include?('\n')
|
|
186
138
|
##============================================================##
|
|
187
|
-
##
|
|
188
|
-
##
|
|
189
|
-
##
|
|
190
|
-
## the default behavior)
|
|
139
|
+
## Bloc littéral. On ajoute "-" si la valeur ne se termine
|
|
140
|
+
## pas par un newline (chomp). Indent indicator si la valeur
|
|
141
|
+
## a des leading spaces sur ses lignes.
|
|
191
142
|
##============================================================##
|
|
192
143
|
line += "#{SPACE}|"
|
|
193
144
|
indent_level = value[/\A */].size
|
|
@@ -196,25 +147,21 @@ module ImmosquareYaml
|
|
|
196
147
|
lines << line
|
|
197
148
|
|
|
198
149
|
##============================================================##
|
|
199
|
-
##
|
|
200
|
-
##
|
|
150
|
+
## Décode les escapes \U0001F600 dans les blocs littéraux
|
|
151
|
+
## (Psych ne les désescape pas pour LITERAL/FOLDED).
|
|
201
152
|
##============================================================##
|
|
202
|
-
value =
|
|
203
|
-
|
|
153
|
+
value = decode_unicode_escapes(value)
|
|
204
154
|
|
|
205
|
-
##============================================================##
|
|
206
|
-
## We parse on the 2 types of line breaks
|
|
207
|
-
##============================================================##
|
|
208
155
|
value.split(/\\n|\n/).each do |subline|
|
|
209
156
|
lines << "#{SPACE * (indent + INDENT_SIZE)}#{subline}"
|
|
210
157
|
end
|
|
211
158
|
else
|
|
212
|
-
line += "#{SPACE}#{value}"
|
|
159
|
+
line += "#{SPACE}#{format_scalar_value(value)}"
|
|
213
160
|
lines << line
|
|
214
161
|
end
|
|
215
162
|
when Hash
|
|
216
163
|
lines << line
|
|
217
|
-
|
|
164
|
+
render_hash(value, lines, indent + INDENT_SIZE)
|
|
218
165
|
when Array
|
|
219
166
|
formated_value = Psych.dump(value)
|
|
220
167
|
if formated_value == "--- []\n"
|
|
@@ -226,568 +173,179 @@ module ImmosquareYaml
|
|
|
226
173
|
lines << line
|
|
227
174
|
lines << formated_value
|
|
228
175
|
end
|
|
176
|
+
else
|
|
177
|
+
##============================================================##
|
|
178
|
+
## Numbers, booleans, dates : laissés tels quels.
|
|
179
|
+
##============================================================##
|
|
180
|
+
line += "#{SPACE}#{value}"
|
|
181
|
+
lines << line
|
|
229
182
|
end
|
|
230
183
|
end
|
|
231
184
|
|
|
232
|
-
##============================================================##
|
|
233
|
-
## Finalizing the construction by adding a newline at the end and
|
|
234
|
-
## removing whitespace from empty lines.
|
|
235
|
-
##============================================================##
|
|
236
185
|
lines += [NOTHING]
|
|
237
186
|
lines = lines.map {|l| l.strip.empty? ? NOTHING : l }
|
|
238
187
|
lines.join("\n")
|
|
239
188
|
end
|
|
240
189
|
|
|
241
|
-
|
|
242
|
-
private
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
190
|
##============================================================##
|
|
247
|
-
##
|
|
191
|
+
## Walker AST : transforme un Psych::Nodes::* en valeur Ruby.
|
|
192
|
+
## Le hash anchors mémorise les ancres rencontrées pour
|
|
193
|
+
## résoudre les aliases.
|
|
248
194
|
##============================================================##
|
|
249
|
-
def
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
## First, we normalize the file by ensuring it always ends with an empty line
|
|
259
|
-
## This also allows us to get the total number of lines in the file,
|
|
260
|
-
## helping us to determine when we are processing the last line
|
|
261
|
-
##============================================================##
|
|
262
|
-
line_count = File.normalize_last_line(file_path)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
File.foreach(file_path) do |current_line|
|
|
266
|
-
last_line = line_index == line_count
|
|
267
|
-
|
|
268
|
-
##============================================================##
|
|
269
|
-
## Cleaning the current line by removing multiple spaces occurring after a non-space character
|
|
270
|
-
##============================================================##
|
|
271
|
-
current_line = current_line.to_s.gsub(/(?<=\S)\s+/, SPACE)
|
|
272
|
-
|
|
273
|
-
##============================================================##
|
|
274
|
-
## Trimming potential whitespace characters from the end of the line
|
|
275
|
-
##============================================================##
|
|
276
|
-
current_line = current_line.rstrip
|
|
277
|
-
|
|
278
|
-
##============================================================##
|
|
279
|
-
## Detecting blank lines to specially handle the last line within a block;
|
|
280
|
-
## if we are inside a block or it's the last line, we avoid skipping
|
|
281
|
-
##============================================================##
|
|
282
|
-
blank_line = current_line.gsub(NEWLINE, NOTHING).empty?
|
|
283
|
-
next if !(last_line || inblock || !blank_line)
|
|
284
|
-
|
|
285
|
-
##============================================================##
|
|
286
|
-
## Identifying the indentation level of the current line
|
|
287
|
-
##============================================================##
|
|
288
|
-
last_inblock = inblock
|
|
289
|
-
indent_level = current_line[/\A */].size
|
|
290
|
-
need_to_clean_prev_inblock = inblock == true && ((!blank_line && indent_level <= inblock_indent) || last_line)
|
|
291
|
-
need_to_clen_prev_weirdblock = weirdblock == true && (indent_level <= weirdblock_indent || last_line)
|
|
292
|
-
|
|
293
|
-
##============================================================##
|
|
294
|
-
## Handling the exit from a block:
|
|
295
|
-
## if we are exiting a block, we clean the entire block
|
|
296
|
-
##============================================================##
|
|
297
|
-
if need_to_clean_prev_inblock
|
|
298
|
-
inblock = false
|
|
299
|
-
##============================================================##
|
|
300
|
-
## Extracting the entire block by tracing back lines until we find a lesser indentation
|
|
301
|
-
## Subsequently determining the type of block we are in and clean accordingly
|
|
302
|
-
##============================================================##
|
|
303
|
-
i = -1
|
|
304
|
-
block_indent = lines[i][/\A */].size
|
|
305
|
-
block_lines = [lines[i].lstrip]
|
|
306
|
-
while lines[i][/\A */].size == lines[i - 1][/\A */].size
|
|
307
|
-
block_lines << lines[i - 1].lstrip
|
|
308
|
-
i -= 1
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
##============================================================##
|
|
312
|
-
## Handling different types of blocks (literal blocks "|",
|
|
313
|
-
## folded blocks ">", etc.)
|
|
314
|
-
## and applying the respective formatting strategies based on
|
|
315
|
-
## block type and additional indent specified
|
|
316
|
-
##
|
|
317
|
-
## | => Literal blocks: It keeps line breaks as
|
|
318
|
-
## that they are given in the text block.
|
|
319
|
-
## Final new line: A new line is added to the
|
|
320
|
-
## end of text.
|
|
321
|
-
## |- => Literal blocks: It keeps line breaks as
|
|
322
|
-
## that they are given in the text block.
|
|
323
|
-
## New final line: The final line break is deleted,
|
|
324
|
-
## unlike the option |
|
|
325
|
-
## > Folded blocks: It replaces each new line with a space,
|
|
326
|
-
## transforming the block of text into a single line.
|
|
327
|
-
## However, it preserves newlines that follow an empty line.
|
|
328
|
-
## Final new line: A new line is added at the end of the text.
|
|
329
|
-
## We can also have |4- or |4+ to say with indentation 4
|
|
330
|
-
##============================================================##
|
|
331
|
-
block_lines = block_lines.reverse
|
|
332
|
-
block_type = lines[i - 1].split(": ").last
|
|
333
|
-
indent_suppl = block_type.scan(/\d+/).first.to_i
|
|
334
|
-
indent_suppl = indent_suppl > 0 ? indent_suppl - INDENT_SIZE : 0
|
|
335
|
-
case block_type[0]
|
|
336
|
-
when ">"
|
|
337
|
-
lines[i - 1] = lines[i - 1].gsub(">", "|")
|
|
338
|
-
lines[i] = "#{SPACE * (block_indent + indent_suppl)}#{clean_value(block_lines.join(SPACE))}"
|
|
339
|
-
((i + 1)..-1).to_a.size.times { lines.pop }
|
|
340
|
-
else
|
|
341
|
-
split = clean_value(block_lines.join(NEWLINE), false).split(NEWLINE)
|
|
342
|
-
(i..-1).each do |ii|
|
|
343
|
-
lines[ii] = "#{SPACE * (block_indent + indent_suppl)}#{split.shift}"
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
##============================================================##
|
|
349
|
-
## Handling 'weirdblocks': cases where multi-line values are enclosed in quotes,
|
|
350
|
-
## which should actually be single-line values
|
|
351
|
-
## key: "
|
|
352
|
-
## line1
|
|
353
|
-
## line2
|
|
354
|
-
## line3"
|
|
355
|
-
## key: '
|
|
356
|
-
## line1
|
|
357
|
-
## line2
|
|
358
|
-
## line3'
|
|
359
|
-
##============================================================##
|
|
360
|
-
if need_to_clen_prev_weirdblock
|
|
361
|
-
weirdblock = false
|
|
362
|
-
key, value = lines[-1].split(":", 2)
|
|
363
|
-
lines[-1] = "#{key}: #{clean_value(value)}"
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
##============================================================##
|
|
367
|
-
## Handling keys without values: if the previous line ends with a colon (:) and is not
|
|
368
|
-
## followed by a value, we assign 'null' as the value
|
|
369
|
-
##============================================================##
|
|
370
|
-
if inblock == false && weirdblock == false && lines[-1] && lines[-1].end_with?(":") && last_inblock == false
|
|
371
|
-
prev_indent = lines[-1][/\A */].size
|
|
372
|
-
lines[-1] += " null" if prev_indent >= indent_level
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
##============================================================##
|
|
376
|
-
## Splitting the current line into key and value parts for further processing
|
|
377
|
-
## You have to split on ":" and not on ": " because we don't have a space when it's
|
|
378
|
-
## just a key.. but we have a newline
|
|
379
|
-
## fr: => ["fr", "\n"]
|
|
380
|
-
##============================================================##
|
|
381
|
-
split = inblock || weirdblock ? [current_line] : current_line.strip.split(":", 2)
|
|
382
|
-
key = inblock || weirdblock ? nil : split[0].to_s.strip
|
|
383
|
-
|
|
384
|
-
##============================================================##
|
|
385
|
-
## Line processing based on various conditions such as being inside a block,
|
|
386
|
-
## starting with a comment symbol (#), or being a part of a 'weirdblock'
|
|
387
|
-
## Each case has its specific line cleaning strategy
|
|
388
|
-
## If the line is commented out, we keep and we remove newlines
|
|
389
|
-
##============================================================##
|
|
390
|
-
if current_line.lstrip.start_with?("#")
|
|
391
|
-
lines << current_line.gsub(NEWLINE, NOTHING)
|
|
392
|
-
##============================================================##
|
|
393
|
-
## If is in a block (multiline > | or |-), we clean
|
|
394
|
-
## the line because it can start with spaces tabs etc.
|
|
395
|
-
## and put it with the block indenter
|
|
396
|
-
##============================================================##
|
|
397
|
-
elsif inblock == true
|
|
398
|
-
current_line = current_line.gsub(NEWLINE, NOTHING).strip
|
|
399
|
-
lines << "#{SPACE * (inblock_indent + INDENT_SIZE)}#{current_line}"
|
|
400
|
-
##============================================================##
|
|
401
|
-
## if the line ends with a multi-line character and we have a key.
|
|
402
|
-
## we start a block
|
|
403
|
-
## The regex works as follows:
|
|
404
|
-
## \S+ : All non-space characters at the start of the line.
|
|
405
|
-
## : : Matches the string ": " literally (space included).
|
|
406
|
-
## [>|] : Matches a single character that is either ">" or "|".
|
|
407
|
-
## (\d*) : Capture group that matches zero or more digits (0-9).
|
|
408
|
-
## [-+]? : Matches zero or a character that is either "-" or "+".
|
|
409
|
-
## $ : Matches the end of the line/string.
|
|
410
|
-
##============================================================##
|
|
411
|
-
elsif current_line.rstrip.match?(/\S+: [>|](\d*)[-+]?$/)
|
|
412
|
-
lines << current_line.gsub(NEWLINE, NOTHING)
|
|
413
|
-
inblock_indent = indent_level
|
|
414
|
-
inblock = true
|
|
415
|
-
##============================================================##
|
|
416
|
-
## We are in the scenario of a multiline block
|
|
417
|
-
## but without > | or |- at the end of the line
|
|
418
|
-
## which should actually be inline.
|
|
419
|
-
## mykey:
|
|
420
|
-
## line1
|
|
421
|
-
## line2
|
|
422
|
-
## line3
|
|
423
|
-
## my key: line1 line2 line3
|
|
424
|
-
##============================================================##
|
|
425
|
-
elsif split.size < 2
|
|
426
|
-
if current_line.lstrip.start_with?("-")
|
|
427
|
-
lines << current_line
|
|
428
|
-
else
|
|
429
|
-
lines[-1] = (lines[-1] + " #{current_line.lstrip}").gsub(NEWLINE, NOTHING)
|
|
430
|
-
end
|
|
431
|
-
##============================================================##
|
|
432
|
-
## Otherwise we are in the case of a classic line
|
|
433
|
-
## key: value
|
|
434
|
-
## or
|
|
435
|
-
## key: without value
|
|
436
|
-
## - key: value (list)
|
|
437
|
-
## - key: without value (list)
|
|
438
|
-
##============================================================##
|
|
439
|
-
else
|
|
440
|
-
key = clean_key(key)
|
|
441
|
-
spaces = (SPACE * indent_level).to_s
|
|
442
|
-
current_line = "#{spaces}#{key}:"
|
|
443
|
-
|
|
444
|
-
if !split[1].empty?
|
|
445
|
-
value = split[1].to_s.strip
|
|
446
|
-
|
|
447
|
-
##============================================================##
|
|
448
|
-
## We are in a multiline block which should be an inline
|
|
449
|
-
## if the value starts with a " and the number of " is odd
|
|
450
|
-
##============================================================##
|
|
451
|
-
if (value.start_with?(DOUBLE_QUOTE) && value.count(DOUBLE_QUOTE).odd?) || (value.start_with?(SIMPLE_QUOTE) && value.count(SIMPLE_QUOTE).odd?)
|
|
452
|
-
weirdblock = true
|
|
453
|
-
weirdblock_indent = indent_level
|
|
454
|
-
else
|
|
455
|
-
value = clean_value(split[1])
|
|
456
|
-
end
|
|
457
|
-
current_line += " #{value}"
|
|
458
|
-
end
|
|
459
|
-
|
|
195
|
+
def node_to_value(node, anchors)
|
|
196
|
+
case node
|
|
197
|
+
when Psych::Nodes::Scalar
|
|
198
|
+
value = scalar_to_ruby(node)
|
|
199
|
+
anchors[node.anchor] = value if node.anchor
|
|
200
|
+
value
|
|
201
|
+
when Psych::Nodes::Mapping
|
|
202
|
+
h = {}
|
|
203
|
+
node.children.each_slice(2) do |key_node, val_node|
|
|
460
204
|
##============================================================##
|
|
461
|
-
##
|
|
205
|
+
## Toujours convertir les clés en String. Cela évite les
|
|
206
|
+
## hashs aux types mixtes (Integer/String) qui cassent le tri
|
|
207
|
+
## et déstabilisent les fichiers de traduction.
|
|
462
208
|
##============================================================##
|
|
463
|
-
|
|
209
|
+
key = node_to_value(key_node, anchors).to_s
|
|
210
|
+
h[key] = node_to_value(val_node, anchors)
|
|
464
211
|
end
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
212
|
+
anchors[node.anchor] = h if node.anchor
|
|
213
|
+
h
|
|
214
|
+
when Psych::Nodes::Sequence
|
|
215
|
+
arr = node.children.map {|c| node_to_value(c, anchors) }
|
|
216
|
+
anchors[node.anchor] = arr if node.anchor
|
|
217
|
+
arr
|
|
218
|
+
when Psych::Nodes::Alias
|
|
219
|
+
raise("Unknown YAML alias: *#{node.anchor}") if !anchors.key?(node.anchor)
|
|
220
|
+
|
|
221
|
+
anchors[node.anchor]
|
|
222
|
+
else
|
|
223
|
+
raise("Unsupported YAML node type: #{node.class}")
|
|
470
224
|
end
|
|
471
|
-
|
|
472
|
-
##============================================================##
|
|
473
|
-
## We finish the file with a newline and we delete
|
|
474
|
-
## spaces on "empty" lines + double spaces
|
|
475
|
-
## with the same technique as above
|
|
476
|
-
##============================================================##
|
|
477
|
-
lines += [NOTHING]
|
|
478
|
-
lines = lines.map {|l| (l.strip.empty? ? NOTHING : l).to_s.gsub(/(?<=\S)\s+/, SPACE) }
|
|
479
|
-
File.write(file_path, lines.join(NEWLINE))
|
|
480
225
|
end
|
|
481
226
|
|
|
482
227
|
##============================================================##
|
|
483
|
-
##
|
|
484
|
-
##
|
|
485
|
-
##
|
|
486
|
-
##
|
|
487
|
-
##
|
|
488
|
-
##
|
|
489
|
-
##
|
|
490
|
-
##
|
|
491
|
-
##
|
|
228
|
+
## Convertit un Psych::Nodes::Scalar en valeur Ruby.
|
|
229
|
+
## Règles :
|
|
230
|
+
## - quoted (single/double) → toujours String
|
|
231
|
+
## - plain vide ou null/~ → nil
|
|
232
|
+
## - plain "yes/no/on/off/true/false" → String (Norway problem)
|
|
233
|
+
## - plain entier → Integer
|
|
234
|
+
## - plain flottant → Float
|
|
235
|
+
## - sinon → String
|
|
236
|
+
## - LITERAL/FOLDED : String, mais on décode \U... à l'usage
|
|
237
|
+
## dans le dump (pas ici, pour ne pas perdre l'info brute)
|
|
492
238
|
##============================================================##
|
|
493
|
-
def
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
##============================================================##
|
|
497
|
-
key = key.to_s
|
|
239
|
+
def scalar_to_ruby(node)
|
|
240
|
+
raw = node.value
|
|
241
|
+
style = node.style
|
|
498
242
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
##============================================================##
|
|
502
|
-
key = key[1..-2] if (key.start_with?(DOUBLE_QUOTE) && key.end_with?(DOUBLE_QUOTE)) || (key.start_with?(SIMPLE_QUOTE) && key.end_with?(SIMPLE_QUOTE))
|
|
243
|
+
return raw if [Psych::Nodes::Scalar::SINGLE_QUOTED, Psych::Nodes::Scalar::DOUBLE_QUOTED].include?(style)
|
|
244
|
+
return raw if [Psych::Nodes::Scalar::LITERAL, Psych::Nodes::Scalar::FOLDED].include?(style)
|
|
503
245
|
|
|
504
246
|
##============================================================##
|
|
505
|
-
##
|
|
247
|
+
## Style PLAIN : on type prudemment.
|
|
506
248
|
##============================================================##
|
|
507
|
-
|
|
249
|
+
return nil if raw == NOTHING || ["~", "null", "Null", "NULL"].include?(raw)
|
|
250
|
+
return raw if RESERVED_KEYS.include?(raw)
|
|
251
|
+
return raw.to_i if raw.match?(/\A-?\d+\z/)
|
|
252
|
+
return raw.to_f if raw.match?(/\A-?\d+\.\d+\z/)
|
|
508
253
|
|
|
509
|
-
|
|
510
|
-
## Re-add quotes if the key is in the list of reserved keys or is an integer
|
|
511
|
-
##============================================================##
|
|
512
|
-
key = "\"#{key}\"" if RESERVED_KEYS.include?(key) || is_int
|
|
513
|
-
key
|
|
254
|
+
raw
|
|
514
255
|
end
|
|
515
256
|
|
|
516
257
|
##============================================================##
|
|
517
|
-
##
|
|
258
|
+
## Décode les séquences \U0001F600 en emoji UTF-8.
|
|
259
|
+
## Appelé sur les valeurs string au moment du dump (pas au
|
|
260
|
+
## parse, pour préserver l'idempotence si l'utilisateur a vraiment
|
|
261
|
+
## la séquence littérale dans son YAML).
|
|
518
262
|
##============================================================##
|
|
519
|
-
def
|
|
520
|
-
|
|
521
|
-
string_striped = string.strip
|
|
522
|
-
string_striped.match(/^\[.*\]$/) ? string_striped[1..-2].split(/,\s?/) : string
|
|
523
|
-
rescue StandardError
|
|
524
|
-
string
|
|
525
|
-
end
|
|
263
|
+
def decode_unicode_escapes(value)
|
|
264
|
+
value.gsub(/\\U([0-9A-Fa-f]{8})/) { [::Regexp.last_match(1).to_i(16)].pack("U*") }
|
|
526
265
|
end
|
|
527
266
|
|
|
528
267
|
##============================================================##
|
|
529
|
-
##
|
|
530
|
-
##
|
|
531
|
-
##
|
|
532
|
-
## around values as it's inherently handled.
|
|
268
|
+
## clean_key : prépare une clé pour le dump.
|
|
269
|
+
## - retire les quotes englobantes éventuelles
|
|
270
|
+
## - re-quote si la clé est un mot réservé YAML 1.1 ou un entier
|
|
533
271
|
##============================================================##
|
|
534
|
-
def
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
##============================================================##
|
|
540
|
-
is_array = string_in_array(values)
|
|
541
|
-
values = is_array.instance_of?(String) ? [values] : is_array
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
values = values.map do |value|
|
|
545
|
-
##============================================================##
|
|
546
|
-
## Convert value to string to prevent issues in subsequent operations
|
|
547
|
-
##============================================================##
|
|
548
|
-
value = value.to_s
|
|
549
|
-
|
|
550
|
-
##============================================================##
|
|
551
|
-
## Remove newline characters at the end of the value if present.
|
|
552
|
-
## This should be done prior to strip operation to handle scenarios
|
|
553
|
-
## where the value ends with a space followed by a newline.
|
|
554
|
-
##============================================================##
|
|
555
|
-
value = value[0..-2] if value.end_with?(NEWLINE)
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
##============================================================##
|
|
559
|
-
## Clean up the value:
|
|
560
|
-
## - Remove tabs, carriage returns, form feeds, and vertical tabs.
|
|
561
|
-
## \t: corresponds to a tab
|
|
562
|
-
## \r: corresponds to a carriage return
|
|
563
|
-
## \f: corresponds to a form feed
|
|
564
|
-
## \v: corresponds to a vertical tab
|
|
565
|
-
## We keep the \n
|
|
566
|
-
##============================================================##
|
|
567
|
-
value = value.gsub(/[\t\r\f\v]+/, NOTHING)
|
|
568
|
-
|
|
569
|
-
##============================================================##
|
|
570
|
-
## Replace multiple spaces with a single space.
|
|
571
|
-
##============================================================##
|
|
572
|
-
value = value.gsub(/ {2,}/, SPACE)
|
|
573
|
-
|
|
574
|
-
##============================================================##
|
|
575
|
-
## Trim leading and trailing spaces.
|
|
576
|
-
##============================================================##
|
|
577
|
-
value = value.strip
|
|
578
|
-
|
|
579
|
-
##============================================================##
|
|
580
|
-
## Replace special quotes with standard single quotes.
|
|
581
|
-
##============================================================##
|
|
582
|
-
value = value.gsub(WEIRD_QUOTES_REGEX, SIMPLE_QUOTE)
|
|
583
|
-
|
|
584
|
-
##============================================================##
|
|
585
|
-
## Remove all quotes surrounding the value if they are present.
|
|
586
|
-
## They will be re-added later if necessary.
|
|
587
|
-
## """"value"""" => value
|
|
588
|
-
##============================================================##
|
|
589
|
-
value = value[1..-2] while (value.start_with?(DOUBLE_QUOTE) && value.end_with?(DOUBLE_QUOTE)) || (value.start_with?(SIMPLE_QUOTE) && value.end_with?(SIMPLE_QUOTE))
|
|
590
|
-
|
|
591
|
-
##============================================================##
|
|
592
|
-
## Convert emoji representations such as \U0001F600 to their respective emojis.
|
|
593
|
-
##============================================================##
|
|
594
|
-
value = value.gsub(/\\U([0-9A-Fa-f]{8})/) { [::Regexp.last_match(1).to_i(16)].pack("U*") }
|
|
595
|
-
|
|
596
|
-
##============================================================##
|
|
597
|
-
## Handling cases where the value must be surrounded by quotes
|
|
598
|
-
## if:
|
|
599
|
-
## management of "" and " ". Not possible to have more spaces
|
|
600
|
-
## because we have already removed the double spaces
|
|
601
|
-
## else
|
|
602
|
-
## value.include?(": ") => key: text with: here
|
|
603
|
-
## value.include?(" #") => key: text with # here
|
|
604
|
-
## value.include?(NEWLINE) => key: Line 1\nLine 2\nLine 3
|
|
605
|
-
## value.include?('\n') => key: Line 1"\n"Line 2"\n"Line 3
|
|
606
|
-
## value.start_with?(*YML_SPECIAL_CHARS) => key: @text
|
|
607
|
-
## value.end_with?(":") => key: text:
|
|
608
|
-
## RESERVED_KEYS.include?(value) => key: YES
|
|
609
|
-
## value.start_with?(SPACE) => key: 'text'
|
|
610
|
-
## value.end_with?(SPACE) => key: text '
|
|
611
|
-
##============================================================##
|
|
612
|
-
if value.empty?
|
|
613
|
-
value = "\"#{value}\""
|
|
614
|
-
elsif with_quotes_verif == true
|
|
615
|
-
value = "\"#{value}\"" if value.include?(": ") ||
|
|
616
|
-
value.include?(" #") ||
|
|
617
|
-
value.include?(NEWLINE) ||
|
|
618
|
-
value.include?('\n') ||
|
|
619
|
-
value.start_with?(*YML_SPECIAL_CHARS) ||
|
|
620
|
-
value.end_with?(":") ||
|
|
621
|
-
(is_array ? false : RESERVED_KEYS.include?(value)) ||
|
|
622
|
-
value.start_with?(SPACE) ||
|
|
623
|
-
value.end_with?(SPACE)
|
|
624
|
-
end
|
|
625
|
-
|
|
626
|
-
##============================================================##
|
|
627
|
-
## Final clean to prevent
|
|
628
|
-
## "yes": YES
|
|
629
|
-
## "no": NO
|
|
630
|
-
##============================================================##
|
|
631
|
-
value = "\"#{value}\"" if RESERVED_KEYS.include?(value)
|
|
632
|
-
|
|
633
|
-
##============================================================##
|
|
634
|
-
## Return the cleaned value
|
|
635
|
-
##============================================================##
|
|
636
|
-
value
|
|
637
|
-
end
|
|
638
|
-
is_array.instance_of?(String) ? values.first : "[#{values.join(", ")}]"
|
|
639
|
-
end
|
|
640
|
-
|
|
641
|
-
##============================================================##
|
|
642
|
-
## Normalize indentation for array values without intent
|
|
643
|
-
## for the first level.
|
|
644
|
-
##============================================================##
|
|
645
|
-
def normalize_indentation(lines)
|
|
646
|
-
initial_indentation = lines.first.match(/^(\s*)/)[1].length
|
|
647
|
-
lines.map do |line|
|
|
648
|
-
line[initial_indentation..(line.end_with?(NEWLINE) ? -2 : -1)]
|
|
649
|
-
end
|
|
272
|
+
def clean_key(key)
|
|
273
|
+
key = strip_wrapping_quotes(key.to_s)
|
|
274
|
+
is_int = key.match?(/\A[-+]?\d+\z/)
|
|
275
|
+
key = "\"#{key}\"" if RESERVED_KEYS.include?(key) || is_int
|
|
276
|
+
key
|
|
650
277
|
end
|
|
651
278
|
|
|
652
279
|
##============================================================##
|
|
653
|
-
##
|
|
654
|
-
##
|
|
280
|
+
## format_scalar_value : prépare une valeur String pour le dump.
|
|
281
|
+
## Décode les escapes Unicode et décide si on doit quoter.
|
|
655
282
|
##
|
|
656
|
-
##
|
|
657
|
-
##
|
|
283
|
+
## On quote si la valeur contient des caractères qui auraient
|
|
284
|
+
## un sens YAML particulier en plain (": ", " #", début par un
|
|
285
|
+
## caractère spécial, fin par ":", mot réservé, espace en bord).
|
|
658
286
|
##============================================================##
|
|
659
|
-
def
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
inlist_data = nil
|
|
664
|
-
last_keys = []
|
|
665
|
-
|
|
287
|
+
def format_scalar_value(value)
|
|
288
|
+
value = value.to_s
|
|
289
|
+
value = decode_unicode_escapes(value)
|
|
290
|
+
value = value.gsub(WEIRD_QUOTES_REGEX, SIMPLE_QUOTE)
|
|
666
291
|
|
|
667
292
|
##============================================================##
|
|
668
|
-
##
|
|
669
|
-
##
|
|
670
|
-
## all the values and the formatting type then we will pass
|
|
671
|
-
## on each of these arrays subsequently to transform them
|
|
672
|
-
## in the corresponding string
|
|
293
|
+
## On enlève les guillemets parasites éventuels (cas de fichiers
|
|
294
|
+
## historiques produits par l'ancienne version).
|
|
673
295
|
##============================================================##
|
|
674
|
-
|
|
675
|
-
##============================================================##
|
|
676
|
-
## Determine the indentation level of the line.
|
|
677
|
-
##============================================================##
|
|
678
|
-
indent_level = line[/\A */].size
|
|
679
|
-
|
|
680
|
-
##============================================================##
|
|
681
|
-
## Check for blank lines (which can be present within multi-line blocks)
|
|
682
|
-
##============================================================##
|
|
683
|
-
blank_line = line.gsub(NEWLINE, NOTHING).empty?
|
|
684
|
-
|
|
685
|
-
##============================================================##
|
|
686
|
-
## Split the line into key and value.
|
|
687
|
-
##============================================================##
|
|
688
|
-
split = line.strip.split(":", 2)
|
|
689
|
-
key = split[0].to_s.strip
|
|
690
|
-
inblock = nil if !inblock.nil? && !blank_line && indent_level <= inblock
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
##============================================================##
|
|
694
|
-
## inlist Enter
|
|
695
|
-
##============================================================##
|
|
696
|
-
if inlist.nil? && !blank_line && line.strip.start_with?("-") && inblock.nil?
|
|
697
|
-
inlist = indent_level
|
|
698
|
-
inlist_data = []
|
|
699
|
-
end
|
|
700
|
-
|
|
701
|
-
##============================================================##
|
|
702
|
-
## inlist Exit
|
|
703
|
-
## We use Pscyh to parse the yaml of the list content
|
|
704
|
-
##============================================================##
|
|
705
|
-
if !inlist.nil? && !blank_line && indent_level < inlist
|
|
706
|
-
yaml = normalize_indentation(inlist_data).join(NEWLINE)
|
|
707
|
-
current_key = last_keys.last
|
|
708
|
-
parent_keys = last_keys[0..-2]
|
|
709
|
-
result = parent_keys.reduce(nested_hash) {|hash, k| hash[k] }
|
|
710
|
-
result[current_key] = Psych.safe_load(yaml, :permitted_classes => [Date])
|
|
711
|
-
inlist = nil
|
|
712
|
-
inlist_data = []
|
|
713
|
-
end
|
|
714
|
-
|
|
715
|
-
##============================================================##
|
|
716
|
-
## Set the key level based on indentation
|
|
717
|
-
##============================================================##
|
|
718
|
-
last_keys = last_keys[0, (blank_line ? inblock + INDENT_SIZE : indent_level) / INDENT_SIZE]
|
|
296
|
+
value = strip_wrapping_quotes(value)
|
|
719
297
|
|
|
298
|
+
##============================================================##
|
|
299
|
+
## Note : un " au milieu d'une string plain est légal en YAML.
|
|
300
|
+
## On ne quote que si le " est en début (déjà couvert par
|
|
301
|
+
## start_with?(*YML_SPECIAL_CHARS)). Quoter dès qu'un " apparaît
|
|
302
|
+
## n'importe où dans la valeur produirait des diffs inutiles.
|
|
303
|
+
##============================================================##
|
|
304
|
+
need_quotes = value.empty? ||
|
|
305
|
+
value.include?(": ") ||
|
|
306
|
+
value.include?(" #") ||
|
|
307
|
+
value.start_with?(*YML_SPECIAL_CHARS) ||
|
|
308
|
+
value.end_with?(":") ||
|
|
309
|
+
RESERVED_KEYS.include?(value) ||
|
|
310
|
+
value.start_with?(SPACE) ||
|
|
311
|
+
value.end_with?(SPACE)
|
|
720
312
|
|
|
721
|
-
|
|
722
|
-
## If inside a multi-line block, append the line to the current key's value
|
|
723
|
-
##============================================================##
|
|
724
|
-
if !inblock.nil?
|
|
725
|
-
current_key = last_keys.last
|
|
726
|
-
parent_keys = last_keys[0..-2]
|
|
727
|
-
result = parent_keys.reduce(nested_hash) {|hash, k| hash[k] }
|
|
728
|
-
result[current_key][1] << line.strip
|
|
729
|
-
##============================================================##
|
|
730
|
-
## Handle list declarations.
|
|
731
|
-
##============================================================##
|
|
732
|
-
elsif !inlist.nil?
|
|
733
|
-
inlist_data << line
|
|
734
|
-
##============================================================##
|
|
735
|
-
## Handle multi-line key declarations.
|
|
736
|
-
## We no longer have the >
|
|
737
|
-
## because it is transformed in the clean_xml into |
|
|
738
|
-
##============================================================##
|
|
739
|
-
elsif line.gsub("#{key}:", NOTHING).strip.start_with?("|")
|
|
740
|
-
inblock = indent_level
|
|
741
|
-
block_type = line.gsub("#{key}:", NOTHING).strip
|
|
742
|
-
result = last_keys.reduce(nested_hash) {|hash, k| hash[k] }
|
|
743
|
-
result[key] = ["#{CUSTOM_SEPARATOR}#{block_type}#{CUSTOM_SEPARATOR}", []]
|
|
744
|
-
last_keys << key
|
|
745
|
-
##============================================================##
|
|
746
|
-
## Handle regular key-value pair declarations
|
|
747
|
-
##============================================================##
|
|
748
|
-
else
|
|
749
|
-
value = split[1].to_s.strip
|
|
750
|
-
result = last_keys.reduce(nested_hash) {|hash, k| hash[k] }
|
|
751
|
-
if value.empty?
|
|
752
|
-
result[key] = {}
|
|
753
|
-
last_keys << key
|
|
754
|
-
else
|
|
755
|
-
result[key] = value.strip == "null" ? nil : string_in_array(value)
|
|
756
|
-
end
|
|
757
|
-
end
|
|
758
|
-
end
|
|
759
|
-
|
|
313
|
+
return value if !need_quotes
|
|
760
314
|
|
|
761
315
|
##============================================================##
|
|
762
|
-
##
|
|
763
|
-
##
|
|
764
|
-
##
|
|
765
|
-
##
|
|
766
|
-
##
|
|
316
|
+
## Choix du style de quoting :
|
|
317
|
+
## - single-quoted par défaut (plus léger, pas d'escapes)
|
|
318
|
+
## - double-quoted seulement si la valeur contient une
|
|
319
|
+
## apostrophe ou un caractère qui nécessite un escape
|
|
320
|
+
## (\, tab). Cela minimise les diffs git sur les fichiers
|
|
321
|
+
## existants et améliore la lisibilité (HTML notamment).
|
|
767
322
|
##============================================================##
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
indent_supp = [indent_supp - INDENT_SIZE, 0].max
|
|
773
|
-
value[1] = value[1].map {|l| "#{SPACE * indent_supp}#{l}" }
|
|
774
|
-
text = value[1].join(NEWLINE)
|
|
775
|
-
modifier = style_type[-1]
|
|
776
|
-
case modifier
|
|
777
|
-
when "+"
|
|
778
|
-
text << NEWLINE unless text.end_with?(NEWLINE)
|
|
779
|
-
when "-"
|
|
780
|
-
text.chomp!
|
|
781
|
-
else
|
|
782
|
-
text << NEWLINE unless text.end_with?(NEWLINE)
|
|
783
|
-
end
|
|
784
|
-
text
|
|
785
|
-
else
|
|
786
|
-
value
|
|
787
|
-
end
|
|
323
|
+
if value.include?(SIMPLE_QUOTE) || value.include?("\\") || value.include?("\t")
|
|
324
|
+
yaml_double_quote(value)
|
|
325
|
+
else
|
|
326
|
+
"#{SIMPLE_QUOTE}#{value}#{SIMPLE_QUOTE}"
|
|
788
327
|
end
|
|
789
328
|
end
|
|
790
329
|
|
|
330
|
+
##============================================================##
|
|
331
|
+
## Échappe une string pour la sérialiser en YAML double-quoted.
|
|
332
|
+
## On gère \, ", \t et \n. Les newlines réels n'arrivent pas
|
|
333
|
+
## ici car ils sont rendus en bloc littéral plus haut.
|
|
334
|
+
##============================================================##
|
|
335
|
+
def yaml_double_quote(value)
|
|
336
|
+
escaped = value.gsub("\\", "\\\\\\\\").gsub("\"", '\\"').gsub("\t", '\\t')
|
|
337
|
+
"\"#{escaped}\""
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
##============================================================##
|
|
341
|
+
## Retire récursivement les paires de guillemets englobants.
|
|
342
|
+
## Sert pour les fichiers historiques produits par v0.1.28
|
|
343
|
+
## qui pouvaient contenir des valeurs avec quotes incluses.
|
|
344
|
+
##============================================================##
|
|
345
|
+
def strip_wrapping_quotes(value)
|
|
346
|
+
value = value[1..-2] while (value.start_with?(DOUBLE_QUOTE) && value.end_with?(DOUBLE_QUOTE)) || (value.start_with?(SIMPLE_QUOTE) && value.end_with?(SIMPLE_QUOTE))
|
|
347
|
+
value
|
|
348
|
+
end
|
|
791
349
|
|
|
792
350
|
end
|
|
793
351
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: immosquare-yaml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- immosquare
|
|
@@ -62,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
62
62
|
- !ruby/object:Gem::Version
|
|
63
63
|
version: '0'
|
|
64
64
|
requirements: []
|
|
65
|
-
rubygems_version:
|
|
65
|
+
rubygems_version: 4.0.11
|
|
66
66
|
specification_version: 4
|
|
67
67
|
summary: A YAML parser optimized for translation files.
|
|
68
68
|
test_files: []
|