rbexy 1.1.0 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -1
  3. data/README.md +18 -0
  4. data/lib/rbexy.rb +26 -9
  5. data/lib/rbexy/ast_transformer.rb +21 -0
  6. data/lib/rbexy/component.rb +1 -6
  7. data/lib/rbexy/component_resolver.rb +60 -0
  8. data/lib/rbexy/configuration.rb +18 -1
  9. data/lib/rbexy/lexer.rb +20 -13
  10. data/lib/rbexy/nodes.rb +15 -135
  11. data/lib/rbexy/nodes/abstract_attr.rb +12 -0
  12. data/lib/rbexy/nodes/abstract_element.rb +13 -0
  13. data/lib/rbexy/nodes/abstract_node.rb +58 -0
  14. data/lib/rbexy/nodes/component_element.rb +69 -0
  15. data/lib/rbexy/nodes/component_prop.rb +29 -0
  16. data/lib/rbexy/nodes/declaration.rb +15 -0
  17. data/lib/rbexy/nodes/expression.rb +15 -0
  18. data/lib/rbexy/nodes/expression_group.rb +55 -0
  19. data/lib/rbexy/nodes/html_attr.rb +13 -0
  20. data/lib/rbexy/nodes/html_element.rb +48 -0
  21. data/lib/rbexy/nodes/newline.rb +9 -0
  22. data/lib/rbexy/nodes/raw.rb +23 -0
  23. data/lib/rbexy/nodes/root.rb +19 -0
  24. data/lib/rbexy/nodes/text.rb +15 -0
  25. data/lib/rbexy/nodes/util.rb +9 -0
  26. data/lib/rbexy/parser.rb +22 -16
  27. data/lib/rbexy/rails/component_template_resolver.rb +3 -3
  28. data/lib/rbexy/rails/controller_helper.rb +0 -6
  29. data/lib/rbexy/rails/engine.rb +1 -7
  30. data/lib/rbexy/refinements.rb +5 -0
  31. data/lib/rbexy/refinements/array.rb +9 -0
  32. data/lib/rbexy/refinements/array/find_map.rb +13 -0
  33. data/lib/rbexy/refinements/array/insert_between_types.rb +26 -0
  34. data/lib/rbexy/refinements/array/map_type_when_neighboring_type.rb +26 -0
  35. data/lib/rbexy/runtime.rb +15 -23
  36. data/lib/rbexy/template.rb +12 -0
  37. data/lib/rbexy/version.rb +1 -1
  38. data/rbexy.gemspec +1 -0
  39. metadata +41 -11
  40. data/example.rb +0 -113
  41. data/lib/rbexy/component_providers/namespaced_rbexy_provider.rb +0 -20
  42. data/lib/rbexy/component_providers/rbexy_provider.rb +0 -21
  43. data/lib/rbexy/component_providers/view_component_provider.rb +0 -21
  44. data/lib/rbexy/component_tag_builder.rb +0 -19
  45. data/lib/rbexy/hash_mash.rb +0 -15
  46. data/lib/rbexy/view_context_helper.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6728982815c307bb37a495c3d11450e9582bf1cd17eef5306c9e940c0c36eea
4
- data.tar.gz: 5fdfa1cc3c5fc70b573aa730f827b2f5d38b8031f1adc6344b31926ce520b4af
3
+ metadata.gz: c25ae0414d2229676a67b1ec6ee63c6092d00bb699668cd1213c93d50dee2356
4
+ data.tar.gz: 00d80e6099de62ca467cf6c75b661cf43eeb8641fe15c510163ba6f02a1fcc99
5
5
  SHA512:
6
- metadata.gz: 46385e65a3cd976497efd8d6db789eee4a2c73668e085dd6772d8c6a9a835783f6bb80ef923463af97cfec5d5625fffd641229a5a97a1c24e024b75d996969af
7
- data.tar.gz: 025e25a41d9cc5e848342c668721cdef046ab13e5cff9737acc69a66eaca6cdd3ac06493bfcbb42b735fcbd6e7ae243e8a65588d648d3442924306944addb714
6
+ metadata.gz: e29d842f6f9d90c2908be38c58e8d86a365496eb60b093dacc15364f19af00238457a8e7d4877f8db9305a88a29db5c5a6682b2684c41abd8c0ffa19b8279cb4
7
+ data.tar.gz: 8298b8fa13abef5f8a85f0df96d9abda969cc0b98417c5668f34b644ba79a9d0821c372989f0111149b4abc1b9851eb4d69e8aa79b5d69a91f425a931845e24d
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbexy (1.1.0)
4
+ rbexy (2.0.0.beta1)
5
5
  actionview (>= 6.0, < 7.0)
