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/
VALID_FOR_EXPRESSION =

Valid :for expression: single identifier or tuple-destructured identifiers (e.g., “item in items”, “key, value in hash”, “(key, value) in hash”) followed by “ in ” and the collection expression.

/\A\s*(\(?[a-z_]\w*(?:\s*,\s*[a-z_]\w*)*\)?)\s+in\s+(.+)\z/m

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Filters

Returns a new instance of Filters.



24
25
26
27
# File 'lib/orb/temple/filters.rb', line 24

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



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
80
81
82
# File 'lib/orb/temple/filters.rb', line 45

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?(VALID_SLOT_NAME)
    raise ORB::SyntaxError.new("Invalid :with directive value: must be a valid Ruby identifier", 0)
  end

  # Compile attributes in a single pass: captures for variable assignment +
  # args string for the component constructor call.
  arg_captures, args = @attributes_compiler.compile_captures_and_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]`



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/orb/temple/filters.rb', line 161

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



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

def on_orb_for(expression, content)
  # Match single identifier or tuple-destructured identifiers (e.g., "key, value" or "(key, value)")
  match = expression.match(VALID_FOR_EXPRESSION)
  unless match
    raise ORB::SyntaxError.new("Invalid :for expression: enumerator must be a valid Ruby identifier",
      0)
  end

  enumerator = match[1]
  collection = 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



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

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



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
116
# File 'lib/orb/temple/filters.rb', line 90

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

  # Compile attributes in a single pass
  arg_captures, args = @attributes_compiler.compile_captures_and_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?(VALID_SLOT_NAME)
    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



35
36
37
# File 'lib/orb/temple/filters.rb', line 35

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