vue_component_compiler 0.1.0
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.
- checksums.yaml +7 -0
- data/.editorconfig +9 -0
- data/.gitignore +4 -0
- data/.rspec +3 -0
- data/.travis.yml +3 -0
- data/Gemfile +11 -0
- data/LICENSE +21 -0
- data/README.md +9 -0
- data/Rakefile +6 -0
- data/lib/vue_component_compiler.rb +14 -0
- data/lib/vue_component_compiler/node.rb +108 -0
- data/lib/vue_component_compiler/parser.rb +181 -0
- data/lib/vue_component_compiler/token.rb +105 -0
- data/lib/vue_component_compiler/tokenizer.rb +322 -0
- data/lib/vue_component_compiler/version.rb +3 -0
- data/vue_component_compiler.gemspec +24 -0
- metadata +101 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9c4f7b83e30ac6e415fc1d356d67f6da85b377682b5de1813a31e2ccd2160d78
|
|
4
|
+
data.tar.gz: fe4f450d390c613c09b21062c159879ed88ac5573ac04747dd89086b7d08466e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 320dea5c591312eb4fbbdebc695296e2d485b78f6d4f24d90b88db744736cb44548ff114cb96689878e5a9ae588a5ba915cc9933774abb4ce0da9ebeb33123aa
|
|
7
|
+
data.tar.gz: 60a0086f3d387c73afe65ddb0e3a252d4373fb65e5b6c6ef70f2f5ceb213eb521129a42ddb50c4f224778d2143365fbd7a432e46d76c4bbfed75580a1fd506e4
|
data/.editorconfig
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018-present Pig Fang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'vue_component_compiler/token'
|
|
2
|
+
require 'vue_component_compiler/tokenizer'
|
|
3
|
+
require 'vue_component_compiler/node'
|
|
4
|
+
require 'vue_component_compiler/parser'
|
|
5
|
+
|
|
6
|
+
module VueCompiler
|
|
7
|
+
def parse(code)
|
|
8
|
+
tokenizer = Tokenizer.new(code)
|
|
9
|
+
parser = Parser.new(tokenizer.tokenize)
|
|
10
|
+
parser.parse
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module_function :parse
|
|
14
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module VueCompiler
|
|
2
|
+
module Node
|
|
3
|
+
class Program
|
|
4
|
+
attr_reader :children, :comments
|
|
5
|
+
|
|
6
|
+
def initialize(children, comments = [])
|
|
7
|
+
@children = children
|
|
8
|
+
@comments = comments
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class Comment
|
|
13
|
+
attr_reader :body
|
|
14
|
+
|
|
15
|
+
def initialize(body)
|
|
16
|
+
@body = body
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Element
|
|
21
|
+
attr_reader :name, :attributes, :children
|
|
22
|
+
|
|
23
|
+
def initialize(name, attributes = [], children = [])
|
|
24
|
+
@name = name
|
|
25
|
+
@attributes = attributes
|
|
26
|
+
@children = children
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class Text
|
|
31
|
+
attr_reader :content
|
|
32
|
+
|
|
33
|
+
def initialize(content)
|
|
34
|
+
@content = content
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class Interpolation
|
|
39
|
+
attr_reader :expression
|
|
40
|
+
|
|
41
|
+
def initialize(expression)
|
|
42
|
+
@expression = expression
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Attribute
|
|
47
|
+
attr_reader :name, :value
|
|
48
|
+
|
|
49
|
+
def initialize(name, value)
|
|
50
|
+
@name = name
|
|
51
|
+
@value = value
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class AttributeName
|
|
56
|
+
attr_reader :name
|
|
57
|
+
|
|
58
|
+
def initialize(name)
|
|
59
|
+
@name = name
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class Directive
|
|
64
|
+
attr_reader :name, :argument, :shorthand
|
|
65
|
+
|
|
66
|
+
def initialize(name, argument, shorthand = false)
|
|
67
|
+
@name = name
|
|
68
|
+
@argument = argument
|
|
69
|
+
@shorthand = shorthand
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class AttributeValue
|
|
74
|
+
attr_reader :value
|
|
75
|
+
|
|
76
|
+
def initialize(value)
|
|
77
|
+
@value = value
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class TemplateBlock
|
|
82
|
+
attr_reader :body
|
|
83
|
+
|
|
84
|
+
def initialize(body)
|
|
85
|
+
@body = body
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class ScriptBlock
|
|
90
|
+
attr_reader :content, :lang
|
|
91
|
+
|
|
92
|
+
def initialize(content, lang = 'js')
|
|
93
|
+
@content = content
|
|
94
|
+
@lang = lang
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class StyleBlock
|
|
99
|
+
attr_reader :content, :lang, :scoped
|
|
100
|
+
|
|
101
|
+
def initialize(content, lang = 'css', scoped = false)
|
|
102
|
+
@content = content
|
|
103
|
+
@lang = lang
|
|
104
|
+
@scoped = scoped
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
module VueCompiler
|
|
2
|
+
class Parser
|
|
3
|
+
DIRECTIVE_RE = /v\-(\w+)(?:\:(\w+))?/
|
|
4
|
+
|
|
5
|
+
# @param tokens {Array}
|
|
6
|
+
def initialize(tokens)
|
|
7
|
+
@tokens = tokens
|
|
8
|
+
@comments = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def parse
|
|
12
|
+
root_children = []
|
|
13
|
+
|
|
14
|
+
until @tokens[0].instance_of?(Token::EndOfSource)
|
|
15
|
+
token = @tokens[0]
|
|
16
|
+
if token.instance_of?(Token::ScriptBlock)
|
|
17
|
+
token = self.eat
|
|
18
|
+
root_children << Node::ScriptBlock.new(
|
|
19
|
+
token.content,
|
|
20
|
+
token.lang
|
|
21
|
+
)
|
|
22
|
+
next
|
|
23
|
+
elsif token.instance_of?(Token::StyleBlock)
|
|
24
|
+
token = self.eat
|
|
25
|
+
root_children << Node::StyleBlock.new(
|
|
26
|
+
token.content,
|
|
27
|
+
token.lang,
|
|
28
|
+
token.scoped
|
|
29
|
+
)
|
|
30
|
+
next
|
|
31
|
+
elsif token.instance_of?(Token::CommentStart)
|
|
32
|
+
self.parse_comment
|
|
33
|
+
next
|
|
34
|
+
elsif token.instance_of?(Token::CustomBlock)
|
|
35
|
+
root_children << token
|
|
36
|
+
self.eat
|
|
37
|
+
next
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
self.expect(Token::OpenTagStart)
|
|
41
|
+
self.expect(Token::OpenTagName)
|
|
42
|
+
self.expect(Token::TagEnd)
|
|
43
|
+
|
|
44
|
+
while token.instance_of?(Token::CommentStart)
|
|
45
|
+
self.parse_comment
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if @tokens[0].instance_of?(Token::OpenTagStart)
|
|
49
|
+
element = Node::Element.new(
|
|
50
|
+
'template',
|
|
51
|
+
[],
|
|
52
|
+
[self.parse_element]
|
|
53
|
+
)
|
|
54
|
+
root_children << Node::TemplateBlock.new(element)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
while token.instance_of?(Token::CommentStart)
|
|
58
|
+
self.parse_comment
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
self.expect(Token::ClosingTagStart)
|
|
62
|
+
self.expect(Token::ClosingTagName)
|
|
63
|
+
self.expect(Token::TagEnd)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
Node::Program.new(root_children, @comments)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def eat
|
|
70
|
+
@tokens.shift
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def expect(token)
|
|
74
|
+
if @tokens[0].instance_of?(token)
|
|
75
|
+
@tokens.shift
|
|
76
|
+
else
|
|
77
|
+
raise Exception.new("Found token type #{@tokens[0].class}, expecting #{token}")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def parse_comment
|
|
82
|
+
self.eat
|
|
83
|
+
@comments << Node::Comment.new(self.eat.value)
|
|
84
|
+
self.eat
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def parse_element
|
|
88
|
+
self.expect(Token::OpenTagStart)
|
|
89
|
+
tag_name = self.expect(Token::OpenTagName).name
|
|
90
|
+
attributes = []
|
|
91
|
+
|
|
92
|
+
while @tokens[0].instance_of?(Token::AttributeName)
|
|
93
|
+
attributes << self.parse_attribute
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if self.eat.instance_of?(Token::SelfClosingTag)
|
|
97
|
+
return Node::Element.new(tag_name, attributes)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
children = self.parse_element_children
|
|
101
|
+
|
|
102
|
+
self.expect(Token::ClosingTagStart)
|
|
103
|
+
self.expect(Token::ClosingTagName)
|
|
104
|
+
self.expect(Token::TagEnd)
|
|
105
|
+
|
|
106
|
+
Node::Element.new(tag_name, attributes, children)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def parse_attribute
|
|
110
|
+
name = self.eat.name
|
|
111
|
+
value = nil
|
|
112
|
+
|
|
113
|
+
if @tokens[0].instance_of?(Token::AttributeValue)
|
|
114
|
+
value = self.eat.value
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if name.start_with?(':')
|
|
118
|
+
if value
|
|
119
|
+
Node::Attribute.new(
|
|
120
|
+
Node::Directive.new('bind', name[1..-1], true),
|
|
121
|
+
Node::AttributeValue.new(value)
|
|
122
|
+
)
|
|
123
|
+
else
|
|
124
|
+
raise Exception.new('Missing value of `v-bind` directive.')
|
|
125
|
+
end
|
|
126
|
+
elsif name.start_with?('@')
|
|
127
|
+
if value
|
|
128
|
+
Node::Attribute.new(
|
|
129
|
+
Node::Directive.new('on', name[1..-1], true),
|
|
130
|
+
Node::AttributeValue.new(value)
|
|
131
|
+
)
|
|
132
|
+
else
|
|
133
|
+
raise Exception.new('Missing value of `v-on` directive.')
|
|
134
|
+
end
|
|
135
|
+
elsif name.start_with?('v-')
|
|
136
|
+
matched = name.match(DIRECTIVE_RE)
|
|
137
|
+
name = matched[1]
|
|
138
|
+
argument = matched[2]
|
|
139
|
+
Node::Attribute.new(
|
|
140
|
+
Node::Directive.new(name, argument),
|
|
141
|
+
Node::AttributeValue.new(value)
|
|
142
|
+
)
|
|
143
|
+
else
|
|
144
|
+
Node::Attribute.new(
|
|
145
|
+
Node::AttributeName.new(name),
|
|
146
|
+
Node::AttributeValue.new(value)
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def parse_element_children
|
|
152
|
+
children = []
|
|
153
|
+
|
|
154
|
+
until @tokens[0].instance_of?(Token::ClosingTagStart)
|
|
155
|
+
token = @tokens[0]
|
|
156
|
+
|
|
157
|
+
if token.instance_of?(Token::OpenTagStart)
|
|
158
|
+
children << self.parse_element
|
|
159
|
+
elsif token.instance_of?(Token::Text)
|
|
160
|
+
children << Node::Text.new(self.eat.content)
|
|
161
|
+
elsif token.instance_of?(Token::InterpolationStart)
|
|
162
|
+
children << self.parse_interpolation
|
|
163
|
+
elsif token.instance_of?(Token::CommentStart)
|
|
164
|
+
self.parse_comment
|
|
165
|
+
else
|
|
166
|
+
raise Exception.new('Unknown token type.')
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
children
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def parse_interpolation
|
|
174
|
+
self.eat
|
|
175
|
+
interpolation = Node::Interpolation.new(self.eat.expression)
|
|
176
|
+
self.eat
|
|
177
|
+
|
|
178
|
+
interpolation
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
module VueCompiler
|
|
2
|
+
module Token
|
|
3
|
+
class EndOfSource end
|
|
4
|
+
|
|
5
|
+
class CommentStart end
|
|
6
|
+
|
|
7
|
+
class Comment
|
|
8
|
+
attr_reader :value
|
|
9
|
+
|
|
10
|
+
def initialize(value)
|
|
11
|
+
@value = value
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class CommentEnd end
|
|
16
|
+
|
|
17
|
+
class OpenTagStart end
|
|
18
|
+
|
|
19
|
+
class ClosingTagStart end
|
|
20
|
+
|
|
21
|
+
class TagEnd end
|
|
22
|
+
|
|
23
|
+
class SelfClosingTag end
|
|
24
|
+
|
|
25
|
+
class TagName
|
|
26
|
+
attr_reader :name
|
|
27
|
+
|
|
28
|
+
def initialize(name)
|
|
29
|
+
@name = name
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class OpenTagName < TagName
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class ClosingTagName < TagName
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class AttributeName
|
|
40
|
+
attr_reader :name
|
|
41
|
+
|
|
42
|
+
def initialize(name)
|
|
43
|
+
@name = name
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class AttributeValue
|
|
48
|
+
attr_reader :value
|
|
49
|
+
|
|
50
|
+
def initialize(value)
|
|
51
|
+
@value = value
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class Text
|
|
56
|
+
attr_reader :content
|
|
57
|
+
|
|
58
|
+
def initialize(content)
|
|
59
|
+
@content = content
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class InterpolationStart
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class Interpolation
|
|
67
|
+
attr_reader :expression
|
|
68
|
+
|
|
69
|
+
def initialize(expression)
|
|
70
|
+
@expression = expression
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class InterpolationEnd
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class ScriptBlock
|
|
78
|
+
attr_reader :content, :lang
|
|
79
|
+
|
|
80
|
+
def initialize(content, lang = 'js')
|
|
81
|
+
@content = content
|
|
82
|
+
@lang = lang
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class StyleBlock
|
|
87
|
+
attr_reader :content, :lang, :scoped
|
|
88
|
+
|
|
89
|
+
def initialize(content, lang = 'css', scoped = false)
|
|
90
|
+
@content = content
|
|
91
|
+
@lang = lang
|
|
92
|
+
@scoped = scoped
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class CustomBlock
|
|
97
|
+
attr_reader :type, :attributes, :content
|
|
98
|
+
|
|
99
|
+
def initialize(type, content, attributes = {})
|
|
100
|
+
@type = type
|
|
101
|
+
@content = content
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
module VueCompiler
|
|
2
|
+
class Tokenizer
|
|
3
|
+
TAG_RE = /^[a-z][a-z0-9:-]*/
|
|
4
|
+
TAG_NAME_RE = /^[a-z][a-z0-9:-]*/
|
|
5
|
+
ATTRIBUTE_NAME_RE = /^[a-z0-9@:\-]+/
|
|
6
|
+
ATTRIBUTE_VALUE_RE = /^"([^"\r\n]*)"|'([^'\r\n]*)'|([^\s\r\n\t<>'"]+)/
|
|
7
|
+
WHITESPACE_RE = /^[\s\n\t\r]+/
|
|
8
|
+
QUOTE_RE = /^'|"/
|
|
9
|
+
|
|
10
|
+
VOID_TAG = %w(area base br col command embed hr img input keygen link meta param source track wbr)
|
|
11
|
+
|
|
12
|
+
# @param code {String}
|
|
13
|
+
def initialize(code)
|
|
14
|
+
@code = code.delete_prefix("\uFEFF")
|
|
15
|
+
@tokens = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def tokenize
|
|
19
|
+
while @code && !@code.empty?
|
|
20
|
+
if @code.start_with?('<template')
|
|
21
|
+
self.scan_top_level_template
|
|
22
|
+
elsif @code =~ WHITESPACE_RE
|
|
23
|
+
self.eat_whitespaces
|
|
24
|
+
elsif @code.start_with?('<script')
|
|
25
|
+
self.scan_script_block
|
|
26
|
+
elsif @code.start_with?('<style')
|
|
27
|
+
self.scan_style_block
|
|
28
|
+
elsif @code.start_with?('<!--')
|
|
29
|
+
self.scan_comment
|
|
30
|
+
elsif @code =~ TAG_RE
|
|
31
|
+
self.scan_custom_block
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
@tokens << Token::EndOfSource.new
|
|
35
|
+
|
|
36
|
+
@tokens
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def eat(str)
|
|
40
|
+
@code.delete_prefix!(str)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def eat_whitespaces
|
|
44
|
+
@code.sub!(WHITESPACE_RE, '')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def expect(str)
|
|
48
|
+
if @code.start_with?(str)
|
|
49
|
+
self.eat(str)
|
|
50
|
+
elsif
|
|
51
|
+
raise Exception.new("Found character `#{@code[0]}`, expecting `#{str}`")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def scan_top_level_template
|
|
56
|
+
@tokens << Token::OpenTagStart.new
|
|
57
|
+
@tokens << Token::OpenTagName.new('template')
|
|
58
|
+
@tokens << Token::TagEnd.new
|
|
59
|
+
self.eat('<template')
|
|
60
|
+
self.eat_whitespaces
|
|
61
|
+
self.expect('>')
|
|
62
|
+
|
|
63
|
+
self.eat_whitespaces
|
|
64
|
+
root_scanned = false
|
|
65
|
+
until @code.start_with?('</')
|
|
66
|
+
if @code.start_with?('<!--')
|
|
67
|
+
self.scan_comment
|
|
68
|
+
else
|
|
69
|
+
unless root_scanned
|
|
70
|
+
self.scan_tag
|
|
71
|
+
root_scanned = true
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
self.eat_whitespaces
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
self.eat('</')
|
|
78
|
+
@tokens << Token::ClosingTagStart.new
|
|
79
|
+
self.expect('template')
|
|
80
|
+
@tokens << Token::ClosingTagName.new('template')
|
|
81
|
+
self.eat_whitespaces
|
|
82
|
+
self.expect('>')
|
|
83
|
+
@tokens << Token::TagEnd.new
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def scan_tag
|
|
87
|
+
tag_name = self.scan_tag_name
|
|
88
|
+
is_v_pre = false
|
|
89
|
+
self.eat_whitespaces
|
|
90
|
+
|
|
91
|
+
while (@code =~ ATTRIBUTE_NAME_RE) == 0
|
|
92
|
+
attr = self.scan_attribute
|
|
93
|
+
if attr[:name] == 'v-pre'
|
|
94
|
+
is_v_pre = true
|
|
95
|
+
end
|
|
96
|
+
self.eat_whitespaces
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
if @code.start_with?('/>')
|
|
100
|
+
@tokens << Token::SelfClosingTag.new
|
|
101
|
+
self.eat('/>')
|
|
102
|
+
return
|
|
103
|
+
elsif @code.start_with?('>')
|
|
104
|
+
if VOID_TAG.include?(tag_name)
|
|
105
|
+
@tokens << Token::SelfClosingTag.new
|
|
106
|
+
self.eat('>')
|
|
107
|
+
return
|
|
108
|
+
else
|
|
109
|
+
@tokens << Token::TagEnd.new
|
|
110
|
+
self.eat('>')
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
until @code.start_with?('</')
|
|
115
|
+
if @code.start_with?('<!--')
|
|
116
|
+
self.scan_comment
|
|
117
|
+
elsif @code.start_with?('<')
|
|
118
|
+
self.scan_tag
|
|
119
|
+
elsif @code.start_with?('{{')
|
|
120
|
+
self.scan_interpolation
|
|
121
|
+
else
|
|
122
|
+
self.scan_text(is_v_pre)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
self.expect('</')
|
|
127
|
+
@tokens << Token::ClosingTagStart.new
|
|
128
|
+
self.expect(tag_name)
|
|
129
|
+
@tokens << Token::ClosingTagName.new(tag_name)
|
|
130
|
+
self.eat_whitespaces
|
|
131
|
+
self.expect('>')
|
|
132
|
+
@tokens << Token::TagEnd.new
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def scan_tag_name
|
|
136
|
+
self.expect('<')
|
|
137
|
+
@tokens << Token::OpenTagStart.new
|
|
138
|
+
|
|
139
|
+
tag_name = @code.slice(TAG_NAME_RE)
|
|
140
|
+
@tokens << Token::OpenTagName.new(tag_name)
|
|
141
|
+
self.eat(tag_name)
|
|
142
|
+
tag_name
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def scan_attribute
|
|
146
|
+
name = @code.slice(ATTRIBUTE_NAME_RE)
|
|
147
|
+
value = nil
|
|
148
|
+
@tokens << Token::AttributeName.new(name)
|
|
149
|
+
self.eat(name)
|
|
150
|
+
|
|
151
|
+
self.eat_whitespaces
|
|
152
|
+
return { name: name, value: value } unless @code.start_with?('=')
|
|
153
|
+
|
|
154
|
+
self.expect('=')
|
|
155
|
+
self.eat_whitespaces
|
|
156
|
+
if (@code =~ ATTRIBUTE_VALUE_RE) == 0
|
|
157
|
+
self.scan_attribute_value
|
|
158
|
+
value = self.eat_whitespaces
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
{ name: name, value: value }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def scan_attribute_value
|
|
165
|
+
quoted = @code.start_with?("'") || @code.start_with?('"')
|
|
166
|
+
matched = @code.match(ATTRIBUTE_VALUE_RE)
|
|
167
|
+
value = matched[1] || matched[2] || matched[3]
|
|
168
|
+
@tokens << Token::AttributeValue.new(value)
|
|
169
|
+
|
|
170
|
+
@code = @code[((quoted ? 2 : 0) + value.size)..-1]
|
|
171
|
+
|
|
172
|
+
value
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def scan_text(ignore_interpolation = false)
|
|
176
|
+
text = ''
|
|
177
|
+
until @code.start_with?('<') || (!ignore_interpolation && @code.start_with?('{{'))
|
|
178
|
+
char = @code[0]
|
|
179
|
+
self.eat(char)
|
|
180
|
+
next if char == ' ' && text.end_with?(' ')
|
|
181
|
+
text << char
|
|
182
|
+
end
|
|
183
|
+
@tokens << Token::Text.new(text)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def scan_interpolation
|
|
187
|
+
expr = ''
|
|
188
|
+
is_string = false
|
|
189
|
+
|
|
190
|
+
@tokens << Token::InterpolationStart.new
|
|
191
|
+
self.eat('{{')
|
|
192
|
+
|
|
193
|
+
while is_string || (!is_string && !@code.start_with?('}}'))
|
|
194
|
+
char = @code[0]
|
|
195
|
+
expr << char
|
|
196
|
+
self.eat(char)
|
|
197
|
+
if char == '"' || char == "'" || char == '`'
|
|
198
|
+
is_string = !is_string
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
expr.strip!
|
|
202
|
+
@tokens << Token::Interpolation.new(expr)
|
|
203
|
+
|
|
204
|
+
@tokens << Token::InterpolationEnd.new
|
|
205
|
+
self.eat('}}')
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def scan_comment
|
|
209
|
+
@tokens << Token::CommentStart.new
|
|
210
|
+
end_pos = @code.index('-->')
|
|
211
|
+
if end_pos
|
|
212
|
+
@tokens << Token::Comment.new(@code[4...end_pos])
|
|
213
|
+
@tokens << Token::CommentEnd.new
|
|
214
|
+
@code = @code[end_pos + 3..-1]
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def scan_script_block
|
|
219
|
+
self.eat('<script')
|
|
220
|
+
self.eat_whitespaces
|
|
221
|
+
|
|
222
|
+
lang = if @code.start_with?('lang')
|
|
223
|
+
self.eat('lang')
|
|
224
|
+
self.eat_whitespaces
|
|
225
|
+
self.expect('=')
|
|
226
|
+
self.eat_whitespaces
|
|
227
|
+
quoted = @code.start_with?("'") || @code.start_with?('"')
|
|
228
|
+
matched = @code.match(ATTRIBUTE_VALUE_RE)
|
|
229
|
+
lang = matched[1] || matched[2] || matched[3]
|
|
230
|
+
@code = @code[((quoted ? 2 : 0) + lang.size)..-1]
|
|
231
|
+
self.eat_whitespaces
|
|
232
|
+
self.expect('>')
|
|
233
|
+
lang
|
|
234
|
+
elsif @code.start_with?('>')
|
|
235
|
+
self.eat('>')
|
|
236
|
+
nil
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
content = ''
|
|
240
|
+
is_string = false
|
|
241
|
+
while is_string || (!is_string && !@code.start_with?('</script'))
|
|
242
|
+
char = @code[0]
|
|
243
|
+
if char == '"' || char == "'" || char == '`'
|
|
244
|
+
is_string = !is_string
|
|
245
|
+
end
|
|
246
|
+
content << char
|
|
247
|
+
self.eat(char)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
self.eat('</script')
|
|
251
|
+
self.eat_whitespaces
|
|
252
|
+
self.expect('>')
|
|
253
|
+
|
|
254
|
+
@tokens << Token::ScriptBlock.new(content, lang)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def scan_style_block
|
|
258
|
+
content = ''
|
|
259
|
+
scoped = false
|
|
260
|
+
lang = 'css'
|
|
261
|
+
|
|
262
|
+
self.eat('<style')
|
|
263
|
+
self.eat_whitespaces
|
|
264
|
+
|
|
265
|
+
until @code.start_with?('>')
|
|
266
|
+
if @code.start_with?('scoped')
|
|
267
|
+
scoped = true
|
|
268
|
+
self.eat('scoped')
|
|
269
|
+
elsif @code.start_with?('lang')
|
|
270
|
+
self.eat('lang')
|
|
271
|
+
self.eat_whitespaces
|
|
272
|
+
self.expect('=')
|
|
273
|
+
self.eat_whitespaces
|
|
274
|
+
quoted = @code.start_with?("'") || @code.start_with?('"')
|
|
275
|
+
matched = @code.match(ATTRIBUTE_VALUE_RE)
|
|
276
|
+
lang = matched[1] || matched[2] || matched[3]
|
|
277
|
+
@code.sub!(QUOTE_RE, '')
|
|
278
|
+
self.eat(lang)
|
|
279
|
+
@code.sub!(QUOTE_RE, '')
|
|
280
|
+
end
|
|
281
|
+
self.eat_whitespaces
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
self.eat('>')
|
|
285
|
+
|
|
286
|
+
until @code.start_with?('</style')
|
|
287
|
+
char = @code[0]
|
|
288
|
+
content << char
|
|
289
|
+
self.eat(char)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
self.eat('</style')
|
|
293
|
+
self.eat_whitespaces
|
|
294
|
+
self.expect('>')
|
|
295
|
+
|
|
296
|
+
@tokens << Token::StyleBlock.new(content, lang, scoped)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def scan_custom_block
|
|
300
|
+
self.eat('<')
|
|
301
|
+
type = @code.slice(TAG_NAME_RE)
|
|
302
|
+
content = ''
|
|
303
|
+
self.eat(type)
|
|
304
|
+
|
|
305
|
+
self.eat_whitespaces
|
|
306
|
+
# TODO: support attributes on custom block.
|
|
307
|
+
self.eat('>')
|
|
308
|
+
|
|
309
|
+
until @code.start_with?("</#{type}")
|
|
310
|
+
char = @code[0]
|
|
311
|
+
content << char
|
|
312
|
+
self.eat(char)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
self.eat("</#{type}")
|
|
316
|
+
self.eat_whitespaces
|
|
317
|
+
self.eat('>')
|
|
318
|
+
|
|
319
|
+
@tokens << Token::CustomBlock.new(type, content)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
require "vue_component_compiler/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'vue_component_compiler'
|
|
7
|
+
spec.version = VueCompiler::VERSION
|
|
8
|
+
spec.authors = ['Pig Fang']
|
|
9
|
+
spec.email = ['g-plane@hotmail.com']
|
|
10
|
+
spec.licenses = ['MIT']
|
|
11
|
+
spec.summary = 'Vue component compiler'
|
|
12
|
+
spec.homepage = 'https://github.com/g-plane/vue-component-compiler-rb'
|
|
13
|
+
|
|
14
|
+
# Specify which files should be added to the gem when it is released.
|
|
15
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
16
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
17
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
18
|
+
end
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: vue_component_compiler
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Pig Fang
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-11-05 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.16'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.16'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.0'
|
|
55
|
+
description:
|
|
56
|
+
email:
|
|
57
|
+
- g-plane@hotmail.com
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- ".editorconfig"
|
|
63
|
+
- ".gitignore"
|
|
64
|
+
- ".rspec"
|
|
65
|
+
- ".travis.yml"
|
|
66
|
+
- Gemfile
|
|
67
|
+
- LICENSE
|
|
68
|
+
- README.md
|
|
69
|
+
- Rakefile
|
|
70
|
+
- lib/vue_component_compiler.rb
|
|
71
|
+
- lib/vue_component_compiler/node.rb
|
|
72
|
+
- lib/vue_component_compiler/parser.rb
|
|
73
|
+
- lib/vue_component_compiler/token.rb
|
|
74
|
+
- lib/vue_component_compiler/tokenizer.rb
|
|
75
|
+
- lib/vue_component_compiler/version.rb
|
|
76
|
+
- vue_component_compiler.gemspec
|
|
77
|
+
homepage: https://github.com/g-plane/vue-component-compiler-rb
|
|
78
|
+
licenses:
|
|
79
|
+
- MIT
|
|
80
|
+
metadata: {}
|
|
81
|
+
post_install_message:
|
|
82
|
+
rdoc_options: []
|
|
83
|
+
require_paths:
|
|
84
|
+
- lib
|
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '0'
|
|
95
|
+
requirements: []
|
|
96
|
+
rubyforge_project:
|
|
97
|
+
rubygems_version: 2.7.6
|
|
98
|
+
signing_key:
|
|
99
|
+
specification_version: 4
|
|
100
|
+
summary: Vue component compiler
|
|
101
|
+
test_files: []
|