curlybars 0.9.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/lib/curlybars.rb +108 -0
  3. data/lib/curlybars/configuration.rb +41 -0
  4. data/lib/curlybars/dependency_tracker.rb +8 -0
  5. data/lib/curlybars/error/base.rb +18 -0
  6. data/lib/curlybars/error/compile.rb +11 -0
  7. data/lib/curlybars/error/lex.rb +22 -0
  8. data/lib/curlybars/error/parse.rb +41 -0
  9. data/lib/curlybars/error/presenter/not_found.rb +23 -0
  10. data/lib/curlybars/error/render.rb +11 -0
  11. data/lib/curlybars/error/validate.rb +18 -0
  12. data/lib/curlybars/lexer.rb +60 -0
  13. data/lib/curlybars/method_whitelist.rb +69 -0
  14. data/lib/curlybars/node/block_helper_else.rb +108 -0
  15. data/lib/curlybars/node/boolean.rb +24 -0
  16. data/lib/curlybars/node/each_else.rb +69 -0
  17. data/lib/curlybars/node/if_else.rb +33 -0
  18. data/lib/curlybars/node/item.rb +31 -0
  19. data/lib/curlybars/node/literal.rb +28 -0
  20. data/lib/curlybars/node/option.rb +25 -0
  21. data/lib/curlybars/node/output.rb +24 -0
  22. data/lib/curlybars/node/partial.rb +24 -0
  23. data/lib/curlybars/node/path.rb +137 -0
  24. data/lib/curlybars/node/root.rb +29 -0
  25. data/lib/curlybars/node/string.rb +24 -0
  26. data/lib/curlybars/node/template.rb +32 -0
  27. data/lib/curlybars/node/text.rb +24 -0
  28. data/lib/curlybars/node/unless_else.rb +33 -0
  29. data/lib/curlybars/node/variable.rb +34 -0
  30. data/lib/curlybars/node/with_else.rb +54 -0
  31. data/lib/curlybars/parser.rb +183 -0
  32. data/lib/curlybars/position.rb +7 -0
  33. data/lib/curlybars/presenter.rb +288 -0
  34. data/lib/curlybars/processor/tilde.rb +31 -0
  35. data/lib/curlybars/processor/token_factory.rb +9 -0
  36. data/lib/curlybars/railtie.rb +18 -0
  37. data/lib/curlybars/rendering_support.rb +222 -0
  38. data/lib/curlybars/safe_buffer.rb +11 -0
  39. data/lib/curlybars/template_handler.rb +93 -0
  40. data/lib/curlybars/version.rb +3 -0
  41. data/spec/acceptance/application_layout_spec.rb +60 -0
  42. data/spec/acceptance/collection_blocks_spec.rb +28 -0
  43. data/spec/acceptance/global_helper_spec.rb +25 -0
  44. data/spec/curlybars/configuration_spec.rb +57 -0
  45. data/spec/curlybars/error/base_spec.rb +41 -0
  46. data/spec/curlybars/error/compile_spec.rb +19 -0
  47. data/spec/curlybars/error/lex_spec.rb +25 -0
  48. data/spec/curlybars/error/parse_spec.rb +74 -0
  49. data/spec/curlybars/error/render_spec.rb +19 -0
  50. data/spec/curlybars/error/validate_spec.rb +19 -0
  51. data/spec/curlybars/lexer_spec.rb +466 -0
  52. data/spec/curlybars/method_whitelist_spec.rb +168 -0
  53. data/spec/curlybars/processor/tilde_spec.rb +60 -0
  54. data/spec/curlybars/rendering_support_spec.rb +426 -0
  55. data/spec/curlybars/safe_buffer_spec.rb +46 -0
  56. data/spec/curlybars/template_handler_spec.rb +222 -0
  57. data/spec/integration/cache_spec.rb +124 -0
  58. data/spec/integration/comment_spec.rb +60 -0
  59. data/spec/integration/exception_spec.rb +31 -0
  60. data/spec/integration/node/block_helper_else_spec.rb +422 -0
  61. data/spec/integration/node/each_else_spec.rb +204 -0
  62. data/spec/integration/node/each_spec.rb +291 -0
  63. data/spec/integration/node/escape_spec.rb +27 -0
  64. data/spec/integration/node/helper_spec.rb +176 -0
  65. data/spec/integration/node/if_else_spec.rb +129 -0
  66. data/spec/integration/node/if_spec.rb +143 -0
  67. data/spec/integration/node/output_spec.rb +68 -0
  68. data/spec/integration/node/partial_spec.rb +66 -0
  69. data/spec/integration/node/path_spec.rb +286 -0
  70. data/spec/integration/node/root_spec.rb +15 -0
  71. data/spec/integration/node/template_spec.rb +86 -0
  72. data/spec/integration/node/unless_else_spec.rb +129 -0
  73. data/spec/integration/node/unless_spec.rb +130 -0
  74. data/spec/integration/node/with_spec.rb +116 -0
  75. data/spec/integration/processor/tilde_spec.rb +38 -0
  76. data/spec/integration/processors_spec.rb +30 -0
  77. metadata +358 -0
