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
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hotcell::Commands::If do
4
+ def parse source
5
+ Hotcell::Template.parse(source)
6
+ end
7
+
8
+ describe '#validate!' do
9
+ specify { expect { parse('{{ if }}{{ end if }}').syntax
10
+ }.to raise_error Hotcell::ArgumentError, 'Wrond number of arguments for `if` (0 for 1) at 1:4' }
11
+ specify { expect { parse('{{ if true }}{{ else true }}{{ end if }}').syntax
12
+ }.to raise_error Hotcell::ArgumentError, 'Wrond number of arguments for `else` (1 for 0) at 1:17' }
13
+ specify { expect { parse('{{ if true }}{{ elsif }}{{ end if }}').syntax
14
+ }.to raise_error Hotcell::ArgumentError, 'Wrond number of arguments for `elsif` (0 for 1) at 1:17' }
15
+ specify { expect { parse('{{ if true }}{{ else }}{{ elsif true }}{{ end if }}').syntax
16
+ }.to raise_error Hotcell::BlockError, 'Unexpected `else` for `if` command at 1:17' }
17
+ specify { expect { parse('{{ if true }}{{ else true }}{{ elsif true }}{{ end if }}').syntax
18
+ }.to raise_error Hotcell::BlockError, 'Unexpected `else` for `if` command at 1:17' }
19
+ specify { expect { parse('{{ if true }}{{ else }}{{ else }}{{ end if }}').syntax
20
+ }.to raise_error Hotcell::BlockError, 'Unexpected `else` for `if` command at 1:17' }
21
+ end
22
+
23
+ describe '#render' do
24
+ specify { parse('{{ if true }}Hello{{ end if }}').render.should == 'Hello' }
25
+ specify { parse('{{ if false }}Hello{{ end if }}').render.should == '' }
26
+ specify { parse('{{ if true }}Hello{{ else }}World{{ end if }}').render.should == 'Hello' }
27
+ specify { parse('{{ if false }}Hello{{ else }}World{{ end if }}').render.should == 'World' }
28
+ specify { parse('{{ if true }}Hello{{ elsif true }}Beautiful{{ else }}World{{ end if }}').render.should == 'Hello' }
29
+ specify { parse('{{ if false }}Hello{{ elsif true }}Beautiful{{ else }}World{{ end if }}').render.should == 'Beautiful' }
30
+ specify { parse('{{ if false }}Hello{{ elsif false }}Beautiful{{ else }}World{{ end if }}').render.should == 'World' }
31
+ specify { parse('{{ if false }}Hello{{ elsif true }}Beautiful{{ elsif true }}Ruby{{ else }}World{{ end if }}').render.should == 'Beautiful' }
32
+ specify { parse('{{ if false }}Hello{{ elsif false }}Beautiful{{ elsif true }}Ruby{{ else }}World{{ end if }}').render.should == 'Ruby' }
33
+ specify { parse('{{ if false }}Hello{{ elsif false }}Beautiful{{ elsif false }}Ruby{{ else }}World{{ end if }}').render.should == 'World' }
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hotcell::Commands::Include do
4
+ def parse source
5
+ Hotcell::Template.parse(source)
6
+ end
7
+
8
+ let(:resolver_class) do
9
+ Class.new(Hotcell::Resolver) do
10
+ attr_reader :templates
11
+ def initialize templates
12
+ @templates = templates.stringify_keys
13
+ end
14
+
15
+ def resolve path, context = nil
16
+ templates[path] or raise 'Template not found'
17
+ end
18
+ end
19
+ end
20
+ let(:templates) { {
21
+ template1: 'Hello',
22
+ template2: 'Hello, {{ name }}',
23
+ template3: '{{ name }} - {{ action }}'
24
+ } }
25
+ let(:resolver) { resolver_class.new templates }
26
+ let(:render_options) { { shared: { resolver: resolver } } }
27
+
28
+ describe '#render' do
29
+ specify { parse("{{ include 'template0' }}").render(render_options).should =~ /Template not found/ }
30
+ specify { parse("{{ include 'template1' }}").render(render_options).should == 'Hello' }
31
+ specify { parse("{{ include 'template2' }}").render(render_options).should == 'Hello, ' }
32
+ specify { parse(
33
+ "{{ include 'template2', name: 'Pyrosha' }}"
34
+ ).render(render_options).should == 'Hello, Pyrosha' }
35
+ specify { parse(
36
+ "{{! name = Pyrosha; action = 'CRUSH!' }}{{ include 'template3', name: 'Hulk' }}"
37
+ ).render(render_options).should == 'Hulk - CRUSH!' }
38
+ end
39
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hotcell::Commands::Scope do
4
+ def parse source
5
+ Hotcell::Template.parse(source)
6
+ end
7
+
8
+ describe '#render' do
9
+ specify { parse(
10
+ '{{ count = 42 }} {{ scope count: count + 8 }}{{ count }}{{ end scope }} {{ count }}'
11
+ ).render.should == '42 50 42' }
12
+ specify { parse(
13
+ '{{! captured = scope }}Hello{{ end scope }} {{ captured }}'
14
+ ).render(reraise: true).should == ' Hello' }
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hotcell::Commands::Unless do
4
+ def parse source
5
+ Hotcell::Template.parse(source)
6
+ end
7
+
8
+ describe '#validate!' do
9
+ specify { expect { parse('{{ unless }}{{ end unless }}').syntax
10
+ }.to raise_error Hotcell::ArgumentError, 'Wrond number of arguments for `unless` (0 for 1) at 1:4' }
11
+ specify { expect { parse('{{ unless true }}{{ else false }}{{ end unless }}').syntax
12
+ }.to raise_error Hotcell::ArgumentError, 'Wrond number of arguments for `else` (1 for 0) at 1:21' }
13
+ specify { expect { parse('{{ unless true }}{{ else }}{{ else }}{{ end unless }}').syntax
14
+ }.to raise_error Hotcell::BlockError, 'Unexpected `else` for `unless` command at 1:31' }
15
+ end
16
+
17
+ describe '#render' do
18
+ specify { parse('{{ unless true }}Hello{{ end unless }}').render.should == '' }
19
+ specify { parse('{{ unless false }}Hello{{ end unless }}').render.should == 'Hello' }
20
+ specify { parse('{{ unless true }}Hello{{ else }}World{{ end unless }}').render.should == 'World' }
21
+ specify { parse('{{ unless false }}Hello{{ else }}World{{ end unless }}').render.should == 'Hello' }
22
+ end
23
+ end
@@ -6,36 +6,48 @@ describe Hotcell::Config do
6
6
  let(:command_class) { Class.new(Hotcell::Command) }
