Class: NonHaml::NonHamlParser

Inherits:
Object
  • Object
show all
Defined in:
lib/non-haml/parser.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeNonHamlParser

Returns a new instance of NonHamlParser.



25
26
27
# File 'lib/non-haml/parser.rb', line 25

def initialize
  self.out = ""
end

Instance Attribute Details

#base_dirObject

Returns the value of attribute base_dir.



24
25
26
# File 'lib/non-haml/parser.rb', line 24

def base_dir
  @base_dir
end

#last_ok_lineObject

Returns the value of attribute last_ok_line.



24
25
26
# File 'lib/non-haml/parser.rb', line 24

def last_ok_line
  @last_ok_line
end

#outObject

Returns the value of attribute out.



24
25
26
# File 'lib/non-haml/parser.rb', line 24

def out
  @out
end

Instance Method Details

#concat(spaces = nil, text = nil) ⇒ Object



29
30
31
32
33
34
35
36
37
# File 'lib/non-haml/parser.rb', line 29

def concat spaces=nil, text=nil
  if spaces.nil? and text.nil?
    self.out << "\n"
  else
    text.to_s.lines do |l|
      self.out << "#{' '*spaces}#{l.rstrip}\n"
    end
  end
end

#control_indentObject



67
68
69
# File 'lib/non-haml/parser.rb', line 67

def control_indent
  '  ' * (@block_starts.length + @base_control_indent)
end

#current_filenameObject



53
54
55
# File 'lib/non-haml/parser.rb', line 53

def current_filename
  "#{base_dir}#{filename}"
end

#dedent(indent, suppress_end = false) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/non-haml/parser.rb', line 71

def dedent indent, suppress_end=false
  dedented = false
  @block_starts.reverse.take_while{|x| x >= indent}.each do |x|
    # Close some blocks to get back to the right indentation level.
    @block_starts.pop
    unless suppress_end and %w{if elsif else}.include?(@statements.pop) and x == indent
      store control_indent + 'end'
    end
    dedented = true  # return status so we know whether to skip a blank line.
  end
  dedented
end

#evaluate(code) ⇒ Object



170
171
172
# File 'lib/non-haml/parser.rb', line 170

def evaluate(code)
  eval(code, @context)
end

#filenameObject



39
40
41
42
# File 'lib/non-haml/parser.rb', line 39

def filename
  # Retrieves the current filename.
  @filenames.last
end

#generate(out_name, in_name, context_or_vars, base_dir, verbose) ⇒ Object



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
226
# File 'lib/non-haml/parser.rb', line 190

def generate out_name, in_name, context_or_vars, base_dir, verbose
  self.base_dir = base_dir
  push_filename(in_name)

  context = prepare_context(context_or_vars)
  source = File.read(current_filename)
  parsed = parse(source)

  if verbose
    parsed.lines.each_with_index do |l,i|
      print Color.blue '%3d  ' % (i + 1)
      puts l
    end
  end

  begin
    evaluate(parsed)
  rescue Exception => e
    # Interrupt everything, give more info, then dump out the old exception.
    $stderr.puts "In #{current_filename}:"
    $stderr.puts Color.red " #{e.class.name}: #{Color.blue e.to_s}"
    File.read(current_filename).lines.each_with_index.drop([last_ok_line - 2, 0].max).first(5).each do |line,i|
      if i == last_ok_line
        $stderr.print Color.red ' %3d  ' % (i + 1)
        $stderr.print Color.red line
      else
        $stderr.print ' %3d  ' % (i + 1)
        $stderr.print line
      end
    end
    raise e
  else
    File.open(out_name, "w") do |f|
      f.write(out)
    end
  end
end

#parse(text, base_control_indent = 0, base_indent = 0) ⇒ Object



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
# File 'lib/non-haml/parser.rb', line 57

