Skip to content

Commit 3370782

Browse files
authored
Merge pull request #10 from alerque/strip
Post-process element(s) to remove common indents and leading/trailing whitespace. Also adds support for formatting various types including parameter substitution for VariableReferences.
2 parents 77eb75d + 63f0590 commit 3370782

File tree

4 files changed

+90
-46
lines changed

4 files changed

+90
-46
lines changed

fluent/parser.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ local ftl_grammar = epnf.define(function (_ENV)
6868
PatternElement = Cg(C(inline_text + block_text), "value") + Cg(inline_placeable + block_placeable, "expression")
6969
Pattern = V"PatternElement"^1
7070
Attribute = line_end * blank^-1 * P"." * V"Identifier" * blank_inline^-1 * "=" * blank_inline^-1 * V"Pattern"
71-
local junk_line = (1-line_end)^0 * (P"\n" + P(nulleof))
71+
local junk_line = (1-P"\n"-P(nulleof))^0 * (P"\n" + P(nulleof))
7272
Junk = Cg(junk_line * (junk_line - P"#" - P"-" - R("az","AZ"))^0, "content")
7373
local comment_char = any_char - line_end
7474
CommentLine = Cg(P"###" + P"##" + P"#", "sigil") * (" " * Cg(C(comment_char^0), "content"))^-1 * line_end

fluent/resource.lua

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ local tablex = require("pl.tablex")
44

55
local node_types = {}
66
local node_to_type
7-
local dedent
87

98
local FluentNode = class({
109
discardable = false,
@@ -33,7 +32,13 @@ local FluentNode = class({
3332
self.value = node
3433
else
3534
if not self.elements then self.elements = {} end
36-
table.insert(self.elements, node)
35+
if #self.elements >= 1 then
36+
if not self.elements[#self.elements]:append(node) then
37+
table.insert(self.elements, node)
38+
end
39+
else
40+
table.insert(self.elements, node)
41+
end
3742
end
3843
end,
3944

@@ -100,36 +105,60 @@ node_types.Pattern = class({
100105
_base = FluentNode,
101106
_init = function (self, node)
102107
self:super(node)
103-
-- TODO: merge sequential mergables in elements
104-
-- TODO: move dedent to here after merge?
108+
self:dedent()
109+
end,
110+
dedent = function (self)
111+
local mindent = function(node)
112+
local indents = {}
113+
if type(node.value) == "string" then
114+
for indent in string.gmatch(node.value, "\n *%S") do
115+
table.insert(indents, #indent-2)
116+
end
117+
end
118+
return tablex.reduce(math.min, indents) or 0
119+
end
120+
local striplen = tablex.reduce(math.min, tablex.imap(mindent, self.elements)) or 0
121+
local i, strippref = 1, "\n"
122+
while i <= striplen do
123+
strippref = strippref .. " "
124+
i = i + 1
125+
end
126+
local strip = function(node, key, len)
127+
if type(node.value) == "string" then
128+
local value = string.gsub(node.value, "\r\n", "\n")
129+
if len >= 1 then
130+
value = string.gsub(value, strippref, "\n\n")
131+
end
132+
-- local function next_is_text
133+
value = key == 1 and string.gsub(value, "^[\n ]+", "") or value
134+
value = key == #self.elements and string.gsub(value, "[\n ]+$", "") or value
135+
self.elements[key].value = value
136+
end
137+
end
138+
tablex.foreachi(self.elements, strip, striplen)
105139
end,
106140
format = function (self, parameters)
107-
local value = #self.elements >= 2 and dedent() or self.elements[1].value
108-
-- Todo parse elements and actually format a value
141+
local function evaluate (node) return node:format(parameters) end
142+
local value = table.concat(tablex.map(evaluate, self.elements))
109143
return value, parameters
110144
end
111145
})
112-
-- local lasttype = "none"
113-
-- for key, value in ipairs(stuff) do
114-
-- if lasttype == value.id then
115-
-- ast.elements[#ast.elements] = ast.elements[#ast.elements].value .. value.value
116-
-- else
117-
-- table.insert(ast.elements, self(value))
118-
-- lasttype = value.id
119-
-- end
120-
-- end
121-
-- for key, value in ipairs(ast.elements) do
122-
-- if key == "value" then
123-
-- ast.elements[key] = dedent(value)
124-
-- end
125-
-- end
126146

127147
node_types.TextElement = class({
128148
appendable = true,
129149
_base = FluentNode,
130150
_init = function (self, node)
131151
node.id = "TextElement"
132152
self:super(node)
153+
end,
154+
__add = function (self, node)
155+
if self:is_a(node:is_a()) and self.appendable and node.appendable then
156+
self.value = (self.value or "") .. "\n" .. (node.value or "")
157+
return self
158+
end
159+
end,
160+
format = function (self)
161+
return self.value
133162
end
134163
})
135164

@@ -142,6 +171,9 @@ node_types.Placeable = class({
142171
if node.expression then
143172
self.expression = node_to_type(node.expression[1])
144173
end
174+
end,
175+
format = function (self, parameters)
176+
return self.expression:format(parameters)
145177
end
146178
})
147179

@@ -157,20 +189,39 @@ node_types.StringLiteral = class({
157189
_base = FluentNode,
158190
_init = function (self, node)
159191
self:super(node)
192+
end,
193+
format = function (self)
194+
return self.value
160195
end
161196
})
162197

163198
node_types.NumberLiteral = class({
164199
_base = FluentNode,
165200
_init = function (self, node)
166201
self:super(node)
202+
end,
203+
format = function (self)
204+
return self.value
167205
end
168206
})
169207

170208
node_types.VariableReference = class({
171209
_base = FluentNode,
172210
_init = function (self, node)
173211
self:super(node)
212+
end,
213+
format = function (self, parameters)
214+
return parameters[self.id.name]
215+
end
216+
})
217+
218+
node_types.MessageReference = class({
219+
_base = FluentNode,
220+
_init = function (self, node)
221+
self:super(node)
222+
end,
223+
format = function (self)
224+
return self.value
174225
end
175226
})
176227

@@ -235,25 +286,6 @@ node_to_type = function (node)
235286
end
236287
end
237288

238-
dedent = function (content)
239-
local min
240-
for indent in string.gmatch(content, "\n *%S") do
241-
min = min and math.min(min, #indent) or #indent
242-
end
243-
local common = function(shortest)
244-
local i = 0
245-
local s = ""
246-
while i < shortest do
247-
s = s .. " "
248-
i = i + 1
249-
end
250-
return s
251-
end
252-
local sp = common(min-2)
253-
local rep = string.gsub(content, "\n"..sp, "\n")
254-
return rep
255-
end
256-
257289
local FluentResource = class({
258290
type = "Resource",
259291
index = {},

spec/fixtures_spec.lua

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ describe('upstream reference fixture', function ()
4141
-- or fname:match("/messages.ftl$")
4242
-- or fname:match("/mixed_entries.ftl$")
4343
-- or fname:match("/multiline_values.ftl$")
44-
-- or fname:match("/numbers.ftl$")
44+
or fname:match("/numbers.ftl$")
4545
or fname:match("/obsolete.ftl$")
4646
-- or fname:match("/placeables.ftl$")
4747
-- or fname:match("/reference_expressions.ftl$")
@@ -53,7 +53,7 @@ describe('upstream reference fixture', function ()
5353
-- or fname:match("/terms.ftl$")
5454
or fname:match("/variables.ftl$")
5555
-- or fname:match("/variant_keys.ftl$")
56-
-- or fname:match("/whitespace_in_value.ftl$")
56+
or fname:match("/whitespace_in_value.ftl$")
5757
or fname:match("/zero_length.ftl$")
5858
) then
5959
describe(object, function ()
@@ -119,9 +119,9 @@ describe('upstream structure fixture', function ()
119119
-- or fname:match("/message_with_empty_multiline_pattern.ftl")
120120
-- or fname:match("/message_with_empty_pattern.ftl")
121121
or fname:match("/multiline-comment.ftl")
122-
-- or fname:match("/multiline_pattern.ftl")
122+
or fname:match("/multiline_pattern.ftl")
123123
or fname:match("/multiline_string.ftl")
124-
-- or fname:match("/multiline_with_non_empty_first_line.ftl")
124+
or fname:match("/multiline_with_non_empty_first_line.ftl")
125125
-- or fname:match("/multiline_with_placeables.ftl")
126126
-- or fname:match("/non_id_attribute_name.ftl")
127127
-- or fname:match("/placeable_at_eol.ftl")
@@ -153,8 +153,8 @@ describe('upstream structure fixture', function ()
153153
-- or fname:match("/variant_with_empty_pattern.ftl")
154154
-- or fname:match("/variant_with_leading_space_in_name.ftl")
155155
or fname:match("/variant_with_symbol_with_space.ftl")
156-
-- or fname:match("/whitespace_leading.ftl")
157-
-- or fname:match("/whitespace_trailing.ftl")
156+
or fname:match("/whitespace_leading.ftl")
157+
or fname:match("/whitespace_trailing.ftl")
158158
) then
159159
describe(object, function ()
160160
local ftl = filetostring(fname)

spec/fluent_spec.lua

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ describe('fluent.bundle', function ()
2828
assert.same("baz", en:format("bar"))
2929
end)
3030

31+
it('should parse and format literals', function ()
32+
local en = FluentBundle("en-US")
33+
en:add_messages('foo = bar {"baz"} quz {-3.14}')
34+
assert.same("bar baz quz -3.14", en:format("foo"))
35+
end)
36+
37+
it('should parse and format a variable substitution', function ()
38+
local en = FluentBundle("en-US")
39+
en:add_messages('foo = bar { $baz }')
40+
assert.same("bar qux", en:format("foo", { baz = "qux" }))
41+
end)
42+
3143
it('should keep locale instances separate', function ()
3244
local en = FluentBundle("en-US")
3345
local tr = FluentBundle("tr-TR")

0 commit comments

Comments
 (0)