1# coding=utf8
2from __future__ import unicode_literals
3from __future__ import absolute_import
4
5import textwrap
6
7import fluent.syntax.ast as FTL
8from fluent.syntax.parser import FluentParser, FluentParserStream
9
10
11fluent_parser = FluentParser(with_spans=False)
12
13
14def parse(Parser, string):
15    if Parser is FluentParser:
16        return fluent_parser.parse(string)
17
18    # Parsing a legacy resource.
19
20    # Parse the string into the internal Context.
21    parser = Parser()
22    # compare-locales expects ASCII strings.
23    parser.readContents(string.encode('utf8'))
24    # Transform the parsed result which is an iterator into a dict.
25    return {ent.key: ent for ent in parser}
26
27
28def ftl_resource_to_ast(code):
29    return fluent_parser.parse(ftl(code))
30
31
32def ftl_resource_to_json(code):
33    return fluent_parser.parse(ftl(code)).to_json()
34
35
36def ftl_pattern_to_json(code):
37    ps = FluentParserStream(ftl(code))
38    return fluent_parser.maybe_get_pattern(ps).to_json()
39
40
41def to_json(merged_iter):
42    return {
43        path: resource.to_json()
44        for path, resource in merged_iter
45    }
46
47
48LOCALIZABLE_ENTRIES = (FTL.Message, FTL.Term)
49
50
51def get_message(body, ident):
52    """Get message called `ident` from the `body` iterable."""
53    for entity in body:
54        if isinstance(entity, LOCALIZABLE_ENTRIES) and entity.id.name == ident:
55            return entity
56
57
58def get_transform(body, ident):
59    """Get entity called `ident` from the `body` iterable."""
60    for transform in body:
61        if transform.id.name == ident:
62            return transform
63
64
65def skeleton(node):
66    """Create a skeleton copy of the given node.
67
68    For localizable entries, the value is None and the attributes are {}.
69    That's not a valid Fluent entry, so it requires further manipulation to
70    set values and/or attributes.
71    """
72    if isinstance(node, LOCALIZABLE_ENTRIES):
73        return type(node)(id=node.id.clone(), value=None)
74    return node.clone()
75
76
77def ftl(code):
78    """Nicer indentation for FTL code.
79
80    The code returned by this function is meant to be compared against the
81    output of the FTL Serializer.  The input code will end with a newline to
82    match the output of the serializer.
83    """
84
85    # The code might be triple-quoted.
86    code = code.lstrip('\n')
87
88    return textwrap.dedent(code)
89
90
91def fold(fun, node, init):
92    """Reduce `node` to a single value using `fun`.
93
94    Apply `fun` against an accumulator and each subnode of `node` (in postorder
95    traversal) to reduce it to a single value.
96    """
97
98    def fold_(vals, acc):
99        if not vals:
100            return acc
101
102        head = list(vals)[0]
103        tail = list(vals)[1:]
104
105        if isinstance(head, FTL.BaseNode):
106            acc = fold(fun, head, acc)
107        if isinstance(head, list):
108            acc = fold_(head, acc)
109        if isinstance(head, dict):
110            acc = fold_(head.values(), acc)
111
112        return fold_(tail, fun(acc, head))
113
114    return fold_(vars(node).values(), init)
115