amp-front 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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