require 'strscan' module Sass # This module contains functionality that's shared between Haml and Sass. module Shared extend self # 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 processed def handle_interpolation(str) scan = StringScanner.new(str) yield scan while scan.scan(/(.*?)(\\*)\#\{/) scan.rest end # 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 [Character] The character opening the balanced pair. # A `Fixnum` in 1.8, a `String` in 1.9 # @param finish [Character] The character closing the balanced pair. # A `Fixnum` in 1.8, a `String` in 1.9 # @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. def balance(scanner, start, finish, count = 0) str = '' 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 # Formats a string for use in error messages about indentation. # # @param indentation [String] The string used for indentation # @param was [Boolean] Whether or not to add `"was"` or `"were"` # (depending on how many characters were in `indentation`) # @return [String] The name of the indentation (e.g. `"12 spaces"`, `"1 tab"`) def human_indentation(indentation, was = false) if !indentation.include?(?\t) noun = 'space' elsif !indentation.include?(?\s) noun = 'tab' else return indentation.inspect + (was ? ' was' : '') end singular = indentation.length == 1 if was was = singular ? ' was' : ' were' else was = '' end "#{indentation.length} #{noun}#{'s' unless singular}#{was}" end end end