Module: Haml::Util

Extended by:
Util
Included in:
Buffer, Compiler, Engine, Parser, Util
Defined in:
lib/haml/util.rb,
lib/haml/template.rb

Overview

A module containing various useful functions.

Instance Method Summary collapse

Instance Method Details

#balance(scanner, start, finish, count = 0) ⇒ (String, String)

Moves a scanner through a balanced pair of characters. For example:

Foo (Bar (Baz bang) bop) (Bang (bop bip))
^                       ^
from                    to

Parameters:

  • scanner (StringScanner)

    The string scanner to move

  • start (String)

    The character opening the balanced pair.

  • finish (String)

    The character closing the balanced pair.

  • count (Fixnum) (defaults to: 0)

    The number of opening characters matched before calling this method

Returns:

  • ((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.

168
169
170
171
172
173
174
175
176
177
178
# File 'lib/haml/util.rb', line 168

def balance(scanner, start, finish, count = 0)
  str = ''.dup
  scanner = StringScanner.new(scanner) unless scanner.is_a? StringScanner
  regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]", Regexp::MULTILINE)
  while scanner.scan(regexp)
    str << scanner.matched
    count += 1 if scanner.matched[-1] == start
    count -= 1 if scanner.matched[-1] == finish
    return [str.strip, scanner.rest] if count == 0
  end
end

#check_encoding(str) {|msg| ... } ⇒ String

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.

Parameters:

  • str (String)

    The string of which to check the encoding

Yields:

  • (msg)

    A block in which an encoding error can be raised. Only yields if there is an encoding error

Yield Parameters:

  • msg (String)

    The error message to be raised

Returns:

  • (String)

    str, potentially with encoding gotchas like BOMs removed

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
# File 'lib/haml/util.rb', line 62

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 <<MSG.rstrip, i + 1
Invalid #{encoding.name} character #{e.error_char.dump}
MSG
    end
  end
  return str
end

#check_haml_encoding(str) {|msg| ... } ⇒ String

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.

Parameters:

  • str (String)

    The Haml template of which to check the encoding

Yields:

  • (msg)

    A block in which an encoding error can be raised. Only yields if there is an encoding error

Yield Parameters:

  • msg (String)

    The error message to be raised

Returns:

  • (String)

    The original string encoded properly

Raises:

  • (ArgumentError)

    if the document declares an unknown encoding

109
110
111
112
113
114
115
116
117
118
# File 'lib/haml/util.rb', line 109

def check_haml_encoding(str, &block)
  str = str.dup if str.frozen?

  bom, encoding = parse_haml_magic_comment(str)
  if encoding; str.force_encoding(encoding)
  elsif bom; str.force_encoding(Encoding::UTF_8)
  end

  return check_encoding(str, &block)
end

#contains_interpolation?(str) ⇒ Boolean

Returns:

  • (Boolean)
197
198
199
# File 'lib/haml/util.rb', line 197

def contains_interpolation?(str)
  /#[\{$@]/ === str
end

#handle_interpolation(str) {|scan| ... } ⇒ String

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]).

Yield Parameters:

  • scan (StringScanner)

    The scanner scanning through the string

Returns:

  • (String)

    The text remaining in the scanner after all #{s have been processed

147
148
149
150
151
# File 'lib/haml/util.rb', line 147

def handle_interpolation(str)
  scan = StringScanner.new(str)
  yield scan while scan.scan(/(.*?)(\\*)#([\{@$])/)
  scan.rest
end

#html_safe(text) ⇒ String?

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.

Parameters:

  • text (String, nil)

Returns:

  • (String, nil)

    text, marked as HTML-safe

47
48
49
50
# File 'lib/haml/util.rb', line 47

def html_safe(text)
  return unless text
  text.html_safe
end

#human_indentation(indentation) ⇒ String

Formats a string for use in error messages about indentation.

Parameters:

  • indentation (String)

    The string used for indentation

Returns:

  • (String)

    The name of the indentation (e.g. "12 spaces", "1 tab")

184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/haml/util.rb', line 184

def human_indentation(indentation)
  if !indentation.include?(?\t)
    noun = 'space'
  elsif !indentation.include?(?\s)
    noun = 'tab'
  else
    return indentation.inspect
  end

  singular = indentation.length == 1
  "#{indentation.length} #{noun}#{'s' unless singular}"
end

#inspect_obj(obj) ⇒ String

Like Object#inspect, but preserves non-ASCII characters rather than escaping them. This is necessary so that the precompiled Haml template can be #encoded into @options[:encoding] before being evaluated.

Parameters:

  • obj (Object)

Returns:

  • (String)
126
127
128
129
130
131
132
133
134
135
# File 'lib/haml/util.rb', line 126

def inspect_obj(obj)
  case obj
  when String
    %Q!"#{obj.gsub(/[\x00-\x7F]+/) {|s| s.dump[1...-1]}}"!
  when Symbol
    ":#{inspect_obj(obj.to_s)}"
  else
    obj.inspect
  end
end

#rails_xss_safe?Boolean

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.

Returns:

  • (Boolean)
34
35
36
# File 'lib/haml/util.rb', line 34

def rails_xss_safe?
  false
end

#silence_warnings { ... }

Silence all output to STDERR within a block.

Yields:

  • A block in which no output will be printed to STDERR

20
21
22
23
24
25
# File 'lib/haml/util.rb', line 20

def silence_warnings
  the_real_stderr, $stderr = $stderr, StringIO.new
  yield
ensure
  $stderr = the_real_stderr
end

#unescape_interpolation(str, escape_html = nil)

201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/haml/util.rb', line 201

def unescape_interpolation(str, escape_html = nil)
  res = ''.dup
  rest = Haml::Util.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 = "Haml::Helpers.html_escape((#{content}))" if escape_html

      res << "\#{#{content}}"
    end
  end
  res + rest
end