Class: ORB::Temple::Filters

Inherits:
Temple::Filter
  • Object
show all
Defined in:
lib/orb/temple/filters.rb

Constant Summary collapse

VALID_HTML_TAG_NAME =

Valid HTML tag names: starts with a letter, followed by letters, digits, or hyphens. Used to validate dynamic tag names before interpolation into generated Ruby code.

/\A[a-zA-Z][a-zA-Z0-9-]*\z/
VALID_COMPONENT_NAME =

Valid Ruby constant path: one or more segments like Foo, Foo::Bar, Foo::Bar::Baz. Each segment starts with an uppercase letter followed by word characters. Used to validate component names before interpolation into generated Ruby code.

/\A[A-Z]\w*(::[A-Z]\w*)*\z/
VALID_SLOT_NAME =

Valid Ruby identifier for use as a method name suffix in with_ slot calls. Used to validate slot names before interpolation into generated Ruby code.

/\A[a-z_]\w*\z/

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Filters

Returns a new instance of Filters.



19
20
21
22
# File 'lib/orb/temple/filters.rb', line 19

def initialize(options = {})
  @options = options
  @attributes_compiler = AttributesCompiler.new
end

Instance Method Details

#on_orb_component(node, content = []) ⇒ Array

Handle a component tag expression ‘[:orb, :component, name, attributes, content]`

Parameters:

  • name (String)

    The name of the component

  • attributes (Array<ORB::AST::Attribute>)

    The attributes to be passed in to the component

  • content (Array) (defaults to: [])

    (optional) Temple expression

Returns:

  • (Array)

    compiled Temple core expression



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/orb/temple/filters.rb', line 40

def on_orb_component(node, content = [])
  tmp = unique_name

  # Lookup the component class name using the ORB lookup mechanism
  # that traverses the configured namespaces
  name = node.tag.gsub('.', '::')
  komponent = ORB.lookup_component(name)
  komponent_name = komponent || name
  unless komponent_name.match?(VALID_COMPONENT_NAME)
    raise ORB::SyntaxError.new("Invalid component name: #{komponent_name.inspect}", 0)
  end

  block_name = "__orb__#{komponent_name.rpartition('::').last.underscore}"
  block_name = node.directives.fetch(:with, block_name)
  unless block_name.match?(/\A[a-z_]\w*\z/)
    raise ORB::SyntaxError.new("Invalid :with directive value: must be a valid Ruby identifier", 0)
  end

  # We need to compile the attributes into a set of captures and a set of arguments
  # since arguments passed to the view component constructor may be defined as
  # dynamic expressions in our template, and we need to first capture their results.
  arg_captures = @attributes_compiler.compile_captures(node.attributes, tmp)
  args = @attributes_compiler.compile_komponent_args(node.attributes, tmp)

  # Construct the render call for the view component
  code = "render #{komponent_name}.new(#{args}) do |#{block_name}|"

  # Return a compiled Temple expression that captures the component render call
  # and then evaluates the result into the OutputBuffer.
  [:multi,
    *arg_captures,
    # Capture the result of the component render call into a variable
    # we can't do :dynamic here because it's probably not a complete expression
    [:block, "#{tmp} = #{code}",
      # Capture the content of the block into a separate buffer
      # [:capture, unique_name, compile(content)]
      compile(content)],
    # Output the content
    [:escape, true, [:dynamic, tmp.to_s]]]
end

#on_orb_dynamic(node, content) ⇒ Object

Handle a dynamic node expression ‘[:orb, :dynamic, node, content]`



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/orb/temple/filters.rb', line 155

def on_orb_dynamic(node, content)
  # Determine whether the node is an html_tag, component, or slot node
  if node.component_tag?
    on_orb_component(node, content)
  elsif node.component_slot_tag?
    on_orb_slot(node, content)
  else
    # It's a dynamic HTML tag
    unless node.tag.match?(VALID_HTML_TAG_NAME)
      raise ORB::SyntaxError.new("Invalid tag name: #{node.tag.inspect}", 0)
    end
    tmp = unique_name
    splats = @attributes_compiler.compile_splat_attributes(node.splat_attributes)
    code = "content_tag('#{node.tag}', #{splats}) do"

    [:multi,
      [:block, "#{tmp} = #{code}", compile(content)],
      [:escape, true, [:dynamic, tmp]]]
  end
