rbexy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,8 @@
1
+ module Rbexy
2
+ class OutputBuffer < String
3
+ def <<(content)
4
+ value = content.is_a?(Array) ? content.join : content
5
+ super([nil, false].include?(value) ? "" : value.to_s)
6
+ end
7
+ end
8
+ end
@@ -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
@@ -0,0 +1,8 @@
1
+ module Rbexy
2
+ autoload :Component, "rbexy/component"
3
+
4
+ module Rails
5
+ autoload :TemplateHandler, "rbexy/rails/template_handler"
6
+ autoload :Engine, "rbexy/rails/engine"
7
+ end
8
+ end
@@ -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,9 @@
1
+ module Rbexy
2
+ module Rails
3
+ class TemplateHandler
4
+ def self.call(template, source)
5
+ Rbexy.compile(source)
6
+ end
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,3 @@
1
+ module Rbexy
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,11 @@
1
+ module Rbexy
2
+ module ViewContextHelper
3
+ def rbexy_tag
4
+ @rbexy_tag ||= Runtime.create_tag_builder(self)
5
+ end
6
+
7
+ def rbexy_context
8
+ @rbexy_context ||= [{}]
9
+ end
10
+ end
11
+ end