Using fluent.syntax

The fluent.syntax package provides a parser, a serializer, and libraries for analysis and processing of Fluent files.

Parsing

To parse a full resource, you can use the fluent.syntax.parse() shorthand:

from fluent.syntax import parse
resource = parse("""
### Fluent resource comment

first = creating a { $thing }
second = more content
""")

To parse a single fluent.syntax.ast.Message or fluent.syntax.ast.Term, use fluent.syntax.parser.FluentParser.parse_entry():

from fluent.syntax.parser import FluentParser
parser = FluentParser()
key_message = parser.parse_entry("""
### Fluent resource comment

key = value
""")

Serialization

To create Fluent syntax from AST objects, use fluent.syntax.serialize() or fluent.syntax.serializer.FluentSerializer.

from fluent.syntax import serialize
from fluent.syntax.serializer import FluentSerializer
serialize(resource)
serializer = FluentSerializer()
serializer.serialize(resource)
serializer.serialize_entry(key_message)

Analysis (Visitor)

To analyze an AST tree in a read-only fashion, you can subclass fluent.syntax.visitor.Visitor.

You overload individual visit_NodeName() methods to handle nodes of that type, and then call into :py:func`self.generic_visit` to continue iteration.

from fluent.syntax import visitor
import re

class WordCounter(visitor.Visitor):
    COUNTER = re.compile(r"[\w,.-]+")
    @classmethod
    def count(cls, node):
        wordcounter = cls()
        wordcounter.visit(node)
        return wordcounter.word_count
    def __init__(self):
        super()
        self.word_count = 0
    def visit_TextElement(self, node):
        self.word_count += len(self.COUNTER.findall(node.value))
        self.generic_visit(node)

WordCounter.count(resource)
WordCounter.count(key_message)

In-place Modification (Transformer)

Manipulation of an AST tree can be done in-place with a subclass of fluent.syntax.visitor.Transformer. The coding pattern matches that of visitor.Visitor, but you can modify the node in-place. You can even return different types, or remove nodes alltogether.

class Skeleton(visitor.Transformer):
    def visit_SelectExpression(self, node):
        # This should do more checks, good enough for docs
        for variant in node.variants:
            if variant.default:
                default_variant = variant
                break
        template_variant = self.visit(default_variant)
        template_variant.default = False
        node.variants[:] = []
        for key in ('one', 'few', 'many'):
            variant = template_variant.clone()
            variant.key.name = key
            node.variants.append(variant)
        node.variants[-1].default = True
        return node
    def visit_TextElement(self, node):
      return None

skeleton = Skeleton()
skeleton.visit(key_message)
WordCounter.count(key_message)
# Returns 0.
new_plural = skeleton.visit(parser.parse_entry("""
with-plural = { $num ->
  [one] Using { -one-term-reference } to hide
 *[other] Using { $num } {-term-reference} as template
}
"""))
print(serializer.serialize_entry(new_plural))

This returns

with-plural =
    { $num ->
        [one] { $num }{ -term-reference }
        [few] { $num }{ -term-reference }
       *[many] { $num }{ -term-reference }
    }

Warning

Serializing an AST tree that was created like this might not produce valid Fluent content.