7
7
  let(:block_class) do
8
8
  Class.new(Hotcell::Block) do
9
- subcommands :else, :elsif
9
+ subcommand :else
10
+ subcommand :elsif
10
11
  end
11
12
  end
12
13
  let(:misc_class) { Class.new }
13
14
 
14
15
  specify { subject.blocks.should == {} }
15
- specify { subject.subcommands.should == {} }
16
16
  specify { subject.commands.should == {} }
17
+ specify { subject.helpers.should == [] }
18
+ specify { subject.resolver.should be_a Hotcell::Resolver }
19
+
20
+ describe '#resolver=' do
21
+ let(:resolver) { Hotcell::FileSystemResolver.new('/') }
22
+ before { subject.resolver = resolver }
23
+ its(:resolver) { should == resolver }
24
+ end
17
25
 
18
26
  describe '#register_command' do
19
27
  context do
20
28
  before { subject.register_command :for, command_class }
21
- specify { subject.blocks.should == {} }
22
- specify { subject.subcommands.should == {} }
23
- specify { subject.commands.should == { 'for' => command_class } }
29
+ its(:blocks) { should == {} }
30
+ its(:commands) { should == { 'for' => command_class } }
24
31
  end
25
32
 
26
33
  context do
27
34
  before { subject.register_command 'for', block_class }
28
- specify { subject.blocks.should == { 'for' => block_class } }
29
- specify { subject.subcommands.should == { 'else' => block_class, 'elsif' => block_class } }
30
- specify { subject.commands.should == {} }
35
+ its(:blocks) { should == { 'for' => block_class } }
36
+ its(:commands) { should == {} }
31
37
  end
32
38
 
33
39
  context do
34
40
  before { subject.register_command 'for', block_class }
35
41
  before { subject.register_command :forloop, block_class }
36
42
  before { subject.register_command :include, command_class }
