rbexy 0.3.1 → 2.0.0.beta1

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.
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
@@ -0,0 +1,48 @@
1
+ module Rbexy
2
+ module Nodes
3
+ class HTMLElement < AbstractElement
4
+ KNOWN_VOID_ELEMENTS = ActionView::Helpers::TagHelper::TagBuilder::VOID_ELEMENTS.map(&:to_s).to_set
5
+
6
+ def precompile
7
+ nodes = []
8
+
9
+ if void? && children.length == 0
10
+ nodes.concat(precompile_open_tag)
11
+ else
12
+ nodes.concat(precompile_open_tag)
13
+ nodes.concat(children.map(&:precompile).flatten)
14
+ nodes << Raw.new("</#{name}>")
15
+ end
16
+
17
+ nodes
18
+ end
19
+
20
+ private
21
+
22
+ def void?
23
+ KNOWN_VOID_ELEMENTS.include?(name)
24
+ end
25
+
26
+ def precompile_open_tag
27
+ nodes = [Raw.new("<#{name}")]
28
+ nodes.concat(precompile_members)
29
+ nodes << Raw.new(">")
30
+ nodes
31
+ end
32
+
33
+ def precompile_members
34
+ members.map do |node|
35
+ if node.is_a? ExpressionGroup
36
+ ExpressionGroup.new(
37
+ node.members,
38
+ inner_template: "Rbexy::Runtime.splat_attrs(%s)",
39
+ outer_template: ExpressionGroup::OUTPUT_SAFE
40
+ )
41
+ else
42
+ node
43
+ end
44
+ end.map(&:precompile).flatten
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,9 @@
1
+ module Rbexy
2
+ module Nodes
3
+ class Newline < AbstractNode
4
+ def compile
5
+ "\n"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ module Rbexy
2
+ module Nodes
3
+ class Raw < AbstractNode
4
+ attr_reader :content, :template
5
+
6
+ OUTPUT = "@output_buffer.safe_concat('%s'.freeze);"
7
+ EXPR_STRING = "'%s'.html_safe.freeze"
8
+
9
+ def initialize(content, template: OUTPUT)
10
+ @content = content
11
+ @template = template
12
+ end
13
+
14
+ def compile
15
+ template % content
16
+ end
17
+
18
+ def merge(other_raw)
19
+ content << other_raw.content
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module Rbexy
2
+ module Nodes
3
+ class Root < AbstractNode
4
+ attr_accessor :children
5
+
6
+ def initialize(children)
7
+ @children = children
8
+ end
9
+
10
+ def precompile
11
+ Root.new(compact(children.map(&:precompile).flatten))
12
+ end
13
+
14
+ def compile
15
+ "#{children.map(&:compile).join}@output_buffer.to_s"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module Rbexy
2
+ module Nodes
3
+ class Text < AbstractNode
4
+ attr_accessor :content
5
+
6
+ def initialize(content)
7
+ @content = content
8
+ end
9
+
10
+ def precompile
11
+ [Raw.new(Util.escape_string(content))]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module Rbexy
2
+ module Nodes
3
+ module Util
4
+ def self.escape_string(str)
5
+ str.gsub('"', '\\"').gsub("'", "\\'")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -12,7 +12,7 @@ module Rbexy
12
12
 
13
13
  def parse
14
14
  validate_tokens!
15
- Nodes::Template.new(parse_tokens)
15
+ Nodes::Root.new(parse_tokens)
16
16
  end
17
17
 
18
18
  def parse_tokens
@@ -26,7 +26,7 @@ module Rbexy
26
26
  end
27
27
 
28
28
  def parse_token
29
- parse_text || parse_silent_newline || parse_expression || parse_tag || parse_declaration
29
+ parse_text || parse_newline || parse_expression || parse_tag || parse_declaration
30
30
  end
31
31
 
32
32
  def parse_text
@@ -37,14 +37,14 @@ module Rbexy
37
37
  def parse_expression
38
38
  return unless take(:OPEN_EXPRESSION)
39
39
 
40
- statements = []
40
+ members = []
41
41
 
42
42
  eventually!(:CLOSE_EXPRESSION)
43
43
  until take(:CLOSE_EXPRESSION)
44
- statements << (parse_expression_body || parse_tag)
44
+ members << (parse_expression_body || parse_tag)
45
45
  end
46
46
 
47
- Nodes::ExpressionGroup.new(statements)
47
+ Nodes::ExpressionGroup.new(members)
48
48
  end
49
49
 
50
50
  def parse_expression!
@@ -60,26 +60,32 @@ module Rbexy
60
60
  def parse_tag
61
61
  return unless take(:OPEN_TAG_DEF)
62
62
 
63
- name = take!(:TAG_NAME)
63
+ details = take!(:TAG_DETAILS)[1]
64
+ attr_class = details[:type] == :component ? Nodes::ComponentProp : Nodes::HTMLAttr
65
+
64
66
  members = []
