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.
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')