Module: ImmosquareYaml
- Extended by:
- SharedMethods
- Defined in:
- lib/immosquare-yaml.rb,
lib/immosquare-yaml/delete.rb,
lib/immosquare-yaml/flatten.rb,
lib/immosquare-yaml/version.rb,
lib/immosquare-yaml/shared_methods.rb
Overview
##
ImmosquareYaml — post-processeur Psych dédié aux fichiers de traduction (locales Rails).
Trois responsabilités :
- parse(file) : YAML → Hash, en s'appuyant sur l'AST Psych
- dump(hash) : Hash → YAML formaté (quotes minimales,
blocs littéraux, emojis décodés)
- clean(file) : parse + tri par clé + dump → écrit
La gem résout cinq problèmes que Psych seul ne traite pas :
1. Norway problem (yes/no/on/off lus comme String)
2. Tri déterministe par clé
3. Préservation des blocs littéraux (|, |-)
4. Quotes minimales pour la lisibilité
5. Décodage des escapes \U0001F600 → emoji
##
Defined Under Namespace
Modules: SharedMethods
Constant Summary collapse
- VERSION =
"1.0.3".freeze
Constants included from SharedMethods
SharedMethods::CUSTOM_SEPARATOR, SharedMethods::DOUBLE_QUOTE, SharedMethods::DOUBLE_SIMPLE_QUOTE, SharedMethods::INDENT_SIZE, SharedMethods::NEWLINE, SharedMethods::NOTHING, SharedMethods::RESERVED_KEYS, SharedMethods::SIMPLE_QUOTE, SharedMethods::SPACE, SharedMethods::WEIRD_QUOTES_REGEX, SharedMethods::YML_SPECIAL_CHARS
Class Method Summary collapse
-
.clean(file_path, **options) ⇒ Object
## clean(file_path, sort: true, output: file_path) Charge le fichier, le re-écrit propre et trié.
-
.delete_paths(file_path, paths, **options) ⇒ Object
## delete_paths(file_path, paths, sort: true, output: file_path).
-
.dump(hash) ⇒ Object
## dump(hash) → String YAML Sérialise un Hash en YAML avec nos règles de formatage : - clés “yes/no/on/…” re-quotées - valeurs plain quand c’est sûr, sinon doublequotées - chaînes multi-lignes en bloc littéral | ou |- - arrays imbriqués délégués à Psych.dump puis indentés ============================================================##.
-
.flatten_keys(input, **options) ⇒ Object
## flatten_keys(input, with_values: false, with_file: false).
-
.parse(file_path, **options) ⇒ Object
## parse(file_path, sort: true) Lit un fichier YAML et retourne un Hash Ruby.
-
.parse_path(dot_path) ⇒ Object
## parse_path(dot_path) → Array<String>.
Methods included from SharedMethods
Class Method Details
.clean(file_path, **options) ⇒ Object
##
clean(file_path, sort: true, output: file_path) Charge le fichier, le re-écrit propre et trié. Retourne true / false selon le succès.
##
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/immosquare-yaml.rb', line 35 def clean(file_path, **) = { :sort => true, :output => file_path }.merge() begin raise("File not found") if !File.exist?(file_path) parsed_yml = parse(file_path, :sort => [:sort]) return false if parsed_yml == false output = dump(parsed_yml) FileUtils.mkdir_p(File.dirname([:output])) File.write([:output], output) true rescue StandardError => e puts(e.) puts(e.backtrace) false end end |
.delete_paths(file_path, paths, **options) ⇒ Object
##
delete_paths(file_path, paths, sort: true, output: file_path)
paths : a single dot-path String or an Array<String>.
Returns a Hash :
{ :deleted => [paths actually removed],
:not_found => [paths that did not exist in the file] }
Returns false if the file cannot be parsed or if its root node is not a Hash (i18n YAML files are always mappings).
Note : the file is always rewritten through ‘dump`, even when every path is reported as :not_found. This means calling `delete_paths` doubles as a `clean` (sort + reformat) on the target file. Use `:output => “…”` to write elsewhere.
##
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/immosquare-yaml/delete.rb', line 32 def delete_paths(file_path, paths, **) = { :sort => true, :output => file_path }.merge() begin raise("File not found") if !File.exist?(file_path) parsed = parse(file_path, :sort => [:sort]) return false if parsed == false || !parsed.is_a?(Hash) paths = Array(paths) deleted = [] not_found = [] ##============================================================## ## Walk each dot-path, delete the leaf if present, prune ## empty parents on the way back up. ##============================================================## paths.each do |dot_path| segments = parse_path(dot_path) if !segments.empty? && delete_at_segments(parsed, segments) deleted << dot_path else not_found << dot_path end end output = dump(parsed) FileUtils.mkdir_p(File.dirname([:output])) File.write([:output], output) {:deleted => deleted, :not_found => not_found} rescue StandardError => e puts(e.) puts(e.backtrace) false end end |
.dump(hash) ⇒ Object
##
dump(hash) → String YAML Sérialise un Hash en YAML avec nos règles de formatage :
- clés "yes/no/on/..." re-quotées
- valeurs plain quand c'est sûr, sinon doublequotées
- chaînes multi-lignes en bloc littéral | ou |-
- arrays imbriqués délégués à Psych.dump puis indentés
##
106 107 108 |
# File 'lib/immosquare-yaml.rb', line 106 def dump(hash) render_hash(hash, [], 0) end |
.flatten_keys(input, **options) ⇒ Object
##
flatten_keys(input, with_values: false, with_file: false)
input :
- Hash : flattened in memory (no I/O). File
column is nil in the resulting tuples.
- String : a single YAML file path.
- Array<String> : a list of YAML file paths.
Globs are NOT expanded — the caller is expected to expand them upstream (e.g. with Dir.glob) and pass an Array<String>. Mixing a Hash with file paths in the same Array is not supported.
No option → Array<String> sorted + deduplicated with_values: true → Array<[path, value]> not deduplicated with_file: true → adds the source file_path to each entry
(nil for Hash inputs)
Missing / empty / unreadable file: silently ignored.
##
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/immosquare-yaml/flatten.rb', line 38 def flatten_keys(input, **) = { :with_values => false, :with_file => false }.merge() entries = [] if input.is_a?(Hash) flatten_hash(input, [], entries, nil) if !input.empty? else paths = input.is_a?(Array) ? input : [input] paths.each do |path| path = path.to_s next if path.empty? || !File.exist?(path) parsed = parse(path, :sort => false) next if parsed == false || !parsed.is_a?(Hash) || parsed.empty? flatten_hash(parsed, [], entries, path) end end format_entries(entries, ) end |
.parse(file_path, **options) ⇒ Object
##
parse(file_path, sort: true) Lit un fichier YAML et retourne un Hash Ruby. Hash trié par clé par défaut.
Implémentation : on parcourt l’AST Psych plutôt que d’appeler Psych.load. Cela permet de :
- distinguer un scalaire plain "yes" d'un bool true
- garder les valeurs problématiques (Norway) en String
- décoder nous-mêmes les escapes \U... pour les blocs
littéraux qui ne sont pas désescapés par Psych
##
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/immosquare-yaml.rb', line 70 def parse(file_path, **) = {:sort => true}.merge() begin raise("File not found") if !File.exist?(file_path) ##============================================================## ## Psych.parse_file retourne un Document. Si le fichier est ## vide ou ne contient que des commentaires, root est nil. ##============================================================## doc = Psych.parse_file(file_path) return {} if !doc || doc.root.nil? result = node_to_value(doc.root, {}) ##============================================================## ## On accepte tous les types racine (Hash, Array, scalaire), ## mais on ne trie que si la racine est un Hash. ##============================================================## result = result.sort_by_key if [:sort] && result.is_a?(Hash) result rescue StandardError => e puts(e.) puts(e.backtrace) false end end |
.parse_path(dot_path) ⇒ Object
##
parse_path(dot_path) → Array<String>
Symmetric inverse of the quoting done by flatten_keys: splits on “.” and strips wrapping quotes from reserved or numeric segments. The result can be passed directly to Hash#dig on a hash returned by ImmosquareYaml.parse.
Limitation : keys containing a literal “.” in their name are not supported (the path would be split into two segments).
Examples :
parse_path("fr.statuses.\"yes\"") # => ["fr", "statuses", "yes"]
parse_path("fr.counts.\"42\"") # => ["fr", "counts", "42"]
parse_path("fr.app.title") # => ["fr", "app", "title"]
##
80 81 82 |
# File 'lib/immosquare-yaml/flatten.rb', line 80 def parse_path(dot_path) dot_path.to_s.split(".").map {|segment| unquote_segment(segment) } end |