6
6
  activesupport (>= 6.0, < 7.0)
7
7
 
@@ -101,6 +101,7 @@ GEM
101
101
  mini_mime (>= 0.1.1)
102
102
  marcel (0.3.3)
103
103
  mimemagic (~> 0.3.2)
104
+ memory_profiler (0.9.14)
104
105
  method_source (1.0.0)
105
106
  mimemagic (0.3.5)
106
107
  mini_mime (1.0.2)
@@ -199,6 +200,7 @@ PLATFORMS
199
200
 
200
201
  DEPENDENCIES
201
202
  guard-rspec (~> 4.7, >= 4.7.3)
203
+ memory_profiler (~> 0.9.14)
202
204
  pry-byebug
203
205
  rails (>= 6.0, < 7.0)
204
206
  rake
data/README.md CHANGED
@@ -173,6 +173,14 @@ Loops:
173
173
  </ul>
174
174
  ```
175
175
 
176
+ Blocks:
177
+
178
+ ```jsx
179
+ {link_to "/" do
180
+ <span>Click me</span>
181
+ end}
182
+ ```
183
+
176
184
  As an attribute:
177
185
 
178
186
  ```jsx
@@ -189,6 +197,16 @@ Pass a lambda to a prop, that when called returns a tag:
189
197
  </Hero>
