Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

use maruku for markdown and vendor it

  • Loading branch information...
commit 128f72d96dae6175011f1fc82d5ca1346f8eb57c 1 parent 9efe3fb
@adamwiggins adamwiggins authored
Showing with 9,169 additions and 4 deletions.
  1. +2 −2 lib/post.rb
  2. +2 −2 spec/post_spec.rb
  3. +141 −0 vendor/maruku/maruku.rb
  4. +462 −0 vendor/maruku/maruku/attic/parse_span.rb.txt
  5. +227 −0 vendor/maruku/maruku/attributes.rb
  6. +70 −0 vendor/maruku/maruku/defaults.rb
  7. +92 −0 vendor/maruku/maruku/errors_management.rb
  8. +100 −0 vendor/maruku/maruku/ext/div.rb
  9. +41 −0 vendor/maruku/maruku/ext/math.rb
  10. +27 −0 vendor/maruku/maruku/ext/math/elements.rb
  11. +11 −0 vendor/maruku/maruku/ext/math/latex_fix.rb
  12. +104 −0 vendor/maruku/maruku/ext/math/mathml_engines/blahtex.rb
  13. +29 −0 vendor/maruku/maruku/ext/math/mathml_engines/itex2mml.rb
  14. +20 −0 vendor/maruku/maruku/ext/math/mathml_engines/none.rb
  15. +24 −0 vendor/maruku/maruku/ext/math/mathml_engines/ritex.rb
  16. +105 −0 vendor/maruku/maruku/ext/math/parsing.rb
  17. +170 −0 vendor/maruku/maruku/ext/math/to_html.rb
  18. +22 −0 vendor/maruku/maruku/ext/math/to_latex.rb
  19. +260 −0 vendor/maruku/maruku/helpers.rb
  20. +326 −0 vendor/maruku/maruku/input/charsource.rb
  21. +69 −0 vendor/maruku/maruku/input/extensions.rb
  22. +189 −0 vendor/maruku/maruku/input/html_helper.rb
  23. +111 −0 vendor/maruku/maruku/input/linesource.rb
  24. +613 −0 vendor/maruku/maruku/input/parse_block.rb
  25. +227 −0 vendor/maruku/maruku/input/parse_doc.rb
  26. +732 −0 vendor/maruku/maruku/input/parse_span_better.rb
  27. +225 −0 vendor/maruku/maruku/input/rubypants.rb
  28. +144 −0 vendor/maruku/maruku/input/type_detection.rb
  29. +163 −0 vendor/maruku/maruku/input_textile2/t2_parser.rb
  30. +33 −0 vendor/maruku/maruku/maruku.rb
  31. +756 −0 vendor/maruku/maruku/output/s5/fancy.rb
  32. +125 −0 vendor/maruku/maruku/output/s5/to_s5.rb
  33. +971 −0 vendor/maruku/maruku/output/to_html.rb
  34. +563 −0 vendor/maruku/maruku/output/to_latex.rb
  35. +367 −0 vendor/maruku/maruku/output/to_latex_entities.rb
  36. +64 −0 vendor/maruku/maruku/output/to_latex_strings.rb
  37. +164 −0 vendor/maruku/maruku/output/to_markdown.rb
  38. +53 −0 vendor/maruku/maruku/output/to_s.rb
  39. +191 −0 vendor/maruku/maruku/string_utils.rb
  40. +165 −0 vendor/maruku/maruku/structures.rb
  41. +87 −0 vendor/maruku/maruku/structures_inspect.rb
  42. +61 −0 vendor/maruku/maruku/structures_iterators.rb
  43. +82 −0 vendor/maruku/maruku/tests/benchmark.rb
  44. +370 −0 vendor/maruku/maruku/tests/new_parser.rb
  45. +136 −0 vendor/maruku/maruku/tests/tests.rb
  46. +1 −0  vendor/maruku/maruku/textile2.rb
  47. +199 −0 vendor/maruku/maruku/toc.rb
  48. +33 −0 vendor/maruku/maruku/usage/example1.rb
  49. +40 −0 vendor/maruku/maruku/version.rb
