# frozen_string_literal: falsebeginrequire'erubis/tiny'rescueLoadErrorrequire'erb'endrequire'set'require'stringio'require'strscan'moduleHaml# A module containing various useful functions.moduleUtilextendself# Silence all output to STDERR within a block.## @yield A block in which no output will be printed to STDERRdefsilence_warningsthe_real_stderr,$stderr=$stderr,StringIO.newyieldensure$stderr=the_real_stderrend## 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]defrails_xss_safe?falseend# 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-safedefhtml_safe(text)returnunlesstexttext.html_safeend# 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 removeddefcheck_encoding(str)ifstr.valid_encoding?# Get rid of the Unicode BOM if possible# Shortcut for UTF-8 which might be the majority caseifstr.encoding==Encoding::UTF_8returnstr.gsub(/\A\uFEFF/,'')elsifstr.encoding.name=~/^UTF-(16|32)(BE|LE)?$/returnstr.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding)),'')elsereturnstrendendencoding=str.encodingnewlines=Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding(Encoding::ASCII_8BIT))str.force_encoding(Encoding::ASCII_8BIT).split(newlines).each_with_indexdo|line,i|beginline.encode(encoding)rescueEncoding::UndefinedConversionError=>eyield<<MSG.rstrip,i+1
Invalid #{encoding.name} character #{e.error_char.dump}MSGendendreturnstrend# Like {\#check\_encoding}, but also checks for a Ruby-style `-# coding:` comment# at the beginning of the template and uses that encoding if it exists.## The Haml encoding rules are simple.# If a `-# coding:` comment exists,# we assume that that's the original encoding of the document.# Otherwise, we use whatever encoding Ruby has.## Haml uses the same rules for parsing coding comments as Ruby.# This means that it can understand Emacs-style comments# (e.g. `-*- encoding: "utf-8" -*-`),# and also that it cannot understand non-ASCII-compatible encodings# such as `UTF-16` and `UTF-32`.## @param str [String] The Haml template 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] The original string encoded properly# @raise [ArgumentError] if the document declares an unknown encodingdefcheck_haml_encoding(str,&block)str=str.dupifstr.frozen?bom,encoding=parse_haml_magic_comment(str)ifencoding;str.force_encoding(encoding)elsifbom;str.force_encoding(Encoding::UTF_8)endreturncheck_encoding(str,&block)end# Like `Object#inspect`, but preserves non-ASCII characters rather than escaping them.# This is necessary so that the precompiled Haml template can be `#encode`d into `@options[:encoding]`# before being evaluated.## @param obj {Object}# @return {String}definspect_obj(obj)caseobjwhenString%Q!"#{obj.gsub(/[\x00-\x7F]+/){|s|s.inspect[1...-1]}}"!whenSymbol":#{inspect_obj(obj.to_s)}"elseobj.inspectendend# Scans through a string looking for the interoplation-opening `#{`# and, when it's found, yields the scanner to the calling code# so it can handle it properly.## The scanner will have any backslashes immediately in front of the `#{`# as the second capture group (`scan[2]`),# and the text prior to that as the first (`scan[1]`).## @yieldparam scan [StringScanner] The scanner scanning through the string# @return [String] The text remaining in the scanner after all `#{`s have been processeddefhandle_interpolation(str)scan=StringScanner.new(str)yieldscanwhilescan.scan(/(.*?)(\\*)#([\{@$])/)scan.restend# Moves a scanner through a balanced pair of characters.# For example:## Foo (Bar (Baz bang) bop) (Bang (bop bip))# ^ ^# from to## @param scanner [StringScanner] The string scanner to move# @param start [String] The character opening the balanced pair.# @param finish [String] The character closing the balanced pair.# @param count [Fixnum] The number of opening characters matched# before calling this method# @return [(String, String)] The string matched within the balanced pair# and the rest of the string.# `["Foo (Bar (Baz bang) bop)", " (Bang (bop bip))"]` in the example above.defbalance(scanner,start,finish,count=0)str=''scanner=StringScanner.new(scanner)unlessscanner.is_a?StringScannerregexp=Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]",Regexp::MULTILINE)whilescanner.scan(regexp)str<<scanner.matchedcount+=1ifscanner.matched[-1]==startcount-=1ifscanner.matched[-1]==finishreturn[str.strip,scanner.rest]ifcount==0endend# Formats a string for use in error messages about indentation.## @param indentation [String] The string used for indentation# @return [String] The name of the indentation (e.g. `"12 spaces"`, `"1 tab"`)defhuman_indentation(indentation)if!indentation.include?(?\t)noun='space'elsif!indentation.include?(?\s)noun='tab'elsereturnindentation.inspectendsingular=indentation.length==1"#{indentation.length}#{noun}#{'s'unlesssingular}"enddefcontains_interpolation?(str)/#[\{$@]/===strenddefunescape_interpolation(str,escape_html=nil)res=''rest=Haml::Util.handle_interpolationstr.dumpdo|scan|escapes=(scan[2].size-1)/2char=scan[3]# '{', '@' or '$'res<<scan.matched[0...-3-escapes]ifescapes%2==1res<<"\##{char}"elseinterpolated=ifchar=='{'balance(scan,?{,?},1)[0][0...-1]elsescan.scan(/\w+/)endcontent=eval('"'+interpolated+'"')content.prepend(char)ifchar=='@'||char=='$'content="Haml::Helpers.html_escape((#{content}))"ifescape_htmlres<<"\#{#{content}}"endendres+restendprivate# 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)defparse_haml_magic_comment(str)scanner=StringScanner.new(str.dup.force_encoding(Encoding::ASCII_8BIT))bom=scanner.scan(/\xEF\xBB\xBF/n)returnbomunlessscanner.scan(/-\s*#\s*/n)ifcoding=try_parse_haml_emacs_magic_comment(scanner)returnbom,codingendreturnbomunlessscanner.scan(/.*?coding[=:]\s*([\w-]+)/in)returnbom,scanner[1]enddeftry_parse_haml_emacs_magic_comment(scanner)pos=scanner.posreturnunlessscanner.scan(/.*?-\*-\s*/n)# From Ruby's parse.yreturnunlessscanner.scan(/([^\s'":;]+)\s*:\s*("(?:\\.|[^"])*"|[^"\s;]+?)[\s;]*-\*-/n)name,val=scanner[1],scanner[2]returnunlessname=~/(en)?coding/inval=$1ifval=~/^"(.*)"$/nreturnvalensurescanner.pos=posendendend