hotcell 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -1
- data/.rspec +1 -0
- data/.rvmrc +1 -1
- data/.travis.yml +7 -0
- data/Gemfile +4 -1
- data/README.md +361 -2
- data/Rakefile +28 -6
- data/ext/lexerc/extconf.rb +3 -0
- data/ext/lexerc/lexerc.c +618 -0
- data/ext/lexerc/lexerc.h +20 -0
- data/ext/lexerc/lexerc.rl +167 -0
- data/hotcell.gemspec +8 -7
- data/lib/hotcell/commands/case.rb +59 -0
- data/lib/hotcell/commands/cycle.rb +38 -0
- data/lib/hotcell/commands/for.rb +70 -0
- data/lib/hotcell/commands/if.rb +51 -0
- data/lib/hotcell/commands/include.rb +21 -0
- data/lib/hotcell/commands/scope.rb +13 -0
- data/lib/hotcell/commands/unless.rb +23 -0
- data/lib/hotcell/commands.rb +13 -0
- data/lib/hotcell/config.rb +33 -6
- data/lib/hotcell/context.rb +40 -7
- data/lib/hotcell/errors.rb +37 -28
- data/lib/hotcell/extensions.rb +4 -0
- data/lib/hotcell/lexer.rb +19 -635
- data/lib/hotcell/lexerr.rb +572 -0
- data/lib/hotcell/lexerr.rl +137 -0
- data/lib/hotcell/node/assigner.rb +1 -5
- data/lib/hotcell/node/block.rb +17 -40
- data/lib/hotcell/node/command.rb +29 -22
- data/lib/hotcell/node/hasher.rb +1 -1
- data/lib/hotcell/node/summoner.rb +2 -6
- data/lib/hotcell/node/tag.rb +10 -7
- data/lib/hotcell/node.rb +12 -1
- data/lib/hotcell/parser.rb +474 -408
- data/lib/hotcell/parser.y +175 -117
- data/lib/hotcell/resolver.rb +44 -0
- data/lib/hotcell/source.rb +35 -0
- data/lib/hotcell/template.rb +15 -6
- data/lib/hotcell/version.rb +1 -1
- data/lib/hotcell.rb +15 -10
- data/spec/data/templates/simple.hc +1 -0
- data/spec/lib/hotcell/commands/case_spec.rb +39 -0
- data/spec/lib/hotcell/commands/cycle_spec.rb +29 -0
- data/spec/lib/hotcell/commands/for_spec.rb +65 -0
- data/spec/lib/hotcell/commands/if_spec.rb +35 -0
- data/spec/lib/hotcell/commands/include_spec.rb +39 -0
- data/spec/lib/hotcell/commands/scope_spec.rb +16 -0
- data/spec/lib/hotcell/commands/unless_spec.rb +23 -0
- data/spec/lib/hotcell/config_spec.rb +35 -10
- data/spec/lib/hotcell/context_spec.rb +58 -18
- data/spec/lib/hotcell/lexer_spec.rb +37 -28
- data/spec/lib/hotcell/node/block_spec.rb +28 -56
- data/spec/lib/hotcell/node/command_spec.rb +7 -31
- data/spec/lib/hotcell/node/tag_spec.rb +16 -0
- data/spec/lib/hotcell/parser_spec.rb +152 -123
- data/spec/lib/hotcell/resolver_spec.rb +28 -0
- data/spec/lib/hotcell/source_spec.rb +41 -0
- data/spec/lib/hotcell/template_spec.rb +47 -4
- data/spec/lib/hotcell_spec.rb +2 -1
- data/spec/spec_helper.rb +6 -2
- metadata +54 -24
- data/lib/hotcell/.DS_Store +0 -0
- data/lib/hotcell/lexer.rl +0 -299
- data/misc/rage.rl +0 -1999
- data/misc/unicode2ragel.rb +0 -305
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 449b5df474465cb7aface35f5e960b944d3efb03
|
4
|
+
data.tar.gz: b0628e14925479f151259181106c924b57cf625e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fdd6e11a933a6d2ccba2e4e9f3cc82c291042d8f3c543f750e626f08626514a2b120d0453e4a7e4dc572a5a8faccd1332d02a4da368d12694e00481b1ef7c8f7
|
7
|
+
data.tar.gz: 29101765a3ddc4dc679ff59bddece44e18edf6314103dcb715984907b1cd3e493c8c2a4a639072e274e03408769b5dbbad31f19db8657f36087f3cbb655ed59b
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm use
|
1
|
+
rvm use 2.0.0@hotcell --create
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,16 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/pyromaniac/hotcell.png)](https://travis-ci.org/pyromaniac/hotcell)
|
2
|
+
|
1
3
|
# Hotcell
|
2
4
|
|
3
|
-
|
5
|
+
Hotcell is a sanboxed template processor.
|
6
|
+
|
7
|
+
Key features:
|
8
|
+
|
9
|
+
* Ruby-like adult syntax, every expression returns its value
|
10
|
+
* Ragel-based lexer + Racc-based parser = fast engine base
|
11
|
+
* Stateless: once compiled - rendered as many times as nessesary
|
12
|
+
* Safe and sandboxed. Template variables and functions are evaluated safely
|
13
|
+
and have no any access to the environment
|
4
14
|
|
5
15
|
## Installation
|
6
16
|
|
@@ -16,9 +26,358 @@ Or install it yourself as:
|
|
16
26
|
|
17
27
|
$ gem install hotcell
|
18
28
|
|
29
|
+
## Language reference
|
30
|
+
|
31
|
+
Hotcell template consists of template parts and tags. Tags are enclosed in
|
32
|
+
double curly braces `Hello, {{ name }}!`.
|
33
|
+
Tags can contain expressions or commands. Expression is a combination of
|
34
|
+
values and operators. Value can be an object, referenced by variable or
|
35
|
+
a basic type.
|
36
|
+
|
37
|
+
### Basic types
|
38
|
+
|
39
|
+
Hotcell has several basic types:
|
40
|
+
|
41
|
+
* Numbers: integer `{{ 42 }}` or float `{{ 36.6 }}`
|
42
|
+
* Strings: single-quoted `{{ 'Hello' }}` or double-quoted `{{ "World" }}`.
|
43
|
+
Strings support escaping `{{ 'tlhab \'oS \'Iw HoHwI\' So\' batlh\' }}`.
|
44
|
+
Double-quoted strings also support escape sequences {{ "\n\r\s\t" }}
|
45
|
+
* Regular expressions `{{ /Foo/i }}`. Simple. Expression plus options (imx)
|
46
|
+
* Arrays `{{ [42, 'hello', /regex/m] }}`
|
47
|
+
* Hashes `{{ { a: 42, b: 'bar' } }}` has js-like syntax, so only string
|
48
|
+
can be a key for a hash
|
49
|
+
* Constant values `{{ true }}`, `{{ false }}`, `{{ nil }}` and `{{ null }}`.
|
50
|
+
Last two have the same meaning, use whatever you like.
|
51
|
+
|
52
|
+
All the basic types are objects and support method calls on themselfs.
|
53
|
+
|
54
|
+
### Variables
|
55
|
+
|
56
|
+
Variable is a value reference (`{{ name }}`). Variable name is better
|
57
|
+
to describe with regexp
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
/\[_a-z\]\[_a-z0-9\]*[?!]?/i
|
61
|
+
```
|
62
|
+
|
63
|
+
So `_` or `sum42?` - is a
|
64
|
+
variable, but `123foo` - is not.
|
65
|
+
|
66
|
+
### Operators
|
67
|
+
|
68
|
+
Operators between values form an expression: `{{ forty_two = 6 * 7 }}` or
|
69
|
+
`{{ [1, 2, 3].sum && true == 'wtf?' }}`
|
70
|
+
|
71
|
+
1. Arithmetical:
|
72
|
+
* `+`, `-`, `*`, `/` are ordinary
|
73
|
+
* `%` means modulo
|
74
|
+
* `**` is the power
|
75
|
+
2. Logical:
|
76
|
+
* `&&` - and, `||` - or, `!` - not
|
77
|
+
* `==` - equal and `!=` - inequal
|
78
|
+
* comparation: `>`, `>=`, `<`, `<=`
|
79
|
+
3. Other:
|
80
|
+
* `=` for assigning (`{{ hello_string = 'Hello' }}`)
|
81
|
+
* `.` for method calling (`{{ 'hello'.strip }}`)
|
82
|
+
* `;` as an expressions delimiter (`{{ a = 'foo'; 3 == 7 }}`)
|
83
|
+
* `(` and `)` for operator precedence (`{{ (2 + 2) * 2 }}`) or
|
84
|
+
method arguments passing: `{{ hello(who, where) }}`
|
85
|
+
* `[]` is used for array or hash elements access (`{{ some_array[2] }}`
|
86
|
+
or `{{ [1, 2, 3][2] }}` or `{{ { foo: 'bar' }['foo'] }}`)
|
87
|
+
|
88
|
+
Method call args are similar to ruby ones. You can call function like
|
89
|
+
this: `{{ foo(42, 'string', opt1: 3, opt2: /regexp/) }}`, and the last
|
90
|
+
argument would be a hash. Also parentheses are not required if function
|
91
|
+
takes no arguments: `{{ foo.bar.baz }}` is similar to
|
92
|
+
`{{ foo().bar().baz() }}`. Unlike ruby, in case of arguments presence,
|
93
|
+
parentheses are required.
|
94
|
+
|
95
|
+
### Expressions
|
96
|
+
|
97
|
+
Hotcell supports multiline complex expressions. Only the last expression of
|
98
|
+
an expression sequence will be returned. Expression delimeters are `;` or `\n`.
|
99
|
+
|
100
|
+
```
|
101
|
+
{{
|
102
|
+
forty_two = 6 * 7
|
103
|
+
sum = [1, 2, 3].sum;
|
104
|
+
(forty_two + sum) / 8
|
105
|
+
}} {{# => 6 #}}
|
106
|
+
```
|
107
|
+
|
108
|
+
Feel free to combine operators, variables and objects - hotcell has
|
109
|
+
really flexible expressions syntax.
|
110
|
+
|
111
|
+
### Tags
|
112
|
+
|
113
|
+
There is some tag modificators to set tag mode.
|
114
|
+
|
115
|
+
* `!` - silence modificator. Prevents tag value to be concatenated with
|
116
|
+
template, so `{{! 42 }}` will return '' - empty string. This modificator
|
117
|
+
is useful for pre-calculation: `{{! var = 'foo' * 3 }}`.
|
118
|
+
|
119
|
+
### Comments
|
120
|
+
|
121
|
+
There is two comments types in Hotcell: in-tag line comment and block comment.
|
122
|
+
|
123
|
+
#### In-tag line comments
|
124
|
+
|
125
|
+
In-tag line comment works inside tag only. It starts from `#` symbol
|
126
|
+
and finishes at the end of the line (`\n`):
|
127
|
+
|
128
|
+
```
|
129
|
+
{{
|
130
|
+
# here I will calculate number forty two
|
131
|
+
# with arithmetic operations help
|
132
|
+
6 * 7 # found!
|
133
|
+
}}
|
134
|
+
```
|
135
|
+
|
136
|
+
#### Block comments
|
137
|
+
|
138
|
+
Block comments are useful to enclose even tags:
|
139
|
+
|
140
|
+
```
|
141
|
+
{{# {{ 'string'.truncate(10) }} Template {{ nil || value }} #}}
|
142
|
+
```
|
143
|
+
|
144
|
+
### Commands
|
145
|
+
|
146
|
+
Command is a special tag case. It is a logic of your template. It
|
147
|
+
is more complex then function call and divided into two types:
|
148
|
+
commands and blocks.
|
149
|
+
|
150
|
+
Command syntax looks like this: `{{ [variable =] command_name [arg1, arg2...] }}`
|
151
|
+
Unlike methods or functions call, command doesn't use parentheses to take
|
152
|
+
arguments. Blocks are consist of command, closing tag and optional subcommands:
|
153
|
+
|
154
|
+
```
|
155
|
+
{{ [variable =] command_name [arg1, arg2...] }}
|
156
|
+
{{ subcommand [arg1, arg2...] }}
|
157
|
+
{{ end command_name }} {{# or {{ endcommand_name }} #}}
|
158
|
+
```
|
159
|
+
|
160
|
+
Variable and assigment is optional and with it you can assign the
|
161
|
+
command result to variable. For example, next command will put `Hello`
|
162
|
+
string into `result` variable and concat nothing to template (because of
|
163
|
+
the silence tag mode):
|
164
|
+
|
165
|
+
```
|
166
|
+
{{! result = if true }}
|
167
|
+
Hello
|
168
|
+
{{ else }}
|
169
|
+
World
|
170
|
+
{{ end if }}
|
171
|
+
```
|
172
|
+
|
173
|
+
#### Built-in Commands
|
174
|
+
|
175
|
+
##### Include
|
176
|
+
|
177
|
+
Include command renders and returns rendering result of another template
|
178
|
+
into current.
|
179
|
+
|
180
|
+
```
|
181
|
+
{{ include 'template/path', name: 'Hulk' }}
|
182
|
+
```
|
183
|
+
|
184
|
+
Also additional local variables could be transferred to the included
|
185
|
+
template via optional command hash.
|
186
|
+
|
187
|
+
`Hotcell::Resolver` ancestors are used for template resolving. Default resolver
|
188
|
+
stored in `Hotcell.resolver`, also resolver for current rendering could be
|
189
|
+
set up via `:resolver` shared option:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
Hotcell::Template.parse('{{ include 'template' }}').render(shared: { resolver: MyResolver.new })
|
193
|
+
```
|
194
|
+
|
195
|
+
##### Cycle
|
196
|
+
|
197
|
+
Command used for cycled output values from array. Useful with loops.
|
198
|
+
|
199
|
+
```
|
200
|
+
{{ for i, in: [1, 2, 3], loop: true }}
|
201
|
+
{{ i }} {{ cycle ['one', 'two', 'three'] }}{{ unless loop.last? }}, {{ end unless }}
|
202
|
+
{{ end for }}
|
203
|
+
```
|
204
|
+
|
205
|
+
This will output `1 one, 2 two, 3 three`.
|
206
|
+
|
207
|
+
#### Built-in Blocks
|
208
|
+
|
209
|
+
##### If
|
210
|
+
|
211
|
+
Conditional command. Like in most programming languages
|
212
|
+
|
213
|
+
```
|
214
|
+
{{ if var == 3 }}
|
215
|
+
foo
|
216
|
+
{{ elsif !cool }}
|
217
|
+
bar
|
218
|
+
{{ else }}
|
219
|
+
baz
|
220
|
+
{{ end if }}
|
221
|
+
```
|
222
|
+
|
223
|
+
##### Unless
|
224
|
+
|
225
|
+
Reversed conditional command. Sumilar to rubys. Only `else` subcommand is supported
|
226
|
+
|
227
|
+
```
|
228
|
+
{{ unless var == 3 }}
|
229
|
+
foo
|
230
|
+
{{ else }}
|
231
|
+
bar
|
232
|
+
{{ end unless }}
|
233
|
+
```
|
234
|
+
|
235
|
+
##### Case
|
236
|
+
|
237
|
+
Case command. Like in most programming languages
|
238
|
+
|
239
|
+
```
|
240
|
+
{{ case 42 }}
|
241
|
+
{{ when 42, 43 }}
|
242
|
+
foo
|
243
|
+
{{ when value, 'string' }}
|
244
|
+
bar
|
245
|
+
{{ else }}
|
246
|
+
baz
|
247
|
+
{{ end case }}
|
248
|
+
```
|
249
|
+
|
250
|
+
##### For
|
251
|
+
|
252
|
+
Loop command, first argument - variable to put next value, `in` option
|
253
|
+
takes an array.
|
254
|
+
|
255
|
+
```
|
256
|
+
{{ for post, in: posts }}
|
257
|
+
{{ post.title }}
|
258
|
+
{{ end for }}
|
259
|
+
```
|
260
|
+
|
261
|
+
Additional option `loop`. Takes `true` or `'string'`. Uses string as variable
|
262
|
+
name to store loop options, in case of `true`, the default variable name is `'loop'`.
|
263
|
+
|
264
|
+
```
|
265
|
+
{{ for post, in: [1, 2, 3], loop: true }}
|
266
|
+
{{# or {{ for post, in: [1, 2, 3], loop: 'forloop' }} #}}
|
267
|
+
{{ loop.index }}
|
268
|
+
{{# or {{ forloop.index }} #}}
|
269
|
+
{{ end for }}
|
270
|
+
```
|
271
|
+
|
272
|
+
Full list of loop object methods:
|
273
|
+
|
274
|
+
* `prev` - previous element of the array (nil if current is the first)
|
275
|
+
* `next` - previous element of the array (nil if current is the last)
|
276
|
+
* `length` or `size` or `count` - number, array length
|
277
|
+
* `index` - current array value index, counting from 0
|
278
|
+
* `rindex` - the same, but starting from the array tail
|
279
|
+
* `first` or `first?` - boolean value, detect whether the current element is the first
|
280
|
+
* `last` or `last?` - boolean value, detect whether the current element is the last
|
281
|
+
|
282
|
+
##### Scope
|
283
|
+
|
284
|
+
Block with encapsulated variables. Used for variables environment changing:
|
285
|
+
|
286
|
+
```
|
287
|
+
{{ scope count: 50, foo: some_value }}
|
288
|
+
{{ count }}
|
289
|
+
{{ foo }}
|
290
|
+
{{ end scope }}
|
291
|
+
```
|
292
|
+
|
293
|
+
Or for template capturing:
|
294
|
+
|
295
|
+
```
|
296
|
+
{{! title = scope }}<h1>Hello</h1>{{ end scope }}
|
297
|
+
{{ title }}
|
298
|
+
```
|
299
|
+
|
19
300
|
## Usage
|
20
301
|
|
21
|
-
|
302
|
+
### Basic usage:
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
Hotcell::Template.parse('Hello, {{ name }}!').render name: 'Pyromaniac'
|
306
|
+
```
|
307
|
+
|
308
|
+
### Additional `render` options:
|
309
|
+
|
310
|
+
* `:variables` - variables hash
|
311
|
+
* `:environment` - environment variables hash
|
312
|
+
* `:scope` - variables and environment variables together
|
313
|
+
|
314
|
+
The main difference between environment and ordinary variables is: ordinary variables
|
315
|
+
are accessible from the template and environment variables are not. Environment variables
|
316
|
+
are user for official purposes, in tag, for example. At the options level, variables have
|
317
|
+
string keys and environment variables have symbol keys.
|
318
|
+
|
319
|
+
```ruby
|
320
|
+
Hotcell::Template.parse('Hello, {{ name }}!').render(
|
321
|
+
variables: { name: 'Pyromaniac' },
|
322
|
+
environments: { some_access_token: '1234567890' },
|
323
|
+
scope: { 'foo' => 42, bar: 43 },
|
324
|
+
moo: 'Hello'
|
325
|
+
)
|
326
|
+
```
|
327
|
+
|
328
|
+
So if you will use something like above, all three options will be merged, but `:variables`
|
329
|
+
hash keys will be stringified, `:environment` hash keys will be symbolized, `:scope`
|
330
|
+
hash will be lived as is and the rest non-official options will be stringified and used as
|
331
|
+
variables. The result of this algorithm will be:
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
{
|
335
|
+
'name' => 'Pyromaniac', 'foo' => 42, 'moo' => 'Hello',
|
336
|
+
some_access_token: '1234567890', bar: 43
|
337
|
+
}
|
338
|
+
```
|
339
|
+
|
340
|
+
Remaining allowed options are:
|
341
|
+
|
342
|
+
* `:rescuer` - a lambda for error rescuing logic. The result of lambda call will be joined to
|
343
|
+
the template. The default lambda just returns error message to the template.
|
344
|
+
|
345
|
+
```ruby
|
346
|
+
Hotcell::Template.parse('Hello, {{ name }}!').render(
|
347
|
+
name: 'Pyromaniac',
|
348
|
+
rescuer: ->(e) { Rollbar.report_exception(e) }
|
349
|
+
)
|
350
|
+
```
|
351
|
+
|
352
|
+
* `:reraise` - raise exception after occurence or not. Error raises after `:rescuer` execution and
|
353
|
+
doesn't affect it. Accepts true or false.
|
354
|
+
* `:helpers` - array of modules with fuctions accessible from template. `Hotcell.helpers` config
|
355
|
+
option is used by default. Works similar to ActionController's helpers.
|
356
|
+
|
357
|
+
```ruby
|
358
|
+
Hotcell::Template.parse('Hello, {{ name }}!').render(
|
359
|
+
name: 'Pyromaniac',
|
360
|
+
helpers: MyHelper # or array [MyHelper1, MyHelper2]
|
361
|
+
)
|
362
|
+
```
|
363
|
+
* `:shared` - just hash of shared variables, for internal usage
|
364
|
+
|
365
|
+
### Configuring Hotcell
|
366
|
+
|
367
|
+
Hotcell has several configuration methods, which provide default internals for
|
368
|
+
template processor proper work.
|
369
|
+
|
370
|
+
* `commands` accessor returns a hash of default commands
|
371
|
+
* `blocks` - same is for blocks
|
372
|
+
* `helpers` - default helper modules array
|
373
|
+
* `resolver` - default resolver for `include` command
|
374
|
+
|
375
|
+
Also there are methods to setup configuration options:
|
376
|
+
|
377
|
+
* `register_command` adds command or block to the list of default commands or blocks
|
378
|
+
* `register_helpers` used for adding module to the list of helpers
|
379
|
+
* `resolver=` setups new default resolver
|
380
|
+
|
22
381
|
|
23
382
|
## Contributing
|
24
383
|
|
data/Rakefile
CHANGED
@@ -1,17 +1,39 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/extensiontask'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
Rake::ExtensionTask.new('lexerc') do |config|
|
7
|
+
config.lib_dir = 'lib/hotcell'
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :spec
|
11
|
+
|
12
|
+
desc 'Builds all the project'
|
13
|
+
task :project do
|
14
|
+
%w(project:lexerr project:lexerc project:parser clobber compile).each do |task|
|
15
|
+
Rake::Task[task].invoke
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
namespace :project do
|
20
|
+
desc 'Build lexer'
|
21
|
+
task :lexerr do
|
22
|
+
`ragel -R -F1 lib/hotcell/lexerr.rl`
|
23
|
+
end
|
2
24
|
|
3
|
-
namespace :build do
|
4
25
|
desc 'Build lexer'
|
5
|
-
task :
|
6
|
-
`ragel -
|
26
|
+
task :lexerc do
|
27
|
+
`ragel -C -G2 ext/lexerc/lexerc.rl`
|
7
28
|
end
|
8
29
|
|
9
30
|
task :dot do
|
10
|
-
`ragel -Vp lib/hotcell/
|
31
|
+
`ragel -Vp lib/hotcell/lexerr.rl > lexerr.dot`
|
32
|
+
`ragel -Vp lib/hotcell/lexerc.rl > lexerc.dot`
|
11
33
|
end
|
12
34
|
|
13
35
|
desc 'Build parser'
|
14
36
|
task :parser do
|
15
|
-
`racc -
|
37
|
+
`racc -o lib/hotcell/parser.rb -O lib/hotcell/parser.out lib/hotcell/parser.y`
|
16
38
|
end
|
17
39
|
end
|