From 95aef89e0045f9ad86459f39d0f477244a8d5f08 Mon Sep 17 00:00:00 2001 From: Tom Brouwer Date: Thu, 15 Feb 2024 13:12:11 +0100 Subject: [PATCH] 20321 Fix key splitting fails if key contains any parenthesis char Some of our keys contained the '->' char, which caused missing and normalize tasks to fail Ref bookingexperts/support#20321 --- lib/i18n/tasks/split_key.rb | 79 +++++++++++++++---------------------- spec/split_key_spec.rb | 5 ++- 2 files changed, 36 insertions(+), 48 deletions(-) diff --git a/lib/i18n/tasks/split_key.rb b/lib/i18n/tasks/split_key.rb index 01296b7e..4e3c30b8 100644 --- a/lib/i18n/tasks/split_key.rb +++ b/lib/i18n/tasks/split_key.rb @@ -5,6 +5,13 @@ module Tasks module SplitKey module_function + PARENTHESIS_PAIRS = %w({} [] () <>).freeze + START_KEYS = PARENTHESIS_PAIRS.to_set { |pair| pair[0] } + END_KEYS = PARENTHESIS_PAIRS.each_with_object({}) do |pair, result| + result[pair[0]] = pair[1] + end + private_constant :PARENTHESIS_PAIRS, :START_KEYS, :END_KEYS + # split a key by dots (.) # dots inside braces or parenthesis are not split on # @@ -12,61 +19,39 @@ module SplitKey # split_key 'a.#{b.c}' # => ['a', '#{b.c}'] # split_key 'a.b.c', 2 # => ['a', 'b.c'] def split_key(key, max = Float::INFINITY) - parts = [] - pos = 0 return [key] if max == 1 - key_parts(key) do |part| - parts << part - pos += part.length + 1 - if parts.length + 1 >= max - parts << key[pos..] unless pos == key.length - break + parts = [] + current_parenthesis_end_char = nil + part = '' + key.each_char.with_index do |char, index| + if current_parenthesis_end_char + part += char + current_parenthesis_end_char = nil if char == current_parenthesis_end_char + elsif START_KEYS.include?(char) + part += char + current_parenthesis_end_char = END_KEYS[char] + elsif char == '.' + parts << part + if parts.size + 1 == max + remaining = key[(index + 1)..] + parts << remaining unless remaining.empty? + return parts + end + part = '' + else + part += char end end - parts - end - def last_key_part(key) - last = nil - key_parts(key) { |part| last = part } - last - end + return parts if part.empty? - # yield each key part - # dots inside braces or parenthesis are not split on - def key_parts(key, &block) - return enum_for(:key_parts, key) unless block - - nesting = PARENS - counts = PARENS_ZEROS # dup'd later if key contains parenthesis - delim = '.' - from = to = 0 - key.each_char do |char| - if char == delim && PARENS_ZEROS == counts - block.yield key[from...to] - from = to = (to + 1) - else - nest_i, nest_inc = nesting[char] - if nest_i - counts = counts.dup if counts.frozen? - counts[nest_i] += nest_inc - end - to += 1 - end - end - block.yield(key[from...to]) if from < to && to <= key.length - true + current_parenthesis_end_char ? parts.concat(part.split('.')) : parts << part end - PARENS = %w({} [] () <>).each_with_object({}) do |s, h| - i = h.size / 2 - h[s[0].freeze] = [i, 1].freeze - h[s[1].freeze] = [i, -1].freeze - end.freeze - PARENS_ZEROS = Array.new(PARENS.size, 0).freeze - private_constant :PARENS - private_constant :PARENS_ZEROS + def last_key_part(key) + split_key(key).last + end end end end diff --git a/spec/split_key_spec.rb b/spec/split_key_spec.rb index 4f938b29..4bbf1c6c 100644 --- a/spec/split_key_spec.rb +++ b/spec/split_key_spec.rb @@ -15,7 +15,10 @@ ['a.#{b.c}', %w[a #{b.c}]], ['a.#{b.c}.', %w[a #{b.c}]], ['a.#{b.c}.d', %w[a #{b.c} d]], - ['a.#{b.c}.d.[e.f]', %w(a #{b.c} d [e.f])] + ['a.#{b.c}.d.[e.f]', %w(a #{b.c} d [e.f])], + ['a.#{b.c}.d.', %w[a #{b.c} d ]], + ['a.b->c.d.', %w[a b->c d ]], + ['a.b.c.d.