rbexy 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/.gitignore +1 -0
- data/.rspec +1 -0
- data/.travis.yml +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Dockerfile +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +213 -0
- data/Guardfile +70 -0
- data/LICENSE.txt +21 -0
- data/README.md +452 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +15 -0
- data/example.rb +113 -0
- data/lib/rbexy.rb +35 -0
- data/lib/rbexy/component.rb +100 -0
- data/lib/rbexy/component_providers/rbexy_provider.rb +23 -0
- data/lib/rbexy/component_providers/view_component_provider.rb +23 -0
- data/lib/rbexy/component_tag_builder.rb +19 -0
- data/lib/rbexy/configuration.rb +15 -0
- data/lib/rbexy/hash_mash.rb +15 -0
- data/lib/rbexy/lexer.rb +279 -0
- data/lib/rbexy/nodes.rb +142 -0
- data/lib/rbexy/output_buffer.rb +8 -0
- data/lib/rbexy/parser.rb +204 -0
- data/lib/rbexy/rails.rb +8 -0
- data/lib/rbexy/rails/engine.rb +25 -0
- data/lib/rbexy/rails/template_handler.rb +9 -0
- data/lib/rbexy/runtime.rb +33 -0
- data/lib/rbexy/version.rb +3 -0
- data/lib/rbexy/view_context_helper.rb +11 -0
- data/rbexy.gemspec +40 -0
- metadata +269 -0
data/lib/rbexy/nodes.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
module Rbexy
|
2
|
+
module Nodes
|
3
|
+
module Util
|
4
|
+
def self.safe_string(str)
|
5
|
+
str.gsub('"', '\\"')
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.safe_tag_name(name)
|
9
|
+
name.gsub(".", "__")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Template
|
14
|
+
attr_reader :children
|
15
|
+
|
16
|
+
def initialize(children)
|
17
|
+
@children = children
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile
|
21
|
+
[
|
22
|
+
"Rbexy::OutputBuffer.new.tap { |output|",
|
23
|
+
children.map(&:compile).map { |c| "output << (#{c})"}.join(";"),
|
24
|
+
"}.html_safe"
|
25
|
+
].join(" ")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Text
|
30
|
+
attr_reader :content
|
31
|
+
|
32
|
+
def initialize(content)
|
33
|
+
@content = content
|
34
|
+
end
|
35
|
+
|
36
|
+
def compile
|
37
|
+
"\"#{Util.safe_string(content)}\""
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class ExpressionGroup
|
42
|
+
attr_reader :statements
|
43
|
+
|
44
|
+
def initialize(statements)
|
45
|
+
@statements = statements
|
46
|
+
end
|
47
|
+
|
48
|
+
def compile
|
49
|
+
statements.map(&:compile).join
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Expression
|
54
|
+
attr_reader :content
|
55
|
+
|
56
|
+
def initialize(content)
|
57
|
+
@content = content
|
58
|
+
end
|
59
|
+
|
60
|
+
def compile
|
61
|
+
content
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class XmlNode
|
66
|
+
attr_reader :name, :members, :children
|
67
|
+
|
68
|
+
def initialize(name, members, children)
|
69
|
+
@name = name
|
70
|
+
@members = members || []
|
71
|
+
@children = children
|
72
|
+
end
|
73
|
+
|
74
|
+
def compile
|
75
|
+
base_tag = "rbexy_tag.#{Util.safe_tag_name(name)}(#{compile_members})"
|
76
|
+
tag = if children.length > 0
|
77
|
+
[
|
78
|
+
"#{base_tag} {",
|
79
|
+
"Rbexy::OutputBuffer.new.tap { |output|",
|
80
|
+
children.map(&:compile).map { |c| "output << (#{c})"}.join(";"),
|
81
|
+
"}.html_safe",
|
82
|
+
"}"
|
83
|
+
].join(" ")
|
84
|
+
else
|
85
|
+
base_tag
|
86
|
+
end
|
87
|
+
|
88
|
+
[
|
89
|
+
"Rbexy::OutputBuffer.new.tap { |output|",
|
90
|
+
"rbexy_context.push({}) if defined?(Rbexy::Component) && self.is_a?(Rbexy::Component);",
|
91
|
+
"output << (#{tag});",
|
92
|
+
"rbexy_context.pop if defined?(Rbexy::Component) && self.is_a?(Rbexy::Component);",
|
93
|
+
"}.html_safe"
|
94
|
+
].join(" ")
|
95
|
+
end
|
96
|
+
|
97
|
+
def compile_members
|
98
|
+
members.each_with_object("") do |member, result|
|
99
|
+
case member
|
100
|
+
when ExpressionGroup
|
101
|
+
result << "**#{member.compile},"
|
102
|
+
when SilentNewline
|
103
|
+
result << member.compile
|
104
|
+
else
|
105
|
+
result << "#{member.compile},"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class XmlAttr
|
112
|
+
attr_reader :name, :value
|
113
|
+
|
114
|
+
def initialize(name, value)
|
115
|
+
@name = name
|
116
|
+
@value = value
|
117
|
+
end
|
118
|
+
|
119
|
+
def compile
|
120
|
+
"\"#{name}\": #{value.compile}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class SilentNewline
|
125
|
+
def compile
|
126
|
+
"\n"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class Declaration
|
131
|
+
attr_reader :content
|
132
|
+
|
133
|
+
def initialize(content)
|
134
|
+
@content = content
|
135
|
+
end
|
136
|
+
|
137
|
+
def compile
|
138
|
+
"\"#{Util.safe_string(content)}\".html_safe"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
data/lib/rbexy/parser.rb
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
module Rbexy
|
2
|
+
class Parser
|
3
|
+
class ParseError < StandardError; end
|
4
|
+
|
5
|
+
attr_reader :tokens
|
6
|
+
attr_accessor :position
|
7
|
+
|
8
|
+
def initialize(tokens)
|
9
|
+
@tokens = tokens
|
10
|
+
@position = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse
|
14
|
+
validate_tokens!
|
15
|
+
Nodes::Template.new(parse_tokens)
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_tokens
|
19
|
+
results = []
|
20
|
+
|
21
|
+
while result = parse_token
|
22
|
+
results << result
|
23
|
+
end
|
24
|
+
|
25
|
+
results
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_token
|
29
|
+
parse_text || parse_silent_newline || parse_expression || parse_tag || parse_declaration
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_text
|
33
|
+
return unless token = take(:TEXT)
|
34
|
+
Nodes::Text.new(token[1])
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_expression
|
38
|
+
return unless take(:OPEN_EXPRESSION)
|
39
|
+
|
40
|
+
statements = []
|
41
|
+
|
42
|
+
eventually!(:CLOSE_EXPRESSION)
|
43
|
+
until take(:CLOSE_EXPRESSION)
|
44
|
+
statements << (parse_expression_body || parse_tag)
|
45
|
+
end
|
46
|
+
|
47
|
+
Nodes::ExpressionGroup.new(statements)
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_expression!
|
51
|
+
peek!(:OPEN_EXPRESSION)
|
52
|
+
parse_expression
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse_expression_body
|
56
|
+
return unless token = take(:EXPRESSION_BODY)
|
57
|
+
Nodes::Expression.new(token[1])
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_tag
|
61
|
+
return unless take(:OPEN_TAG_DEF)
|
62
|
+
|
63
|
+
name = take!(:TAG_NAME)
|
64
|
+
members = []
|
65
|
+
members.concat(take_all(:SILENT_NEWLINE).map { Nodes::SilentNewline.new })
|
66
|
+
members.concat(parse_attrs)
|
67
|
+
|
68
|
+
take!(:CLOSE_TAG_DEF)
|
69
|
+
|
70
|
+
children = parse_children
|
71
|
+
|
72
|
+
Nodes::XmlNode.new(name[1], members, children)
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse_attrs
|
76
|
+
return [] unless take(:OPEN_ATTRS)
|
77
|
+
|
78
|
+
attrs = []
|
79
|
+
|
80
|
+
eventually!(:CLOSE_ATTRS)
|
81
|
+
until take(:CLOSE_ATTRS)
|
82
|
+
attrs << (parse_splat_attr || parse_silent_newline || parse_attr)
|
83
|
+
end
|
84
|
+
|
85
|
+
attrs
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_splat_attr
|
89
|
+
return unless take(:OPEN_ATTR_SPLAT)
|
90
|
+
|
91
|
+
expression = parse_expression!
|
92
|
+
take!(:CLOSE_ATTR_SPLAT)
|
93
|
+
|
94
|
+
expression
|
95
|
+
end
|
96
|
+
|
97
|
+
def parse_silent_newline
|
98
|
+
return unless take(:SILENT_NEWLINE)
|
99
|
+
Nodes::SilentNewline.new
|
100
|
+
end
|
101
|
+
|
102
|
+
def parse_attr
|
103
|
+
name = take!(:ATTR_NAME)[1]
|
104
|
+
value = nil
|
105
|
+
|
106
|
+
if take(:OPEN_ATTR_VALUE)
|
107
|
+
value = parse_text || parse_expression
|
108
|
+
raise ParseError, "Missing attribute value" unless value
|
109
|
+
take(:CLOSE_ATTR_VALUE)
|
110
|
+
else
|
111
|
+
value = default_empty_attr_value
|
112
|
+
end
|
113
|
+
|
114
|
+
Nodes::XmlAttr.new(name, value)
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse_children
|
118
|
+
children = []
|
119
|
+
|
120
|
+
eventually!(:OPEN_TAG_END)
|
121
|
+
until take(:OPEN_TAG_END)
|
122
|
+
children << parse_token
|
123
|
+
end
|
124
|
+
|
125
|
+
take(:TAG_NAME)
|
126
|
+
take!(:CLOSE_TAG_END)
|
127
|
+
|
128
|
+
children
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def parse_declaration
|
134
|
+
return unless token = take(:DECLARATION)
|
135
|
+
Nodes::Declaration.new(token[1])
|
136
|
+
end
|
137
|
+
|
138
|
+
def take(token_name)
|
139
|
+
if token = peek(token_name)
|
140
|
+
self.position += 1
|
141
|
+
token
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def take_all(token_name)
|
146
|
+
result = []
|
147
|
+
while token = take(token_name)
|
148
|
+
result << token
|
149
|
+
end
|
150
|
+
result
|
151
|
+
end
|
152
|
+
|
153
|
+
def take!(token_name)
|
154
|
+
take(token_name) || unexpected_token!(token_name)
|
155
|
+
end
|
156
|
+
|
157
|
+
def peek(token_name)
|
158
|
+
if (token = tokens[position]) && token[0] == token_name
|
159
|
+
token
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def peek!(token_name)
|
164
|
+
peek(token_name) || unexpected_token!(token_name)
|
165
|
+
end
|
166
|
+
|
167
|
+
def eventually!(token_name)
|
168
|
+
tokens[position..-1].first { |t| t[0] == token_name } ||
|
169
|
+
raise(ParseError, "Expected to find a #{token_name} but never did")
|
170
|
+
end
|
171
|
+
|
172
|
+
def default_empty_attr_value
|
173
|
+
Nodes::Text.new("")
|
174
|
+
end
|
175
|
+
|
176
|
+
def error_window
|
177
|
+
window_start = position - 2
|
178
|
+
window_start = 0 if window_start < 0
|
179
|
+
window_end = position + 2
|
180
|
+
window_end = tokens.length-1 if window_end >= tokens.length
|
181
|
+
|
182
|
+
tokens[window_start..window_end].map.with_index do |token, i|
|
183
|
+
prefix = window_start + i == position ? "=>" : " "
|
184
|
+
"#{prefix} #{token}"
|
185
|
+
end.join("\n")
|
186
|
+
end
|
187
|
+
|
188
|
+
def unexpected_token!(expected_token)
|
189
|
+
raise(ParseError, "Unexpected token #{tokens[position][0]}, expecting #{expected_token}\n#{error_window}")
|
190
|
+
end
|
191
|
+
|
192
|
+
def validate_tokens!
|
193
|
+
validate_all_tags_close!
|
194
|
+
end
|
195
|
+
|
196
|
+
def validate_all_tags_close!
|
197
|
+
open_count = tokens.count { |t| t[0] == :OPEN_TAG_DEF }
|
198
|
+
close_count = tokens.count { |t| t[0] == :OPEN_TAG_END }
|
199
|
+
if open_count != close_count
|
200
|
+
raise(ParseError, "#{open_count - close_count} tags fail to close. All tags must close, either <NAME></NAME> or self-closing <NAME />")
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
data/lib/rbexy/rails.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "rbexy/rails"
|
2
|
+
|
3
|
+
module Rbexy
|
4
|
+
module Rails
|
5
|
+
class Engine < ::Rails::Engine
|
6
|
+
initializer "rbexy" do |app|
|
7
|
+
ActionView::Template.register_template_handler(:rbx, Rbexy::Rails::TemplateHandler)
|
8
|
+
|
9
|
+
ActiveSupport.on_load :action_controller do
|
10
|
+
helper Rbexy::ViewContextHelper
|
11
|
+
end
|
12
|
+
|
13
|
+
if defined?(ViewComponent)
|
14
|
+
ViewComponent::Base.include Rbexy::ViewContextHelper
|
15
|
+
end
|
16
|
+
|
17
|
+
Rbexy.configure do |config|
|
18
|
+
require "rbexy/component_providers/rbexy_provider"
|
19
|
+
config.component_provider = Rbexy::ComponentProviders::RbexyProvider.new
|
20
|
+
config.template_paths << ::Rails.root.join("app", "components")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "active_support/all"
|
2
|
+
require "action_view/helpers"
|
3
|
+
require "action_view/context"
|
4
|
+
require "action_view/buffers"
|
5
|
+
|
6
|
+
module Rbexy
|
7
|
+
class Runtime
|
8
|
+
include ActionView::Context
|
9
|
+
include ActionView::Helpers::TagHelper
|
10
|
+
include ViewContextHelper
|
11
|
+
|
12
|
+
DefaultTagBuilder = ActionView::Helpers::TagHelper::TagBuilder
|
13
|
+
|
14
|
+
def self.create_tag_builder(context, provider = Rbexy.configuration.component_provider)
|
15
|
+
if provider
|
16
|
+
ComponentTagBuilder.new(context, provider)
|
17
|
+
else
|
18
|
+
ActionView::Helpers::TagHelper::TagBuilder.new(context)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(component_provider = nil)
|
23
|
+
@rbexy_tag = self.class.create_tag_builder(self, component_provider)
|
24
|
+
end
|
25
|
+
|
26
|
+
def evaluate(code)
|
27
|
+
instance_eval(code)
|
28
|
+
rescue => e
|
29
|
+
e.set_backtrace(e.backtrace.map { |l| l.gsub("(eval)", "(rbx template string)") })
|
30
|
+
raise e
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|