@@ -0,0 +1,28 @@
1
+ describe "Collection blocks", type: :request do
2
+ before do
3
+ Curlybars.configure do |config|
4
+ config.presenters_namespace = 'curlybars_presenters'
5
+ end
6
+ end
7
+
8
+ example "Rendering collections" do
9
+ get '/categories'
10
+
11
+ expect(body).to eq(<<-HTML.strip_heredoc)
12
+ <html>
13
+ <head>
14
+ <title>Dummy app</title>
15
+ </head>
16
+ <body>
17
+
18
+ <h1>This are the categories</h1>
19
+
20
+ <p>One</p>
21
+ <p>Two</p>
22
+
23
+
24
+ </body>
25
+ </html>
26
+ HTML
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ describe "Collection blocks", type: :request do
2
+ before do
3
+ Curlybars.configure do |config|
4
+ config.presenters_namespace = 'curlybars_presenters'
5
+ config.global_helpers_provider_classes = [GlobalHelpers]
6
+ end
7
+ end
8
+
9
+ example "Render a global helper" do
10
+ get '/welcome'
11
+
12
+ expect(body).to eq(<<-HTML.strip_heredoc)
13
+ <html>
14
+ <head>
15
+ <title>Dummy app</title>
16
+ </head>
17
+ <body>
18
+ Login as: Admin
19
+ Account: Testing
20
+
21
+ </body>
22
+ </html>
23
+ HTML
24
+ end
25
+ end
@@ -0,0 +1,57 @@
1
+ describe Curlybars::Configuration do
2
+ after do
3
+ Curlybars.reset
4
+ end
5
+
6
+ describe "#presenters_namespace" do
7
+ it "defaults to an empty string" do
8
+ presenters_namespace = Curlybars::Configuration.new.presenters_namespace
9
+
10
+ expect(presenters_namespace).to eq('')
11
+ end
12
+ end
13
+
14
+ describe "#presenters_namespace=" do
15
+ it "can set value" do
16
+ config = Curlybars::Configuration.new
17
+ config.presenters_namespace = 'foo'
18
+ expect(config.presenters_namespace).to eq('foo')
19
+ end
20
+ end
21
+
22
+ describe "#custom_processors" do
23
+ it "can set value" do
24
+ config = Curlybars::Configuration.new
25
+ config.custom_processors = ['test']
26
+ expect(config.custom_processors).to eq(['test'])
27
+ end
28
+ end
29
+
30
+ describe ".configure" do
31
+ before do
32
+ Curlybars.configure do |config|
33
+ config.presenters_namespace = 'bar'
34
+ end
35
+ end
36
+
37
+ it "returns correct value for presenters_namespace" do
38
+ presenters_namespace = Curlybars.configuration.presenters_namespace
39
+
40
+ expect(presenters_namespace).to eq('bar')
41
+ end
42
+ end
43
+
44
+ describe ".reset" do
45
+ it "resets the configuration to default value" do
46
+ Curlybars.configure do |config|
47
+ config.presenters_namespace = 'foobarbaz'
48
+ end
49
+
50
+ Curlybars.reset
51
+
52
+ presenters_namespace = Curlybars.configuration.presenters_namespace
53
+
54
+ expect(presenters_namespace).to eq('')
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,41 @@
1
+ describe Curlybars::Error::Base do
2
+ let(:position) do
3
+ OpenStruct.new(line_number: 1, line_offset: 0)
4
+ end
5
+
6
+ it "creates an exception with the given message" do
7
+ message = "message"
8
+
9
+ exception = Curlybars::Error::Base.new('id', message, position)
10
+
11
+ expect(exception.message).to be message
12
+ end
13
+
14
+ it "doesn't change the backtrace for nil position.file_name" do
15
+ exception = Curlybars::Error::Base.new('id', 'message', position)
16
+
17
+ expect(exception.backtrace).to be nil
18
+ end
19
+
20
+ it "sets the right backtrace for non-nil position.file_name" do
21
+ position.file_name = 'template.hbs'
22
+
23
+ exception = Curlybars::Error::Base.new('id', 'message', position)
24
+
25
+ expect(exception.backtrace).not_to be nil
26
+ end
27
+
28
+ it "sets the position as an instance varaible" do
29
+ exception = Curlybars::Error::Base.new('id', 'message', position)
30
+
31
+ expect(exception.position).to be position
32
+ end
33
+
34
+ it "sets the id as an instance varaible" do
35
+ id = 'id'
36
+
37
+ exception = Curlybars::Error::Base.new(id, 'message', position)
38
+
39
+ expect(exception.id).to be id
40
+ end
41
+ end
@@ -0,0 +1,19 @@
1
+ describe Curlybars::Error::Compile do
2
+ let(:position) do
3
+ OpenStruct.new(
4
+ line_number: 2,
5
+ line_offset: 3,
6
+ stream_offset: 14,
7
+ length: 3,
8
+ file_name: 'template.hbs'
9
+ )
10
+ end
11
+
12
+ it "prefixes the id with `compile.`" do
13
+ id = 'id'
14
+
15
+ exception = Curlybars::Error::Compile.new(id, 'message', position)
16
+
17
+ expect(exception.id).to eq 'compile.%s' % id
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ describe Curlybars::Error::Lex do
2
+ let(:file_name) { 'template.hbs' }
3
+
4
+ let(:source) { "first_line\n0123456789\nthird_line" }
5
+
6
+ let(:exception) do
7
+ OpenStruct.new(line_number: 2, line_offset: 2)
8
+ end
9
+
10
+ it "creates an exception with a meaningful message" do
11
+ message = ".. 01 `2` 3456789 .. is not permitted symbol in this context"
12
+
13
+ lex_exception = Curlybars::Error::Lex.new(source, file_name, exception)
14
+
15
+ expect(lex_exception.message).to eq message
16
+ end
17
+
18
+ it "figures out a position from the exception" do
19
+ backtrace = "%s:%d:%d" % [file_name, exception.line_number, exception.line_offset]
20
+
21
+ lex_exception = Curlybars::Error::Lex.new(source, file_name, exception)
22
+
23
+ expect(lex_exception.backtrace).to eq [backtrace]
24
+ end
25
+ end
@@ -0,0 +1,74 @@
1
+ describe Curlybars::Error::Parse do
2
+ let(:source) { "first_line\n0123456789\nthird_line" }
3
+
4
+ describe "with current token not being EOS" do
5
+ let(:position) do
6
+ OpenStruct.new(
7
+ line_number: 2,
8
+ line_offset: 3,
9
+ length: 3,
10
+ file_name: 'template.hbs'
11
+ )
12
+ end
13
+
14
+ let(:exception) do
15
+ current = double(:current, position: position, type: :type)
16
+ double(:exception, current: current)
17
+ end
18
+
19
+ it "creates an exception with a meaningful message" do
20
+ message = ".. 012 `345` 6789 .. is not permitted in this context"
21
+
22
+ lex_exception = Curlybars::Error::Parse.new(source, exception)
23
+
24
+ expect(lex_exception.message).to eq message
25
+ end
26
+
27
+ it "figures out a position from the exception" do
28
+ backtrace = "%s:%d:%d" % [position.file_name, position.line_number, position.line_offset]
29
+
30
+ lex_exception = Curlybars::Error::Parse.new(source, exception)
31
+
32
+ expect(lex_exception.backtrace).to eq [backtrace]
33
+ end
34
+ end
35
+
36
+ describe "with current token being EOS" do
37
+ let(:exception) do
38
+ current = double(:current, position: nil, type: :EOS)
39
+ double(:exception, current: current)
40
+ end
41
+
42
+ it "creates an exception with a meaningful message" do
43
+ message = "A block helper hasn't been closed properly"
44
+
45
+ lex_exception = Curlybars::Error::Parse.new(source, exception)
46
+
47
+ expect(lex_exception.message).to eq message
48
+ end
49
+
50
+ it "creates an exception whose position contains the right line_number" do
51
+ lex_exception = Curlybars::Error::Parse.new(source, exception)
52
+
53
+ expect(lex_exception.position.line_number).to be 3
54
+ end
55
+
56
+ it "creates an exception whose position contains the right line_offset" do
57
+ lex_exception = Curlybars::Error::Parse.new(source, exception)
58
+
59
+ expect(lex_exception.position.line_offset).to be_zero
60
+ end
61
+
62
+ it "creates an exception whose position contains the right length" do
63
+ lex_exception = Curlybars::Error::Parse.new(source, exception)
64
+
65
+ expect(lex_exception.position.length).to be 'third_line'.length
66
+ end
67
+
68
+ it "creates an exception whose position contains a nil file_name" do
69
+ lex_exception = Curlybars::Error::Parse.new(source, exception)
70
+
71
+ expect(lex_exception.position.file_name).to be_nil
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,19 @@
1
+ describe Curlybars::Error::Render do
2
+ let(:position) do
3
+ OpenStruct.new(
4
+ line_number: 2,
5
+ line_offset: 3,
6
+ stream_offset: 14,
7
+ length: 3,
8
+ file_name: 'template.hbs'
9
+ )
10
+ end
11
+
12
+ it "prefixes the id with `render.`" do
13
+ id = 'id'
14
+
15
+ exception = Curlybars::Error::Render.new(id, 'message', position)
16
+
17
+ expect(exception.id).to eq 'render.%s' % id
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ describe Curlybars::Error::Validate do
2
+ let(:position) do
3
+ OpenStruct.new(
4
+ line_number: 2,
5
+ line_offset: 3,
6
+ stream_offset: 14,
7
+ length: 3,
8
+ file_name: 'template.hbs'
9
+ )
10
+ end
11
+
12
+ it "prefixes the id with `validate.`" do
13
+ id = 'id'
14
+
15
+ exception = Curlybars::Error::Validate.new(id, 'message', position)
16
+
17
+ expect(exception.id).to eq 'validate.%s' % id
18
+ end
19
+ end
@@ -0,0 +1,466 @@
1
+ # rubocop:disable Layout/AlignArray
2
+ describe Curlybars::Lexer do
3
+ describe "{{!-- ... --}}" do
4
+ it "skips begin of block comment" do
5
+ expect(lex('{{!--')).to produce []
6
+ end
7
+
8
+ it "skips begin and end of block comment" do
9
+ expect(lex('{{!----}}')).to produce []
10
+ end
11
+
12
+ it "skips a comment block containing curlybar code" do
13
+ expect(lex('{{!--{{helper}}--}}')).to produce []
14
+ end
15
+
16
+ it "is resilient to whitespaces" do
17
+ expect(lex('{{!-- --}}')).to produce []
18
+ end
19
+
20
+ it "is resilient to newlines" do
21
+ expect(lex("{{!--\n--}}")).to produce []
22
+ end
23
+
24
+ it "is skipped when present in plain text" do
25
+ expect(lex('text {{!----}} text')).to produce [:TEXT, :TEXT]
26
+ end
27
+ end
28
+
29
+ describe "{{! ... }}" do
30
+ it "skips begin of block comment" do
31
+ expect(lex('{{!')).to produce []
32
+ end
33
+
34
+ it "skips begin and end of block comment" do
35
+ expect(lex('{{!}}')).to produce []
36
+ end
37
+
38
+ it "is resilient to whitespaces" do
39
+ expect(lex('{{! }}')).to produce []
40
+ end
41
+ it "is resilient to newlines" do
42
+ expect(lex("{{!\n}}")).to produce []
43
+ end
44
+
45
+ it "is lexed when present in plain text" do
46
+ expect(lex('text {{!}} text')).to produce [:TEXT, :TEXT]
47
+ end
48
+ end
49
+
50
+ describe "{{<integer>}}" do
51
+ it "is lexed as an integer" do
52
+ expect(lex("{{7}}")).to produce [:START, :LITERAL, :END]
53
+ end
54
+
55
+ it "returns the expressed integer" do
56
+ literal_token = lex("{{7}}").detect { |token| token.type == :LITERAL }
57
+ expect(literal_token.value).to eq 7
58
+ end
59
+ end
60
+
61
+ describe "{{<boolean>}}" do
62
+ it "{{true}} is lexed as boolean" do
63
+ expect(lex("{{true}}")).to produce [:START, :LITERAL, :END]
64
+ end
65
+
66
+ it "{{false}} is lexed as boolean" do
67
+ expect(lex("{{false}}")).to produce [:START, :LITERAL, :END]
68
+ end
69
+
70
+ it "returns the expressed boolean" do
71
+ literal_token = lex("{{true}}").detect { |token| token.type == :LITERAL }
72
+ expect(literal_token.value).to be_truthy
73
+ end
74
+ end
75
+
76
+ describe "{{''}}" do
77
+ it "is lexed as a literal" do
78
+ expect(lex("{{''}}")).to produce [:START, :LITERAL, :END]
79
+ end
80
+
81
+ it "returns the string between quotes" do
82
+ literal_token = lex("{{'string'}}").detect { |token| token.type == :LITERAL }
83
+ expect(literal_token.value).to eq '"string"'
84
+ end
85
+
86
+ it "is resilient to whitespaces" do
87
+ expect(lex("{{ '' }}")).to produce [:START, :LITERAL, :END]
88
+ end
89
+
90
+ it "is lexed when present in plain text" do
91
+ expect(lex("text {{''}} text")).to produce [:TEXT, :START, :LITERAL, :END, :TEXT]
92
+ end
93
+ end
94
+
95
+ describe '{{""}}' do
96
+ it "is lexed as a literal" do
97
+ expect(lex('{{""}}')).to produce [:START, :LITERAL, :END]
98
+ end
99
+
100
+ it "returns the string between quotes" do
101
+ literal_token = lex('{{"string"}}').detect { |token| token.type == :LITERAL }
102
+ expect(literal_token.value).to eq '"string"'
103
+ end
104
+
105
+ it "is resilient to whitespaces" do
106
+ expect(lex('{{ "" }}')).to produce [:START, :LITERAL, :END]
107
+ end
108
+
109
+ it "is lexed when present in plain text" do
110
+ expect(lex('text {{""}} text')).to produce [:TEXT, :START, :LITERAL, :END, :TEXT]
111
+ end
112
+ end
113
+
114
+ describe "{{@variable}}" do
115
+ it "is lexed as a varaible" do
116
+ expect(lex('{{@var}}')).to produce [:START, :VARIABLE, :END]
117
+ end
118
+
119
+ it "returns the identifier after `@`" do
120
+ variable_token = lex('{{@var}}').detect { |token| token.type == :VARIABLE }
121
+ expect(variable_token.value).to eq 'var'
122
+ end
123
+
124
+ it "returns the identifier after `@` also when using `../`" do
125
+ variable_token = lex('{{@../var}}').detect { |token| token.type == :VARIABLE }
126
+ expect(variable_token.value).to eq '../var'
127
+ end
128
+
129
+ it "is resilient to whitespaces" do
130
+ expect(lex('{{ @var }}')).to produce [:START, :VARIABLE, :END]
131
+ end
132
+
133
+ it "is lexed when present in plain text" do
134
+ expect(lex('text {{@var}} text')).to produce [:TEXT, :START, :VARIABLE, :END, :TEXT]
135
+ end
136
+ end
137
+
138
+ describe "{{path context options}}" do
139
+ it "is lexed with context and options" do
140
+ expect(lex('{{path context key=value}}')).to produce [:START, :PATH, :PATH, :KEY, :PATH, :END]
141
+ end
142
+
143
+ it "is lexed without context" do
144
+ expect(lex('{{path key=value}}')).to produce [:START, :PATH, :KEY, :PATH, :END]
145
+ end
146
+
147
+ it "is lexed without options" do
148
+ expect(lex('{{path context}}')).to produce [:START, :PATH, :PATH, :END]
149
+ end
150
+
151
+ it "is lexed without context and options" do
152
+ expect(lex('{{path}}')).to produce [:START, :PATH, :END]
153
+ end
154
+
155
+ it "is resilient to whitespaces" do
156
+ expect(lex('{{ path }}')).to produce [:START, :PATH, :END]
157
+ end
158
+
159
+ it "is lexed when present in plain text" do
160
+ expect(lex('text {{ path }} text')).to produce [:TEXT, :START, :PATH, :END, :TEXT]
161
+ end
162
+ end
163
+
164
+ describe "{{#if path}}...{{/if}}" do
165
+ it "is lexed" do
166
+ expect(lex('{{#if path}} text {{/if}}')).to produce(
167
+ [:START, :HASH, :IF, :PATH, :END, :TEXT, :START, :SLASH, :IF, :END]
168
+ )
169
+ end
170
+
171
+ it "is resilient to whitespaces" do
172
+ expect(lex('{{ # if path }} text {{/ if }}')).to produce(
173
+ [:START, :HASH, :IF, :PATH, :END, :TEXT, :START, :SLASH, :IF, :END]
174
+ )
175
+ end
176
+
177
+ it "is lexed when present in plain text" do
178
+ expect(lex('text {{#if path}} text {{/if}} text')).to produce(
179
+ [:TEXT, :START, :HASH, :IF, :PATH, :END, :TEXT, :START, :SLASH, :IF, :END, :TEXT]
180
+ )
181
+ end
182
+ end
183
+
184
+ describe "{{#if path}}...{{else}}...{{/if}}" do
185
+ it "is lexed" do
186
+ expect(lex('{{#if path}} text {{else}} text {{/if}}')).to produce(
187
+ [:START, :HASH, :IF, :PATH, :END,
188
+ :TEXT, :START, :ELSE, :END,
189
+ :TEXT, :START, :SLASH, :IF, :END]
190
+ )
191
+ end
192
+
193
+ it "is resilient to whitespaces" do
194
+ expect(lex('{{ # if path }} text {{ else }} text {{/ if }}')).to produce(
195
+ [:START, :HASH, :IF, :PATH, :END,
196
+ :TEXT, :START, :ELSE, :END,
197
+ :TEXT, :START, :SLASH, :IF, :END]
198
+ )
199
+ end
200
+
201
+ it "is lexed when present in plain text" do
202
+ expect(lex('text {{#if path}} text {{else}} text {{/if}} text')).to produce(
203
+ [:TEXT, :START, :HASH, :IF, :PATH, :END,
204
+ :TEXT, :START, :ELSE, :END,
205
+ :TEXT, :START, :SLASH, :IF, :END, :TEXT]
206
+ )
207
+ end
208
+ end
209
+
210
+ describe "{{#unless path}}...{{/unless}}" do
211
+ it "is lexed" do
212
+ expect(lex('{{#unless path}} text {{/unless}}')).to produce(
213
+ [:START, :HASH, :UNLESS, :PATH, :END, :TEXT, :START, :SLASH, :UNLESS, :END]
214
+ )
215
+ end
216
+
217
+ it "is resilient to whitespaces" do
218
+ expect(lex('{{ # unless path }} text {{/ unless }}')).to produce(
219
+ [:START, :HASH, :UNLESS, :PATH, :END, :TEXT, :START, :SLASH, :UNLESS, :END]
220
+ )
221
+ end
222
+
223
+ it "is lexed when present in plain text" do
224
+ expect(lex('text {{#unless path}} text {{/unless}} text')).to produce(
225
+ [:TEXT, :START, :HASH, :UNLESS, :PATH, :END, :TEXT, :START, :SLASH, :UNLESS, :END, :TEXT]
226
+ )
227
+ end
228
+ end
229
+
230
+ describe "{{#unless path}}...{{else}}...{{/unless}}" do
231
+ it "is lexed" do
232
+ expect(lex('{{#unless path}} text {{else}} text {{/unless}}')).to produce(
233
+ [:START, :HASH, :UNLESS, :PATH, :END,
234
+ :TEXT, :START, :ELSE, :END,
235
+ :TEXT, :START, :SLASH, :UNLESS, :END]
236
+ )
237
+ end
238
+
239
+ it "is resilient to whitespaces" do
240
+ expect(lex('{{ # unless path }} text {{ else }} text {{/ unless }}')).to produce(
241
+ [:START, :HASH, :UNLESS, :PATH, :END,
242
+ :TEXT, :START, :ELSE, :END,
243
+ :TEXT, :START, :SLASH, :UNLESS, :END]
244
+ )
245
+ end
246
+
247
+ it "is lexed when present in plain text" do
248
+ expect(lex('text {{#unless path}} text {{else}} text {{/unless}} text')).to produce(
249
+ [:TEXT, :START, :HASH, :UNLESS, :PATH, :END,
250
+ :TEXT, :START, :ELSE, :END,
251
+ :TEXT, :START, :SLASH, :UNLESS, :END, :TEXT]
252
+ )
253
+ end
254
+ end
255
+
256
+ describe "{{#each path}}...{{/each}}" do
257
+ it "is lexed" do
258
+ expect(lex('{{#each path}} text {{/each}}')).to produce(
259
+ [:START, :HASH, :EACH, :PATH, :END, :TEXT, :START, :SLASH, :EACH, :END]
260
+ )
261
+ end
262
+
263
+ it "is resilient to whitespaces" do
264
+ expect(lex('{{ # each path }} text {{/ each }}')).to produce(
265
+ [:START, :HASH, :EACH, :PATH, :END, :TEXT, :START, :SLASH, :EACH, :END]
266
+ )
267
+ end
268
+
269
+ it "is lexed when present in plain text" do
270
+ expect(lex('text {{#each path}} text {{/each}} text')).to produce(
271
+ [:TEXT, :START, :HASH, :EACH, :PATH, :END, :TEXT, :START, :SLASH, :EACH, :END, :TEXT]
272
+ )
273
+ end
274
+ end
275
+
276
+ describe "{{#each path}}...{{else}}...{{/each}}" do
277
+ it "is lexed" do
278
+ expect(lex('{{#each path}} text {{else}} text {{/each}}')).to produce(
279
+ [:START, :HASH, :EACH, :PATH, :END,
280
+ :TEXT, :START, :ELSE, :END,
281
+ :TEXT, :START, :SLASH, :EACH, :END]
282
+ )
283
+ end
284
+
285
+ it "is resilient to whitespaces" do
286
+ expect(lex('{{ # each path }} text {{ else }} text {{/ each }}')).to produce(
287
+ [:START, :HASH, :EACH, :PATH, :END,
288
+ :TEXT, :START, :ELSE, :END,
289
+ :TEXT, :START, :SLASH, :EACH, :END]
290
+ )
291
+ end
292
+
293
+ it "is lexed when present in plain text" do
294
+ expect(lex('text {{#each path}} text {{else}} text {{/each}} text')).to produce(
295
+ [:TEXT, :START, :HASH, :EACH, :PATH, :END,
296
+ :TEXT, :START, :ELSE, :END,
297
+ :TEXT, :START, :SLASH, :EACH, :END, :TEXT]
298
+ )
299
+ end
300
+ end
301
+
302
+ describe "{{#with path}}...{{/with}}" do
303
+ it "is lexed" do
304
+ expect(lex('{{#with path}} text {{/with}}')).to produce(
305
+ [:START, :HASH, :WITH, :PATH, :END, :TEXT, :START, :SLASH, :WITH, :END]
306
+ )
307
+ end
308
+
309
+ it "is resilient to whitespaces" do
310
+ expect(lex('{{ # with path }} text {{/ with }}')).to produce(
311
+ [:START, :HASH, :WITH, :PATH, :END, :TEXT, :START, :SLASH, :WITH, :END]
312
+ )
313
+ end
314
+
315
+ it "is lexed when present in plain text" do
316
+ expect(lex('text {{#with path}} text {{/with}} text')).to produce(
317
+ [:TEXT, :START, :HASH, :WITH, :PATH, :END, :TEXT, :START, :SLASH, :WITH, :END, :TEXT]
318
+ )
319
+ end
320
+ end
321
+
322
+ describe "{{#path path options}}...{{/path}}" do
323
+ it "is lexed with context and options" do
324
+ expect(lex('{{#path context key=value}} text {{/path}}')).to produce(
325
+ [:START, :HASH, :PATH, :PATH, :KEY, :PATH, :END, :TEXT, :START, :SLASH, :PATH, :END]
326
+ )
327
+ end
328
+
329
+ it "is lexed without options" do
330
+ expect(lex('{{#path context}} text {{/path}}')).to produce(
331
+ [:START, :HASH, :PATH, :PATH, :END, :TEXT, :START, :SLASH, :PATH, :END]
332
+ )
333
+ end
334
+
335
+ it "is resilient to whitespaces" do
336
+ expect(lex('{{ # path context key = value}} text {{/ path }}')).to produce(
337
+ [:START, :HASH, :PATH, :PATH, :KEY, :PATH, :END, :TEXT, :START, :SLASH, :PATH, :END]
338
+ )
339
+ end
340
+
341
+ it "is lexed when present in plain text" do
342
+ expect(lex('text {{#path context key=value}} text {{/path}} text')).to produce(
343
+ [:TEXT, :START, :HASH, :PATH, :PATH, :KEY, :PATH, :END, :TEXT, :START, :SLASH, :PATH, :END, :TEXT]
344
+ )
345
+ end
346
+ end
347
+
348
+ describe "{{>path}}" do
349
+ it "is lexed" do
350
+ expect(lex('{{>path}}')).to produce [:START, :GT, :PATH, :END]
351
+ end
352
+
353
+ it "is resilient to whitespaces" do
354
+ expect(lex('{{ > path }}')).to produce [:START, :GT, :PATH, :END]
355
+ end
356
+
357
+ it "is lexed when present in plain text" do
358
+ expect(lex('text {{>path}} text')).to produce [:TEXT, :START, :GT, :PATH, :END, :TEXT]
359
+ end
360
+ end
361
+
362
+ describe "when a leading backslash is present" do
363
+ it "`{` is lexed as plain text" do
364
+ expect(lex('\{')).to produce [:TEXT]
365
+ end
366
+
367
+ it "returns the original text" do
368
+ text_token = lex('\{').detect { |token| token.type == :TEXT }
369
+ expect(text_token.value).to eq '{'
370
+ end
371
+
372
+ it "is lexed when present in plain text" do
373
+ expect(lex('text \{ text')).to produce [:TEXT, :TEXT, :TEXT]
374
+ end
375
+ end
376
+
377
+ describe "can lex paths with or without leading `../`s" do
378
+ it "`path` is lexed as a path" do
379
+ expect(lex('{{path}}')).to produce [:START, :PATH, :END]
380
+ end
381
+
382
+ it "`../path` is lexed as a path" do
383
+ expect(lex('{{../path}}')).to produce [:START, :PATH, :END]
384
+ end
385
+
386
+ it "`../../path` is lexed as a path" do
387
+ expect(lex('{{../../path}}')).to produce [:START, :PATH, :END]
388
+ end
389
+
390
+ it "`path/../` raises an error" do
391
+ expect do
392
+ lex('{{path/../}}')
393
+ end.to raise_error(RLTK::LexingError)
394
+ end
395
+ end
396
+
397
+ describe "can lex paths with dashes" do
398
+ it "`surrounded by other valid chars" do
399
+ expect(lex('{{a-path}}')).to produce [:START, :PATH, :END]
400
+ end
401
+
402
+ it "at the beginning" do
403
+ expect(lex('{{-path}}')).to produce [:START, :PATH, :END]
404
+ end
405
+
406
+ it "at the end" do
407
+ expect(lex('{{path-}}')).to produce [:START, :PATH, :END]
408
+ end
409
+ end
410
+
411
+ describe "can lex paths with identifiers that are numebrs" do
412
+ it "`surrounded by other valid chars" do
413
+ expect(lex('{{path.123}}')).to produce [:START, :PATH, :END]
414
+ end
415
+ end
416
+
417
+ describe "outside a curlybar context" do
418
+ it "`--}}` is lexed as plain text" do
419
+ expect(lex('--}}')).to produce [:TEXT]
420
+ end
421
+
422
+ it "`}}` is lexed as plain text" do
423
+ expect(lex('}}')).to produce [:TEXT]
424
+ end
425
+
426
+ it "`#` is lexed as plain text" do
427
+ expect(lex('#')).to produce [:TEXT]
428
+ end
429
+
430
+ it "`/` is lexed as plain text" do
431
+ expect(lex('/')).to produce [:TEXT]
432
+ end
433
+
434
+ it "`>` is lexed as plain text" do
435
+ expect(lex('>')).to produce [:TEXT]
436
+ end
437
+
438
+ it "`if` is lexed as plain text" do
439
+ expect(lex('if')).to produce [:TEXT]
440
+ end
441
+
442
+ it "`unless` is lexed as plain text" do
443
+ expect(lex('unless')).to produce [:TEXT]
444
+ end
445
+
446
+ it "`each` is lexed as plain text" do
447
+ expect(lex('each')).to produce [:TEXT]
448
+ end
449
+
450
+ it "`with` is lexed as plain text" do
451
+ expect(lex('with')).to produce [:TEXT]
452
+ end
453
+
454
+ it "`else` is lexed as plain text" do
455
+ expect(lex('else')).to produce [:TEXT]
456
+ end
457
+
458
+ it "a path is lexed as plain text" do
459
+ expect(lex('this.is.a.path')).to produce [:TEXT]
460
+ end
461
+
462
+ it "an option is lexed as plain text" do
463
+ expect(lex('key=value')).to produce [:TEXT]
464
+ end
465
+ end
466
+ end