65
- members.concat(take_all(:SILENT_NEWLINE).map { Nodes::SilentNewline.new })
66
- members.concat(parse_attrs)
67
+ members.concat(take_all(:NEWLINE).map { Nodes::Newline.new })
68
+ members.concat(parse_attrs(attr_class))
67
69
 
68
70
  take!(:CLOSE_TAG_DEF)
69
71
 
70
72
  children = parse_children
71
73
 
72
- Nodes::XmlNode.new(name[1], members, children)
74
+ if details[:type] == :component
75
+ Nodes::ComponentElement.new(details[:component_class], members, children)
76
+ else
77
+ Nodes::HTMLElement.new(details[:name], members, children)
78
+ end
73
79
  end
74
80
 
75
- def parse_attrs
81
+ def parse_attrs(attr_class)
76
82
  return [] unless take(:OPEN_ATTRS)
77
83
 
78
84
  attrs = []
79
85
 
80
86
  eventually!(:CLOSE_ATTRS)
81
87
  until take(:CLOSE_ATTRS)
82
- attrs << (parse_splat_attr || parse_silent_newline || parse_attr)
88
+ attrs << (parse_splat_attr || parse_newline || parse_attr(attr_class))
83
89
  end
84
90
 
85
91
  attrs
@@ -94,12 +100,12 @@ module Rbexy
94
100
  expression
95
101
  end
96
102
 
97
- def parse_silent_newline
98
- return unless take(:SILENT_NEWLINE)
99
- Nodes::SilentNewline.new
103
+ def parse_newline
104
+ return unless take(:NEWLINE)
105
+ Nodes::Newline.new
100
106
  end
101
107
 
102
- def parse_attr
108
+ def parse_attr(attr_class)
103
109
  name = take!(:ATTR_NAME)[1]
104
110
  value = nil
105
111
 
@@ -111,7 +117,7 @@ module Rbexy
111
117
  value = default_empty_attr_value
112
118
  end
113
119
 
114
- Nodes::XmlAttr.new(name, value)
120
+ attr_class.new(name, value)
115
121
  end
116
122
 
117
123
  def parse_children
@@ -1,8 +1,8 @@
1
- require "action_view"
2
-
3
1
  module Rbexy
4
2
  module Rails
5
3
  class ComponentTemplateResolver < ActionView::FileSystemResolver
4
+ VIRTUAL_ROOT = "rbexy_component".freeze
5
+
6
6
  # Rails 6 requires us to override `_find_all` in order to hook
7
7
  def _find_all(name, prefix, partial, details, key, locals)
8
8
  find_templates(name, prefix, partial, details, locals)
@@ -19,7 +19,7 @@ module Rbexy
19
19
  Dir["#{templates_path}.*{#{extensions}}"].map do |template_path|
20
20
  source = File.binread(template_path)
21
21
  handler = ActionView::Template.handler_for_extension(File.extname(template_path)[1..-1])
22
- virtual_path = ["rbexy_component", prefix, name].join("/")
22
+ virtual_path = File.join(VIRTUAL_ROOT, prefix, name)
23
23
 
