as
+ onclick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
|
<%= link_to('Cancel', {:web => @web.address, :action => 'cancel_edit', :id => @page.name},
diff --git a/app/views/wiki/tex.rhtml b/app/views/wiki/tex.rhtml
index ea9a06c6..6ac2fa5d 100644
--- a/app/views/wiki/tex.rhtml
+++ b/app/views/wiki/tex.rhtml
@@ -1,12 +1,12 @@
\documentclass[12pt,titlepage]{article}
-\usepackage[danish]{babel} %danske tekster
+\usepackage{amsmath}
+\usepackage{amsfonts}
\usepackage[OT1]{fontenc} %rigtige danske bogstaver...
-\usepackage{a4}
\usepackage{graphicx}
\usepackage{ucs}
\usepackage[utf8x]{inputenc}
-\input epsf
+\usepackage{hyperref}
%-------------------------------------------------------------------
diff --git a/db/production.db.sqlite3 b/db/production.db.sqlite3
new file mode 100644
index 0000000000000000000000000000000000000000..649297cd69d2722141fd804406d3832c0277137e
GIT binary patch
literal 19456
zcmeI2TaVjB6o5T;Hg5M;mTF~HqS~ofXx9o7Y0*|79w2Q)wIZajq$+)~Ch?@P*0F=_
z>;(y-lzphgJ1>3g@8J*hUmzr2Dj{a<~wu7nKSwJo0=O_
z+z!GniSb8DK|x6Q9AiaMw%A*IZL%9A2*k1=o-VF1k*595E3Rsr%+01)`^
z39PR(8<&dgq8B#U6=|;&_6IK@00iYc5Xd0`i2ocq9fk}9h6Et~
zVJZND{1Slp&)cDV$$?z)auYKv0c9%2u1bo
zQMJ;j;zs38t%`*TynjN&$PIk#`Z0Y%Lu=4ib;B^w&xc8SJ@SM`$wkYwk%;iV={hDp
ztT(Fnt4H|jqrZTYqbxs*$OF%sbiDa#11RPZkLMn{T`RjuRosb*vc;_Xq)TPlP+9Dd^|Ykv#?B79
zct06;NKahOB^nnBnemUsZ-+r*?lp|KJKhYb6)-3J%ndO;i^m-I_C%1RA9sRKCg%1s
z601|tjceD?pp;I!^C+g>D7mg=Fr~7^81+aLodzNEwBSLD$G@sPSCr@KZ}QAv^8Lk!
zE4s10jhb9NqF2B
zrsYA|nv8-^aMz=INkd*{J_}mq^2c{>-R4SNayIO(9l1ZS@l5r&%xy-bP3>;r%uwLf
z&5*EFD30vF_t-)tw^6p9mW#SkDxnJ_oiXmQYlpN=L+ZC^beg@roSjG6%C|G67F$-v
zBXI0R=b-2M$98<)6K2j$!7-B+DT#l`|1Wu^1M@&&Nd#88T{!<+5`8cW1eQwx;(xjP
z!8#CFCIPpS#poEb_suPiCUl5wJV
zb-1lNtlz7Chs`u2kz{gXo=H)A%G@yZWDT}gb>sSVH25*iF0o_<&%{N;|C^b8@kqMZhIpu*
zq{>EFreVs0B_T+f#{>q24c)k3LWAApJW=}f
zZ}#+9s@%#RElRc7k0!^9l7@JmJa}(YHy*KW+@0AC>Bqh45vSDLyRYxg%qHe^OVTy1
FzX2jHlgj`A
literal 0
HcmV?d00001
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 00000000..2147c8fb
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,78 @@
+# This file is autogenerated. Instead of editing this file, please use the
+# migrations feature of ActiveRecord to incrementally modify your database, and
+# then regenerate this schema definition.
+
+ActiveRecord::Schema.define(:version => 2) do
+
+ create_table "pages", :force => true do |t|
+ t.column "created_at", :datetime, :null => false
+ t.column "updated_at", :datetime, :null => false
+ t.column "web_id", :integer, :default => 0, :null => false
+ t.column "locked_by", :string, :limit => 60
+ t.column "name", :string, :limit => 60
+ t.column "locked_at", :datetime
+ end
+
+ create_table "revisions", :force => true do |t|
+ t.column "created_at", :datetime, :null => false
+ t.column "updated_at", :datetime, :null => false
+ t.column "revised_at", :datetime, :null => false
+ t.column "page_id", :integer, :default => 0, :null => false
+ t.column "content", :text, :default => "", :null => false
+ t.column "author", :string, :limit => 60
+ t.column "ip", :string, :limit => 60
+ end
+
+ add_index "revisions", ["author"], :name => "revisions_author_index"
+ add_index "revisions", ["created_at"], :name => "revisions_created_at_index"
+ add_index "revisions", ["page_id"], :name => "revisions_page_id_index"
+
+ create_table "sessions", :force => true do |t|
+ t.column "session_id", :string
+ t.column "data", :text
+ t.column "updated_at", :datetime
+ end
+
+ add_index "sessions", ["session_id"], :name => "sessions_session_id_index"
+
+ create_table "system", :force => true do |t|
+ t.column "password", :string, :limit => 60
+ end
+
+ create_table "webs", :force => true do |t|
+ t.column "created_at", :datetime, :null => false
+ t.column "updated_at", :datetime, :null => false
+ t.column "name", :string, :limit => 60, :default => "", :null => false
+ t.column "address", :string, :limit => 60, :default => "", :null => false
+ t.column "password", :string, :limit => 60
+ t.column "additional_style", :string
+ t.column "allow_uploads", :integer, :default => 1
+ t.column "published", :integer, :default => 0
+ t.column "count_pages", :integer, :default => 0
+ t.column "markup", :string, :limit => 50, :default => "textile"
+ t.column "color", :string, :limit => 6, :default => "008B26"
+ t.column "max_upload_size", :integer, :default => 100
+ t.column "safe_mode", :integer, :default => 0
+ t.column "brackets_only", :integer, :default => 0
+ end
+
+ create_table "wiki_files", :force => true do |t|
+ t.column "created_at", :datetime, :null => false
+ t.column "updated_at", :datetime, :null => false
+ t.column "web_id", :integer, :null => false
+ t.column "file_name", :string, :null => false
+ t.column "description", :string, :null => false
+ end
+
+ create_table "wiki_references", :force => true do |t|
+ t.column "created_at", :datetime, :null => false
+ t.column "updated_at", :datetime, :null => false
+ t.column "page_id", :integer, :default => 0, :null => false
+ t.column "referenced_name", :string, :limit => 60, :default => "", :null => false
+ t.column "link_type", :string, :limit => 1, :default => "", :null => false
+ end
+
+ add_index "wiki_references", ["referenced_name"], :name => "wiki_references_referenced_name_index"
+ add_index "wiki_references", ["page_id"], :name => "wiki_references_page_id_index"
+
+end
diff --git a/instiki b/instiki
index db95d002..bf720a1c 100755
--- a/instiki
+++ b/instiki
@@ -1,7 +1,6 @@
-#!/bin/sh
+#!/usr/bin/env ruby
-cd $(dirname $0)
-
-export LD_LIBRARY_PATH=./lib/native/linux-x86:$LD_LIBRARY_PATH
-ruby script/server
+# Executable file for a gem
+# must be same as ./instiki.rb
+load File.dirname(__FILE__) + '/script/server'
diff --git a/lib/bluecloth_tweaked.rb b/lib/bluecloth_tweaked.rb
index b91622f1..92883441 100644
--- a/lib/bluecloth_tweaked.rb
+++ b/lib/bluecloth_tweaked.rb
@@ -1112,7 +1112,7 @@ class BlueCloth < String
### Return a copy of +str+ with angle brackets and ampersands HTML-encoded.
def encode_html( str )
- str.gsub( /&(?!#?[x]?(?:[0-9a-f]+|\w{1,8});)/i, "&" ).
+ str.gsub( /&(?!#?[x]?(?:[0-9a-f]+|\w+);)/i, "&" ).
gsub( %r{<(?![a-z/?\$!])}i, "<" )
end
diff --git a/lib/chunks/engines.rb b/lib/chunks/engines.rb
index 3b7b5bbe..82808123 100644
--- a/lib/chunks/engines.rb
+++ b/lib/chunks/engines.rb
@@ -40,6 +40,14 @@ module Engines
end
end
+ class MarkdownMML < AbstractEngine
+ def mask
+ require_dependency 'maruku'
+ require_dependency 'maruku/ext/math'
+ Maruku.new(@content.delete("\r")).to_html
+ end
+ end
+
class Mixed < AbstractEngine
def mask
require_dependency 'redcloth'
@@ -57,6 +65,6 @@ module Engines
end
end
- MAP = { :textile => Textile, :markdown => Markdown, :mixed => Mixed, :rdoc => RDoc }
+ MAP = { :textile => Textile, :markdown => Markdown, :markdownMML => MarkdownMML, :mixed => Mixed, :rdoc => RDoc }
MAP.default = Textile
end
diff --git a/lib/maruku.rb b/lib/maruku.rb
new file mode 100644
index 00000000..c9e26ab1
--- /dev/null
+++ b/lib/maruku.rb
@@ -0,0 +1,133 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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
+#++
+
+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'
+
+# Exporting to text: strips all formatting (not complete)
+require 'maruku/output/to_s'
+
+# class Maruku is the global interface
+require 'maruku/maruku'
diff --git a/lib/maruku/attic/parse_span.rb.txt b/lib/maruku/attic/parse_span.rb.txt
new file mode 100644
index 00000000..71c57711
--- /dev/null
+++ b/lib/maruku/attic/parse_span.rb.txt
@@ -0,0 +1,462 @@
+# Copyright (C) 2006 Andrea Censi
+#
+# 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:
+ # 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
+ 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
+ # A dopwkk *maruk* A
+ 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
diff --git a/lib/maruku/attributes.rb b/lib/maruku/attributes.rb
new file mode 100644
index 00000000..07736186
--- /dev/null
+++ b/lib/maruku/attributes.rb
@@ -0,0 +1,218 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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]
+ 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 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
+
+
+ def merge_ial(elements, src, con)
+ # We need a helper
+ def is_ial(e); e.kind_of? MDElement and e.node_type == :ial end
+
+ # 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 "I don't know who you are referring to:"+
+ " {#{e.ial.to_md}}", src, con
+ # xxx dire se c'รจ empty vicino
+ maruku_recover "Ignoring IAL: {#{e.ial.to_md}}", src, con
+ 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
diff --git a/lib/maruku/defaults.rb b/lib/maruku/defaults.rb
new file mode 100644
index 00000000..2d4af38e
--- /dev/null
+++ b/lib/maruku/defaults.rb
@@ -0,0 +1,52 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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,
+
+ :debug_keep_ials => false,
+
+ :maruku_signature => false,
+ :code_background_color => '#fef',
+ :code_show_spaces => false,
+ :html_math_engine => 'itex2mml', #ritex, itex2mml, none
+ :html_use_syntax => false,
+ :on_error => :warning
+}
+
+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
diff --git a/lib/maruku/errors_management.rb b/lib/maruku/errors_management.rb
new file mode 100644
index 00000000..387acea8
--- /dev/null
+++ b/lib/maruku/errors_management.rb
@@ -0,0 +1,92 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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
+
+
diff --git a/lib/maruku/ext/math.rb b/lib/maruku/ext/math.rb
new file mode 100644
index 00000000..55b51ceb
--- /dev/null
+++ b/lib/maruku/ext/math.rb
@@ -0,0 +1,10 @@
+
+
+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'
diff --git a/lib/maruku/ext/math/elements.rb b/lib/maruku/ext/math/elements.rb
new file mode 100644
index 00000000..5f9c4dfa
--- /dev/null
+++ b/lib/maruku/ext/math/elements.rb
@@ -0,0 +1,26 @@
+module MaRuKu; class MDElement
+
+ def md_inline_math(math)
+ self.md_el(:inline_math, [], meta={:math=>math})
+ end
+
+ def md_equation(math, label=nil)
+ reglabel= /\\label\{(\w+)\}/
+ if math =~ reglabel
+ label = $1
+ math.gsub!(reglabel,'')
+ end
+# puts "Found label = #{label} math #{math.inspect} "
+ num = nil
+ if label && @doc #take number
+ @doc.eqid2eq ||= {}
+ num = @doc.eqid2eq.size + 1
+ 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
\ No newline at end of file
diff --git a/lib/maruku/ext/math/mathml_engines/itex2mml.rb b/lib/maruku/ext/math/mathml_engines/itex2mml.rb
new file mode 100644
index 00000000..4f5be42e
--- /dev/null
+++ b/lib/maruku/ext/math/mathml_engines/itex2mml.rb
@@ -0,0 +1,35 @@
+
+module MaRuKu; module Out; module HTML
+
+ def convert_to_mathml_itex2mml(tex, method)
+ begin
+ if not $itex2mml_parser
+ require 'itextomml'
+ $itex2mml_parser = Itex2MML::Parser.new
+ end
+
+ mathml = $itex2mml_parser.send(method, 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."
+ 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
+
+ def to_html_inline_math_itex2mml
+ convert_to_mathml_itex2mml(self.math, :inline_filter)
+ end
+
+ def to_html_equation_itex2mml
+ convert_to_mathml_itex2mml(self.math, :block_filter)
+ end
+
+end end end
diff --git a/lib/maruku/ext/math/mathml_engines/none.rb b/lib/maruku/ext/math/mathml_engines/none.rb
new file mode 100644
index 00000000..5fd04ec4
--- /dev/null
+++ b/lib/maruku/ext/math/mathml_engines/none.rb
@@ -0,0 +1,20 @@
+module MaRuKu; module Out; module HTML
+
+ def to_html_inline_math_none
+ # 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 = self.math
+ tex.gsub!('&','&')
+ mathml = "#{tex}"
+ return Document.new(mathml).root
+ end
+
+ def to_html_equation_none
+ return to_html_inline_math_none
+ end
+
+end end end
+
diff --git a/lib/maruku/ext/math/mathml_engines/ritex.rb b/lib/maruku/ext/math/mathml_engines/ritex.rb
new file mode 100644
index 00000000..fbb753b0
--- /dev/null
+++ b/lib/maruku/ext/math/mathml_engines/ritex.rb
@@ -0,0 +1,34 @@
+module MaRuKu; module Out; module HTML
+ def convert_to_mathml_ritex(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
+
+ def to_html_inline_math_ritex
+ tex = self.math
+ mathml = convert_to_mathml_ritex(tex)
+ return mathml || []
+ end
+
+ def to_html_equation_ritex
+ tex = self.math
+ mathml = convert_to_mathml_ritex(tex)
+ return mathml || []
+ end
+end end end
diff --git a/lib/maruku/ext/math/parsing.rb b/lib/maruku/ext/math/parsing.rb
new file mode 100644
index 00000000..ff317afb
--- /dev/null
+++ b/lib/maruku/ext/math/parsing.rb
@@ -0,0 +1,82 @@
+module MaRuKu
+ class MDDocument
+ # Hash equation id (String) to equation element (MDElement)
+ attr_accessor :eqid2eq
+ end
+end
+
+
+ # At least one slash inside
+ #RegInlineMath1 = /\$([^\$]*[\\][^\$]*)\$/
+ # No spaces around the delimiters
+ #RegInlineMath2 = /\$([^\s\$](?:[^\$]*[^\s\$])?)\$/
+ #RegInlineMath = Regexp::union(RegInlineMath1,RegInlineMath2)
+
+ # Everything goes; takes care of escaping the "\$" inside the expression
+ RegInlineMath = /\${1}((?:[^\$]|\\\$)+)\$/
+
+ MaRuKu::In::Markdown::
+ register_span_extension(:chars => ?$, :regexp => RegInlineMath) do
+ |doc, src, con|
+ 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
+ end
+
+ EquationStart = /^[ ]{0,3}(?:\\\[|\$\$)(.*)$/
+
+ EqLabel = /(?:\((\w+)\))/
+ OneLineEquation = /^[ ]{0,3}(?:\\\[|\$\$)(.*)(?:\\\]|\$\$)\s*#{EqLabel}?\s*$/
+ EquationEnd = /^(.*)(?:\\\]|\$\$)\s*#{EqLabel}?\s*$/
+
+ MaRuKu::In::Markdown::
+ register_block_extension(:regexp => EquationStart) do |doc, src, con|
+# puts "Equation :#{self}"
+ first = src.shift_line
+ if first =~ OneLineEquation
+ math = $1
+ label = $2
+ con.push doc.md_equation($1, $2)
+ else
+ first =~ EquationStart
+ math = $1
+ label = nil
+ while true
+ if not src.cur_line
+ maruku_error "Stream finished while reading equation\n\n"+
+ add_tabs(math,1,'$> '), src, con
+ break
+ end
+ line = src.shift_line
+ if line =~ EquationEnd
+ math += $1 + "\n"
+ label = $2 if $2
+ break
+ else
+ math += line + "\n"
+ end
+ end
+ con.push doc.md_equation(math, label)
+ end
+ true
+ end
+
+
+ # 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) do
+ |doc, src, con|
+ eqid = src.read_regexp(RegEqref).captures.compact.first
+ r = doc.md_el(:eqref, [], meta={:eqid=>eqid})
+ con.push r
+ true
+ end
diff --git a/lib/maruku/ext/math/to_html.rb b/lib/maruku/ext/math/to_html.rb
new file mode 100644
index 00000000..5bc3fcab
--- /dev/null
+++ b/lib/maruku/ext/math/to_html.rb
@@ -0,0 +1,107 @@
+
+=begin maruku_doc
+Attribute: html_math_engine
+Scope: document, element
+Output: html
+Summary: Select the rendering engine for math.
+Default:
+
+Select the rendering engine for math.
+
+If you want to use your engine `foo`, then set:
+
+ HTML math engine: foo
+{:lang=markdown}
+
+and then implement two functions:
+
+ def to_html_inline_math_foo
+ # 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 = self.math
+ tex.gsub!('&','&')
+ mathml = "#{tex}"
+ return Document.new(mathml).root
+ end
+
+ def to_html_equation_foo
+ # same thing
+ ...
+ end
+{:lang=ruby}
+
+=end
+
+module MaRuKu; module Out; module HTML
+
+ def to_html_inline_math
+ s = get_setting(:html_math_engine)
+ method = "to_html_inline_math_#{s}".to_sym
+ if self.respond_to? method
+ self.send method || to_html_equation_none
+ else
+ puts "A method called #{method} should be defined."
+ return []
+ end
+ end
+
+ def add_class_to(el, cl)
+ el.attributes['class'] =
+ if already = el.attributes['class']
+ already + " " + cl
+ else
+ cl
+ end
+ end
+
+ def to_html_equation
+ s = get_setting(:html_math_engine)
+ method = "to_html_equation_#{s}".to_sym
+ if self.respond_to? method
+ mathml = self.send(method) || to_html_equation_none
+ 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
+ div << mathml
+
+ source_div = Element.new 'div'
+ add_class_to(source_div, 'maruku-eq-tex')
+ code = to_html_equation_none
+ code.attributes['style'] = 'display: none'
+ source_div << code
+ div << source_div
+ div
+ else
+ puts "A method called #{method} should be defined."
+ return []
+ end
+ 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 "(#{self.eqid})"
+ end
+ end
+
+
+end end end
+
+
diff --git a/lib/maruku/ext/math/to_latex.rb b/lib/maruku/ext/math/to_latex.rb
new file mode 100644
index 00000000..578eb713
--- /dev/null
+++ b/lib/maruku/ext/math/to_latex.rb
@@ -0,0 +1,21 @@
+
+module MaRuKu; module Out; module Latex
+
+ def to_latex_inline_math
+ "$#{self.math.strip}$"
+ end
+
+ def to_latex_equation
+ if self.label
+ l = "\\label{#{self.label}}"
+ "\\begin{equation}\n#{self.math.strip}\n#{l}\\end{equation}\n"
+ else
+ "\\begin{displaymath}\n#{self.math.strip}\n\\end{displaymath}\n"
+ end
+ end
+
+ def to_latex_eqref
+ "\\eqref{#{self.eqid}}"
+ end
+
+end end end
\ No newline at end of file
diff --git a/lib/maruku/helpers.rb b/lib/maruku/helpers.rb
new file mode 100644
index 00000000..63defabb
--- /dev/null
+++ b/lib/maruku/helpers.rb
@@ -0,0 +1,259 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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 = "#{raw_html}"
+ e.instance_variable_set :@parsed_html,
+ REXML::Document.new(raw_html)
+ rescue
+# 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
+
+ #
+ def md_url(url, al=nil)
+ md_el(:immediate_link, [], {:url=>url}, al)
+ end
+
+ #
+ #
+ 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
+ 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
+
+
+
+
+
+
+
diff --git a/lib/maruku/input/charsource.rb b/lib/maruku/input/charsource.rb
new file mode 100644
index 00000000..6ce554fb
--- /dev/null
+++ b/lib/maruku/input/charsource.rb
@@ -0,0 +1,325 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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: "<=?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)
+ @s = StringScanner.new(s)
+ 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)
+ @a = CharSourceManual.new(s)
+ @b = CharSourceStrscan.new(s)
+ 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
diff --git a/lib/maruku/input/extensions.rb b/lib/maruku/input/extensions.rb
new file mode 100644
index 00000000..b8110b00
--- /dev/null
+++ b/lib/maruku/input/extensions.rb
@@ -0,0 +1,68 @@
+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, &block)
+ e = SpanExtension.new
+ e.chars = [*args[:chars]]
+ e.regexp = args[:regexp]
+ e.block = block
+ e.chars.each do |c|
+ (SpanExtensionsTrigger[c] ||= []).push e
+ end
+ end
+
+ def self.register_block_extension(args, &block)
+ regexp = args[:regexp]
+ BlockExtensions[regexp] = block
+ 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]
+ return true if block.call(doc, src, con)
+ 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
+ end
+
+end end end
diff --git a/lib/maruku/input/html_helper.rb b/lib/maruku/input/html_helper.rb
new file mode 100644
index 00000000..4275d90f
--- /dev/null
+++ b/lib/maruku/input/html_helper.rb
@@ -0,0 +1,144 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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
+
+# This class helps me read and sanitize HTML blocks
+
+# I tried to do this with REXML, but wasn't able to. (suggestions?)
+
+ class HTMLHelper
+ include MaRuKu::Strings
+
+ Tag = %r{^<(/)?(\w+)\s*([^>]*)>}m
+ EverythingElse = %r{^[^<]+}m
+ CommentStart = %r{^}
+ TO_SANITIZE = ['img','hr']
+
+# attr_accessor :inside_comment
+ attr_reader :rest
+
+ def initialize
+ @rest = ""
+ @tag_stack = []
+ @m = nil
+ @already = ""
+ @inside_comment = false
+ end
+
+ def eat_this(line)
+ @rest = line + @rest
+ things_read = 0
+ until @rest.empty?
+ if @inside_comment
+ if @m = CommentEnd.match(@rest)
+ @inside_comment = false
+ @already += @m.pre_match + @m.to_s
+ @rest = @m.post_match
+ elsif @m = EverythingElse.match(@rest)
+ @already += @m.pre_match + @m.to_s
+ @rest = @m.post_match
+ end
+ else
+ if @m = CommentStart.match(@rest)
+ things_read += 1
+ @inside_comment = true
+ @already += @m.pre_match + @m.to_s
+ @rest = @m.post_match
+ elsif @m = Tag.match(@rest)
+ things_read += 1
+ @already += @m.pre_match
+ @rest = @m.post_match
+
+ is_closing = !!@m[1]
+ tag = @m[2]
+ attributes = @m[3]
+
+ is_single = false
+ if attributes =~ /\A(.*)\/\Z/
+ attributes = $1
+ is_single = true
+ end
+
+ if TO_SANITIZE.include? tag
+ attributes.strip!
+ # puts "Attributes: #{attributes.inspect}"
+ if attributes.size > 0
+ @already += '<%s %s />' % [tag, attributes]
+ else
+ @already += '<%s />' % [tag]
+ end
+ elsif is_closing
+ @already += @m.to_s
+ if @tag_stack.empty?
+ error "Malformed: closing tag #{tag.inspect} "+
+ "in empty list"
+ end
+ if @tag_stack.last != tag
+ error "Malformed: tag <#{tag}> "+
+ "closes <#{@tag_stack.last}>"
+ end
+ @tag_stack.pop
+ elsif not is_single
+ @tag_stack.push tag
+ @already += @m.to_s
+ end
+ elsif @m = EverythingElse.match(@rest)
+ @already += @m.pre_match + @m.to_s
+ @rest = @m.post_match
+ else
+ error "Malformed HTML: not complete: #{@rest.inspect}"
+ end
+ end # not inside comment
+
+# puts inspect
+# puts "Read: #{@tag_stack.inspect}"
+ break if is_finished? and things_read>0
+ end
+ end
+
+
+ def error(s)
+ raise Exception, "Error: #{s} \n"+ inspect, caller
+ end
+
+ def inspect; "HTML READER\n comment=#{@inside_comment} "+
+ "match=#{@m.to_s.inspect}\n"+
+ "Tag stack = #{@tag_stack.inspect} \n"+
+ "Before:\n"+
+ add_tabs(@already,1,'|')+"\n"+
+ "After:\n"+
+ add_tabs(@rest,1,'|')+"\n"
+
+ end
+
+
+ def stuff_you_read
+ @already
+ end
+
+ def is_finished?
+ not @inside_comment and @tag_stack.empty?
+ end
+ end # html helper
+
+end end end end
diff --git a/lib/maruku/input/linesource.rb b/lib/maruku/input/linesource.rb
new file mode 100644
index 00000000..5d0a67d8
--- /dev/null
+++ b/lib/maruku/input/linesource.rb
@@ -0,0 +1,111 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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 BlockLevelParser
+
+# This represents a source of lines that can be consumed.
+#
+# It is the twin of CharSource.
+#
+
+class LineSource
+ include MaRuKu::Strings
+
+ def initialize(lines, parent=nil, parent_offset=nil)
+ raise "NIL lines? " if not lines
+ @lines = lines
+ @lines_index = 0
+ @parent = parent
+ @parent_offset = parent_offset
+ end
+
+ def cur_line() @lines[@lines_index] end
+ def next_line() @lines[@lines_index+1] end
+
+ def shift_line()
+ raise "Over the rainbow" if @lines_index >= @lines.size
+ l = @lines[@lines_index]
+ @lines_index += 1
+ return l
+ end
+
+ def ignore_line
+ raise "Over the rainbow" if @lines_index >= @lines.size
+ @lines_index += 1
+ end
+
+ def describe
+ #s = "At line ##{@lines_index} of #{@lines.size}:\n"
+ s = "At line #{original_line_number(@lines_index)}\n"
+
+ context = 3 # lines
+ from = [@lines_index-context, 0].max
+ to = [@lines_index+context, @lines.size-1].min
+
+ for i in from..to
+ prefix = (i == @lines_index) ? '--> ' : ' ';
+ l = @lines[i]
+ s += "%10s %4s|#{l}" %
+ [@lines[i].md_type.to_s, prefix]
+
+ s += "|\n"
+ end
+
+# if @parent
+# s << "Parent context is: \n"
+# s << add_tabs(@parent.describe,1,'|')
+# end
+ s
+ end
+
+ def original_line_number(index)
+ if @parent
+ return index + @parent.original_line_number(@parent_offset)
+ else
+ 1 + index
+ end
+ end
+
+ def cur_index
+ @lines_index
+ end
+
+ # Returns the type of next line as a string
+ # breaks at first :definition
+ def tell_me_the_future
+ s = ""; num_e = 0;
+ for i in @lines_index..@lines.size-1
+ c = case @lines[i].md_type
+ when :text; "t"
+ when :empty; num_e+=1; "e"
+ when :definition; "d"
+ else "o"
+ end
+ s += c
+ break if c == "d" or num_e>1
+ end
+ s
+ end
+
+end # linesource
+
+end end end end # block
+
diff --git a/lib/maruku/input/parse_block.rb b/lib/maruku/input/parse_block.rb
new file mode 100644
index 00000000..3c1d1288
--- /dev/null
+++ b/lib/maruku/input/parse_block.rb
@@ -0,0 +1,594 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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 BlockLevelParser
+
+ include Helpers
+ include MaRuKu::Strings
+ include MaRuKu::In::Markdown::SpanLevelParser
+
+ class BlockContext < Array
+ def describe
+ n = 5
+ desc = size > n ? self[-n,n] : self
+ "Last #{n} elements: "+
+ desc.map{|x| "\n -" + x.inspect}.join
+ end
+ end
+
+ # Splits the string and calls parse_lines_as_markdown
+ def parse_text_as_markdown(text)
+ lines = split_lines(text)
+ src = LineSource.new(lines)
+ return parse_blocks(src)
+ end
+
+ # Input is a LineSource
+ def parse_blocks(src)
+ output = BlockContext.new
+
+ # run state machine
+ while src.cur_line
+
+ next if check_block_extensions(src, output, src.cur_line)
+
+# Prints detected type (useful for debugging)
+# puts "#{src.cur_line.md_type}|#{src.cur_line}"
+ case src.cur_line.md_type
+ when :empty;
+ output.push :empty
+ src.ignore_line
+ when :ial
+ m = InlineAttributeList.match src.shift_line
+ content = m[1] || ""
+ src2 = CharSource.new(content, src)
+ interpret_extension(src2, output, [nil])
+ when :ald
+ output.push read_ald(src)
+ when :text
+ if src.cur_line =~ MightBeTableHeader and
+ (src.next_line && src.next_line =~ TableSeparator)
+ output.push read_table(src)
+ elsif [:header1,:header2].include? src.next_line.md_type
+ output.push read_header12(src)
+ elsif eventually_comes_a_def_list(src)
+ definition = read_definition(src)
+ if output.last.kind_of?(MDElement) &&
+ output.last.node_type == :definition_list then
+ output.last.children << definition
+ else
+ output.push md_el(:definition_list, [definition])
+ end
+ else # Start of a paragraph
+ output.push read_paragraph(src)
+ end
+ when :header2, :hrule
+ # hrule
+ src.shift_line
+ output.push md_hrule()
+ when :header3
+ output.push read_header3(src)
+ when :ulist, :olist
+ list_type = src.cur_line.md_type == :ulist ? :ul : :ol
+ li = read_list_item(src)
+ # append to current list if we have one
+ if output.last.kind_of?(MDElement) &&
+ output.last.node_type == list_type then
+ output.last.children << li
+ else
+ output.push md_el(list_type, [li])
+ end
+ when :quote; output.push read_quote(src)
+ when :code; e = read_code(src); output << e if e
+ when :raw_html; e = read_raw_html(src); output << e if e
+
+ when :footnote_text; output.push read_footnote_text(src)
+ when :ref_definition; output.push read_ref_definition(src)
+ when :abbreviation; output.push read_abbreviation(src)
+ when :xml_instr; read_xml_instruction(src, output)
+ when :metadata;
+ maruku_error "Please use the new meta-data syntax: \n"+
+ " http://maruku.rubyforge.org/proposal.html\n", src
+ src.ignore_line
+ else # warn if we forgot something
+ md_type = src.cur_line.md_type
+ line = src.cur_line
+ maruku_error "Ignoring line '#{line}' type = #{md_type}", src
+ src.shift_line
+ end
+ end
+
+ merge_ial(output, src, output)
+ output.delete_if {|x| x.kind_of?(MDElement) &&
+ x.node_type == :ial}
+
+ # get rid of empty line markers
+ output.delete_if {|x| x == :empty}
+ # See for each list if we can omit the paragraphs and use li_span
+ # TODO: do this after
+ output.each do |c|
+ # Remove paragraphs that we can get rid of
+ if [:ul,:ol].include? c.node_type
+ if c.children.all? {|li| !li.want_my_paragraph} then
+ c.children.each do |d|
+ d.node_type = :li_span
+ d.children = d.children[0].children
+ end
+ end
+ end
+ if c.node_type == :definition_list
+ if c.children.all?{|defi| !defi.want_my_paragraph} then
+ c.children.each do |definition|
+ definition.definitions.each do |dd|
+ dd.children = dd.children[0].children
+ end
+ end
+ end
+ end
+ end
+
+ output
+ end
+
+
+
+ def read_ald(src)
+ if (l=src.shift_line) =~ AttributeDefinitionList
+ id = $1; al=$2;
+ al = read_attribute_list(CharSource.new(al,src), context=nil, break_on=[nil])
+ self.ald[id] = al;
+ return md_ald(id, al)
+ else
+ maruku_error "Bug Bug:\n#{l.inspect}"
+ return nil
+ end
+ end
+
+ # reads a header (with ----- or ========)
+ def read_header12(src)
+ line = src.shift_line.strip
+ al = nil
+ # Check if there is an IAL
+ if new_meta_data? and line =~ /^(.*)\{(.*)\}\s*$/
+ line = $1.strip
+ ial = $2
+ al = read_attribute_list(CharSource.new(ial,src), context=nil, break_on=[nil])
+ end
+ text = parse_lines_as_span [ line ]
+ level = src.cur_line.md_type == :header2 ? 2 : 1;
+ src.shift_line
+ return md_header(level, text, al)
+ end
+
+ # reads a header like '#### header ####'
+ def read_header3(src)
+ line = src.shift_line.strip
+ al = nil
+ # Check if there is an IAL
+ if new_meta_data? and line =~ /^(.*)\{(.*)\}\s*$/
+ line = $1.strip
+ ial = $2
+ al = read_attribute_list(CharSource.new(ial,src), context=nil, break_on=[nil])
+ end
+ level = num_leading_hashes(line)
+ text = parse_lines_as_span [strip_hashes(line)]
+ return md_header(level, text, al)
+ end
+
+ def read_xml_instruction(src, output)
+ m = /^\s*<\?((\w+)\s*)?(.*)$/.match src.shift_line
+ raise "BugBug" if not m
+ target = m[2] || ''
+ code = m[3]
+ until code =~ /\?>/
+ code += "\n"+src.shift_line
+ end
+ if not code =~ (/\?>\s*$/)
+ garbage = (/\?>(.*)$/.match(code))[1]
+ maruku_error "Trailing garbage on last line: #{garbage.inspect}:\n"+
+ add_tabs(code, 1, '|'), src
+ end
+ code.gsub!(/\?>\s*$/, '')
+
+ if target == 'mrk' && MaRuKu::Globals[:unsafe_features]
+ result = safe_execute_code(self, code)
+ if result
+ if result.kind_of? String
+ raise "Not expected"
+ else
+ output.push *result
+ end
+ end
+ else
+ output.push md_xml_instr(target, code)
+ end
+ end
+
+ def read_raw_html(src)
+ h = HTMLHelper.new
+ begin
+ h.eat_this(l=src.shift_line)
+# puts "\nBLOCK:\nhtml -> #{l.inspect}"
+ while src.cur_line and not h.is_finished?
+ l=src.shift_line
+# puts "html -> #{l.inspect}"
+ h.eat_this "\n"+l
+ end
+ rescue Exception => e
+ ex = e.inspect + e.backtrace.join("\n")
+ maruku_error "Bad block-level HTML:\n#{add_tabs(ex,1,'|')}\n", src
+ end
+ raw_html = h.stuff_you_read
+ return md_html(raw_html)
+ end
+
+ def read_paragraph(src)
+ lines = []
+ while src.cur_line
+ # :olist does not break
+ case t = src.cur_line.md_type
+ when :quote,:header3,:empty,:raw_html,:ref_definition,:ial,:xml_instr
+ break
+ when :olist,:ulist
+ break if src.next_line.md_type == t
+ end
+ break if src.cur_line.strip.size == 0
+ break if [:header1,:header2].include? src.next_line.md_type
+ break if any_matching_block_extension?(src.cur_line)
+
+ lines << src.shift_line
+ end
+# dbg_describe_ary(lines, 'PAR')
+ children = parse_lines_as_span(lines, src)
+
+ return md_par(children)
+ end
+
+ # Reads one list item, either ordered or unordered.
+ def read_list_item(src)
+ parent_offset = src.cur_index
+
+ item_type = src.cur_line.md_type
+ first = src.shift_line
+
+ # Ugly things going on inside `read_indented_content`
+ indentation = spaces_before_first_char(first)
+ break_list = [:ulist, :olist, :ial]
+ lines, want_my_paragraph =
+ read_indented_content(src,indentation, break_list, item_type)
+
+ # add first line
+ # Strip first '*', '-', '+' from first line
+ stripped = first[indentation, first.size-1]
+ lines.unshift stripped
+
+ #dbg_describe_ary(lines, 'LIST ITEM ')
+
+ src2 = LineSource.new(lines, src, parent_offset)
+ children = parse_blocks(src2)
+ with_par = want_my_paragraph || (children.size>1)
+
+ return md_li(children, with_par)
+ end
+
+ def read_abbreviation(src)
+ if not (l=src.shift_line) =~ Abbreviation
+ maruku_error "Bug: it's Andrea's fault. Tell him.\n#{l.inspect}"
+ end
+
+ abbr = $1
+ desc = $2
+
+ if (not abbr) or (abbr.size==0)
+ maruku_error "Bad abbrev. abbr=#{abbr.inspect} desc=#{desc.inspect}"
+ end
+
+ self.abbreviations[abbr] = desc
+
+ return md_abbr_def(abbr, desc)
+ end
+
+ def read_footnote_text(src)
+ parent_offset = src.cur_index
+
+ first = src.shift_line
+
+ if not first =~ FootnoteText
+ maruku_error "Bug (it's Andrea's fault)"
+ end
+
+ id = $1
+ text = $2
+
+ # Ugly things going on inside `read_indented_content`
+ indentation = 4 #first.size-text.size
+
+# puts "id =_#{id}_; text=_#{text}_ indent=#{indentation}"
+
+ break_list = [:footnote_text]
+ item_type = :footnote_text
+ lines, want_my_paragraph =
+ read_indented_content(src,indentation, break_list, item_type)
+
+ # add first line
+ if text && text.strip != "" then lines.unshift text end
+
+# dbg_describe_ary(lines, 'FOOTNOTE')
+ src2 = LineSource.new(lines, src, parent_offset)
+ children = parse_blocks(src2)
+
+ e = md_footnote(id, children)
+ self.footnotes[id] = e
+ return e
+ end
+
+
+ # This is the only ugly function in the code base.
+ # It is used to read list items, descriptions, footnote text
+ def read_indented_content(src, indentation, break_list, item_type)
+ lines =[]
+ # collect all indented lines
+ saw_empty = false; saw_anything_after = false
+ while src.cur_line
+ #puts "#{src.cur_line.md_type} #{src.cur_line.inspect}"
+ if src.cur_line.md_type == :empty
+ saw_empty = true
+ lines << src.shift_line
+ next
+ end
+
+ # after a white line
+ if saw_empty
+ # we expect things to be properly aligned
+ if (ns=number_of_leading_spaces(src.cur_line)) < indentation
+ #puts "breaking for spaces, only #{ns}: #{src.cur_line}"
+ break
+ end
+ saw_anything_after = true
+ else
+ break if break_list.include? src.cur_line.md_type
+# break if src.cur_line.md_type != :text
+ end
+
+
+ stripped = strip_indent(src.shift_line, indentation)
+ lines << stripped
+
+ #puts "Accepted as #{stripped.inspect}"
+
+ # You are only required to indent the first line of
+ # a child paragraph.
+ if stripped.md_type == :text
+ while src.cur_line && (src.cur_line.md_type == :text)
+ lines << strip_indent(src.shift_line, indentation)
+ end
+ end
+ end
+
+ want_my_paragraph = saw_anything_after ||
+ (saw_empty && (src.cur_line && (src.cur_line.md_type == item_type)))
+
+# dbg_describe_ary(lines, 'LI')
+ # create a new context
+
+ while lines.last && (lines.last.md_type == :empty)
+ lines.pop
+ end
+
+ return lines, want_my_paragraph
+ end
+
+
+ def read_quote(src)
+ parent_offset = src.cur_index
+
+ lines = []
+ # collect all indented lines
+ while src.cur_line && src.cur_line.md_type == :quote
+ lines << unquote(src.shift_line)
+ end
+# dbg_describe_ary(lines, 'QUOTE')
+
+ src2 = LineSource.new(lines, src, parent_offset)
+ children = parse_blocks(src2)
+ return md_quote(children)
+ end
+
+ def read_code(src)
+ # collect all indented lines
+ lines = []
+ while src.cur_line && ([:code, :empty].include? src.cur_line.md_type)
+ lines << strip_indent(src.shift_line, 4)
+ end
+
+ #while lines.last && (lines.last.md_type == :empty )
+ while lines.last && lines.last.strip.size == 0
+ lines.pop
+ end
+
+ while lines.first && lines.first.strip.size == 0
+ lines.shift
+ end
+
+ return nil if lines.empty?
+
+ source = lines.join("\n")
+
+# dbg_describe_ary(lines, 'CODE')
+
+ return md_codeblock(source)
+ end
+
+ # Reads a series of metadata lines with empty lines in between
+ def read_metadata(src)
+ hash = {}
+ while src.cur_line
+ case src.cur_line.md_type
+ when :empty; src.shift_line
+ when :metadata; hash.merge! parse_metadata(src.shift_line)
+ else break
+ end
+ end
+ hash
+ end
+
+
+ def read_ref_definition(src)
+ line = src.shift_line
+
+ # if link is incomplete, shift next line
+ if src.cur_line && (src.cur_line.md_type != :ref_definition) &&
+ ([1,2,3].include? number_of_leading_spaces(src.cur_line) )
+ line += " "+ src.shift_line
+ end
+
+# puts "total= #{line}"
+
+ match = LinkRegex.match(line)
+ if not match
+ error "Link does not respect format: '#{line}'"
+ end
+
+ id = match[1]; url = match[2]; title = match[3];
+ id = id.strip.downcase
+
+ hash = self.refs[id] = {:url=>url,:title=>title}
+
+ stuff=match[4]
+
+ if stuff
+ stuff.split.each do |couple|
+# puts "found #{couple}"
+ k, v = couple.split('=')
+ v ||= ""
+ if v[0,1]=='"' then v = v[1, v.size-2] end
+# puts "key:_#{k}_ value=_#{v}_"
+ hash[k.to_sym] = v
+ end
+ end
+# puts hash.inspect
+
+ return md_ref_def(id, url, meta={:title=>title})
+ end
+
+ def read_table(src)
+
+ def split_cells(s)
+ s.strip.split('|').select{|x|x.strip.size>0}.map{|x|x.strip}
+ end
+
+ head = split_cells(src.shift_line).map{|s| md_el(:head_cell, parse_lines_as_span([s])) }
+
+ separator=split_cells(src.shift_line)
+
+ align = separator.map { |s| s =~ Sep
+ if $1 and $2 then :center elsif $2 then :right else :left end }
+
+ num_columns = align.size
+
+ if head.size != num_columns
+ maruku_error "Table head does not have #{num_columns} columns: \n#{head.inspect}"
+ tell_user "I will ignore this table."
+ # XXX try to recover
+ return md_br()
+ end
+
+ rows = []
+
+ while src.cur_line && src.cur_line =~ /\|/
+ row = split_cells(src.shift_line).map{|s|
+ md_el(:cell, parse_lines_as_span([s]))}
+ if head.size != num_columns
+ maruku_error "Row does not have #{num_columns} columns: \n#{row.inspect}"
+ tell_user "I will ignore this table."
+ # XXX try to recover
+ return md_br()
+ end
+ rows << row
+ end
+
+ children = (head+rows).flatten
+ return md_el(:table, children, {:align => align})
+ end
+
+ # If current line is text, a definition list is coming
+ # if 1) text,empty,[text,empty]*,definition
+
+ def eventually_comes_a_def_list(src)
+ future = src.tell_me_the_future
+ ok = future =~ %r{^t+e?d}x
+# puts "future: #{future} - #{ok}"
+ ok
+ end
+
+
+ def read_definition(src)
+ # Read one or more terms
+ terms = []
+ while src.cur_line && src.cur_line.md_type == :text
+ terms << md_el(:definition_term, parse_lines_as_span([src.shift_line]))
+ end
+# dbg_describe_ary(terms, 'DT')
+
+ want_my_paragraph = false
+
+ raise "Chunky Bacon!" if not src.cur_line
+
+ # one optional empty
+ if src.cur_line.md_type == :empty
+ want_my_paragraph = true
+ src.shift_line
+ end
+
+ raise "Chunky Bacon!" if src.cur_line.md_type != :definition
+
+ # Read one or more definitions
+ definitions = []
+ while src.cur_line && src.cur_line.md_type == :definition
+ parent_offset = src.cur_index
+
+ first = src.shift_line
+ first =~ Definition
+ first = $1
+
+ # I know, it's ugly!!!
+
+ lines, w_m_p =
+ read_indented_content(src,4, [:definition], :definition)
+ want_my_paragraph ||= w_m_p
+
+ lines.unshift first
+
+# dbg_describe_ary(lines, 'DD')
+ src2 = LineSource.new(lines, src, parent_offset)
+ children = parse_blocks(src2)
+ definitions << md_el(:definition_data, children)
+ end
+
+ return md_el(:definition, terms+definitions, {
+ :terms => terms,
+ :definitions => definitions,
+ :want_my_paragraph => want_my_paragraph})
+ end
+end # BlockLevelParser
+end # MaRuKu
+end
+end
\ No newline at end of file
diff --git a/lib/maruku/input/parse_doc.rb b/lib/maruku/input/parse_doc.rb
new file mode 100644
index 00000000..07c6bfed
--- /dev/null
+++ b/lib/maruku/input/parse_doc.rb
@@ -0,0 +1,225 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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
+#++
+
+
+require 'iconv'
+
+
+module MaRuKu; module In; module Markdown; module BlockLevelParser
+
+ def parse_doc(s)
+
+ meta2 = parse_email_headers(s)
+ data = meta2[:data]
+ meta2.delete :data
+
+ self.attributes.merge! meta2
+
+=begin maruku_doc
+Attribute: encoding
+Scope: document
+Summary: Encoding for the document.
+
+If the `encoding` attribute is specified, then the content
+will be converted from the specified encoding to UTF-8.
+
+Conversion happens using the `iconv` library.
+=end
+
+ enc = self.attributes[:encoding]
+ self.attributes.delete :encoding
+ if enc && enc.downcase != 'utf-8'
+ converted = Iconv.new('utf-8', enc).iconv(data)
+
+# puts "Data: #{data.inspect}: #{data}"
+# puts "Conv: #{converted.inspect}: #{converted}"
+
+ data = converted
+ end
+
+ @children = parse_text_as_markdown(data)
+
+ if true #markdown_extra?
+ self.search_abbreviations
+ self.substitute_markdown_inside_raw_html
+ end
+
+ toc = create_toc
+
+ # use title if not set
+ if not self.attributes[:title] and toc.header_element
+ title = toc.header_element.to_s
+ self.attributes[:title] = title
+# puts "Set document title to #{title}"
+ end
+
+ # save for later use
+ self.toc = toc
+
+ # Now do the attributes magic
+ each_element do |e|
+ # default attribute list
+ if default = self.ald[e.node_type.to_s]
+ expand_attribute_list(default, e.attributes)
+ end
+ expand_attribute_list(e.al, e.attributes)
+# puts "#{e.node_type}: #{e.attributes.inspect}"
+ end
+
+=begin maruku_doc
+Attribute: unsafe_features
+Scope: global
+Summary: Enables execution of XML instructions.
+
+Disabled by default because of security concerns.
+=end
+
+ if Maruku::Globals[:unsafe_features]
+ self.execute_code_blocks
+ # TODO: remove executed code blocks
+ end
+ end
+
+ # Expands an attribute list in an Hash
+ def expand_attribute_list(al, result)
+ al.each do |k, v|
+ case k
+ when :class
+ if not result[:class]
+ result[:class] = v
+ else
+ result[:class] += " " + v
+ end
+ when :id; result[:id] = v
+ when :ref;
+ if self.ald[v]
+ already = (result[:expanded_references] ||= [])
+ if not already.include?(v)
+ already.push v
+ expand_attribute_list(self.ald[v], result)
+ else
+ already.push v
+ maruku_error "Circular reference between labels.\n\n"+
+ "Label #{v.inspect} calls itself via recursion.\nThe recursion is "+
+ (already.map{|x| x.inspect}.join(' => '))
+ end
+ else
+ if not result[:unresolved_references]
+ result[:unresolved_references] = v
+ else
+ result[:unresolved_references] << " #{v}"
+ end
+
+ result[v.to_sym] = true
+ end
+ else
+ result[k.to_sym]=v
+ end
+ end
+ end
+
+ def safe_execute_code(object, code)
+ begin
+ return object.instance_eval(code)
+ rescue Exception => e
+ maruku_error "Exception while executing this:\n"+
+ add_tabs(code, 1, ">")+
+ "\nThe error was:\n"+
+ add_tabs(e.inspect+"\n"+e.caller.join("\n"), 1, "|")
+ rescue RuntimeError => e
+ maruku_error "2: Exception while executing this:\n"+
+ add_tabs(code, 1, ">")+
+ "\nThe error was:\n"+
+ add_tabs(e.inspect, 1, "|")
+ rescue SyntaxError => e
+ maruku_error "2: Exception while executing this:\n"+
+ add_tabs(code, 1, ">")+
+ "\nThe error was:\n"+
+ add_tabs(e.inspect, 1, "|")
+ end
+ nil
+ end
+
+ def execute_code_blocks
+ self.each_element(:xml_instr) do |e|
+ if e.target == 'maruku'
+ result = safe_execute_code(e, e.code)
+ if result.kind_of?(String)
+ puts "Result is : #{result.inspect}"
+ end
+ end
+ end
+ end
+
+ def search_abbreviations
+ self.abbreviations.each do |abbrev, title|
+ reg = Regexp.new(Regexp.escape(abbrev))
+ self.replace_each_string do |s|
+ if m = reg.match(s)
+ e = md_abbr(abbrev.dup, title ? title.dup : nil)
+ [m.pre_match, e, m.post_match]
+ else
+ s
+ end
+ end
+ end
+ end
+
+ include REXML
+ # (PHP Markdown extra) Search for elements that have
+ # markdown=1 or markdown=block defined
+ def substitute_markdown_inside_raw_html
+ self.each_element(:raw_html) do |e|
+ doc = e.instance_variable_get :@parsed_html
+ if doc # valid html
+ # parse block-level markdown elements in these HTML tags
+ block_tags = ['div']
+
+ # use xpath to find elements with 'markdown' attribute
+ XPath.match(doc, "//*[attribute::markdown]" ).each do |e|
+# puts "Found #{e}"
+ # should we parse block-level or span-level?
+ parse_blocks = (e.attributes['markdown'] == 'block') ||
+ block_tags.include?(e.name)
+ # remove 'markdown' attribute
+ e.delete_attribute 'markdown'
+ # Select all text elements of e
+ XPath.match(e, "//text()" ).each { |original_text|
+ s = original_text.value.strip
+ if s.size > 0
+ el = md_el(:dummy,
+ parse_blocks ? parse_text_as_markdown(s) :
+ parse_lines_as_span([s]) )
+ p = original_text.parent
+ el.children_to_html.each do |x|
+ p.insert_before(original_text, x)
+ end
+ p.delete(original_text)
+
+ end
+ }
+
+ end
+
+ end
+ end
+ end
+
+end end end end
diff --git a/lib/maruku/input/parse_span_better.rb b/lib/maruku/input/parse_span_better.rb
new file mode 100644
index 00000000..211d7683
--- /dev/null
+++ b/lib/maruku/input/parse_span_better.rb
@@ -0,0 +1,692 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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
+#++
+
+
+require 'set'
+
+module MaRuKu; module In; module Markdown; module SpanLevelParser
+ include MaRuKu::Helpers
+
+ EscapedCharInText =
+ Set.new [?\\,?`,?*,?_,?{,?},?[,?],?(,?),?#,?.,?!,?|,?:,?+,?-,?>]
+
+ EscapedCharInQuotes =
+ Set.new [?\\,?`,?*,?_,?{,?},?[,?],?(,?),?#,?.,?!,?|,?:,?+,?-,?>,?',?"]
+
+ EscapedCharInInlineCode = [?\\,?`]
+
+ def parse_lines_as_span(lines, parent=nil)
+ parse_span_better lines.join("\n"), parent
+ end
+
+ def parse_span_better(string, parent=nil)
+ if not string.kind_of? String then
+ error "Passed #{string.class}." end
+
+ st = (string + "")
+ st.freeze
+ src = CharSource.new(st, parent)
+ read_span(src, EscapedCharInText, [nil])
+ end
+
+ # This is the main loop for reading span elements
+ #
+ # It's long, but not *complex* or difficult to understand.
+ #
+ #
+ def read_span(src, escaped, exit_on_chars, exit_on_strings=nil)
+ con = SpanContext.new
+ c = d = nil
+ while true
+ c = src.cur_char
+
+ # This is only an optimization which cuts 50% of the time used.
+ # (but you can't use a-zA-z in exit_on_chars)
+ if c && ((c>=?a && c<=?z) || ((c>=?A && c<=?Z)))
+ con.cur_string << src.shift_char
+ next
+ end
+
+ break if exit_on_chars && exit_on_chars.include?(c)
+ break if exit_on_strings && exit_on_strings.any? {|x| src.cur_chars_are x}
+
+ # check if there are extensions
+ if check_span_extensions(src, con)
+ next
+ end
+
+ case c = src.cur_char
+ when ?\ # it's space (32)
+ if src.cur_chars_are " \n"
+ src.ignore_chars(3)
+ con.push_element md_br()
+ next
+ else
+ src.ignore_char
+ con.push_space
+ end
+ when ?\n, ?\t
+ src.ignore_char
+ con.push_space
+ when ?`
+ read_inline_code(src,con)
+ when ?<
+ # It could be:
+ # 1) HTML "
>
+
+ case d = src.next_char
+ when ?<; # guillemettes
+ src.ignore_chars(2)
+ con.push_char ?<
+ con.push_char ?<
+ when ?!;
+ if src.cur_chars_are '
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ "
+
+
+end end end
+
diff --git a/lib/maruku/output/to_latex_strings.rb b/lib/maruku/output/to_latex_strings.rb
new file mode 100644
index 00000000..da043a79
--- /dev/null
+++ b/lib/maruku/output/to_latex_strings.rb
@@ -0,0 +1,64 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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
+
+ # These are TeX's special characters
+ LATEX_ADD_SLASH = [ ?{, ?}, ?$, ?&, ?#, ?_, ?%]
+
+ # These, we transform to {\tt \char}
+ LATEX_TO_CHARCODE = [ ?^, ?~, ?>,?<]
+
+ def escape_to_latex(s)
+ s2 = ""
+ s.each_byte do |b|
+ if LATEX_TO_CHARCODE.include? b
+ s2 += "{\\tt \\char#{b}}"
+ elsif LATEX_ADD_SLASH.include? b
+ s2 << ?\\ << b
+ elsif b == ?\\
+ # there is no backslash in cmr10 fonts
+ s2 += "$\\backslash$"
+ else
+ s2 << b
+ end
+ end
+ s2
+ end
+
+ # escapes special characters
+ def to_latex
+ s = escape_to_latex(self)
+ OtherGoodies.each do |k, v|
+ s.gsub!(k, v)
+ end
+ s
+ end
+
+ # other things that are good on the eyes
+ OtherGoodies = {
+ /(\s)LaTeX/ => '\1\\LaTeX\\xspace ', # XXX not if already \LaTeX
+# 'HTML' => '\\textsc{html}\\xspace ',
+# 'PDF' => '\\textsc{pdf}\\xspace '
+ }
+
+end
\ No newline at end of file
diff --git a/lib/maruku/output/to_markdown.rb b/lib/maruku/output/to_markdown.rb
new file mode 100644
index 00000000..98d9322d
--- /dev/null
+++ b/lib/maruku/output/to_markdown.rb
@@ -0,0 +1,164 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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
+ # XXX: markdown escaping
+ def to_md(c=nil)
+ to_s
+ end
+
+ # " andrea censi " => [" andrea ", "censi "]
+ def mysplit
+ split.map{|x| x+" "}
+ end
+end
+
+
+module MaRuKu; module Out; module Markdown
+
+ DefaultLineLength = 40
+
+ def to_md(context={})
+ children_to_md(context)
+ end
+
+ def to_md_paragraph(context)
+ line_length = context[:line_length] || DefaultLineLength
+ wrap(@children, line_length, context)+"\n"
+ end
+
+ def to_md_li_span(context)
+ len = (context[:line_length] || DefaultLineLength) - 2
+ s = add_tabs(wrap(@children, len-2, context), 1, ' ')
+ s[0] = ?*
+ s + "\n"
+ end
+
+ def to_md_abbr_def(context)
+ "*[#{self.abbr}]: #{self.text}\n"
+ end
+
+ def to_md_ol(context)
+ len = (context[:line_length] || DefaultLineLength) - 2
+ md = ""
+ self.children.each_with_index do |li, i|
+ s = add_tabs(w=wrap(li.children, len-2, context), 1, ' ')+"\n"
+ s[0,4] = "#{i+1}. "[0,4]
+# puts w.inspect
+ md += s
+ end
+ md + "\n"
+ end
+
+ def to_md_ul(context)
+ len = (context[:line_length] || DefaultLineLength) - 2
+ md = ""
+ self.children.each_with_index do |li, i|
+ w = wrap(li.children, len-2, context)
+# puts "W: "+ w.inspect
+ s = add_indent(w)
+# puts "S: " +s.inspect
+ s[0,1] = "-"
+ md += s
+ end
+ md + "\n"
+ end
+
+ def add_indent(s,char=" ")
+ t = s.split("\n").map{|x| char+x }.join("\n")
+ s << ?\n if t[-1] == ?\n
+ s
+ end
+
+ # Convert each child to html
+ def children_to_md(context)
+ array_to_md(@children, context)
+ end
+
+ def wrap(array, line_length, context)
+ out = ""
+ line = ""
+ array.each do |c|
+ if c.kind_of?(MDElement) && c.node_type == :linebreak
+ out << line.strip << " \n"; line="";
+ next
+ end
+
+ pieces =
+ if c.kind_of? String
+ c.to_md.mysplit
+ else
+ [c.to_md(context)].flatten
+ end
+
+ # puts "Pieces: #{pieces.inspect}"
+ pieces.each do |p|
+ if p.size + line.size > line_length
+ out << line.strip << "\n";
+ line = ""
+ end
+ line << p
+ end
+ end
+ out << line.strip << "\n" if line.size > 0
+ out << ?\n if not out[-1] == ?\n
+ out
+ end
+
+
+ def array_to_md(array, context, join_char='')
+ e = []
+ array.each do |c|
+ method = c.kind_of?(MDElement) ?
+ "to_md_#{c.node_type}" : "to_md"
+
+ if not c.respond_to?(method)
+ #raise "Object does not answer to #{method}: #{c.class} #{c.inspect[0,100]}"
+# tell_user "Using default for #{c.node_type}"
+ method = 'to_md'
+ end
+
+# puts "#{c.inspect} created with method #{method}"
+ h = c.send(method, context)
+
+ if h.nil?
+ raise "Nil md for #{c.inspect} created with method #{method}"
+ end
+
+ if h.kind_of?Array
+ e = e + h
+ else
+ e << h
+ end
+ end
+ e.join(join_char)
+ end
+
+end end end
+
+module MaRuKu; class MDDocument
+ alias old_md to_md
+ def to_md(context={})
+ s = old_md(context)
+# puts s
+ s
+ end
+end end
\ No newline at end of file
diff --git a/lib/maruku/output/to_s.rb b/lib/maruku/output/to_s.rb
new file mode 100644
index 00000000..577660e0
--- /dev/null
+++ b/lib/maruku/output/to_s.rb
@@ -0,0 +1,53 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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
+
+class MDElement
+
+ # Strips all formatting from the string
+ def to_s
+ children_to_s
+ end
+
+ def children_to_s
+ @children.join
+ end
+
+ # Generate an id for headers. Assumes @children is set.
+ def generate_id
+ title = children_to_s
+ title.gsub!(/ /,'_')
+ title.downcase!
+ title.gsub!(/[^\w_]/,'')
+ title.strip!
+
+ if title.size == 0
+ $uid ||= 0
+ $uid += 1
+ title = "id#{$uid}"
+ end
+
+ title
+ end
+end
+
+end
\ No newline at end of file
diff --git a/lib/maruku/string_utils.rb b/lib/maruku/string_utils.rb
new file mode 100644
index 00000000..80ffbb12
--- /dev/null
+++ b/lib/maruku/string_utils.rb
@@ -0,0 +1,184 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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
+#++
+
+
+# Boring stuff with strings.
+module MaRuKu; module Strings
+
+ def add_tabs(s,n=1,char="\t")
+ s.split("\n").map{|x| char*n+x }.join("\n")
+ end
+
+ TabSize = 4;
+
+ def split_lines(s)
+ s.split("\n")
+ end
+
+ # This parses email headers. Returns an hash.
+ #
+ # +hash['data']+ is the message.
+ #
+ # Keys are downcased, space becomes underscore, converted to symbols.
+ #
+ # My key: true
+ #
+ # becomes:
+ #
+ # {:my_key => true}
+ #
+ def parse_email_headers(s)
+ keys={}
+ match = (s =~ /((\w[\w\s]+: .*\n)+)\n/)
+ if match != 0
+ keys[:data] = s
+ else
+ keys[:data] = $'
+ headers = $1
+ headers.split("\n").each do |l|
+ k, v = l.split(':')
+ k, v = normalize_key_and_value(k, v)
+ k = k.to_sym
+# puts "K = #{k}, V=#{v}"
+ keys[k] = v
+ end
+ end
+ keys
+ end
+
+ # Keys are downcased, space becomes underscore, converted to symbols.
+ def normalize_key_and_value(k,v)
+ v = v ? v.strip : true # no value defaults to true
+ k = k.strip
+
+ # check synonyms
+ v = true if ['yes','true'].include?(v.to_s.downcase)
+ v = false if ['no','false'].include?(v.to_s.downcase)
+
+ k = k.downcase.gsub(' ','_')
+ return k, v
+ end
+
+ # Returns the number of leading spaces, considering that
+ # a tab counts as `TabSize` spaces.
+ def number_of_leading_spaces(s)
+ n=0; i=0;
+ while i < s.size
+ c = s[i,1]
+ if c == ' '
+ i+=1; n+=1;
+ elsif c == "\t"
+ i+=1; n+=TabSize;
+ else
+ break
+ end
+ end
+ n
+ end
+
+ # This returns the position of the first real char in a list item
+ #
+ # For example:
+ # '*Hello' # => 1
+ # '* Hello' # => 2
+ # ' * Hello' # => 3
+ # ' * Hello' # => 5
+ # '1.Hello' # => 2
+ # ' 1. Hello' # => 5
+
+ def spaces_before_first_char(s)
+ case s.md_type
+ when :ulist
+ i=0;
+ # skip whitespace if present
+ while s[i,1] =~ /\s/; i+=1 end
+ # skip indicator (+, -, *)
+ i+=1
+ # skip optional whitespace
+ while s[i,1] =~ /\s/; i+=1 end
+ return i
+ when :olist
+ i=0;
+ # skip whitespace
+ while s[i,1] =~ /\s/; i+=1 end
+ # skip digits
+ while s[i,1] =~ /\d/; i+=1 end
+ # skip dot
+ i+=1
+ # skip whitespace
+ while s[i,1] =~ /\s/; i+=1 end
+ return i
+ else
+ tell_user "BUG (my bad): '#{s}' is not a list"
+ 0
+ end
+ end
+
+ # Counts the number of leading '#' in the string
+ def num_leading_hashes(s)
+ i=0;
+ while i<(s.size-1) && (s[i,1]=='#'); i+=1 end
+ i
+ end
+
+ # Strips initial and final hashes
+ def strip_hashes(s)
+ s = s[num_leading_hashes(s), s.size]
+ i = s.size-1
+ while i > 0 && (s[i,1] =~ /(#|\s)/); i-=1; end
+ s[0, i+1].strip
+ end
+
+
+ # removes initial quote
+ def unquote(s)
+ s.gsub(/^>\s?/,'')
+ end
+
+ # toglie al massimo n caratteri
+ def strip_indent(s, n)
+ i = 0
+ while i < s.size && n>0
+ c = s[i,1]
+ if c == ' '
+ n-=1;
+ elsif c == "\t"
+ n-=TabSize;
+ else
+ break
+ end
+ i+=1
+ end
+ s[i, s.size-1]
+ end
+
+ def dbg_describe_ary(a, prefix='')
+ i = 0
+ a.each do |l|
+ puts "#{prefix} (#{i+=1})# #{l.inspect}"
+ end
+ end
+
+ def force_linebreak?(l)
+ l =~ / $/
+ end
+
+end
+end
diff --git a/lib/maruku/structures.rb b/lib/maruku/structures.rb
new file mode 100644
index 00000000..937e4a5b
--- /dev/null
+++ b/lib/maruku/structures.rb
@@ -0,0 +1,165 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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 Module
+ def safe_attr_accessor1(symbol, klass)
+ attr_reader symbol
+ code = <<-EOF
+ def #{symbol}=(val)
+ if not val.kind_of? #{klass}
+ s = "\nCould not assign an object of type \#{val.class} to #{symbol}.\n\n"
+ s += "Tried to assign object of class \#{val.class}:\n"+
+ "\#{val.inspect}\n"+
+ "to \#{self.class}::#{symbol} constrained to be of class #{klass}.\n"
+ raise s
+ end
+ @#{symbol} = val
+ end
+
+EOF
+ module_eval code
+ end
+
+ def safe_attr_accessor2(symbol, klass)
+ attr_accessor symbol
+ end
+
+ alias safe_attr_accessor safe_attr_accessor2
+end
+
+module MaRuKu
+
+# I did not want to have a class for each possible element.
+# Instead I opted to have only the class "MDElement"
+# that represents eveything in the document (paragraphs, headers, etc).
+#
+# You can tell what it is by the variable `node_type`.
+#
+# In the instance-variable `children` there are the children. These
+# can be of class 1) String or 2) MDElement.
+#
+# The @doc variable points to the document to which the MDElement
+# belongs (which is an instance of Maruku, subclass of MDElement).
+#
+# Attributes are contained in the hash `attributes`.
+# Keys are symbols (downcased, with spaces substituted by underscores)
+#
+# For example, if you write in the source document.
+#
+# Title: test document
+# My property: value
+#
+# content content
+#
+# You can access `value` by writing:
+#
+# @doc.attributes[:my_property] # => 'value'
+#
+# from whichever MDElement in the hierarchy.
+#
+class MDElement
+ # See helpers.rb for the list of allowed #node_type values
+ safe_attr_accessor :node_type, Symbol
+
+ # Children are either Strings or MDElement
+ safe_attr_accessor :children, Array
+
+ # An attribute list, may not be nil
+ safe_attr_accessor :al, Array #Maruku::AttributeList
+
+ # These are the processed attributes
+ safe_attr_accessor :attributes, Hash
+
+ # Reference of the document (which is of class Maruku)
+ attr_accessor :doc
+
+ def initialize(node_type=:unset, children=[], meta={},
+ al=MaRuKu::AttributeList.new )
+ super();
+ self.children = children
+ self.node_type = node_type
+
+ @attributes = {}
+
+ meta.each do |symbol, value|
+ self.instance_eval "
+ def #{symbol}; @#{symbol}; end
+ def #{symbol}=(val); @#{symbol}=val; end"
+ self.send "#{symbol}=", value
+ end
+
+ self.al = al || AttributeList.new
+
+ self.meta_priv = meta
+ end
+
+ attr_accessor :meta_priv
+
+ def ==(o)
+ ok = o.kind_of?(MDElement) &&
+ (self.node_type == o.node_type) &&
+ (self.meta_priv == o.meta_priv) &&
+ (self.children == o.children)
+
+ if not ok
+# puts "This:\n"+self.inspect+"\nis different from\n"+o.inspect+"\n\n"
+ end
+ ok
+ end
+end
+
+# This represents the whole document and holds global data.
+
+class MDDocument
+
+ safe_attr_accessor :refs, Hash
+ safe_attr_accessor :footnotes, Hash
+
+ # This is an hash. The key might be nil.
+ safe_attr_accessor :abbreviations, Hash
+
+ # Attribute lists definition
+ safe_attr_accessor :ald, Hash
+
+ # The order in which footnotes are used. Contains the id.
+ safe_attr_accessor :footnotes_order, Array
+
+ safe_attr_accessor :latex_required_packages, Array
+
+ def initialize(s=nil)
+ super(:document)
+ @doc = self
+
+ self.refs = {}
+ self.footnotes = {}
+ self.footnotes_order = []
+ self.abbreviations = {}
+ self.ald = {}
+ self.latex_required_packages = []
+
+ parse_doc(s) if s
+ end
+end
+
+
+end # MaRuKu
+
diff --git a/lib/maruku/structures_inspect.rb b/lib/maruku/structures_inspect.rb
new file mode 100644
index 00000000..c0064f90
--- /dev/null
+++ b/lib/maruku/structures_inspect.rb
@@ -0,0 +1,87 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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 inspect_more(a=nil,b=nil)
+ inspect
+ end
+end
+
+class Object
+ def inspect_more(a=nil,b=nil)
+ inspect
+ end
+end
+
+class Array
+ def inspect_more(compact, join_string, add_brackets=true)
+ s = map {|x|
+ x.kind_of?(String) ? x.inspect :
+ x.kind_of?(MaRuKu::MDElement) ? x.inspect(compact) :
+ (raise "WTF #{x.class} #{x.inspect}")
+ }.join(join_string)
+
+ add_brackets ? "[#{s}]" : s
+ end
+end
+
+class Hash
+ def inspect_ordered(a=nil,b=nil)
+ "{"+keys.map{|x|x.to_s}.sort.map{|x|x.to_sym}.
+ map{|k| k.inspect + "=>"+self[k].inspect}.join(',')+"}"
+ end
+end
+
+module MaRuKu
+class MDElement
+ def inspect(compact=true)
+ if compact
+ i2 = inspect2
+ return i2 if i2
+ end
+
+ "md_el(:%s,%s,%s,%s)" %
+ [
+ self.node_type,
+ children_inspect(compact),
+ @meta_priv.inspect_ordered,
+ self.al.inspect
+ ]
+ end
+
+ def children_inspect(compact=true)
+ s = @children.inspect_more(compact,', ')
+ if @children.empty?
+ "[]"
+ elsif s.size < 70
+ s
+ else
+ "[\n"+
+ add_tabs(@children.inspect_more(compact,",\n",false))+
+ "\n]"
+ end
+ end
+
+end
+
+end
+
diff --git a/lib/maruku/structures_iterators.rb b/lib/maruku/structures_iterators.rb
new file mode 100644
index 00000000..ff9c6b43
--- /dev/null
+++ b/lib/maruku/structures_iterators.rb
@@ -0,0 +1,61 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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
+
+class MDElement
+
+ # Yields to each element of specified node_type
+ # All elements if e_node_type is nil.
+ def each_element(e_node_type=nil, &block)
+ @children.each do |c|
+ if c.kind_of? MDElement
+ if (not e_node_type) || (e_node_type == c.node_type)
+ block.call c
+ end
+ c.each_element(e_node_type, &block)
+ end
+ end
+ end
+
+ # Apply passed block to each String in the hierarchy.
+ def replace_each_string(&block)
+ for c in @children
+ if c.kind_of? MDElement
+ c.replace_each_string(&block)
+ end
+ end
+
+ processed = []
+ until @children.empty?
+ c = @children.shift
+ if c.kind_of? String
+ result = block.call(c)
+ [*result].each do |e| processed << e end
+ else
+ processed << c
+ end
+ end
+ @children = processed
+ end
+
+end
+end
\ No newline at end of file
diff --git a/lib/maruku/tests/benchmark.rb b/lib/maruku/tests/benchmark.rb
new file mode 100644
index 00000000..9854e10b
--- /dev/null
+++ b/lib/maruku/tests/benchmark.rb
@@ -0,0 +1,82 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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
+#++
+
+
+require 'maruku'
+require 'bluecloth'
+
+
+data = $stdin.read
+
+num = 10
+
+if ARGV.size > 0 && ((n=ARGV[0].to_i) != 0)
+ num = n
+end
+
+methods =
+[
+
+ [Maruku, :to_html],
+ [BlueCloth, :to_html],
+ [Maruku, :to_latex]
+
+]
+
+#methods = [[Maruku, :class]]
+#num = 10
+
+stats =
+methods .map do |c, method|
+ puts "Computing for #{c}"
+
+ start = Time.now
+ doc = nil
+ for i in 1..num
+ $stdout.write "#{i} "; $stdout.flush
+ doc = c.new(data)
+ end
+ stop = Time.now
+ parsing = (stop-start)/num
+
+ start = Time.now
+ for i in 1..num
+ $stdout.write "#{i} "; $stdout.flush
+ s = doc.send method
+ end
+ stop = Time.now
+ rendering = (stop-start)/num
+
+ puts ("%s (%s): parsing %0.2f sec + rendering %0.2f sec "+
+ "= %0.2f sec ") % [c, method, parsing,rendering,parsing+rendering]
+
+ [c, method, parsing, rendering]
+end
+
+puts "\n\n\n"
+stats.each do |x| x.push(x[2]+x[3]) end
+max = stats.map{|x|x[4]}.max
+stats.sort! { |x,y| x[4] <=> y[4] } . reverse!
+for c, method, parsing, rendering, tot in stats
+ puts ("%20s: parsing %0.2f sec + rendering %0.2f sec "+
+ "= %0.2f sec (%0.2fx)") %
+ ["#{c} (#{method})", parsing,rendering,tot,max/tot]
+end
+
diff --git a/lib/maruku/tests/new_parser.rb b/lib/maruku/tests/new_parser.rb
new file mode 100644
index 00000000..c2229552
--- /dev/null
+++ b/lib/maruku/tests/new_parser.rb
@@ -0,0 +1,359 @@
+#--
+# Copyright (C) 2006 Andrea Censi
+#
+# 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
+#++
+
+
+require 'maruku'
+
+module MaRuKu; module Tests
+ # 5 accented letters in italian, encoded as UTF-8
+ AccIta8 = "\303\240\303\250\303\254\303\262\303\271"
+
+ # Same letters, written in ISO-8859-1 (one byte per letter)
+ AccIta1 = "\340\350\354\362\371"
+
+ # The word MA-RU-KU, written in katakana using UTF-8
+ Maruku8 = "\343\203\236\343\203\253\343\202\257"
+
+ def test_span_parser(verbose, break_on_first_error, quiet)
+ good_cases = [
+
+ ["", [], 'Empty string gives empty list'],
+ ["a", ["a"], 'Easy char'],
+ [" a", ["a"], 'First space in the paragraph is ignored'],
+ ["a\n \n", ["a"], 'Last spaces in the paragraphs are ignored'],
+ [' ', [], 'One char => nothing'],
+ [' ', [], 'Two chars => nothing'],
+ ['a b', ['a b'], 'Spaces are compressed'],
+ ['a b', ['a b'], 'Newlines are spaces'],
+ ["a\nb", ['a b'], 'Newlines are spaces'],
+ ["a\n b", ['a b'], 'Compress newlines 1'],
+ ["a \nb", ['a b'], 'Compress newlines 2'],
+ [" \nb", ['b'], 'Compress newlines 3'],
+ ["\nb", ['b'], 'Compress newlines 4'],
+ ["b\n", ['b'], 'Compress newlines 5'],
+ ["\n", [], 'Compress newlines 6'],
+ ["\n\n\n", [], 'Compress newlines 7'],
+
+ [nil, :throw, "Should throw on nil input"],
+
+ # Code blocks
+ ["`" , :throw, 'Unclosed single ticks'],
+ ["``" , :throw, 'Unclosed double ticks'],
+ ["`a`" , [md_code('a')], 'Simple inline code'],
+ ["`` ` ``" , [md_code('`')], ],
+ ["`` \\` ``" , [md_code('\\`')], ],
+ ["``a``" , [md_code('a')], ],
+ ["`` a ``" , [md_code('a')], ],
+
+ # Newlines
+ ["a \n", ['a',md_el(:linebreak)], 'Two spaces give br.'],
+ ["a \n", ['a'], 'Newlines 2'],
+ [" \n", [md_el(:linebreak)], 'Newlines 3'],
+ [" \n \n", [md_el(:linebreak),md_el(:linebreak)],'Newlines 3'],
+ [" \na \n", [md_el(:linebreak),'a',md_el(:linebreak)],'Newlines 3'],
+
+ # Inline HTML
+ ["a < b", ['a < b'], '< can be on itself'],
+ ["", [md_html('')], 'HR will be sanitized'],
+ ["", [md_html('')], 'Closed tag is ok'],
+ ["", [md_html('')], 'Closed tag is ok 2'],
+ ["a", [md_html(''),'a'], 'Closed tag is ok 2'],
+ ["a", [md_html(''),'a'], 'Inline HTML 1'],
+ ["ea", [md_html('e'),'a'], 'Inline HTML 2'],
+ ["aeb", ['a',md_html('e'),'b'], 'Inline HTML 3'],
+ ["eaf",
+ [md_html('e'),'a',md_html('f')],
+ 'Inline HTML 4'],
+ ["efa",
+ [md_html('e'),md_html('f'),'a'],
+ 'Inline HTML 5'],
+
+ ["", [md_html("")], 'Attributes'],
+ [""],
+
+ # emphasis
+ ["**", :throw, 'Unclosed double **'],
+ ["\\*", ['*'], 'Escaping of *'],
+ ["a *b* ", ['a ', md_em('b')], 'Emphasis 1'],
+ ["a *b*", ['a ', md_em('b')], 'Emphasis 2'],
+ ["a * b", ['a * b'], 'Emphasis 3'],
+ ["a * b*", :throw, 'Unclosed emphasis'],
+ # same with underscore
+ ["__", :throw, 'Unclosed double __'],
+ ["\\_", ['_'], 'Escaping of _'],
+ ["a _b_ ", ['a ', md_em('b')], 'Emphasis 4'],
+ ["a _b_", ['a ', md_em('b')], 'Emphasis 5'],
+ ["a _ b", ['a _ b'], 'Emphasis 6'],
+ ["a _ b_", :throw, 'Unclosed emphasis'],
+ ["_b_", [md_em('b')], 'Emphasis 7'],
+ ["_b_ _c_", [md_em('b'),' ',md_em('c')], 'Emphasis 8'],
+ ["_b__c_", [md_em('b'),md_em('c')], 'Emphasis 9'],
+ # strong
+ ["**a*", :throw, 'Unclosed double ** 2'],
+ ["\\**a*", ['*', md_em('a')], 'Escaping of *'],
+ ["a **b** ", ['a ', md_strong('b')], 'Emphasis 1'],
+ ["a **b**", ['a ', md_strong('b')], 'Emphasis 2'],
+ ["a ** b", ['a ** b'], 'Emphasis 3'],
+ ["a ** b**", :throw, 'Unclosed emphasis'],
+ ["**b****c**", [md_strong('b'),md_strong('c')], 'Emphasis 9'],
+ # strong (with underscore)
+ ["__a_", :throw, 'Unclosed double __ 2'],
+ ["\\__a_", ['_', md_em('a')], 'Escaping of _'],
+ ["a __b__ ", ['a ', md_strong('b')], 'Emphasis 1'],
+ ["a __b__", ['a ', md_strong('b')], 'Emphasis 2'],
+ ["a __ b", ['a __ b'], 'Emphasis 3'],
+ ["a __ b__", :throw, 'Unclosed emphasis'],
+ ["__b____c__", [md_strong('b'),md_strong('c')], 'Emphasis 9'],
+ # extra strong
+ ["***a**", :throw, 'Unclosed triple *** '],
+ ["\\***a**", ['*', md_strong('a')], 'Escaping of *'],
+ ["a ***b*** ", ['a ', md_emstrong('b')], 'Strong elements'],
+ ["a ***b***", ['a ', md_emstrong('b')]],
+ ["a *** b", ['a *** b']],
+ ["a ** * b", ['a ** * b']],
+ ["***b******c***", [md_emstrong('b'),md_emstrong('c')]],
+ ["a *** b***", :throw, 'Unclosed emphasis'],
+ # same with underscores
+ ["___a__", :throw, 'Unclosed triple *** '],
+ ["\\___a__", ['_', md_strong('a')], 'Escaping of *'],
+ ["a ___b___ ", ['a ', md_emstrong('b')], 'Strong elements'],
+ ["a ___b___", ['a ', md_emstrong('b')]],
+ ["a ___ b", ['a ___ b']],
+ ["a __ _ b", ['a __ _ b']],
+ ["___b______c___", [md_emstrong('b'),md_emstrong('c')]],
+ ["a ___ b___", :throw, 'Unclosed emphasis'],
+ # mixing is bad
+ ["*a_", :throw, 'Mixing is bad'],
+ ["_a*", :throw],
+ ["**a__", :throw],
+ ["__a**", :throw],
+ ["___a***", :throw],
+ ["***a___", :throw],
+ # links of the form [text][ref]
+ ["\\[a]", ["[a]"], 'Escaping 1'],
+ ["\\[a\\]", ["[a]"], 'Escaping 2'],
+ ["[a]", ["a"], 'Not a link'],
+ ["[a][]", [ md_link(["a"],'')], 'Empty link'],
+ ["[a][]b", [ md_link(["a"],''),'b'], 'Empty link'],
+ ["[a\\]][]", [ md_link(["a]"],'')], 'Escape inside link'],
+
+ ["[a", :throw, 'Link not closed'],
+ ["[a][", :throw, 'Ref not closed'],
+
+ # links of the form [text](url)
+ ["\\[a](b)", ["[a](b)"], 'Links'],
+ ["[a](url)c", [md_im_link(['a'],'url'),'c'], 'url'],
+ ["[a]( url )c" ],
+ ["[a] ( url )c" ],
+ ["[a] ( url)c" ],
+
+ ["[a](ur:/l/ 'Title')", [md_im_link(['a'],'ur:/l/','Title')],
+ 'url and title'],
+ ["[a] ( ur:/l/ \"Title\")" ],
+ ["[a] ( ur:/l/ \"Title\")" ],
+ ["[a]( ur:/l/ Title)", :throw, "Must quote title" ],
+
+ ["[a](url 'Tit\\\"l\\\\e')", [md_im_link(['a'],'url','Tit"l\\e')],
+ 'url and title escaped'],
+ ["[a] ( url \"Tit\\\"l\\\\e\")" ],
+ ["[a] ( url \"Tit\\\"l\\\\e\" )" ],
+ ['[a] ( url "Tit\\"l\\\\e" )' ],
+ ["[a]()", [md_im_link(['a'],'')], 'No URL is OK'],
+
+ ["[a](\"Title\")", :throw, "No url specified" ],
+ ["[a](url \"Title)", :throw, "Unclosed quotes" ],
+ ["[a](url \"Title\\\")", :throw],
+ ["[a](url \"Title\" ", :throw],
+
+ ["[a](url \'Title\")", :throw, "Mixing is bad" ],
+ ["[a](url \"Title\')"],
+
+ ["[a](/url)", [md_im_link(['a'],'/url')], 'Funny chars in url'],
+ ["[a](#url)", [md_im_link(['a'],'#url')]],
+ ["[a]()", [md_im_link(['a'],'/script?foo=1&bar=2')]],
+
+
+ # Images
+ ["\\![a](url)", ['!', md_im_link(['a'],'url') ], 'Escaping images'],
+
+ ["![a](url)", [md_im_image(['a'],'url')], 'Image no title'],
+ ["![a]( url )" ],
+ ["![a] ( url )" ],
+ ["![a] ( url)" ],
+
+ ["![a](url 'ti\"tle')", [md_im_image(['a'],'url','ti"tle')], 'Image with title'],
+ ['![a]( url "ti\\"tle")' ],
+
+ ["![a](url", :throw, 'Invalid images'],
+ ["![a( url )" ],
+ ["![a] ('url )" ],
+
+ ["![a][imref]", [md_image(['a'],'imref')], 'Image with ref'],
+ ["![a][ imref]"],
+ ["![a][ imref ]"],
+ ["![a][\timref\t]"],
+
+
+ ['',
+ [md_url('http://example.com/?foo=1&bar=2')], 'Immediate link'],
+ ['ab',
+ ['a',md_url('http://example.com/?foo=1&bar=2'),'b'] ],
+ ['',
+ [md_email('andrea@censi.org')], 'Email address'],
+ [''],
+ ["Developmen ",
+ ["Developmen ", md_url("http://rubyforge.org/projects/maruku/")]],
+ ["ab", ['a',md_html(''),'b'],
+ 'HTML Comment'],
+
+ ["a|\Z)/m)
+ start_group :comment, matched
+ else
+ case peek(1)
+ when "<"
+ start_group :punct, getch
+ case peek(1)
+ when "?"
+ append getch
+ when "/"
+ append getch
+ when "!"
+ append getch
+ end
+ start_group :normal, matched if scan( /\s+/ )
+ if scan( /([-\w]+):([-\w]+)/ )
+ start_group :namespace, subgroup(1)
+ start_group :punct, ":"
+ start_group :tag, subgroup(2)
+ elsif scan( /[-\w]+/ )
+ start_group :tag, matched
+ end
+ @in_tag = true
+ when "&"
+ if scan( /&\S{1,10};/ )
+ start_group :entity, matched
+ else
+ start_group :normal, scan( /&/ )
+ end
+ end
+ end
+ else
+ append scan_until( /\Z/ )
+ end
+ end
+
+ private
+
+ # Scan the string starting at the current position, with the given
+ # delimiter character.
+ def scan_string( delim )
+ start_group :punct, delim
+ match = /(?=[&\\]|#{delim})/
+ loop do
+ break unless ( text = scan_until( match ) )
+ start_group :string, text unless text.empty?
+ case peek(1)
+ when "&"
+ if scan( /&\S{1,10};/ )
+ start_group :entity, matched
+ else
+ start_group :string, getch
+ end
+ when "\\"
+ start_group :string, getch
+ append getch || ""
+ when delim
+ start_group :punct, getch
+ break
+ end
+ end
+ end
+
+ end
+
+ SYNTAX["xml"] = XML
+
+end
diff --git a/lib/syntax/lang/yaml.rb b/lib/syntax/lang/yaml.rb
new file mode 100644
index 00000000..53b052db
--- /dev/null
+++ b/lib/syntax/lang/yaml.rb
@@ -0,0 +1,105 @@
+require 'syntax'
+
+module Syntax
+
+ # A simple implementation of an YAML lexer. It handles most cases. It is
+ # not a validating lexer.
+ class YAML < Tokenizer
+
+ # Step through a single iteration of the tokenization process. This will
+ # yield (potentially) many tokens, and possibly zero tokens.
+ def step
+ if bol?
+ case
+ when scan(/---(\s*.+)?$/)
+ start_group :document, matched
+ when scan(/(\s*)([a-zA-Z][-\w]*)(\s*):/)
+ start_group :normal, subgroup(1)
+ start_group :key, subgroup(2)
+ start_group :normal, subgroup(3)
+ start_group :punct, ":"
+ when scan(/(\s*)-/)
+ start_group :normal, subgroup(1)
+ start_group :punct, "-"
+ when scan(/\s*$/)
+ start_group :normal, matched
+ when scan(/#.*$/)
+ start_group :comment, matched
+ else
+ append getch
+ end
+ else
+ case
+ when scan(/[\n\r]+/)
+ start_group :normal, matched
+ when scan(/[ \t]+/)
+ start_group :normal, matched
+ when scan(/!+(.*?^)?\S+/)
+ start_group :type, matched
+ when scan(/&\S+/)
+ start_group :anchor, matched
+ when scan(/\*\S+/)
+ start_group :ref, matched
+ when scan(/\d\d:\d\d:\d\d/)
+ start_group :time, matched
+ when scan(/\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d(\.\d+)? [-+]\d\d:\d\d/)
+ start_group :date, matched
+ when scan(/['"]/)
+ start_group :punct, matched
+ scan_string matched
+ when scan(/:\w+/)
+ start_group :symbol, matched
+ when scan(/[:]/)
+ start_group :punct, matched
+ when scan(/#.*$/)
+ start_group :comment, matched
+ when scan(/>-?/)
+ start_group :punct, matched
+ start_group :normal, scan(/.*$/)
+ append getch until eos? || bol?
+ return if eos?
+ indent = check(/ */)
+ start_group :string
+ loop do
+ line = check_until(/[\n\r]|\Z/)
+ break if line.nil?
+ if line.chomp.length > 0
+ this_indent = line.chomp.match( /^\s*/ )[0]
+ break if this_indent.length < indent.length
+ end
+ append scan_until(/[\n\r]|\Z/)
+ end
+ else
+ start_group :normal, scan_until(/(?=$|#)/)
+ end
+ end
+ end
+
+ private
+
+ def scan_string( delim )
+ regex = /(?=[#{delim=="'" ? "" : "\\\\"}#{delim}])/
+ loop do
+ text = scan_until( regex )
+ if text.nil?
+ start_group :string, scan_until( /\Z/ )
+ break
+ else
+ start_group :string, text unless text.empty?
+ end
+
+ case peek(1)
+ when "\\"
+ start_group :expr, scan(/../)
+ else
+ start_group :punct, getch
+ break
+ end
+ end
+ end
+
+ end
+
+ SYNTAX["yaml"] = YAML
+
+end
diff --git a/lib/syntax/version.rb b/lib/syntax/version.rb
new file mode 100644
index 00000000..d5330468
--- /dev/null
+++ b/lib/syntax/version.rb
@@ -0,0 +1,9 @@
+module Syntax
+ module Version
+ MAJOR=1
+ MINOR=0
+ TINY=0
+
+ STRING=[MAJOR,MINOR,TINY].join('.')
+ end
+end
diff --git a/log/production.log b/log/production.log
new file mode 100644
index 00000000..8b84199c
--- /dev/null
+++ b/log/production.log
@@ -0,0 +1,3 @@
+# Logfile created on Mon Jan 22 07:45:04 CST 2007 by logger.rb/1.5.2.7
+Migrating to Beta1Schema (1)
+Migrating to Beta2ChangesBulk (2)
diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css
index e676f3b9..2d6aedf3 100644
--- a/public/stylesheets/instiki.css
+++ b/public/stylesheets/instiki.css
@@ -317,4 +317,9 @@ div.errorExplanation p,div.errorExplanation li {
border:none;
margin:0;
padding:0;
-}
\ No newline at end of file
+}
+
+merror {display:inline;font-size:1em;}
+math[display=block] {overflow:auto;}
+math { white-space: nowrap }
+.maruku-eq-number {float:right}