Skip to content

Commit d450b1e

Browse files
committed
Refactor Transform.pattern_of
Move some of the functions that pattern_of calls out of Transform and inline others.
1 parent 43a1309 commit d450b1e

File tree

1 file changed

+84
-82
lines changed

1 file changed

+84
-82
lines changed

fluent/migrate/transforms.py

Lines changed: 84 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -79,104 +79,105 @@ def eval_node(subnode):
7979
return node.traverse(eval_node)
8080

8181

82+
def get_text(element):
83+
'''Get text content of a PatternElement.'''
84+
if isinstance(element, FTL.TextElement):
85+
return element.value
86+
if isinstance(element, FTL.Placeable):
87+
if isinstance(element.expression, FTL.StringExpression):
88+
return element.expression.value
89+
else:
90+
return None
91+
raise RuntimeError('Expected PatternElement')
92+
93+
94+
def chain_elements(elements):
95+
'''Flatten a list of FTL nodes into an iterator over PatternElements.'''
96+
for element in elements:
97+
if isinstance(element, FTL.Pattern):
98+
# PY3 yield from element.elements
99+
for child in element.elements:
100+
yield child
101+
elif isinstance(element, FTL.PatternElement):
102+
yield element
103+
elif isinstance(element, FTL.Expression):
104+
yield FTL.Placeable(element)
105+
else:
106+
raise RuntimeError(
107+
'Expected Pattern, PatternElement or Expression')
108+
109+
110+
re_leading_ws = re.compile(r'^(?P<whitespace>\s+)(?P<text>.*?)$')
111+
re_trailing_ws = re.compile(r'^(?P<text>.*?)(?P<whitespace>\s+)$')
112+
113+
114+
def extract_whitespace(regex, element):
115+
'''Extract leading or trailing whitespace from a TextElement.
116+
117+
Return a tuple of (Placeable, TextElement) in which the Placeable
118+
encodes the extracted whitespace as a StringExpression and the
119+
TextElement has the same amount of whitespace removed. The
120+
Placeable with the extracted whitespace is always returned first.
121+
'''
122+
match = re.search(regex, element.value)
123+
if match:
124+
whitespace = match.group('whitespace')
125+
placeable = FTL.Placeable(FTL.StringExpression(whitespace))
126+
if whitespace == element.value:
127+
return placeable, None
128+
else:
129+
return placeable, FTL.TextElement(match.group('text'))
130+
else:
131+
return None, element
132+
133+
82134
class Transform(FTL.BaseNode):
83135
def __call__(self, ctx):
84136
raise NotImplementedError
85137

86138
@staticmethod
87-
def flatten_elements(elements):
88-
'''Flatten a list of FTL nodes into an iterator over PatternElements.'''
89-
for element in elements:
90-
if isinstance(element, FTL.Pattern):
91-
# PY3 yield from element.elements
92-
for child in element.elements:
93-
yield child
94-
elif isinstance(element, FTL.PatternElement):
95-
yield element
96-
elif isinstance(element, FTL.Expression):
97-
yield FTL.Placeable(element)
98-
else:
99-
raise RuntimeError(
100-
'Expected Pattern, PatternElement or Expression')
139+
def pattern_of(*elements):
140+
normalized = []
101141

102-
@staticmethod
103-
def normalize_text_content(elements):
104-
'''Normalize PatternElements with text content.
105-
106-
Convert TextElements and StringExpressions into TextElements and join
107-
adjacent ones.
108-
'''
109-
110-
def get_text(element):
111-
if isinstance(element, FTL.TextElement):
112-
return element.value
113-
elif isinstance(element, FTL.Placeable):
114-
if isinstance(element.expression, FTL.StringExpression):
115-
return element.expression.value
116-
117-
joined = []
118-
for current in elements:
142+
# Normalize text content: convert all text to TextElements, join
143+
# adjacent text and prune empty.
144+
for current in chain_elements(elements):
119145
current_text = get_text(current)
120146
if current_text is None:
121-
joined.append(current)
147+
normalized.append(current)
122148
continue
123149

124-
previous = joined[-1] if len(joined) else None
150+
previous = normalized[-1] if len(normalized) else None
125151
if isinstance(previous, FTL.TextElement):
152+
# Join adjacent TextElements
126153
previous.value += current_text
127154
elif len(current_text) > 0:
128-
# Normalize to a TextElement
129-
joined.append(FTL.TextElement(current_text))
130-
return joined
155+
# Normalize non-empty text to a TextElement
156+
normalized.append(FTL.TextElement(current_text))
157+
else:
158+
# Prune empty text
159+
pass
131160

