rbexy 0.3.1 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +6 -5
  3. data/README.md +20 -0
  4. data/lib/rbexy.rb +27 -10
  5. data/lib/rbexy/ast_transformer.rb +21 -0
  6. data/lib/rbexy/component.rb +12 -18
  7. data/lib/rbexy/component/backtrace_cleaner.rb +59 -0
  8. data/lib/rbexy/component_context.rb +18 -0
  9. data/lib/rbexy/component_resolver.rb +60 -0
  10. data/lib/rbexy/configuration.rb +19 -1
  11. data/lib/rbexy/lexer.rb +20 -14
  12. data/lib/rbexy/nodes.rb +15 -141
  13. data/lib/rbexy/nodes/abstract_attr.rb +12 -0
  14. data/lib/rbexy/nodes/abstract_element.rb +13 -0
  15. data/lib/rbexy/nodes/abstract_node.rb +58 -0
  16. data/lib/rbexy/nodes/component_element.rb +69 -0
  17. data/lib/rbexy/nodes/component_prop.rb +29 -0
  18. data/lib/rbexy/nodes/declaration.rb +15 -0
  19. data/lib/rbexy/nodes/expression.rb +15 -0
  20. data/lib/rbexy/nodes/expression_group.rb +55 -0
  21. data/lib/rbexy/nodes/html_attr.rb +13 -0
  22. data/lib/rbexy/nodes/html_element.rb +48 -0
  23. data/lib/rbexy/nodes/newline.rb +9 -0
  24. data/lib/rbexy/nodes/raw.rb +23 -0
  25. data/lib/rbexy/nodes/root.rb +19 -0
  26. data/lib/rbexy/nodes/text.rb +15 -0
  27. data/lib/rbexy/nodes/util.rb +9 -0
  28. data/lib/rbexy/parser.rb +22 -16
  29. data/lib/rbexy/rails/component_template_resolver.rb +3 -3
  30. data/lib/rbexy/rails/controller_helper.rb +5 -4
  31. data/lib/rbexy/rails/engine.rb +1 -11
  32. data/lib/rbexy/refinements.rb +5 -0
  33. data/lib/rbexy/refinements/array.rb +9 -0
  34. data/lib/rbexy/refinements/array/find_map.rb +13 -0
  35. data/lib/rbexy/refinements/array/insert_between_types.rb +26 -0
  36. data/lib/rbexy/refinements/array/map_type_when_neighboring_type.rb +26 -0
  37. data/lib/rbexy/runtime.rb +17 -23
  38. data/lib/rbexy/template.rb +12 -0
  39. data/lib/rbexy/version.rb +1 -1
  40. data/rbexy.gemspec +4 -4
  41. metadata +49 -38
  42. data/example.rb +0 -113
  43. data/lib/rbexy/component_providers/namespaced_rbexy_provider.rb +0 -20
  44. data/lib/rbexy/component_providers/rbexy_provider.rb +0 -21
  45. data/lib/rbexy/component_providers/view_component_provider.rb +0 -21
  46. data/lib/rbexy/component_tag_builder.rb +0 -19
  47. data/lib/rbexy/hash_mash.rb +0 -15
  48. data/lib/rbexy/output_buffer.rb +0 -10
  49. data/lib/rbexy/view_context_helper.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e21e51ccf4a683d3f4c26ef53af86382de7fd9b26e3db2a0790fdf6d7e3df800
4
- data.tar.gz: f956dd52d3e20229df1290269f17fe28a7cd352cfa4b2c5649ede1f38f8bb9dd
3
+ metadata.gz: c25ae0414d2229676a67b1ec6ee63c6092d00bb699668cd1213c93d50dee2356
4
+ data.tar.gz: 00d80e6099de62ca467cf6c75b661cf43eeb8641fe15c510163ba6f02a1fcc99
5
5
  SHA512:
