amp-front 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/.document +5 -0
  2. data/.gitignore +24 -0
  3. data/Ampfile +3 -0
  4. data/Gemfile +10 -0
  5. data/Gemfile.lock +36 -0
  6. data/LICENSE +20 -0
  7. data/README.md +50 -0
  8. data/Rakefile +64 -0
  9. data/VERSION +1 -0
  10. data/design_docs/commands.md +91 -0
  11. data/design_docs/dependencies.md +35 -0
  12. data/design_docs/plugins.md +47 -0
  13. data/features/amp.feature +8 -0
  14. data/features/amp_help.feature +36 -0
  15. data/features/amp_plugin_list.feature +10 -0
  16. data/features/step_definitions/amp-front_steps.rb +23 -0
  17. data/features/support/env.rb +4 -0
  18. data/lib/amp-front.rb +30 -0
  19. data/lib/amp-front/dispatch/commands/base.rb +158 -0
  20. data/lib/amp-front/dispatch/commands/builtin/help.rb +23 -0
  21. data/lib/amp-front/dispatch/commands/builtin/plugin.rb +24 -0
  22. data/lib/amp-front/dispatch/commands/validations.rb +171 -0
  23. data/lib/amp-front/dispatch/runner.rb +86 -0
  24. data/lib/amp-front/help/entries/__default__.erb +31 -0
  25. data/lib/amp-front/help/entries/ampfiles.md +42 -0
  26. data/lib/amp-front/help/entries/commands.erb +6 -0
  27. data/lib/amp-front/help/entries/new-commands.md +81 -0
  28. data/lib/amp-front/help/help.rb +312 -0
  29. data/lib/amp-front/plugins/base.rb +87 -0
  30. data/lib/amp-front/support/module_extensions.rb +92 -0
  31. data/lib/amp-front/third_party/maruku.rb +136 -0
  32. data/lib/amp-front/third_party/maruku/attributes.rb +227 -0
  33. data/lib/amp-front/third_party/maruku/defaults.rb +71 -0
  34. data/lib/amp-front/third_party/maruku/errors_management.rb +92 -0
  35. data/lib/amp-front/third_party/maruku/helpers.rb +260 -0
  36. data/lib/amp-front/third_party/maruku/input/charsource.rb +326 -0
  37. data/lib/amp-front/third_party/maruku/input/extensions.rb +69 -0
  38. data/lib/amp-front/third_party/maruku/input/html_helper.rb +189 -0
  39. data/lib/amp-front/third_party/maruku/input/linesource.rb +111 -0
  40. data/lib/amp-front/third_party/maruku/input/parse_block.rb +615 -0
  41. data/lib/amp-front/third_party/maruku/input/parse_doc.rb +234 -0
  42. data/lib/amp-front/third_party/maruku/input/parse_span_better.rb +746 -0
  43. data/lib/amp-front/third_party/maruku/input/rubypants.rb +225 -0
  44. data/lib/amp-front/third_party/maruku/input/type_detection.rb +147 -0
  45. data/lib/amp-front/third_party/maruku/input_textile2/t2_parser.rb +163 -0
  46. data/lib/amp-front/third_party/maruku/maruku.rb +33 -0
  47. data/lib/amp-front/third_party/maruku/output/to_ansi.rb +223 -0
  48. data/lib/amp-front/third_party/maruku/output/to_html.rb +991 -0
  49. data/lib/amp-front/third_party/maruku/output/to_markdown.rb +164 -0
  50. data/lib/amp-front/third_party/maruku/output/to_s.rb +56 -0
  51. data/lib/amp-front/third_party/maruku/string_utils.rb +191 -0
  52. data/lib/amp-front/third_party/maruku/structures.rb +167 -0
  53. data/lib/amp-front/third_party/maruku/structures_inspect.rb +87 -0
  54. data/lib/amp-front/third_party/maruku/structures_iterators.rb +61 -0
  55. data/lib/amp-front/third_party/maruku/textile2.rb +1 -0
  56. data/lib/amp-front/third_party/maruku/toc.rb +199 -0
  57. data/lib/amp-front/third_party/maruku/usage/example1.rb +33 -0
  58. data/lib/amp-front/third_party/maruku/version.rb +40 -0
  59. data/lib/amp-front/third_party/trollop.rb +766 -0
  60. data/spec/amp-front_spec.rb +25 -0
  61. data/spec/command_specs/base_spec.rb +123 -0
  62. data/spec/command_specs/command_spec.rb +97 -0
  63. data/spec/command_specs/help_spec.rb +33 -0
  64. data/spec/command_specs/spec_helper.rb +37 -0
  65. data/spec/command_specs/validations_spec.rb +267 -0
  66. data/spec/dispatch_specs/runner_spec.rb +116 -0
  67. data/spec/dispatch_specs/spec_helper.rb +15 -0
  68. data/spec/help_specs/help_entry_spec.rb +78 -0
  69. data/spec/help_specs/help_registry_spec.rb +77 -0
  70. data/spec/help_specs/spec_helper.rb +15 -0
  71. data/spec/plugin_specs/base_spec.rb +36 -0
  72. data/spec/plugin_specs/spec_helper.rb +15 -0
  73. data/spec/spec.opts +1 -0
  74. data/spec/spec_helper.rb +33 -0
  75. data/spec/support_specs/module_extensions_spec.rb +104 -0
  76. data/spec/support_specs/spec_helper.rb +15 -0
  77. data/test/third_party_tests/test_trollop.rb +1181 -0
  78. metadata +192 -0
