# -*- coding: utf-8 -*- # # frozen_string_literal: true module Rouge module Lexers # A lexer for the Haml templating system for Ruby. # @see http://haml.info class Haml < RegexLexer include Indentation title "Haml" desc "The Haml templating system for Ruby (haml.info)" tag 'haml' aliases 'HAML' filenames '*.haml' mimetypes 'text/x-haml' option 'filters[filter_name]', 'Mapping of lexers to use for haml :filters' attr_reader :filters # @option opts :filters # A hash of filter name to lexer of how various filters should be # highlighted. By default, :javascript, :css, :ruby, and :erb # are supported. def initialize(opts={}) super default_filters = { 'javascript' => Javascript.new(options), 'css' => CSS.new(options), 'ruby' => ruby, 'erb' => ERB.new(options), 'markdown' => Markdown.new(options), 'sass' => Sass.new(options), # TODO # 'textile' => Textile.new(options), # 'maruku' => Maruku.new(options), } @filters = hash_option(:filters, default_filters) do |v| as_lexer(v) || PlainText.new(@options) end end def ruby @ruby ||= Ruby.new(@options) end def html @html ||= HTML.new(@options) end def ruby!(state) ruby.reset! push state end start { ruby.reset!; html.reset! } identifier = /[\w:-]+/ ruby_var = /[a-z]\w*/ # Haml can include " |\n" anywhere, # which is ignored and used to wrap long lines. # To accomodate this, use this custom faux dot instead. dot = /[ ]\|\n(?=.*[ ]\|)|./ state :root do rule %r/\s*\n/, Text rule(/\s*/) { |m| token Text; indentation(m[0]) } end state :content do mixin :css rule(/%#{identifier}/) { token Name::Tag; goto :tag } rule %r/!!!#{dot}*\n/, Name::Namespace, :pop! rule %r( (/) (\[#{dot}*?\]) (#{dot}*\n) )x do groups Comment, Comment::Special, Comment pop! end rule %r(/#{dot}*\n) do token Comment pop! starts_block :html_comment_block end rule %r/-##{dot}*\n/ do token Comment pop! starts_block :haml_comment_block end rule %r/-/ do token Punctuation reset_stack ruby! :ruby_line end # filters rule %r/:(#{dot}*)\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 " haml: filter #{filter_name.inspect} #{@filter_lexer.inspect}" if @debug end mixin :eval_or_plain end state :css do rule(/\.#{identifier}/) { token Name::Class; goto :tag } rule(/##{identifier}/) { token Name::Function; goto :tag } end state :tag do mixin :css rule(/[{]/) { token Punctuation; ruby! :ruby_tag } rule(/\[#{dot}*?\]/) { delegate ruby } rule %r/\(/, Punctuation, :html_attributes rule %r/\s*\n/, Text, :pop! # whitespace chompers rule %r/[<>]{1,2}(?=[ \t=])/, Punctuation mixin :eval_or_plain end state :plain do rule(/([^#\n]|#[^{\n]|(\\\\)*\\#\{)+/) { delegate html } mixin :interpolation rule(/\n/) { token Text; reset_stack } end state :eval_or_plain do rule %r/[&!]?==/, Punctuation, :plain rule %r/[&!]?[=!]/ do token Punctuation reset_stack ruby! :ruby_line end rule(//) { push :plain } end state :ruby_line do rule %r/\n/, Text, :pop! rule(/,[ \t]*\n/) { delegate ruby } rule %r/[ ]\|[ \t]*\n/, Str::Escape rule(/.*?(?=(,$| \|)?[ \t]*$)/) { delegate ruby } end state :ruby_tag do mixin :ruby_inner end state :html_attributes do rule %r/\s+/, Text rule %r/#{identifier}\s*=/, Name::Attribute, :html_attribute_value rule identifier, Name::Attribute rule %r/\)/, Text, :pop! end state :html_attribute_value do rule %r/\s+/, Text rule ruby_var, Name::Variable, :pop! rule %r/@#{ruby_var}/, Name::Variable::Instance, :pop! rule %r/\$#{ruby_var}/, Name::Variable::Global, :pop! rule %r/'(\\\\|\\'|[^'\n])*'/, Str, :pop! rule %r/"(\\\\|\\"|[^"\n])*"/, Str, :pop! end state :html_comment_block do rule %r/#{dot}+/, Comment mixin :indented_block end state :haml_comment_block do rule %r/#{dot}+/, Comment::Preproc mixin :indented_block end state :filter_block do rule %r/([^#\n]|#[^{\n]|(\\\\)*\\#\{)+/ do if @filter_lexer delegate @filter_lexer else token Name::Decorator end end mixin :interpolation mixin :indented_block end state :interpolation do rule %r/#[{]/, Str::Interpol, :ruby end state :ruby do rule %r/[}]/, Str::Interpol, :pop! mixin :ruby_inner end state :ruby_inner do rule(/[{]/) { delegate ruby; push :ruby_inner } rule(/[}]/) { delegate ruby; pop! } rule(/[^{}]+/) { delegate ruby } end state :indented_block do rule(/\n/) { token Text; reset_stack } end end end end