6
- metadata.gz: e792baa90cab354b588425c5a5851c5c23372c7cafcdb34e65d79bdd8a75b7271668dd02867a4987f6687ee704d4f8d63201e5192cb49a9fa27377610968fb07
7
- data.tar.gz: 3d022540841d61746e14b909c70e3e7e769c78e6d03bbc5ca87dd6f611043a197309e83ab068a903089a201f88f7941edd5c1effc68497bb156ec203da2814bc
6
+ metadata.gz: e29d842f6f9d90c2908be38c58e8d86a365496eb60b093dacc15364f19af00238457a8e7d4877f8db9305a88a29db5c5a6682b2684c41abd8c0ffa19b8279cb4
7
+ data.tar.gz: 8298b8fa13abef5f8a85f0df96d9abda969cc0b98417c5668f34b644ba79a9d0821c372989f0111149b4abc1b9851eb4d69e8aa79b5d69a91f425a931845e24d
@@ -1,10 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbexy (0.3.1)
5
- actionview (>= 5.0, < 7.0)
6
- activesupport (>= 5.0, < 7.0)
7
- railties (>= 5.0, < 7.0)
4
+ rbexy (2.0.0.beta1)
5
+ actionview (>= 6.0, < 7.0)
6
+ activesupport (>= 6.0, < 7.0)
8
7
 
9
8
  GEM
10
9
  remote: https://rubygems.org/
@@ -102,6 +101,7 @@ GEM
102
101
  mini_mime (>= 0.1.1)
103
102
  marcel (0.3.3)
104
103
  mimemagic (~> 0.3.2)
104
+ memory_profiler (0.9.14)
105
105
  method_source (1.0.0)
106
106
  mimemagic (0.3.5)
107
107
  mini_mime (1.0.2)
@@ -200,8 +200,9 @@ PLATFORMS
200
200
 
201
201
  DEPENDENCIES
202
202
  guard-rspec (~> 4.7, >= 4.7.3)
203
+ memory_profiler (~> 0.9.14)
203
204
  pry-byebug
204
- rails (>= 5.0, < 7.0)
205
+ rails (>= 6.0, < 7.0)
205
206
  rake
206
207
  rbexy!
207
208
  rspec (~> 3.9)
data/README.md CHANGED
@@ -48,6 +48,8 @@ Add it to your Gemfile and `bundle install`:
48
48
  gem "rbexy"
49
49
  ```
50
50
 
51
+ _From 1.0 onward, we only support Rails 6. If you're using Rails 5, use the 0.x releases._
52
+
51
53
  In `config/application.rb`:
52
54
 
53
55
  ```ruby
@@ -171,6 +173,14 @@ Loops:
171
173
  </ul>
172
174
  ```
173
175
 
176
+ Blocks:
177
+
178
+ ```jsx
179
+ {link_to "/" do
180
+ <span>Click me</span>
181
+ end}
182
+ ```
183
+
174
184
  As an attribute:
175
185
 
176
186
  ```jsx
@@ -187,6 +197,16 @@ Pass a lambda to a prop, that when called returns a tag:
187
197
  </Hero>
188
198
  ```
189
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
+
190
210
  ### Tags
191
211
 
192
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 :OutputBuffer, "rbexy/output_buffer"
10
- autoload :ComponentTagBuilder, "rbexy/component_tag_builder"
11
- autoload :ViewContextHelper, "rbexy/view_context_helper"
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,8 +1,9 @@
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
5
+ autoload :BacktraceCleaner, "rbexy/component/backtrace_cleaner"
6
+
6
7
  class TemplatePath < String
7
8
  def to_s
8
9
  self
@@ -43,21 +44,13 @@ module Rbexy
43
44
  path = TemplatePath.new(component_name)
44
45
  template = view_context.lookup_context.find(path)
45
46
  template.render(self, {})
47
+ rescue ActionView::Template::Error => error
48
+ error.set_backtrace clean_template_backtrace(error.backtrace)
49
+ raise error
46
50
  end
47
51
 
48
52
  def content
49
- content_block ? view_context.capture(self, &content_block) : ""
50
- end
51
-
52
- def create_context(name, value)
53
- rbexy_context.last[name] = value
54
- end
55
-
56
- def use_context(name)
57
- index = rbexy_context.rindex { |c| c.has_key?(name) }
58
- index ?
59
- rbexy_context[index][name] :
60
- raise(ContextNotFound, "no parent context `#{name}`")
53
+ content_block ? content_block.call : ""
61
54
  end