@@ -0,0 +1,69 @@
1
+ module MaRuKu; module In; module Markdown
2
+
3
+
4
+ # Hash Fixnum -> name
5
+ SpanExtensionsTrigger = {}
6
+
7
+
8
+ class SpanExtension
9
+ # trigging chars
10
+ attr_accessor :chars
11
+ # trigging regexp
12
+ attr_accessor :regexp
13
+ # lambda
14
+ attr_accessor :block
15
+ end
16
+
17
+ # Hash String -> Extension
18
+ SpanExtensions = {}
19
+
20
+ def check_span_extensions(src, con)
21
+ c = src.cur_char
22
+ if extensions = SpanExtensionsTrigger[c]
23
+ extensions.each do |e|
24
+ if e.regexp && (match = src.next_matches(e.regexp))
25
+ return true if e.block.call(doc, src, con)
26
+ end
27
+ end
28
+ end
29
+ return false # not special
30
+ end
31
+
32
+ def self.register_span_extension(args)
33
+ e = SpanExtension.new
34
+ e.chars = [*args[:chars]]
35
+ e.regexp = args[:regexp]
36
+ e.block = args[:handler] || raise("No blocks passed")
37
+ e.chars.each do |c|
38
+ (SpanExtensionsTrigger[c] ||= []).push e
39
+ end
40
+ end
41
+
42
+ def self.register_block_extension(args)
43
+ regexp = args[:regexp]
44
+ BlockExtensions[regexp] = (args[:handler] || raise("No blocks passed"))
45
+ end
46
+
47
+ # Hash Regexp -> Block
48
+ BlockExtensions = {}
49
+
50
+ def check_block_extensions(src, con, line)
51
+ BlockExtensions.each do |reg, block|
52
+ if m = reg.match(line)
53
+ block = BlockExtensions[reg]
54
+ accepted = block.call(doc, src, con)
55
+ return true if accepted
56
+ end
57
+ end
58
+ return false # not special
59
+ end
60
+
61
+ def any_matching_block_extension?(line)
62
+ BlockExtensions.each_key do |reg|
63
+ m = reg.match(line)
64
+ return m if m
65
+ end
66
+ return false
67
+ end
68
+
69
+ end end end
@@ -0,0 +1,189 @@
1
+ #--
2
+ # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
3
+ #
4
+ # This file is part of Maruku.
5
+ #
6
+ # Maruku is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Maruku is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Maruku; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
+ #++
20
+
21
+
22
+ module MaRuKu; module In; module Markdown; module SpanLevelParser
23
+
24
+ # This class helps me read and sanitize HTML blocks
25
+
26
+ # I tried to do this with REXML, but wasn't able to. (suggestions?)
27
+
28
+ class HTMLHelper
29
+ include MaRuKu::Strings
30
+
31
+ Tag = %r{^<(/)?(\w+)\s*([^>]*)>}m
32
+ PartialTag = %r{^<.*}m
33
+
34
+ EverythingElse = %r{^[^<]+}m
35
+ CommentStart = %r{^<!--}x
36
+ CommentEnd = %r{^.*-->}
37
+ TO_SANITIZE = ['img','hr','br']
38
+
39
+ attr_reader :rest
40
+
41
+ def my_debug(s)
42
+ # puts "---"*10+"\n"+inspect+"\t>>>\t"s
43
+ end
44
+
45
+ def initialize
46
+ @rest = ""
47
+ @tag_stack = []
48
+ @m = nil
49
+ @already = ""
50
+ self.state = :inside_element
51
+ end
52
+
53
+ attr_accessor :state # = :inside_element, :inside_tag, :inside_comment,
54
+
55
+ def eat_this(line)
56
+ @rest = line + @rest
57
+ things_read = 0
58
+ until @rest.empty?
59
+ case self.state
60
+ when :inside_comment
61
+ if @m = CommentEnd.match(@rest)
62
+ @already += @m.pre_match + @m.to_s
63
+ @rest = @m.post_match
64
+ self.state = :inside_element
65
+ else
66
+ @already += @rest
67
+ @rest = ""
68
+ self.state = :inside_comment
69
+ end
70
+ when :inside_element
71
+ if @m = CommentStart.match(@rest)
72
+ things_read += 1
73
+ @already += @m.pre_match + @m.to_s
74
+ @rest = @m.post_match
75
+ self.state = :inside_comment
76
+ elsif @m = Tag.match(@rest) then
77
+ my_debug "#{@state}: Tag: #{@m.to_s.inspect}"
78
+ things_read += 1
79
+ handle_tag
80
+ self.state = :inside_element
81
+ elsif @m = PartialTag.match(@rest) then
82
+ my_debug "#{@state}: PartialTag: #{@m.to_s.inspect}"
83
+ @already += @m.pre_match
84
+ @rest = @m.post_match
85
+ @partial_tag = @m.to_s
86
+ self.state = :inside_tag
87
+ elsif @m = EverythingElse.match(@rest)
88
+ my_debug "#{@state}: Everything: #{@m.to_s.inspect}"
89
+ @already += @m.pre_match + @m.to_s
90
+ @rest = @m.post_match
91
+ self.state = :inside_element
92
+ else
93
+ error "Malformed HTML: not complete: #{@rest.inspect}"
94
+ end
95
+ when :inside_tag
96
+ if @m = /^[^>]*>/.match(@rest) then
97
+ my_debug "#{@state}: inside_tag: matched #{@m.to_s.inspect}"
98
+ @partial_tag += @m.to_s
99
+ my_debug "#{@state}: inside_tag: matched TOTAL: #{@partial_tag.to_s.inspect}"
100
+ @rest = @partial_tag + @m.post_match
101
+ @partial_tag = nil
102
+ self.state = :inside_element
103
+ else
104
+ @partial_tag += @rest
105
+ @rest = ""
106
+ self.state = :inside_tag
107
+ end
108
+ else
109
+ raise "Bug bug: state = #{self.state.inspect}"
110
+ end # not inside comment
111
+
112
+ # puts inspect
113
+ # puts "Read: #{@tag_stack.inspect}"
114
+ break if is_finished? and things_read>0
115
+ end
116
+ end
117
+
118
+ def handle_tag()
119
+ @already += @m.pre_match
120
+ @rest = @m.post_match
121
+
122
+ is_closing = !!@m[1]
123
+ tag = @m[2]
124
+ attributes = @m[3].to_s
125
+
126
+ is_single = false
127
+ if attributes[-1] == ?/ # =~ /\A(.*)\/\Z/
128
+ attributes = attributes[0, attributes.size-1]
129
+ is_single = true
130
+ end
131
+
132
+ my_debug "Attributes: #{attributes.inspect}"
133
+ my_debug "READ TAG #{@m.to_s.inspect} tag = #{tag} closing? #{is_closing} single = #{is_single}"
134
+
135
+ if TO_SANITIZE.include? tag
136
+ attributes.strip!
137
+ # puts "Attributes: #{attributes.inspect}"
138
+ if attributes.size > 0
139
+ @already += '<%s %s />' % [tag, attributes]
140
+ else
141
+ @already += '<%s />' % [tag]
142
+ end
143
+ elsif is_closing
144
+ @already += @m.to_s
145
+ if @tag_stack.empty?
146
+ error "Malformed: closing tag #{tag.inspect} "+
147
+ "in empty list"
148
+ end
149
+ if @tag_stack.last != tag
150
+ error "Malformed: tag <#{tag}> "+
151
+ "closes <#{@tag_stack.last}>"
152
+ end
153
+ @tag_stack.pop
154
+ else
155
+ @already += @m.to_s
156
+
157
+ if not is_single
158
+ @tag_stack.push(tag)
159
+ my_debug "Pushing #{tag.inspect} when read #{@m.to_s.inspect}"
160
+ end
161
+ end
162
+ end
163
+ def error(s)
164
+ raise Exception, "Error: #{s} \n"+ inspect, caller
165
+ end
166
+
167
+ def inspect; "HTML READER\n state=#{self.state} "+
168
+ "match=#{@m.to_s.inspect}\n"+
169
+ "Tag stack = #{@tag_stack.inspect} \n"+
170
+ "Before:\n"+
171
+ add_tabs(@already,1,'|')+"\n"+
172
+ "After:\n"+
173
+ add_tabs(@rest,1,'|')+"\n"
174
+
175
+ end
176
+
177
+
178
+ def stuff_you_read
179
+ @already
180
+ end
181
+
182
+ def rest() @rest end
183
+
184
+ def is_finished?
185
+ (self.state == :inside_element) and @tag_stack.empty?
186
+ end
187
+ end # html helper
188
+
189
+ end end end end
@@ -0,0 +1,111 @@
1
+ #--
2
+ # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
3
+ #
4
+ # This file is part of Maruku.
5
+ #
6
+ # Maruku is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Maruku is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Maruku; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
+ #++
20
+
21
+
22
+ module MaRuKu; module In; module Markdown; module BlockLevelParser
23
+
24
+ # This represents a source of lines that can be consumed.
25
+ #
26
+ # It is the twin of CharSource.
27
+ #
28
+
29
+ class LineSource
30
+ include MaRuKu::Strings
31
+ attr_reader :parent
32
+
33
+ def initialize(lines, parent=nil, parent_offset=nil)
34
+ raise "NIL lines? " if not lines
35
+ @lines = lines
36
+ @lines_index = 0
37
+ @parent = parent
38
+ @parent_offset = parent_offset
39
+ end
40
+
41
+ def cur_line() @lines[@lines_index] end
42
+ def next_line() @lines[@lines_index+1] end
43
+
44
+ def shift_line()
45
+ raise "Over the rainbow" if @lines_index >= @lines.size
46
+ l = @lines[@lines_index]
47
+ @lines_index += 1
48
+ return l
49
+ end
50
+
51
+ def ignore_line
52
+ raise "Over the rainbow" if @lines_index >= @lines.size
53
+ @lines_index += 1
54
+ end
55
+
56
+ def describe
57
+ s = "At line #{original_line_number(@lines_index)}\n"
58
+
59
+ context = 3 # lines
60
+ from = [@lines_index-context, 0].max
61
+ to = [@lines_index+context, @lines.size-1].min
62
+
63
+ for i in from..to
64
+ prefix = (i == @lines_index) ? '--> ' : ' ';
65
+ l = @lines[i]
66
+ s += "%10s %4s|%s" %
67
+ [@lines[i].md_type.to_s, prefix, l]
68
+
69
+ s += "|\n"
70
+ end
71
+
72
+ # if @parent
73
+ # s << "Parent context is: \n"
74
+ # s << add_tabs(@parent.describe,1,'|')
75
+ # end
76
+ s
77
+ end
78
+
79
+ def original_line_number(index)
80
+ if @parent
81
+ return index + @parent.original_line_number(@parent_offset)
82
+ else
83
+ 1 + index
84
+ end
85
+ end
86
+
87
+ def cur_index
88
+ @lines_index
89
+ end
90
+
91
+ # Returns the type of next line as a string
92
+ # breaks at first :definition
93
+ def tell_me_the_future
94
+ s = ""; num_e = 0;
95
+ for i in @lines_index..@lines.size-1
96
+ c = case @lines[i].md_type
97
+ when :text; "t"
98
+ when :empty; num_e+=1; "e"
99
+ when :definition; "d"
100
+ else "o"
101
+ end
102
+ s += c
103
+ break if c == "d" or num_e>1
104
+ end
105
+ s
106
+ end
107
+
108
+ end # linesource
109
+
110
+ end end end end # block
111
+
@@ -0,0 +1,615 @@
1
+ #--
2
+ # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
3
+ #
4
+ # This file is part of Maruku.
5
+ #
6
+ # Maruku is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Maruku is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Maruku; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
+ #++
20
+
21
+
22
+ module MaRuKu; module In; module Markdown; module BlockLevelParser
23
+
24
+ include Helpers
25
+ include MaRuKu::Strings
26
+ include MaRuKu::In::Markdown::SpanLevelParser
27
+
28
+ class BlockContext < Array
29
+ def describe
30
+ n = 5
31
+ desc = size > n ? self[-n,n] : self
32
+ "Last #{n} elements: "+
33
+ desc.map{|x| "\n -" + x.inspect}.join
34
+ end
35
+ end
36
+
37
+ # Splits the string and calls parse_lines_as_markdown
38
+ def parse_text_as_markdown(text)
39
+ lines = split_lines(text)
40
+ src = LineSource.new(lines)
41
+ return parse_blocks(src)
42
+ end
43
+
44
+ # Input is a LineSource
45
+ def parse_blocks(src)
46
+ output = BlockContext.new
47
+
48
+ # run state machine
49
+ while src.cur_line
50
+
51
+ next if check_block_extensions(src, output, src.cur_line)
52
+
53
+ # Prints detected type (useful for debugging)
54
+ # puts "#{src.cur_line.md_type}|#{src.cur_line}"
55
+ case src.cur_line.md_type
56
+ when :empty;
57
+ output.push :empty
58
+ src.ignore_line
59
+ when :ial
60
+ m = InlineAttributeList.match src.shift_line
61
+ content = m[1] || ""
62
+ # puts "Content: #{content.inspect}"
63
+ src2 = CharSource.new(content, src)
64
+ interpret_extension(src2, output, [nil])
65
+ when :ald
66
+ output.push read_ald(src)
67
+ when :text
68
+ # paragraph, or table, or definition list
69
+ read_text_material(src, output)
70
+ when :header2, :hrule
71
+ # hrule
72
+ src.shift_line
73
+ output.push md_hrule()
74
+ when :header3
75
+ output.push read_header3(src)
76
+ when :ulist, :olist
77
+ list_type = src.cur_line.md_type == :ulist ? :ul : :ol
78
+ li = read_list_item(src)
79
+ # append to current list if we have one
80
+ if output.last.kind_of?(MDElement) &&
81
+ output.last.node_type == list_type then
82
+ output.last.children << li
83
+ else
84
+ output.push md_el(list_type, [li])
85
+ end
86
+ when :quote; output.push read_quote(src)
87
+ when :code; e = read_code(src); output << e if e
88
+ when :raw_html; e = read_raw_html(src); output << e if e
89
+
90
+ when :footnote_text; output.push read_footnote_text(src)
91
+ when :ref_definition;
92
+ if src.parent && (src.cur_index == 0)
93
+ read_text_material(src, output)
94
+ else
95
+ read_ref_definition(src, output)
96
+ end
97
+ when :abbreviation; output.push read_abbreviation(src)
98
+ when :xml_instr; read_xml_instruction(src, output)
99
+ when :metadata;
100
+ maruku_error "Please use the new meta-data syntax: \n"+
101
+ " http://maruku.rubyforge.org/proposal.html\n", src
102
+ src.ignore_line
103
+ else # warn if we forgot something
104
+ md_type = src.cur_line.md_type
105
+ line = src.cur_line
106
+ maruku_error "Ignoring line '#{line}' type = #{md_type}", src
107
+ src.shift_line
108
+ end
109
+ end
110
+
111
+ merge_ial(output, src, output)
112
+ output.delete_if {|x| x.kind_of?(MDElement) &&
113
+ x.node_type == :ial}
114
+
115
+ # get rid of empty line markers
116
+ output.delete_if {|x| x == :empty}
117
+ # See for each list if we can omit the paragraphs and use li_span
118
+ # TODO: do this after
119
+ output.each do |c|
120
+ # Remove paragraphs that we can get rid of
121
+ if [:ul,:ol].include? c.node_type
122
+ if c.children.all? {|li| !li.want_my_paragraph} then
123
+ c.children.each do |d|
124
+ d.node_type = :li_span
125
+ d.children = d.children[0].children
126
+ end
127
+ end
128
+ end
129
+ if c.node_type == :definition_list
130
+ if c.children.all?{|defi| !defi.want_my_paragraph} then
131
+ c.children.each do |definition|
132
+ definition.definitions.each do |dd|
133
+ dd.children = dd.children[0].children
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ output
141
+ end
142
+
143
+ def read_text_material(src, output)
144
+ if src.cur_line =~ MightBeTableHeader and
145
+ (src.next_line && src.next_line =~ TableSeparator)
146
+ output.push read_table(src)
147
+ elsif [:header1,:header2].include? src.next_line.md_type
148
+ output.push read_header12(src)
149
+ elsif eventually_comes_a_def_list(src)
150
+ definition = read_definition(src)
151
+ if output.last.kind_of?(MDElement) &&
152
+ output.last.node_type == :definition_list then
153
+ output.last.children << definition
154
+ else
155
+ output.push md_el(:definition_list, [definition])
156
+ end
157
+ else # Start of a paragraph
158
+ output.push read_paragraph(src)
159
+ end
160
+ end
161
+
162
+
163
+ def read_ald(src)
164
+ if (l=src.shift_line) =~ AttributeDefinitionList
165
+ id = $1; al=$2;
166
+ al = read_attribute_list(CharSource.new(al,src), context=nil, break_on=[nil])
167
+ self.ald[id] = al;
168
+ return md_ald(id, al)
169
+ else
170
+ maruku_error "Bug Bug:\n#{l.inspect}"
171
+ return nil
172
+ end
173
+ end
174
+
175
+ # reads a header (with ----- or ========)
176
+ def read_header12(src)
177
+ line = src.shift_line.strip
178
+ al = nil
179
+ # Check if there is an IAL
180
+ if new_meta_data? and line =~ /^(.*)\{(.*)\}\s*$/
181
+ line = $1.strip
182
+ ial = $2
183
+ al = read_attribute_list(CharSource.new(ial,src), context=nil, break_on=[nil])
184
+ end
185
+ text = parse_lines_as_span [ line ]
186
+ level = src.cur_line.md_type == :header2 ? 2 : 1;
187
+ src.shift_line
188
+ return md_header(level, text, al)
189
+ end
190
+
191
+ # reads a header like '#### header ####'
192
+ def read_header3(src)
193
+ line = src.shift_line.strip
194
+ al = nil
195
+ # Check if there is an IAL
196
+ if new_meta_data? and line =~ /^(.*)\{(.*)\}\s*$/
197
+ line = $1.strip
198
+ ial = $2
199
+ al = read_attribute_list(CharSource.new(ial,src), context=nil, break_on=[nil])
200
+ end
201
+ level = num_leading_hashes(line)
202
+ text = parse_lines_as_span [strip_hashes(line)]
203
+ return md_header(level, text, al)
204
+ end
205
+
206
+ def read_xml_instruction(src, output)
207
+ m = /^\s*<\?((\w+)\s*)?(.*)$/.match src.shift_line
208
+ raise "BugBug" if not m
209
+ target = m[2] || ''
210
+ code = m[3]
211
+ until code =~ /\?>/
212
+ code += "\n"+src.shift_line
213
+ end
214
+ if not code =~ (/\?>\s*$/)
215
+ garbage = (/\?>(.*)$/.match(code))[1]
216
+ maruku_error "Trailing garbage on last line: #{garbage.inspect}:\n"+
217
+ add_tabs(code, 1, '|'), src
218
+ end
219
+ code.gsub!(/\?>\s*$/, '')
220
+
221
+ if target == 'mrk' && MaRuKu::Globals[:unsafe_features]
222
+ result = safe_execute_code(self, code)
223
+ if result
224
+ if result.kind_of? String
225
+ raise "Not expected"
226
+ else
227
+ output.push(*result)
228
+ end
229
+ end
230
+ else
231
+ output.push md_xml_instr(target, code)
232
+ end
233
+ end
234
+
235
+ def read_raw_html(src)
236
+ h = HTMLHelper.new
237
+ begin
238
+ h.eat_this(l=src.shift_line)
239
+ # puts "\nBLOCK:\nhtml -> #{l.inspect}"
240
+ while src.cur_line and not h.is_finished?
241
+ l=src.shift_line
242
+ # puts "html -> #{l.inspect}"
243
+ h.eat_this "\n"+l
244
+ end
245
+ rescue Exception => e
246
+ ex = e.inspect + e.backtrace.join("\n")
247
+ maruku_error "Bad block-level HTML:\n#{add_tabs(ex,1,'|')}\n", src
248
+ end
249
+ if not (h.rest =~ /^\s*$/)
250
+ maruku_error "Could you please format this better?\n"+
251
+ "I see that #{h.rest.inspect} is left after the raw HTML.", src
252
+ end
253
+ raw_html = h.stuff_you_read
254
+
255
+ return md_html(raw_html)
256
+ end
257
+
258
+ def read_paragraph(src)
259
+ lines = [src.shift_line]
260
+ while src.cur_line
261
+ # :olist does not break
262
+ case t = src.cur_line.md_type
263
+ when :quote,:header3,:empty,:ref_definition,:ial #,:xml_instr,:raw_html
264
+ break
265
+ when :olist,:ulist
266
+ break if src.next_line.md_type == t
267
+ end
268
+ break if src.cur_line.strip.size == 0
269
+ break if [:header1,:header2].include? src.next_line.md_type
270
+ break if any_matching_block_extension?(src.cur_line)
271
+
272
+ lines << src.shift_line
273
+ end
274
+ # dbg_describe_ary(lines, 'PAR')
275
+ children = parse_lines_as_span(lines, src)
276
+
277
+ return md_par(children)
278
+ end
279
+
280
+ # Reads one list item, either ordered or unordered.
281
+ def read_list_item(src)
282
+ parent_offset = src.cur_index
283
+
284
+ item_type = src.cur_line.md_type
285
+ first = src.shift_line
286
+
287
+ indentation = spaces_before_first_char(first)
288
+ break_list = [:ulist, :olist, :ial]
289
+ # Ugly things going on inside `read_indented_content`
290
+ lines, want_my_paragraph =
291
+ read_indented_content(src,indentation, break_list, item_type)
292
+
293
+ # add first line
294
+ # Strip first '*', '-', '+' from first line
295
+ stripped = first[indentation, first.size-1]
296
+ lines.unshift stripped
297
+
298
+ # dbg_describe_ary(lines, 'LIST ITEM ')
299
+
300
+ src2 = LineSource.new(lines, src, parent_offset)
301
+ children = parse_blocks(src2)
302
+ with_par = want_my_paragraph || (children.size>1)
303
+
304
+ return md_li(children, with_par)
305
+ end
306
+
307
+ def read_abbreviation(src)
308
+ if not (l=src.shift_line) =~ Abbreviation
309
+ maruku_error "Bug: it's Andrea's fault. Tell him.\n#{l.inspect}"
310
+ end
311
+
312
+ abbr = $1
313
+ desc = $2
314
+
315
+ if (not abbr) or (abbr.size==0)
316
+ maruku_error "Bad abbrev. abbr=#{abbr.inspect} desc=#{desc.inspect}"
317
+ end
318
+
319
+ self.abbreviations[abbr] = desc
320
+
321
+ return md_abbr_def(abbr, desc)
322
+ end
323
+
324
+ def read_footnote_text(src)
325
+ parent_offset = src.cur_index
326
+
327
+ first = src.shift_line
328
+
329
+ if not first =~ FootnoteText
330
+ maruku_error "Bug (it's Andrea's fault)"
331
+ end
332
+
333
+ id = $1
334
+ text = $2
335
+
336
+ # Ugly things going on inside `read_indented_content`
337
+ indentation = 4 #first.size-text.size
338
+
339
+ # puts "id =_#{id}_; text=_#{text}_ indent=#{indentation}"
340
+
341
+ break_list = [:footnote_text, :ref_definition, :definition, :abbreviation]
342
+ item_type = :footnote_text
343
+ lines, want_my_paragraph =
344
+ read_indented_content(src,indentation, break_list, item_type)
345
+
346
+ # add first line
347
+ if text && text.strip != "" then lines.unshift text end
348
+
349
+ # dbg_describe_ary(lines, 'FOOTNOTE')
350
+ src2 = LineSource.new(lines, src, parent_offset)
351
+ children = parse_blocks(src2)
352
+
353
+ e = md_footnote(id, children)
354
+ self.footnotes[id] = e
355
+ return e
356
+ end
357
+
358
+
359
+ # This is the only ugly function in the code base.
360
+ # It is used to read list items, descriptions, footnote text
361
+ def read_indented_content(src, indentation, break_list, item_type)
362
+ lines =[]
363
+ # collect all indented lines
364
+ saw_empty = false; saw_anything_after = false
365
+ while src.cur_line
366
+ # puts "Reading indent = #{indentation} #{src.cur_line.inspect}"
367
+ #puts "#{src.cur_line.md_type} #{src.cur_line.inspect}"
368
+ if src.cur_line.md_type == :empty
369
+ saw_empty = true
370
+ lines << src.shift_line
371
+ next
372
+ end
373
+
374
+ # after a white line
375
+ if saw_empty
376
+ # we expect things to be properly aligned
377
+ if (ns=number_of_leading_spaces(src.cur_line)) < indentation
378
+ #puts "breaking for spaces, only #{ns}: #{src.cur_line}"
379
+ break
380
+ end
381
+ saw_anything_after = true
382
+ else
383
+ # if src.cur_line[0] != ?\
384
+ break if break_list.include? src.cur_line.md_type
385
+ # end
386
+ # break if src.cur_line.md_type != :text
387
+ end
388
+
389
+
390
+ stripped = strip_indent(src.shift_line, indentation)
391
+ lines << stripped
392
+
393
+ #puts "Accepted as #{stripped.inspect}"
394
+
395
+ # You are only required to indent the first line of
396
+ # a child paragraph.
397
+ if stripped.md_type == :text
398
+ while src.cur_line && (src.cur_line.md_type == :text)
399
+ lines << strip_indent(src.shift_line, indentation)
400
+ end
401
+ end
402
+ end
403
+
404
+ want_my_paragraph = saw_anything_after ||
405
+ (saw_empty && (src.cur_line && (src.cur_line.md_type == item_type)))
406
+
407
+ # dbg_describe_ary(lines, 'LI')
408
+ # create a new context
409
+
410
+ while lines.last && (lines.last.md_type == :empty)
411
+ lines.pop
412
+ end
413
+
414
+ return lines, want_my_paragraph
415
+ end
416
+
417
+
418
+ def read_quote(src)
419
+ parent_offset = src.cur_index
420
+
421
+ lines = []
422
+ # collect all indented lines
423
+ while src.cur_line && src.cur_line.md_type == :quote
424
+ lines << unquote(src.shift_line)
425
+ end
426
+ # dbg_describe_ary(lines, 'QUOTE')
427
+
428
+ src2 = LineSource.new(lines, src, parent_offset)
429
+ children = parse_blocks(src2)
430
+ return md_quote(children)
431
+ end
432
+
433
+ def read_code(src)
434
+ # collect all indented lines
435
+ lines = []
436
+ while src.cur_line && ([:code, :empty].include? src.cur_line.md_type)
437
+ lines << strip_indent(src.shift_line, 4)
438
+ end
439
+
440
+ #while lines.last && (lines.last.md_type == :empty )
441
+ while lines.last && lines.last.strip.size == 0
442
+ lines.pop
443
+ end
444
+
445
+ while lines.first && lines.first.strip.size == 0
446
+ lines.shift
447
+ end
448
+
449
+ return nil if lines.empty?
450
+
451
+ source = lines.join("\n")
452
+
453
+ # dbg_describe_ary(lines, 'CODE')
454
+
455
+ return md_codeblock(source)
456
+ end
457
+
458
+ # Reads a series of metadata lines with empty lines in between
459
+ def read_metadata(src)
460
+ hash = {}
461
+ while src.cur_line
462
+ case src.cur_line.md_type
463
+ when :empty; src.shift_line
464
+ when :metadata; hash.merge! parse_metadata(src.shift_line)
465
+ else break
466
+ end
467
+ end
468
+ hash
469
+ end
470
+
471
+
472
+ def read_ref_definition(src, out)
473
+ line = src.shift_line
474
+
475
+
476
+ # if link is incomplete, shift next line
477
+ if src.cur_line && !([:footnote_text, :ref_definition, :definition, :abbreviation].include? src.cur_line.md_type) &&
478
+ ([1,2,3].include? number_of_leading_spaces(src.cur_line) )
479
+ line += " "+ src.shift_line
480
+ end
481
+
482
+ # puts "total= #{line}"
483
+
484
+ match = LinkRegex.match(line)
485
+ if not match
486
+ maruku_error "Link does not respect format: '#{line}'"
487
+ return
488
+ end
489
+
490
+ id = match[1]; url = match[2]; title = match[3];
491
+ id = sanitize_ref_id(id)
492
+
493
+ hash = self.refs[id] = {:url=>url,:title=>title}
494
+
495
+ stuff=match[4]
496
+
497
+ if stuff
498
+ stuff.split.each do |couple|
499
+ # puts "found #{couple}"
500
+ k, v = couple.split('=')
501
+ v ||= ""
502
+ if v[0,1]=='"' then v = v[1, v.size-2] end
503
+ # puts "key:_#{k}_ value=_#{v}_"
504
+ hash[k.to_sym] = v
505
+ end
506
+ end
507
+ # puts hash.inspect
508
+
509
+ out.push md_ref_def(id, url, meta={:title=>title})
510
+ end
511
+
512
+ def split_cells(s)
513
+ # s.strip.split('|').select{|x|x.strip.size>0}.map{|x|x.strip}
514
+ # changed to allow empty cells
515
+ s.strip.split('|').select{|x|x.size>0}.map{|x|x.strip}
516
+ end
517
+
518
+ def read_table(src)
519
+ head = split_cells(src.shift_line).map{|s| md_el(:head_cell, parse_lines_as_span([s])) }
520
+
521
+ separator=split_cells(src.shift_line)
522
+
523
+ align = separator.map { |s| s =~ Sep
524
+ if $1 and $2 then :center elsif $2 then :right else :left end }
525
+
526
+ num_columns = align.size
527
+
528
+ if head.size != num_columns
529
+ maruku_error "Table head does not have #{num_columns} columns: \n#{head.inspect}"
530
+ tell_user "I will ignore this table."
531
+ # XXX try to recover
532
+ return md_br()
533
+ end
534
+
535
+ rows = []
536
+
537
+ while src.cur_line && src.cur_line =~ /\|/
538
+ row = split_cells(src.shift_line).map{|s|
539
+ md_el(:cell, parse_lines_as_span([s]))}
540
+ if head.size != num_columns
541
+ maruku_error "Row does not have #{num_columns} columns: \n#{row.inspect}"
542
+ tell_user "I will ignore this table."
543
+ # XXX try to recover
544
+ return md_br()
545
+ end
546
+ rows << row
547
+ end
548
+
549
+ children = (head+rows).flatten
550
+ return md_el(:table, children, {:align => align})
551
+ end
552
+
553
+ # If current line is text, a definition list is coming
554
+ # if 1) text,empty,[text,empty]*,definition
555
+
556
+ def eventually_comes_a_def_list(src)
557
+ future = src.tell_me_the_future
558
+ ok = future =~ %r{^t+e?d}x
559
+ # puts "future: #{future} - #{ok}"
560
+ ok
561
+ end
562
+
563
+
564
+ def read_definition(src)
565
+ # Read one or more terms
566
+ terms = []
567
+ while src.cur_line && src.cur_line.md_type == :text
568
+ terms << md_el(:definition_term, parse_lines_as_span([src.shift_line]))
569
+ end
570
+ # dbg_describe_ary(terms, 'DT')
571
+
572
+ want_my_paragraph = false
573
+
574
+ raise "Chunky Bacon!" if not src.cur_line
575
+
576
+ # one optional empty
577
+ if src.cur_line.md_type == :empty
578
+ want_my_paragraph = true
579
+ src.shift_line
580
+ end
581
+
582
+ raise "Chunky Bacon!" if src.cur_line.md_type != :definition
583
+
584
+ # Read one or more definitions
585
+ definitions = []
586
+ while src.cur_line && src.cur_line.md_type == :definition
587
+ parent_offset = src.cur_index
588
+
589
+ first = src.shift_line
590
+ first =~ Definition
591
+ first = $1
592
+
593
+ # I know, it's ugly!!!
594
+
595
+ lines, w_m_p =
596
+ read_indented_content(src,4, [:definition], :definition)
597
+ want_my_paragraph ||= w_m_p
598
+
599
+ lines.unshift first
600
+
601
+ # dbg_describe_ary(lines, 'DD')
602
+ src2 = LineSource.new(lines, src, parent_offset)
603
+ children = parse_blocks(src2)
604
+ definitions << md_el(:definition_data, children)
605
+ end
606
+
607
+ return md_el(:definition, terms+definitions, {
608
+ :terms => terms,
609
+ :definitions => definitions,
610
+ :want_my_paragraph => want_my_paragraph})
611
+ end
612
+ end # BlockLevelParser
613
+ end # MaRuKu
614
+ end
615
+ end