View
4 lib/post.rb
@@ -1,4 +1,4 @@
-require 'rdiscount'
+require File.dirname(__FILE__) + '/../vendor/maruku/maruku'
require 'syntax/convertors/html'
class Post < Sequel::Model
@@ -52,7 +52,7 @@ def self.make_slug(title)
########
def to_html(markdown)
- h = RDiscount.new(markdown).to_html
+ h = Maruku.new(markdown).to_html
h.gsub(/<code>([^<]+)<\/code>/m) do
convertor = Syntax::Convertors::HTML.for_syntax "ruby"
highlighted = convertor.convert($1)
View
4 spec/post_spec.rb
@@ -20,11 +20,11 @@
it "produces html from the markdown body" do
@post.body = "* Bullet"
- @post.body_html.should == "<ul>\n<li>Bullet</li>\n</ul>\n\n"
+ @post.body_html.should == "<ul>\n<li>Bullet</li>\n</ul>"
end
it "syntax highlights code blocks" do
- @post.to_html("<code>\none\ntwo</code>").should == "<p><code><pre>\n<span class=\"ident\">one</span>\n<span class=\"ident\">two</span></pre></code></p>\n"
+ @post.to_html("<code>\none\ntwo</code>").should == "<code><pre>\n<span class=\"ident\">one</span>\n<span class=\"ident\">two</span></pre></code>"
end
it "makes the tags into links to the tag search" do
View
141 vendor/maruku/maruku.rb
@@ -0,0 +1,141 @@
+#--
+# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
+#
+# This file is part of Maruku.
+#
+# Maruku is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Maruku is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Maruku; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#++
+$:.unshift File.dirname(__FILE__)
+
+require 'rexml/document'
+
+# :include:MaRuKu.txt
+module MaRuKu
+
+ module In
+ module Markdown
+ module SpanLevelParser; end
+ module BlockLevelParser; end
+ end
+ # more to come?
+ end
+
+ module Out
+ # Functions for exporting to MarkDown.
+ module Markdown; end
+ # Functions for exporting to HTML.
+ module HTML; end
+ # Functions for exporting to Latex
+ module Latex; end
+ end
+
+ # These are strings utilities.
+ module Strings; end
+
+ module Helpers; end
+
+ module Errors; end
+
+ class MDElement
+ include REXML
+ include MaRuKu
+ include Out::Markdown
+ include Out::HTML
+ include Out::Latex
+ include Strings
+ include Helpers
+ include Errors
+ end
+
+
+ class MDDocument < MDElement
+ include In::Markdown
+ include In::Markdown::SpanLevelParser
+ include In::Markdown::BlockLevelParser
+ end
+end
+
+# This is the public interface
+class Maruku < MaRuKu::MDDocument; end
+
+
+
+require 'rexml/document'
+
+# Structures definition
+require 'maruku/structures'
+require 'maruku/structures_inspect'
+
+require 'maruku/defaults'
+# Less typing
+require 'maruku/helpers'
+
+# Code for parsing whole Markdown documents
+require 'maruku/input/parse_doc'
+
+# Ugly things kept in a closet
+require 'maruku/string_utils'
+require 'maruku/input/linesource'
+require 'maruku/input/type_detection'
+
+# A class for reading and sanitizing inline HTML
+require 'maruku/input/html_helper'
+
+# Code for parsing Markdown block-level elements
+require 'maruku/input/parse_block'
+
+# Code for parsing Markdown span-level elements
+require 'maruku/input/charsource'
+require 'maruku/input/parse_span_better'
+require 'maruku/input/rubypants'
+
+require 'maruku/input/extensions'
+
+require 'maruku/attributes'
+
+require 'maruku/structures_iterators'
+
+require 'maruku/errors_management'
+
+# Code for creating a table of contents
+require 'maruku/toc'
+
+# Version and URL
+require 'maruku/version'
+
+
+# Exporting to html
+require 'maruku/output/to_html'
+
+# Exporting to latex
+require 'maruku/output/to_latex'
+require 'maruku/output/to_latex_strings'
+require 'maruku/output/to_latex_entities'
+
+# Pretty print
+require 'maruku/output/to_markdown'
+
+# S5 slides
+require 'maruku/output/s5/to_s5'
+require 'maruku/output/s5/fancy'
+
+# Exporting to text: strips all formatting (not complete)
+require 'maruku/output/to_s'
+
+# class Maruku is the global interface
+require 'maruku/maruku'
+
+# require the new DIV syntax, by default
+require 'maruku/ext/div'
View
462 vendor/maruku/maruku/attic/parse_span.rb.txt
@@ -0,0 +1,462 @@
+# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
+#
+# This file is part of Maruku.
+#
+# Maruku is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Maruku is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Maruku; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+
+
+# NOTE: this is the old span-level regexp-based parser.
+#
+# The new parser is a real parser and is defined with functions in parse_span_better.rb
+# The new parser is faster, handles syntax errors, but it's absolutely not readable.
+#
+# Also, regexp parsers simply CANNOT handle inline HTML properly.
+
+
+
+# There are two black-magic methods `match_couple_of` and `map_match`,
+# defined at the end of the file, that make the function
+# `parse_lines_as_span` so elegant.
+
+class Maruku
+
+ # Takes care of all span-level formatting, links, images, etc.
+ #
+ # Lines must not contain block-level elements.
+ def parse_lines_as_span(lines)
+
+ # first, get rid of linebreaks
+ res = resolve_linebreaks(lines)
+
+ span = MDElement.new(:dummy, res)
+
+ # encode all escapes
+ span.replace_each_string { |s| s.escape_md_special }
+
+
+# The order of processing is significant:
+# 1. inline code
+# 2. immediate links
+# 3. inline HTML
+# 4. everything else
+
+ # search for ``code`` markers
+ span.match_couple_of('``') { |children, match1, match2|
+ e = create_md_element(:inline_code)
+ # this is now opaque to processing
+ e.meta[:raw_code] = children.join('').it_was_a_code_block
+ e
+ }
+
+ # Search for `single tick` code markers
+ span.match_couple_of('`') { |children, match1, match2|
+ e = create_md_element(:inline_code)
+ # this is now opaque to processing
+ e.meta[:raw_code] = children.join('').it_was_a_code_block
+ # this is now opaque to processing
+ e
+ }
+
+ # Detect any immediate link: <http://www.google.com>
+ # we expect an http: or something: at the beginning
+ span.map_match( /<(\w+:[^\>]+)>/) { |match|
+ url = match[1]
+
+ e = create_md_element(:immediate_link, [])
+ e.meta[:url] = url
+ e
+ }
+
+ # Search for inline HTML (the support is pretty basic for now)
+
+ # this searches for a matching block
+ inlineHTML1 = %r{
+ ( # put everything in 1
+ < # open
+ (\w+) # opening tag in 2
+ > # close
+ .* # anything
+ </\2> # match closing tag
+ )
+ }x
+
+ # this searches for only one block
+ inlineHTML2 = %r{
+ ( # put everything in 1
+ < # open
+ \w+ #
+ # close
+ [^<>]* # anything except
+ /> # closing tag
+ )
+ }x
+
+ for reg in [inlineHTML1, inlineHTML2]
+ span.map_match(reg) { |match|
+ raw_html = match[1]
+ convert_raw_html_in_list(raw_html)
+ }
+ end
+
+ # Detect footnotes references: [^1]
+ span.map_match(/\[(\^[^\]]+)\]/) { |match|
+ id = match[1].strip.downcase
+ e = create_md_element(:footnote_reference)
+ e.meta[:footnote_id] = id
+ e
+ }
+
+ # Detect any image like ![Alt text][url]
+ span.map_match(/\!\[([^\]]+)\]\s?\[([^\]]*)\]/) { |match|
+ alt = match[1]
+ id = match[2].strip.downcase
+
+ if id.size == 0
+ id = text.strip.downcase
+ end
+
+ e = create_md_element(:image)
+ e.meta[:ref_id] = id
+ e
+ }
+
+ # Detect any immage with immediate url: ![Alt](url "title")
+ # a dummy ref is created and put in the symbol table
+ link1 = /!\[([^\]]+)\]\s?\(([^\s\)]*)(?:\s+["'](.*)["'])?\)/
+ span.map_match(link1) { |match|
+ alt = match[1]
+ url = match[2]
+ title = match[3]
+
+ url = url.strip
+ # create a dummy id
+ id="dummy_#{@refs.size}"
+ @refs[id] = {:url=>url, :title=>title}
+
+ e = create_md_element(:image)
+ e.meta[:ref_id] = id
+ e
+ }
+
+ # an id reference: "[id]", "[ id ]"
+ reg_id_ref = %r{
+ \[ # opening bracket
+ ([^\]]*) # 0 or more non-closing bracket (this is too permissive)
+ \] # closing bracket
+ }x
+
+
+ # validates a url, only $1 is set to the url
+ reg_url =
+ /((?:\w+):\/\/(?:\w+:{0,1}\w*@)?(?:\S+)(?::[0-9]+)?(?:\/|\/([\w#!:.?+=&%@!\-\/]))?)/
+ reg_url = %r{([^\s\]\)]+)}
+
+ # A string enclosed in quotes.
+ reg_title = %r{
+ " # opening
+ [^"]* # anything = 1
+ " # closing
+ }x
+
+ # [bah](http://www.google.com "Google.com"),
+ # [bah](http://www.google.com),
+ # [empty]()
+ reg_url_and_title = %r{
+ \( # opening
+ \s* # whitespace
+ #{reg_url}? # url = 1 might be empty
+ (?:\s+["'](.*)["'])? # optional title = 2
+ \s* # whitespace
+ \) # closing
+ }x
+
+ # Detect a link like ![Alt text][id]
+ span.map_match(/\[([^\]]+)\]\s?\[([^\]]*)\]/) { |match|
+ text = match[1]
+ id = match[2].strip.downcase
+
+ if id.size == 0
+ id = text.strip.downcase
+ end
+
+ children = parse_lines_as_span(text)
+ e = create_md_element(:link, children)
+ e.meta[:ref_id] = id
+ e
+ }
+
+ # Detect any immage with immediate url: ![Alt](url "title")
+ # a dummy ref is created and put in the symbol table
+ link1 = /!\[([^\]]+)\]\s?\(([^\s\)]*)(?:\s+["'](.*)["'])?\)/
+ span.map_match(link1) { |match|
+ text = match[1]
+ children = parse_lines_as_span(text)
+
+ url = match[2]
+ title = match[3]
+
+ url = url.strip
+ # create a dummy id
+ id="dummy_#{@refs.size}"
+ @refs[id] = {:url=>url, :title=>title}
+ @refs[id][:title] = title if title
+
+ e = create_md_element(:link, children)
+ e.meta[:ref_id] = id
+ e
+ }
+
+
+ # Detect any link like [Google engine][google]
+ span.match_couple_of('[', # opening bracket
+ %r{\] # closing bracket
+ [ ]? # optional whitespace
+ #{reg_id_ref} # ref id, with $1 being the reference
+ }x
+ ) { |children, match1, match2|
+ id = match2[1]
+ id = id.strip.downcase
+
+ if id.size == 0
+ id = children.join.strip.downcase
+ end
+
+ e = create_md_element(:link, children)
+ e.meta[:ref_id] = id
+ e
+ }
+
+ # Detect any link with immediate url: [Google](http://www.google.com)
+ # XXX Note that the url can be empty: [Empty]()
+ # a dummy ref is created and put in the symbol table
+ span.match_couple_of('[', # opening bracket
+ %r{\] # closing bracket
+ [ ]? # optional whitespace
+ #{reg_url_and_title} # ref id, with $1 being the url and $2 being the title
+ }x
+ ) { |children, match1, match2|
+
+ url = match2[1]
+ title = match2[3] # XXX? Is it a bug? I would use [2]
+
+ # create a dummy id
+ id="dummy_#{@refs.size}"
+ @refs[id] = {:url=>url}
+ @refs[id][:title] = title if title
+
+ e = create_md_element(:link, children)
+ e.meta[:ref_id] = id
+ e
+ }
+
+ # Detect an email address <andrea@invalid.it>
+ span.map_match(EMailAddress) { |match|
+ email = match[1]
+ e = create_md_element(:email_address, [])
+ e.meta[:email] = email
+ e
+ }
+
+ # Detect HTML entitis
+ span.map_match(/&([\w\d]+);/) { |match|
+ entity_name = match[1]
+
+ e = create_md_element(:entity, [])
+ e.meta[:entity_name] = entity_name
+ e
+ }
+
+
+ # And now the easy stuff
+
+ # search for ***strong and em***
+ span.match_couple_of('***') { |children,m1,m2|
+ create_md_element(:strong, [create_md_element(:emphasis, children)] ) }
+
+ span.match_couple_of('___') { |children,m1,m2|
+ create_md_element(:strong, [create_md_element(:emphasis, children)] ) }
+
+ # search for **strong**
+ span.match_couple_of('**') { |children,m1,m2| create_md_element(:strong, children) }
+
+ # search for __strong__
+ span.match_couple_of('__') { |children,m1,m2| create_md_element(:strong, children) }
+
+ # search for *emphasis*
+ span.match_couple_of('*') { |children,m1,m2| create_md_element(:emphasis, children) }
+
+ # search for _emphasis_
+ span.match_couple_of('_') { |children,m1,m2| create_md_element(:emphasis, children) }
+
+ # finally, unescape the special characters
+ span.replace_each_string { |s| s.unescape_md_special}
+
+ span.children
+ end
+
+ # returns array containing Strings or :linebreak elements
+ def resolve_linebreaks(lines)
+ res = []
+ s = ""
+ lines.each do |l|
+ s += (s.size>0 ? " " : "") + l.strip
+ if force_linebreak?(l)
+ res << s
+ res << create_md_element(:linebreak)
+ s = ""
+ end
+ end
+ res << s if s.size > 0
+ res
+ end
+
+ # raw_html is something like
+ # <em> A</em> dopwkk *maruk* <em>A</em>
+ def convert_raw_html_in_list(raw_html)
+ e = create_md_element(:raw_html)
+ e.meta[:raw_html] = raw_html
+ begin
+ e.meta[:parsed_html] = Document.new(raw_html)
+ rescue
+ $stderr.puts "convert_raw_html_in_list Malformed HTML:\n#{raw_html}"
+ end
+ e
+ end
+
+end
+
+# And now the black magic that makes the part above so elegant
+class MDElement
+
+ # Try to match the regexp to each string in the hierarchy
+ # (using `replace_each_string`). If the regexp match, eliminate
+ # the matching string and substitute it with the pre_match, the
+ # result of the block, and the post_match
+ #
+ # ..., matched_string, ... -> ..., pre_match, block.call(match), post_match
+ #
+ # the block might return arrays.
+ #
+ def map_match(regexp, &block)
+ replace_each_string { |s|
+ processed = []
+ while (match = regexp.match(s))
+ # save the pre_match
+ processed << match.pre_match if match.pre_match && match.pre_match.size>0
+ # transform match
+ result = block.call(match)
+ # and append as processed
+ [*result].each do |e| processed << e end
+ # go on with the rest of the string
+ s = match.post_match
+ end
+ processed << s if s.size > 0
+ processed
+ }
+ end
+
+ # Finds couple of delimiters in a hierarchy of Strings and MDElements
+ #
+ # Open and close are two delimiters (like '[' and ']'), or two Regexp.
+ #
+ # If you don't pass close, it defaults to open.
+ #
+ # Each block is called with |contained children, match1, match2|
+ def match_couple_of(open, close=nil, &block)
+ close = close || open
+ open_regexp = open.kind_of?(Regexp) ? open : Regexp.new(Regexp.escape(open))
+ close_regexp = close.kind_of?(Regexp) ? close : Regexp.new(Regexp.escape(close))
+
+ # Do the same to children first
+ for c in @children; if c.kind_of? MDElement
+ c.match_couple_of(open_regexp, close_regexp, &block)
+ end end
+
+ processed_children = []
+
+ until @children.empty?
+ c = @children.shift
+ if c.kind_of? String
+ match1 = open_regexp.match(c)
+ if not match1
+ processed_children << c
+ else # we found opening, now search closing
+# puts "Found opening (#{marker}) in #{c.inspect}"
+ # pre match is processed
+ processed_children.push match1.pre_match if
+ match1.pre_match && match1.pre_match.size > 0
+ # we will process again the post_match
+ @children.unshift match1.post_match if
+ match1.post_match && match1.post_match.size>0
+
+ contained = []; found_closing = false
+ until @children.empty? || found_closing
+ c = @children.shift
+ if c.kind_of? String
+ match2 = close_regexp.match(c)
+ if not match2
+ contained << c
+ else
+ # we found closing
+ found_closing = true
+ # pre match is contained
+ contained.push match2.pre_match if
+ match2.pre_match && match2.pre_match.size>0
+ # we will process again the post_match
+ @children.unshift match2.post_match if
+ match2.post_match && match2.post_match.size>0
+
+ # And now we call the block
+ substitute = block.call(contained, match1, match2)
+ processed_children << substitute
+
+# puts "Found closing (#{marker}) in #{c.inspect}"
+# puts "Children: #{contained.inspect}"
+# puts "Substitute: #{substitute.inspect}"
+ end
+ else
+ contained << c
+ end
+ end
+
+ if not found_closing
+ # $stderr.puts "##### Could not find closing for #{open}, #{close} -- ignoring"
+ processed_children << match1.to_s
+ contained.reverse.each do |c|
+ @children.unshift c
+ end
+ end
+ end
+ else
+ processed_children << c
+ end
+ end
+
+ raise "BugBug" unless @children.empty?
+
+ rebuilt = []
+ # rebuild strings
+ processed_children.each do |c|
+ if c.kind_of?(String) && rebuilt.last && rebuilt.last.kind_of?(String)
+ rebuilt.last << c
+ else
+ rebuilt << c
+ end
+ end
+ @children = rebuilt
+ end
+end
View
227 vendor/maruku/maruku/attributes.rb
@@ -0,0 +1,227 @@
+#--
+# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
+#
+# This file is part of Maruku.
+#
+# Maruku is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Maruku is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Maruku; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#++
+
+
+class String
+ def quote_if_needed
+ if /[\s\'\"]/.match self
+ inspect
+ else
+ self
+ end
+ end
+end
+
+module MaRuKu;
+ MagicChar = ':'
+
+ class AttributeList < Array
+
+ # An attribute list becomes
+ # {#id .cl key="val" ref}
+ # [ [:id, 'id'], [:class, 'id'], ['key', 'val'], [ :ref, 'ref' ]]
+
+ private :push
+
+ def push_key_val(key, val);
+ raise "Bad #{key.inspect}=#{val.inspect}" if not key and val
+ push [key, val]
+ end
+ def push_ref(ref_id);
+
+ raise "Bad :ref #{ref_id.inspect}" if not ref_id
+ push [:ref, ref_id+""]
+
+# p "Now ", self ########################################
+ end
+ def push_class(val);
+ raise "Bad :id #{val.inspect}" if not val
+ push [:class, val]
+ end
+ def push_id(val);
+ raise "Bad :id #{val.inspect}" if not val
+ push [:id, val]
+ end
+
+ def to_s
+ map do |k,v|
+ case k
+ when :id; "#" + v.quote_if_needed
+ when :class; "." + v.quote_if_needed
+ when :ref; v.quote_if_needed
+ else k.quote_if_needed + "=" + v.quote_if_needed
+ end
+ end . join(' ')
+ end
+ alias to_md to_s
+ end
+
+end
+
+module MaRuKu; module In; module Markdown; module SpanLevelParser
+
+ def unit_tests_for_attribute_lists
+ [
+ [ "", [], "Empty lists are allowed" ],
+ [ "=", :throw, "Bad char to begin a list with." ],
+ [ "a =b", :throw, "No whitespace before `=`." ],
+ [ "a= b", :throw, "No whitespace after `=`." ],
+
+ [ "a b", [[:ref, 'a'],[:ref, 'b']], "More than one ref" ],
+ [ "a b c", [[:ref, 'a'],[:ref, 'b'],[:ref, 'c']], "More than one ref" ],
+ [ "hello notfound", [[:ref, 'hello'],[:ref, 'notfound']]],
+
+ [ "'a'", [[:ref, 'a']], "Quoted value." ],
+ [ '"a"' ],
+
+ [ "a=b", [['a','b']], "Simple key/val" ],
+ [ "'a'=b" ],
+ [ "'a'='b'" ],
+ [ "a='b'" ],
+
+ [ 'a="b\'"', [['a',"b\'"]], "Key/val with quotes" ],
+ [ 'a=b\''],
+ [ 'a="\\\'b\'"', [['a',"\'b\'"]], "Key/val with quotes" ],
+
+ ['"', :throw, "Unclosed quotes"],
+ ["'"],
+ ["'a "],
+ ['"a '],
+
+ [ "#a", [[:id, 'a']], "Simple ID" ],
+ [ "#'a'" ],
+ [ '#"a"' ],
+
+ [ "#", :throw, "Unfinished '#'." ],
+ [ ".", :throw, "Unfinished '.'." ],
+ [ "# a", :throw, "No white-space after '#'." ],
+ [ ". a", :throw, "No white-space after '.' ." ],
+
+ [ "a=b c=d", [['a','b'],['c','d']], "Tabbing" ],
+ [ " \ta=b \tc='d' "],
+ [ "\t a=b\t c='d'\t\t"],
+
+ [ ".\"a'", :throw, "Mixing quotes is bad." ],
+
+ ].map { |s, expected, comment|
+ @expected = (expected ||= @expected)
+ @comment = (comment ||= (last=@comment) )
+ (comment == last && (comment += (@count+=1).to_s)) || @count = 1
+ expected = [md_ial(expected)] if expected.kind_of? Array
+ ["{#{MagicChar}#{s}}", expected, "Attributes: #{comment}"]
+ }
+ end
+
+ def md_al(s=[]); AttributeList.new(s) end
+
+ # returns nil or an AttributeList
+ def read_attribute_list(src, con, break_on_chars)
+
+ separators = break_on_chars + [?=,?\ ,?\t]
+ escaped = Maruku::EscapedCharInQuotes
+
+ al = AttributeList.new
+ while true
+ src.consume_whitespace
+ break if break_on_chars.include? src.cur_char
+
+ case src.cur_char
+ when nil
+ maruku_error "Attribute list terminated by EOF:\n "+
+ "#{al.inspect}" , src, con
+ tell_user "I try to continue and return partial attribute list:\n"+
+ al.inspect
+ break
+ when ?= # error
+ maruku_error "In attribute lists, cannot start identifier with `=`."
+ tell_user "I try to continue"
+ src.ignore_char
+ when ?# # id definition
+ src.ignore_char
+ if id = read_quoted_or_unquoted(src, con, escaped, separators)
+ al.push_id id
+ else
+ maruku_error 'Could not read `id` attribute.', src, con
+ tell_user 'Trying to ignore bad `id` attribute.'
+ end
+ when ?. # class definition
+ src.ignore_char
+ if klass = read_quoted_or_unquoted(src, con, escaped, separators)
+ al.push_class klass
+ else
+ maruku_error 'Could not read `class` attribute.', src, con
+ tell_user 'Trying to ignore bad `class` attribute.'
+ end
+ else
+ if key = read_quoted_or_unquoted(src, con, escaped, separators)
+ if src.cur_char == ?=
+ src.ignore_char # skip the =
+ if val = read_quoted_or_unquoted(src, con, escaped, separators)
+ al.push_key_val(key, val)
+ else
+ maruku_error "Could not read value for key #{key.inspect}.",
+ src, con
+ tell_user "Ignoring key #{key.inspect}."
+ end
+ else
+ al.push_ref key
+ end
+ else
+ maruku_error 'Could not read key or reference.'
+ end
+ end # case
+ end # while true
+ al
+ end
+
+
+ # We need a helper
+ def is_ial(e); e.kind_of? MDElement and e.node_type == :ial end
+
+ def merge_ial(elements, src, con)
+
+ # Apply each IAL to the element before
+ elements.each_with_index do |e, i|
+ if is_ial(e) && i>= 1 then
+ before = elements[i-1]
+ after = elements[i+1]
+ if before.kind_of? MDElement
+ before.al = e.ial
+ elsif after.kind_of? MDElement
+ after.al = e.ial
+ else
+ maruku_error "It is not clear to me what element this IAL {:#{e.ial.to_md}} \n"+
+ "is referring to. The element before is a #{before.class.to_s}, \n"+
+ "the element after is a #{after.class.to_s}.\n"+
+ "\n before: #{before.inspect}"+
+ "\n after: #{after.inspect}",
+ src, con
+ # xxx dire se c'è empty vicino
+ end
+ end
+ end
+
+ if not Globals[:debug_keep_ials]
+ elements.delete_if {|x| is_ial(x) unless x == elements.first}
+ end
+ end
+
+end end end end
+#module MaRuKu; module In; module Markdown; module SpanLevelParser
View
70 vendor/maruku/maruku/defaults.rb
@@ -0,0 +1,70 @@
+#--
+# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
+#
+# This file is part of Maruku.
+#
+# Maruku is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Maruku is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Maruku; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#++
+
+
+module MaRuKu
+
+Globals = {
+ :unsafe_features => false,
+ :on_error => :warning,
+
+
+ :use_numbered_headers => false,
+
+ :maruku_signature => false,
+ :code_background_color => '#fef',
+ :code_show_spaces => false,
+
+ :filter_html => false,
+
+ :html_math_output_mathml => true, # also set :html_math_engine
+ :html_math_engine => 'none', #ritex, itex2mml
+
+ :html_math_output_png => false,
+ :html_png_engine => 'none',
+ :html_png_dir => 'pngs',
+ :html_png_url => 'pngs/',
+ :html_png_resolution => 200,
+
+ :html_use_syntax => false,
+
+ :latex_use_listings => false,
+ :latex_cjk => false,
+
+ :debug_keep_ials => false,
+ :doc_prefix => ''
+}
+
+class MDElement
+ def get_setting(sym)
+ if self.attributes.has_key?(sym) then
+ return self.attributes[sym]
+ elsif self.doc && self.doc.attributes.has_key?(sym) then
+ return self.doc.attributes[sym]
+ elsif MaRuKu::Globals.has_key?(sym)
+ return MaRuKu::Globals[sym]
+ else
+ $stderr.puts "Bug: no default for #{sym.inspect}"
+ nil
+ end
+ end
+end
+
+end
View
92 vendor/maruku/maruku/errors_management.rb
@@ -0,0 +1,92 @@
+#--
+# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
+#
+# This file is part of Maruku.
+#
+# Maruku is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Maruku is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Maruku; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#++
+
+
+
+#m Any method that detects formatting error calls the
+#m maruku_error() method.
+#m if @meta[:on_error] ==
+#m
+#m - :warning write on the standard err (or @error_stream if defined),
+#m then do your best.
+#m - :ignore be shy and try to continue
+#m - :raise raises a MarukuException
+#m
+#m default is :raise
+
+module MaRuKu
+
+ class Exception < RuntimeError
+ end
+
+module Errors
+
+ def maruku_error(s,src=nil,con=nil)
+ policy = get_setting(:on_error)
+
+ case policy
+ when :ignore
+ when :raise
+ raise_error create_frame(describe_error(s,src,con))
+ when :warning
+ tell_user create_frame(describe_error(s,src,con))
+ else
+ raise "BugBug: policy = #{policy.inspect}"
+ end
+ end
+
+ def maruku_recover(s,src=nil,con=nil)
+ tell_user create_frame(describe_error(s,src,con))
+ end
+
+ alias error maruku_error
+
+ def raise_error(s)
+ raise MaRuKu::Exception, s, caller
+ end
+
+ def tell_user(s)
+ error_stream = self.attributes[:error_stream] || $stderr
+ error_stream << s
+ end
+
+ def create_frame(s)
+ n = 75
+ "\n" +
+ " "+"_"*n + "\n"+
+ "| Maruku tells you:\n" +
+ "+" + ("-"*n) +"\n"+
+ add_tabs(s,1,'| ') + "\n" +
+ "+" + ("-"*n) + "\n" +
+ add_tabs(caller[0, 5].join("\n"),1,'!') + "\n" +
+ "\\" + ("_"*n) + "\n"
+ end
+
+ def describe_error(s,src,con)
+ t = s
+ src && (t += "\n#{src.describe}\n")
+ con && (t += "\n#{con.describe}\n")
+ t
+ end
+
+end # Errors
+end # MaRuKu
+
+
View
100 vendor/maruku/maruku/ext/div.rb
@@ -0,0 +1,100 @@
+
+
+OpenDiv = /^[ ]{0,3}\+\-\-+\s*([^\s-]*)\s*\-*\s*$/
+CloseDiv = /^[ ]{0,3}\=\-\-+\s*([^\s-]*)\s*\-*\s*$/
+StartPipe = /^[ ]{0,3}\|(.*)$/ # $1 is rest of line
+DecorativeClosing = OpenDiv
+
+MaRuKu::In::Markdown::register_block_extension(
+ :regexp => OpenDiv,
+ :handler => lambda { |doc, src, context|
+ # return false if not doc.is_math_enabled?
+ first = src.shift_line
+ first =~ OpenDiv
+ ial_at_beginning = $1
+ ial_at_end = nil
+
+ lines = []
+ # if second line starts with "|"
+ if src.cur_line =~ StartPipe
+ # then we read until no more "|"
+ while src.cur_line && (src.cur_line =~ StartPipe)
+ content = $1
+ lines.push content
+ src.shift_line
+ end
+ if src.cur_line =~ DecorativeClosing
+ ial_at_end = $1
+ src.shift_line
+ end
+ else
+ # else we read until CloseDiv
+ divs_open = 1
+ while src.cur_line && (divs_open>0)
+ if src.cur_line =~ CloseDiv
+ divs_open -= 1
+ if divs_open == 0
+ ial_at_end = $1
+ src.shift_line
+ break
+ else
+ lines.push src.shift_line
+ end
+ else
+ if src.cur_line =~ OpenDiv
+ divs_open += 1
+ end
+ lines.push src.shift_line
+ end
+ end
+
+ if divs_open > 0
+ e = "At end of input, I still have #{divs_open} DIVs open."
+ doc.maruku_error(e, src, context)
+ return true
+ end
+ end
+
+ ial_at_beginning = nil unless
+ (ial_at_beginning&&ial_at_beginning.size > 0)
+ ial_at_end = nil unless (ial_at_end && ial_at_end.size > 0)
+
+ if ial_at_beginning && ial_at_end
+ e = "Found two conflicting IALs: #{ial_at_beginning.inspect} and #{ial_at_end.inspect}"
+ doc.maruku_error(e, src, context)
+ end
+
+ al_string = ial_at_beginning || ial_at_end
+ al = nil
+
+ if al_string =~ /^\{(.*)\}$/
+ inside = $1
+ cs = MaRuKu::In::Markdown::SpanLevelParser::CharSource
+ al = al_string &&
+ doc.read_attribute_list(cs.new(inside), its_context=nil, break_on=[nil])
+ end
+
+ src = MaRuKu::In::Markdown::BlockLevelParser::LineSource.new(lines)
+ children = doc.parse_blocks(src)
+
+ context.push doc.md_div(children, al)
+ true
+ })
+
+
+module MaRuKu; class MDElement
+
+ def md_div(children, a=nil)
+ self.md_el(:div, children, meta={}, a)
+ end
+
+end end
+
+
+module MaRuKu; module Out; module HTML
+
+ def to_html_div
+ add_ws wrap_as_element('div')
+ end
+
+end end end
View
41 vendor/maruku/maruku/ext/math.rb
@@ -0,0 +1,41 @@
+
+
+require 'maruku/ext/math/elements'
+require 'maruku/ext/math/parsing'
+require 'maruku/ext/math/to_latex'
+require 'maruku/ext/math/to_html'
+
+require 'maruku/ext/math/mathml_engines/none'
+require 'maruku/ext/math/mathml_engines/ritex'
+require 'maruku/ext/math/mathml_engines/itex2mml'
+require 'maruku/ext/math/mathml_engines/blahtex'
+
+
+=begin maruku_doc
+Attribute: math_enabled
+Scope: global, document
+Summary: Enables parsing of LaTeX math
+
+To explicitly disable the math parsing:
+
+ Maruku.new(string, {:math_enabled => false})
+ {:ruby}
+
+=end
+
+MaRuKu::Globals[:math_enabled] = true
+
+
+=begin maruku_doc
+Attribute: math_numbered
+Scope: global, document
+Summary: Math openings which should be numerated
+
+Array containing any of `'\\['`, `'\\begin{equation}'`, `'$$'`.
+
+ MaRuKu::Globals[:math_numbered] = ['\\[']
+
+=end
+
+
+MaRuKu::Globals[:math_numbered] = []
View
27 vendor/maruku/maruku/ext/math/elements.rb
@@ -0,0 +1,27 @@
+module MaRuKu; class MDElement
+
+ def md_inline_math(math)
+ self.md_el(:inline_math, [], meta={:math=>math})
+ end
+
+ def md_equation(math, label, numerate)
+ reglabel= /\\label\{(\w+)\}/
+ if math =~ reglabel
+ label = $1
+ math.gsub!(reglabel,'')
+ end
+# puts "Found label = #{label} math #{math.inspect} "
+ num = nil
+ if (label || numerate) && @doc #take number
+ @doc.eqid2eq ||= {}
+ num = @doc.eqid2eq.size + 1
+ label = "eq#{num}" if not label # FIXME do id for document
+ end
+ e = self.md_el(:equation, [], meta={:math=>math, :label=>label,:num=>num})
+ if label && @doc #take number
+ @doc.eqid2eq[label] = e
+ end
+ e
+ end
+
+end end
View
11 vendor/maruku/maruku/ext/math/latex_fix.rb
@@ -0,0 +1,11 @@
+class String
+ # fix some LaTeX command-name clashes
+ def fix_latex
+ if #{html_math_engine} == 'itex2mml'
+ s = self.gsub("\\mathop{", "\\operatorname{")
+ s.gsub("\\space{", "\\itexspace{")
+ else
+ self
+ end
+ end
+end
View
104 vendor/maruku/maruku/ext/math/mathml_engines/blahtex.rb
@@ -0,0 +1,104 @@
+
+require 'tempfile'
+require 'fileutils'
+require 'digest/md5'
+require 'pstore'
+
+module MaRuKu; module Out; module HTML
+
+ PNG = Struct.new(:src,:depth,:height)
+
+ def convert_to_png_blahtex(kind, tex)
+ begin
+ FileUtils::mkdir_p MaRuKu::Globals[:html_png_dir]
+
+ # first, we check whether this image has already been processed
+ md5sum = Digest::MD5.hexdigest(tex+" params: ")
+ result_file = File.join(MaRuKu::Globals[:html_png_dir], md5sum+".txt")
+
+ if not File.exists?(result_file)
+ tmp_in = Tempfile.new('maruku_blahtex')
+ f = tmp_in.open
+ f.write tex
+ f.close
+
+ resolution = get_setting(:html_png_resolution)
+
+ options = "--png --use-preview-package --shell-dvipng 'dvipng -D #{resolution}' "
+ options += ("--png-directory '%s'" % MaRuKu::Globals[:html_png_dir])
+
+ cmd = "blahtex #{options} < #{tmp_in.path} > #{result_file}"
+ $stderr.puts "$ #{cmd}"
+ system cmd
+ tmp_in.delete
+ end
+
+ result = File.read(result_file)
+ if result.nil? || result.empty?
+ raise "Blahtex error: empty output"
+ end
+
+ doc = Document.new(result, {:respect_whitespace =>:all})
+ png = doc.root.elements[1]
+ if png.name != 'png'
+ raise "Blahtex error: \n#{doc}"
+ end
+ depth = png.elements['depth'] || (raise "No depth element in:\n #{doc}")
+ height = png.elements['height'] || (raise "No height element in:\n #{doc}")
+ md5 = png.elements['md5'] || (raise "No md5 element in:\n #{doc}")
+
+ depth = depth.text.to_f
+ height = height.text.to_f # XXX check != 0
+ md5 = md5.text
+
+ dir_url = MaRuKu::Globals[:html_png_url]
+ return PNG.new("#{dir_url}#{md5}.png", depth, height)
+ rescue Exception => e
+ maruku_error "Error: #{e}"
+ end
+ nil
+ end
+
+ BlahtexCache = PStore.new("blahtex_cache.pstore")
+
+ def convert_to_mathml_blahtex(kind, tex)
+ begin
+ BlahtexCache.transaction do
+ if BlahtexCache[tex].nil?
+ tmp_in = Tempfile.new('maruku_blahtex')
+ f = tmp_in.open
+ f.write tex
+ f.close
+ tmp_out = Tempfile.new('maruku_blahtex')
+
+ options = "--mathml"
+ cmd = "blahtex #{options} < #{tmp_in.path} > #{tmp_out.path}"
+ $stderr.puts "$ #{cmd}"
+ system cmd
+ tmp_in.delete
+
+ result = nil
+ File.open(tmp_out.path) do |f| result=f.read end
+ puts result
+
+ BlahtexCache[tex] = result
+ end
+
+ blahtex = BlahtexCache[tex]
+ doc = Document.new(blahtex, {:respect_whitespace =>:all})
+ mathml = doc.root.elements['mathml']
+ if not mathml
+ maruku_error "Blahtex error: \n#{doc}"
+ return nil
+ else
+ return mathml
+ end
+ end
+
+ rescue Exception => e
+ maruku_error "Error: #{e}"
+ end
+ nil
+ end
+
+end end end
View
29 vendor/maruku/maruku/ext/math/mathml_engines/itex2mml.rb
@@ -0,0 +1,29 @@
+
+module MaRuKu; module Out; module HTML
+
+ def convert_to_mathml_itex2mml(kind, tex)
+ begin
+ if not $itex2mml_parser
+ require 'itextomml'
+ $itex2mml_parser = Itex2MML::Parser.new
+ end
+
+ itex_method = {:equation=>:block_filter,:inline=>:inline_filter}
+
+ mathml = $itex2mml_parser.send(itex_method[kind], tex)
+ doc = Document.new(mathml, {:respect_whitespace =>:all}).root
+ return doc
+ rescue LoadError => e
+ maruku_error "Could not load package 'itex2mml'.\n"+ "Please install it." unless $already_warned_itex2mml
+ $already_warned_itex2mml = true
+ rescue REXML::ParseException => e
+ maruku_error "Invalid MathML TeX: \n#{add_tabs(tex,1,'tex>')}"+
+ "\n\n #{e.inspect}"
+ rescue
+ maruku_error "Could not produce MathML TeX: \n#{tex}"+
+ "\n\n #{e.inspect}"
+ end
+ nil
+ end
+
+end end end
View
20 vendor/maruku/maruku/ext/math/mathml_engines/none.rb
@@ -0,0 +1,20 @@
+module MaRuKu; module Out; module HTML
+
+ def convert_to_mathml_none(kind, tex)
+ # You can: either return a REXML::Element
+ # return Element.new 'div'
+ # or return an empty array on error
+ # return []
+ # or have a string parsed by REXML:
+ tex = tex.gsub('&','&amp;')
+ mathml = "<code>#{tex}</code>"
+ return Document.new(mathml).root
+ end
+
+ def convert_to_png_none(kind, tex)
+ return nil
+ end
+
+
+end end end
+
View
24 vendor/maruku/maruku/ext/math/mathml_engines/ritex.rb
@@ -0,0 +1,24 @@
+module MaRuKu; module Out; module HTML
+
+ def convert_to_mathml_ritex(kind, tex)
+ begin
+ if not $ritex_parser
+ require 'ritex'
+ $ritex_parser = Ritex::Parser.new
+ end
+
+ mathml = $ritex_parser.parse(tex.strip)
+ doc = Document.new(mathml, {:respect_whitespace =>:all}).root
+ return doc
+ rescue LoadError => e
+ maruku_error "Could not load package 'ritex'.\n"+
+ "Please install it using:\n"+
+ " $ gem install ritex\n\n"+e.inspect
+ rescue Racc::ParseError => e
+ maruku_error "Could not parse TeX: \n#{tex}"+
+ "\n\n #{e.inspect}"
+ end
+ nil
+ end
+
+end end end
View
105 vendor/maruku/maruku/ext/math/parsing.rb
@@ -0,0 +1,105 @@
+module MaRuKu
+
+ class MDDocument
+ # Hash equation id (String) to equation element (MDElement)
+ attr_accessor :eqid2eq
+
+ def is_math_enabled?
+ get_setting :math_enabled
+ end
+ end
+end
+
+
+ # Everything goes; takes care of escaping the "\$" inside the expression
+ RegInlineMath = /\${1}((?:[^\$]|\\\$)+)\$/
+
+ MaRuKu::In::Markdown::register_span_extension(
+ :chars => ?$,
+ :regexp => RegInlineMath,
+ :handler => lambda { |doc, src, con|
+ return false if not doc.is_math_enabled?
+
+ if m = src.read_regexp(RegInlineMath)
+ math = m.captures.compact.first
+ con.push doc.md_inline_math(math)
+ true
+ else
+ #puts "not math: #{src.cur_chars 10}"
+ false
+ end
+ }
+ )
+
+
+ MathOpen1 = Regexp.escape('\\begin{equation}')
+ MathClose1 = Regexp.escape('\\end{equation}')
+ MathOpen2 = Regexp.escape('\\[')
+ MathClose2 = Regexp.escape('\\]')
+ MathOpen3 = Regexp.escape('$$')
+ MathClose3 = Regexp.escape('$$')
+
+ EqLabel = /(?:\((\w+)\))/
+ EquationOpen = /#{MathOpen1}|#{MathOpen2}|#{MathOpen3}/
+ EquationClose = /#{MathClose1}|#{MathClose2}|#{MathClose3}/
+
+ # $1 is opening, $2 is tex
+ EquationStart = /^[ ]{0,3}(#{EquationOpen})(.*)$/
+ # $1 is tex, $2 is closing, $3 is tex
+ EquationEnd = /^(.*)(#{EquationClose})\s*#{EqLabel}?\s*$/
+ # $1 is opening, $2 is tex, $3 is closing, $4 is label
+ OneLineEquation = /^[ ]{0,3}(#{EquationOpen})(.*)(#{EquationClose})\s*#{EqLabel}?\s*$/
+
+ MaRuKu::In::Markdown::register_block_extension(
+ :regexp => EquationStart,
+ :handler => lambda { |doc, src, con|
+ return false if not doc.is_math_enabled?
+ first = src.shift_line
+ if first =~ OneLineEquation
+ opening, tex, closing, label = $1, $2, $3, $4
+ numerate = doc.get_setting(:math_numbered).include?(opening)
+ con.push doc.md_equation(tex, label, numerate)
+ else
+ first =~ EquationStart
+ opening, tex = $1, $2
+
+ numerate = doc.get_setting(:math_numbered).include?(opening)
+ label = nil
+ while true
+ if not src.cur_line
+ doc.maruku_error("Stream finished while reading equation\n\n"+
+ doc.add_tabs(tex,1,'$> '), src, con)
+ break
+ end
+ line = src.shift_line
+ if line =~ EquationEnd
+ tex_line, closing = $1, $2
+ label = $3 if $3
+ tex += tex_line + "\n"
+ break
+ else
+ tex += line + "\n"
+ end
+ end
+ con.push doc.md_equation(tex, label, numerate)
+ end
+ true
+ })
+
+
+ # This adds support for \eqref
+ RegEqrefLatex = /\\eqref\{(\w+)\}/
+ RegEqPar = /\(eq:(\w+)\)/
+ RegEqref = Regexp::union(RegEqrefLatex, RegEqPar)
+
+ MaRuKu::In::Markdown::register_span_extension(
+ :chars => [?\\, ?(],
+ :regexp => RegEqref,
+ :handler => lambda { |doc, src, con|
+ return false if not doc.is_math_enabled?
+ eqid = src.read_regexp(RegEqref).captures.compact.first
+ r = doc.md_el(:eqref, [], meta={:eqid=>eqid})
+ con.push r
+ true
+ }
+ )
View
170 vendor/maruku/maruku/ext/math/to_html.rb
@@ -0,0 +1,170 @@
+
+=begin maruku_doc
+Extension: math
+Attribute: html_math_engine
+Scope: document, element
+Output: html
+Summary: Select the rendering engine for MathML.
+Default: <?mrk Globals[:html_math_engine].to_s ?>
+
+Select the rendering engine for math.
+
+If you want to use your custom engine `foo`, then set:
+
+ HTML math engine: foo
+{:lang=markdown}
+
+and then implement two functions:
+
+ def convert_to_mathml_foo(kind, tex)
+ ...
+ end
+=end
+
+=begin maruku_doc
+Extension: math
+Attribute: html_png_engine
+Scope: document, element
+Output: html
+Summary: Select the rendering engine for math.
+Default: <?mrk Globals[:html_math_engine].to_s ?>
+
+Same thing as `html_math_engine`, only for PNG output.
+
+ def convert_to_png_foo(kind, tex)
+ # same thing
+ ...
+ end
+{:lang=ruby}
+
+=end
+
+module MaRuKu; module Out; module HTML
+
+
+
+ # Creates an xml Mathml document of self.math
+ def render_mathml(kind, tex)
+ engine = get_setting(:html_math_engine)
+ method = "convert_to_mathml_#{engine}".to_sym
+ if self.respond_to? method
+ mathml = self.send(method, kind, tex)
+ return mathml || convert_to_mathml_none(kind, tex)
+ else
+ puts "A method called #{method} should be defined."
+ return convert_to_mathml_none(kind, tex)
+ end
+ end
+
+ # Creates an xml Mathml document of self.math
+ def render_png(kind, tex)
+ engine = get_setting(:html_png_engine)
+ method = "convert_to_png_#{engine}".to_sym
+ if self.respond_to? method
+ return self.send(method, kind, tex)
+ else
+ puts "A method called #{method} should be defined."
+ return nil
+ end
+ end
+
+ def pixels_per_ex
+ if not $pixels_per_ex
+ x = render_png(:inline, "x")
+ $pixels_per_ex = x.height # + x.depth
+ end
+ $pixels_per_ex
+ end
+
+ def adjust_png(png, use_depth)
+ src = png.src
+
+ height_in_px = png.height
+ depth_in_px = png.depth
+ height_in_ex = height_in_px / pixels_per_ex
+ depth_in_ex = depth_in_px / pixels_per_ex
+ total_height_in_ex = height_in_ex + depth_in_ex
+ style = ""
+ style += "vertical-align: -#{depth_in_ex}ex;" if use_depth
+ style += "height: #{total_height_in_ex}ex;"
+ img = Element.new 'img'
+ img.attributes['src'] = src
+ img.attributes['style'] = style
+ img.attributes['alt'] = "equation"
+ img
+ end
+
+ def to_html_inline_math
+ mathml = get_setting(:html_math_output_mathml) && render_mathml(:inline, self.math)
+ png = get_setting(:html_math_output_png) && render_png(:inline, self.math)
+
+ span = create_html_element 'span'
+ add_class_to(span, 'maruku-inline')
+
+ if mathml
+ add_class_to(mathml, 'maruku-mathml')
+ span << mathml
+ end
+
+ if png
+ img = adjust_png(png, use_depth=true)
+ add_class_to(img, 'maruku-png')
+ span << img
+ end
+ span
+
+ end
+
+ def to_html_equation
+ mathml = get_setting(:html_math_output_mathml) && render_mathml(:equation, self.math)
+ png = get_setting(:html_math_output_png) && render_png(:equation, self.math)
+
+ div = create_html_element 'div'
+ add_class_to(div, 'maruku-equation')
+ if self.label # then numerate
+ span = Element.new 'span'
+ span.attributes['class'] = 'maruku-eq-number'
+ num = self.num
+ span << Text.new("(#{num})")
+ div << span
+ div.attributes['id'] = "eq:#{self.label}"
+ end
+
+ if mathml
+ add_class_to(mathml, 'maruku-mathml')
+ div << mathml
+ end
+
+ if png
+ img = adjust_png(png, use_depth=false)
+ add_class_to(img, 'maruku-png')
+ div << img
+ end
+
+ source_div = Element.new 'div'
+ add_class_to(source_div, 'maruku-eq-tex')
+ code = convert_to_mathml_none(:equation, self.math)
+ code.attributes['style'] = 'display: none'
+ source_div << code
+ div << source_div
+ div
+ end
+
+ def to_html_eqref
+ if eq = self.doc.eqid2eq[self.eqid]
+ num = eq.num
+ a = Element.new 'a'
+ a.attributes['class'] = 'maruku-eqref'
+ a.attributes['href'] = "#eq:#{self.eqid}"
+ a << Text.new("(#{num})")
+ a
+ else
+ maruku_error "Cannot find equation #{self.eqid.inspect}"
+ Text.new "(eq:#{self.eqid})"
+ end
+ end
+
+
+end end end
+
+
View
22 vendor/maruku/maruku/ext/math/to_latex.rb
@@ -0,0 +1,22 @@
+require 'maruku/ext/math/latex_fix'
+
+module MaRuKu; module Out; module Latex
+
+ def to_latex_inline_math
+ "$#{self.math.strip}$".fix_latex
+ end
+
+ def to_latex_equation
+ if self.label
+ l = "\\label{#{self.label}}"
+ "\\begin{equation}\n#{self.math.strip}\n#{l}\\end{equation}\n".fix_latex
+ else
+ "\\begin{displaymath}\n#{self.math.strip}\n\\end{displaymath}\n".fix_latex
+ end
+ end
+
+ def to_latex_eqref
+ "\\eqref{#{self.eqid}}"
+ end
+
+end end end
View
260 vendor/maruku/maruku/helpers.rb
@@ -0,0 +1,260 @@
+#--
+# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
+#
+# This file is part of Maruku.
+#
+# Maruku is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Maruku is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Maruku; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#++
+
+
+
+
+# A series of helper functions for creating elements: they hide the
+# particular internal representation.
+#
+# Please, always use these instead of creating MDElement.
+#
+
+module MaRuKu
+module Helpers
+
+ # if the first is a md_ial, it is used as such
+ def md_el(node_type, children=[], meta={}, al=nil)
+ if (e=children.first).kind_of?(MDElement) and
+ e.node_type == :ial then
+ if al
+ al += e.ial
+ else
+ al = e.ial
+ end
+ children.shift
+ end
+ e = MDElement.new(node_type, children, meta, al)
+ e.doc = @doc
+ return e
+ end
+
+ def md_header(level, children, al=nil)
+ md_el(:header, children, {:level => level}, al)
+ end
+
+ # Inline code
+ def md_code(code, al=nil)
+ md_el(:inline_code, [], {:raw_code => code}, al)
+ end
+
+ # Code block
+ def md_codeblock(source, al=nil)
+ md_el(:code, [], {:raw_code => source}, al)
+ end
+
+ def md_quote(children, al=nil)
+ md_el(:quote, children, {}, al)
+ end
+
+ def md_li(children, want_my_par, al=nil)
+ md_el(:li, children, {:want_my_paragraph=>want_my_par}, al)
+ end
+
+ def md_footnote(footnote_id, children, al=nil)
+ md_el(:footnote, children, {:footnote_id=>footnote_id}, al)
+ end
+
+ def md_abbr_def(abbr, text, al=nil)
+ md_el(:abbr_def, [], {:abbr=>abbr, :text=>text}, al)
+ end
+
+ def md_abbr(abbr, title)
+ md_el(:abbr, [abbr], {:title=>title})
+ end
+
+ def md_html(raw_html, al=nil)
+ e = md_el(:raw_html, [], {:raw_html=>raw_html})
+ begin
+ # remove newlines and whitespace at begin
+ # end end of string, or else REXML gets confused
+ raw_html = raw_html.gsub(/\A\s*</,'<').
+ gsub(/>[\s\n]*\Z/,'>')
+
+ raw_html = "<marukuwrap>#{raw_html}</marukuwrap>"
+ e.instance_variable_set :@parsed_html,
+ REXML::Document.new(raw_html)
+ rescue #Exception => ex
+ e.instance_variable_set :@parsed_html, nil
+# tell_user "Malformed block of HTML:\n"+
+# add_tabs(raw_html,1,'|')
+# " #{raw_html.inspect}\n\n"+ex.inspect
+ end
+ e
+ end
+
+ def md_link(children, ref_id, al=nil)
+ md_el(:link, children, {:ref_id=>ref_id.downcase}, al)
+ end
+
+ def md_im_link(children, url, title=nil, al=nil)
+ md_el(:im_link, children, {:url=>url,:title=>title}, al)
+ end
+
+ def md_image(children, ref_id, al=nil)
+ md_el(:image, children, {:ref_id=>ref_id}, al)
+ end
+
+ def md_im_image(children, url, title=nil, al=nil)
+ md_el(:im_image, children, {:url=>url,:title=>title},al)
+ end
+
+ def md_em(children, al=nil)
+ md_el(:emphasis, [children].flatten, {}, al)
+ end
+
+ def md_br()
+ md_el(:linebreak, [], {}, nil)
+ end
+
+ def md_hrule()
+ md_el(:hrule, [], {}, nil)
+ end
+
+ def md_strong(children, al=nil)
+ md_el(:strong, [children].flatten, {}, al)
+ end
+
+ def md_emstrong(children, al=nil)
+ md_strong(md_em(children), al)
+ end
+
+ # <http://www.example.com/>
+ def md_url(url, al=nil)
+ md_el(:immediate_link, [], {:url=>url}, al)
+ end
+
+ # <andrea@rubyforge.org>
+ # <mailto:andrea@rubyforge.org>
+ def md_email(email, al=nil)
+ md_el(:email_address, [], {:email=>email}, al)
+ end
+
+ def md_entity(entity_name, al=nil)
+ md_el(:entity, [], {:entity_name=>entity_name}, al)
+ end
+
+ # Markdown extra
+ def md_foot_ref(ref_id, al=nil)
+ md_el(:footnote_reference, [], {:footnote_id=>ref_id}, al)
+ end
+
+ def md_par(children, al=nil)
+ md_el(:paragraph, children, meta={}, al)
+ end
+
+ # [1]: http://url [properties]
+ def md_ref_def(ref_id, url, title=nil, meta={}, al=nil)
+ meta[:url] = url
+ meta[:ref_id] = ref_id
+ meta[:title] = title if title
+ md_el(:ref_definition, [], meta, al)
+ end
+
+ # inline attribute list
+ def md_ial(al)
+ al = Maruku::AttributeList.new(al) if
+ not al.kind_of?Maruku::AttributeList
+ md_el(:ial, [], {:ial=>al})
+ end
+
+ # Attribute list definition
+ def md_ald(id, al)
+ md_el(:ald, [], {:ald_id=>id,:ald=>al})
+ end
+
+ # Server directive <?target code... ?>
+ def md_xml_instr(target, code)
+ md_el(:xml_instr, [], {:target=>target, :code=>code})
+ end
+
+end
+end
+
+module MaRuKu
+
+class MDElement
+ # outputs abbreviated form (this should be eval()uable to get the document)
+ def inspect2
+ s =
+ case @node_type
+ when :paragraph
+ "md_par(%s)" % children_inspect
+ when :footnote_reference
+ "md_foot_ref(%s)" % self.footnote_id.inspect
+ when :entity
+ "md_entity(%s)" % self.entity_name.inspect
+ when :email_address
+ "md_email(%s)" % self.email.inspect
+ when :inline_code
+ "md_code(%s)" % self.raw_code.inspect
+ when :raw_html
+ "md_html(%s)" % self.raw_html.inspect
+ when :emphasis
+ "md_em(%s)" % children_inspect
+ when :strong
+ "md_strong(%s)" % children_inspect
+ when :immediate_link
+ "md_url(%s)" % self.url.inspect
+ when :image
+ "md_image(%s, %s)" % [
+ children_inspect,
+ self.ref_id.inspect]
+ when :im_image
+ "md_im_image(%s, %s, %s)" % [
+ children_inspect,
+ self.url.inspect,
+ self.title.inspect]
+ when :link
+ "md_link(%s,%s)" % [
+ children_inspect, self.ref_id.inspect]
+ when :im_link
+ "md_im_link(%s, %s, %s)" % [
+ children_inspect,
+ self.url.inspect,
+ self.title.inspect,
+ ]
+ when :ref_definition
+ "md_ref_def(%s, %s, %s)" % [
+ self.ref_id.inspect,
+ self.url.inspect,
+ self.title.inspect
+ ]
+ when :ial
+ "md_ial(%s)" % self.ial.inspect
+ else
+ return nil
+ end
+ if @al and not @al.empty? then
+ s = s.chop + ", #{@al.inspect})"
+ end
+ s
+ end
+
+end
+
+end
+
+
+
+
+
+
+
View
326 vendor/maruku/maruku/input/charsource.rb
@@ -0,0 +1,326 @@
+#--
+# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
+#
+# This file is part of Maruku.
+#
+# Maruku is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Maruku is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Maruku; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#++
+
+
+module MaRuKu; module In; module Markdown; module SpanLevelParser
+
+# a string scanner coded by me
+class CharSourceManual; end
+
+# a wrapper around StringScanner
+class CharSourceStrscan; end
+
+# A debug scanner that checks the correctness of both
+# by comparing their output
+class CharSourceDebug; end
+
+# Choose!
+
+CharSource = CharSourceManual # faster! 58ms vs. 65ms
+#CharSource = CharSourceStrscan
+#CharSource = CharSourceDebug
+
+
+class CharSourceManual
+ include MaRuKu::Strings
+
+ def initialize(s, parent=nil)
+ raise "Passed #{s.class}" if not s.kind_of? String
+ @buffer = s
+ @buffer_index = 0
+ @parent = parent
+ end
+
+ # Return current char as a FixNum (or nil).
+ def cur_char; @buffer[@buffer_index] end
+
+ # Return the next n chars as a String.
+ def cur_chars(n); @buffer[@buffer_index,n] end
+
+ # Return the char after current char as a FixNum (or nil).
+ def next_char; @buffer[@buffer_index+1] end
+
+ def shift_char
+ c = @buffer[@buffer_index]
+ @buffer_index+=1
+ c
+ end
+
+ def ignore_char
+ @buffer_index+=1
+ nil
+ end
+
+ def ignore_chars(n)
+ @buffer_index+=n
+ nil
+ end
+
+ def current_remaining_buffer
+ @buffer[@buffer_index, @buffer.size-@buffer_index]
+ end
+
+ def cur_chars_are(string)
+ # There is a bug here
+ if false
+ r2 = /^.{#{@buffer_index}}#{Regexp.escape string}/m
+ @buffer =~ r2
+ else
+ cur_chars(string.size) == string
+ end
+ end
+
+ def next_matches(r)
+ r2 = /^.{#{@buffer_index}}#{r}/m
+ md = r2.match @buffer
+ return !!md
+ end
+
+ def read_regexp3(r)
+ r2 = /^.{#{@buffer_index}}#{r}/m
+ m = r2.match @buffer
+ if m
+ consumed = m.to_s.size - @buffer_index
+# puts "Consumed #{consumed} chars (entire is #{m.to_s.inspect})"
+ ignore_chars consumed
+ else
+# puts "Could not read regexp #{r2.inspect} from buffer "+
+# " index=#{@buffer_index}"
+# puts "Cur chars = #{cur_chars(20).inspect}"
+# puts "Matches? = #{cur_chars(20) =~ r}"
+ end
+ m
+ end
+
+ def read_regexp(r)
+ r2 = /^#{r}/
+ rest = current_remaining_buffer
+ m = r2.match(rest)
+ if m
+ @buffer_index += m.to_s.size
+# puts "#{r} matched #{rest.inspect}: #{m.to_s.inspect}"
+ end
+ return m
+ end
+
+ def consume_whitespace
+ while c = cur_char
+ if (c == 32 || c == ?\t)
+# puts "ignoring #{c}"
+ ignore_char
+ else
+# puts "#{c} is not ws: "<<c
+ break
+ end
+ end
+ end
+
+ def read_text_chars(out)
+ s = @buffer.size; c=nil
+ while @buffer_index < s && (c=@buffer[@buffer_index]) &&
+ ((c>=?a && c<=?z) || (c>=?A && c<=?Z))
+ out << c
+ @buffer_index += 1
+ end
+ end
+
+ def describe
+ s = describe_pos(@buffer, @buffer_index)
+ if @parent
+ s += "\n\n" + @parent.describe
+ end
+ s
+ end
+ include SpanLevelParser
+end
+
+def describe_pos(buffer, buffer_index)
+ len = 75
+ num_before = [len/2, buffer_index].min
+ num_after = [len/2, buffer.size-buffer_index].min
+ num_before_max = buffer_index
+ num_after_max = buffer.size-buffer_index
+
+# puts "num #{num_before} #{num_after}"
+ num_before = [num_before_max, len-num_after].min
+ num_after = [num_after_max, len-num_before].min
+# puts "num #{num_before} #{num_after}"
+
+ index_start = [buffer_index - num_before, 0].max
+ index_end = [buffer_index + num_after, buffer.size].min
+
+ size = index_end- index_start
+
+# puts "- #{index_start} #{size}"
+
+ str = buffer[index_start, size]
+ str.gsub!("\n",'N')
+ str.gsub!("\t",'T')
+
+ if index_end == buffer.size
+ str += "EOF"
+ end
+
+ pre_s = buffer_index-index_start
+ pre_s = [pre_s, 0].max
+ pre_s2 = [len-pre_s,0].max
+# puts "pre_S = #{pre_s}"
+ pre =" "*(pre_s)
+
+ "-"*len+"\n"+
+ str + "\n" +
+ "-"*pre_s + "|" + "-"*(pre_s2)+"\n"+
+# pre + "|\n"+
+ pre + "+--- Byte #{buffer_index}\n"+
+
+ "Shown bytes [#{index_start} to #{size}] of #{buffer.size}:\n"+
+ add_tabs(buffer,1,">")
+
+# "CharSource: At character #{@buffer_index} of block "+
+# " beginning with:\n #{@buffer[0,50].inspect} ...\n"+
+# " before: \n ... #{cur_chars(50).inspect} ... "
+end
+
+
+require 'strscan'
+
+class CharSourceStrscan
+ include SpanLevelParser
+ include MaRuKu::Strings
+
+ def initialize(s, parent=nil)
+ @s = StringScanner.new(s)
+ @parent = parent
+ end
+
+ # Return current char as a FixNum (or nil).
+ def cur_char
+ @s.peek(1)[0]
+ end
+
+ # Return the next n chars as a String.
+ def cur_chars(n);
+ @s.peek(n)
+ end
+
+ # Return the char after current char as a FixNum (or nil).
+ def next_char;
+ @s.peek(2)[1]
+ end
+
+ def shift_char
+ (@s.get_byte)[0]
+ end
+
+ def ignore_char
+ @s.get_byte
+ nil
+ end
+
+ def ignore_chars(n)
+ n.times do @s.get_byte end
+ nil
+ end
+
+ def current_remaining_buffer
+ @s.rest #nil #@buffer[@buffer_index, @buffer.size-@buffer_index]
+ end
+
+ def cur_chars_are(string)
+ cur_chars(string.size) == string
+ end
+
+ def next_matches(r)
+ len = @s.match?(r)
+ return !!len
+ end
+
+ def read_regexp(r)
+ string = @s.scan(r)
+ if string
+ return r.match(string)
+ else
+ return nil
+ end
+ end
+
+ def consume_whitespace
+ @s.scan(/\s+/)
+ nil
+ end
+
+ def describe
+ describe_pos(@s.string, @s.pos)
+ end
+
+end
+
+
+class CharSourceDebug
+ def initialize(s, parent)
+ @a = CharSourceManual.new(s, parent)
+ @b = CharSourceStrscan.new(s, parent)
+ end
+
+ def method_missing(methodname, *args)
+ a_bef = @a.describe
+ b_bef = @b.describe
+
+ a = @a.send(methodname, *args)
+ b = @b.send(methodname, *args)
+
+# if methodname == :describe
+# return a
+# end
+
+ if a.kind_of? MatchData
+ if a.to_a != b.to_a
+ puts "called: #{methodname}(#{args})"
+ puts "Matchdata:\na = #{a.to_a.inspect}\nb = #{b.to_a.inspect}"
+ puts "AFTER: "+@a.describe
+ puts "AFTER: "+@b.describe
+ puts "BEFORE: "+a_bef
+ puts "BEFORE: "+b_bef
+ puts caller.join("\n")
+ exit
+ end
+ else
+ if a!=b
+ puts "called: #{methodname}(#{args})"
+ puts "Attenzione!\na = #{a.inspect}\nb = #{b.inspect}"
+ puts ""+@a.describe
+ puts ""+@b.describe
+ puts caller.join("\n")
+ exit
+ end
+ end
+
+ if @a.cur_char != @b.cur_char
+ puts "Fuori sincronia dopo #{methodname}(#{args})"
+ puts ""+@a.describe
+ puts ""+@b.describe
+ exit
+ end
+
+ return a
+ end
+end
+
+end end end end
View
69 vendor/maruku/maruku/input/extensions.rb
@@ -0,0 +1,69 @@
+module MaRuKu; module In; module Markdown
+
+
+ # Hash Fixnum -> name
+ SpanExtensionsTrigger = {}
+
+
+ class SpanExtension
+ # trigging chars
+ attr_accessor :chars
+ # trigging regexp
+ attr_accessor :regexp
+ # lambda
+ attr_accessor :block
+ end
+
+ # Hash String -> Extension
+ SpanExtensions = {}
+
+ def check_span_extensions(src, con)
+ c = src.cur_char
+ if extensions = SpanExtensionsTrigger[c]
+ extensions.each do |e|
+ if e.regexp && (match = src.next_matches(e.regexp))
+ return true if e.block.call(doc, src, con)
+ end
+ end
+ end
+ return false # not special
+ end
+
+ def self.register_span_extension(args)
+ e = SpanExtension.new
+ e.chars = [*args[:chars]]
+ e.regexp = args[:regexp]
+ e.block = args[:handler] || raise("No blocks passed")
+ e.chars.each do |c|
+ (SpanExtensionsTrigger[c] ||= []).push e
+ end
+ end
+
+ def self.register_block_extension(args)
+ regexp = args[:regexp]
+ BlockExtensions[regexp] = (args[:handler] || raise("No blocks passed"))
+ end
+
+ # Hash Regexp -> Block
+ BlockExtensions = {}
+
+ def check_block_extensions(src, con, line)
+ BlockExtensions.each do |reg, block|
+ if m = reg.match(line)
+ block = BlockExtensions[reg]
+ accepted = block.call(doc, src, con)
+ return true if accepted
+ end
+ end
+ return false # not special
+ end
+
+ def any_matching_block_extension?(line)
+ BlockExtensions.each_key do |reg|
+ m = reg.match(line)
+ return m if m
+ end
+ return false