37
- specify { subject.blocks.should == { 'for' => block_class, 'forloop' => block_class } }
38
- specify { subject.commands.should == { 'include' => command_class } }
43
+ its(:blocks) { should == { 'for' => block_class, 'forloop' => block_class } }
44
+ its(:commands) { should == { 'include' => command_class } }
45
+ end
46
+
47
+ context do
48
+ before { subject.register_command 'for' => block_class, forloop: block_class, include: command_class }
49
+ its(:blocks) { should == { 'for' => block_class, 'forloop' => block_class } }
50
+ its(:commands) { should == { 'include' => command_class } }
39
51
  end
40
52
 
41
53
  context 'errors' do
@@ -54,4 +66,17 @@ describe Hotcell::Config do
54
66
  end
55
67
  end
56
68
  end
69
+
70
+ describe '#register_helpers' do
71
+ context do
72
+ 3.times do |i|
73
+ let("helper#{i+1}") { Module.new }
74
+ end
75
+ before { subject.register_helpers helper1, helper2 }
76
+ before { subject.register_helpers helper1 }
77
+ before { subject.register_helpers [helper3] }
78
+
79
+ its(:helpers) { should == [helper1, helper2, helper3] }
80
+ end
81
+ end
57
82
  end
@@ -1,25 +1,49 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Hotcell::Context do
4
- describe '#initialize' do
5
- its('scope.scope') { should == [{}] }
6
-
7
- context do
8
- subject { described_class.new(
9
- scope: { foo: 42, 'bar' => 'baz' },
10
- variables: { baz: 'moo' },
11
- boo: 'goo',
12
- 'taz' => 'man',
13
- rescuer: ->{},
14
- reraise: true
15
- ) }
16
- its('scope.scope') { should == [{foo: 42, 'bar' => 'baz', 'baz' => 'moo', 'boo' => 'goo', 'taz' => 'man'}] }
17
- end
4
+ its('scope.scope') { should == [{}] }
5
+ its(:helpers) { should be_a Hotcell::Manipulator }
18
6
 
19
- context do
20
- subject { described_class.new(variables: { foo: 42, 'bar' => 'baz' }, environment: { 'baz' => 'moo' }) }
21
- its('scope.scope') { should == [{'foo' => 42, 'bar' => 'baz', baz: 'moo'}] }
7
+ describe '#normalize_options' do
8
+ def result options = {}
9
+ default = { scope: {}, shared: {}, helpers: [], reraise: false, rescuer: described_class::DEFAULT_RESCUER }
10
+ default.merge options
22
11
  end
12
+ let(:rscr) { ->{} }
13
+
14
+ specify { subject.normalize_options({}).should == result }
15
+ specify { subject.normalize_options(foo: 42, 'bar' => 'baz').should == result(
16
+ scope: { 'foo' => 42, 'bar' => 'baz' }) }
17
+ specify { subject.normalize_options(foo: 42, scope: { bar: 'baz' }).should == result(
18
+ scope: { 'foo' => 42, bar: 'baz' }) }
19
+ specify { subject.normalize_options(foo: 42, variables: { bar: 'baz' }).should == result(
20
+ scope: { 'foo' => 42, 'bar' => 'baz' }) }
21
+ specify { subject.normalize_options(foo: 42, environment: { 'bar' => 'baz' }).should == result(
22
+ scope: { 'foo' => 42, bar: 'baz' }) }
23
+ specify { subject.normalize_options(shared: {resolver: 'resolver'}).should == result(
24
+ shared: {resolver: 'resolver'}) }
25
+ specify { subject.normalize_options(helpers: 'helper1').should == result(
26
+ helpers: ['helper1']) }
27
+ specify { subject.normalize_options(helpers: ['helper1', 'helper2']).should == result(
28
+ helpers: ['helper1', 'helper2']) }
29
+ specify { subject.normalize_options(reraise: nil).should == result(reraise: false) }
30
+ specify { subject.normalize_options(reraise: true).should == result(reraise: true) }
31
+ specify { subject.normalize_options(reraise: 'foo').should == result(reraise: true) }
32
+ specify { subject.normalize_options(rescuer: rscr).should == result(rescuer: rscr) }
33
+
34
+ specify { subject.normalize_options(
35
+ scope: { foo: 42, 'bar' => 'baz' },
36
+ variables: { baz: 'moo' },
37
+ environment: { hello: 'world' },
38
+ boo: 'goo',
39
+ 'taz' => 'man',
40
+ rescuer: rscr,
41
+ reraise: true
42
+ ).should == result(
43
+ scope: { foo: 42, hello: 'world', 'bar' => 'baz', 'baz' => 'moo', 'boo' => 'goo', 'taz' => 'man' },
44
+ rescuer: rscr,
45
+ reraise: true
46
+ ) }
23
47
  end
24
48
 
25
49
  describe '#safe' do
@@ -46,8 +70,24 @@ describe Hotcell::Context do
46
70
  end
47
71
 
48
72
  describe '#manipulator_invoke' do
49
- subject { described_class.new(variables: { foo: 42, 'bar' => 'baz' }, environment: { 'baz' => 'moo' }) }
73
+ subject { described_class.new(
74
+ variables: { foo: 42, 'bar' => 'baz' }, environment: { 'baz' => 'moo' },
75
+ helpers: Module.new do
76
+ def strip string
77
+ string.strip
78
+ end
79
+
80
+ def bar
81
+ 'bazzzzz'
82
+ end
83
+ end
84
+ ) }
50
85
  specify { subject.manipulator_invoke('foo').should == 42 }
51
86
  specify { subject.manipulator_invoke('moo').should be_nil }
87
+ specify { subject.manipulator_invoke('baz').should be_nil }
88
+ specify { subject.manipulator_invoke('bar').should == 'baz' }
89
+ specify { expect { subject.manipulator_invoke('bar', 42) }.to raise_error ArgumentError }
90
+ specify { expect { subject.manipulator_invoke('strip') }.to raise_error ArgumentError }
91
+ specify { subject.manipulator_invoke('strip', ' hello ').should == 'hello' }
52
92
  end
53
93
  end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
 
4
4
  describe Hotcell::Lexer do
5
5
  def scan template
6
- described_class.new(template).tokens
6
+ described_class.new(template).tokens.map { |token| [token[0], token[1][0]] }
7
7
  end
8
8
 
9
9
  def expression template
@@ -65,7 +65,8 @@ describe Hotcell::Lexer do
65
65
  context 'numeric' do
66
66
  context 'integer' do
67
67
  specify { expression('42').should == [[:INTEGER, 42]] }
68
- specify { expression('-42').should == [[:MINUS, '-'], [:INTEGER, 42]] }
68
+ specify { expression('-42').should == [[:INTEGER, -42]] }
69
+ specify { expression('- 42').should == [[:MINUS, '-'], [:INTEGER, 42]] }
69
70
  specify { expression('!42').should == [[:NOT, '!'], [:INTEGER, 42]] }
70
71
  specify { expression('42.').should == [[:INTEGER, 42], [:PERIOD, '.']] }
71
72
  specify { expression('42.foo').should == [[:INTEGER, 42], [:PERIOD, '.'], [:IDENTIFER, 'foo']] }
@@ -74,7 +75,8 @@ describe Hotcell::Lexer do
74
75
 
75
76
  context 'float' do
76
77
  specify { expression('42.42').should == [[:FLOAT, 42.42]] }
77
- specify { expression('-42.42').should == [[:MINUS, '-'], [:FLOAT, 42.42]] }
78
+ specify { expression('-42.42').should == [[:FLOAT, -42.42]] }
79
+ specify { expression('- 42.42').should == [[:MINUS, '-'], [:FLOAT, 42.42]] }
78
80
  specify { expression('!42.42').should == [[:NOT, '!'], [:FLOAT, 42.42]] }
79
81
  specify { expression('0.42').should == [[:FLOAT, 0.42]] }
80
82
  specify { expression('.42').should == [[:FLOAT, 0.42]] }
@@ -146,7 +148,8 @@ describe Hotcell::Lexer do
146
148
  specify { expression(%q{"fo\mo"}).should == [[:STRING, "fo\mo"]] }
147
149
  specify { expression(%q{"foo42"}).should == [[:STRING, "foo42"]] }
148
150
  specify { expression(%q{"привет"}).should == [[:STRING, "привет"]] }
149
- specify { expression(%q{"при\вет"}).should == [[:STRING, "при\вет"]] }
151
+ # RBX can not handle this
152
+ # specify { expression(%q{"при\вет"}).should == [[:STRING, "при\вет"]] }
150
153
 
151
154
  context do
152
155
  let(:strings) { data 'dstrings' }
@@ -168,8 +171,11 @@ describe Hotcell::Lexer do
168
171
  specify { expression('/regexp/x').should == [[:REGEXP, /regexp/x]] }