24
24
  ActionView::Template.new(
25
25
  source,
@@ -1,17 +1,18 @@
1
- require "active_support/concern"
2
-
3
1
  module Rbexy
4
2
  module Rails
5
3
  module ControllerHelper
6
4
  extend ActiveSupport::Concern
5
+ include ComponentContext
7
6
 
8
- def rbexy_component_provider; end
7
+ included do
8
+ helper_method :rbexy_context, :create_context, :use_context
9
+ end
9
10
 
10
11
  class_methods do
11
12
  def inherited(klass)
12
13
  super
13
14
  Rbexy.configuration.template_paths.each do |path|
14
- prepend_view_path(Rbexy::Rails::ComponentTemplateResolver.new(path))
15
+ prepend_view_path(ComponentTemplateResolver.new(path))
15
16
  end
16
17
  end
17
18
  end
@@ -4,25 +4,15 @@ module Rbexy
4
4
  module Rails
5
5
  class Engine < ::Rails::Engine
6
6
  initializer "rbexy" do |app|
7
- template_handler = ::Rails.version.to_f >= 6.0 ?
8
- proc { |template, source| Rbexy.compile(source) } :
9
- proc { |template| Rbexy.compile(template.source) }
7
+ template_handler = proc { |template, source| Rbexy.compile(template) }
10
8
 
11
9
  ActionView::Template.register_template_handler(:rbx, template_handler)
12
10
 
13
11
  ActiveSupport.on_load :action_controller_base do
14
- helper Rbexy::ViewContextHelper
15
- helper_method :rbexy_component_provider
16
12
  include ControllerHelper
17
13
  end
18
14
 
19
- if defined?(ViewComponent)
20
- ViewComponent::Base.include Rbexy::ViewContextHelper
21
- end
22
-
23
15
  Rbexy.configure do |config|
24
- require "rbexy/component_providers/rbexy_provider"
25
- config.component_provider = Rbexy::ComponentProviders::RbexyProvider.new
26
16
  config.template_paths << ::Rails.root.join("app", "components")
27
17
  config.enable_context = true
28
18
  end
@@ -0,0 +1,5 @@
1
+ module Rbexy
2
+ module Refinements
3
+ autoload :Array, "rbexy/refinements/array"
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module Rbexy
2
+ module Refinements
3
+ module Array
4
+ autoload :FindMap, "rbexy/refinements/array/find_map"
5
+ autoload :InsertBetweenTypes, "rbexy/refinements/array/insert_between_types"
6
+ autoload :MapTypeWhenNeighboringType, "rbexy/refinements/array/map_type_when_neighboring_type"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Rbexy
2
+ module Refinements
3
+ module Array
4
+ module FindMap
5
+ refine ::Array do
6
+ def find_map
7
+ lazy.map { |i| yield(i) }.reject { |v| !v }.first
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ module Rbexy
2
+ module Refinements
3
+ module Array
4
+ module InsertBetweenTypes
5
+ refine ::Array do
6
+ def insert_between_types(type1, type2, &block)
7
+ map.with_index do |curr, i|
8
+ prev_i = i - 1
9
+
10
+ if prev_i >= 0 && InsertBetweenTypes.one_of_each_type?([self[prev_i], curr], [type1, type2])
11
+ [block.call, curr]
12
+ else
13
+ [curr]
14
+ end
15
+ end.flatten
16
+ end
17
+ end
18
+
19
+ def self.one_of_each_type?(items_pair, types_pair)
20
+ items_pair[0].is_a?(types_pair[0]) && items_pair[1].is_a?(types_pair[1]) ||
21
+ items_pair[0].is_a?(types_pair[1]) && items_pair[1].is_a?(types_pair[0])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module Rbexy
2
+ module Refinements
3
+ module Array
4
+ module MapTypeWhenNeighboringType
5
+ refine ::Array do
6
+ def map_type_when_neighboring_type(map_type, neighboring_type, &block)
7
+ map.with_index do |curr, i|
8
+ prev_i = i - 1
9
+ next_i = i + 1
10
+
11
+ if !curr.is_a?(map_type)
12
+ curr
13
+ elsif prev_i >= 0 && self[prev_i].is_a?(neighboring_type)
14
+ block.call(curr)
15
+ elsif next_i < length && self[next_i].is_a?(neighboring_type)
16
+ block.call(curr)
17
+ else
18
+ curr
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,39 +1,33 @@
1
- require "active_support/all"
2
- require "action_view/helpers"
3
- require "action_view/context"
4
- require "action_view/buffers"
5
-
6
1
  module Rbexy
7
2
  class Runtime
8
3
  include ActionView::Context
9
4
  include ActionView::Helpers::TagHelper
10
- include ViewContextHelper
11
-
12
- DefaultTagBuilder = ActionView::Helpers::TagHelper::TagBuilder
5
+ include ComponentContext
13
6
 
14
- def self.create_tag_builder(context, provider = nil)
15
- provider = provider ||
16
- provider_from_context(context) ||
17
- Rbexy.configuration.component_provider
7
+ def self.tag_builder
8
+ # TagBuilder requires a view_context arg, but it's only used in #tag_string.
9
+ # Since all we need is #tag_options, we pass in a nil view_context.
10
+ @tag_builder ||= TagBuilder.new(nil)
11
+ end
18
12
 
19
- if provider
20
- ComponentTagBuilder.new(context, provider)
21
- else
22
- ActionView::Helpers::TagHelper::TagBuilder.new(context)
23
- end
13
+ def self.splat_attrs(attrs_hash)
14
+ tag_builder.tag_options(attrs_hash).html_safe
24
15
  end
25
16
 
26
- def self.provider_from_context(context)
27
- if context.respond_to?(:rbexy_component_provider)
28
- context.rbexy_component_provider
29
- end
17
+ def self.expr_out(*value)
18
+ return if value.length == 0
19
+ value = value.first
20
+
21
+ value = html_safe_array?(value) ? value.join.html_safe : value
22
+ [nil, false].include?(value) ? "" : value.to_s
30
23
  end
31
24
 
32
- def initialize(component_provider = nil)
33
- @rbexy_tag = self.class.create_tag_builder(self, component_provider)
25
+ def self.html_safe_array?(value)
26
+ value.is_a?(Array) && value.all? { |v| v.respond_to?(:html_safe?) && v.html_safe? }
34
27
  end
35
28
 
36
29
  def evaluate(code)
30
+ @output_buffer = ActionView::OutputBuffer.new
37
31
  instance_eval(code)
38
32
  rescue => e
39
33
  e.set_backtrace(e.backtrace.map { |l| l.gsub("(eval)", "(rbx template string)") })