lib/rouge/lexers/slim.rb



# -*- coding: utf-8 -*- #

module Rouge
  module Lexers
    # A lexer for the Slim tempalte language
    # @see http://slim-lang.org
    class Slim < RegexLexer
      include Indentation

      desc 'The Slim template language'

      tag 'slim'

      filenames '*.slim'

      # Ruby identifier characters
      ruby_chars = /[\w\!\?\@\$]/

      # Since you are allowed to wrap lines with a backslash, include \\\n in characters
      dot = /(\\\n|.)/

      def self.analyze_text(text)
        return 1 if text.start_with? 'doctype'
        return 1 if text =~ /(\*)(\{.+?\})/ # Contans a hash splat
      end

      def ruby
        @ruby ||= Ruby.new(options)
      end

      def html
        @html ||= HTML.new(options)
      end

      def filters
        @filters ||= {
          'ruby' => ruby,
          'erb' => ERB.new(options),
          'javascript' => Javascript.new(options),
          'css' => CSS.new(options),
          'coffee' => Coffeescript.new(options),
          'markdown' => Markdown.new(options),
          'scss' => Scss.new(options),
          'sass' => Sass.new(options)
        }
      end

      state :root do
        rule /\s*\n/, Text
        rule(/\s*/) { |m| token Text; indentation(m[0]) }
      end

      state :content do
        mixin :css

        rule /\/#{dot}*/, Comment, :indented_block

        rule /(doctype)(\s+)(.*)/ do
          groups Name::Namespace, Text::Whitespace, Text
          pop!
        end

        # filters, shamelessly ripped from HAML
        rule /(\w*):\s*\n/ do |m|
          token Name::Decorator
          pop!
          starts_block :filter_block

          filter_name = m[1].strip

          @filter_lexer = self.filters[filter_name]
          @filter_lexer.reset! unless @filter_lexer.nil?

          puts "    slim: filter #{filter_name.inspect} #{@filter_lexer.inspect}" if @debug
        end

        # Text
        rule %r([\|'](?=\s)) do
          token Punctuation
          pop!
          starts_block :plain_block
          goto :plain_block
        end

        rule /-|==|=/, Punctuation, :ruby_line

        # Dynamic tags
        rule /(\*)(#{ruby_chars}+\(.*?\))/ do |m|
          token Punctuation, m[1]
          delegate ruby, m[2]
          push :tag
        end

        rule /(\*)(#{ruby_chars}+)/ do |m|
          token Punctuation, m[1]
          delegate ruby, m[2]
          push :tag
        end

        #rule /<\w+(?=.*>)/, Keyword::Constant, :tag # Maybe do this, look ahead and stuff
        rule %r((</?[\w\s\=\'\"]+?/?>)) do |m| # Dirty html
          delegate html, m[1]
          pop!
        end

        # Ordinary slim tags
        rule /\w+/, Name::Tag, :tag

      end

      state :tag do
        mixin :css
        mixin :indented_block
        mixin :interpolation

        # Whitespace control
        rule /[<>]/, Punctuation

        # Trim whitespace
        rule /\s+?/, Text::Whitespace

        # Splats, these two might be mergable?
        rule /(\*)(#{ruby_chars}+)/ do |m|
          token Punctuation, m[1]
          delegate ruby, m[2]
        end

        rule /(\*)(\{#{dot}+?\})/ do |m|
          token Punctuation, m[1]
          delegate ruby, m[2]
        end

        # Attributes
        rule /([\w\-]+)(\s*)(\=)/ do |m|
          token Name::Attribute, m[1]
          token Text::Whitespace, m[2]
          token Punctuation, m[3]
          push :html_attr
        end

        # Ruby value
        rule /(\=)(#{dot}+)/ do |m|
          token Punctuation, m[1]
          #token Keyword::Constant, m[2]
          delegate ruby, m[2]
        end

        rule /#{dot}+?/, Text

        rule /\s*\n/, Text::Whitespace, :pop!
      end

      state :css do
        rule(/\.\w+/) { token Name::Class; goto :tag }
        rule(/#\w+/) { token Name::Function; goto :tag }
      end

      state :html_attr do
        # Strings, double/single quoted
        rule %r(\s*(['"])#{dot}*\1), Literal::String, :pop!

        # Ruby stuff
        rule(/(#{ruby_chars}+\(.*?\))/) { |m| delegate ruby, m[1]; pop! }
        rule(/(#{ruby_chars}+)/) { |m| delegate ruby, m[1]; pop! }

        rule /\s+/, Text::Whitespace
      end

      state :ruby_line do
        # Need at top
        mixin :indented_block

        rule(/,\s*\n/) { delegate ruby }
        rule /[ ]\|[ \t]*\n/, Str::Escape
        rule(/.*?(?=(,$| \|)?[ \t]*$)/) { delegate ruby }
      end

      state :filter_block do
        rule /([^#\n]|#[^{\n]|(\\\\)*\\#\{)+/ do
          if @filter_lexer
            delegate @filter_lexer
          else
            token Name::Decorator
          end
        end

        mixin :interpolation
        mixin :indented_block
      end

      state :plain_block do
        mixin :interpolation

        rule %r((</?[\w\s\=\'\"]+?/?>)) do |m| # Dirty html
          delegate html, m[1]
        end

        #rule /([^#\n]|#[^{\n]|(\\\\)*\\#\{)+/ do
        rule /#{dot}+?/, Text

        mixin :indented_block
      end

      state :interpolation do
        rule /#[{]/, Str::Interpol, :ruby_interp
      end

      state :ruby_interp do
        rule /[}]/, Str::Interpol, :pop!
        mixin :ruby_interp_inner
      end

      state :ruby_interp_inner do
        rule(/[{]/) { delegate ruby; push :ruby_interp_inner }
        rule(/[}]/) { delegate ruby; pop! }
        rule(/[^{}]+/) { delegate ruby }
      end

      state :indented_block do
        rule(/(?<!\\)\n/) { token Text; reset_stack }
      end
    end
  end
end