1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
|
# frozen_string_literal: true
module Slim
module Smart
# Perform newline processing in the
# expressions `[:slim, :text, type, Expression]`.
#
# @api private
class Filter < ::Slim::Filter
define_options smart_text: true,
smart_text_end_chars: '([{',
smart_text_begin_chars: ',.;:!?)]}'
def initialize(opts = {})
super
@active = @prepend = @append = false
@prepend_re = /\A#{chars_re(options[:smart_text_begin_chars])}/
@append_re = /#{chars_re(options[:smart_text_end_chars])}\Z/
end
def call(exp)
if options[:smart_text]
super
else
exp
end
end
def on_multi(*exps)
# The [:multi] blocks serve two purposes.
# On outer level, they collect the building blocks like
# tags, verbatim text, and implicit/explicit text.
# Within a text block, they collect the individual
# lines in [:slim, :interpolate, string] blocks.
#
# Our goal here is to decide when we want to prepend and
# append newlines to those individual interpolated lines.
# We basically want the text to come out as it was originally entered,
# while removing newlines next to the enclosing tags.
#
# On outer level, we choose to prepend every time, except
# right after the opening tag or after other text block.
# We also use the append flag to recognize the last expression
# before the closing tag, as we don't want to append newline there.
#
# Within text block, we prepend only before the first line unless
# the outer level tells us not to, and we append only after the last line,
# unless the outer level tells us it is the last line before the closing tag.
# Of course, this is later subject to the special begin/end characters
# which may further suppress the newline at the corresponding line boundary.
# Also note that the lines themselves are already correctly separated by newlines,
# so we don't have to worry about that at all.
block = [:multi]
prev = nil
last_exp = exps.reject { |exp| exp.first == :newline }.last unless @active && @append
exps.each do |exp|
@append = exp.equal?(last_exp)
if @active
@prepend = false if prev
else
@prepend = prev && (prev.first != :slim || prev[1] != :text)
end
block << compile(exp)
prev = exp unless exp.first == :newline
end
block
end
def on_slim_text(type, content)
@active = type != :verbatim
[:slim, :text, type, compile(content)]
ensure
@active = false
end
def on_slim_text_inline(content)
# Inline text is not wrapped in multi block, so set it up as if it was.
@prepend = false
@append = true
on_slim_text(:inline, content)
end
def on_slim_interpolate(string)
if @active
string = "\n" + string if @prepend && string !~ @prepend_re
string += "\n" if @append && string !~ @append_re
end
[:slim, :interpolate, string]
end
private
def chars_re(string)
Regexp.union(string.split(//))
end
end
end
end
|