Hrb: not as good as I thought
This commit is contained in:
18
lib/hrb.rb
Normal file
18
lib/hrb.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "hrb/version"
|
||||
require "hrb/template"
|
||||
require "hrb/tilt" if defined?(Tilt)
|
||||
require "hrb/action_view" if defined?(ActionView::Template)
|
||||
|
||||
module Hrb
|
||||
module_function
|
||||
|
||||
def render(locals: {}, scope: nil, &block)
|
||||
Hrb::Template.render(locals: locals, scope: scope, &block)
|
||||
end
|
||||
|
||||
def render_file(path, locals: {}, scope: nil)
|
||||
Hrb::Template.render_file(path, locals: locals, scope: scope)
|
||||
end
|
||||
end
|
17
lib/hrb/action_view.rb
Normal file
17
lib/hrb/action_view.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Hrb
|
||||
module Rails
|
||||
class TemplateHandler
|
||||
def call(template)
|
||||
<<-CODE
|
||||
Hrb::Template.build do
|
||||
#{template.source}
|
||||
end.to_s
|
||||
CODE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ActionView::Template.register_template_handler :hrb, Hrb::Rails::TemplateHandler
|
131
lib/hrb/template.rb
Normal file
131
lib/hrb/template.rb
Normal file
@@ -0,0 +1,131 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "erb"
|
||||
|
||||
module Hrb
|
||||
class Template
|
||||
TAGS = %i[a address article aside b bdi body button code details dialog div dd dl dt em fieldset figcaption figure
|
||||
footer form h1 h2 h3 h4 h5 h6 head header html i iframe label li main mark menuitem meter nav ol option
|
||||
option p pre progress rp rt ruby s script section select small span strong summary table tbody td textarea
|
||||
th thead time title tr u ul video wbr].freeze
|
||||
EMPTY_TAGS = %i[img br hr input link meta source].freeze
|
||||
|
||||
ENDINGS = Hash[TAGS.map do |tag|
|
||||
[tag, "</#{tag}>"]
|
||||
end].freeze
|
||||
|
||||
def self.render(locals: {}, scope: nil, &block)
|
||||
template = Template.new(locals: locals, scope: scope)
|
||||
template.instance_eval(&block)
|
||||
template.to_s
|
||||
end
|
||||
|
||||
def self.render_file(path, locals: {}, scope: nil)
|
||||
template = Template.new(locals: locals, scope: scope)
|
||||
template.instance_eval(File.read(path))
|
||||
template.to_s
|
||||
end
|
||||
|
||||
def initialize(locals: {}, scope: nil)
|
||||
@fragments = [[]]
|
||||
self.locals = locals
|
||||
@scope = scope
|
||||
@view = @fragments.last
|
||||
end
|
||||
|
||||
TAGS.each do |name|
|
||||
define_method(name) do |content = nil, options = {}, &block|
|
||||
tag(name, content, options, &block)
|
||||
end
|
||||
end
|
||||
|
||||
EMPTY_TAGS.each do |name|
|
||||
define_method(name) do |options = {}|
|
||||
tag(name, nil, options, end_tag: false)
|
||||
end
|
||||
end
|
||||
|
||||
def tag(name, content = nil, options = {}, end_tag: true, &block)
|
||||
output = "<#{name}#{tag_attributes(content&.is_a?(Hash) ? content : options)}>"
|
||||
output += content if content&.is_a?(String)
|
||||
output += capture(&block) if block
|
||||
output += ENDINGS[name] if end_tag
|
||||
|
||||
raw output
|
||||
end
|
||||
|
||||
def method_missing(name, *args, &block)
|
||||
if @scope.respond_to?(name)
|
||||
@scope.send(name, *args, &block)
|
||||
elsif @locals&.key?(name) || @locals&.key?(name.to_s)
|
||||
@locals.fetch(name) { @locals[name.to_s] }
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def respond_to_missing?(name, include_all = false)
|
||||
@scope.respond_to?(name) || @locals&.key?(name) || @locals&.key?(name.to_s) || super
|
||||
end
|
||||
|
||||
def view
|
||||
@fragments.last
|
||||
end
|
||||
|
||||
def doctype
|
||||
raw "<!DOCTYPE html>"
|
||||
end
|
||||
|
||||
def text(output)
|
||||
raw escape(output)
|
||||
end
|
||||
|
||||
def raw(output)
|
||||
view.push output.to_s
|
||||
end
|
||||
|
||||
def capture(&block)
|
||||
@fragments.push []
|
||||
instance_eval(&block)
|
||||
|
||||
view.join.tap do
|
||||
@fragments.pop
|
||||
end
|
||||
end
|
||||
|
||||
def locals=(locals)
|
||||
locals.each do |key, value|
|
||||
metaclass do
|
||||
define_method key do
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
@fragments.join
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def metaclass(&block)
|
||||
metaclass = class << self; self; end
|
||||
metaclass.class_eval(&block)
|
||||
end
|
||||
|
||||
def tag_attributes(options)
|
||||
attrs = ""
|
||||
|
||||
options.each do |key, value|
|
||||
attrs += %( #{key.to_s.tr("_", "-")}="#{escape(value)}")
|
||||
end
|
||||
|
||||
attrs
|
||||
end
|
||||
|
||||
def escape(value)
|
||||
ERB::Util.html_escape(value.respond_to?(:join) ? value.join(" ") : value.to_s)
|
||||
end
|
||||
end
|
||||
end
|
21
lib/hrb/tilt.rb
Normal file
21
lib/hrb/tilt.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Tilt
|
||||
class HrbTemplate < Tilt::Template
|
||||
def prepare; end
|
||||
|
||||
def evaluate(scope, locals, &block)
|
||||
builder = Hrb::Template.new(locals: locals, scope: scope)
|
||||
|
||||
if block
|
||||
builder.capture(&block)
|
||||
else
|
||||
builder.instance_eval(data)
|
||||
end
|
||||
|
||||
builder.to_s
|
||||
end
|
||||
end
|
||||
|
||||
register Tilt::HrbTemplate, "rb"
|
||||
end
|
5
lib/hrb/version.rb
Normal file
5
lib/hrb/version.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Hrb
|
||||
VERSION = "1.0.0-alpha.1"
|
||||
end
|
Reference in New Issue
Block a user