190
198
  ```
191
199
 
200
+ _Note that when using tags inside blocks, the block must evaluate to a single root element. Rbexy behaves similar to JSX in this way. E.g.:_
201
+
202
+ ```
203
+ # Do
204
+ -> { <span><i>Hello</i> World</span> }
205
+
206
+ # Don't
207
+ -> { <i>Hello</i> World }
208
+ ```
209
+
192
210
  ### Tags
193
211
 
194
212
  You can put standard HTML tags anywhere.
@@ -1,15 +1,22 @@
1
1
  require "rbexy/version"
2
+ require "active_support/inflector"
3
+ require "active_support/concern"
4
+ require "action_view/helpers/output_safety_helper"
5
+ require "action_view/helpers/capture_helper"
6
+ require "action_view/helpers/tag_helper"
7
+ require "action_view/context"
2
8
 
3
9
  module Rbexy
4
10
  autoload :Lexer, "rbexy/lexer"
5
11
  autoload :Parser, "rbexy/parser"
6
12
  autoload :Nodes, "rbexy/nodes"
7
13
  autoload :Runtime, "rbexy/runtime"
8
- autoload :HashMash, "rbexy/hash_mash"
9
- autoload :ComponentTagBuilder, "rbexy/component_tag_builder"
10
- autoload :ViewContextHelper, "rbexy/view_context_helper"
11
14
  autoload :ComponentContext, "rbexy/component_context"
12
15
  autoload :Configuration, "rbexy/configuration"
16
+ autoload :ComponentResolver, "rbexy/component_resolver"
17
+ autoload :Template, "rbexy/template"
18
+ autoload :Refinements, "rbexy/refinements"
19
+ autoload :ASTTransformer, "rbexy/ast_transformer"
13
20
 
14
21
  ContextNotFound = Class.new(StandardError)
15
22
 
@@ -22,14 +29,24 @@ module Rbexy
22
29
  @configuration ||= Configuration.new
23
30
  end
24
31
 
25
- def compile(template_string)
26
- tokens = Rbexy::Lexer.new(template_string).tokenize
27
- template = Rbexy::Parser.new(tokens).parse
28
- template.compile
32
+ def compile(template, context = build_default_compile_context(template))
33
+ tokens = Lexer.new(template, context.element_resolver).tokenize
34
+ root = Parser.new(tokens).parse
35
+ root.inject_compile_context(context)
36
+ root.transform!
37
+ root.precompile.compile
29
38
  end
30
39
 
31
- def evaluate(template_string, runtime)
32
- runtime.evaluate compile(template_string)
40
+ def evaluate(template_string, runtime = Rbexy::Runtime.new)
41
+ runtime.evaluate compile(Template.new(template_string))
42
+ end
43
+
44
+ def build_default_compile_context(template)
45
+ OpenStruct.new(
46
+ template: template,
47
+ element_resolver: configuration.element_resolver,
48
+ ast_transformer: configuration.transforms
49
+ )
33
50
  end
34
51
  end
35
52
  end
@@ -0,0 +1,21 @@
1
+ module Rbexy
2
+ class ASTTransformer
3
+ attr_reader :registry
4
+
5
+ def initialize
6
+ clear!
7
+ end
8
+
9
+ def register(*node_classes, &block)
10
+ node_classes.each { |k| (registry[k] ||= []) << block }
11
+ end
12
+
13
+ def transform(node, context)
14
+ registry[node.class]&.each { |t| t.call(node, context) }
15
+ end
16
+
17
+ def clear!
18
+ @registry = {}
19
+ end
20
+ end
21
+ end
@@ -1,5 +1,4 @@
1
1
  require "action_view"
2
- require "active_support/core_ext/class/attribute"
3
2
 
4
3
  module Rbexy
5
4
  class Component < ActionView::Base
@@ -63,11 +62,7 @@ module Rbexy
63
62
  attr_reader :view_context, :content_block
64
63
 
65
64
  def method_missing(meth, *args, &block)
66
- if view_context.respond_to?(meth)
67
- view_context.send(meth, *args, &block)
68
- else
69
- super
70
- end
65
+ view_context.send(meth, *args, &block)
71
66
  end
72
67
 
73
68
  def clean_template_backtrace(backtrace)
@@ -0,0 +1,60 @@
1
+ module Rbexy
2
+ class ComponentResolver
3
+ using Rbexy::Refinements::Array::FindMap
4
+
5
+ KNOWN_HTML_ELEMENTS = %w(
6
+ a abbr acronym address animate animateMotion animateTransform applet area article aside audio b base basefont
7
+ bdi bdo bgsound big blink blockquote body br button canvas caption center circle cite clipPath code col colgroup
8
+ color-profile command content data datalist dd defs del desc details dfn dialog dir discard div dl dt element
9
+ ellipse em embed feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting
10
+ feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage
11
+ feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence
12
+ fieldset figcaption figure filter font footer foreignObject form frame frameset g h1 h2 h3 h4 h5 h6 hatch
13
+ hatchpath head header hgroup hr html i iframe image img input ins isindex kbd keygen label legend li line
14
+ linearGradient link listing main map mark marker marquee mask menu menuitem mesh meshgradient meshpatch meshrow
15
+ meta metadata meter mpath multicol nav nextid nobr noembed noframes noscript object ol optgroup option output p
16
+ param path pattern picture plaintext polygon polyline pre progress q radialGradient rb rect rp rt rtc ruby s
17
+ samp script section select set shadow slot small solidcolor source spacer span stop strike strong style sub
18
+ summary sup svg switch symbol table tbody td template text textarea textPath tfoot th thead time title tr track
19
+ tspan tt u ul unknown use var video view wbr xmp
20
+ ).to_set
21
+
22
+ attr_reader :component_namespaces
23
+
24
+ def initialize
25
+ self.component_namespaces = {}
26
+ end
27
+
28
+ def component_namespaces=(hash)
29
+ @component_namespaces = hash.transform_keys(&:to_s)
30
+ end
31
+
32
+ def component?(name, template)
33
+ return false if KNOWN_HTML_ELEMENTS.include?(name)
34
+ return true if component_class(name, template)
35
+ false
36
+ end
37
+
38
+ def component_class(name, template)
39
+ possible_names = matching_namespaces(template).map { |ns| "#{ns}.#{name}" } << name
40
+ possible_names.find_map(&method(:find))
41
+ end
42
+
43
+ private
44
+
45
+ def find(name)
46
+ find!(name)
47
+ rescue NameError => e
48
+ raise e unless e.message =~ /wrong constant name/ || e.message =~ /uninitialized constant/
49
+ nil
50
+ end
51
+
52
+ def matching_namespaces(template)
53
+ component_namespaces.select { |path, ns| template.identifier.start_with?(path) }.values.flatten.uniq
54
+ end
55
+
56
+ def find!(name)
57
+ ActiveSupport::Inflector.constantize("#{name.gsub(".", "::")}Component")
58
+ end
59
+ end
60
+ end
@@ -1,12 +1,29 @@
1
1
  module Rbexy
2
2
  class Configuration
3
- attr_accessor :component_provider
3
+ attr_accessor :element_resolver
4
4
  attr_accessor :template_paths
5
5
  attr_accessor :enable_context
6
6
  attr_accessor :debug
7
+ attr_accessor :component_rendering_templates
8
+ attr_accessor :transforms
7
9
 
8
10
  def template_paths
9
11
  @template_paths ||= []
10
12
  end
13
+
14
+ def element_resolver
15
+ @element_resolver ||= ComponentResolver.new
16
+ end
17
+
18
+ def transforms
19
+ @transforms ||= ASTTransformer.new
20
+ end
21
+
22
+ def component_rendering_templates
23
+ @component_rendering_templates ||= {
24
+ children: "{capture{%{children}}}",
25
+ component: "::%{component_class}.new(%{view_context},%{kwargs}).render%{children_block}"
26
+ }
27
+ end
11
28
  end
12
29
  end
@@ -10,7 +10,7 @@ module Rbexy
10
10
  end
11
11
  end
12
12
 
13
- Patterns = HashMash.new(
13
+ Patterns = OpenStruct.new(
14
14
  open_expression: /{/,
15
15
  close_expression: /}/,
16
16
  expression_content: /[^}{"'<]+/,
@@ -33,19 +33,19 @@ module Rbexy
33
33
  declaration: /<![^>]*>/
34
34
  )
35
35
 
36
- attr_reader :stack, :tokens, :scanner, :curr_expr_quote_levels
37
- attr_accessor :curr_expr_bracket_levels, :curr_expr, :curr_default_text,
36
+ attr_reader :stack, :tokens, :scanner, :element_resolver, :template
37
+ attr_accessor :curr_expr, :curr_default_text,
38
38
  :curr_quoted_text
39
39
 
40
- def initialize(code)
40
+ def initialize(template, element_resolver)
41
+ @template = template
42
+ @scanner = StringScanner.new(template.source)
43
+ @element_resolver = element_resolver
41
44
  @stack = [:default]
42
- @curr_expr_bracket_levels = 0
43
- @curr_expr_quote_levels = { single: 0, double: 0 }
44
45
  @curr_expr = ""
45
46
  @curr_default_text = ""
46
47
  @curr_quoted_text = ""
47
48
  @tokens = []
48
- @scanner = StringScanner.new(code)
49
49
  end
50
50
 
51
51
  def tokenize
@@ -59,7 +59,7 @@ module Rbexy
59
59
  elsif scanner.scan(Patterns.open_expression)
60
60
  open_expression
61
61
  elsif scanner.scan(Patterns.comment)
62
- tokens << [:SILENT_NEWLINE]
62
+ tokens << [:NEWLINE]
63
63
  elsif scanner.check(Patterns.text_content)
64
64
  stack.push(:default_text)
65
65
  else
@@ -74,7 +74,7 @@ module Rbexy
74
74
  elsif scanner.scan(Patterns.open_expression)
75
75
  open_expression
76
76
  elsif scanner.scan(Patterns.comment)
77
- tokens << [:SILENT_NEWLINE]
77
+ tokens << [:NEWLINE]
78
78
  elsif scanner.check(Patterns.text_content)
79
79
  stack.push(:default_text)
80
80
  else
@@ -163,9 +163,9 @@ module Rbexy
163
163
  tokens << [:CLOSE_TAG_DEF]
164
164
  stack.pop
165
165
  elsif scanner.scan(Patterns.tag_name)
166
- tokens << [:TAG_NAME, scanner.matched]
166
+ tokens << [:TAG_DETAILS, tag_details(scanner.matched)]
167
167
  elsif scanner.scan(Patterns.whitespace)
168
- scanner.matched.count("\n").times { tokens << [:SILENT_NEWLINE] }
168
+ scanner.matched.count("\n").times { tokens << [:NEWLINE] }
169
169
  tokens << [:OPEN_ATTRS]
170
170
  stack.push(:tag_attrs)
171
171
  else
@@ -182,7 +182,7 @@ module Rbexy
182
182
  end
183
183
  when :tag_attrs
184
184
  if scanner.scan(Patterns.whitespace)
185
- scanner.matched.count("\n").times { tokens << [:SILENT_NEWLINE] }
185
+ scanner.matched.count("\n").times { tokens << [:NEWLINE] }
186
186
  elsif scanner.check(Patterns.close_tag)
187
187
  tokens << [:CLOSE_ATTRS]
188
188
  stack.pop
@@ -205,7 +205,7 @@ module Rbexy
205
205
  open_expression
206
206
  elsif scanner.scan(Patterns.whitespace) || scanner.check(Patterns.close_tag)
207
207
  tokens << [:CLOSE_ATTR_VALUE]
208
- scanner.matched.count("\n").times { tokens << [:SILENT_NEWLINE] }
208
+ scanner.matched.count("\n").times { tokens << [:NEWLINE] }
209
209
  stack.pop
210
210
  else
211
211
  raise SyntaxError, self
@@ -286,5 +286,12 @@ module Rbexy
286
286
  # etc).
287
287
  scanner.scan(Patterns.expression_content) || scanner.scan(Patterns.open_tag_end)
288
288
  end
289
+
290
+ def tag_details(name)
291
+ type = element_resolver.component?(name, template) ? :component : :html
292
+ details = { name: scanner.matched, type: type }
293
+ details[:component_class] = element_resolver.component_class(name, template) if type == :component
294
+ details
295
+ end
289
296
  end
290
297
  end
@@ -1,139 +1,19 @@
1
1
  module Rbexy
2
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
- "#{children.map(&:compile).map { |c| "@output_buffer << rbexy_prep_output(#{c})"}.join(";")};@output_buffer"
22
- end
23
- end
24
-
25
- class Text
26
- attr_reader :content
27
-
28
- def initialize(content)
29
- @content = content
30
- end
31
-
32
- def compile
33
- "\"#{Util.safe_string(content)}\""
34
- end
35
- end
36
-
37
- class ExpressionGroup
38
- attr_reader :statements
39
-
40
- def initialize(statements)
41
- @statements = statements
42
- end
43
-
44
- def compile
45
- statements.map(&:compile).join
46
- end
47
- end
48
-
49
- class Expression
50
- attr_reader :content
51
-
52
- def initialize(content)
53
- @content = content
54
- end
55
-
56
- def compile
57
- content
58
- end
59
- end
60
-
61
- class XmlNode
62
- attr_reader :name, :members, :children
63
-
64
- def initialize(name, members, children)
65
- @name = name
66
- @members = members || []
67
- @children = children
68
- end
69
-
70
- def compile
71
- base_tag = "rbexy_tag.#{Util.safe_tag_name(name)}(#{compile_members})"
72
- tag = if children.length > 0
73
- [
74
- "#{base_tag} { capture {",
75
- children.map(&:compile).map { |c| "@output_buffer << rbexy_prep_output(#{c})" }.join(";"),
76
- "} }"
77
- ].join
78
- else
79
- base_tag
80
- end + ".html_safe"
81
-
82
- if Rbexy.configuration.enable_context
83
- [
84
- "(",
85
- "rbexy_context.push({});",
86
- "#{tag}.tap { rbexy_context.pop }",
87
- ")"
88
- ].join
89
- else
90
- tag
91
- end
92
- end
93
-
94
- def compile_members
95
- members.each_with_object("") do |member, result|
96
- case member
97
- when ExpressionGroup
98
- result << "**#{member.compile},"
99
- when SilentNewline
100
- result << member.compile
101
- else
102
- result << "#{member.compile},"
103
- end
104
- end
105
- end
106
- end
107
-
108
- class XmlAttr
109
- attr_reader :name, :value
110
-
111
- def initialize(name, value)
112
- @name = name
113
- @value = value
114
- end
115
-
116
- def compile
117
- "\"#{name}\": #{value.compile}"
118
- end
119
- end
120
-
121
- class SilentNewline
122
- def compile
123
- "\n"
124
- end
125
- end
126
-
127
- class Declaration
128
- attr_reader :content
129
-
130
- def initialize(content)
131
- @content = content
132
- end
133
-
134
- def compile
135
- "\"#{Util.safe_string(content)}\".html_safe"
136
- end
137
- end
3
+ autoload :Util, "rbexy/nodes/util"
4
+ autoload :AbstractNode, "rbexy/nodes/abstract_node"
5
+ autoload :Root, "rbexy/nodes/root"
6
+ autoload :Raw, "rbexy/nodes/raw"
7
+ autoload :Text, "rbexy/nodes/text"
8
+ autoload :ExpressionGroup, "rbexy/nodes/expression_group"
9
+ autoload :Expression, "rbexy/nodes/expression"
10
+ autoload :AbstractElement, "rbexy/nodes/abstract_element"
11
+ autoload :HTMLElement, "rbexy/nodes/html_element"
12
+ autoload :ComponentElement, "rbexy/nodes/component_element"
13
+ autoload :AbstractAttr, "rbexy/nodes/abstract_attr"
14
+ autoload :HTMLAttr, "rbexy/nodes/html_attr"
15
+ autoload :ComponentProp, "rbexy/nodes/component_prop"
16
+ autoload :Newline, "rbexy/nodes/newline"
17
+ autoload :Declaration, "rbexy/nodes/declaration"
138
18
  end
139
19
  end