132-
@staticmethod
133-
def preserve_whitespace(elements):
134161
# Handle empty values
135-
if len(elements) == 0:
136-
return [FTL.Placeable(FTL.StringExpression(''))]
137-
138-
re_leading = re.compile(r'^(?P<whitespace>\s+)(?P<text>.*?)$')
139-
re_trailing = re.compile(r'^(?P<text>.*?)(?P<whitespace>\s+)$')
140-
141-
def extract_whitespace(regex, element):
142-
'''Extract leading or trailing whitespace from a TextElement.
143-
144-
Return a tuple of (Placeable, TextElement) in which the Placeable
145-
encodes the extracted whitespace as a StringExpression and the
146-
TextElement has the same amount of whitespace removed. The
147-
Placeable with the extracted whitespace is always returned first.
148-
'''
149-
match = re.search(regex, element.value)
150-
if match:
151-
whitespace = match.group('whitespace')
152-
empty_expr = FTL.Placeable(FTL.StringExpression(whitespace))
153-
if whitespace == element.value:
154-
return empty_expr, None
155-
else:
156-
return empty_expr, FTL.TextElement(match.group('text'))
157-
else:
158-
return None, element
162+
if len(normalized) == 0:
163+
empty = FTL.Placeable(FTL.StringExpression(''))
164+
return FTL.Pattern([empty])
159165

160-
if isinstance(elements[0], FTL.TextElement):
161-
ws, text = extract_whitespace(re_leading, elements[0])
162-
elements[:1] = [ws, text]
166+
# Handle explicit leading whitespace
167+
if isinstance(normalized[0], FTL.TextElement):
168+
ws, text = extract_whitespace(re_leading_ws, normalized[0])
169+
normalized[:1] = [ws, text]
163170

164-
if isinstance(elements[-1], FTL.TextElement):
165-
ws, text = extract_whitespace(re_trailing, elements[-1])
166-
elements[-1:] = [text, ws]
171+
# Handle explicit trailing whitespace
172+
if isinstance(normalized[-1], FTL.TextElement):
173+
ws, text = extract_whitespace(re_trailing_ws, normalized[-1])
174+
normalized[-1:] = [text, ws]
167175

168-
return [
176+
return FTL.Pattern([
169177
element
170-
for element in elements
178+
for element in normalized
171179
if element is not None
172-
]
173-
174-
@staticmethod
175-
def pattern_of(*elements):
176-
elements = Transform.flatten_elements(elements)
177-
elements = Transform.normalize_text_content(elements)
178-
elements = Transform.preserve_whitespace(elements)
179-
return FTL.Pattern(elements)
180+
])
180181

181182

182183
class Source(Transform):
@@ -315,8 +316,8 @@ def __call__(self, ctx):
315316
key for key in reversed(self.DEFAULT_ORDER) if key in keys
316317
][0]
317318

318-
# Match keys to legacy forms in order they are defined in
319-
# Gecko's PluralForm.jsm. Filter out empty forms.
319+
# Match keys to legacy forms in the order they are defined in Gecko's
320+
# PluralForm.jsm. Filter out empty forms.
320321
pairs = [
321322
(key, var)
322323
for key, var in zip(keys, forms)
@@ -332,11 +333,12 @@ def __call__(self, ctx):
332333
# variant. We don't need to insert a SelectExpression for them.
333334
if len(pairs) == 1:
334335
_, only_form = pairs[0]
335-
return evaluate(ctx, self.foreach(only_form))
336+
only_variant = evaluate(ctx, self.foreach(only_form))
337+
return Transform.pattern_of(only_variant)
336338

337339
# Make sure the default key is defined. If it's missing, use the last
338340
# form (in CLDR order) found in the legacy translation.
339-
pairs.sort(key=lambda (k, v): self.DEFAULT_ORDER.index(k))
341+
pairs.sort(key=lambda pair: self.DEFAULT_ORDER.index(pair[0]))
340342
last_key, last_form = pairs[-1]
341343
if last_key != default_key:
342344
pairs.append((default_key, last_form))

0 commit comments

Comments
 (0)