end

#on_orb_for(expression, content) ⇒ Array

Handle a for block expression ‘[:orb, :for, expression, content]`

Parameters:

  • expression (String)

    The iterator expression to be evaluated

  • content (Array)

    The content to be rendered for each iteration

Returns:

  • (Array)

    compiled Temple expression

Raises:



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/orb/temple/filters.rb', line 134

def on_orb_for(expression, content)
  match = expression.match(/\A\s*([a-z_]\w*)\s+in\s+(.+)\z/m)
  raise ORB::SyntaxError.new("Invalid :for expression: enumerator must be a valid Ruby identifier", 0) unless match

  enumerator, collection = match[1], match[2]

  # Reject semicolons in the collection expression to prevent statement injection.
  # Legitimate complex expressions should be assigned in a {%...%} block first.
  if collection.include?(';')
    raise ORB::SyntaxError.new("Invalid :for collection expression: semicolons are not allowed", 0)
  end

  code = "#{collection}.each do |#{enumerator}|"

  [:multi,
    [:code, code], compile(content),
    [:code, "end"]]
end

#on_orb_if(condition, yes, no = nil) ⇒ Array

Handle an if block expression ‘[:orb, :if, condition, yes, no]`

Parameters:

  • condition (String)

    The condition to be evaluated

  • yes (Array)

    The content to be rendered if the condition is true

  • no (Array) (defaults to: nil)

    (optional) The content to be rendered if the condition is false

Returns:

  • (Array)

    compiled Temple expression



123
124
125
126
127
# File 'lib/orb/temple/filters.rb', line 123

def on_orb_if(condition, yes, no = nil)
  result = [:if, condition, compile(yes)]
  result << compile(no) if no
  result
end

#on_orb_slot(node, content = []) ⇒ Array

Handle a component slot tag expression ‘[:orb, :slot, name, attributes, content]`

Parameters:

  • name (String)

    The name of the slot

  • attributes (Array<ORB::AST::Attribute>)

    the attributes to be passed in to the slot

  • content (Array) (defaults to: [])

    (optional) Temple expression for the slot content

Returns:

  • (Array)

    compiled Temple expression



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/orb/temple/filters.rb', line 87

def on_orb_slot(node, content = [])
  tmp = unique_name

  # We need to compile the attributes into a set of captures and a set of arguments
  # since arguments passed to the view component constructor may be defined as
  # dynamic expressions in our template, and we need to first capture their results.
  arg_captures = @attributes_compiler.compile_captures(node.attributes, tmp)
  args = @attributes_compiler.compile_komponent_args(node.attributes, tmp)

  # Prepare the slot name, parent name, and block name
  slot_name = node.slot
  unless slot_name.match?(VALID_SLOT_NAME)
    raise ORB::SyntaxError.new("Invalid slot name: #{slot_name.inspect}", 0)
  end
  parent_name = "__orb__#{node.component.underscore}"
  block_name = node.directives.fetch(:with, "__orb__#{slot_name}")
  unless block_name.match?(/\A[a-z_]\w*\z/)
    raise ORB::SyntaxError.new("Invalid :with directive value: must be a valid Ruby identifier", 0)
  end

  # Construct the code to call the slot on the parent component
  code = "#{parent_name}.with_#{slot_name}(#{args}) do |#{block_name}|"

  # Return a compiled Temple expression that captures the slot call
  [:multi,
    *arg_captures,
    [:code, code], compile(content),
    [:code, "end"]]
end

#on_orb_tag(name, attributes, content = nil) ⇒ Array

Handle an HTML tag expression ‘[:orb, :tag, name, attributes, content]`

Parameters:

  • name (String)

    The name of the tag

  • attributes (Array)

    The attributes to be passed in to the tag

  • content (Array) (defaults to: nil)

    (optional) child nodes of the tag

Returns:

  • (Array)

    compiled Temple core expression



30
31
32
# File 'lib/orb/temple/filters.rb', line 30

def on_orb_tag(name, attributes, content = nil)
  [:html, :tag, name, @attributes_compiler.compile_attributes(attributes), compile(content)]
end