Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit of discount ruby extension at 1.2.6

BSD license taken from  the original Discount distribution.

See the README and the following for more information:

http://www.pell.portland.or.us/~orc/Code/markdown/
  • Loading branch information...
commit 7c232af20fff48173dd1f2e9a93cd28e9984fd7b 0 parents
@rtomayko rtomayko authored
11 .gitignore
@@ -0,0 +1,11 @@
+ext/discount.o
+ext/docheader.o
+ext/dumptree.o
+ext/generate.o
+ext/markdown.o
+ext/mkdio.o
+ext/rbstrio.o
+ext/resource.o
+ext/discount.bundle
+lib/discount.bundle
+ext/Makefile
52 COPYING
@@ -0,0 +1,52 @@
+The core Discount C sources are
+Copyright (C) 2007 David Loren Parsons.
+
+The Discount Ruby extension sources are
+Copyright (C) 2008 Ryan Tomayko.
+
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicence, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions, and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution, and in the same place and form as other
+ copyright, license and disclaimer information.
+
+ 3. The end-user documentation included with the redistribution, if
+ any, must include the following acknowledgment:
+
+ This product includes software developed by
+ David Loren Parsons <http://www.pell.portland.or.us/~orc>
+
+ in the same place and form as other third-party acknowledgments.
+ Alternately, this acknowledgment may appear in the software
+ itself, in the same form and location as other such third-party
+ acknowledgments.
+
+ 4. Except as contained in this notice, the name of David Loren
+ Parsons shall not be used in advertising or otherwise to promote
+ the sale, use or other dealings in this Software without prior
+ written authorization from David Loren Parsons.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL DAVID LOREN PARSONS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
17 README
@@ -0,0 +1,17 @@
+Discount (For Ruby)
+===================
+
+Discount is a implementation of John Gruber's Markdown markup
+language in C. It implements all of the language as described in
+<http://daringfireball.net/projects/markdown/syntax>
+and passes the Markdown test suite at
+<http://daringfireball.net/projects/downloads/MarkdownTest_1.0.zip>
+
+Discount was developed by
+David Loren Parsons <http://www.pell.portland.or.us/~orc>. The Ruby
+extension was developed by Ryan Tomayko <http://tomayko.com/>.
+
+Discount is free software; it is released under a BSD-style license
+that allows you to do as you wish with it as long as you don't attempt
+to claim it as your own work. The Ruby Discount extension adopts
+Discount's license verbatim. See the file COPYING for more information.
115 Rakefile
@@ -0,0 +1,115 @@
+require 'rake/clean'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+
+task :default => :test
+
+DLEXT = Config::CONFIG['DLEXT']
+VERS = '1.2.6'
+
+spec =
+ Gem::Specification.new do |s|
+ s.name = "discount"
+ s.version = VERS
+ s.summary = "Discount Markdown Implementation"
+ s.files = FileList['README','COPYING','Rakefile','test.rb','{lib,ext}/**.rb','ext/*.{c,h}']
+ s.bindir = 'bin'
+ s.require_path = 'lib'
+ s.has_rdoc = true
+ s.extra_rdoc_files = ['README', 'COPYING']
+ s.test_files = Dir['test.rb']
+ s.extensions = ['ext/extconf.rb']
+
+ s.author = 'Ryan Tomayko'
+ s.email = 'r@tomayko.com'
+ s.homepage = 'http://github.com/rtomayko/rdiscount'
+ s.rubyforge_project = 'wink'
+ end
+
+ Rake::GemPackageTask.new(spec) do |p|
+ p.gem_spec = spec
+ p.need_tar_gz = true
+ p.need_tar = false
+ p.need_zip = false
+ end
+
+namespace :submodule do
+ desc 'Init the upstream submodule'
+ task :init do |t|
+ unless File.exist? 'discount/markdown.c'
+ rm_rf 'discount'
+ sh 'git submodule init discount'
+ sh 'git submodule update discount'
+ end
+ end
+
+ desc 'Update the discount submodule'
+ task :update => :init do
+ sh 'git submodule update discount' unless File.symlink?('discount')
+ end
+
+ file 'discount/markdown.c' do
+ Rake::Task['submodule:init'].invoke
+ end
+ task :exist => 'discount/markdown.c'
+end
+
+desc 'Gather required discount sources into extension directory'
+task :gather => 'submodule:exist' do |t|
+ files =
+ FileList[
+ 'discount/{markdown,mkdio,amalloc,cstring}.h',
+ 'discount/{markdown,docheader,dumptree,generate,mkdio,resource}.c'
+ ]
+ cp files, 'ext/',
+ :preserve => true,
+ :verbose => true
+end
+
+file 'ext/Makefile' => FileList['ext/{*.c,*.h,*.rb}'] do
+ chdir('ext') { ruby 'extconf.rb' }
+end
+CLEAN.include 'ext/Makefile'
+
+file "ext/discount.#{DLEXT}" => FileList['ext/Makefile', 'ext/*.{c,h,rb}'] do |f|
+ sh 'cd ext && make'
+end
+CLEAN.include 'ext/*.{o,bundle,so,dll}'
+
+file "lib/discount.#{DLEXT}" => "ext/discount.#{DLEXT}" do |f|
+ cp f.prerequisites, "lib/", :preserve => true
+end
+
+desc 'Build the discount extension'
+task :build => "lib/discount.#{DLEXT}"
+
+desc 'Run unit tests'
+task 'test:unit' => [:build] do |t|
+ ruby 'test.rb'
+end
+
+desc 'Run conformance tests'
+task 'test:conformance' => %w[submodule:exist build] do |t|
+ script = "#{pwd}/bin/rdiscount"
+ chdir('MarkdownTest_1.0.3') do
+ sh "./MarkdownTest.pl --script='#{script}' --tidy"
+ end
+end
+
+desc 'Run unit and conformance tests'
+task :test => %w[test:unit test:conformance]
+
+
+# ==========================================================
+# Rubyforge
+# ==========================================================
+
+PKGNAME = "pkg/discount-#{VERS}"
+
+desc 'Publish new release to rubyforge'
+task :release => [ "#{PKGNAME}.gem", "#{PKGNAME}.tar.gz" ] do |t|
+ sh <<-end
+ rubyforge add_release wink discount #{VERS} #{PKGNAME}.gem &&
+ rubyforge add_file wink discount #{VERS} #{PKGNAME}.tar.gz
+ end
+end
5 bin/rdiscount
@@ -0,0 +1,5 @@
+#!/usr/bin/env ruby
+
+$: << File.expand_path(File.dirname(__FILE__) + "/../lib")
+require 'discount'
+STDOUT.write(Discount.new(ARGF.read).to_html)
29 ext/amalloc.h
@@ -0,0 +1,29 @@
+/*
+ * debugging malloc()/realloc()/calloc()/free() that attempts
+ * to keep track of just what's been allocated today.
+ */
+#ifndef AMALLOC_D
+#define AMALLOC_D
+
+#include "config.h"
+
+#ifdef USE_AMALLOC
+
+extern void *amalloc(int);
+extern void *acalloc(int,int);
+extern void *arealloc(void*,int);
+extern void afree(void*);
+extern void adump();
+
+#define malloc amalloc
+#define calloc acalloc
+#define realloc arealloc
+#define free afree
+
+#else
+
+#define adump() (void)1
+
+#endif
+
+#endif/*AMALLOC_D*/
14 ext/config.h
@@ -0,0 +1,14 @@
+
+/* rdiscount extension configuration */
+
+#undef USE_AMALLOC
+
+#define TABSTOP 4
+
+#define COINTOSS() (random()&1)
+
+#define HAVE_SRANDOM 1
+
+#define INITRNG(x) srandom((unsigned int)x)
+
+#define HAVE_RANDOM 1
68 ext/cstring.h
@@ -0,0 +1,68 @@
+/* two template types: STRING(t) which defines a pascal-style string
+ * of element (t) [STRING(char) is the closest to the pascal string],
+ * and ANCHOR(t) which defines a baseplate that a linked list can be
+ * built up from. [The linked list /must/ contain a ->next pointer
+ * for linking the list together with.]
+ */
+#ifndef _CSTRING_D
+#define _CSTRING_D
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "amalloc.h"
+
+/* expandable Pascal-style string.
+ */
+#define STRING(type) struct { type *text; int size, alloc; }
+
+#define CREATE(x) T(x) = (void*)(S(x) = (x).alloc = 0)
+#define EXPAND(x) (S(x)++)[(S(x) < (x).alloc) \
+ ? (T(x)) \
+ : (T(x) = T(x) ? realloc(T(x), sizeof T(x)[0] * ((x).alloc += 100)) \
+ : malloc(sizeof T(x)[0] * ((x).alloc += 100)) )]
+
+#define DELETE(x) (x).alloc ? (free(T(x)), S(x) = (x).alloc = 0) \
+ : ( S(x) = 0 )
+#define CLIP(t,i,sz) \
+ ( ((i) >= 0) && ((sz) > 0) && (((i)+(sz)) <= S(t)) ) ? \
+ (memmove(&T(t)[i], &T(t)[i+sz], (S(t)-(i+sz)+1)*sizeof(T(t)[0])), \
+ S(t) -= (sz)) : -1
+
+#define RESERVE(x, sz) T(x) = ((x).alloc > S(x) + (sz) \
+ ? T(x) \
+ : T(x) \
+ ? realloc(T(x), sizeof T(x)[0] * ((x).alloc = 100+(sz)+S(x))) \
+ : malloc(sizeof T(x)[0] * ((x).alloc = 100+(sz)+S(x))))
+#define SUFFIX(t,p,sz) \
+ memcpy(((S(t) += (sz)) - (sz)) + \
+ (T(t) = T(t) ? realloc(T(t), sizeof T(t)[0] * ((t).alloc += sz)) \
+ : malloc(sizeof T(t)[0] * ((t).alloc += sz))), \
+ (p), sizeof(T(t)[0])*(sz))
+
+#define PREFIX(t,p,sz) \
+ RESERVE( (t), (sz) ); \
+ if ( S(t) ) { memmove(T(t)+(sz), T(t), S(t)); } \
+ memcpy( T(t), (p), (sz) ); \
+ S(t) += (sz)
+
+/* reference-style links (and images) are stored in an array
+ */
+#define T(x) (x).text
+#define S(x) (x).size
+
+/* abstract anchor type that defines a list base
+ * with a function that attaches an element to
+ * the end of the list.
+ *
+ * the list base field is named .text so that the T()
+ * macro will work with it.
+ */
+#define ANCHOR(t) struct { t *text, *end; }
+
+#define ATTACH(t, p) ( (t).text ?( ((t).end->next = (p)), ((t).end = (p)) ) \
+ :( ((t).text = (t).end = (p)) ) )
+
+typedef STRING(char) Cstring;
+
+#endif/*_CSTRING_D*/
48 ext/discount.c
@@ -0,0 +1,48 @@
+#include <stdio.h>
+#include "ruby.h"
+#include "mkdio.h"
+#include "rbstrio.h"
+
+static VALUE rb_cDiscount;
+
+static ID id_text;
+static ID id_smart;
+static ID id_notes;
+
+
+static VALUE
+rb_discount_to_html(VALUE self)
+{
+ /* grab char pointer to markdown input text */
+ VALUE text = rb_funcall(self, id_text, 0);
+ Check_Type(text, T_STRING);
+
+ /* allocate a ruby string buffer and wrap it in a stream */
+ VALUE buf = rb_str_buf_new(4096);
+ FILE *stream = rb_str_io_new(buf);
+
+ /* compile flags */
+ int flags = MKD_TABSTOP | MKD_NOHEADER;
+ if (rb_funcall(self, id_smart, 0) != Qtrue )
+ flags = flags | MKD_NOPANTS;
+
+ MMIOT *doc = mkd_string(RSTRING(text)->ptr, RSTRING(text)->len, flags);
+ markdown(doc, stream, flags);
+
+ fclose(stream);
+
+ return buf;
+}
+
+void Init_discount()
+{
+ /* Initialize frequently used Symbols */
+ id_text = rb_intern("text");
+ id_smart = rb_intern("smart");
+ id_notes = rb_intern("notes");
+
+ rb_cDiscount = rb_define_class("Discount", rb_cObject);
+ rb_define_method(rb_cDiscount, "to_html", rb_discount_to_html, 0);
+}
+
+// vim: ts=4 sw=4
43 ext/docheader.c
@@ -0,0 +1,43 @@
+/*
+ * docheader -- get values from the document header
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+#define afterdle(t) (T((t)->text) + (t)->dle)
+
+char *
+mkd_doc_title(Document *doc)
+{
+ if ( doc && doc->headers )
+ return afterdle(doc->headers);
+ return 0;
+}
+
+
+char *
+mkd_doc_author(Document *doc)
+{
+ if ( doc && doc->headers && doc->headers->next )
+ return afterdle(doc->headers->next);
+ return 0;
+}
+
+
+char *
+mkd_doc_date(Document *doc)
+{
+ if ( doc && doc->headers && doc->headers->next && doc->headers->next->next )
+ return afterdle(doc->headers->next->next);
+ return 0;
+}
147 ext/dumptree.c
@@ -0,0 +1,147 @@
+/* markdown: a C implementation of John Gruber's Markdown markup language.
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include <stdio.h>
+#include "markdown.h"
+#include "cstring.h"
+#include "amalloc.h"
+
+struct frame {
+ int indent;
+ char c;
+};
+
+typedef STRING(struct frame) Stack;
+
+static char *
+Pptype(int typ)
+{
+ switch (typ) {
+ case WHITESPACE: return "whitespace";
+ case CODE : return "code";
+ case QUOTE : return "quote";
+ case MARKUP : return "markup";
+ case HTML : return "html";
+ case DL : return "dl";
+ case UL : return "ul";
+ case OL : return "ol";
+ case LISTITEM : return "item";
+ case HDR : return "header";
+ case HR : return "HR";
+ default : return "mystery node!";
+ }
+}
+
+static void
+pushpfx(int indent, char c, Stack *sp)
+{
+ struct frame *q = &EXPAND(*sp);
+
+ q->indent = indent;
+ q->c = c;
+}
+
+
+static void
+poppfx(Stack *sp)
+{
+ S(*sp)--;
+}
+
+
+static void
+changepfx(Stack *sp, char c)
+{
+ char ch;
+
+ if ( !S(*sp) ) return;
+
+ ch = T(*sp)[S(*sp)-1].c;
+
+ if ( ch == '+' || ch == '|' )
+ T(*sp)[S(*sp)-1].c = c;
+}
+
+
+static void
+printpfx(Stack *sp, FILE *f)
+{
+ int i;
+ char c;
+
+ if ( !S(*sp) ) return;
+
+ c = T(*sp)[S(*sp)-1].c;
+
+ if ( c == '+' || c == '-' ) {
+ fprintf(f, "--%c", c);
+ T(*sp)[S(*sp)-1].c = (c == '-') ? ' ' : '|';
+ }
+ else
+ for ( i=0; i < S(*sp); i++ ) {
+ if ( i )
+ fprintf(f, " ");
+ fprintf(f, "%*s%c", T(*sp)[i].indent + 2, " ", T(*sp)[i].c);
+ if ( T(*sp)[i].c == '`' )
+ T(*sp)[i].c = ' ';
+ }
+ fprintf(f, "--");
+}
+
+
+static void
+dumptree(Paragraph *pp, Stack *sp, FILE *f)
+{
+ int count;
+ Line *p;
+ int d;
+ static char *Begin[] = { 0, "P", "center" };
+
+ while ( pp ) {
+ if ( !pp->next )
+ changepfx(sp, '`');
+ printpfx(sp, f);
+
+ d = fprintf(f, "[%s", Pptype(pp->typ));
+ if ( pp->align )
+ d += fprintf(f, ", <%s>", Begin[pp->align]);
+
+ for (count=0, p=pp->text; p; ++count, (p = p->next) )
+ ;
+
+ if ( count )
+ d += fprintf(f, ", %d line%s", count, (count==1)?"":"s");
+
+ d += fprintf(f, "]");
+
+ if ( pp->down ) {
+ pushpfx(d, pp->down->next ? '+' : '-', sp);
+ dumptree(pp->down, sp, f);
+ poppfx(sp);
+ }
+ else fputc('\n', f);
+ pp = pp->next;
+ }
+}
+
+
+int
+mkd_dump(Document *doc, FILE *out, int flags, char *title)
+{
+ Stack stack;
+
+ if (mkd_compile(doc, flags) ) {
+
+ CREATE(stack);
+ pushpfx(fprintf(out, "%s", title), doc->code->next ? '+' : '-', &stack);
+ dumptree(doc->code, &stack, out);
+ DELETE(stack);
+
+ mkd_cleanup(doc);
+ return 0;
+ }
+ return -1;
+}
4 ext/extconf.rb
@@ -0,0 +1,4 @@
+require 'mkmf'
+
+dir_config('discount')
+create_makefile('discount')
1,319 ext/generate.c
@@ -0,0 +1,1319 @@
+/* markdown: a C implementation of John Gruber's Markdown markup language.
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "config.h"
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+/* prefixes for <automatic links>
+ */
+static char *autoprefix[] = { "http://", "https://", "ftp://", "news://" };
+#define SZAUTOPREFIX (sizeof autoprefix / sizeof autoprefix[0])
+
+typedef int (*stfu)(const void*,const void*);
+
+
+/* forward declarations */
+static void code(int, MMIOT*);
+static void text(MMIOT *f);
+static Paragraph *display(Paragraph*, MMIOT*);
+
+/* externals from markdown.c */
+int __mkd_footsort(Footnote *, Footnote *);
+
+/*
+ * push text into the generator input buffer
+ */
+static void
+push(char *bfr, int size, MMIOT *f)
+{
+ while ( size-- > 0 )
+ EXPAND(f->in) = *bfr++;
+}
+
+
+/* look <i> characters ahead of the cursor.
+ */
+static int
+peek(MMIOT *f, int i)
+{
+
+ i += (f->isp-1);
+
+ return (i >= 0) && (i < S(f->in)) ? T(f->in)[i] : EOF;
+}
+
+
+/* pull a byte from the input buffer
+ */
+static int
+pull(MMIOT *f)
+{
+ return ( f->isp < S(f->in) ) ? T(f->in)[f->isp++] : EOF;
+}
+
+
+/* return a pointer to the current position in the input buffer.
+ */
+static char*
+cursor(MMIOT *f)
+{
+ return T(f->in) + f->isp;
+}
+
+
+/* return/set the current cursor position
+ */
+#define mmiotseek(f,x) (f->isp = x)
+#define mmiottell(f) (f->isp)
+
+
+/* move n characters forward ( or -n characters backward) in the input buffer.
+ */
+static void
+shift(MMIOT *f, int i)
+{
+ if (f->isp + i >= 0 )
+ f->isp += i;
+}
+
+
+/* Qchar()
+ */
+static void
+Qchar(char c, MMIOT *f)
+{
+ block *cur;
+
+ if ( S(f->Q) == 0 ) {
+ cur = &EXPAND(f->Q);
+ memset(cur, 0, sizeof *cur);
+ cur->b_type = bTEXT;
+ }
+ else
+ cur = &T(f->Q)[S(f->Q)-1];
+
+ EXPAND(cur->b_text) = c;
+
+}
+
+
+/* Qstring()
+ */
+static void
+Qstring(char *s, MMIOT *f)
+{
+ while (*s)
+ Qchar(*s++, f);
+}
+
+
+/* Qwrite()
+ */
+static void
+Qwrite(char *s, int size, MMIOT *f)
+{
+ while (size-- > 0)
+ Qchar(*s++, f);
+}
+
+
+/* Qprintf()
+ */
+static void
+Qprintf(MMIOT *f, char *fmt, ...)
+{
+ char bfr[80];
+ va_list ptr;
+
+ va_start(ptr,fmt);
+ vsnprintf(bfr, sizeof bfr, fmt, ptr);
+ va_end(ptr);
+ Qstring(bfr, f);
+}
+
+
+/* Qem()
+ */
+static void
+Qem(MMIOT *f, char c, int count)
+{
+ block *p = &EXPAND(f->Q);
+
+ memset(p, 0, sizeof *p);
+ p->b_type = (c == '*') ? bSTAR : bUNDER;
+ p->b_char = c;
+ p->b_count = count;
+
+ memset(&EXPAND(f->Q), 0, sizeof(block));
+}
+
+
+/* empair()
+ */
+static int
+empair(MMIOT *f, int go, int level)
+{
+
+ int i;
+ block *begin, *p;
+
+ begin = &T(f->Q)[go];
+ for (i=go+1; i < S(f->Q); i++) {
+ p = &T(f->Q)[i];
+
+ if ( (p->b_type != bTEXT) && (p->b_count <= 0) )
+ break;
+
+ if ( p->b_type == begin->b_type ) {
+ if ( p->b_count == level ) /* exact match */
+ return i-go;
+
+ if ( p->b_count > 2 ) /* fuzzy match */
+ return i-go;
+ }
+ }
+ return EOF;
+}
+
+
+
+static struct emtags {
+ char open[10];
+ char close[10];
+ int size;
+} emtags[] = { { "<em>" , "</em>", 5 }, { "<strong>", "</strong>", 9 } };
+
+
+static void
+emclose(Cstring *s, int level)
+{
+ PREFIX(*s, emtags[level-1].close, emtags[level-1].size);
+}
+
+
+static void
+emopen(Cstring *s, int level)
+{
+ SUFFIX(*s, emtags[level-1].open, emtags[level-1].size-1);
+}
+
+
+/* emmatch()
+ */
+static void
+emmatch(MMIOT *f, int go)
+{
+ block *start = &T(f->Q)[go], *end;
+ int e, e2, i, match;
+
+ while ( start->b_count ) {
+ switch (start->b_count) {
+ case 2: e = empair(f,go,match=2);
+ if ( e != EOF ) break;
+ case 1: e = empair(f,go,match=1); break;
+ default:
+ e = empair(f,go,1);
+ e2= empair(f,go,2);
+
+ if ( e == EOF || ((e2 != EOF) && (e2 >= e)) ) {
+ e = e2;
+ match = 2;
+ }
+ else
+ match = 1;
+ }
+ if ( e != EOF ) {
+ end = &T(f->Q)[go+e];
+ emclose(&end->b_post, match);
+ emopen(&start->b_text, match);
+ end->b_count -= match;
+ }
+ else {
+ for (i=0; i < match; i++)
+ EXPAND(start->b_text) = start->b_char;
+ }
+
+ start->b_count -= match;
+ }
+}
+
+
+/* emblock()
+ */
+static void
+emblock(MMIOT *f)
+{
+ int i;
+ block *p;
+
+ for (i=0; i < S(f->Q); i++) {
+ p = &T(f->Q)[i];
+
+ if ( p->b_type != bTEXT ) emmatch(f, i);
+
+ if ( S(p->b_post) ) { SUFFIX(f->out, T(p->b_post), S(p->b_post));
+ DELETE(p->b_post); }
+ if ( S(p->b_text) ) { SUFFIX(f->out, T(p->b_text), S(p->b_text));
+ DELETE(p->b_text); }
+ }
+ S(f->Q) = 0;
+}
+
+
+/* generate html from a markup fragment
+ */
+static void
+reparse(char *bfr, int size, int flags, MMIOT *f)
+{
+ MMIOT sub;
+
+ ___mkd_initmmiot(&sub, f->footnotes);
+
+ sub.flags = f->flags | flags;
+ sub.base = f->base;
+
+ push(bfr, size, &sub);
+ EXPAND(sub.in) = 0;
+ S(sub.in)--;
+
+ text(&sub);
+ emblock(&sub);
+
+ Qwrite(T(sub.out), S(sub.out), f);
+
+ ___mkd_freemmiot(&sub, f->footnotes);
+}
+
+
+/*
+ * write out a url, escaping problematic characters
+ */
+static void
+puturl(char *s, int size, MMIOT *f)
+{
+ unsigned char c;
+
+ while ( size-- > 0 ) {
+ c = *s++;
+
+ if ( c == '&' )
+ Qstring("&amp;", f);
+ else if ( c == '<' )
+ Qstring("&lt;", f);
+ else if ( isalnum(c) || c == '.' || c == '-' || c == '_' || c == '/'
+ || c == '=' || c == '?' || c == ':' || c == '#' )
+ Qchar(c, f);
+ else
+ Qprintf(f, "%%%02X", c);
+ }
+}
+
+
+/* advance forward until the next character is not whitespace
+ */
+static int
+eatspace(MMIOT *f)
+{
+ int c;
+
+ for ( ; ((c=peek(f, 1)) != EOF) && isspace(c); pull(f) )
+ ;
+ return c;
+}
+
+
+/* (match (a (nested (parenthetical (string.)))))
+ */
+static int
+parenthetical(int in, int out, MMIOT *f)
+{
+ int size, indent, c;
+
+ for ( indent=1,size=0; indent; size++ ) {
+ if ( (c = pull(f)) == EOF )
+ return EOF;
+ else if ( c == in )
+ ++indent;
+ else if ( c == out )
+ --indent;
+ }
+ return size-1;
+}
+
+
+/* extract a []-delimited label from the input stream.
+ */
+static char *
+linkylabel(MMIOT *f, int *sizep)
+{
+ char *ptr = cursor(f);
+
+ if ( (*sizep = parenthetical('[',']',f)) != EOF )
+ return ptr;
+ return 0;
+}
+
+
+/* extract a (-prefixed url from the input stream.
+ * the label is either of the format `<link>`, where I
+ * extract until I find a >, or it is of the format
+ * `text`, where I extract until I reach a ')' or
+ * whitespace.
+ */
+static char*
+linkyurl(MMIOT *f, int *sizep)
+{
+ int size = 0;
+ char *ptr;
+ int c;
+
+ if ( (c = eatspace(f)) == EOF )
+ return 0;
+
+ ptr = cursor(f);
+
+ if ( c == '<' ) {
+ pull(f);
+ ptr++;
+ if ( (size = parenthetical('<', '>', f)) == EOF )
+ return 0;
+ }
+ else {
+ for ( ; ((c=pull(f)) != ')') && !isspace(c); size++)
+ if ( c == EOF ) return 0;
+ if ( c == ')' )
+ shift(f, -1);
+ }
+ *sizep = size;
+ return ptr;
+}
+
+
+/* extract a =HHHxWWW size from the input stream
+ */
+static int
+linkysize(MMIOT *f, int *heightp, int *widthp)
+{
+ int height=0, width=0;
+ int c;
+
+ *heightp = 0;
+ *widthp = 0;
+
+ if ( (c = eatspace(f)) != '=' )
+ return (c != EOF);
+ pull(f); /* eat '=' */
+
+ for ( c = pull(f); isdigit(c); c = pull(f))
+ width = (width * 10) + (c - '0');
+
+ if ( c == 'x' ) {
+ for ( c = pull(f); isdigit(c); c = pull(f))
+ height = (height*10) + (c - '0');
+
+ if ( c != EOF ) {
+ if ( !isspace(c) ) shift(f, -1);
+ *heightp = height;
+ *widthp = width;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/* extract a )-terminated title from the input stream.
+ */
+static char*
+linkytitle(MMIOT *f, int *sizep)
+{
+ int countq=0, qc, c, size;
+ char *ret, *lastqc = 0;
+
+ eatspace(f);
+ if ( (qc=pull(f)) != '"' && qc != '\'' && qc != '(' )
+ return 0;
+
+ if ( qc == '(' ) qc = ')';
+
+ for ( ret = cursor(f); (c = pull(f)) != EOF; ) {
+ if ( (c == ')') && countq ) {
+ size = (lastqc ? lastqc : cursor(f)) - ret;
+ *sizep = size-1;
+ return ret;
+ }
+ else if ( c == qc ) {
+ lastqc = cursor(f);
+ countq++;
+ }
+ }
+ return 0;
+}
+
+
+/* look up (or construct) a footnote from the [xxx] link
+ * at the head of the stream.
+ */
+static int
+linkykey(int image, Footnote *val, MMIOT *f)
+{
+ Footnote *ret;
+ Cstring mylabel;
+
+ memset(val, 0, sizeof *val);
+
+ if ( (T(val->tag) = linkylabel(f, &S(val->tag))) == 0 )
+ return 0;
+
+ eatspace(f);
+ switch ( pull(f) ) {
+ case '(':
+ /* embedded link */
+ if ( (T(val->link) = linkyurl(f,&S(val->link))) == 0 )
+ return 0;
+
+ if ( image && !linkysize(f, &val->height, &val->width) )
+ return 0;
+
+ T(val->title) = linkytitle(f, &S(val->title));
+
+ return peek(f,0) == ')';
+
+ case '[':
+ /* footnote link */
+ mylabel = val->tag;
+ if ( (T(val->tag) = linkylabel(f, &S(val->tag))) == 0 )
+ return 0;
+
+ if ( !S(val->tag) )
+ val->tag = mylabel;
+
+ ret = bsearch(val, T(*f->footnotes), S(*f->footnotes),
+ sizeof *val, (stfu)__mkd_footsort);
+
+ if ( ret ) {
+ val->tag = mylabel;
+ val->link = ret->link;
+ val->title = ret->title;
+ val->height = ret->height;
+ val->width = ret->width;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/*
+ * all the tag types that linkylinky can produce are
+ * defined by this structure.
+ */
+typedef struct linkytype {
+ char *pat;
+ int szpat;
+ char *link_pfx; /* tag prefix and link pointer (eg: "<a href="\"" */
+ char *link_sfx; /* link suffix (eg: "\"" */
+ int WxH; /* this tag allows width x height arguments */
+ char *text_pfx; /* text prefix (eg: ">" */
+ char *text_sfx; /* text suffix (eg: "</a>" */
+ int flags; /* reparse flags */
+} linkytype;
+
+static linkytype imaget = { 0, 0, "<img src=\"", "\"",
+ 1, " alt=\"", "\" />", DENY_IMG|INSIDE_TAG };
+static linkytype linkt = { 0, 0, "<a href=\"", "\"",
+ 0, ">", "</a>", DENY_A };
+
+/*
+ * pseudo-protocols for [][];
+ *
+ * id: generates <a id="link">tag</a>
+ * class: generates <span class="link">tag</span>
+ * raw: just dump the link without any processing
+ */
+static linkytype specials[] = {
+ { "id:", 3, "<a id=\"", "\"", 0, ">", "</a>", 0 },
+ { "class:", 6, "<span class=\"", "\"", 0, ">", "</span>", 0 },
+ { "raw:", 4, 0, 0, 0, 0, 0, 0 },
+} ;
+
+#define NR(x) (sizeof x / sizeof x[0])
+
+/* see if t contains one of our pseudo-protocols.
+ */
+static linkytype *
+extratag(Cstring t)
+{
+ int i;
+ linkytype *r;
+
+ for ( i=0; i < NR(specials); i++ ) {
+ r = &specials[i];
+ if ( (S(t) > r->szpat) && (strncasecmp(T(t), r->pat, r->szpat) == 0) )
+ return r;
+ }
+ return 0;
+}
+
+
+/*
+ * process embedded links and images
+ */
+static int
+linkylinky(int image, MMIOT *f)
+{
+ int start = mmiottell(f);
+ Footnote link;
+ linkytype *tag;
+
+ if ( !(linkykey(image, &link, f) && S(link.tag)) ) {
+ mmiotseek(f, start);
+ return 0;
+ }
+
+ if ( image )
+ tag = &imaget;
+ else if ( (f->flags & NO_PSEUDO_PROTO) || (tag = extratag(link.link)) == 0 )
+ tag = &linkt;
+
+ if ( f->flags & tag-> flags ) {
+ mmiotseek(f, start);
+ return 0;
+ }
+
+ if ( tag->link_pfx ) {
+ Qstring(tag->link_pfx, f);
+ if ( f->base && (T(link.link)[tag->szpat] == '/') )
+ puturl(f->base, strlen(f->base), f);
+ puturl(T(link.link) + tag->szpat, S(link.link) - tag->szpat, f);
+ Qstring(tag->link_sfx, f);
+
+ if ( tag->WxH && link.height && link.width ) {
+ Qprintf(f," height=\"%d\"", link.height);
+ Qprintf(f, " width=\"%d\"", link.width);
+ }
+
+ if ( S(link.title) ) {
+ Qstring(" title=\"", f);
+ reparse(T(link.title), S(link.title), INSIDE_TAG, f);
+ Qchar('"', f);
+ }
+
+ Qstring(tag->text_pfx, f);
+ reparse(T(link.tag), S(link.tag), tag->flags, f);
+ Qstring(tag->text_sfx, f);
+ }
+ else
+ Qwrite(T(link.link) + tag->szpat, S(link.link) - tag->szpat, f);
+
+ return 1;
+}
+
+
+/* write a character to output, doing text escapes ( & -> &amp;,
+ * > -> &gt; < -> &lt; )
+ */
+static void
+cputc(int c, MMIOT *f)
+{
+ switch (c) {
+ case '&': Qstring("&amp;", f); break;
+ case '>': Qstring("&gt;", f); break;
+ case '<': Qstring("&lt;", f); break;
+ default : Qchar(c, f); break;
+ }
+}
+
+
+/*
+ * convert an email address to a string of nonsense
+ */
+static void
+mangle(unsigned char *s, int len, MMIOT *f)
+{
+ while ( len-- > 0 ) {
+ Qstring("&#", f);
+ Qprintf(f, COINTOSS() ? "x%02x;" : "%02d;", *s++);
+ }
+}
+
+
+/* before letting a tag through, validate against
+ * DENY_A and DENY_IMG
+ */
+static int
+forbidden_tag(MMIOT *f)
+{
+ int c = toupper(peek(f, 1));
+
+ if ( c == 'A' && (f->flags & DENY_A) && !isalnum(peek(f,2)) )
+ return 1;
+ if ( c == 'I' && (f->flags & DENY_IMG)
+ && strncasecmp(cursor(f)+1, "MG", 2) == 0
+ && !isalnum(peek(f,4)) )
+ return 1;
+ return 0;
+}
+
+
+
+/* a < may be just a regular character, the start of an embedded html
+ * tag, or the start of an <automatic link>. If it's an automatic
+ * link, we also need to know if it's an email address because if it
+ * is we need to mangle it in our futile attempt to cut down on the
+ * spaminess of the rendered page.
+ */
+static int
+maybe_tag_or_link(MMIOT *f)
+{
+ char *text;
+ int c, size, i;
+ int maybetag=1, maybeaddress=0;
+ int mailto;
+
+ if ( f->flags & INSIDE_TAG )
+ return 0;
+
+ for ( size=0; ((c = peek(f,size+1)) != '>') && !isspace(c); size++ ) {
+ if ( ! (c == '/' || isalnum(c) || c == '~') )
+ maybetag=0;
+ if ( c == '@' )
+ maybeaddress=1;
+ else if ( c == EOF )
+ return 0;
+ }
+
+ if ( size == 0 )
+ return 0;
+
+ if ( maybetag || (size >= 3 && strncmp(cursor(f), "!--", 3) == 0) ) {
+ Qstring(forbidden_tag(f) ? "&lt;" : "<", f);
+ while ( ((c = peek(f, size+1)) != EOF) && (c != '>') )
+ cputc(pull(f), f);
+ return 1;
+ }
+
+ if ( f->flags & DENY_A ) return 0;
+
+ text = cursor(f);
+ shift(f, size+1);
+
+ for ( i=0; i < SZAUTOPREFIX; i++ )
+ if ( strncasecmp(text, autoprefix[i], strlen(autoprefix[i])) == 0 ) {
+ Qstring("<a href=\"", f);
+ puturl(text,size,f);
+ Qstring("\">", f);
+ puturl(text,size,f);
+ Qstring("</a>", f);
+ return 1;
+ }
+ if ( maybeaddress ) {
+
+ Qstring("<a href=\"", f);
+ if ( (size > 7) && strncasecmp(text, "mailto:", 7) == 0 )
+ mailto = 7;
+ else {
+ mailto = 0;
+ /* supply a mailto: protocol if one wasn't attached */
+ mangle("mailto:", 7, f);
+ }
+
+ mangle(text, size, f);
+ Qstring("\">", f);
+ mangle(text+mailto, size-mailto, f);
+ Qstring("</a>", f);
+ return 1;
+ }
+
+ shift(f, -(size+1));
+ return 0;
+} /* maybe_tag_or_link */
+
+
+static int
+isthisspace(MMIOT *f, int i)
+{
+ int c = peek(f, i);
+
+ return isspace(c) || (c == EOF);
+}
+
+
+static int
+isthisnonword(MMIOT *f, int i)
+{
+ return isthisspace(f, i) || ispunct(peek(f,i));
+}
+
+
+/* smartyquote code that's common for single and double quotes
+ */
+static int
+smartyquote(int *flags, char typeofquote, MMIOT *f)
+{
+ int bit = (typeofquote == 's') ? 0x01 : 0x02;
+
+ if ( bit & (*flags) ) {
+ if ( isthisnonword(f,1) ) {
+ Qprintf(f, "&r%cquo;", typeofquote);
+ (*flags) &= ~bit;
+ return 1;
+ }
+ }
+ else if ( isthisnonword(f,-1) && peek(f,1) != EOF ) {
+ Qprintf(f, "&l%cquo;", typeofquote);
+ (*flags) |= bit;
+ return 1;
+ }
+ return 0;
+}
+
+
+static int
+islike(MMIOT *f, char *s)
+{
+ int len;
+ int i;
+
+ if ( s[0] == '<' ) {
+ if ( !isthisnonword(f, -1) )
+ return 0;
+ ++s;
+ }
+
+ if ( !(len = strlen(s)) )
+ return 0;
+
+ if ( s[len-1] == '>' ) {
+ if ( !isthisnonword(f,len-1) )
+ return 0;
+ len--;
+ }
+
+ for (i=1; i < len; i++)
+ if (tolower(peek(f,i)) != s[i])
+ return 0;
+ return 1;
+}
+
+
+static struct smarties {
+ char c0;
+ char *pat;
+ char *entity;
+ int shift;
+} smarties[] = {
+ { '\'', "'s>", "rsquo", 0 },
+ { '\'', "'t>", "rsquo", 0 },
+ { '-', "--", "mdash", 1 },
+ { '-', "<->", "ndash", 0 },
+ { '.', "...", "hellip", 2 },
+ { '.', ". . .", "hellip", 4 },
+ { '(', "(c)", "copy", 2 },
+ { '(', "(r)", "reg", 2 },
+ { '(', "(tm)", "trade", 3 },
+ { '3', "<3/4>", "frac34", 2 },
+ { '3', "<3/4ths>", "frac34", 2 },
+ { '1', "<1/2>", "frac12", 2 },
+ { '1', "<1/4>", "frac14", 2 },
+ { '1', "<1/4th>", "frac14", 2 },
+ { '&', "&#0;", 0, 3 },
+} ;
+#define NRSMART ( sizeof smarties / sizeof smarties[0] )
+
+
+/* Smarty-pants-style chrome for quotes, -, ellipses, and (r)(c)(tm)
+ */
+static int
+smartypants(int c, int *flags, MMIOT *f)
+{
+ int i;
+
+ if ( f->flags & DENY_SMARTY )
+ return 0;
+
+ for ( i=0; i < NRSMART; i++)
+ if ( (c == smarties[i].c0) && islike(f, smarties[i].pat) ) {
+ if ( smarties[i].entity )
+ Qprintf(f, "&%s;", smarties[i].entity);
+ shift(f, smarties[i].shift);
+ return 1;
+ }
+
+ switch (c) {
+ case '<' : return 0;
+ case '\'': if ( smartyquote(flags, 's', f) ) return 1;
+ break;
+
+ case '"': if ( smartyquote(flags, 'd', f) ) return 1;
+ break;
+
+ case '`': if ( peek(f, 1) == '`' ) {
+ int j = 2;
+
+ while ( (c=peek(f,j)) != EOF ) {
+ if ( c == '\\' )
+ j += 2;
+ else if ( c == '`' )
+ break;
+ else if ( c == '\'' && peek(f, j+1) == '\'' ) {
+ Qstring("&ldquo;", f);
+ reparse(cursor(f)+1, j-2, 0, f);
+ Qstring("&rdquo;", f);
+ shift(f,j+1);
+ return 1;
+ }
+ else ++j;
+ }
+
+ }
+ break;
+ }
+ return 0;
+} /* smartypants */
+
+
+#define tag_text(f) (f->flags & INSIDE_TAG)
+
+
+static void
+text(MMIOT *f)
+{
+ int c, j;
+ int rep;
+ int smartyflags = 0;
+
+ while ( (c = pull(f)) != EOF ) {
+ if ( smartypants(c, &smartyflags, f) )
+ continue;
+ switch (c) {
+ case 0: break;
+
+ case '>': if ( tag_text(f) )
+ Qstring("&gt;", f);
+ else
+ Qchar(c, f);
+ break;
+
+ case '"': if ( tag_text(f) )
+ Qstring("&quot;", f);
+ else
+ Qchar(c, f);
+ break;
+
+ case '!': if ( peek(f,1) == '[' ) {
+ pull(f);
+ if ( tag_text(f) || !linkylinky(1, f) )
+ Qstring("![", f);
+ }
+ else
+ Qchar(c, f);
+ break;
+ case '[': if ( tag_text(f) || !linkylinky(0, f) )
+ Qchar(c, f);
+ break;
+ case '*':
+ case '_': if ( tag_text(f) )
+ Qchar(c, f);
+#if RELAXED_EMPHASIS
+ else if ( peek(f,1) == c ) {
+ for ( rep = 1; peek(f,1) == c; pull(f) )
+ ++rep;
+
+ Qem(f, c, rep);
+ }
+ else if ( (isthisspace(f,-1) && isthisspace(f,1))
+ || (isalnum(peek(f,-1)) && isalnum(peek(f,1))) )
+ Qchar(c, f);
+ else {
+ Qem(f, c, 1);
+ }
+#else
+ else {
+ for (rep = 1; peek(f,1) == c; pull(f) )
+ ++rep;
+ Qem(f,c,rep);
+ }
+#endif
+ break;
+
+ case '`': if ( tag_text(f) )
+ Qchar(c, f);
+ else {
+ Qstring("<code>", f);
+ if ( peek(f, 1) == '`' ) {
+ pull(f);
+ code(2, f);
+ }
+ else
+ code(1, f);
+ Qstring("</code>", f);
+ }
+ break;
+
+ case '\\': switch ( c = pull(f) ) {
+ case '&': Qstring("&amp;", f);
+ break;
+ case '<': Qstring("&lt;", f);
+ break;
+ case '\\':
+ case '>': case '#': case '.': case '-':
+ case '+': case '{': case '}': case ']':
+ case '(': case ')': case '"': case '\'':
+ case '!': case '[': case '*': case '_':
+ case '`': Qchar(c, f);
+ break;
+ default:
+ Qchar('\\', f);
+ if ( c != EOF )
+ shift(f,-1);
+ break;
+ }
+ break;
+
+ case '<': if ( !maybe_tag_or_link(f) )
+ Qstring("&lt;", f);
+ break;
+
+ case '&': j = (peek(f,1) == '#' ) ? 2 : 1;
+ while ( isalnum(peek(f,j)) )
+ ++j;
+
+ if ( peek(f,j) != ';' )
+ Qstring("&amp;", f);
+ else
+ Qchar(c, f);
+ break;
+
+ default: Qchar(c, f);
+ break;
+ }
+ }
+} /* text */
+
+
+static int
+endofcode(int escape, int offset, MMIOT *f)
+{
+ switch (escape) {
+ case 2: if ( peek(f, offset+1) == '`' ) {
+ shift(f,1);
+ case 1: shift(f,offset);
+ return 1;
+ }
+ default:return 0;
+ }
+}
+
+
+/* the only characters that have special meaning in a code block are
+ * `<' and `&' , which are /always/ expanded to &lt; and &amp;
+ */
+static void
+code(int escape, MMIOT *f)
+{
+ int c;
+
+ if ( escape && (peek(f,1) == ' ') )
+ shift(f,1);
+
+ while ( (c = pull(f)) != EOF ) {
+ switch (c) {
+ case ' ': if ( peek(f,1) == '`' && endofcode(escape, 1, f) )
+ return;
+ Qchar(c, f);
+ break;
+
+ case '`': if ( endofcode(escape, 0, f) )
+ return;
+ Qchar(c, f);
+ break;
+
+ case '\\': cputc(c, f);
+ if ( peek(f,1) == '>' || (c = pull(f)) == EOF )
+ break;
+
+ default: cputc(c, f);
+ break;
+ }
+ }
+} /* code */
+
+
+/* print a header block
+ */
+static void
+printheader(Paragraph *pp, MMIOT *f)
+{
+ Qprintf(f, "<h%d>", pp->hnumber);
+ push(T(pp->text->text), S(pp->text->text), f);
+ text(f);
+ Qprintf(f, "</h%d>", pp->hnumber);
+}
+
+
+static int
+printblock(Paragraph *pp, MMIOT *f)
+{
+ Line *t = pp->text;
+ static char *Begin[] = { "", "<p>", "<center>" };
+ static char *End[] = { "", "</p>","</center>" };
+
+ while (t) {
+ if ( S(t->text) ) {
+ if ( S(t->text) > 2 && T(t->text)[S(t->text)-2] == ' '
+ && T(t->text)[S(t->text)-1] == ' ') {
+ push(T(t->text), S(t->text)-2, f);
+ push("<br/>\n", 6, f);
+ }
+ else {
+ push(T(t->text), S(t->text), f);
+ if ( t->next )
+ push("\n", 1, f);
+ }
+ }
+ t = t->next;
+ }
+ Qstring(Begin[pp->align], f);
+ text(f);
+ Qstring(End[pp->align], f);
+ return 1;
+}
+
+
+static void
+printcode(Line *t, MMIOT *f)
+{
+ int blanks;
+
+ for ( blanks = 0; t ; t = t->next )
+ if ( S(t->text) > t->dle ) {
+ while ( blanks ) {
+ push("\n", 1, f);
+ --blanks;
+ }
+ push(T(t->text), S(t->text), f);
+ push("\n", 1, f);
+ }
+ else blanks++;
+
+ Qstring("<pre><code>", f);
+ code(0, f);
+ Qstring("</code></pre>", f);
+}
+
+
+static void
+printhtml(Line *t, MMIOT *f)
+{
+ int blanks;
+
+ for ( blanks=0; t ; t = t->next )
+ if ( S(t->text) ) {
+ for ( ; blanks; --blanks )
+ Qchar('\n', f);
+
+ Qwrite(T(t->text), S(t->text), f);
+ Qchar('\n', f);
+ }
+ else
+ blanks++;
+}
+
+
+static void
+htmlify(Paragraph *p, char *block, MMIOT *f)
+{
+ emblock(f);
+ if ( block ) Qprintf(f, "<%s>", block);
+ emblock(f);
+
+ while (( p = display(p, f) )) {
+ emblock(f);
+ Qstring("\n\n", f);
+ }
+
+ if ( block ) Qprintf(f, "</%s>", block);
+ emblock(f);
+}
+
+
+#if DL_TAG_EXTENSION
+static void
+definitionlist(Paragraph *p, MMIOT *f)
+{
+ Line *tag;
+
+ if ( p ) {
+ Qstring("<dl>\n", f);
+
+ for ( ; p ; p = p->next) {
+ for ( tag = p->text; tag; tag = tag->next ) {
+ Qstring("<dt>", f);
+ reparse(T(tag->text), S(tag->text), 0, f);
+ Qstring("</dt>\n", f);
+ }
+
+ htmlify(p->down, "dd", f);
+ }
+
+ Qstring("</dl>", f);
+ }
+}
+#endif
+
+
+static void
+listdisplay(int typ, Paragraph *p, MMIOT* f)
+{
+ if ( p ) {
+ Qprintf(f, "<%cl>\n", (typ==UL)?'u':'o');
+
+ for ( ; p ; p = p->next ) {
+ htmlify(p->down, "li", f);
+ Qchar('\n', f);
+ }
+
+ Qprintf(f, "</%cl>\n", (typ==UL)?'u':'o');
+ }
+}
+
+
+/* dump out a Paragraph in the desired manner
+ */
+static Paragraph*
+display(Paragraph *p, MMIOT *f)
+{
+ if ( !p ) return 0;
+
+ switch ( p->typ ) {
+ case STYLE:
+ case WHITESPACE:
+ break;
+
+ case HTML:
+ printhtml(p->text, f);
+ break;
+
+ case CODE:
+ printcode(p->text, f);
+ break;
+
+ case QUOTE:
+ htmlify(p->down, "blockquote", f);
+ break;
+
+ case UL:
+ case OL:
+ listdisplay(p->typ, p->down, f);
+ break;
+
+#if DL_TAG_EXTENSION
+ case DL:
+ definitionlist(p->down, f);
+ break;
+#endif
+
+ case HR:
+ Qstring("<hr />", f);
+ break;
+
+ case HDR:
+ printheader(p, f);
+ break;
+
+ default:
+ printblock(p, f);
+ break;
+ }
+ return p->next;
+}
+
+
+/*
+ * dump out stylesheet sections.
+ */
+static int
+stylesheets(Paragraph *p, FILE *f)
+{
+ Line* q;
+
+ for ( ; p ; p = p->next ) {
+ if ( p->typ == STYLE ) {
+ for ( q = p->text; q ; q = q->next )
+ if ( fwrite(T(q->text), S(q->text), 1, f) == 1 )
+ putc('\n', f);
+ else
+ return EOF;
+ }
+ if ( p->down && (stylesheets(p->down, f) == EOF) )
+ return EOF;
+ }
+ return 0;
+}
+
+
+/* return a pointer to the compiled markdown
+ * document.
+ */
+int
+mkd_document(Document *p, char **res)
+{
+ if ( p && p->compiled ) {
+ if ( ! p->html ) {
+ htmlify(p->code, 0, p->ctx);
+ p->html = 1;
+ }
+
+ *res = T(p->ctx->out);
+ return S(p->ctx->out);
+ }
+ return EOF;
+}
+
+
+/* public interface for reparse()
+ */
+int
+mkd_text(char *bfr, int size, FILE *output, int flags)
+{
+ MMIOT f;
+
+ ___mkd_initmmiot(&f, 0);
+ f.flags = flags & USER_FLAGS;
+
+ reparse(bfr, size, 0, &f);
+ emblock(&f);
+ if ( flags & CDATA_OUTPUT )
+ ___mkd_xml(T(f.out), S(f.out), output);
+ else
+ fwrite(T(f.out), S(f.out), 1, output);
+
+ ___mkd_freemmiot(&f, 0);
+ return 0;
+}
+
+
+/* dump any embedded styles
+ */
+int
+mkd_style(Document *d, FILE *f)
+{
+ if ( d && d->compiled )
+ return stylesheets(d->code, f);
+ return EOF;
+}
+
866 ext/markdown.c
@@ -0,0 +1,866 @@
+/* markdown: a C implementation of John Gruber's Markdown markup language.
+ *
+ * Copyright (C) 2007 David L Parsons.
+ * The redistribution terms are provided in the COPYRIGHT file that must
+ * be distributed with this source code.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "config.h"
+
+#include "cstring.h"
+#include "markdown.h"
+#include "amalloc.h"
+
+/* block-level tags for passing html blocks through the blender
+ */
+struct kw {
+ char *id;
+ int siz;
+} ;
+
+#define KW(x) { x, sizeof(x)-1 }
+
+static struct kw blocktags[] = { KW("!--"), KW("STYLE"), KW("SCRIPT"),
+ KW("ADDRESS"), KW("BDO"), KW("BLOCKQUOTE"),
+ KW("CENTER"), KW("DFN"), KW("DIV"), KW("H1"),
+ KW("H2"), KW("H3"), KW("H4"), KW("H5"),
+ KW("H6"), KW("LISTING"), KW("NOBR"),
+ KW("UL"), KW("P"), KW("OL"), KW("DL"),
+ KW("PLAINTEXT"), KW("PRE"), KW("TABLE"),
+ KW("WBR"), KW("XMP"), KW("HR"), KW("BR") };
+#define SZTAGS (sizeof blocktags / sizeof blocktags[0])
+#define MAXTAG 11 /* sizeof "BLOCKQUOTE" */
+
+typedef int (*stfu)(const void*,const void*);
+
+typedef ANCHOR(Paragraph) ParagraphRoot;
+
+
+/* case insensitive string sort (for qsort() and bsearch() of block tags)
+ */
+static int
+casort(struct kw *a, struct kw *b)
+{
+ if ( a->siz != b->siz )
+ return a->siz - b->siz;
+ return strncasecmp(a->id, b->id, b->siz);
+}
+
+
+/* case insensitive string sort for Footnote tags.
+ */
+int
+__mkd_footsort(Footnote *a, Footnote *b)
+{
+ int i;
+ char ac, bc;
+
+ if ( S(a->tag) != S(b->tag) )
+ return S(a->tag) - S(b->tag);
+
+ for ( i=0; i < S(a->tag); i++) {
+ ac = tolower(T(a->tag)[i]);
+ bc = tolower(T(b->tag)[i]);
+
+ if ( isspace(ac) && isspace(bc) )
+ continue;
+ if ( ac != bc )
+ return ac - bc;
+ }
+ return 0;
+}
+
+
+/* find the first blank character after position <i>
+ */
+static int
+nextblank(Line *t, int i)
+{
+ while ( (i < S(t->text)) && !isspace(T(t->text)[i]) )
+ ++i;
+ return i;
+}
+
+
+/* find the next nonblank character after position <i>
+ */
+static int
+nextnonblank(Line *t, int i)
+{
+ while ( (i < S(t->text)) && isspace(T(t->text)[i]) )
+ ++i;
+ return i;
+}
+
+
+/* find the first nonblank character on the Line.
+ */
+int
+mkd_firstnonblank(Line *p)
+{
+ return nextnonblank(p,0);
+}
+
+
+static int
+blankline(Line *p)
+{
+ return ! (p && (S(p->text) > p->dle) );
+}
+
+
+static Line *
+skipempty(Line *p)
+{
+ while ( p && (p->dle == S(p->text)) )
+ p = p->next;
+ return p;
+}
+
+
+static char *
+isopentag(Line *p)
+{
+ int i=0, len;
+ struct kw key, *ret;
+
+ if ( !p ) return 0;
+
+ len = S(p->text);
+
+ if ( len < 3 || T(p->text)[0] != '<' )
+ return 0;
+
+ /* find how long the tag is so we can check to see if
+ * it's a block-level tag
+ */
+ for ( i=1; i < len && T(p->text)[i] != '>'
+ && T(p->text)[i] != '/'
+ && !isspace(T(p->text)[i]); ++i )
+ ;
+
+ key.id = T(p->text)+1;
+ key.siz = i-1;
+
+ if ( ret = bsearch(&key,blocktags,SZTAGS,sizeof key, (stfu)casort))
+ return ret->id;
+
+ return 0;
+}
+
+
+static int
+selfclose(Line *t, char *tag)
+{
+ char *q = T(t->text);
+ int siz = strlen(tag);
+ int i;
+
+ if ( strcasecmp(tag, "HR") == 0 || strcasecmp(tag, "BR") == 0 )
+ /* <HR> and <BR> are self-closing block-level tags,
+ */
+ return 1;
+
+ i = S(t->text) - (siz + 3);
+
+ /* we specialcase start and end tags on the same line.
+ */
+ return ( i > 0 ) && (q[i] == '<') && (q[i+1] == '/')
+ && (q[i+2+siz] == '>')
+ && (strncasecmp(&q[i+2], tag, siz) == 0);
+}
+
+
+static Line *
+htmlblock(Paragraph *p, char *tag)
+{
+ Line *t = p->text, *ret;
+ int closesize;
+ char close[MAXTAG+4];
+
+ if ( selfclose(t, tag) || (strlen(tag) >= MAXTAG) ) {
+ ret = t->next;
+ t->next = 0;
+ return ret;
+ }
+
+ closesize = sprintf(close, "</%s>", tag);
+
+ for ( ; t ; t = t->next) {
+ if ( strncasecmp(T(t->text), close, closesize) == 0 ) {
+ ret = t->next;
+ t->next = 0;
+ return ret;
+ }
+ }
+ return 0;
+}
+
+
+static Line *
+comment(Paragraph *p, char *key)
+{
+ Line *t, *ret;
+
+ for ( t = p->text; t ; t = t->next) {
+ if ( strstr(T(t->text), "-->") ) {
+ ret = t->next;
+ t->next = 0;
+ return ret;
+ }
+ }
+ return t;
+
+}
+
+
+/* footnotes look like ^<whitespace>{0,3}[stuff]: <content>$
+ */
+static int
+isfootnote(Line *t)
+{
+ int i;
+
+ if ( ( (i = t->dle) > 3) || (T(t->text)[i] != '[') )
+ return 0;
+
+ for ( ++i; i < S(t->text) ; ++i ) {
+ if ( T(t->text)[i] == '[' )
+ return 0;
+ else if ( T(t->text)[i] == ']' && T(t->text)[i+1] == ':' )
+ return 1;
+ }
+ return 0;
+}
+
+
+static int
+isquote(Line *t)
+{
+ return ( T(t->text)[0] == '>' );
+}
+
+
+static int
+dashchar(char c)
+{
+ return (c == '*') || (c == '-') || (c == '_');
+}
+
+
+static int
+iscode(Line *t)
+{
+ return (t->dle >= 4);
+}
+
+
+static int
+ishr(Line *t)
+{
+ int i, count=0;
+ char dash = 0;
+ char c;
+
+ if ( iscode(t) ) return 0;
+
+ for ( i = 0; i < S(t->text); i++) {
+ c = T(t->text)[i];
+ if ( (dash == 0) && dashchar(c) )
+ dash = c;
+
+ if ( c == dash ) ++count;
+ else if ( !isspace(c) )
+ return 0;
+ }
+ return (count >= 3);
+}
+
+
+static int
+ishdr(Line *t, int *htyp)
+{
+ int i, j;
+
+
+ /* first check for etx-style ###HEADER###
+ */
+
+ /* leading run of `#`'s ?
+ */
+ for ( i=0; T(t->text)[i] == '#'; ++i)
+ ;
+
+ if ( i ) {
+ i = nextnonblank(t, i);
+
+ j = S(t->text)-1;
+
+ while ( (j > i) && (T(t->text)[j] == '#') )
+ --j;
+
+ while ( (j > 1) && isspace(T(t->text)[j]) )
+ --j;
+
+ if ( i < j ) {
+ *htyp = ETX;
+ return 1;
+ }
+ }
+
+ /* then check for setext-style HEADER
+ * ======
+ */
+
+ if ( t->next ) {
+ char *q = T(t->next->text);
+
+ if ( (*q == '=') || (*q == '-') ) {
+ for (i=1; i < S(t->next->text); i++)
+ if ( q[0] != q[i] )
+ return 0;
+ *htyp = SETEXT;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+static int
+isdefinition(Line *t)
+{
+#if DL_TAG_EXTENSION
+ return t && t->next
+ && (S(t->text) > 2)
+ && (t->dle == 0)
+ && (T(t->text)[0] == '=')
+ && (T(t->text)[S(t->text)-1] == '=')
+ && ( (t->next->dle >= 4) || isdefinition(t->next) );
+#else
+ return 0;
+#endif
+}
+
+
+static int
+islist(Line *t, int *trim)
+{
+ int i, j;
+ char *q;
+
+ if ( iscode(t) || blankline(t) || ishdr(t,&i) || ishr(t) )
+ return 0;
+
+ if ( isdefinition(t) ) {
+ *trim = 4;
+ return DL;
+ }
+
+ if ( strchr("*-+", T(t->text)[t->dle]) && isspace(T(t->text)[t->dle+1]) ) {
+ i = nextnonblank(t, t->dle+1);
+ *trim = (i > 4) ? 4 : i;
+ return UL;
+ }
+
+ if ( (j = nextblank(t,t->dle)) > t->dle ) {
+ if ( T(t->text)[j-1] == '.' ) {
+ strtoul(T(t->text)+t->dle, &q, 10);
+ if ( (q > T(t->text)+t->dle) && (q == T(t->text) + (j-1)) ) {
+ j = nextnonblank(t,j);
+ *trim = j;
+ return OL;
+ }
+ }
+ }
+ return 0;
+}
+
+
+static Line *
+headerblock(Paragraph *pp, int htyp)
+{
+ Line *ret = 0;
+ Line *p = pp->text;
+ int i, j;
+
+ switch (htyp) {
+ case SETEXT:
+ /* p->text is header, p->next->text is -'s or ='s
+ */
+ pp->hnumber = (T(p->next->text)[0] == '=') ? 1 : 2;
+
+ ret = p->next->next;
+ ___mkd_freeLine(p->next);
+ p->next = 0;
+ break;
+
+ case ETX:
+ /* p->text is ###header###, so we need to trim off
+ * the leading and trailing `#`'s
+ */
+
+ for (i=0; T(p->text)[i] == T(p->text)[0]; i++)
+ ;
+
+ pp->hnumber = i;
+
+ CLIP(p->text, 0, i);
+
+ for (j=S(p->text); j && (T(p->text)[j-1] == '#'); --j)
+ ;
+
+ S(p->text) = j;
+
+ ret = p->next;
+ p->next = 0;
+ break;
+ }
+ return ret;
+}
+
+
+static Line *
+codeblock(Paragraph *p)
+{
+ Line *t = p->text, *r;
+
+ /* HORRIBLE STANDARDS KLUDGE: the first line of every block
+ * has trailing whitespace trimmed off.
+ */
+ while ( S(t->text) && isspace(T(t->text)[S(t->text)-1]) )
+ --S(t->text);
+
+ for ( ; t; t = r ) {
+ CLIP(t->text,0,4);
+ t->dle = mkd_firstnonblank(t);
+
+ if ( !( (r = skipempty(t->next)) && iscode(r)) ) {
+ ___mkd_freeLineRange(t,r);
+ t->next = 0;
+ return r;
+ }
+ }
+ return t;
+}
+
+
+static int
+centered(Line *first, Line *last)
+{
+
+ if ( first&&last ) {
+ int len = S(last->text);
+
+ if ( (len > 2) && (strncmp(T(first->text), "->", 2) == 0)
+ && (strncmp(T(last->text)+len-2, "<-", 2) == 0) ) {
+ CLIP(first->text, 0, 2);
+ S(last->text) -= 2;
+ return CENTER;
+ }
+ }
+ return 0;
+}
+
+
+static int
+endoftextblock(Line *t, int toplevelblock)
+{
+ int z;
+
+ if ( blankline(t)||isquote(t)||iscode(t)||ishdr(t,&z)||ishr(t) )
+ return 1;
+
+ /* HORRIBLE STANDARDS KLUDGE: Toplevel paragraphs eat absorb adjacent
+ * list items, but sublevel blocks behave properly.
+ */
+ return toplevelblock ? 0 : islist(t,&z);
+}
+
+
+static Line *
+textblock(Paragraph *p, int toplevel)
+{
+ Line *t, *next;
+
+ for ( t = p->text; t ; t = next )
+ if ( ((next = t->next) == 0) || endoftextblock(next, toplevel) ) {
+ p->align = centered(p->text, t);
+ t->next = 0;
+ return next;
+ }
+ return t;
+}
+
+
+/*
+ * accumulate a blockquote.
+ *
+ * one sick horrible thing about blockquotes is that even though
+ * it just takes ^> to start a quote, following lines, if quoted,
+ * assume that the prefix is ``>''. This means that code needs
+ * to be indented *5* spaces from the leading '>', but *4* spaces
+ * from the start of the line. This does not appear to be
+ * documented in the reference implementation, but it's the
+ * way the markdown sample web form at Daring Fireball works.
+ */
+static Line *
+quoteblock(Paragraph *p)
+{
+ Line *t, *q;
+ int qp;
+
+ for ( t = p->text; t ; t = q ) {
+ if ( isquote(t) ) {
+ qp = (T(t->text)[1] == ' ') ? 2 : 1;
+ CLIP(t->text, 0, qp);
+ t->dle = mkd_firstnonblank(t);
+ }
+
+ if ( !(q = skipempty(t->next)) || ((q != t->next) && !isquote(q)) ) {
+ ___mkd_freeLineRange(t, q);
+ return q;
+ }
+ }
+ return t;
+}
+
+
+static Paragraph *Pp(ParagraphRoot *, Line *, int);
+static Paragraph *compile(Line *, int, MMIOT *);
+
+
+/*
+ * pull in a list block. A list block starts with a list marker and
+ * runs until the next list marker, the next non-indented paragraph,
+ * or EOF. You do not have to indent nonblank lines after the list
+ * marker, but multiple paragraphs need to start with a 4-space indent.
+ */
+static Line *
+listitem(Paragraph *p, int indent)
+{
+ Line *t, *q;
+ int clip = indent;
+ int z;
+
+ for ( t = p->text; t ; t = q) {
+ CLIP(t->text, 0, clip);
+ t->dle = mkd_firstnonblank(t);
+
+ if ( (q = skipempty(t->next)) == 0 ) {
+ ___mkd_freeLineRange(t,q);
+ return 0;
+ }
+
+ /* after a blank line, the next block needs to start with a line
+ * that's indented 4 spaces, but after that the line doesn't
+ * need any indentation
+ */
+ if ( q != t->next ) {
+ if (q->dle < 4) {
+ q = t->next;
+ t->next = 0;
+ return q;
+ }
+ indent = 4;
+ }
+
+ if ( (q->dle < indent) && (ishr(q) || ishdr(q,&z) || islist(q,&z)) ) {
+ q = t->next;
+ t->next = 0;
+ return q;
+ }
+
+ clip = (q->dle > indent) ? indent : q->dle;
+ }
+ return t;
+}
+
+
+static Line *
+listblock(Paragraph *top, int trim, MMIOT *f)
+{
+ ParagraphRoot d = { 0, 0 };
+ Paragraph *p;
+ Line *q = top->text, *text;
+ Line *label;
+ int para = 0;
+
+ while (( text = q )) {
+ if ( top->typ == DL ) {
+ Line *lp;
+
+ for ( lp = label = text; lp ; lp = lp->next ) {
+ text = lp->next;
+ CLIP(lp->text, 0, 1);
+ S(lp->text)--;
+ if ( !isdefinition(lp->next) )
+ lp->next = 0;
+ }
+ }
+ else label = 0;
+
+ p = Pp(&d, text, LISTITEM);
+ text = listitem(p, trim);
+
+ p->down = compile(p->text, 0, f);
+ p->text = label;
+
+ if ( para && (top->typ != DL) ) p->down->align = PARA;
+
+ if ( !(q = skipempty(text)) || (islist(q,&trim) != top->typ) )
+ break;
+
+ if ( para = (q != text) ) {
+ Line anchor;
+
+ anchor.next = text;
+ ___mkd_freeLineRange(&anchor, q);
+ }
+
+ if ( para && (top->typ != DL) ) p->down->align = PARA;
+ }
+ top->text = 0;
+ top->down = T(d);
+ return text;
+}
+