from __future__ import unicode_literals
from . import ast
def indent_except_first_line(content):
return " ".join(
content.splitlines(True)
)
def includes_new_line(elem):
return isinstance(elem, ast.TextElement) and "\n" in elem.value
def is_select_expr(elem):
return (
isinstance(elem, ast.Placeable) and
isinstance(elem.expression, ast.SelectExpression))
def should_start_on_new_line(pattern):
is_multiline = any(is_select_expr(elem) for elem in pattern.elements) \
or any(includes_new_line(elem) for elem in pattern.elements)
if is_multiline:
first_element = pattern.elements[0]
if isinstance(first_element, ast.TextElement):
first_char = first_element.value[0]
if first_char in ("[", ".", "*"):
return False
return True
return False
[docs]class FluentSerializer(object):
"""FluentSerializer converts :class:`.ast.SyntaxNode` objects to unicode strings.
`with_junk` controls if parse errors are written back or not.
"""
HAS_ENTRIES = 1
def __init__(self, with_junk=False):
self.with_junk = with_junk
[docs] def serialize(self, resource):
"Serialize a :class:`.ast.Resource` to a string."
if not isinstance(resource, ast.Resource):
raise Exception('Unknown resource type: {}'.format(type(resource)))
state = 0
parts = []
for entry in resource.body:
if not isinstance(entry, ast.Junk) or self.with_junk:
parts.append(self.serialize_entry(entry, state))
if not state & self.HAS_ENTRIES:
state |= self.HAS_ENTRIES
return "".join(parts)
[docs] def serialize_entry(self, entry, state=0):
"Serialize an :class:`.ast.Entry` to a string."
if isinstance(entry, ast.Message):
return serialize_message(entry)
if isinstance(entry, ast.Term):
return serialize_term(entry)
if isinstance(entry, ast.Comment):
if state & self.HAS_ENTRIES:
return "\n{}\n".format(serialize_comment(entry, "#"))
return "{}\n".format(serialize_comment(entry, "#"))
if isinstance(entry, ast.GroupComment):
if state & self.HAS_ENTRIES:
return "\n{}\n".format(serialize_comment(entry, "##"))
return "{}\n".format(serialize_comment(entry, "##"))
if isinstance(entry, ast.ResourceComment):
if state & self.HAS_ENTRIES:
return "\n{}\n".format(serialize_comment(entry, "###"))
return "{}\n".format(serialize_comment(entry, "###"))
if isinstance(entry, ast.Junk):
return serialize_junk(entry)
raise Exception('Unknown entry type: {}'.format(type(entry)))
def serialize_comment(comment, prefix="#"):
prefixed = "\n".join([
prefix if len(line) == 0 else "{} {}".format(prefix, line)
for line in comment.content.split("\n")
])
# Add the trailing line break.
return '{}\n'.format(prefixed)
def serialize_junk(junk):
return junk.content
def serialize_message(message):
parts = []
if message.comment:
parts.append(serialize_comment(message.comment))
parts.append("{} =".format(message.id.name))
if message.value:
parts.append(serialize_pattern(message.value))
if message.attributes:
for attribute in message.attributes:
parts.append(serialize_attribute(attribute))
parts.append("\n")
return ''.join(parts)
def serialize_term(term):
parts = []
if term.comment:
parts.append(serialize_comment(term.comment))
parts.append("-{} =".format(term.id.name))
parts.append(serialize_pattern(term.value))
if term.attributes:
for attribute in term.attributes:
parts.append(serialize_attribute(attribute))
parts.append("\n")
return ''.join(parts)
def serialize_attribute(attribute):
return "\n .{} ={}".format(
attribute.id.name,
indent_except_first_line(serialize_pattern(attribute.value))
)
def serialize_pattern(pattern):
content = "".join(serialize_element(elem) for elem in pattern.elements)
content = indent_except_first_line(content)
if should_start_on_new_line(pattern):
return '\n {}'.format(content)
return ' {}'.format(content)
def serialize_element(element):
if isinstance(element, ast.TextElement):
return element.value
if isinstance(element, ast.Placeable):
return serialize_placeable(element)
raise Exception('Unknown element type: {}'.format(type(element)))
def serialize_placeable(placeable):
expr = placeable.expression
if isinstance(expr, ast.Placeable):
return "{{{}}}".format(serialize_placeable(expr))
if isinstance(expr, ast.SelectExpression):
# Special-case select expressions to control the withespace around the
# opening and the closing brace.
return "{{ {}}}".format(serialize_expression(expr))
if isinstance(expr, ast.Expression):
return "{{ {} }}".format(serialize_expression(expr))
def serialize_expression(expression):
if isinstance(expression, ast.StringLiteral):
return '"{}"'.format(expression.value)
if isinstance(expression, ast.NumberLiteral):
return expression.value
if isinstance(expression, ast.VariableReference):
return "${}".format(expression.id.name)
if isinstance(expression, ast.TermReference):
out = "-{}".format(expression.id.name)
if expression.attribute is not None:
out += ".{}".format(expression.attribute.name)
if expression.arguments is not None:
out += serialize_call_arguments(expression.arguments)
return out
if isinstance(expression, ast.MessageReference):
out = expression.id.name
if expression.attribute is not None:
out += ".{}".format(expression.attribute.name)
return out
if isinstance(expression, ast.FunctionReference):
args = serialize_call_arguments(expression.arguments)
return "{}{}".format(expression.id.name, args)
if isinstance(expression, ast.SelectExpression):
out = "{} ->".format(
serialize_expression(expression.selector))
for variant in expression.variants:
out += serialize_variant(variant)
return "{}\n".format(out)
if isinstance(expression, ast.Placeable):
return serialize_placeable(expression)
raise Exception('Unknown expression type: {}'.format(type(expression)))
def serialize_variant(variant):
return "\n{}[{}]{}".format(
" *" if variant.default else " ",
serialize_variant_key(variant.key),
indent_except_first_line(serialize_pattern(variant.value))
)
def serialize_call_arguments(expr):
positional = ", ".join(
serialize_expression(arg) for arg in expr.positional)
named = ", ".join(
serialize_named_argument(arg) for arg in expr.named)
if len(expr.positional) > 0 and len(expr.named) > 0:
return '({}, {})'.format(positional, named)
return '({})'.format(positional or named)
def serialize_named_argument(arg):
return "{}: {}".format(
arg.name.name,
serialize_expression(arg.value)
)
def serialize_variant_key(key):
if isinstance(key, ast.Identifier):
return key.name
if isinstance(key, ast.NumberLiteral):
return key.value
raise Exception('Unknown variant key type: {}'.format(type(key)))