simple_templates 0.8.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
1
+ require_relative "../../test_helper"
2
+
3
+ module SimpleTemplates
4
+ class Parser
5
+ class TestNodeParserImpl < NodeParser
6
+ STARTING_TOKENS = [:text]
7
+ end
8
+ end
9
+ end
10
+
11
+ describe SimpleTemplates::Parser::NodeParser do
12
+ let(:target) {SimpleTemplates::Parser::NodeParser}
13
+ let(:impl) {SimpleTemplates::Parser::TestNodeParserImpl}
14
+ let(:example) {SimpleTemplates::Lexer::Token.new(:text, 'a', 0)}
15
+
16
+ describe "#initialize" do
17
+ it 'raises an error if the input is not valid' do
18
+ -> {
19
+ impl.new( SimpleTemplates::Unescapes.new('<'),
20
+ [SimpleTemplates::Lexer::Token.new(:ph_start, '<', 0)],
21
+ ['name'])
22
+ }.must_raise ArgumentError
23
+ end
24
+ end
25
+
26
+ describe "#applicable?" do
27
+ it "ignores missing" do
28
+ impl.applicable?([]).must_equal false
29
+ end
30
+ it "can be applied" do
31
+ impl.applicable?([example]).must_equal true
32
+ end
33
+ it "can't be applied" do
34
+ target.applicable?([example]).must_equal false
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,105 @@
1
+ require_relative '../../test_helper'
2
+
3
+ describe SimpleTemplates::Parser::Placeholder do
4
+ describe '#parse' do
5
+ let(:delimiter) { SimpleTemplates::Delimiter.new(/\\</, /\\>/, /\</, /\>/) }
6
+ let(:unescapes) { SimpleTemplates::Unescapes.new('<', '>') }
7
+ let(:target) { SimpleTemplates::Parser::Placeholder }
8
+ let(:ast_ph) { SimpleTemplates::AST::Placeholder }
9
+ let(:lexer_token) { SimpleTemplates::Lexer::Token }
10
+ let(:parse_error) { SimpleTemplates::Parser::Error }
11
+ let(:valid_phs) { ['name'] }
12
+ let (:ph_tokens) do
13
+ [
14
+ lexer_token.new(:ph_start, '<', 0),
15
+ lexer_token.new(:ph_name, 'name', 1),
16
+ lexer_token.new(:ph_end, '>', 9),
17
+ ]
18
+ end
19
+
20
+ describe 'with a placeholder as the first part of the input' do
21
+ let(:tokens) { ph_tokens << lexer_token.new(:text, ' some text', 10) }
22
+
23
+ it 'returns no errors' do
24
+ _, errors, _ = target.new(unescapes, tokens, valid_phs).parse
25
+ errors.must_be_empty
26
+ end
27
+
28
+ it 'returns the remaining tokens after the placeholder' do
29
+ _, _, remaining_tokens = target.new(unescapes, tokens, valid_phs).parse
30
+ remaining_tokens.must_equal [lexer_token.new(:text, ' some text', 10)]
31
+ end
32
+
33
+ it 'returns a AST placeholder' do
34
+ placeholder_ast, _, _ = target.new(unescapes, tokens, valid_phs).parse
35
+ placeholder_ast.must_equal [ast_ph.new('name', 0, true)]
36
+ end
37
+ end
38
+
39
+ describe 'with no placeholder as the first part of the input' do
40
+ let(:tokens) { ph_tokens.unshift(lexer_token.new(:text, 'hello ', 0)) }
41
+
42
+ it 'raises an error' do
43
+ -> {
44
+ target.new(unescapes, tokens, valid_phs)
45
+ }.must_raise ArgumentError
46
+ end
47
+ end
48
+
49
+ describe 'with an empty placeholder' do
50
+ let(:tokens) do
51
+ [
52
+ lexer_token.new(:ph_start, '<', 0),
53
+ lexer_token.new(:ph_end, '>', 1),
54
+ lexer_token.new(:text, ' some text', 2)
55
+ ]
56
+ end
57
+
58
+ it 'returns an error about not finding the placeholder' do
59
+ _, errors, _ = target.new(unescapes, tokens, valid_phs).parse
60
+ errors.must_equal [
61
+ parse_error.new('Expected placeholder name token at character position 1, but found a placeholder end token instead.')
62
+ ]
63
+ end
64
+
65
+ it 'returns an empty Array as the remaining tokens' do
66
+ _, _, remaining_tokens = target.new(unescapes, tokens, valid_phs).parse
67
+ remaining_tokens.must_be_empty
68
+ end
69
+
70
+ it 'returns an empty list of AST placeholders' do
71
+ placeholder_ast, _, _ = target.new(unescapes, tokens, valid_phs).parse
72
+ placeholder_ast.must_be_empty
73
+ end
74
+ end
75
+
76
+ describe 'with an invalid placeholder name' do
77
+ let(:tokens) do
78
+ [
79
+ lexer_token.new(:ph_start, '<', 0),
80
+ lexer_token.new(:ph_name, 'name', 1),
81
+ lexer_token.new(:text, '-', 5),
82
+ lexer_token.new(:ph_end, '>', 6),
83
+ lexer_token.new(:text, ' some text', 7),
84
+ ]
85
+ end
86
+
87
+ it 'returns an error about not finding the placeholder' do
88
+ _, errors, _ = target.new(unescapes, tokens, valid_phs).parse
89
+ errors.must_equal [
90
+ parse_error.new('Expected placeholder end token at character position 5, but found a text token instead.')
91
+ ]
92
+ end
93
+
94
+ it 'returns an empty Array of remaining tokens' do
95
+ _, _, remaining_tokens = target.new(unescapes, tokens, valid_phs).parse
96
+ remaining_tokens.must_be_empty
97
+ end
98
+
99
+ it 'returns an empty Array of AST placeholders' do
100
+ placeholder_ast, _, _ = target.new(unescapes, tokens, valid_phs).parse
101
+ placeholder_ast.must_be_empty
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,118 @@
1
+ require_relative '../../test_helper'
2
+
3
+ describe SimpleTemplates::Parser::Text do
4
+ describe '#parse' do
5
+ let(:delimiter) { SimpleTemplates::Delimiter.new(/\\</, /\\>/, /\</, /\>/) }
6
+ let(:unescapes) { SimpleTemplates::Unescapes.new('<', '>') }
7
+ let(:ast_text) { SimpleTemplates::AST::Text }
8
+ let(:lexer_token) { SimpleTemplates::Lexer::Token }
9
+ let(:target) { SimpleTemplates::Parser::Text }
10
+ let(:valid_phs) { ['name'] }
11
+ let(:ph_tokens) do
12
+ [
13
+ lexer_token.new(:ph_start, '<', 6),
14
+ lexer_token.new(:ph_name, 'name', 7),
15
+ lexer_token.new(:ph_end, '>', 15)
16
+ ]
17
+ end
18
+
19
+ describe 'with a valid input' do
20
+ let(:tokens) { [lexer_token.new(:text, 'hello ', 0)].concat(ph_tokens) }
21
+
22
+ it 'returns a list with only the text before the placeholder' do
23
+ txt_nodes, _, _ = target.new(unescapes, tokens, valid_phs).parse
24
+ txt_nodes.must_equal [ast_text.new('hello ', 0, true)]
25
+ end
26
+
27
+ it 'returns no errors' do
28
+ _, errors_list, _ = target.new(unescapes, tokens, valid_phs).parse
29
+ errors_list.must_be_empty
30
+ end
31
+
32
+ it 'returns the remaining tokens containing only the placeholder part' do
33
+ _, _, remaining_tokens = target.new(unescapes, tokens, valid_phs).parse
34
+ remaining_tokens.must_equal ph_tokens
35
+ end
36
+ end
37
+
38
+ describe 'with a new line in the placeholder name' do
39
+ let(:other_tokens) do
40
+ [
41
+ lexer_token.new(:ph_start, '<', 6),
42
+ lexer_token.new(:ph_name, 'na', 7),
43
+ lexer_token.new(:text, "\nme", 9),
44
+ lexer_token.new(:ph_end, '>', 12)
45
+ ]
46
+ end
47
+ let(:tokens) { [lexer_token.new(:text, 'hello ', 0)].concat(other_tokens) }
48
+
49
+ it 'returns the remainin tokens with a new line as text in the placeholder' do
50
+ _, _, remaining_tokens = target.new(unescapes, tokens, valid_phs).parse
51
+ remaining_tokens.must_equal other_tokens
52
+ end
53
+
54
+ it 'returns no errors' do
55
+ _, errors_list, _ = target.new(unescapes, tokens, valid_phs).parse
56
+ errors_list.must_be_empty
57
+ end
58
+
59
+ it 'returns the text nodes' do
60
+ txt_nodes, _, _ = target.new(unescapes, tokens, valid_phs).parse
61
+ txt_nodes.must_equal [ast_text.new('hello ', 0, true)]
62
+ end
63
+ end
64
+
65
+ describe 'with leading and trailing whitespaces' do
66
+ let(:tokens) { [lexer_token.new(:text, 'hello ', 0)].concat(ph_tokens) }
67
+
68
+ it 'returns the remaining tokens with no placeholder name' do
69
+ _, _, remaining_tokens = target.new(unescapes, tokens, valid_phs ).parse
70
+ remaining_tokens.must_equal ph_tokens
71
+ end
72
+
73
+ it 'returns the text nodes' do
74
+ txt_nodes, _, _ = target.new(unescapes, tokens, valid_phs ).parse
75
+ txt_nodes.must_equal [ast_text.new('hello ', 0, true)]
76
+ end
77
+ end
78
+
79
+ describe 'with a newline after the placeholder' do
80
+ let(:other_tokens) { ph_tokens << lexer_token.new(:text, "\n world", 12) }
81
+ let(:tokens) { [lexer_token.new(:text, 'hello ', 0)].concat(other_tokens) }
82
+
83
+ it 'returns a list with remaining tokens with the placeholder and followed by text' do
84
+ _, _, remaining_tokens = target.new(unescapes, tokens, valid_phs ).parse
85
+ remaining_tokens.must_equal other_tokens
86
+ end
87
+
88
+ it 'returns no errors' do
89
+ _, errors_list, _ = target.new(unescapes, tokens, valid_phs).parse
90
+ errors_list.must_be_empty
91
+ end
92
+
93
+ it 'returns the text nodes' do
94
+ txt_nodes, _, _ = target.new(unescapes, tokens, valid_phs ).parse
95
+ txt_nodes.must_equal [ast_text.new('hello ', 0, true)]
96
+ end
97
+ end
98
+
99
+ describe 'with a new line character before the placeholder' do
100
+ let(:tokens) { [lexer_token.new(:text, "hello \n", 0)].concat(ph_tokens) }
101
+
102
+ it 'returns remaining tokens with the placeholder' do
103
+ _, _, remaining_tokens = target.new(unescapes, tokens, valid_phs ).parse
104
+ remaining_tokens.must_equal ph_tokens
105
+ end
106
+
107
+ it 'returns no errors' do
108
+ _, errors_list, _ = target.new(unescapes, tokens, valid_phs).parse
109
+ errors_list.must_be_empty
110
+ end
111
+
112
+ it 'returns the text nodes' do
113
+ txt_nodes, _, _ = target.new(unescapes, tokens, valid_phs ).parse
114
+ txt_nodes.must_equal [ast_text.new("hello \n", 0, true)]
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,184 @@
1
+ require_relative "../test_helper"
2
+
3
+ require 'set'
4
+
5
+ describe SimpleTemplates::Parser do
6
+ describe "#parse" do
7
+ it "parses a simple valid template" do
8
+ pholder = SimpleTemplates::AST::Placeholder.new('bar', 4, true)
9
+
10
+ SimpleTemplates.parse('foo <bar>', ['bar']).must_equal SimpleTemplates::Template.new(
11
+ [SimpleTemplates::AST::Text.new('foo ', 0, true), pholder],
12
+ [],
13
+ []
14
+ )
15
+ end
16
+
17
+ it "parses a two-byte unicode character" do
18
+ SimpleTemplates.parse('Ford® Flex').must_equal SimpleTemplates::Template.new(
19
+ [SimpleTemplates::AST::Text.new('Ford® Flex', 0, true)],
20
+ [],
21
+ []
22
+ )
23
+ end
24
+
25
+ it "marks all nodes as allowed and returns no errors if the allowed_placeholders is nil" do
26
+ pholder = SimpleTemplates::AST::Placeholder.new('bar', 4, true)
27
+
28
+ SimpleTemplates.parse('foo <bar>', nil).must_equal SimpleTemplates::Template.new(
29
+ [SimpleTemplates::AST::Text.new('foo ', 0, true), pholder],
30
+ [],
31
+ []
32
+ )
33
+ end
34
+
35
+ it "marks all nodes as disallowed and returns errors if the allowed_placeholders is empty" do
36
+ pholder = SimpleTemplates::AST::Placeholder.new('bar', 4, false)
37
+
38
+ SimpleTemplates.parse('foo <bar>', []).must_equal SimpleTemplates::Template.new(
39
+ [SimpleTemplates::AST::Text.new('foo ', 0, true), pholder],
40
+ [SimpleTemplates::Parser::Error.new("Invalid Placeholder with contents, 'bar' found starting at position 4.")],
41
+ []
42
+ )
43
+ end
44
+
45
+ it "compresses adjacent text nodes after unescaping" do
46
+ pholder = SimpleTemplates::AST::Placeholder.new('bar', 7, true)
47
+
48
+ SimpleTemplates.parse('foo \< <bar>', ['bar']).ast.must_equal [
49
+ SimpleTemplates::AST::Text.new('foo < ', 0, true), pholder
50
+ ]
51
+ end
52
+
53
+ it "allows text after placeholders" do
54
+ pholder = SimpleTemplates::AST::Placeholder.new('bar', 7, true)
55
+ pholder2 = SimpleTemplates::AST::Placeholder.new('baz', 13, true)
56
+
57
+ SimpleTemplates.parse('foo \< <bar> <baz>', [:bar, :baz]).
58
+ must_equal SimpleTemplates::Template.new(
59
+ [
60
+ SimpleTemplates::AST::Text.new('foo < ', 0, true),
61
+ pholder,
62
+ SimpleTemplates::AST::Text.new(' ', 12, true),
63
+ pholder2
64
+ ],
65
+ [],
66
+ []
67
+ )
68
+ end
69
+
70
+ it "allows templates starting with placeholders" do
71
+ pholder = SimpleTemplates::AST::Placeholder.new('foo', 0, true)
72
+
73
+ SimpleTemplates.parse('<foo> bar', ['foo']).ast.must_equal [
74
+ pholder,
75
+ SimpleTemplates::AST::Text.new(' bar', 5, true)
76
+ ]
77
+ end
78
+
79
+ it "allows templates with placeholders that contain underscores" do
80
+ pholder = SimpleTemplates::AST::Placeholder.new('some_name', 0, true)
81
+
82
+ SimpleTemplates.parse('<some_name> bar', [:some_name]).ast.must_equal [
83
+ pholder,
84
+ SimpleTemplates::AST::Text.new(' bar', 11, true)
85
+ ]
86
+ end
87
+
88
+ it "parses other placeholder types by changing the delimiter Struct" do
89
+ pholder = SimpleTemplates::AST::Placeholder.new('foo', 0, true)
90
+
91
+ delim = SimpleTemplates::Delimiter.new(/\\\[\\\[/, /\\\]\\\]/, /\[\[/, /\]\]/)
92
+
93
+ ast, errors, remaining_tokens =
94
+ SimpleTemplates::Template.new(
95
+ *SimpleTemplates::Parser.new(
96
+ SimpleTemplates::Unescapes.new('[[', ']]'),
97
+ SimpleTemplates::Lexer.new(delim, '[[foo]] \[\[ bar').tokenize,
98
+ ['foo']
99
+ ).parse
100
+ )
101
+
102
+ ast.ast.must_equal [
103
+ pholder,
104
+ SimpleTemplates::AST::Text.new(' [[ bar', 7, true)
105
+ ]
106
+ end
107
+
108
+ it "returns an error when an opening bracket is found without a closing bracket" do
109
+ SimpleTemplates.parse('foo < <bar>', [:bar]).errors.must_equal [
110
+ SimpleTemplates::Parser::Error.new("Expected placeholder name token at character position 5, but found a text token instead.")
111
+ ]
112
+ end
113
+
114
+ it "returns an error when a closing bracket is found before an opening bracket" do
115
+ SimpleTemplates.parse('foo > <bar>', [:bar]).errors.must_equal [
116
+ SimpleTemplates::Parser::Error.new('Encountered unexpected token in stream (placeholder end), but expected to see one of the following types: placeholder start, quoted placeholder start, quoted placeholder end, text.')
117
+ ]
118
+ end
119
+
120
+ it "returns errors about invalid placeholders encountered before a syntactical error" do
121
+ SimpleTemplates.parse('foo <baz> >', []).errors.must_equal [
122
+ SimpleTemplates::Parser::Error.new('Encountered unexpected token in stream (placeholder end), but expected to see one of the following types: placeholder start, quoted placeholder start, quoted placeholder end, text.'),
123
+ SimpleTemplates::Parser::Error.new('Invalid Placeholder with contents, \'baz\' found starting at position 4.')
124
+ ]
125
+ end
126
+
127
+ it "returns an error when an invalid placeholder name is found" do
128
+ SimpleTemplates.parse('foo <baz>', [:bar]).errors.must_equal [
129
+ SimpleTemplates::Parser::Error.
130
+ new("Invalid Placeholder with contents, 'baz' found starting at position 4.")]
131
+ end
132
+
133
+ it "returns an error when a placeholder with newlines is found" do
134
+ SimpleTemplates.parse("foo <ba\nr>", ["ba\nr"]).errors.must_equal [
135
+ SimpleTemplates::Parser::Error.new("Expected placeholder end token at character position 7, but found a text token instead.")]
136
+ end
137
+
138
+ it "returns an error when a placeholder with spaces is found" do
139
+ SimpleTemplates.parse("foo <ba r>", ['ba r']).errors.must_equal [
140
+ SimpleTemplates::Parser::Error.
141
+ new("Expected placeholder end token at character position 7, but found a text token instead.")]
142
+ end
143
+
144
+ it "returns an error when a placeholder with tabs is found" do
145
+ SimpleTemplates.parse("foo <ba\tr>", ["ba\tr"]).errors.must_equal [
146
+ SimpleTemplates::Parser::Error.new("Expected placeholder end token at character position 7, but found a text token instead.")]
147
+ end
148
+
149
+ it "returns an error when a placeholder with other characters is found" do
150
+ SimpleTemplates.parse("foo <ba-r>", ['ba-r']).errors.must_equal [
151
+ SimpleTemplates::Parser::Error.new("Expected placeholder end token at character position 7, but found a text token instead.")]
152
+ end
153
+
154
+
155
+ it "returns an multiple errors when there are multiple non-whitelisted placeholders" do
156
+ SimpleTemplates.parse('foo <baz> <buz>', []).errors.must_equal [
157
+ SimpleTemplates::Parser::Error.new("Invalid Placeholder with contents, 'baz' found starting at position 4."),
158
+ SimpleTemplates::Parser::Error.new("Invalid Placeholder with contents, 'buz' found starting at position 10.")
159
+ ]
160
+ end
161
+
162
+ it "returns an error when multiple opening brackets are found" do
163
+ SimpleTemplates.parse('foo <<baz>', ['bar']).errors.must_equal [
164
+ SimpleTemplates::Parser::Error.new("Expected placeholder name token at character position 5, but found a placeholder start token instead.")]
165
+ end
166
+
167
+ it "returns an error when empty placeholder is found" do
168
+ SimpleTemplates.parse('foo <>', ['bar']).errors.must_equal [
169
+ SimpleTemplates::Parser::Error.new("Expected placeholder name token at character position 5, but found a placeholder end token instead.")]
170
+ end
171
+
172
+ it "returns an error when a closing tag is expected, but an opening tag is found" do
173
+ SimpleTemplates.parse('foo <bar<>', ['bar']).errors.must_equal [
174
+ SimpleTemplates::Parser::Error.new("Expected placeholder end token at character position 8, but found a placeholder start token instead.")
175
+ ]
176
+ end
177
+
178
+ it "returns an error when a tag is not closed before the end of the input" do
179
+ SimpleTemplates.parse('foo <bar', ['bar']).errors.must_equal [
180
+ SimpleTemplates::Parser::Error.new("Expected placeholder end token, but reached end of input.")
181
+ ]
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,58 @@
1
+ require_relative "../test_helper"
2
+
3
+ require 'set'
4
+
5
+ describe SimpleTemplates::Template do
6
+ describe "#placeholder_names" do
7
+ it "should return a set of the placeholder names in the template" do
8
+ SimpleTemplates.parse('foo <bar> <baz>', [:bar, :baz]).
9
+ placeholder_names.must_equal ['bar', 'baz'].to_set
10
+ end
11
+ end
12
+
13
+ describe "#render" do
14
+ it "processes an empty template" do
15
+ SimpleTemplates.parse('', []).render(
16
+ {bar: 'baz'}
17
+ ).must_equal ''
18
+ end
19
+
20
+ it "interpolates a simple, valid template" do
21
+ SimpleTemplates.parse('foo <bar>', [:bar]).render(
22
+ {bar: 'baz'}
23
+ ).must_equal 'foo baz'
24
+ end
25
+
26
+ it "interpolates a template containing an escaped '>'" do
27
+ SimpleTemplates.parse("foo <bar> \\>", [:bar]).render(
28
+ {bar: 'baz'}
29
+ ).must_equal "foo baz \>"
30
+ end
31
+
32
+ it "interpolates a template containing an escaped '<'" do
33
+ SimpleTemplates.parse("foo <bar> \\<", [:bar]).render(
34
+ {bar: 'baz'}
35
+ ).must_equal "foo baz \<"
36
+ end
37
+
38
+ it "interpolates a template containing an escaped escape character" do
39
+ SimpleTemplates.parse("foo <bar> \\", [:bar]).render(
40
+ {bar: 'baz'}
41
+ ).must_equal "foo baz \\"
42
+ end
43
+ end
44
+
45
+ describe "#==" do
46
+ it "compares the ast" do
47
+ SimpleTemplates::Template.new([:ast_a], [], []).wont_equal SimpleTemplates::Template.new([:ast_b], [], [])
48
+ end
49
+
50
+ it "compares the errors" do
51
+ SimpleTemplates::Template.new([], [:error_a], []).wont_equal SimpleTemplates::Template.new([], [:error_b], [])
52
+ end
53
+
54
+ it "compares the remaining tokens" do
55
+ SimpleTemplates::Template.new([], [], [:remaining_tokens_a]).wont_equal SimpleTemplates::Template.new([], [], [:remaining_tokens_b])
56
+ end
57
+ end
58
+ end