hotcell 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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
|