simple_templates 0.8.7

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