hamlit-2.11.0/ 0000755 0001750 0001750 00000000000 13574632775 012436 5 ustar joseph joseph hamlit-2.11.0/.gitignore 0000644 0001750 0001750 00000000222 13574632775 014422 0 ustar joseph joseph /.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
.sass-cache
.ruby-version
*.bundle
*.so
*.su
*.o
*.a
*.swp
hamlit-2.11.0/lib/ 0000755 0001750 0001750 00000000000 13574632775 013204 5 ustar joseph joseph hamlit-2.11.0/lib/hamlit/ 0000755 0001750 0001750 00000000000 13574632775 014462 5 ustar joseph joseph hamlit-2.11.0/lib/hamlit/ruby_expression.rb 0000644 0001750 0001750 00000001144 13574632775 020247 0 ustar joseph joseph # frozen_string_literal: true
require 'ripper'
module Hamlit
class RubyExpression < Ripper
class ParseError < StandardError; end
def self.syntax_error?(code)
self.new(code).parse
false
rescue ParseError
true
end
def self.string_literal?(code)
return false if syntax_error?(code)
type, instructions = Ripper.sexp(code)
return false if type != :program
return false if instructions.size > 1
type, _ = instructions.first
type == :string_literal
end
private
def on_parse_error(*)
raise ParseError
end
end
end
hamlit-2.11.0/lib/hamlit/utils.rb 0000644 0001750 0001750 00000001014 13574632775 016143 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
module Utils
# Java extension is not implemented for JRuby yet.
# TruffleRuby does not implement `rb_ary_sort_bang`, etc.
if /java/ === RUBY_PLATFORM || RUBY_ENGINE == 'truffleruby'
require 'cgi/escape'
def self.escape_html(html)
CGI.escapeHTML(html.to_s)
end
else
require 'hamlit/hamlit' # Hamlit::Utils.escape_html
end
def self.escape_html_safe(html)
html.html_safe? ? html : escape_html(html)
end
end
end
hamlit-2.11.0/lib/hamlit/string_splitter.rb 0000644 0001750 0001750 00000000724 13574632775 020246 0 ustar joseph joseph require 'ripper'
require 'hamlit/ruby_expression'
module Hamlit
module StringSplitter
# `code` param must be valid string literal
def self.compile(code)
unless Ripper.respond_to?(:lex) # truffleruby doesn't have Ripper.lex
return [[:dynamic, code]]
end
begin
Temple::Filters::StringSplitter.compile(code)
rescue Temple::FilterError => e
raise Hamlit::InternalError.new(e.message)
end
end
end
end
hamlit-2.11.0/lib/hamlit/compiler/ 0000755 0001750 0001750 00000000000 13574632775 016274 5 ustar joseph joseph hamlit-2.11.0/lib/hamlit/compiler/silent_script_compiler.rb 0000644 0001750 0001750 00000000773 13574632775 023404 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Compiler
class SilentScriptCompiler
def compile(node, &block)
if node.children.empty?
[:multi, [:code, node.value[:text]], [:newline]]
else
compile_with_children(node, &block)
end
end
private
def compile_with_children(node, &block)
[:multi,
[:block, node.value[:text],
[:multi, [:newline], yield(node)],
],
]
end
end
end
end
hamlit-2.11.0/lib/hamlit/compiler/children_compiler.rb 0000644 0001750 0001750 00000005415 13574632775 022310 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Compiler
class ChildrenCompiler
def initialize
@lineno = 1
end
def compile(node, &block)
temple = [:multi]
return temple if node.children.empty?
temple << :whitespace if prepend_whitespace?(node)
node.children.each do |n|
rstrip_whitespace!(temple) if nuke_prev_whitespace?(n)
insert_newlines!(temple, n)
temple << yield(n)
temple << :whitespace if insert_whitespace?(n)
end
rstrip_whitespace!(temple) if nuke_inner_whitespace?(node)
confirm_whitespace(temple)
end
private
def insert_newlines!(temple, node)
(node.line - @lineno).times do
temple << [:newline]
end
@lineno = node.line
case node.type
when :script, :silent_script
@lineno += 1
when :filter
@lineno += (node.value[:text] || '').split("\n").size
when :tag
node.value[:attributes_hashes].each do |attribute_hash|
@lineno += attribute_hash.count("\n")
end
@lineno += 1 if node.children.empty? && node.value[:parse]
end
end
def confirm_whitespace(temple)
temple.map do |exp|
case exp
when :whitespace
[:static, "\n"]
else
exp
end
end
end
def prepend_whitespace?(node)
return false unless %i[comment tag].include?(node.type)
!nuke_inner_whitespace?(node)
end
def nuke_inner_whitespace?(node)
case
when node.type == :tag
node.value[:nuke_inner_whitespace]
when node.parent.nil?
false
else
nuke_inner_whitespace?(node.parent)
end
end
def nuke_prev_whitespace?(node)
case node.type
when :tag
node.value[:nuke_outer_whitespace]
when :silent_script
!node.children.empty? && nuke_prev_whitespace?(node.children.first)
else
false
end
end
def nuke_outer_whitespace?(node)
return false if node.type != :tag
node.value[:nuke_outer_whitespace]
end
def rstrip_whitespace!(temple)
if temple[-1] == :whitespace
temple.delete_at(-1)
end
end
def insert_whitespace?(node)
return false if nuke_outer_whitespace?(node)
case node.type
when :doctype
node.value[:type] != 'xml'
when :comment, :plain, :tag
true
when :script
node.children.empty? && !nuke_inner_whitespace?(node)
when :filter
!%w[ruby].include?(node.value[:name])
else
false
end
end
end
end
end
hamlit-2.11.0/lib/hamlit/compiler/doctype_compiler.rb 0000644 0001750 0001750 00000001701 13574632775 022161 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Compiler
class DoctypeCompiler
def initialize(options = {})
@format = options[:format]
end
def compile(node)
case node.value[:type]
when 'xml'
xml_doctype
when ''
html_doctype(node)
else
[:html, :doctype, node.value[:type]]
end
end
private
def html_doctype(node)
version = node.value[:version] || :transitional
case @format
when :xhtml
[:html, :doctype, version]
when :html4
[:html, :doctype, :transitional]
when :html5
[:html, :doctype, :html]
else
[:html, :doctype, @format]
end
end
def xml_doctype
case @format
when :xhtml
[:static, "\n"]
else
[:multi]
end
end
end
end
end
hamlit-2.11.0/lib/hamlit/compiler/script_compiler.rb 0000644 0001750 0001750 00000006046 13574632775 022025 0 ustar joseph joseph # frozen_string_literal: true
require 'temple/static_analyzer'
require 'hamlit/ruby_expression'
require 'hamlit/string_splitter'
module Hamlit
class Compiler
class ScriptCompiler
def initialize(identity)
@identity = identity
end
def compile(node, &block)
unless Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
return dynamic_compile(node, &block)
end
no_children = node.children.empty?
case
when no_children && node.value[:escape_interpolation]
compile_interpolated_plain(node)
when no_children && RubyExpression.string_literal?(node.value[:text])
delegate_optimization(node)
when no_children && Temple::StaticAnalyzer.static?(node.value[:text])
static_compile(node)
else
dynamic_compile(node, &block)
end
end
private
# String-interpolated plain text must be compiled with this method
# because we have to escape only interpolated values.
def compile_interpolated_plain(node)
temple = [:multi]
StringSplitter.compile(node.value[:text]).each do |type, value|
case type
when :static
temple << [:static, value]
when :dynamic
temple << [:escape, node.value[:escape_interpolation], [:dynamic, value]]
end
end
temple << [:newline]
end
# :dynamic is optimized in other filter: StringSplitter
def delegate_optimization(node)
[:multi,
[:escape, node.value[:escape_html], [:dynamic, node.value[:text]]],
[:newline],
]
end
def static_compile(node)
str = eval(node.value[:text]).to_s
if node.value[:escape_html]
str = Hamlit::Utils.escape_html(str)
elsif node.value[:preserve]
str = ::Hamlit::HamlHelpers.find_and_preserve(str, %w(textarea pre code))
end
[:multi, [:static, str], [:newline]]
end
def dynamic_compile(node, &block)
var = @identity.generate
temple = compile_script_assign(var, node, &block)
temple << compile_script_result(var, node)
end
def compile_script_assign(var, node, &block)
if node.children.empty?
[:multi,
[:code, "#{var} = (#{node.value[:text]}"],
[:newline],
[:code, ')'],
]
else
[:multi,
[:block, "#{var} = #{node.value[:text]}",
[:multi, [:newline], yield(node)],
],
]
end
end
def compile_script_result(result, node)
if !node.value[:escape_html] && node.value[:preserve]
result = find_and_preserve(result)
else
result = "(#{result}).to_s"
end
[:escape, node.value[:escape_html], [:dynamic, result]]
end
def find_and_preserve(code)
%Q[::Hamlit::HamlHelpers.find_and_preserve(#{code}, %w(textarea pre code))]
end
def escape_html(temple)
[:escape, true, temple]
end
end
end
end
hamlit-2.11.0/lib/hamlit/compiler/comment_compiler.rb 0000644 0001750 0001750 00000001661 13574632775 022161 0 ustar joseph joseph module Hamlit
class Compiler
class CommentCompiler
def compile(node, &block)
if node.value[:conditional]
compile_conditional_comment(node, &block)
else
compile_html_comment(node, &block)
end
end
private
def compile_html_comment(node, &block)
if node.children.empty?
[:html, :comment, [:static, " #{node.value[:text]} "]]
else
[:html, :comment, yield(node)]
end
end
def compile_conditional_comment(node, &block)
condition = node.value[:conditional]
if node.value[:conditional] =~ /\A\[(\[*[^\[\]]+\]*)\]/
condition = $1
end
content =
if node.children.empty?
[:static, " #{node.value[:text]} "]
else
yield(node)
end
[:html, :condcomment, condition, content, node.value[:revealed]]
end
end
end
end
hamlit-2.11.0/lib/hamlit/compiler/tag_compiler.rb 0000644 0001750 0001750 00000004542 13574632775 021273 0 ustar joseph joseph # frozen_string_literal: true
require 'hamlit/parser/haml_util'
require 'hamlit/attribute_compiler'
require 'hamlit/string_splitter'
module Hamlit
class Compiler
class TagCompiler
def initialize(identity, options)
@autoclose = options[:autoclose]
@identity = identity
@attribute_compiler = AttributeCompiler.new(identity, options)
end
def compile(node, &block)
attrs = @attribute_compiler.compile(node)
contents = compile_contents(node, &block)
[:html, :tag, node.value[:name], attrs, contents]
end
private
def compile_contents(node, &block)
case
when !node.children.empty?
yield(node)
when node.value[:value].nil? && self_closing?(node)
nil
when node.value[:parse]
return compile_interpolated_plain(node) if node.value[:escape_interpolation]
if Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
return delegate_optimization(node) if RubyExpression.string_literal?(node.value[:value])
return delegate_optimization(node) if Temple::StaticAnalyzer.static?(node.value[:value])
end
var = @identity.generate
[:multi,
[:code, "#{var} = (#{node.value[:value]}"],
[:newline],
[:code, ')'],
[:escape, node.value[:escape_html], [:dynamic, var]]
]
else
[:static, node.value[:value]]
end
end
# :dynamic is optimized in other filters: StringSplitter or StaticAnalyzer
def delegate_optimization(node)
[:multi,
[:escape, node.value[:escape_html], [:dynamic, node.value[:value]]],
[:newline],
]
end
# We should handle interpolation here to escape only interpolated values.
def compile_interpolated_plain(node)
temple = [:multi]
StringSplitter.compile(node.value[:value]).each do |type, value|
case type
when :static
temple << [:static, value]
when :dynamic
temple << [:escape, node.value[:escape_interpolation], [:dynamic, value]]
end
end
temple << [:newline]
end
def self_closing?(node)
return true if @autoclose && @autoclose.include?(node.value[:name])
node.value[:self_closing]
end
end
end
end
hamlit-2.11.0/lib/hamlit/attribute_builder.rb 0000644 0001750 0001750 00000012143 13574632775 020521 0 ustar joseph joseph # frozen_string_literal: true
require 'hamlit/object_ref'
module Hamlit::AttributeBuilder
BOOLEAN_ATTRIBUTES = %w[disabled readonly multiple checked autobuffer
autoplay controls loop selected hidden scoped async
defer reversed ismap seamless muted required
autofocus novalidate formnovalidate open pubdate
itemscope allowfullscreen default inert sortable
truespeed typemustmatch download].freeze
# Java extension is not implemented for JRuby yet.
# TruffleRuby does not implement `rb_ary_sort_bang`, etc.
if /java/ === RUBY_PLATFORM || RUBY_ENGINE == 'truffleruby'
class << self
def build(escape_attrs, quote, format, object_ref, *hashes)
hashes << Hamlit::ObjectRef.parse(object_ref) if object_ref
buf = []
hash = merge_all_attrs(hashes)
keys = hash.keys.sort!
keys.each do |key|
case key
when 'id'.freeze
buf << " id=#{quote}#{build_id(escape_attrs, *hash[key])}#{quote}"
when 'class'.freeze
buf << " class=#{quote}#{build_class(escape_attrs, *hash[key])}#{quote}"
when 'data'.freeze
buf << build_data(escape_attrs, quote, *hash[key])
when *BOOLEAN_ATTRIBUTES, /\Adata-/
build_boolean!(escape_attrs, quote, format, buf, key, hash[key])
else
buf << " #{key}=#{quote}#{escape_html(escape_attrs, hash[key].to_s)}#{quote}"
end
end
buf.join
end
def build_id(escape_attrs, *values)
escape_html(escape_attrs, values.flatten.select { |v| v }.join('_'))
end
def build_class(escape_attrs, *values)
if values.size == 1
value = values.first
case
when value.is_a?(String)
# noop
when value.is_a?(Array)
value = value.flatten.select { |v| v }.map(&:to_s).sort.uniq.join(' ')
when value
value = value.to_s
else
return ''
end
return escape_html(escape_attrs, value)
end
classes = []
values.each do |value|
case
when value.is_a?(String)
classes += value.split(' ')
when value.is_a?(Array)
classes += value.select { |v| v }
when value
classes << value.to_s
end
end
escape_html(escape_attrs, classes.map(&:to_s).sort.uniq.join(' '))
end
def build_data(escape_attrs, quote, *hashes)
build_data_attribute(:data, escape_attrs, quote, *hashes)
end
def build_aria(escape_attrs, quote, *hashes)
build_data_attribute(:aria, escape_attrs, quote, *hashes)
end
private
def build_data_attribute(key, escape_attrs, quote, *hashes)
attrs = []
if hashes.size > 1 && hashes.all? { |h| h.is_a?(Hash) }
data_value = merge_all_attrs(hashes)
else
data_value = hashes.last
end
hash = flatten_attributes(key => data_value)
hash.sort_by(&:first).each do |key, value|
case value
when true
attrs << " #{key}"
when nil, false
# noop
else
attrs << " #{key}=#{quote}#{escape_html(escape_attrs, value.to_s)}#{quote}"
end
end
attrs.join
end
def flatten_attributes(attributes)
flattened = {}
attributes.each do |key, value|
case value
when attributes
when Hash
flatten_attributes(value).each do |k, v|
if k.nil?
flattened[key] = v
else
flattened["#{key}-#{k.to_s.gsub(/_/, '-')}"] = v
end
end
else
flattened[key] = value if value
end
end
flattened
end
def merge_all_attrs(hashes)
merged = {}
hashes.each do |hash|
hash.each do |key, value|
key = key.to_s
case key
when 'id'.freeze, 'class'.freeze, 'data'.freeze
merged[key] ||= []
merged[key] << value
else
merged[key] = value
end
end
end
merged
end
def build_boolean!(escape_attrs, quote, format, buf, key, value)
case value
when true
case format
when :xhtml
buf << " #{key}=#{quote}#{key}#{quote}"
else
buf << " #{key}"
end
when false, nil
# omitted
else
buf << " #{key}=#{quote}#{escape_html(escape_attrs, value)}#{quote}"
end
end
def escape_html(escape_attrs, str)
if escape_attrs
Hamlit::Utils.escape_html(str)
else
str
end
end
end
else
# Hamlit::AttributeBuilder.build
# Hamlit::AttributeBuilder.build_id
# Hamlit::AttributeBuilder.build_class
# Hamlit::AttributeBuilder.build_data
# Hamlit::AttributeBuilder.build_aria
require 'hamlit/hamlit'
end
end
hamlit-2.11.0/lib/hamlit/attribute_compiler.rb 0000644 0001750 0001750 00000010367 13574632775 020713 0 ustar joseph joseph # frozen_string_literal: true
require 'hamlit/attribute_builder'
require 'hamlit/attribute_parser'
require 'hamlit/ruby_expression'
module Hamlit
class AttributeCompiler
def initialize(identity, options)
@identity = identity
@quote = options[:attr_quote]
@format = options[:format]
@escape_attrs = options[:escape_attrs]
end
def compile(node)
hashes = []
if node.value[:object_ref] != :nil || !Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
return runtime_compile(node)
end
node.value[:attributes_hashes].each do |attribute_str|
hash = AttributeParser.parse(attribute_str)
return runtime_compile(node) unless hash
hashes << hash
end
static_compile(node.value[:attributes], hashes)
end
private
def runtime_compile(node)
attrs = node.value[:attributes_hashes]
attrs.unshift(node.value[:attributes].inspect) if node.value[:attributes] != {}
args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect].push(node.value[:object_ref]) + attrs
[:html, :attrs, [:dynamic, "::Hamlit::AttributeBuilder.build(#{args.join(', ')})"]]
end
def static_compile(static_hash, dynamic_hashes)
temple = [:html, :attrs]
keys = [*static_hash.keys, *dynamic_hashes.map(&:keys).flatten].uniq.sort
keys.each do |key|
values = [[:static, static_hash[key]], *dynamic_hashes.map { |h| [:dynamic, h[key]] }]
values.select! { |_, exp| exp != nil }
case key
when 'id'
compile_id!(temple, key, values)
when 'class'
compile_class!(temple, key, values)
when 'data', 'aria'
compile_data!(temple, key, values)
when *AttributeBuilder::BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
compile_boolean!(temple, key, values)
else
compile_common!(temple, key, values)
end
end
temple
end
def compile_id!(temple, key, values)
build_code = attribute_builder(:id, values)
if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
temple << [:html, :attr, key, [:static, eval(build_code).to_s]]
else
temple << [:html, :attr, key, [:dynamic, build_code]]
end
end
def compile_class!(temple, key, values)
build_code = attribute_builder(:class, values)
if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
temple << [:html, :attr, key, [:static, eval(build_code).to_s]]
else
temple << [:html, :attr, key, [:dynamic, build_code]]
end
end
def compile_data!(temple, key, values)
args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", values.map { |v| literal_for(v) }]
build_code = "::Hamlit::AttributeBuilder.build_#{key}(#{args.join(', ')})"
if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
temple << [:static, eval(build_code).to_s]
else
temple << [:dynamic, build_code]
end
end
def compile_boolean!(temple, key, values)
exp = literal_for(values.last)
if Temple::StaticAnalyzer.static?(exp)
value = eval(exp)
case value
when true then temple << [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]
when false, nil
else temple << [:html, :attr, key, [:fescape, @escape_attrs, [:static, value.to_s]]]
end
else
var = @identity.generate
temple << [
:case, "(#{var} = (#{exp}))",
['true', [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]],
['false, nil', [:multi]],
[:else, [:multi, [:static, " #{key}=#{@quote}"], [:fescape, @escape_attrs, [:dynamic, var]], [:static, @quote]]],
]
end
end
def compile_common!(temple, key, values)
temple << [:html, :attr, key, [:fescape, @escape_attrs, values.last]]
end
def attribute_builder(type, values)
args = [@escape_attrs.inspect, *values.map { |v| literal_for(v) }]
"::Hamlit::AttributeBuilder.build_#{type}(#{args.join(', ')})"
end
def literal_for(value)
type, exp = value
type == :static ? "#{exp.inspect}.freeze" : exp
end
end
end
hamlit-2.11.0/lib/hamlit/version.rb 0000644 0001750 0001750 00000000105 13574632775 016470 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
VERSION = '2.11.0'
end
hamlit-2.11.0/lib/hamlit/rails_helpers.rb 0000644 0001750 0001750 00000002556 13574632775 017653 0 ustar joseph joseph # frozen_string_literal: false
require 'hamlit/helpers'
# Currently this Hamlit::Helpers depends on
# ActionView internal implementation. (not desired)
module Hamlit
module RailsHelpers
include Helpers
extend self
DEFAULT_PRESERVE_TAGS = %w[textarea pre code].freeze
def find_and_preserve(input = nil, tags = DEFAULT_PRESERVE_TAGS, &block)
return find_and_preserve(capture_haml(&block), input || tags) if block
tags = tags.each_with_object('') do |t, s|
s << '|' unless s.empty?
s << Regexp.escape(t)
end
re = /<(#{tags})([^>]*)>(.*?)(<\/\1>)/im
input.to_s.gsub(re) do |s|
s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
"<#{$1}#{$2}>#{preserve($3)}#{$1}>"
end
end
def preserve(input = nil, &block)
return preserve(capture_haml(&block)) if block
super.html_safe
end
def surround(front, back = front, &block)
output = capture_haml(&block)
"#{escape_once(front)}#{output.chomp}#{escape_once(back)}\n".html_safe
end
def precede(str, &block)
"#{escape_once(str)}#{capture_haml(&block).chomp}\n".html_safe
end
def succeed(str, &block)
"#{capture_haml(&block).chomp}#{escape_once(str)}\n".html_safe
end
def capture_haml(*args, &block)
capture(*args, &block)
end
end
end
hamlit-2.11.0/lib/hamlit/escapable.rb 0000644 0001750 0001750 00000000555 13574632775 016733 0 ustar joseph joseph # frozen_string_literal: true
require 'hamlit/utils'
module Hamlit
class Escapable < Temple::Filters::Escapable
def initialize(opts = {})
super
@escape_code = options[:escape_code] ||
"::Hamlit::Utils.escape_html#{options[:use_html_safe] ? '_safe' : ''}((%s))"
@escaper = eval("proc {|v| #{@escape_code % 'v'} }")
end
end
end
hamlit-2.11.0/lib/hamlit/filters.rb 0000644 0001750 0001750 00000003604 13574632775 016462 0 ustar joseph joseph # frozen_string_literal: true
require 'hamlit/filters/base'
require 'hamlit/filters/text_base'
require 'hamlit/filters/tilt_base'
require 'hamlit/filters/coffee'
require 'hamlit/filters/css'
require 'hamlit/filters/erb'
require 'hamlit/filters/escaped'
require 'hamlit/filters/javascript'
require 'hamlit/filters/less'
require 'hamlit/filters/markdown'
require 'hamlit/filters/plain'
require 'hamlit/filters/preserve'
require 'hamlit/filters/ruby'
require 'hamlit/filters/sass'
require 'hamlit/filters/scss'
require 'hamlit/filters/cdata'
module Hamlit
class Filters
@registered = {}
class << self
attr_reader :registered
def remove_filter(name)
registered.delete(name.to_s.downcase.to_sym)
if constants.map(&:to_s).include?(name.to_s)
remove_const name.to_sym
end
end
private
def register(name, compiler)
registered[name] = compiler
end
end
register :coffee, Coffee
register :coffeescript, CoffeeScript
register :css, Css
register :erb, Erb
register :escaped, Escaped
register :javascript, Javascript
register :less, Less
register :markdown, Markdown
register :plain, Plain
register :preserve, Preserve
register :ruby, Ruby
register :sass, Sass
register :scss, Scss
register :cdata, Cdata
def initialize(options = {})
@options = options
@compilers = {}
end
def compile(node)
node.value[:text] ||= ''
find_compiler(node).compile(node)
end
private
def find_compiler(node)
name = node.value[:name].to_sym
compiler = Filters.registered[name]
raise FilterNotFound.new("FilterCompiler for '#{name}' was not found", node.line.to_i - 1) unless compiler
@compilers[name] ||= compiler.new(@options)
end
end
end
hamlit-2.11.0/lib/hamlit/object_ref.rb 0000644 0001750 0001750 00000001321 13574632775 017106 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
module ObjectRef
class << self
def parse(args)
object, prefix = args
return {} unless object
suffix = underscore(object.class)
{
'class' => [prefix, suffix].compact.join('_'),
'id' => [prefix, suffix, object.id || 'new'].compact.join('_'),
}
end
private
# Haml::Buffer.underscore
def underscore(camel_cased_word)
word = camel_cased_word.to_s.dup
word.gsub!(/::/, '_')
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
word.tr!('-', '_')
word.downcase!
word
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/ 0000755 0001750 0001750 00000000000 13574632775 016132 5 ustar joseph joseph hamlit-2.11.0/lib/hamlit/filters/tilt_base.rb 0000644 0001750 0001750 00000003055 13574632775 020430 0 ustar joseph joseph # frozen_string_literal: true
require 'tilt'
module Hamlit
class Filters
class TiltBase < Base
def self.render(name, source, indent_width: 0)
text = ::Tilt["t.#{name}"].new { source }.render
return text if indent_width == 0
text.gsub!(/^/, ' ' * indent_width)
end
def explicit_require?(needed_registration)
Gem::Version.new(Tilt::VERSION) >= Gem::Version.new('2.0.0') &&
!Tilt.registered?(needed_registration)
end
private
def compile_with_tilt(node, name, indent_width: 0)
if ::Hamlit::HamlUtil.contains_interpolation?(node.value[:text])
dynamic_compile(node, name, indent_width: indent_width)
else
static_compile(node, name, indent_width: indent_width)
end
end
def static_compile(node, name, indent_width: 0)
temple = [:multi, [:static, TiltBase.render(name, node.value[:text], indent_width: indent_width)]]
node.value[:text].split("\n").size.times do
temple << [:newline]
end
temple
end
def dynamic_compile(node, name, indent_width: 0)
# original: Haml::Filters#compile
text = ::Hamlit::HamlUtil.slow_unescape_interpolation(node.value[:text]).gsub(/(\\+)n/) do |s|
escapes = $1.size
next s if escapes % 2 == 0
"#{'\\' * (escapes - 1)}\n"
end
text.prepend("\n").sub!(/\n"\z/, '"')
[:dynamic, "::Hamlit::Filters::TiltBase.render('#{name}', #{text}, indent_width: #{indent_width})"]
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/text_base.rb 0000644 0001750 0001750 00000001412 13574632775 020433 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Filters
class TextBase < Base
def compile_text!(temple, node, prefix)
text = node.value[:text].rstrip.gsub(/^/, prefix)
if ::Hamlit::HamlUtil.contains_interpolation?(node.value[:text])
# original: Haml::Filters#compile
text = ::Hamlit::HamlUtil.slow_unescape_interpolation(text).gsub(/(\\+)n/) do |s|
escapes = $1.size
next s if escapes % 2 == 0
"#{'\\' * (escapes - 1)}\n"
end
text.prepend("\n")
temple << [:dynamic, text]
else
node.value[:text].split("\n").size.times do
temple << [:newline]
end
temple << [:static, text]
end
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/sass.rb 0000644 0001750 0001750 00000000600 13574632775 017424 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Filters
class Sass < TiltBase
def compile(node)
require 'tilt/sass' if explicit_require?('sass')
temple = [:multi]
temple << [:static, ""]
temple
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/scss.rb 0000644 0001750 0001750 00000000600 13574632775 017426 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Filters
class Scss < TiltBase
def compile(node)
require 'tilt/sass' if explicit_require?('scss')
temple = [:multi]
temple << [:static, ""]
temple
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/plain.rb 0000644 0001750 0001750 00000001313 13574632775 017560 0 ustar joseph joseph # frozen_string_literal: true
require 'hamlit/string_splitter'
module Hamlit
class Filters
class Plain < Base
def compile(node)
text = node.value[:text]
text = text.rstrip unless ::Hamlit::HamlUtil.contains_interpolation?(text) # for compatibility
[:multi, *compile_plain(text)]
end
private
def compile_plain(text)
string_literal = ::Hamlit::HamlUtil.unescape_interpolation(text)
StringSplitter.compile(string_literal).map do |temple|
type, str = temple
case type
when :dynamic
[:escape, false, [:dynamic, str]]
else
temple
end
end
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/escaped.rb 0000644 0001750 0001750 00000000746 13574632775 020072 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Filters
class Escaped < Base
def compile(node)
text = node.value[:text].rstrip
temple = compile_text(text)
[:escape, true, temple]
end
private
def compile_text(text)
if ::Hamlit::HamlUtil.contains_interpolation?(text)
[:dynamic, ::Hamlit::HamlUtil.slow_unescape_interpolation(text)]
else
[:static, text]
end
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/javascript.rb 0000644 0001750 0001750 00000001356 13574632775 020632 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Filters
class Javascript < TextBase
def compile(node)
case @format
when :xhtml
compile_xhtml(node)
else
compile_html(node)
end
end
private
def compile_html(node)
temple = [:multi]
temple << [:static, ""]
temple
end
def compile_xhtml(node)
temple = [:multi]
temple << [:static, ""]
temple
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/markdown.rb 0000644 0001750 0001750 00000000336 13574632775 020303 0 ustar joseph joseph module Hamlit
class Filters
class Markdown < TiltBase
def compile(node)
require 'tilt/redcarpet' if explicit_require?('markdown')
compile_with_tilt(node, 'markdown')
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/erb.rb 0000644 0001750 0001750 00000000260 13574632775 017225 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Filters
class Erb < TiltBase
def compile(node)
compile_with_tilt(node, 'erb')
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/base.rb 0000644 0001750 0001750 00000000320 13574632775 017364 0 ustar joseph joseph # frozen_string_literal: true
require 'hamlit/parser/haml_util'
module Hamlit
class Filters
class Base
def initialize(options = {})
@format = options[:format]
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/preserve.rb 0000644 0001750 0001750 00000000760 13574632775 020315 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Filters
class Preserve < Base
def compile(node)
text = node.value[:text].rstrip + "\n"
text = text.gsub("\n", '
')
compile_text(text)
end
private
def compile_text(text)
if ::Hamlit::HamlUtil.contains_interpolation?(text)
[:dynamic, ::Hamlit::HamlUtil.slow_unescape_interpolation(text)]
else
[:static, text]
end
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/cdata.rb 0000644 0001750 0001750 00000000604 13574632775 017533 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Filters
class Cdata < TextBase
def compile(node)
compile_cdata(node)
end
private
def compile_cdata(node)
temple = [:multi]
temple << [:static, ""]
temple
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/css.rb 0000644 0001750 0001750 00000001340 13574632775 017245 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Filters
class Css < TextBase
def compile(node)
case @format
when :xhtml
compile_xhtml(node)
else
compile_html(node)
end
end
private
def compile_html(node)
temple = [:multi]
temple << [:static, ""]
temple
end
def compile_xhtml(node)
temple = [:multi]
temple << [:static, ""]
temple
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/ruby.rb 0000644 0001750 0001750 00000000251 13574632775 017436 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Filters
class Ruby < Base
def compile(node)
[:code, node.value[:text]]
end
end
end
end
hamlit-2.11.0/lib/hamlit/filters/coffee.rb 0000644 0001750 0001750 00000000645 13574632775 017713 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class Filters
class Coffee < TiltBase
def compile(node)
require 'tilt/coffee' if explicit_require?('coffee')
temple = [:multi]
temple << [:static, ""]
temple
end
end
CoffeeScript = Coffee
end
end
hamlit-2.11.0/lib/hamlit/filters/less.rb 0000644 0001750 0001750 00000001100 13574632775 017415 0 ustar joseph joseph # frozen_string_literal: true
# LESS support is deprecated since it requires therubyracer.gem,
# which is hard to maintain.
#
# It's not supported in Sprockets 3.0+ too.
# https://github.com/sstephenson/sprockets/pull/547
module Hamlit
class Filters
class Less < TiltBase
def compile(node)
require 'tilt/less' if explicit_require?('less')
temple = [:multi]
temple << [:static, "']
temple
end
end
end
end
hamlit-2.11.0/lib/hamlit/railtie.rb 0000644 0001750 0001750 00000000271 13574632775 016440 0 ustar joseph joseph # frozen_string_literal: true
require 'rails'
module Hamlit
class Railtie < ::Rails::Railtie
initializer :hamlit do |app|
require 'hamlit/rails_template'
end
end
end
hamlit-2.11.0/lib/hamlit/helpers.rb 0000644 0001750 0001750 00000000565 13574632775 016457 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
module Helpers
extend self
# The same as original Haml::Helpers#preserve without block support.
def preserve(input)
# https://github.com/haml/haml/blob/4.1.0.beta.1/lib/haml/helpers.rb#L130-L133
s = input.to_s.chomp("\n")
s.gsub!(/\n/, '
')
s.delete!("\r")
s
end
end
end
hamlit-2.11.0/lib/hamlit/attribute_parser.rb 0000644 0001750 0001750 00000005053 13574632775 020371 0 ustar joseph joseph # frozen_string_literal: true
require 'hamlit/ruby_expression'
module Hamlit
class AttributeParser
class ParseSkip < StandardError
end
def self.parse(text)
self.new.parse(text)
end
def parse(text)
exp = wrap_bracket(text)
return if RubyExpression.syntax_error?(exp)
hash = {}
tokens = Ripper.lex(exp)[1..-2] || []
each_attr(tokens) do |attr_tokens|
key = parse_key!(attr_tokens)
hash[key] = attr_tokens.map { |t| t[2] }.join.strip
end
hash
rescue ParseSkip
nil
end
private
def wrap_bracket(text)
text = text.strip
return text if text[0] == '{'
"{#{text}}"
end
def parse_key!(tokens)
_, type, str = tokens.shift
case type
when :on_sp
parse_key!(tokens)
when :on_label
str.tr(':', '')
when :on_symbeg
_, _, key = tokens.shift
assert_type!(tokens.shift, :on_tstring_end) if str != ':'
skip_until_hash_rocket!(tokens)
key
when :on_tstring_beg
_, _, key = tokens.shift
next_token = tokens.shift
unless next_token[1] == :on_label_end
assert_type!(next_token, :on_tstring_end)
skip_until_hash_rocket!(tokens)
end
key
else
raise ParseSkip
end
end
def assert_type!(token, type)
raise ParseSkip if token[1] != type
end
def skip_until_hash_rocket!(tokens)
until tokens.empty?
_, type, str = tokens.shift
break if type == :on_op && str == '=>'
end
end
def each_attr(tokens)
attr_tokens = []
open_tokens = Hash.new { |h, k| h[k] = 0 }
tokens.each do |token|
_, type, _ = token
case type
when :on_comma
if open_tokens.values.all?(&:zero?)
yield(attr_tokens)
attr_tokens = []
next
end
when :on_lbracket
open_tokens[:array] += 1
when :on_rbracket
open_tokens[:array] -= 1
when :on_lbrace
open_tokens[:block] += 1
when :on_rbrace
open_tokens[:block] -= 1
when :on_lparen
open_tokens[:paren] += 1
when :on_rparen
open_tokens[:paren] -= 1
when :on_embexpr_beg
open_tokens[:embexpr] += 1
when :on_embexpr_end
open_tokens[:embexpr] -= 1
when :on_sp
next if attr_tokens.empty?
end
attr_tokens << token
end
yield(attr_tokens) unless attr_tokens.empty?
end
end
end
hamlit-2.11.0/lib/hamlit/dynamic_merger.rb 0000644 0001750 0001750 00000003654 13574632775 020004 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
# Compile [:multi, [:static, 'foo'], [:dynamic, 'bar']] to [:dynamic, '"foo#{bar}"']
class DynamicMerger < Temple::Filter
def on_multi(*exps)
exps = exps.dup
result = [:multi]
buffer = []
until exps.empty?
type, arg = exps.first
if type == :dynamic && arg.count("\n") == 0
buffer << exps.shift
elsif type == :static && exps.size > (count = arg.count("\n")) &&
exps[1, count].all? { |e| e == [:newline] }
(1 + count).times { buffer << exps.shift }
elsif type == :newline && exps.size > (count = count_newline(exps)) &&
exps[count].first == :static && count == exps[count].last.count("\n")
(count + 1).times { buffer << exps.shift }
else
result.concat(merge_dynamic(buffer))
buffer = []
result << compile(exps.shift)
end
end
result.concat(merge_dynamic(buffer))
result.size == 2 ? result[1] : result
end
private
def merge_dynamic(exps)
# Merge exps only when they have both :static and :dynamic
unless exps.any? { |type,| type == :static } && exps.any? { |type,| type == :dynamic }
return exps
end
strlit_body = String.new
exps.each do |type, arg|
case type
when :static
strlit_body << arg.dump.sub!(/\A"/, '').sub!(/"\z/, '').gsub('\n', "\n")
when :dynamic
strlit_body << "\#{#{arg}}"
when :newline
# newline is added by `gsub('\n', "\n")`
else
raise "unexpected type #{type.inspect} is given to #merge_dynamic"
end
end
[[:dynamic, "%Q\0#{strlit_body}\0"]]
end
def count_newline(exps)
count = 0
exps.each do |exp|
if exp == [:newline]
count += 1
else
return count
end
end
return count
end
end
end
hamlit-2.11.0/lib/hamlit/compiler.rb 0000644 0001750 0001750 00000005050 13574632775 016621 0 ustar joseph joseph # frozen_string_literal: true
require 'hamlit/compiler/children_compiler'
require 'hamlit/compiler/comment_compiler'
require 'hamlit/compiler/doctype_compiler'
require 'hamlit/compiler/script_compiler'
require 'hamlit/compiler/silent_script_compiler'
require 'hamlit/compiler/tag_compiler'
require 'hamlit/filters'
require 'hamlit/identity'
module Hamlit
class Compiler
def initialize(options = {})
identity = Identity.new
@children_compiler = ChildrenCompiler.new
@comment_compiler = CommentCompiler.new
@doctype_compiler = DoctypeCompiler.new(options)
@filter_compiler = Filters.new(options)
@script_compiler = ScriptCompiler.new(identity)
@silent_script_compiler = SilentScriptCompiler.new
@tag_compiler = TagCompiler.new(identity, options)
end
def call(ast)
return runtime_error(ast) if ast.is_a?(HamlError)
compile(ast)
rescue Error => e
runtime_error(e)
end
private
def compile(node)
case node.type
when :root
compile_children(node)
when :comment
compile_comment(node)
when :doctype
compile_doctype(node)
when :filter
compile_filter(node)
when :plain
compile_plain(node)
when :script
compile_script(node)
when :silent_script
compile_silent_script(node)
when :tag
compile_tag(node)
when :haml_comment
[:multi]
else
raise InternalError.new("Unexpected node type: #{node.type}")
end
end
def compile_children(node)
@children_compiler.compile(node) { |n| compile(n) }
end
def compile_comment(node)
@comment_compiler.compile(node) { |n| compile_children(n) }
end
def compile_doctype(node)
@doctype_compiler.compile(node)
end
def compile_filter(node)
@filter_compiler.compile(node)
end
def compile_plain(node)
[:static, node.value[:text]]
end
def compile_script(node)
@script_compiler.compile(node) { |n| compile_children(n) }
end
def compile_silent_script(node)
@silent_script_compiler.compile(node) { |n| compile_children(n) }
end
def compile_tag(node)
@tag_compiler.compile(node) { |n| compile_children(n) }
end
def runtime_error(error)
[:multi].tap do |temple|
error.line.times { temple << [:newline] } if error.line
temple << [:code, %Q[raise #{error.class}.new(%q[#{error.message}], #{error.line.inspect})]]
end
end
end
end
hamlit-2.11.0/lib/hamlit/rails_template.rb 0000644 0001750 0001750 00000003266 13574632775 020023 0 ustar joseph joseph # frozen_string_literal: true
require 'temple'
require 'hamlit/engine'
require 'hamlit/rails_helpers'
require 'hamlit/parser/haml_helpers'
require 'hamlit/parser/haml_util'
module Hamlit
class RailsTemplate
# Compatible with: https://github.com/judofyr/temple/blob/v0.7.7/lib/temple/mixins/options.rb#L15-L24
class << self
def options
@options ||= {
generator: Temple::Generators::RailsOutputBuffer,
use_html_safe: true,
streaming: true,
buffer_class: 'ActionView::OutputBuffer',
}
end
def set_options(opts)
options.update(opts)
end
end
def call(template, source = nil)
source ||= template.source
options = RailsTemplate.options
# https://github.com/haml/haml/blob/4.0.7/lib/haml/template/plugin.rb#L19-L20
# https://github.com/haml/haml/blob/4.0.7/lib/haml/options.rb#L228
if template.respond_to?(:type) && template.type == 'text/xml'
options = options.merge(format: :xhtml)
end
Engine.new(options).call(source)
end
def supports_streaming?
RailsTemplate.options[:streaming]
end
end
ActionView::Template.register_template_handler(:haml, RailsTemplate.new)
# https://github.com/haml/haml/blob/4.0.7/lib/haml/template.rb
module HamlHelpers
require 'hamlit/parser/haml_xss_mods'
include Hamlit::HamlHelpers::XssMods
end
module HamlUtil
undef :rails_xss_safe? if defined? rails_xss_safe?
def rails_xss_safe?; true; end
end
end
# Haml extends Haml::Helpers in ActionView each time.
# It costs much, so Hamlit includes a compatible module at first.
ActionView::Base.send :include, Hamlit::RailsHelpers
hamlit-2.11.0/lib/hamlit/html.rb 0000644 0001750 0001750 00000001224 13574632775 015752 0 ustar joseph joseph # frozen_string_literal: true
module Hamlit
class HTML < Temple::HTML::Fast
DEPRECATED_FORMATS = %i[html4 html5].freeze
def initialize(opts = {})
if DEPRECATED_FORMATS.include?(opts[:format])
opts = opts.dup
opts[:format] = :html
end
super(opts)
end
# This dispatcher supports Haml's "revealed" conditional comment.
def on_html_condcomment(condition, content, revealed = false)
on_html_comment [:multi,
[:static, "[#{condition}]>#{'' if revealed}"],
content,
[:static, "#{'' if revealed}"
close = "#{'"
unless block_given?
push_merged_text("#{open} ")
if @node.value[:parse]
push_script(@node.value[:text], :in_tag => true, :nuke_inner_whitespace => true)
else
push_merged_text(@node.value[:text], 0, false)
end
push_merged_text(" #{close}\n", 0, false)
return
end
push_text(open, 1)
@output_tabs += 1
yield if block_given?
@output_tabs -= 1
push_text(close, -1)
end
def compile_doctype
doctype = text_for_doctype
push_text doctype if doctype
end
def compile_filter
unless filter = Filters.defined[@node.value[:name]]
name = @node.value[:name]
if ["maruku", "textile"].include?(name)
raise ::Hamlit::HamlError.new(::Hamlit::HamlError.message(:install_haml_contrib, name), @node.line - 1)
else
raise ::Hamlit::HamlError.new(::Hamlit::HamlError.message(:filter_not_defined, name), @node.line - 1)
end
end
filter.internal_compile(self, @node.value[:text])
end
def text_for_doctype
if @node.value[:type] == "xml"
return nil if @options.html?
wrapper = @options.attr_wrapper
return ""
end
if @options.html5?
''
else
if @options.xhtml?
if @node.value[:version] == "1.1"
''
elsif @node.value[:version] == "5"
''
else
case @node.value[:type]
when "strict"; ''
when "frameset"; ''
when "mobile"; ''
when "rdfa"; ''
when "basic"; ''
else ''
end
end
elsif @options.html4?
case @node.value[:type]
when "strict"; ''
when "frameset"; ''
else ''
end
end
end
end
# Evaluates `text` in the context of the scope object, but
# does not output the result.
def push_silent(text, can_suppress = false)
flush_merged_text
return if can_suppress && @options.suppress_eval?
newline = (text == "end") ? ";" : "\n"
@precompiled << "#{resolve_newlines}#{text}#{newline}"
@output_line = @output_line + text.count("\n") + newline.count("\n")
end
# Adds `text` to `@buffer` with appropriate tabulation
# without parsing it.
def push_merged_text(text, tab_change = 0, indent = true)
text = !indent || @dont_indent_next_line || @options.ugly ? text : "#{' ' * @output_tabs}#{text}"
@to_merge << [:text, text, tab_change]
@dont_indent_next_line = false
end
# Concatenate `text` to `@buffer` without tabulation.
def concat_merged_text(text)
@to_merge << [:text, text, 0]
end
def push_text(text, tab_change = 0)
push_merged_text("#{text}\n", tab_change)
end
def flush_merged_text
return if @to_merge.empty?
mtabs = 0
@to_merge.map! do |type, val, tabs|
case type
when :text
mtabs += tabs
inspect_obj(val)[1...-1]
when :script
if mtabs != 0 && !@options.ugly
val = "_hamlout.adjust_tabs(#{mtabs}); " + val
end
mtabs = 0
"\#{#{val}}"
else
raise ::Hamlit::HamlSyntaxError.new("[HAML BUG] Undefined entry in ::Hamlit::HamlCompiler@to_merge.")
end
end
str = @to_merge.join
unless str.empty?
@precompiled <<
if @options.ugly
"_hamlout.buffer << \"#{str}\";"
else
"_hamlout.push_text(\"#{str}\", #{mtabs}, #{@dont_tab_up_next_text.inspect});"
end
end
@to_merge = []
@dont_tab_up_next_text = false
end
# Causes `text` to be evaluated in the context of
# the scope object and the result to be added to `@buffer`.
#
# If `opts[:preserve_script]` is true, Haml::Helpers#find_and_preserve is run on
# the result before it is added to `@buffer`
def push_script(text, opts = {})
return if @options.suppress_eval?
args = [:preserve_script, :in_tag, :preserve_tag, :escape_html, :nuke_inner_whitespace]
args.map! {|name| !!opts[name]}
args << !block_given? << @options.ugly
no_format = @options.ugly &&
!(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
# Prerender tabulation unless we're in a tag
push_merged_text '' unless opts[:in_tag]
unless block_given?
format_script_method = "_hamlout.format_script((#{text}\n),#{args.join(',')});"
push_generated_script(no_format ? "#{text}\n" : format_script_method)
concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
return
end
flush_merged_text
push_silent "haml_temp = #{text}"
yield
push_silent('end', :can_suppress) unless @node.value[:dont_push_end]
format_script_method = "_hamlout.format_script(haml_temp,#{args.join(',')});"
@precompiled << "_hamlout.buffer << #{no_format ? "haml_temp.to_s;" : format_script_method}"
concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace] || @options.ugly
end
def push_generated_script(text)
@to_merge << [:script, resolve_newlines + text]
@output_line += text.count("\n")
end
# This is a class method so it can be accessed from Buffer.
def self.build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
# @TODO this is an absolutely ridiculous amount of arguments. At least
# some of this needs to be moved into an instance method.
quote_escape = attr_wrapper == '"' ? """ : "'"
other_quote_char = attr_wrapper == '"' ? "'" : '"'
join_char = hyphenate_data_attrs ? '-' : '_'
attributes.each do |key, value|
if value.is_a?(Hash)
data_attributes = attributes.delete(key)
data_attributes = flatten_data_attributes(data_attributes, '', join_char)
data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
attributes = data_attributes.merge(attributes)
end
end
result = attributes.collect do |attr, value|
next if value.nil?
value = filter_and_join(value, ' ') if attr == 'class'
value = filter_and_join(value, '_') if attr == 'id'
if value == true
next " #{attr}" if is_html
next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
elsif value == false
next
end
escaped =
if escape_attrs == :once
::Hamlit::HamlHelpers.escape_once(value.to_s)
elsif escape_attrs
::Hamlit::HamlHelpers.html_escape(value.to_s)
else
value.to_s
end
value = ::Hamlit::HamlHelpers.preserve(escaped)
if escape_attrs
# We want to decide whether or not to escape quotes
value.gsub!(/"|"/, '"')
this_attr_wrapper = attr_wrapper
if value.include? attr_wrapper
if value.include? other_quote_char
value.gsub!(attr_wrapper, quote_escape)
else
this_attr_wrapper = other_quote_char
end
end
else
this_attr_wrapper = attr_wrapper
end
" #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
end
result.compact!
result.sort!
result.join
end
def self.filter_and_join(value, separator)
return '' if (value.respond_to?(:empty?) && value.empty?)
if value.is_a?(Array)
value.flatten!
value.map! {|item| item ? item.to_s : nil}
value.compact!
value = value.join(separator)
else
value = value ? value.to_s : nil
end
!value.nil? && !value.empty? && value
end
def self.build_data_keys(data_hash, hyphenate, attr_name="data")
Hash[data_hash.map do |name, value|
if name == nil
[attr_name, value]
elsif hyphenate
["#{attr_name}-#{name.to_s.tr('_', '-')}", value]
else
["#{attr_name}-#{name}", value]
end
end]
end
def self.flatten_data_attributes(data, key, join_char, seen = [])
return {key => data} unless data.is_a?(Hash)
return {key => nil} if seen.include? data.object_id
seen << data.object_id
data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, (k, v)|
joined = key == '' ? k : [key, k].join(join_char)
hash.merge! flatten_data_attributes(v, joined, join_char, seen)
end
end
def prerender_tag(name, self_close, attributes)
attributes_string = ::Hamlit::HamlCompiler.build_attributes(
@options.html?, @options.attr_wrapper, @options.escape_attrs, @options.hyphenate_data_attrs, attributes)
"<#{name}#{attributes_string}#{self_close && @options.xhtml? ? ' /' : ''}>"
end
def resolve_newlines
diff = @node.line - @output_line
return "" if diff <= 0
@output_line = @node.line
"\n" * diff
end
# Get rid of and whitespace at the end of the buffer
# or the merged text
def rstrip_buffer!(index = -1)
last = @to_merge[index]
if last.nil?
push_silent("_hamlout.rstrip!", false)
@dont_tab_up_next_text = true
return
end
case last.first
when :text
last[1].rstrip!
if last[1].empty?
@to_merge.slice! index
rstrip_buffer! index
end
when :script
last[1].gsub!(/\(haml_temp, (.*?)\);$/, '(haml_temp.rstrip, \1);')
rstrip_buffer! index - 1
else
raise ::Hamlit::HamlSyntaxError.new("[HAML BUG] Undefined entry in ::Hamlit::HamlCompiler@to_merge.")
end
end
end
end
hamlit-2.11.0/lib/hamlit/parser/haml_util.rb 0000644 0001750 0001750 00000023631 13574632775 020266 0 ustar joseph joseph # encoding: utf-8
begin
require 'erubis/tiny'
rescue LoadError
require 'erb'
end
require 'set'
require 'stringio'
require 'strscan'
module Hamlit
# A module containing various useful functions.
module HamlUtil
extend self
# Silence all output to STDERR within a block.
#
# @yield A block in which no output will be printed to STDERR
def silence_warnings
the_real_stderr, $stderr = $stderr, StringIO.new
yield
ensure
$stderr = the_real_stderr
end
## Rails XSS Safety
# Whether or not ActionView's XSS protection is available and enabled,
# as is the default for Rails 3.0+, and optional for version 2.3.5+.
# Overridden in haml/template.rb if this is the case.
#
# @return [Boolean]
def rails_xss_safe?
false
end
# Returns the given text, marked as being HTML-safe.
# With older versions of the Rails XSS-safety mechanism,
# this destructively modifies the HTML-safety of `text`.
#
# It only works if you are using ActiveSupport or the parameter `text`
# implements the #html_safe method.
#
# @param text [String, nil]
# @return [String, nil] `text`, marked as HTML-safe
def html_safe(text)
return unless text
text.html_safe
end
# Checks that the encoding of a string is valid
# and cleans up potential encoding gotchas like the UTF-8 BOM.
# If it's not, yields an error string describing the invalid character
# and the line on which it occurs.
#
# @param str [String] The string of which to check the encoding
# @yield [msg] A block in which an encoding error can be raised.
# Only yields if there is an encoding error
# @yieldparam msg [String] The error message to be raised
# @return [String] `str`, potentially with encoding gotchas like BOMs removed
def check_encoding(str)
if str.valid_encoding?
# Get rid of the Unicode BOM if possible
# Shortcut for UTF-8 which might be the majority case
if str.encoding == Encoding::UTF_8
return str.gsub(/\A\uFEFF/, '')
elsif str.encoding.name =~ /^UTF-(16|32)(BE|LE)?$/
return str.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding)), '')
else
return str
end
end
encoding = str.encoding
newlines = Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding(Encoding::ASCII_8BIT))
str.force_encoding(Encoding::ASCII_8BIT).split(newlines).each_with_index do |line, i|
begin
line.encode(encoding)
rescue Encoding::UndefinedConversionError => e
yield < "\"foo\#{::Hamlit::HamlHelpers.html_escape((bar))}baz\\\"\""
def slow_unescape_interpolation(str, escape_html = nil)
res = ''
rest = ::Hamlit::HamlUtil.handle_interpolation str.dump do |scan|
escapes = (scan[2].size - 1) / 2
char = scan[3] # '{', '@' or '$'
res << scan.matched[0...-3 - escapes]
if escapes % 2 == 1
res << "\##{char}"
else
interpolated = if char == '{'
balance(scan, ?{, ?}, 1)[0][0...-1]
else
scan.scan(/\w+/)
end
content = eval('"' + interpolated + '"')
content.prepend(char) if char == '@' || char == '$'
content = "::Hamlit::HamlHelpers.html_escape((#{content}))" if escape_html
res << "\#{#{content}}"
end
end
res + rest
end
# Customized Haml::Util.unescape_interpolation to handle escape by Hamlit.
# It wraps double quotes to given `str` with escaping `"`.
#
# ex) unescape_interpolation('foo#{bar}baz"') #=> "\"foo\#{bar}baz\\\"\""
def unescape_interpolation(str)
res = ''
rest = ::Hamlit::HamlUtil.handle_interpolation str.dump do |scan|
escapes = (scan[2].size - 1) / 2
char = scan[3] # '{', '@' or '$'
res << scan.matched[0...-3 - escapes]
if escapes % 2 == 1
res << "\##{char}"
else
interpolated = if char == '{'
balance(scan, ?{, ?}, 1)[0][0...-1]
else
scan.scan(/\w+/)
end
content = eval('"' + interpolated + '"')
content.prepend(char) if char == '@' || char == '$'
res << "\#{#{content}}"
end
end
res + rest
end
private
# Parses a magic comment at the beginning of a Haml file.
# The parsing rules are basically the same as Ruby's.
#
# @return [(Boolean, String or nil)]
# Whether the document begins with a UTF-8 BOM,
# and the declared encoding of the document (or nil if none is declared)
def parse_haml_magic_comment(str)
scanner = StringScanner.new(str.dup.force_encoding(Encoding::ASCII_8BIT))
bom = scanner.scan(/\xEF\xBB\xBF/n)
return bom unless scanner.scan(/-\s*#\s*/n)
if coding = try_parse_haml_emacs_magic_comment(scanner)
return bom, coding
end
return bom unless scanner.scan(/.*?coding[=:]\s*([\w-]+)/in)
return bom, scanner[1]
end
def try_parse_haml_emacs_magic_comment(scanner)
pos = scanner.pos
return unless scanner.scan(/.*?-\*-\s*/n)
# From Ruby's parse.y
return unless scanner.scan(/([^\s'":;]+)\s*:\s*("(?:\\.|[^"])*"|[^"\s;]+?)[\s;]*-\*-/n)
name, val = scanner[1], scanner[2]
return unless name =~ /(en)?coding/in
val = $1 if val =~ /^"(.*)"$/n
return val
ensure
scanner.pos = pos
end
end
end
hamlit-2.11.0/lib/hamlit/parser/haml_error.rb 0000644 0001750 0001750 00000006102 13574632775 020434 0 ustar joseph joseph module Hamlit
# An exception raised by Haml code.
class HamlError < StandardError
MESSAGES = {
:bad_script_indent => '"%s" is indented at wrong level: expected %d, but was at %d.',
:cant_run_filter => 'Can\'t run "%s" filter; you must require its dependencies first',
:cant_use_tabs_and_spaces => "Indentation can't use both tabs and spaces.",
:deeper_indenting => "The line was indented %d levels deeper than the previous line.",
:filter_not_defined => 'Filter "%s" is not defined.',
:gem_install_filter_deps => '"%s" filter\'s %s dependency missing: try installing it or adding it to your Gemfile',
:illegal_element => "Illegal element: classes and ids must have values.",
:illegal_nesting_content => "Illegal nesting: nesting within a tag that already has content is illegal.",
:illegal_nesting_header => "Illegal nesting: nesting within a header command is illegal.",
:illegal_nesting_line => "Illegal nesting: content can't be both given on the same line as %%%s and nested within it.",
:illegal_nesting_plain => "Illegal nesting: nesting within plain text is illegal.",
:illegal_nesting_self_closing => "Illegal nesting: nesting within a self-closing tag is illegal.",
:inconsistent_indentation => "Inconsistent indentation: %s used for indentation, but the rest of the document was indented using %s.",
:indenting_at_start => "Indenting at the beginning of the document is illegal.",
:install_haml_contrib => 'To use the "%s" filter, please install the haml-contrib gem.',
:invalid_attribute_list => 'Invalid attribute list: %s.',
:invalid_filter_name => 'Invalid filter name ":%s".',
:invalid_tag => 'Invalid tag: "%s".',
:missing_if => 'Got "%s" with no preceding "if"',
:no_ruby_code => "There's no Ruby code for %s to evaluate.",
:self_closing_content => "Self-closing tags can't have content.",
:unbalanced_brackets => 'Unbalanced brackets.',
:no_end => <<-END
You don't need to use "- end" in Haml. Un-indent to close a block:
- if foo?
%strong Foo!
- else
Not foo.
%p This line is un-indented, so it isn't part of the "if" block
END
}
def self.message(key, *args)
string = MESSAGES[key] or raise "[HAML BUG] No error messages for #{key}"
(args.empty? ? string : string % args).rstrip
end
# The line of the template on which the error occurred.
#
# @return [Fixnum]
attr_reader :line
# @param message [String] The error message
# @param line [Fixnum] See \{#line}
def initialize(message = nil, line = nil)
super(message)
@line = line
end
end
# SyntaxError is the type of exception raised when Haml encounters an
# ill-formatted document.
# It's not particularly interesting,
# except in that it's a subclass of {Haml::Error}.
class HamlSyntaxError < HamlError; end
end
hamlit-2.11.0/lib/hamlit/parser/haml_buffer.rb 0000644 0001750 0001750 00000026236 13574632775 020566 0 ustar joseph joseph require 'hamlit/parser/haml_helpers'
require 'hamlit/parser/haml_util'
require 'hamlit/parser/haml_compiler'
module Hamlit
# This class is used only internally. It holds the buffer of HTML that
# is eventually output as the resulting document.
# It's called from within the precompiled code,
# and helps reduce the amount of processing done within `instance_eval`ed code.
class HamlBuffer
ID_KEY = 'id'.freeze
CLASS_KEY = 'class'.freeze
DATA_KEY = 'data'.freeze
include ::Hamlit::HamlHelpers
include ::Hamlit::HamlUtil
# The string that holds the compiled HTML. This is aliased as
# `_erbout` for compatibility with ERB-specific code.
#
# @return [String]
attr_accessor :buffer
# The options hash passed in from {Haml::Engine}.
#
# @return [{String => Object}]
# @see Haml::Options#for_buffer
attr_accessor :options
# The {Buffer} for the enclosing Haml document.
# This is set for partials and similar sorts of nested templates.
# It's `nil` at the top level (see \{#toplevel?}).
#
# @return [Buffer]
attr_accessor :upper
# nil if there's no capture_haml block running,
# and the position at which it's beginning the capture if there is one.
#
# @return [Fixnum, nil]
attr_accessor :capture_position
# @return [Boolean]
# @see #active?
attr_writer :active
# @return [Boolean] Whether or not the format is XHTML
def xhtml?
not html?
end
# @return [Boolean] Whether or not the format is any flavor of HTML
def html?
html4? or html5?
end
# @return [Boolean] Whether or not the format is HTML4
def html4?
@options[:format] == :html4
end
# @return [Boolean] Whether or not the format is HTML5.
def html5?
@options[:format] == :html5
end
# @return [Boolean] Whether or not this buffer is a top-level template,
# as opposed to a nested partial
def toplevel?
upper.nil?
end
# Whether or not this buffer is currently being used to render a Haml template.
# Returns `false` if a subtemplate is being rendered,
# even if it's a subtemplate of this buffer's template.
#
# @return [Boolean]
def active?
@active
end
# @return [Fixnum] The current indentation level of the document
def tabulation
@real_tabs + @tabulation
end
# Sets the current tabulation of the document.
#
# @param val [Fixnum] The new tabulation
def tabulation=(val)
val = val - @real_tabs
@tabulation = val > -1 ? val : 0
end
# @param upper [Buffer] The parent buffer
# @param options [{Symbol => Object}] An options hash.
# See {Haml::Engine#options\_for\_buffer}
def initialize(upper = nil, options = {})
@active = true
@upper = upper
@options = options
@buffer = new_encoded_string
@tabulation = 0
# The number of tabs that Engine thinks we should have
# @real_tabs + @tabulation is the number of tabs actually output
@real_tabs = 0
end
# Appends text to the buffer, properly tabulated.
# Also modifies the document's indentation.
#
# @param text [String] The text to append
# @param tab_change [Fixnum] The number of tabs by which to increase
# or decrease the document's indentation
# @param dont_tab_up [Boolean] If true, don't indent the first line of `text`
def push_text(text, tab_change, dont_tab_up)
if @tabulation > 0
# Have to push every line in by the extra user set tabulation.
# Don't push lines with just whitespace, though,
# because that screws up precompiled indentation.
text.gsub!(/^(?!\s+$)/m, tabs)
text.sub!(tabs, '') if dont_tab_up
end
@real_tabs += tab_change
@buffer << text
end
# Modifies the indentation of the document.
#
# @param tab_change [Fixnum] The number of tabs by which to increase
# or decrease the document's indentation
def adjust_tabs(tab_change)
@real_tabs += tab_change
end
# the number of arguments here is insane, but passing in an options hash instead of named arguments
# causes a significant performance regression
def format_script(result, preserve_script, in_tag, preserve_tag, escape_html, nuke_inner_whitespace, interpolated, ugly)
result_name = escape_html ? html_escape(result.to_s) : result.to_s
if ugly
result = nuke_inner_whitespace ? result_name.strip : result_name
result = preserve(result, preserve_script, preserve_tag)
fix_textareas!(result) if toplevel? && result.include?('