Skip to content

Commit

Permalink
20321 Fix key splitting fails if key contains any parenthesis char
Browse files Browse the repository at this point in the history
Some of our keys contained the '->' char, which caused missing and
normalize tasks to fail

Ref bookingexperts/support#20321
  • Loading branch information
tom-brouwer-bex committed Feb 15, 2024
1 parent d0e1e20 commit 95aef89
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 48 deletions.
79 changes: 32 additions & 47 deletions lib/i18n/tasks/split_key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,68 +5,53 @@ 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
#
# split_key 'a.b' # => ['a', 'b']
# 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
5 changes: 4 additions & 1 deletion spec/split_key_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.<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.<e.f', %w[a b c d <e f]] # Openend but never closed
# rubocop:enable Lint/InterpolationCheck
].each do |(arg, ret)|
it "#{arg} is split into #{ret.inspect}" do
Expand Down

0 comments on commit 95aef89

Please sign in to comment.