📄 engine.rb
字号:
require File.dirname(__FILE__) + '/helpers'require File.dirname(__FILE__) + '/buffer'module Haml # This is the class where all the parsing and processing of the HAML # template is done. It can be directly used by the user by creating a # new instance and calling to_html to render the template. For example: # # template = File.load('templates/really_cool_template.haml') # haml_engine = Haml::Engine.new(template) # output = haml_engine.to_html # puts output class Engine # Allow access to the precompiled template attr_reader :precompiled # Allow reading and writing of the options hash attr :options, true # Designates an XHTML/XML element. ELEMENT = '%'[0] # Designates a <tt><div></tt> element with the given class. DIV_CLASS = '.'[0] # Designates a <tt><div></tt> element with the given id. DIV_ID = '#'[0] # Designates an XHTML/XML comment. COMMENT = '/'[0] # Designates an XHTML doctype. DOCTYPE = '!'[0] # Designates script, the result of which is output. SCRIPT = '='[0] # Designates script, the result of which is flattened and output. FLAT_SCRIPT = '~'[0] # Designates script which is run but not output. SILENT_SCRIPT = '-'[0] # When following SILENT_SCRIPT, designates a comment that is not output. SILENT_COMMENT = '#'[0] # Designates a non-parsed line. ESCAPE = '\\'[0] # Designates a non-parsed line. Not actually a character. PLAIN_TEXT = -1 # Keeps track of the ASCII values of the characters that begin a # specially-interpreted line. SPECIAL_CHARACTERS = [ ELEMENT, DIV_CLASS, DIV_ID, COMMENT, DOCTYPE, SCRIPT, FLAT_SCRIPT, SILENT_SCRIPT, ESCAPE ] # The value of the character that designates that a line is part # of a multiline string. MULTILINE_CHAR_VALUE = '|'[0] # Characters that designate that a multiline string may be about # to begin. MULTILINE_STARTERS = SPECIAL_CHARACTERS - ["/"[0]] # Keywords that appear in the middle of a Ruby block with lowered # indentation. If a block has been started using indentation, # lowering the indentation with one of these won't end the block. # For example: # # - if foo # %p yes! # - else # %p no! # # The block is ended after <tt>%p no!</tt>, because <tt>else</tt> # is a member of this array. MID_BLOCK_KEYWORDS = ['else', 'elsif', 'rescue', 'ensure', 'when'] # Creates a new instace of Haml::Engine that will compile the given # template string when <tt>to_html</tt> is called. # See REFERENCE for available options. # #-- # When adding options, remember to add information about them # to REFERENCE! #++ # def initialize(template, options = {}) @options = { :suppress_eval => false, :attr_wrapper => "'", :locals => {} }.merge options @precompiled = @options[:precompiled] @template = template.strip #String @to_close_stack = [] @output_tabs = 0 @template_tabs = 0 # This is the base tabulation of the currently active # flattened block. -1 signifies that there is no such block. @flat_spaces = -1 # Only do the first round of pre-compiling if we really need to. # They might be passing in the precompiled string. do_precompile if @precompiled.nil? && (@precompiled = String.new) end # Processes the template and returns the result as a string. def to_html(scope = Object.new, &block) @scope_object = scope @buffer = Haml::Buffer.new(@options) local_assigns = @options[:locals] # Get inside the view object's world @scope_object.instance_eval do # Set all the local assigns local_assigns.each do |key,val| self.class.send(:define_method, key) { val } end end # Compile the @precompiled buffer compile &block # Return the result string @buffer.buffer end private #Precompile each line def do_precompile push_silent <<-END def _haml_render _hamlout = @haml_stack[-1] _erbout = _hamlout.buffer END old_line = nil old_index = nil old_spaces = nil old_tabs = nil (@template + "\n-#").each_with_index do |line, index| spaces, tabs = count_soft_tabs(line) line = line.strip if !line.empty? if old_line block_opened = tabs > old_tabs && !line.empty? suppress_render = handle_multiline(old_tabs, old_line, old_index) if !suppress_render line_empty = old_line.empty? process_indent(old_tabs, old_line) unless line_empty flat = @flat_spaces != -1 if flat push_flat(old_line, old_spaces) elsif !line_empty process_line(old_line, old_index, block_opened) end end end old_line = line old_index = index old_spaces = spaces old_tabs = tabs elsif @flat_spaces != -1 push_flat(old_line, old_spaces) old_line = '' old_spaces = 0 end end # Close all the open tags @template_tabs.times { close } push_silent "end" end # Processes and deals with lowering indentation. def process_indent(count, line) if count <= @template_tabs && @template_tabs > 0 to_close = @template_tabs - count to_close.times do |i| offset = to_close - 1 - i unless offset == 0 && mid_block_keyword?(line) close end end end end # Processes a single line of HAML. # # This method doesn't return anything; it simply processes the line and # adds the appropriate code to <tt>@precompiled</tt>. def process_line(line, index, block_opened) case line[0] when DIV_CLASS, DIV_ID render_div(line, index) when ELEMENT render_tag(line, index) when COMMENT render_comment(line) when SCRIPT push_script(line[1..-1], false, block_opened, index) when FLAT_SCRIPT push_flat_script(line[1..-1], block_opened, index) when SILENT_SCRIPT sub_line = line[1..-1] unless sub_line[0] == SILENT_COMMENT push_silent(sub_line, index) if block_opened && !mid_block_keyword?(line) push_and_tabulate([:script]) end end when DOCTYPE if line[0...3] == '!!!' render_doctype(line) else push_text line end when ESCAPE push_text line[1..-1] else push_text line end end # Returns whether or not the line is a silent script line with one # of Ruby's mid-block keywords. def mid_block_keyword?(line) line.length > 2 && line[0] == SILENT_SCRIPT && MID_BLOCK_KEYWORDS.include?(line[1..-1].split[0]) end # Deals with all the logic of figuring out whether a given line is # the beginning, continuation, or end of a multiline sequence. # # This returns whether or not the line should be # rendered normally. def handle_multiline(count, line, index) suppress_render = false # Multilines are denoting by ending with a `|` (124) if is_multiline?(line) && @multiline_buffer # A multiline string is active, and is being continued @multiline_buffer += line[0...-1] suppress_render = true elsif is_multiline?(line) && (MULTILINE_STARTERS.include? line[0]) # A multiline string has just been activated, start adding the lines @multiline_buffer = line[0...-1] @multiline_count = count @multiline_index = index process_indent(count, line) suppress_render = true elsif @multiline_buffer # A multiline string has just ended, make line into the result unless line.empty? process_line(@multiline_buffer, @multiline_index, count > @multiline_count) @multiline_buffer = nil end end
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -