1 /**
2  * Temple (C) Dylan Knutson, 2013, distributed under the:
3  * Boost Software License - Version 1.0 - August 17th, 2003
4  *
5  * Permission is hereby granted, free of charge, to any person or organization
6  * obtaining a copy of the software and accompanying documentation covered by
7  * this license (the "Software") to use, reproduce, display, distribute,
8  * execute, and transmit the Software, and to prepare derivative works of the
9  * Software, and to permit third-parties to whom the Software is furnished to
10  * do so, all subject to the following:
11  *
12  * The copyright notices in the Software and this entire statement, including
13  * the above license grant, this restriction and the following disclaimer,
14  * must be included in all copies of the Software, in whole or in part, and
15  * all derivative works of the Software, unless such copies or derivative
16  * works are solely in the form of machine-executable object code generated by
17  * a source language processor.
18  */
19 
20 module temple.temple_context;
21 
22 import temple;
23 import temple.output_stream;
24 
25 public import std.variant : Variant;
26 private import std.array, std.string, std.typetuple;
27 
28 final class TempleContext
29 {
30 private:
31 	// context variables
32 	Variant[string] vars;
33 
34 
35 package:
36 	const(CompiledTemple)* partial;
37 
38 	/// package
39 	// TODO: This needs to be marked as a "safe" string
40 	static Appender!string __templeRenderWith(in CompiledTemple temple, TempleContext ctx)
41 	body
42 	{
43 		// Allocate a buffer and call the render func with it
44 		auto buff = appender!string;
45 		temple.render(buff, ctx);
46 		return buff;
47 	}
48 
49 	// sink for rendering templates to
50 	TempleOutputStream sink;
51 
52 	// called by generated temple function
53 	void put(string s) {
54 		sink.put(s);
55 	}
56 
57 public:
58 	string capture(T...)(void delegate(T) block, T args)
59 	{
60 		auto saved = this.sink;
61 		scope(exit) this.sink = saved;
62 
63 		auto buffer = appender!string;
64 		this.sink = TempleOutputStream(buffer);
65 
66 		// Call the block (which resides inside the template, and will
67 		// now write to `buffer`)
68 		block(args);
69 
70 		return buffer.data;
71 	}
72 
73 	bool isSet(string name)
74 	{
75 		return (name in vars && vars[name] != Variant());
76 	}
77 
78 	ref Variant var(string name)
79 	{
80 		if(name !in vars)
81 			vars[name] = Variant();
82 
83 		return vars[name];
84 	}
85 
86 	void opIndexAssign(T)(string name, T val) {
87 		if(name !in vars)
88 			vars[name] = Variant();
89 
90 		vars[name] = val;
91 	}
92 
93 	VarDispatcher var()
94 	{
95 		return VarDispatcher(this);
96 	}
97 
98 	Variant opDispatch(string op)() @property
99 	if(op != "__ctor") // seems a scoped!T bug requires this
100 	in {
101 		assert(op in vars, "variant does not have key: " ~ op);
102 	}
103 	body {
104 		return vars[op];
105 	}
106 
107 	void opDispatch(string op, T)(T other) @property
108 	{
109 		vars[op] = other;
110 	}
111 
112 	TempleInputStream yield() @property
113 	{
114 		auto noop = TempleInputStream(delegate(ref TempleOutputStream) {
115 			//debug debug_writeln("yielded input stream called (was a noop)");
116 		});
117 
118 		if(partial !is null)
119 		{
120 			return TempleInputStream(delegate(ref TempleOutputStream os) {
121 				partial.render(os, this);
122 			});
123 		}
124 
125 		return noop;
126 	}
127 }
128 
129 private struct VarDispatcher
130 {
131 private:
132 	TempleContext context;
133 
134 public:
135 	this(TempleContext context)
136 	{
137 		this.context = context;
138 	}
139 
140 	ref Variant opDispatch(string op)() @property
141 	{
142 		return context.var(op);
143 	}
144 
145 	void opDispatch(string op, T)(T other) @property
146 	{
147 		context.var(op) = other;
148 	}
149 }
150 
151 unittest
152 {
153 	auto context = new TempleContext();
154 	context.foo = "bar";
155 	context.bar = 10;
156 
157 	with(context)
158 	{
159 		assert(var.foo == "bar");
160 		assert(var.bar == 10);
161 
162 		var.baz = true;
163 		assert(var.baz == true);
164 	}
165 }