62
55
 
63
56
  def compiled_method_container
@@ -69,11 +62,12 @@ module Rbexy
69
62
  attr_reader :view_context, :content_block
70
63
 
71
64
  def method_missing(meth, *args, &block)
72
- if view_context.respond_to?(meth)
73
- view_context.send(meth, *args, &block)
74
- else
75
- super
76
- end
65
+ view_context.send(meth, *args, &block)
66
+ end
67
+
68
+ def clean_template_backtrace(backtrace)
69
+ return backtrace if Rbexy.configuration.debug
70
+ BacktraceCleaner.new(backtrace).call
77
71
  end
78
72
  end
79
73
  end
@@ -0,0 +1,59 @@
1
+ module Rbexy
2
+ class Component
3
+ class BacktraceCleaner
4
+ attr_reader :backtrace
5
+
6
+ def initialize(backtrace)
7
+ @backtrace = backtrace
8
+ @found_templates = {}
9
+ end
10
+
11
+ def call
12
+ backtrace
13
+ .reject(&method(:internal_implementation_detail?))
14
+ .map(&method(:strip_rbx_internals_block_mention))
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :found_templates
20
+
21
+ def internal_implementation_detail?(line)
22
+ if template = template_name_if_rbx_internals(line)
23
+ redundant_internal_block?(line, template)
24
+ else
25
+ internal_method_call?(line)
26
+ end
27
+ end
28
+
29
+ def internal_method_call?(line)
30
+ line =~ /lib\/rbexy\/.*\.rb/ ||
31
+ line =~ /lib\/action_view\/.*\.rb/ ||
32
+ line =~ /lib\/active_support\/notifications\.rb/
33
+ end
34
+
35
+ def redundant_internal_block?(line, template)
36
+ if found_templates[template]
37
+ true
38
+ else
39
+ found_templates[template] = true
40
+ false
41
+ end
42
+ end
43
+
44
+ def strip_rbx_internals_block_mention(line)
45
+ if template_name_if_rbx_internals(line)
46
+ line.gsub(/block (\(\d+ levels\))? ?in /, "")
47
+ else
48
+ line
49
+ end
50
+ end
51
+
52
+ def template_name_if_rbx_internals(line)
53
+ if /\/(?<template>[^\/]*)\.rbx:\d+:in `(block |_)/ =~ line
54
+ template
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,18 @@
1
+ module Rbexy
2
+ module ComponentContext
3
+ def rbexy_context
4
+ @rbexy_context ||= [{}]
5
+ end
6
+
7
+ def create_context(name, value)
8
+ rbexy_context.last[name] = value
9
+ end
10
+
11
+ def use_context(name)
12
+ index = rbexy_context.rindex { |c| c.has_key?(name) }
13
+ index ?
14
+ rbexy_context[index][name] :
15
+ raise(ContextNotFound, "no parent context `#{name}`")
16
+ end
17
+ end
18
+ end
@@ -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,11 +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
+ attr_accessor :debug
7
+ attr_accessor :component_rendering_templates
8
+ attr_accessor :transforms
6
9
 
7
10
  def template_paths
8
11
  @template_paths ||= []
9
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
10
28
  end
11
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
@@ -237,7 +237,6 @@ module Rbexy
237
237
  end
238
238
 
239
239
  def potential_expression_inner_tag
240
- # binding.pry
241
240
  if self.curr_expr =~ Patterns.expression_internal_tag_prefixes
242
241
  tokens << [:EXPRESSION_BODY, curr_expr]
243
242
  self.curr_expr = ""
@@ -287,5 +286,12 @@ module Rbexy
287
286
  # etc).
288
287
  scanner.scan(Patterns.expression_content) || scanner.scan(Patterns.open_tag_end)
289
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
290
296
  end
291
297
  end