Skip to content

Commit 0093a59

Browse files
committed
perf(render): Use a Write
`render_to` uses an `io::Write`. This is hopefully better than string concatenation, especially if you can write directly to file. `render` is still available for convinience (though it no longer returns an `Option`). Fixes #187 BREAKING CHANGES: Changed focus to `io::Write` - `Renderable`s must implement `render_to`. - `render` no longer returns an `Option`.
1 parent d8a455a commit 0093a59

17 files changed

+190
-173
lines changed

src/compiler/parser.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,9 @@ mod test_expect {
404404

405405
#[cfg(test)]
406406
mod test_split_block {
407+
use std::collections::HashMap;
408+
use std::io::Write;
409+
407410
use super::super::split_block;
408411
use super::super::tokenize;
409412
use super::super::BoxedBlockParser;
@@ -412,14 +415,13 @@ mod test_split_block {
412415
use interpreter;
413416
use interpreter::Context;
414417
use interpreter::Renderable;
415-
use std::collections::HashMap;
416418

417419
#[derive(Debug)]
418420
struct NullBlock;
419421

420422
impl Renderable for NullBlock {
421-
fn render(&self, _context: &mut Context) -> Result<Option<String>> {
422-
Ok(None)
423+
fn render_to(&self, _writer: &mut Write, _context: &mut Context) -> Result<()> {
424+
Ok(())
423425
}
424426
}
425427

src/interpreter/output.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::fmt;
2+
use std::io::Write;
23

34
use itertools;
45

@@ -53,9 +54,10 @@ impl fmt::Display for Output {
5354
}
5455

5556
impl Renderable for Output {
56-
fn render(&self, context: &mut Context) -> Result<Option<String>> {
57+
fn render_to(&self, writer: &mut Write, context: &mut Context) -> Result<()> {
5758
let entry = self.apply_filters(context)?;
58-
Ok(Some(entry.to_string()))
59+
write!(writer, "{}", entry).chain("Failed to render")?;
60+
Ok(())
5961
}
6062
}
6163

src/interpreter/renderable.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
use std::fmt::Debug;
2+
use std::io::Write;
23

34
use error::Result;
4-
55
use super::Context;
66

77
/// Any object (tag/block) that can be rendered by liquid must implement this trait.
88
pub trait Renderable: Send + Sync + Debug {
99
/// Renders the Renderable instance given a Liquid context.
10-
/// The Result that is returned signals if there was an error rendering,
11-
/// the Option<String> that is wrapped by the Result will be None if
12-
/// the render has run successfully but there is no content to render.
13-
fn render(&self, context: &mut Context) -> Result<Option<String>>;
10+
fn render(&self, context: &mut Context) -> Result<String> {
11+
let mut data = Vec::new();
12+
self.render_to(&mut data, context)?;
13+
Ok(String::from_utf8(data).expect("render only writes UTF-8"))
14+
}
15+
16+
/// Renders the Renderable instance given a Liquid context.
17+
fn render_to(&self, writer: &mut Write, context: &mut Context) -> Result<()>;
1418
}

src/interpreter/template.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use error::Result;
1+
use std::io::Write;
22

3+
use error::Result;
34
use super::Context;
45
use super::Renderable;
56

@@ -9,12 +10,9 @@ pub struct Template {
910
}
1011

1112
impl Renderable for Template {
12-
fn render(&self, context: &mut Context) -> Result<Option<String>> {
13-
let mut buf = String::new();
13+
fn render_to(&self, writer: &mut Write, context: &mut Context) -> Result<()> {
1414
for el in &self.elements {
15-
if let Some(ref x) = el.render(context)? {
16-
buf = buf + x;
17-
}
15+
el.render_to(writer, context)?;
1816

1917
// Did the last element we processed set an interrupt? If so, we
2018
// need to abandon the rest of our child elements and just
@@ -24,7 +22,7 @@ impl Renderable for Template {
2422
break;
2523
}
2624
}
27-
Ok(Some(buf))
25+
Ok(())
2826
}
2927
}
3028

src/interpreter/text.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use error::Result;
1+
use std::io::Write;
22

3+
use error::{Result, ResultLiquidChainExt};
34
use super::Context;
45
use super::Renderable;
56

@@ -9,8 +10,9 @@ pub struct Text {
910
}
1011

1112
impl Renderable for Text {
12-
fn render(&self, _context: &mut Context) -> Result<Option<String>> {
13-
Ok(Some(self.text.to_owned()))
13+
fn render_to(&self, writer: &mut Write, _context: &mut Context) -> Result<()> {
14+
write!(writer, "{}", &self.text).chain("Failed to render")?;
15+
Ok(())
1416
}
1517
}
1618

src/interpreter/variable.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::fmt;
2+
use std::io::Write;
23

34
use itertools;
45

5-
use error::Result;
6+
use error::{Result, ResultLiquidChainExt};
67
use value::Index;
78

89
use super::Context;
@@ -38,9 +39,10 @@ impl fmt::Display for Variable {
3839
}
3940

4041
impl Renderable for Variable {
41-
fn render(&self, context: &mut Context) -> Result<Option<String>> {
42+
fn render_to(&self, writer: &mut Write, context: &mut Context) -> Result<()> {
4243
let value = context.get_val_by_index(self.indexes.iter())?;
43-
Ok(Some(value.to_string()))
44+
write!(writer, "{}", value).chain("Failed to render")?;
45+
Ok(())
4446
}
4547
}
4648

src/tags/assign_tag.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use error::Result;
1+
use std::io::Write;
22

3+
use error::Result;
34
use compiler::LiquidOptions;
45
use compiler::ResultLiquidExt;
56
use compiler::Token;
@@ -21,12 +22,12 @@ impl Assign {
2122
}
2223

2324
impl Renderable for Assign {
24-
fn render(&self, context: &mut Context) -> Result<Option<String>> {
25+
fn render_to(&self, _writer: &mut Write, context: &mut Context) -> Result<()> {
2526
let value = self.src
2627
.apply_filters(context)
2728
.trace_with(|| self.trace().into())?;
2829
context.set_global_val(self.dst.to_owned(), value);
29-
Ok(None)
30+
Ok(())
3031
}
3132
}
3233

@@ -101,7 +102,7 @@ mod test {
101102

102103
let output = template.render(&mut context).unwrap();
103104
assert_eq!(context.get_val("freestyle"), Some(&Value::scalar(false)));
104-
assert_eq!(output, Some("".to_string()));
105+
assert_eq!(output, "");
105106
}
106107

107108
// test two: matching value in `tags`
@@ -119,7 +120,7 @@ mod test {
119120

120121
let output = template.render(&mut context).unwrap();
121122
assert_eq!(context.get_val("freestyle"), Some(&Value::scalar(true)));
122-
assert_eq!(output, Some("<p>Freestyle!</p>".to_string()));
123+
assert_eq!(output, "<p>Freestyle!</p>");
123124
}
124125
}
125126
}

src/tags/capture_block.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use error::{Result, ResultLiquidExt};
1+
use std::io::Write;
22

3+
use error::{Result, ResultLiquidExt};
34
use compiler::Element;
45
use compiler::LiquidOptions;
56
use compiler::Token;
@@ -22,14 +23,15 @@ impl Capture {
2223
}
2324

2425
impl Renderable for Capture {
25-
fn render(&self, context: &mut Context) -> Result<Option<String>> {
26-
let output = self.template
27-
.render(context)
28-
.trace_with(|| self.trace().into())?
29-
.unwrap_or_else(|| "".to_owned());
26+
fn render_to(&self, _writer: &mut Write, context: &mut Context) -> Result<()> {
27+
let mut captured = Vec::new();
28+
self.template
29+
.render_to(&mut captured, context)
30+
.trace_with(|| self.trace().into())?;
3031

32+
let output = String::from_utf8(captured).expect("render only writes UTF-8");
3133
context.set_global_val(self.id.to_owned(), Value::scalar(output));
32-
Ok(None)
34+
Ok(())
3335
}
3436
}
3537

@@ -93,7 +95,7 @@ mod test {
9395
ctx.get_val("attribute_name"),
9496
Some(&Value::scalar("potato-42-color"))
9597
);
96-
assert_eq!(output, Some("".to_owned()));
98+
assert_eq!(output, "");
9799
}
98100

99101
#[test]

src/tags/case_block.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
use std::io::Write;
2+
13
use itertools;
24

35
use error::{Error, Result, ResultLiquidExt};
4-
56
use compiler::Element;
67
use compiler::LiquidOptions;
78
use compiler::Token;
@@ -52,26 +53,26 @@ impl Case {
5253
}
5354

5455
impl Renderable for Case {
55-
fn render(&self, context: &mut Context) -> Result<Option<String>> {
56+
fn render_to(&self, writer: &mut Write, context: &mut Context) -> Result<()> {
5657
let value = self.target.evaluate(context)?;
5758
for case in &self.cases {
5859
if case.evaluate(&value, context)? {
5960
return case.template
60-
.render(context)
61+
.render_to(writer, context)
6162
.trace_with(|| case.trace().into())
6263
.trace_with(|| self.trace().into())
6364
.context_with(|| (self.target.to_string().into(), value.to_string()));
6465
}
6566
}
6667

6768
if let Some(ref t) = self.else_block {
68-
return t.render(context)
69+
return t.render_to(writer, context)
6970
.trace_with(|| "{{% else %}}".to_owned().into())
7071
.trace_with(|| self.trace().into())
7172
.context_with(|| (self.target.to_string().into(), value.to_string()));
7273
}
7374

74-
Ok(None)
75+
Ok(())
7576
}
7677
}
7778

@@ -211,25 +212,25 @@ mod test {
211212
context.set_global_val("x", Value::scalar(2f64));
212213
assert_eq!(
213214
template.render(&mut context).unwrap(),
214-
Some("two".to_owned())
215+
"two"
215216
);
216217

217218
context.set_global_val("x", Value::scalar(3f64));
218219
assert_eq!(
219220
template.render(&mut context).unwrap(),
220-
Some("three and a half".to_owned())
221+
"three and a half"
221222
);
222223

223224
context.set_global_val("x", Value::scalar(4f64));
224225
assert_eq!(
225226
template.render(&mut context).unwrap(),
226-
Some("three and a half".to_owned())
227+
"three and a half"
227228
);
228229

229230
context.set_global_val("x", Value::scalar("nope"));
230231
assert_eq!(
231232
template.render(&mut context).unwrap(),
232-
Some("otherwise".to_owned())
233+
"otherwise"
233234
);
234235
}
235236

@@ -251,7 +252,7 @@ mod test {
251252

252253
let mut context = Context::new();
253254
context.set_global_val("x", Value::scalar("nope"));
254-
assert_eq!(template.render(&mut context).unwrap(), Some("".to_owned()));
255+
assert_eq!(template.render(&mut context).unwrap(), "");
255256
}
256257

257258
#[test]

src/tags/comment_block.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use error::Result;
1+
use std::io::Write;
22

3+
use error::Result;
34
use compiler::Element;
45
use compiler::LiquidOptions;
56
use compiler::Token;
@@ -10,8 +11,8 @@ use interpreter::Renderable;
1011
struct Comment;
1112

1213
impl Renderable for Comment {
13-
fn render(&self, _context: &mut Context) -> Result<Option<String>> {
14-
Ok(None)
14+
fn render_to(&self, _writer: &mut Write, _context: &mut Context) -> Result<()> {
15+
Ok(())
1516
}
1617
}
1718

@@ -48,7 +49,7 @@ mod test {
4849
);
4950
assert_eq!(
5051
comment.unwrap().render(&mut Default::default()).unwrap(),
51-
None
52+
""
5253
);
5354
}
5455
}

src/tags/cycle_tag.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use itertools;
1+
use std::io::Write;
22

3-
use error::{Result, ResultLiquidExt};
3+
use itertools;
44

5+
use error::{Result, ResultLiquidChainExt, ResultLiquidExt};
56
use compiler::LiquidOptions;
67
use compiler::Token;
78
use compiler::{consume_value_token, unexpected_token_error, value_token};
@@ -25,11 +26,14 @@ impl Cycle {
2526
}
2627

2728
impl Renderable for Cycle {
28-
fn render(&self, context: &mut Context) -> Result<Option<String>> {
29+
fn render_to(&self, writer: &mut Write, context: &mut Context) -> Result<()> {
2930
let value = context
3031
.cycle_element(&self.name, &self.values)
3132
.trace_with(|| self.trace().into())?;
32-
Ok(value.map(|v| v.to_string()))
33+
if let Some(ref value) = value {
34+
write!(writer, "{}", value).chain("Failed to render")?;
35+
}
36+
Ok(())
3337
}
3438
}
3539

@@ -137,7 +141,7 @@ mod test {
137141
let mut context = Context::new();
138142
let output = template.render(&mut context);
139143

140-
assert_eq!(output.unwrap(), Some("one\ntwo\none\ntwo\n".to_owned()));
144+
assert_eq!(output.unwrap(), "one\ntwo\none\ntwo\n");
141145
}
142146

143147
#[test]
@@ -156,7 +160,7 @@ mod test {
156160
let mut context = Context::new();
157161
let output = template.render(&mut context);
158162

159-
assert_eq!(output.unwrap(), Some("one\ntwo\nthree\none\n".to_owned()));
163+
assert_eq!(output.unwrap(), "one\ntwo\nthree\none\n");
160164
}
161165

162166
#[test]
@@ -179,7 +183,7 @@ mod test {
179183

180184
let output = template.render(&mut context);
181185

182-
assert_eq!(output.unwrap(), Some("1\n2\n3\n1\n".to_owned()));
186+
assert_eq!(output.unwrap(), "1\n2\n3\n1\n");
183187
}
184188

185189
#[test]

0 commit comments

Comments
 (0)