def parse text, base_control_indent=0, base_indent=0
  @s = ""
  def store text
    @s << "#{text}\n"
  end

  # Number of indents at the start of blocks.
  @block_starts = []
  @statements = []
  @base_control_indent = base_control_indent
  def control_indent
    '  ' * (@block_starts.length + @base_control_indent)
  end

  def dedent indent, suppress_end=false
    dedented = false
    @block_starts.reverse.take_while{|x| x >= indent}.each do |x|
      # Close some blocks to get back to the right indentation level.
      @block_starts.pop
      unless suppress_end and %w{if elsif else}.include?(@statements.pop) and x == indent
        store control_indent + 'end'
      end
      dedented = true  # return status so we know whether to skip a blank line.
    end
    dedented
  end

  text.lines.with_index do |line,i|
    line.rstrip!

    line =~ /^( *)(.*)$/
    indent, line = $1.length, $2
    indent += base_indent

    if (line =~ /^- *((if|unless|for|elsif|else)\b.*)$/) or (line =~ /^- *(.*\bdo\b *(|.*|)?)$/)
      # Entering a block.

      if %w{elsif else}.include? $2
        store control_indent + "self.last_ok_line = #{i}"
        dedent indent, %w{elsif else}.include?($2)
      else
        dedent indent, %w{elsif else}.include?($2)
        store control_indent + "self.last_ok_line = #{i}"
      end

      store control_indent + $1
      @block_starts << indent
      @statements << $2
      # Output should have same indent as block.
      concat_indent = indent
    elsif line =~ /= ?non_haml ['"](.*)['"]/
      store control_indent + "self.last_ok_line = #{i}"
      file = base_dir + $1
      if File.readable? file
        store control_indent + "push_filename '#{$1}'"
        @s += parse open(file).read, control_indent.length, indent
      else
        store control_indent + "raise Errno::ENOENT, '\"#{$1}\"'"
      end
      store control_indent + "pop_filename"
    elsif line.strip.length.zero?
      # Blank line. Output and move on. Don't change indent. Only do this
      # for if blocks though, so 'if false' doesn't generate optional blank
      # line and 'if true' does.
      #if @statements.last == 'if' or @statements.empty?
      # XXX disabled temporarily because it sucked.
      store control_indent + "self.last_ok_line = #{i}"
      #if @statements.empty?
        store "#{control_indent}concat"
      #end
    else
      dedented = dedent indent
      store control_indent + "self.last_ok_line = #{i}"

      # Now deal with whatever we have left.
      if line =~ /^- *(.*)$/
        # Generic Ruby statement that isn't entering/leaving a block.
        store control_indent + $1
      elsif line =~ /^= *(.*)$/
        # Concatenate this for evaluation.
        target_indent = indent - control_indent.length
        # Deal with blank lines.
        content = $1
        content = '""' if content.empty?
        store "#{control_indent}concat(#{target_indent}, (#{content}))"
      elsif dedented and line.strip.empty?
        puts 'skipping'
        # Skip up to one blank line after dedenting.
        next
      else
        # Concatenate this for output.
        target_indent = indent - control_indent.length
        # Replace #{} blocks, but completely quote the rest.
        # TODO clean up more nicely if we fail here!!!
        to_sub = []
        line.gsub!('%', '%%')
        line.gsub!(/#\{(.*?)\}/){to_sub << $1; '%s'}
        if to_sub.empty?
          # Must do pretend substitutions to get around %% characters.
          subst = " % []"
        else
          # Include brackets around all quantities, so bracket-free
          # functions get the right arguments.
          subst = " % [#{to_sub.map{|x| "(#{x})"}.join ', '}]"
        end
        store "#{control_indent}concat #{target_indent}, (%q##{line.gsub('#', '\\#')}##{subst})"
      end
    end
  end
  dedent 0
  @s
end

#pop_filenameObject



49
50
51
# File 'lib/non-haml/parser.rb', line 49

def pop_filename
  @filenames.pop
end

#prepare_context(_context_or_vars) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/non-haml/parser.rb', line 174

def prepare_context(_context_or_vars)
  case _context_or_vars
  when Hash
    @context = binding
    _context_or_vars.each do |name, value|
      evaluate("#{name} = nil")
      setter = evaluate("lambda{|v| #{name} = v}")
      setter.call(value)
    end
  else
    raise TypeError, "cannot use context of type #{_context_or_vars.class}"
  end
  evaluate("concat = nil")
  setter = evaluate("lambda{|v| concat = v}")
end

#push_filename(new_name) ⇒ Object



44
45
46
47
# File 'lib/non-haml/parser.rb', line 44

def push_filename new_name
  @filenames ||= []
  @filenames << new_name
end

#store(text) ⇒ Object



59
60
61
# File 'lib/non-haml/parser.rb', line 59

def store text
  @s << "#{text}\n"
end