curlybars 0.9.13

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