# 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

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

  Moves a scanner through a balanced pair of characters.

- #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.

- #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.

- #contains_interpolation?(str) ⇒ Boolean
- #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.

- #html_safe(text) ⇒ String?

  Returns the given text, marked as being HTML-safe.

- #human_indentation(indentation) ⇒ String

  Formats a string for use in error messages about indentation.

- #inspect_obj(obj) ⇒ String

  Like `Object#inspect`, but preserves non-ASCII characters rather than escaping them.

- #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+.

- #silence_warnings { ... }

  Silence all output to STDERR within a block.

- #unescape_interpolation(str, escape_html = nil)

## 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 `#encode`d 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