Skip to content

Commit

Permalink
Merge branch 'render_in'
Browse files Browse the repository at this point in the history
* render_in:
  Use `ruby2_keywords` for the moment (activeadmin#205)
  Deprecate Element#to_s and #to_str
  Add Document#render_in
  Use ActiveSupport::SafeBuffer instead of StringIO.
  require 'timeout'
  Use render_in(self) to build context cached_html.
  Add FieldsForProxy#render_in
  Deprecate using :to_s for rendering.
  Test using output_buffer from html tags matches #to_s.
  Add input and output variables to html rendering specs.
  Add Context#output_buffer, Element#render_in, ElementCollection#render_in, TextNode#render_in and Tag#render_in.
  • Loading branch information
varyonic committed Dec 5, 2022
2 parents 1e50aa6 + 4770068 commit 312ee73
Show file tree
Hide file tree
Showing 16 changed files with 225 additions and 47 deletions.
3 changes: 3 additions & 0 deletions arbre.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@ Gem::Specification.new do |s|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]

s.required_ruby_version = '>= 2.5'

s.add_dependency("activesupport", ">= 3.0.0")
s.add_dependency("ruby2_keywords", ">= 0.0.2")
end
1 change: 1 addition & 0 deletions lib/arbre.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'active_support/core_ext/string/output_safety'
require 'active_support/deprecation'
require 'active_support/hash_with_indifferent_access'
require 'active_support/inflector'

Expand Down
9 changes: 7 additions & 2 deletions lib/arbre/context.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'arbre/element'
require 'ruby2_keywords'

module Arbre

Expand Down Expand Up @@ -74,7 +75,7 @@ def respond_to_missing?(method, include_all)
# Webservers treat Arbre::Context as a string. We override
# method_missing to delegate to the string representation
# of the html.
def method_missing(method, *args, &block)
ruby2_keywords def method_missing(method, *args, &block)
if cached_html.respond_to? method
cached_html.send method, *args, &block
else
Expand All @@ -94,6 +95,10 @@ def with_current_arbre_element(tag)
end
alias_method :within, :with_current_arbre_element

def output_buffer
@output_buffer ||= ActiveSupport::SafeBuffer.new
end

private


Expand All @@ -103,7 +108,7 @@ def cached_html
if defined?(@cached_html)
@cached_html
else
html = to_s
html = render_in(self)
@cached_html = html if html.length > 0
html
end
Expand Down
36 changes: 32 additions & 4 deletions lib/arbre/element.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'arbre/element/builder_methods'
require 'arbre/element/proxy'
require 'arbre/element_collection'
require 'ruby2_keywords'

module Arbre

Expand Down Expand Up @@ -132,17 +133,36 @@ def each(&block)
end

def inspect
to_s
content
end

def to_str
to_s
ActiveSupport::Deprecation.warn("don't rely on implicit conversion of Element to String")
content
end

def to_s
ActiveSupport::Deprecation.warn("#render_in should be defined for rendering #{method_owner(:to_s)} instead of #to_s")
content
end

# Rendering strategy that visits all elements and appends output to a buffer.
def render_in(context = arbre_context)
children.collect do |element|
element.render_in_or_to_s(context)
end.join('')
end

# Use render_in to render element unless closer ancestor overrides :to_s only.
def render_in_or_to_s(context)
if method_distance(:render_in) <= method_distance(:to_s)
render_in(context)
else
ActiveSupport::Deprecation.warn("#render_in should be defined for rendering #{method_owner(:to_s)} instead of #to_s")
to_s.tap { |s| context.output_buffer << s }
end
end

def +(element)
case element
when Element, ElementCollection
Expand Down Expand Up @@ -172,7 +192,7 @@ def clear_children!
# 3. Call the method on the helper object
# 4. Call super
#
def method_missing(name, *args, &block)
ruby2_keywords def method_missing(name, *args, &block)
if current_arbre_element.respond_to?(name)
current_arbre_element.send name, *args, &block
elsif assigns && assigns.has_key?(name)
Expand All @@ -188,10 +208,18 @@ def method_missing(name, *args, &block)
# which will be rendered (#to_s) inside ActionView::Base#capture.
# We do not want such elements added to self, so we push a dummy
# current_arbre_element.
def helper_capture(name, *args, &block)
ruby2_keywords def helper_capture(name, *args, &block)
s = ""
within(Element.new) { s = helpers.send(name, *args, &block) }
s.is_a?(Element) ? s.to_s : s
end

def method_distance(name)
self.class.ancestors.index method_owner(name)
end

def method_owner(name)
self.class.instance_method(name).owner
end
end
end
6 changes: 6 additions & 0 deletions lib/arbre/element_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ def to_s
element.to_s
end.join('').html_safe
end

def render_in(context)
self.collect do |element|
element.render_in(context)
end.join('').html_safe
end
end

end
6 changes: 6 additions & 0 deletions lib/arbre/html/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ def to_s
doctype + super
end

def render_in(context = arbre_context)
context.output_buffer << doctype
super
context.output_buffer
end

protected

def build_head
Expand Down
30 changes: 30 additions & 0 deletions lib/arbre/html/tag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ def to_s
indent(opening_tag, content, closing_tag).html_safe
end

def render_in(context = arbre_context)
indent_in_context(context)
end

private

def opening_tag
Expand Down Expand Up @@ -136,6 +140,32 @@ def indent(open_tag, child_content, close_tag)
html
end

def indent_in_context(context)
spaces = ' ' * indent_level * INDENT_SIZE

pos = context.output_buffer.length

if no_child? || child_is_text?
if self_closing_tag?
context.output_buffer << spaces << opening_tag.sub( />$/, '/>' ).html_safe
else
# one line
context.output_buffer << spaces << opening_tag.html_safe
children.render_in(context)
context.output_buffer << closing_tag.html_safe
end
else
# multiple lines
context.output_buffer << spaces << opening_tag.html_safe << "\n"
children.render_in(context)
context.output_buffer << spaces << closing_tag.html_safe
end

context.output_buffer << "\n"

context.output_buffer[pos..].html_safe
end

def self_closing_tag?
SELF_CLOSING_ELEMENTS.include?(tag_name.to_sym)
end
Expand Down
4 changes: 4 additions & 0 deletions lib/arbre/html/text_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ def tag_name
def to_s
ERB::Util.html_escape(@content.to_s)
end

def render_in(context)
to_s.tap { |s| context.output_buffer << s }
end
end

end
Expand Down
6 changes: 6 additions & 0 deletions lib/arbre/rails/forms.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ def to_s
children.to_s
end

def render_in(context)
children.collect do |element|
element.render_in_or_to_s(context)
end.join('')
end

end

end
Expand Down
2 changes: 1 addition & 1 deletion lib/arbre/rails/template_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def call(template, source = nil)
<<-END
Arbre::Context.new(assigns, self) {
#{source}
}.to_s
}.render_in(self).html_safe
END
end
end
Expand Down
Loading

0 comments on commit 312ee73

Please sign in to comment.