hotcell 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -1
  3. data/.rspec +1 -0
  4. data/.rvmrc +1 -1
  5. data/.travis.yml +7 -0
  6. data/Gemfile +4 -1
  7. data/README.md +361 -2
  8. data/Rakefile +28 -6
  9. data/ext/lexerc/extconf.rb +3 -0
  10. data/ext/lexerc/lexerc.c +618 -0
  11. data/ext/lexerc/lexerc.h +20 -0
  12. data/ext/lexerc/lexerc.rl +167 -0
  13. data/hotcell.gemspec +8 -7
  14. data/lib/hotcell/commands/case.rb +59 -0
  15. data/lib/hotcell/commands/cycle.rb +38 -0
  16. data/lib/hotcell/commands/for.rb +70 -0
  17. data/lib/hotcell/commands/if.rb +51 -0
  18. data/lib/hotcell/commands/include.rb +21 -0
  19. data/lib/hotcell/commands/scope.rb +13 -0
  20. data/lib/hotcell/commands/unless.rb +23 -0
  21. data/lib/hotcell/commands.rb +13 -0
  22. data/lib/hotcell/config.rb +33 -6
  23. data/lib/hotcell/context.rb +40 -7
  24. data/lib/hotcell/errors.rb +37 -28
  25. data/lib/hotcell/extensions.rb +4 -0
  26. data/lib/hotcell/lexer.rb +19 -635
  27. data/lib/hotcell/lexerr.rb +572 -0
  28. data/lib/hotcell/lexerr.rl +137 -0
  29. data/lib/hotcell/node/assigner.rb +1 -5
  30. data/lib/hotcell/node/block.rb +17 -40
  31. data/lib/hotcell/node/command.rb +29 -22
  32. data/lib/hotcell/node/hasher.rb +1 -1
  33. data/lib/hotcell/node/summoner.rb +2 -6
  34. data/lib/hotcell/node/tag.rb +10 -7
  35. data/lib/hotcell/node.rb +12 -1
  36. data/lib/hotcell/parser.rb +474 -408
  37. data/lib/hotcell/parser.y +175 -117
  38. data/lib/hotcell/resolver.rb +44 -0
  39. data/lib/hotcell/source.rb +35 -0
  40. data/lib/hotcell/template.rb +15 -6
  41. data/lib/hotcell/version.rb +1 -1
  42. data/lib/hotcell.rb +15 -10
  43. data/spec/data/templates/simple.hc +1 -0
  44. data/spec/lib/hotcell/commands/case_spec.rb +39 -0
  45. data/spec/lib/hotcell/commands/cycle_spec.rb +29 -0
  46. data/spec/lib/hotcell/commands/for_spec.rb +65 -0
  47. data/spec/lib/hotcell/commands/if_spec.rb +35 -0
  48. data/spec/lib/hotcell/commands/include_spec.rb +39 -0
  49. data/spec/lib/hotcell/commands/scope_spec.rb +16 -0
  50. data/spec/lib/hotcell/commands/unless_spec.rb +23 -0
  51. data/spec/lib/hotcell/config_spec.rb +35 -10
  52. data/spec/lib/hotcell/context_spec.rb +58 -18
  53. data/spec/lib/hotcell/lexer_spec.rb +37 -28
  54. data/spec/lib/hotcell/node/block_spec.rb +28 -56
  55. data/spec/lib/hotcell/node/command_spec.rb +7 -31
  56. data/spec/lib/hotcell/node/tag_spec.rb +16 -0
  57. data/spec/lib/hotcell/parser_spec.rb +152 -123
  58. data/spec/lib/hotcell/resolver_spec.rb +28 -0
  59. data/spec/lib/hotcell/source_spec.rb +41 -0
  60. data/spec/lib/hotcell/template_spec.rb +47 -4
  61. data/spec/lib/hotcell_spec.rb +2 -1
  62. data/spec/spec_helper.rb +6 -2
  63. metadata +54 -24
  64. data/lib/hotcell/.DS_Store +0 -0
  65. data/lib/hotcell/lexer.rl +0 -299
  66. data/misc/rage.rl +0 -1999
  67. 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
@@ -16,4 +16,7 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  *.out
19
- *.dot
19
+ *.dot
20
+ .DS_Store
21
+ .rbx
22
+ *.bundle
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --color
2
2
  --format progress
3
+ --backtrace
data/.rvmrc CHANGED
@@ -1 +1 @@
1
- rvm use 1.9.3@hotcell --create
1
+ rvm use 2.0.0@hotcell --create
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ - rbx-19mode
5
+ - rbx-20mode
6
+
7
+ before_script: 'bundle exec rake project'
data/Gemfile CHANGED
@@ -1,9 +1,12 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in hotcell.gemspec
4
3
  gemspec
5
4
 
5
+ gem 'rake'
6
+ gem 'rake-compiler'
7
+
6
8
  gem 'awesome_print'
9
+ # gem 'ruby-prof'
7
10
 
8
11
  group :test do
9
12
  gem 'rspec'
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
- TODO: Write a gem description
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
- TODO: Write usage instructions here
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 "bundler/gem_tasks"
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 :lexer do
6
- `ragel -R -T0 lib/hotcell/lexer.rl`
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/lexer.rl > lexer.dot`
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 -d -o lib/hotcell/parser.rb -O lib/hotcell/parser.out lib/hotcell/parser.y`
37
+ `racc -o lib/hotcell/parser.rb -O lib/hotcell/parser.out lib/hotcell/parser.y`
16
38
  end
17
39
  end
@@ -0,0 +1,3 @@
1
+ require 'mkmf'
2
+
3
+ create_makefile('lexerc')