We got nominated! Help us out and vote for GitHub as Best Bootstrapped Startup of 2008. (You can vote once a day.) [ hide ]

public
Rubygem
Fork of nex3/haml
Description: HTML Abstraction Markup Language - A Markup Haiku
Homepage: http://haml.hamptoncatlin.com
Clone URL: git://github.com/chriseppstein/haml.git
nex3 (author)
Sun Dec 10 13:46:06 -0800 2006
commit  ab9c210dacb60234ea22f8462b88526b5d75ee6f
tree    eea38410329b935b0c19a6549be4539e58f4d63d
parent  72315473a337bd362c480a58e839a6cb75382637
haml / lib / haml / buffer.rb
100644 225 lines (200 sloc) 6.698 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
module Haml
  # This class is used only internally. It holds the buffer of XHTML that
  # is eventually output by Haml::Engine's to_html method. It's called
  # from within the precompiled code, and helps reduce the amount of
  # processing done within instance_eval'd code.
  class Buffer
    include Haml::Helpers
 
    # Set the maximum length for a line to be considered a one-liner.
    # Lines <= the maximum will be rendered on one line,
    # i.e. <tt><p>Hello world</p></tt>
    ONE_LINER_LENGTH = 50
 
    # The string that holds the compiled XHTML. This is aliased as
    # _erbout for compatibility with ERB-specific code.
    attr_accessor :buffer
 
    # The number of tabs that are added or subtracted from the
    # tabulation proscribed by the precompiled template.
    attr_accessor :tabulation
 
    # Creates a new buffer.
    def initialize(options = {})
      @options = options
      @quote_escape = options[:attr_wrapper] == '"' ? "&quot;" : "&apos;"
      @other_quote_char = options[:attr_wrapper] == '"' ? "'" : '"'
      @buffer = ""
      @one_liner_pending = false
      @tabulation = 0
    end
 
    # Renders +text+ with the proper tabulation. This also deals with
    # making a possible one-line tag one line or not.
    def push_text(text, tabulation, flattened = false)
      if flattened
        # In this case, tabulation is the number of spaces, rather
        # than the number of tabs.
        @buffer << "#{' ' * tabulation}#{flatten(text + "\n")}"
        @one_liner_pending = true
      elsif @one_liner_pending && one_liner?(text)
        @buffer << text
      else
        if @one_liner_pending
          @buffer << "\n"
          @one_liner_pending = false
        end
        @buffer << "#{tabs(tabulation)}#{text}\n"
      end
    end
 
    # Properly formats the output of a script that was run in the
    # instance_eval.
    def push_script(result, tabulation, flattened)
      if flattened
        result = find_and_flatten(result)
      end
      unless result.nil?
        result = result.to_s
        while result[-1] == 10 # \n
          # String#chomp is slow
          result = result[0...-1]
        end
        
        result = result.gsub("\n", "\n#{tabs(tabulation)}")
        push_text result, tabulation
      end
      nil
    end
 
    # Takes the various information about the opening tag for an
    # element, formats it, and adds it to the buffer.
    def open_tag(name, tabulation, atomic, try_one_line, class_id, attributes_hash, obj_ref, flattened)
      attributes = {}
      attributes.merge!(parse_object_ref(obj_ref)) if obj_ref
      attributes.merge!(parse_class_and_id(class_id)) unless class_id.nil? || class_id.empty?
      attributes.merge!(attributes_hash) if attributes_hash
 
      @one_liner_pending = false
      if atomic
        str = " />\n"
      elsif try_one_line
        @one_liner_pending = true
        str = ">"
      elsif flattened
        str = ">&#x000A;"
      else
        str = ">\n"
      end
      @buffer << "#{tabs(tabulation)}<#{name}#{build_attributes(attributes)}#{str}"
    end
 
    # Creates a closing tag with the given name.
    def close_tag(name, tabulation)
      if @one_liner_pending
        @buffer << "</#{name}>\n"
        @one_liner_pending = false
      else
        push_text("</#{name}>", tabulation)
      end
    end
 
    # Opens an XHTML comment.
    def open_comment(try_one_line, conditional, tabulation)
      conditional << ">" if conditional
      @buffer << "#{tabs(tabulation)}<!--#{conditional.to_s} "
      if try_one_line
        @one_liner_pending = true
      else
        @buffer << "\n"
      end
    end
 
    # Closes an XHTML comment.
    def close_comment(has_conditional, tabulation)
      close_tag = has_conditional ? "<![endif]-->" : "-->"
      if @one_liner_pending
        @buffer << " #{close_tag}\n"
        @one_liner_pending = false
      else
        push_text(close_tag, tabulation)
      end
    end
    
    # Stops parsing a flat section.
    def stop_flat
      buffer.concat("\n")
      @one_liner_pending = false
    end
 
    private
 
    # Gets <tt>count</tt> tabs. Mostly for internal use.
    def tabs(count)
      ' ' * (count + @tabulation)
    end
 
    # Iterates through the classes and ids supplied through <tt>.</tt>
    # and <tt>#</tt> syntax, and returns a hash with them as attributes,
    # that can then be merged with another attributes hash.
    def parse_class_and_id(list)
      attributes = {}
      list.scan(/([#.])([-_a-zA-Z0-9]+)/) do |type, property|
        case type
        when '.'
          if attributes[:class]
            attributes[:class] += " "
          else
            attributes[:class] = ""
          end
          attributes[:class] += property
        when '#'
          attributes[:id] = property
        end
      end
      attributes
    end
 
    # Takes an array of objects and uses the class and id of the first
    # one to create an attributes hash.
    def parse_object_ref(ref)
      ref = ref[0]
      # Let's make sure the value isn't nil. If it is, return the default Hash.
      return {} if ref.nil?
      class_name = ref.class.to_s.underscore
      {:id => "#{class_name}_#{ref.id}", :class => class_name}
    end
 
    # Takes a hash and builds a list of XHTML attributes from it, returning
    # the result.
    def build_attributes(attributes = {})
      result = attributes.collect do |a,v|
        unless v.nil?
          v = v.to_s
          attr_wrapper = @options[:attr_wrapper]
          if v.include? attr_wrapper
            if v.include? @other_quote_char
              v = v.gsub(attr_wrapper, @quote_escape)
            else
              attr_wrapper = @other_quote_char
            end
          end
          " #{a}=#{attr_wrapper}#{v}#{attr_wrapper}"
        end
      end
      result.sort.join
    end
 
    # Returns whether or not the given value is short enough to be rendered
    # on one line.
    def one_liner?(value)
      value.length <= ONE_LINER_LENGTH && value.scan(/\n/).empty?
    end
 
    # Isolates the whitespace-sensitive tags in the string and uses Haml::Helpers#flatten
    # to convert any endlines inside them into html entities.
    def find_and_flatten(input)
      input = input.to_s
      input.scan(/<(textarea|code|pre)[^>]*>(.*?)<\/\1>/im) do |tag, contents|
        input = input.gsub(contents, Haml::Helpers.flatten(contents))
      end
      input
    end
  end
end
 
class String # :nodoc
  alias_method :old_comp, :<=>
  def <=>(other)
    if other.is_a? NilClass
      -1
    else
      old_comp(other)
    end
  end
end
 
class NilClass # :nodoc:
  include Comparable
  
  def <=>(other)
    other.nil? ? 0 : 1
  end
end