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
@@ -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