169
172
  specify { expression('/regexp/sdmfri').should == [[:REGEXP, /regexp/im]] }
170
173
  specify { expression('/\.*/').should == [[:REGEXP, /\.*/]] }
171
- specify { expression('/\//').should == [[:REGEXP, /\//]] }
172
- specify { expression('/\//ix').should == [[:REGEXP, /\//ix]] }
174
+ # Funny ruby 2.0 bug. regexp1.to_s == regexp2.to_s, but regexp1 != regexp2
175
+ # specify { expression('/\//').should == [[:REGEXP, /\//]] }
176
+ # specify { expression('/\//ix').should == [[:REGEXP, /\//ix]] }
177
+ specify { expression('/\//').to_s.should == [[:REGEXP, /\//]].to_s }
178
+ specify { expression('/\//ix').to_s.should == [[:REGEXP, /\//ix]].to_s }
173
179
  specify { expression('/регексп/').should == [[:REGEXP, /регексп/]] }
174
180
  specify { expression('/регексп/m').should == [[:REGEXP, /регексп/m]] }
175
181
 
@@ -219,32 +225,32 @@ describe Hotcell::Lexer do
219
225
  end
220
226
 
221
227
  context 'errors' do
222
- describe Hotcell::Errors::UnexpectedSymbol do
223
- let(:error) { Hotcell::Errors::UnexpectedSymbol }
228
+ describe Hotcell::UnexpectedSymbol do
229
+ let(:error) { Hotcell::UnexpectedSymbol }
224
230
 
225
- specify { expect { expression("hello @world") }.to raise_error(error, /1:10/) }
226
- specify { expect { expression("@hello world") }.to raise_error(error, /1:4/) }
227
- specify { expect { expression("hello world@") }.to raise_error(error, /1:15/) }
228
- specify { expect { expression("hello\n@ world") }.to raise_error(error, /2:1/) }
229
- specify { expect { expression("hello\n @world") }.to raise_error(error, /2:2/) }
230
- specify { expect { expression("hello\n world@") }.to raise_error(error, /2:7/) }
231
- specify { expect { expression("hello@\n world") }.to raise_error(error, /1:9/) }
232
- specify { expect { expression("@hello\n world") }.to raise_error(error, /1:4/) }
233
- specify { expect { expression("'привет' @ 'мир'") }.to raise_error(error, /1:13/) }
231
+ specify { expect { expression("hello @world") }.to raise_error(error, /`@`.*1:10/) }
232
+ specify { expect { expression("@hello world") }.to raise_error(error, /`@`.*1:4/) }
233
+ specify { expect { expression("hello world@") }.to raise_error(error, /`@`.*1:15/) }
234
+ specify { expect { expression("hello\n@ world") }.to raise_error(error, /`@`.*2:1/) }
235
+ specify { expect { expression("hello\n @world") }.to raise_error(error, /`@`.*2:2/) }
236
+ specify { expect { expression("hello\n world@") }.to raise_error(error, /`@`.*2:7/) }
237
+ specify { expect { expression("hello@\n world") }.to raise_error(error, /`@`.*1:9/) }
238
+ specify { expect { expression("@hello\n world") }.to raise_error(error, /`@`.*1:4/) }
239
+ specify { expect { expression("'привет' @ 'мир'") }.to raise_error(error, /`@`.*1:13/) }
234
240
  end
235
241
 
236
- describe Hotcell::Errors::UnterminatedString do
237
- let(:error) { Hotcell::Errors::UnterminatedString }
242
+ describe Hotcell::UnterminatedString do
243
+ let(:error) { Hotcell::UnterminatedString }
238
244
 
239
- specify { expect { expression("hello 'world") }.to raise_error(error, /1:10/) }
240
- specify { expect { expression("hello\nwor'ld") }.to raise_error(error, /2:4/) }
241
- specify { expect { expression("hello 'world\\'") }.to raise_error(error, /1:10/) }
242
- specify { expect { expression("hello 'wor\\'ld") }.to raise_error(error, /1:10/) }
243
- specify { expect { expression("\"hello world") }.to raise_error(error, /1:4/) }
244
- specify { expect { expression("he\"llo\\\" world") }.to raise_error(error, /1:6/) }
245
- specify { expect { expression("he\"llo\\\" \nworld") }.to raise_error(error, /1:6/) }
246
- specify { expect { expression("\"hello\\\"\n world") }.to raise_error(error, /1:4/) }
247
- specify { expect { expression("'привет' 'мир") }.to raise_error(error, /1:13/) }
245
+ specify { expect { expression("hello 'world") }.to raise_error(error, /`'world }}`.*1:10/) }
246
+ specify { expect { expression("hello\nwor'ld") }.to raise_error(error, /`'ld }}`.*2:4/) }
247
+ specify { expect { expression("hello 'world\\'") }.to raise_error(error, /`'world\\' }}`.*1:10/) }
248
+ specify { expect { expression("hello 'wor\\'ld") }.to raise_error(error, /`'wor\\'ld }}`.*1:10/) }
249
+ specify { expect { expression("\"hello world") }.to raise_error(error, /`"hello world }}`.*1:4/) }
250
+ specify { expect { expression("he\"llo\\\" world") }.to raise_error(error, /`"llo\\" world }}`.*1:6/) }
251
+ specify { expect { expression("he\"llo\\\" \nworld") }.to raise_error(error, /`"llo\\" \nworld }}`.*1:6/) }
252
+ specify { expect { expression("\"hello\\\"\n world") }.to raise_error(error, /`"hello\\"\n world }}`.*1:4/) }
253
+ specify { expect { expression("'привет' 'мир") }.to raise_error(error, /`'мир }}`.*1:13/) }
248
254
  end
249
255
  end
250
256
 
@@ -326,6 +332,9 @@ describe Hotcell::Lexer do
326
332
 
327
333
  context 'template comments' do
328
334
  specify { scan('{{#').should == [[:COMMENT, "{{#"]] }
335
+ specify { scan('{{{#').should == [[:TOPEN, "{{"], [:HOPEN, "{"], [:COMMENT, "#"]] }
336
+ specify { scan('{{{{#').should == [[:TOPEN, "{{"], [:HOPEN, "{"], [:HOPEN, "{"], [:COMMENT, "#"]] }
337
+ specify { scan('{#').should == [[:TEMPLATE, "{#"]] }
329
338
  specify { scan('{{# }}').should == [[:COMMENT, "{{# }}"]] }
330
339
  specify { scan('{{##}}').should == [[:COMMENT, "{{##}}"]] }
331
340
  specify { scan('{{###}}').should == [[:COMMENT, "{{###}}"]] }
@@ -21,32 +21,33 @@ describe Hotcell::Block do
21
21
 
22
22
  let(:context) { Hotcell::Context.new }
23
23
 
24
- describe 'complex parsing and rendering' do
24
+ context 'complex parsing and rendering' do
25
25
  def parse source
26
26
  Hotcell::Template.parse(source)
27
27
  end
28
28
 
29
29
  let(:if_tag) do
30
30
  Class.new(described_class) do
31
- subcommands :else, :elsif
31
+ subcommand :else
32
+ subcommand :elsif
32
33
 
33
34
  def validate!
34
- names = subcommands.map { |subcommand| subcommand[:name] }
35
+ names = subcommands.map { |subcommand| subcommand.name }
35
36
  valid = names.empty? || (
36
- names.any? && names.last.in?('elsif', 'else') &&
37
- names[0..-2].uniq.in?(['elsif'], [])
37
+ names.any? && %w(elsif else).include?(names.last) &&
38
+ [['elsif'], []].include?(names[0..-2].uniq)
38
39
  )
39
- raise Hotcell::Errors::BlockError.new 'Invalid if syntax' unless valid
40
+ raise Hotcell::BlockError.new 'Invalid if syntax', *position_info unless valid
40
41
  end
41
42
 
42
- def process context, subnodes, condition
43
+ def process context, condition
43
44
  conditions = [[condition]]
44
45
  subnodes.each do |subnode|
45
- if subnode.is_a?(Hash)
46
+ if subnode.is_a?(Hotcell::Command)
46
47
  conditions.last[1] = '' if conditions.last[1] == nil
47
- conditions << (subnode[:name] == 'elsif' ? [subnode[:args].first] : [true])
48
+ conditions << (subnode.name == 'elsif' ? [subnode.render_children(context).first] : [true])
48
49
  else
49
- conditions.last[1] = subnode
50
+ conditions.last[1] = subnode.render(context)
50
51
  end
51
52
  end
52
53
  condition = conditions.detect { |condition| !!condition[0] }
@@ -57,7 +58,11 @@ describe Hotcell::Block do
57
58
 
58
59
  before { Hotcell.stub(:commands) { {} } }
59
60
  before { Hotcell.stub(:blocks) { { 'if' => if_tag } } }
60
- before { Hotcell.stub(:subcommands) { { 'elsif' => if_tag, 'else' => if_tag } } }
61
+
62
+ specify { parse('{{ if true }}Hello{{ end if }}').render.should == 'Hello' }
63
+ specify { parse('{{! if true }}Hello{{ end if }}').render.should == '' }
64
+ specify { parse('{{ res = if true }}Hello{{ end if }} {{ res }}').render.should == 'Hello Hello' }
65
+ specify { parse('{{! res = if true }}Hello{{ end if }} {{ res }}').render.should == ' Hello' }
61
66
 
62
67
  context do
63
68
  subject(:template) { parse(<<-SOURCE
@@ -91,79 +96,46 @@ describe Hotcell::Block do
91
96
  context do
92
97
  specify { expect {
93
98
  parse("{{ if value == 'hello' }}{{ else }}world{{ elsif }}{{ end if }}").syntax
94
- }.to raise_error Hotcell::Errors::BlockError }
99
+ }.to raise_error Hotcell::BlockError }
95
100
  end
96
101
  end
97
102
 
98
103
  describe '.subcommands' do
99
104
  subject { Class.new(described_class) }
100
105
 
101
- before { subject.subcommands :elsif, :else }
102
- its(:subcommands) { should == %w(elsif else) }
106
+ before { subject.subcommand elsif: Class.new(Hotcell::Command), else: Class.new(Hotcell::Command) }
107
+ its('subcommands.keys') { should == %w(elsif else) }
103
108
 
104
109
  context do
105
- before { subject.subcommands :when }
106
- its(:subcommands) { should == %w(elsif else when) }
110
+ before { subject.subcommand :when }
111
+ its('subcommands.keys') { should == %w(elsif else when) }
107
112
  end
108
113
  end
109
114
 
110
115
  describe '#subcommands' do
111
116
  specify { described_class.new('if',
112
- subnodes: [{name: 'elsif'}, JOINER(), {name: 'else'}, JOINER()]).subcommands.should == [
113
- {name: 'elsif'}, {name: 'else'}] }
114
- end
115
-
116
- describe '#render_subnodes' do
117
- specify { described_class.new('if',
118
- subnodes: [{name: 'elsif', args: ARRAY(3, 5)}, JOINER(42), {name: 'else'}, JOINER(43)]
119
- ).render_subnodes(context).should == [
120
- {name: 'elsif', args: [3, 5]}, '42', {name: 'else'}, '43'
121
- ] }
117
+ subnodes: [COMMAND('elsif'), JOINER(), COMMAND('else'), JOINER()]).subcommands.should == [
118
+ COMMAND('elsif'), COMMAND('else')
119
+ ] }
122
120
  end
123
121
 
124
122
  describe '#render' do
125
123
  let(:block) do
126
124
  Class.new(described_class) do
127
- def process context, subnodes, condition
125
+ def process context, condition
128
126
  "condition #{condition}"
129
127
  end
130
128
  end
131
129
  end
132
130
 
133
131
  specify { block.new('if').render(context).should =~ /ArgumentError/ }
134
- specify { block.new('if', mode: :silence).render(context).should =~ /ArgumentError/ }
135
132
  specify { block.new('if', true).render(context).should == "condition true" }
136
- specify { block.new('if', true, mode: :silence).render(context).should == '' }
137
-
138
- context 'assigning' do
139
- before { subject.render(context) }
140
-
141
- context do
142
- subject { block.new('if', assign: 'result') }
143
- specify { context.key?('result').should be_false }
144
- end
145
-
146
- context do
147
- subject { block.new('if', mode: :silence, assign: 'result') }
148
- specify { context.key?('result').should be_false }
149
- end
150
-
151
- context do
152
- subject { block.new('if', true, assign: 'result') }
153
- specify { context['result'].should == "condition true" }
154
- end
155
-
156
- context do
157
- subject { block.new('if', 42, mode: :silence, assign: 'result') }
158
- specify { context['result'].should == "condition 42" }
159
- end
160
- end
161
133
  end
162
134
 
163
135
  context '#validate!' do
164
136
  let(:block) do
165
137
  Class.new(described_class) do
166
- subcommands :else
138
+ subcommand :else
167
139
  def process context, subnodes, condition
168
140
  "condition #{condition}"
169
141
  end
@@ -176,12 +148,12 @@ describe Hotcell::Block do
176
148
  end
177
149
 
178
150
  context do
179
- subject { block.new('if', true, subnodes: [{ name: 'else' }] ) }
151
+ subject { block.new('if', true, subnodes: [COMMAND('else')] ) }
180
152
  specify { expect { subject.validate! }.not_to raise_error }
181
153
  end
182
154
 
183
155
  context do
184
- subject { block.new('if', true, subnodes: [{ name: 'case' }] ) }
156
+ subject { block.new('if', true, subnodes: [COMMAND('case')] ) }
185
157
  specify { expect { subject.validate! }.to raise_error }
186
158
  end
187
159
  end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Hotcell::Command do
4
4
  let(:context) { Hotcell::Context.new }
5
5
 
6
- describe 'complex parsing and rendering' do
6
+ context 'complex parsing and rendering' do
7
7
  def parse source
8
8
  Hotcell::Template.parse(source)
9
9
  end
@@ -11,7 +11,7 @@ describe Hotcell::Command do
11
11
  let(:include_tag) do
12
12
  Class.new(described_class) do
13
13
  def validate!
14
- raise Hotcell::Errors::ArgumentError.new('Template path is required') if children.count != 1
14
+ raise Hotcell::ArgumentError.new('Template path is required', *position_info) if children.count != 1
15
15
  end
16
16
 
17
17
  def process context, path
@@ -24,10 +24,12 @@ describe Hotcell::Command do
24
24
  before { Hotcell.stub(:blocks) { {} } }
25
25
  before { Hotcell.stub(:subcommands) { {} } }
26
26
 
27
+ specify { expect { parse("{{ include }}").syntax }.to raise_error Hotcell::ArgumentError }
27
28
  specify { parse("{{ include 'template/path' }}").render(context).should == 'included template/path' }
28
- specify { expect {
29
- parse("{{ include }}").syntax
30
- }.to raise_error Hotcell::Errors::ArgumentError }
29
+ specify { parse("{{ include 'template/path' }}").render.should == 'included template/path' }
30
+ specify { parse("{{! include 'template/path' }}").render.should == '' }
31
+ specify { parse("{{ res = include 'template/path' }} {{ res }}").render.should == 'included template/path included template/path' }
32
+ specify { parse("{{! res = include 'template/path' }} {{ res }}").render.should == ' included template/path' }
31
33
  end
32
34
 
33
35
  describe '#render' do
@@ -40,32 +42,6 @@ describe Hotcell::Command do
40
42
  end
41
43
 
42
44
  specify { command.new('include').render(context).should =~ /ArgumentError/ }
43
- specify { command.new('include', mode: :silence).render(context).should =~ /ArgumentError/ }
44
45
  specify { command.new('include', 'path').render(context).should == "rendered path" }
45
- specify { command.new('include', 'path', mode: :silence).render(context).should == '' }
46
-
47
- context 'assigning' do
48
- before { subject.render(context) }
49
-
50
- context do
51
- subject { command.new('include', assign: 'inclusion') }
52
- specify { context.key?('inclusion').should be_false }
53
- end
54
-
55
- context do
56
- subject { command.new('include', mode: :silence, assign: 'inclusion') }
57
- specify { context.key?('inclusion').should be_false }
58
- end
59
-
60
- context do
61
- subject { command.new('include', 'path', assign: 'inclusion') }
62
- specify { context['inclusion'].should == "rendered path" }
63
- end
64
-
65
- context do
66
- subject { command.new('include', 'path', mode: :silence, assign: 'inclusion') }
67
- specify { context['inclusion'].should == "rendered path" }
68
- end
69
- end
70
46
  end
71
47
  end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hotcell::Tag do
4
+ let(:context) { Hotcell::Context.new }
5
+
6
+ context 'complex parsing and rendering' do
7
+ def parse source
8
+ Hotcell::Template.parse(source)
9
+ end
10
+
11
+ specify { parse("{{ 'Hello' }}").render.should == 'Hello' }
12
+ specify { parse("{{! 'Hello' }}").render.should == '' }
13
+ specify { parse("{{ res = 'Hello' }} {{ res }}").render.should == 'Hello Hello' }
14
+ specify { parse("{{! res = 'Hello' }} {{ res }}").render.should == ' Hello' }
15
+ end
16
+ end