Class: Markascend::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/markascend/parser.rb

Constant Summary collapse

REC_START =
/\A[\+\-\>]\ /
NON_PARA_START =
/
  ^[\+\-\>]\                   # rec block
  |
  ^\|\ *(?!\d)\w*\ *$          # block code
  |
  ^h[1-6](?:\#\w+(?:-\w+)*)?\  # header
/x
REC_BLOCK_STARTS =
{
  '+ ' => /\+\ /,
  '- ' => /\-\ /,
  '> ' => /\>\ /
}

Instance Method Summary collapse

Constructor Details

#initialize(env, src) ⇒ Parser

Returns a new instance of Parser.



17
18
19
20
21
22
# File 'lib/markascend/parser.rb', line 17

def initialize env, src
  @src = StringScanner.new src
  @env = env
  @top_level = @env.srcs.empty?
  @env.srcs.push @src
end

Instance Method Details

#parseObject



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
# File 'lib/markascend/parser.rb', line 24

def parse
  @out = []
  while parse_new_line or parse_rec_block or parse_hx or parse_block_code or parse_paragraph
  end
  unless @src.eos?
    @env.warn 'reached end of input'
  end
  @env.srcs.pop

  @out.map! do |(node, content)|
    case node
    when :footnode_id_ref
      if content < 1 or content > @env.footnotes.size
        raise "footnote not defined: #{content}"
      end
      %Q|<a href="#footnote-#{content}">#{content}</a>|
    when :footnode_acronym_ref
      unless index = @env.footnotes.find_index{|k, _| k == content }
        raise "footnote note defined: #{content}"
      end
      %Q|<a href="#footnote-#{index + 1}">#{content}</a>|
    else
      node
    end
  end
  @out.join
end

#parse_block_codeObject



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/markascend/parser.rb', line 147

def parse_block_code
  if lang = @src.scan(/\|\ *(?!\d)\w*\ *\n/)
    lang = lang[1..-1].strip
    if lang.empty? and @env.hi
      lang = @env.hi
    end
    block = @src.scan(/
      (
        \ *\n      # empty line
      |
        \ {2,}.*\n # line indented equal to 2 or more than 2
      )*
    /x)
    block.gsub!(/^  /, '')
    block.rstrip!
    @out << (::Markascend.hilite block, lang)
    true
  end
end

#parse_hxObject



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
# File 'lib/markascend/parser.rb', line 105

def parse_hx
  hx = @src.scan /h[1-6](\#\w+(-\w+)*)?\ /
  return unless hx
  hx.strip!

  # fiddle id
  if @env.sandbox
    if @env.toc
      id = "-#{@env.toc.size}"
    end
  else
    if hx.size > 2
      id = hx[3..-1]
    elsif @env.toc
      id = "-#{@env.toc.size}"
    end
  end
  if id
    id_attr = %Q{ id="#{id}"}
  end
  hx = hx[0...2]

  @out << "<#{hx}#{id_attr}>"
  line, block = scan_line_and_block
  if line
    out = []
    LineUnit.new(@env, line, block).parse(out)
    out.pop if out.last == :"<br>"
    out.each do |token|
      @out << token
    end
    if id and @env.toc
      @env.toc[id] = [hx[1].to_i, out.join]
    end
  end
  @out << "</#{hx}>"

  # consume one more empty line if possible
  @src.scan /\ *\n/ if (!block or block.empty?)
  true
end

#parse_new_lineObject



56
57
58
59
60
61
# File 'lib/markascend/parser.rb', line 56

def parse_new_line
  if @src.scan(/\ *\n/)
    @out << '<br>'
    true
  end
end

#parse_paragraphObject



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/markascend/parser.rb', line 167

def parse_paragraph
  @src.match?(/\ */)
  indent = @src.matched_size
  line, block = scan_line_and_block
  if line
    @out << :"<p>" if @top_level
    LineUnit.new(@env, line, block).parse(@out)

    # same indent and not matching rec/code blocks
    while (@src.match?(/\ */); @src.matched_size) == indent and !@src.match?(NON_PARA_START)
      line, block = scan_line_and_block
      break unless line
      LineUnit.new(@env, line, block).parse(@out)
    end
    # consume one more empty line if possible
    @src.scan /\ *\n/
    # delete back last <br>
    @out.pop if @out.last == :"<br>"
    @out << :"</p>" if @top_level
    true
  end
end

#parse_rec_blockObject



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
# File 'lib/markascend/parser.rb', line 63

def parse_rec_block
  return unless @src.match? REC_START

  # first elem, scans the whole of following string:
  # |
  #   + line 1 of the li. an embed \macro
  #       macro content
  #     line 2 of the li.
  #     line 3 of the li.
  #
  # NOTE that first line is always not indented
  line, block = scan_line_and_block 2
  return unless line
  rec_start = line[REC_START]
  wrapper_begin, elem_begin, elem_end, wrapper_end =
    case rec_start
    when '+ '; ['<ol>', '<li>', '</li>', '</ol>']
    when '- '; ['<ul>', '<li>', '</li>', '</ul>']
    when '> '; ['', '<quote>', '</quote>', '']
    end
  elems = ["#{line[2..-1]}#{block}"]

  # followed up elems
  block_start_re = REC_BLOCK_STARTS[rec_start]
  while @src.match?(block_start_re)
    line, block = scan_line_and_block 2
    break unless line
    elems << "#{line[2..-1]}#{block}"
  end

  # generate
  @out << wrapper_begin
  elems.each do |elem|
    @out << elem_begin
    elem.rstrip!
    @out << Parser.new(@env, elem).parse
    @out << elem_end
  end
  @out << wrapper_end
  true
end

#scan_line_and_block(undent = :all) ⇒ 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
# File 'lib/markascend/parser.rb', line 190

def scan_line_and_block undent=:all
  if line = @src.scan(/.+?(?:\n|\z)/)
    block = @src.scan(/
      (?:\ *\n)*        # leading empty lines
      (?<indent>\ {2,})
      .*?(?:\n|\z)      # first line content
      (
        \g<indent>\ *
        .*?(?:\n|\z)    # rest line content
        |
        (?:\ *\n)*      # rest empty line
      )*
    /x)
    block = nil if block =~ /\A\s*\z/
    # undent block
    if block
      if undent == :all
        /
          (?:\ *\n)*
          (?<indent>\ {2,})
        /x =~ block
        block.gsub! /^#{indent}/, ''
      else
        block.gsub! /^\ \ /, ''
      end
    end
  end
  [line, block]
end

#warningsObject



52
53
54
# File 'lib/markascend/parser.rb', line 52

def warnings
  @env.warnings
end