New Upstream Release - ruby-rdiscount

Ready changes

Summary

Merged new upstream version: 2.2.7 (was: 2.1.8).

Resulting package

Built on 2023-02-26T06:12 (took 2m29s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases ruby-rdiscount-dbgsymapt install -t fresh-releases ruby-rdiscount

Diff

diff --git a/BUILDING b/BUILDING
index 29a72f3..759b653 100644
--- a/BUILDING
+++ b/BUILDING
@@ -20,7 +20,7 @@ the rake gather task to copy discount source files into the ext/ directory:
     Submodule 'discount' (git://github.com/davidfstr/discount.git) registered for path 'discount'
     Cloning into discount...
     $ cd discount
-    $ ./configure.sh --with-fenced-code --with-github-tags --with-dl=both
+    $ ./configure.sh
     $ make  # ensure it compiles
     $ cd ..
     $ rake gather
@@ -52,6 +52,9 @@ ext. This must be done manually. Here's a quick way to get the full list:
 
     $ echo ext/*.c ext/*.h ext/*.rb ext/blocktags ext/VERSION | tr ' ' "\n" | sort
 
+(There is an old Rakefile target called "rdiscount.gemspec" that looks like it
+ is designed to perform an update of these files, however I haven't tested it.)
+
 Build the RDiscount gem. If you get errors related to missing files
 in ext, make sure you updated the gemspec correctly in the previous step.
 
diff --git a/README.markdown b/README.markdown
index 7d1beb8..6ab30fa 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,22 +1,22 @@
 Discount Markdown Processor for Ruby
 ====================================
-[![Build Status](https://travis-ci.org/davidfstr/rdiscount.png)](https://travis-ci.org/davidfstr/rdiscount)
+[![Build Status](https://github.com/davidfstr/rdiscount/actions/workflows/main.yml/badge.svg)](https://github.com/davidfstr/rdiscount/actions/workflows/main.yml)
 
-Discount is an implementation of John Gruber's Markdown markup language in C. It
-implements all of the language described in [the markdown syntax document][1] and
+Discount is an implementation of John Gruber's Markdown markup language in C.
+It implements all of the language described in [the markdown syntax document][1] and
 passes the [Markdown 1.0 test suite][2].
 
 CODE: `git clone git://github.com/davidfstr/rdiscount.git`  
-HOME: <http://dafoster.net/projects/rdiscount/>  
-DOCS: <http://rdoc.info/github/davidfstr/rdiscount/master/RDiscount>  
-BUGS: <http://github.com/davidfstr/rdiscount/issues>  
+HOME: <https://dafoster.net/projects/rdiscount/>  
+DOCS: <https://rdoc.info/github/davidfstr/rdiscount/master/RDiscount>  
+BUGS: <https://github.com/davidfstr/rdiscount/issues>
 
-Discount was developed by [David Loren Parsons][3]. The Ruby extension
-is maintained by [David Foster][4].
+Discount was developed by [David Loren Parsons][3].
+The Ruby extension is maintained by [David Foster][4].
 
-[1]: http://daringfireball.net/projects/markdown/syntax
-[2]: http://daringfireball.net/projects/downloads/MarkdownTest_1.0.zip
-[3]: http://www.pell.portland.or.us/~orc
+[1]: https://daringfireball.net/projects/markdown/syntax
+[2]: https://daringfireball.net/projects/downloads/MarkdownTest_1.0.zip
+[3]: https://www.pell.portland.or.us/~orc
 [4]: https://github.com/davidfstr
 
 INSTALL, HACKING
diff --git a/Rakefile b/Rakefile
index 0127296..6cc39a9 100644
--- a/Rakefile
+++ b/Rakefile
@@ -53,7 +53,7 @@ task :man => 'man/rdiscount.1'
 require 'rake/testtask'
 Rake::TestTask.new('test:unit') do |t|
   t.test_files = FileList['test/*_test.rb']
-  t.ruby_opts += ['-rubygems'] if defined? Gem
+  t.ruby_opts += ['-r rubygems'] if defined? Gem
 end
 task 'test:unit' => [:build]
 
@@ -67,11 +67,20 @@ task 'test:conformance' => [:build] do |t|
     print result
     fail unless result.include? "; 0 failed."
   end
+
+  # Allow to run this rake tasks multiple times
+  # https://medium.com/@shaneilske/invoke-a-rake-task-multiple-times-1bcb01dee9d9
+  ENV.delete("MARKDOWN_TEST_VER")
+  ENV.delete("RDISCOUNT_EXTENSIONS")
+  Rake::Task["test:conformance"].reenable
 end
 
 desc 'Run version 1.0 conformance suite'
 task 'test:conformance:1.0' => [:build] do |t|
   ENV['MARKDOWN_TEST_VER'] = '1.0'
+  # see https://github.com/Orc/discount/issues/261
+  # requires flags -f1.0,tabstop,nopants
+  ENV['RDISCOUNT_EXTENSIONS'] = "md1compat"
   Rake::Task['test:conformance'].invoke
 end
 
@@ -82,7 +91,7 @@ task 'test:conformance:1.0.3' => [:build] do |t|
 end
 
 desc 'Run unit and conformance tests'
-task :test => %w[test:unit test:conformance]
+task :test => %w[test:unit test:conformance:1.0 test:conformance:1.0.3]
 
 desc 'Run benchmarks'
 task :benchmark => :build do |t|
diff --git a/bin/rdiscount b/bin/rdiscount
index 3e3a638..8806789 100755
--- a/bin/rdiscount
+++ b/bin/rdiscount
@@ -1,7 +1,13 @@
 #!/usr/bin/env ruby
-# Usage: rdiscount [<file>...]
-# Convert one or more Markdown files to HTML and write to standard output. With
+#
+# Usage: [env RDISCOUNT_EXTENSIONS='<extension>,...'] rdiscount [<file>...]
 # no <file> or when <file> is '-', read Markdown source text from standard input.
+#
+# Convert one or more Markdown files to HTML and write to standard output.
+# With no <file> or when <file> is '-', read Markdown source text from
+# standard input.  Optionally, the RDISCOUNT_EXTENSIONS environment variable
+# can specify a comma-separated list of extensions to enable in RDiscount.
+#
 if ARGV.include?('--help')
   File.read(__FILE__).split("\n").grep(/^# /).each do |line|
     puts line[2..-1]
@@ -10,4 +16,5 @@ if ARGV.include?('--help')
 end
 
 require 'rdiscount'
-STDOUT.write(RDiscount.new(ARGF.read).to_html)
+extensions = ENV['RDISCOUNT_EXTENSIONS'].to_s.split(',').map{ |key| key.to_sym }
+STDOUT.write(RDiscount.new(ARGF.read, *extensions).to_html)
diff --git a/debian/changelog b/debian/changelog
index 7b99bce..97a8989 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+ruby-rdiscount (2.2.7-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 26 Feb 2023 06:10:46 -0000
+
 ruby-rdiscount (2.1.8-2) unstable; urgency=medium
 
   [ Cédric Boutillier ]
diff --git a/debian/patches/01_use-system-libmarkdown.patch b/debian/patches/01_use-system-libmarkdown.patch
index 7f6fb9c..7067298 100644
--- a/debian/patches/01_use-system-libmarkdown.patch
+++ b/debian/patches/01_use-system-libmarkdown.patch
@@ -5,9 +5,11 @@ Forwarded: not-needed
 Author: Alessandro Ghedini <ghedo@debian.org>
 Last-Update: 2014-04-12
 
---- a/Rakefile
-+++ b/Rakefile
-@@ -17,7 +17,7 @@
+Index: ruby-rdiscount.git/Rakefile
+===================================================================
+--- ruby-rdiscount.git.orig/Rakefile
++++ ruby-rdiscount.git/Rakefile
+@@ -17,7 +17,7 @@ file "ext/ruby-#{RUBYDIGEST}" do |f|
  end
  CLEAN.include "ext/ruby-*"
  
@@ -16,9 +18,11 @@ Last-Update: 2014-04-12
    chdir('ext') { ruby 'extconf.rb' }
  end
  CLEAN.include 'ext/Makefile', 'ext/mkmf.log'
---- a/ext/extconf.rb
-+++ b/ext/extconf.rb
-@@ -2,6 +2,8 @@
+Index: ruby-rdiscount.git/ext/extconf.rb
+===================================================================
+--- ruby-rdiscount.git.orig/ext/extconf.rb
++++ ruby-rdiscount.git/ext/extconf.rb
+@@ -2,6 +2,8 @@ require 'mkmf'
  
  dir_config('rdiscount')
  
diff --git a/ext/Csio.c b/ext/Csio.c
index 5706657..1b418e0 100644
--- a/ext/Csio.c
+++ b/ext/Csio.c
@@ -50,11 +50,11 @@ Cswrite(Cstring *iot, char *bfr, int size)
 /* reparse() into a cstring
  */
 void
-Csreparse(Cstring *iot, char *buf, int size, int flags)
+Csreparse(Cstring *iot, char *buf, int size, mkd_flag_t flags)
 {
     MMIOT f;
     ___mkd_initmmiot(&f, 0);
-    ___mkd_reparse(buf, size, 0, &f, 0);
+    ___mkd_reparse(buf, size, flags, &f, 0);
     ___mkd_emblock(&f);
     SUFFIX(*iot, T(f.out), S(f.out));
     ___mkd_freemmiot(&f, 0);
diff --git a/ext/VERSION b/ext/VERSION
index ebf14b4..541133c 100644
--- a/ext/VERSION
+++ b/ext/VERSION
@@ -1 +1 @@
-2.1.8
+2.2.7c
diff --git a/ext/amalloc.c b/ext/amalloc.c
index bb20ab6..922db77 100644
--- a/ext/amalloc.c
+++ b/ext/amalloc.c
@@ -5,10 +5,11 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include "config.h"
 
 #define MAGIC 0x1f2e3d4c
 
-struct alist { int magic, size; struct alist *next, *last; };
+struct alist { int magic, size, index; int *end; struct alist *next, *last; };
 
 static struct alist list =  { 0, 0, 0, 0 };
 
@@ -16,14 +17,32 @@ static int mallocs=0;
 static int reallocs=0;
 static int frees=0;
 
+static int index = 0;
+
+static void
+die(char *msg, int index)
+{
+    fprintf(stderr, msg, index);
+    abort();
+}
+
+
 void *
-acalloc(int size, int count)
+acalloc(int count, int size)
 {
-    struct alist *ret = calloc(size + sizeof(struct alist), count);
+    struct alist *ret;
+
+    if ( size > 1 ) {
+	count *= size;
+	size = 1;
+    }
 
-    if ( ret ) {
+    if ( ret = calloc(count + sizeof(struct alist) + sizeof(int), size) ) {
 	ret->magic = MAGIC;
 	ret->size = size * count;
+	ret->index = index ++;
+	ret->end = (int*)(count + (char*) (ret + 1));
+	*(ret->end) = ~MAGIC;
 	if ( list.next ) {
 	    ret->next = list.next;
 	    ret->last = &list;
@@ -54,6 +73,8 @@ afree(void *ptr)
     struct alist *p2 = ((struct alist*)ptr)-1;
 
     if ( p2->magic == MAGIC ) {
+	if ( ! (p2->end && *(p2->end) == ~MAGIC) )
+	    die("goddam: corrupted memory block %d in free()!\n", p2->index);
 	p2->last->next = p2->next;
 	p2->next->last = p2->last;
 	++frees;
@@ -71,12 +92,16 @@ arealloc(void *ptr, int size)
     struct alist save;
 
     if ( p2->magic == MAGIC ) {
+	if ( ! (p2->end && *(p2->end) == ~MAGIC) )
+	    die("goddam: corrupted memory block %d in realloc()!\n", p2->index);
 	save.next = p2->next;
 	save.last = p2->last;
-	p2 = realloc(p2, sizeof(*p2) + size);
+	p2 = realloc(p2, sizeof(int) + sizeof(*p2) + size);
 
 	if ( p2 ) {
 	    p2->size = size;
+	    p2->end = (int*)(size + (char*) (p2 + 1));
+	    *(p2->end) = ~MAGIC;
 	    p2->next->last = p2;
 	    p2->last->next = p2;
 	    ++reallocs;
diff --git a/ext/blocktags b/ext/blocktags
index e7bdf73..55ac4b4 100644
--- a/ext/blocktags
+++ b/ext/blocktags
@@ -17,6 +17,7 @@ static struct kw blocktags[] = {
    { "PRE", 3, 0 },
    { "WBR", 3, 0 },
    { "XMP", 3, 0 },
+   { "FORM", 4, 0 },
    { "NOBR", 4, 0 },
    { "STYLE", 5, 0 },
    { "TABLE", 5, 0 },
@@ -30,4 +31,4 @@ static struct kw blocktags[] = {
    { "BLOCKQUOTE", 10, 0 },
 };
 
-#define NR_blocktags 29
+#define NR_blocktags 30
diff --git a/ext/config.h b/ext/config.h
index a009b20..4476ed5 100644
--- a/ext/config.h
+++ b/ext/config.h
@@ -7,15 +7,7 @@
 /* tabs are four spaces */
 #define TABSTOP 4
 
-/* enable fenced code blocks */
-#define WITH_FENCED_CODE 1
-
-/* include - and _ as acceptable characters in HTML tag names */
-#define WITH_GITHUB_TAGS 1
-
-/* enable discount and PHP Markdown Extra definition lists */
-#define USE_EXTRA_DL 1
-#define USE_DISCOUNT_DL 1
+#define DESTRUCTOR  __attribute__((__destructor__))
 
 /* these are setup by extconf.rb */
 #if HAVE_RANDOM
diff --git a/ext/css.c b/ext/css.c
index 3eb30b3..fd9ede9 100644
--- a/ext/css.c
+++ b/ext/css.c
@@ -54,15 +54,13 @@ mkd_css(Document *d, char **res)
 	stylesheets(d->code, &f);
 			
 	if ( (size = S(f)) > 0 ) {
+	    /* null-terminate, then strdup() into a free()able memory
+	     * chunk
+	     */
 	    EXPAND(f) = 0;
-			/* HACK ALERT! HACK ALERT! HACK ALERT! */
-	    *res = T(f);/* we know that a T(Cstring) is a character pointer */
-			/* so we can simply pick it up and carry it away, */
-			/* leaving the husk of the Ctring on the stack */
-			/* END HACK ALERT */
+	    *res = strdup(T(f));
 	}
-	else
-	    DELETE(f);
+	DELETE(f);
 	return size;
     }
     return EOF;
@@ -75,11 +73,13 @@ int
 mkd_generatecss(Document *d, FILE *f)
 {
     char *res;
-    int written = EOF, size = mkd_css(d, &res);
+    int written;
+    int size = mkd_css(d, &res);
 
-    if ( size > 0 )
-	written = fwrite(res, 1, size, f);
+    written = (size > 0) ? fwrite(res,1,size,f) : 0;
+    
     if ( res )
 	free(res);
+    
     return (written == size) ? size : EOF;
 }
diff --git a/ext/cstring.h b/ext/cstring.h
index a03ba2c..f0b7410 100644
--- a/ext/cstring.h
+++ b/ext/cstring.h
@@ -72,6 +72,5 @@ typedef STRING(char) Cstring;
 extern void Csputc(int, Cstring *);
 extern int Csprintf(Cstring *, char *, ...);
 extern int Cswrite(Cstring *, char *, int);
-extern void Csreparse(Cstring *, char *, int, int);
 
 #endif/*_CSTRING_D*/
diff --git a/ext/docheader.c b/ext/docheader.c
index 073f6da..2bde634 100644
--- a/ext/docheader.c
+++ b/ext/docheader.c
@@ -17,7 +17,12 @@
 static char *
 onlyifset(Line *l)
 {
-    char *ret = T(l->text) + l->dle;
+    char *ret;
+
+    if ( l->dle < 0 || l->dle >= S(l->text) )
+	return 0;
+
+    ret = T(l->text) + l->dle;
 
     return ret[0] ? ret : 0;
 }
diff --git a/ext/dumptree.c b/ext/dumptree.c
index 0c0c01f..e3fbaf3 100644
--- a/ext/dumptree.c
+++ b/ext/dumptree.c
@@ -108,9 +108,18 @@ dumptree(Paragraph *pp, Stack *sp, FILE *f)
 	    changepfx(sp, '`');
 	printpfx(sp, f);
 
-	d = fprintf(f, "[%s", Pptype(pp->typ));
+	if ( pp->typ == HDR )
+	    d += fprintf(f, "[h%d", pp->hnumber);
+	else
+	    d = fprintf(f, "[%s", Pptype(pp->typ));
 	if ( pp->ident )
 	    d += fprintf(f, " %s", pp->ident);
+
+#ifdef GITHUB_CHECKBOX
+	if ( pp->flags )
+	    d += fprintf(f, " %x", pp->flags);
+#endif
+	    
 	if ( pp->align > 1 )
 	    d += fprintf(f, ", <%s>", Begin[pp->align]);
 
@@ -134,7 +143,7 @@ dumptree(Paragraph *pp, Stack *sp, FILE *f)
 
 
 int
-mkd_dump(Document *doc, FILE *out, int flags, char *title)
+mkd_dump(Document *doc, FILE *out, mkd_flag_t flags, char *title)
 {
     Stack stack;
 
@@ -145,7 +154,6 @@ mkd_dump(Document *doc, FILE *out, int flags, char *title)
 	dumptree(doc->code, &stack, out);
 	DELETE(stack);
 
-	mkd_cleanup(doc);
 	return 0;
     }
     return -1;
diff --git a/ext/extconf.rb b/ext/extconf.rb
index 0eef0f5..442eef7 100644
--- a/ext/extconf.rb
+++ b/ext/extconf.rb
@@ -8,7 +8,7 @@ HAVE_RAND = have_func('rand')
 HAVE_SRAND = have_func('srand')
 
 def sized_int(size, types)
-  types.find { |type| check_sizeof(type) == 4 } ||
+  types.find { |type| check_sizeof(type) == size } ||
     abort("no int with size #{size}")
 end
 
@@ -33,6 +33,7 @@ open(File.join(File.dirname(__FILE__), "ruby-config.h"), "wb") do |f|
 end
 
 $defs.push("-DVERSION=\\\"#{VERSION}\\\"")
+$defs.push("-DBRANCH=\"\"")
 
 # Post XCode 5.1 the command line tools on OS X treat unrecognised 
 # command line options as errors and it's been seen that
@@ -42,4 +43,8 @@ if /darwin|mac os/.match RbConfig::CONFIG['host_os']
   $DLDFLAGS.gsub!("-multiply_definedsuppress", "")
 end
 
+if /mswin/.match RbConfig::CONFIG['host_os']
+  $defs.push("-Dinline=__inline")
+end
+
 create_makefile('rdiscount')
diff --git a/ext/flags.c b/ext/flags.c
index cc1c589..78ff3dc 100644
--- a/ext/flags.c
+++ b/ext/flags.c
@@ -2,7 +2,7 @@
 #include "markdown.h"
 
 struct flagnames {
-    DWORD flag;
+    mkd_flag_t flag;
     char *name;
 };
 
@@ -30,12 +30,20 @@ static struct flagnames flagnames[] = {
     { MKD_NODLIST,        "!DLIST" },
     { MKD_EXTRA_FOOTNOTE, "FOOTNOTE" },
     { MKD_NOSTYLE,        "!STYLE" },
+    { MKD_NODLDISCOUNT,   "!DLDISCOUNT" },
+    { MKD_DLEXTRA,        "DLEXTRA" },
+    { MKD_FENCEDCODE,     "FENCEDCODE" },
+    { MKD_IDANCHOR,       "IDANCHOR" },
+    { MKD_GITHUBTAGS,     "GITHUBTAGS" },
+    { MKD_URLENCODEDANCHOR, "URLENCODEDANCHOR" },
+    { MKD_LATEX,          "LATEX" },
+    { MKD_EXPLICITLIST,   "EXPLICITLIST" },
 };
 #define NR(x)	(sizeof x/sizeof x[0])
 
 
 void
-mkd_flags_are(FILE *f, DWORD flags, int htmlplease)
+mkd_flags_are(FILE *f, mkd_flag_t flags, int htmlplease)
 {
     int i;
     int not, set, even=1;
diff --git a/ext/generate.c b/ext/generate.c
index 2516a09..3899f18 100644
--- a/ext/generate.c
+++ b/ext/generate.c
@@ -56,16 +56,16 @@ peek(MMIOT *f, int i)
 
     i += (f->isp-1);
 
-    return (i >= 0) && (i < S(f->in)) ? T(f->in)[i] : EOF;
+    return (i >= 0) && (i < S(f->in)) ? (unsigned char)T(f->in)[i] : EOF;
 }
 
 
 /* pull a byte from the input buffer
  */
-static inline int
+static inline unsigned int
 pull(MMIOT *f)
 {
-    return ( f->isp < S(f->in) ) ? T(f->in)[f->isp++] : EOF;
+    return ( f->isp < S(f->in) ) ? (unsigned char)T(f->in)[f->isp++] : EOF;
 }
 
 
@@ -108,8 +108,10 @@ isthisnonword(MMIOT *f, int i)
 
 
 /* return/set the current cursor position
+ * (when setting the current cursor position we also need to flush the
+ * last character written cache)
  */
-#define mmiotseek(f,x)	(f->isp = x)
+#define mmiotseek(f,x)	((f->isp = x), (f->last = 0))
 #define mmiottell(f)	(f->isp)
 
 
@@ -129,7 +131,7 @@ static void
 Qchar(int c, MMIOT *f)
 {
     block *cur;
-    
+
     if ( S(f->Q) == 0 ) {
 	cur = &EXPAND(f->Q);
 	memset(cur, 0, sizeof *cur);
@@ -139,7 +141,7 @@ Qchar(int c, MMIOT *f)
 	cur = &T(f->Q)[S(f->Q)-1];
 
     EXPAND(cur->b_text) = c;
-    
+
 }
 
 
@@ -178,6 +180,16 @@ Qprintf(MMIOT *f, char *fmt, ...)
 }
 
 
+/* Qanchor() prints out a suitable-for-id-tag version of a string
+ */
+static void
+Qanchor(struct line *p, MMIOT *f)
+{
+    mkd_string_to_anchor(T(p->text), S(p->text),
+			 (mkd_sta_function_t)Qchar, f, 1, f);
+}
+
+
 /* Qem()
  */
 static void
@@ -197,13 +209,13 @@ Qem(MMIOT *f, char c, int count)
 /* generate html from a markup fragment
  */
 void
-___mkd_reparse(char *bfr, int size, int flags, MMIOT *f, char *esc)
+___mkd_reparse(char *bfr, int size, mkd_flag_t flags, MMIOT *f, char *esc)
 {
     MMIOT sub;
     struct escaped e;
 
     ___mkd_initmmiot(&sub, f->footnotes);
-    
+
     sub.flags = f->flags | flags;
     sub.cb = f->cb;
     sub.ref_prefix = f->ref_prefix;
@@ -219,11 +231,16 @@ ___mkd_reparse(char *bfr, int size, int flags, MMIOT *f, char *esc)
     push(bfr, size, &sub);
     pushc(0, &sub);
     S(sub.in)--;
-    
+
     text(&sub);
     ___mkd_emblock(&sub);
-    
+
     Qwrite(T(sub.out), S(sub.out), f);
+    /* inherit the last character printed from the reparsed
+     * text;  this way superscripts can work when they're
+     * applied to something embedded in a link
+     */
+    f->last = sub.last;
 
     ___mkd_freemmiot(&sub, f->footnotes);
 }
@@ -263,7 +280,7 @@ puturl(char *s, int size, MMIOT *f, int display)
 	    if ( !( ispunct(c) || isspace(c) ) )
 		Qchar('\\', f);
 	}
-	
+
 	if ( c == '&' )
 	    Qstring("&amp;", f);
 	else if ( c == '<' )
@@ -431,7 +448,7 @@ linkybroket(MMIOT *f, int image, Footnote *p)
     if ( good ) {
 	if ( peek(f, 1) == ')' )
 	    pull(f);
-	    
+
 	___mkd_tidy(&p->link);
     }
 
@@ -456,7 +473,7 @@ linkyurl(MMIOT *f, int image, Footnote *p)
 
     if ( c == '<' ) {
 	pull(f);
-	if ( !(f->flags & MKD_1_COMPAT) )
+	if ( !is_flag_set(f->flags, MKD_1_COMPAT) )
 	    return linkybroket(f,image,p);
 	mayneedtotrim=1;
     }
@@ -477,12 +494,12 @@ linkyurl(MMIOT *f, int image, Footnote *p)
     }
     if ( peek(f, 1) == ')' )
 	pull(f);
-	
+
     ___mkd_tidy(&p->link);
-    
+
     if ( mayneedtotrim && (T(p->link)[S(p->link)-1] == '>') )
 	--S(p->link);
-    
+
     return 1;
 }
 
@@ -578,12 +595,12 @@ static void
 printlinkyref(MMIOT *f, linkytype *tag, char *link, int size)
 {
     char *edit;
-    
-    if ( f->flags & IS_LABEL )
+
+    if ( is_flag_set(f->flags, IS_LABEL) )
 	return;
-    
+
     Qstring(tag->link_pfx, f);
-	
+
     if ( tag->kind & IS_URL ) {
 	if ( f->cb && f->cb->e_url && (edit = (*f->cb->e_url)(link, size, f->cb->e_data)) ) {
 	    puturl(edit, strlen(edit), f, 0);
@@ -623,7 +640,7 @@ extra_linky(MMIOT *f, Cstring text, Footnote *ref)
 {
     if ( ref->flags & REFERENCED )
 	return 0;
-	
+
     if ( f->flags & IS_LABEL )
     	___mkd_reparse(T(text), S(text), linkt.flags, f, 0);
     else {
@@ -637,6 +654,32 @@ extra_linky(MMIOT *f, Cstring text, Footnote *ref)
 } /* extra_linky */
 
 
+
+/* check a url (or url fragment to see that it begins with a known good
+ * protocol (or no protocol at all)
+ */
+static int
+safelink(Cstring link)
+{
+    char *p, *colon;
+
+    if ( T(link) == 0 )	/* no link; safe */
+	return 1;
+
+    p = T(link);
+    if ( (colon = memchr(p, ':', S(link))) == 0 )
+	return 1; /* no protocol specified: safe */
+
+    if ( !isalpha(*p) )	/* protocol/method is [alpha][alnum or '+.-'] */
+	return 1;
+    while ( ++p < colon )
+	if ( !(isalnum(*p) || *p == '.' || *p == '+' || *p == '-') )
+	    return 1;
+
+    return isautoprefix(T(link), S(link));
+}
+
+
 /* print out a linky (or fail if it's Not Allowed)
  */
 static int
@@ -648,12 +691,10 @@ linkyformat(MMIOT *f, Cstring text, int image, Footnote *ref)
     if ( image )
 	tag = &imaget;
     else if ( tag = pseudo(ref->link) ) {
-	if ( f->flags & (MKD_NO_EXT|MKD_SAFELINK) )
+	if ( is_flag_set(f->flags, MKD_NO_EXT) || is_flag_set(f->flags, MKD_SAFELINK) )
 	    return 0;
     }
-    else if ( (f->flags & MKD_SAFELINK) && T(ref->link)
-				        && (T(ref->link)[0] != '/')
-				        && !isautoprefix(T(ref->link), S(ref->link)) )
+    else if ( is_flag_set(f->flags, MKD_SAFELINK) && !safelink(ref->link) )
 	/* if MKD_SAFELINK, only accept links that are local or
 	 * a well-known protocol
 	 */
@@ -664,7 +705,7 @@ linkyformat(MMIOT *f, Cstring text, int image, Footnote *ref)
     if ( f->flags & tag->flags )
 	return 0;
 
-    if ( f->flags & IS_LABEL )
+    if ( is_flag_set(f->flags, IS_LABEL) )
 	___mkd_reparse(T(text), S(text), tag->flags, f, 0);
     else if ( tag->link_pfx ) {
 	printlinkyref(f, tag, T(ref->link), S(ref->link));
@@ -700,7 +741,7 @@ linkylinky(int image, MMIOT *f)
     int start = mmiottell(f);
     Cstring name;
     Footnote key, *ref;
-		
+
     int status = 0;
     int extra_footnote = 0;
 
@@ -718,7 +759,7 @@ linkylinky(int image, MMIOT *f)
 
 	    if ( isspace(peek(f,1)) )
 		pull(f);
-	    
+
 	    if ( peek(f,1) == '[' ) {
 		pull(f);	/* consume leading '[' */
 		goodlink = linkylabel(f, &key.tag);
@@ -728,12 +769,12 @@ linkylinky(int image, MMIOT *f)
 		 * require a second []
 		 */
 		mmiotseek(f, implicit_mark);
-		goodlink = !(f->flags & MKD_1_COMPAT);
+		goodlink = !is_flag_set(f->flags, MKD_1_COMPAT);
 
-		if ( (f->flags & MKD_EXTRA_FOOTNOTE) && (!image) && S(name) && T(name)[0] == '^' )
+		if ( is_flag_set(f->flags, MKD_EXTRA_FOOTNOTE) && (!image) && S(name) && T(name)[0] == '^' )
 		    extra_footnote = 1;
 	    }
-	    
+
 	    if ( goodlink ) {
 		if ( !S(key.tag) ) {
 		    DELETE(key.tag);
@@ -777,7 +818,7 @@ cputc(int c, MMIOT *f)
     }
 }
 
- 
+
 /*
  * convert an email address to a string of nonsense
  */
@@ -786,7 +827,7 @@ mangle(char *s, int len, MMIOT *f)
 {
     while ( len-- > 0 ) {
 #if DEBIAN_GLITCH
-	Qprintf(f, "&#02d;", *((unsigned char*)(s++)) );
+	Qprintf(f, "&#%02d;", *((unsigned char*)(s++)) );
 #else
 	Qstring("&#", f);
 	Qprintf(f, COINTOSS() ? "x%02x;" : "%02d;", *((unsigned char*)(s++)) );
@@ -819,7 +860,7 @@ matchticks(MMIOT *f, int tickchar, int ticks, int *endticks)
 {
     int size, count, c;
     int subsize=0, subtick=0;
-    
+
     *endticks = ticks;
     for (size = 0; (c=peek(f,size+ticks)) != EOF; size ++) {
 	if ( (c == tickchar) && ( count = nrticks(size+ticks,tickchar,f)) ) {
@@ -852,7 +893,7 @@ code(MMIOT *f, char *s, int length)
     int i,c;
 
     for ( i=0; i < length; i++ )
-	if ( (c = s[i]) == MKD_EOLN)  /* ^C: expand back to 2 spaces */
+	if ( (c = s[i]) == MKD_EOLN)  /* expand back to 2 spaces */
 	    Qstring("  ", f);
 	else if ( c == '\\' && (i < length-1) && escaped(f, s[i+1]) )
 	    cputc(s[++i], f);
@@ -860,7 +901,6 @@ code(MMIOT *f, char *s, int length)
 	    cputc(c, f);
 } /* code */
 
-
 /*  delspan() -- write out a chunk of text, blocking with <del>...</del>
  */
 static void
@@ -871,6 +911,41 @@ delspan(MMIOT *f, int size)
     Qstring("</del>", f);
 }
 
+#ifdef TYPORA
+/*  subspan() -- write out a chunk of text, blocking with <sub>...</sub>
+ */
+static void
+subspan(MMIOT *f, int size)
+{
+    Qstring("<sub>", f);
+    ___mkd_reparse(cursor(f)-1, size, 0, f, 0);
+    Qstring("</sub>", f);
+}
+
+
+/*  supspan() -- write out a chunk of text, blocking with <sup>...</sup>
+ */
+static void
+supspan(MMIOT *f, int size)
+{
+    Qstring("<sup>", f);
+    ___mkd_reparse(cursor(f)-1, size, 0, f, 0);
+    Qstring("</sup>", f);
+}
+
+
+/*  highlightspan() -- write out a chunk of text, blocking with <mark>...</mark>
+ */
+static void
+highlightspan(MMIOT *f, int size)
+{
+    Qstring("<mark>", f);
+    ___mkd_reparse(cursor(f)-1, size, 0, f, 0);
+    Qstring("</mark>", f);
+}
+#endif
+
+
 
 /*  codespan() -- write out a chunk of text as code, trimming one
  *                space off the front and/or back as appropriate.
@@ -882,7 +957,7 @@ codespan(MMIOT *f, int size)
 
     if ( size > 1 && peek(f, size-1) == ' ' ) --size;
     if ( peek(f,i) == ' ' ) ++i, --size;
-    
+
     Qstring("<code>", f);
     code(f, cursor(f)+(i-1), size);
     Qstring("</code>", f);
@@ -897,12 +972,12 @@ forbidden_tag(MMIOT *f)
 {
     int c = toupper(peek(f, 1));
 
-    if ( f->flags & MKD_NOHTML )
+    if ( is_flag_set(f->flags, MKD_NOHTML) )
 	return 1;
 
-    if ( c == 'A' && (f->flags & MKD_NOLINKS) && !isthisalnum(f,2) )
+    if ( c == 'A' && is_flag_set(f->flags, MKD_NOLINKS) && !isthisalnum(f,2) )
 	return 1;
-    if ( c == 'I' && (f->flags & MKD_NOIMAGE)
+    if ( c == 'I' && is_flag_set(f->flags, MKD_NOIMAGE)
 		  && strncasecmp(cursor(f)+1, "MG", 2) == 0
 		  && !isthisalnum(f,4) )
 	return 1;
@@ -919,17 +994,17 @@ static int
 maybe_address(char *p, int size)
 {
     int ok = 0;
-    
+
     for ( ;size && (isalnum(*p) || strchr("._-+*", *p)); ++p, --size)
 	;
 
     if ( ! (size && *p == '@') )
 	return 0;
-    
+
     --size, ++p;
 
     if ( size && *p == '.' ) return 0;
-    
+
     for ( ;size && (isalnum(*p) || strchr("._-+", *p)); ++p, --size )
 	if ( *p == '.' && size > 1 ) ok = 1;
 
@@ -948,8 +1023,8 @@ process_possible_link(MMIOT *f, int size)
     int address= 0;
     int mailto = 0;
     char *text = cursor(f);
-    
-    if ( f->flags & MKD_NOLINKS ) return 0;
+
+    if ( is_flag_set(f->flags, MKD_NOLINKS) ) return 0;
 
     if ( (size > 7) && strncasecmp(text, "mailto:", 7) == 0 ) {
 	/* if it says it's a mailto, it's a mailto -- who am
@@ -984,6 +1059,17 @@ process_possible_link(MMIOT *f, int size)
 } /* process_possible_link */
 
 
+/*
+ * check if a character is one of the things the reference implementation considers valid for starting
+ * a html(ish) tag
+ */
+static inline int
+is_a_strict_tag_prefix(int c)
+{
+    return isalpha(c) || (c == '/') || (c == '!') || (c == '$') || (c == '?');
+}
+
+
 /* 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
@@ -993,56 +1079,56 @@ process_possible_link(MMIOT *f, int size)
 static int
 maybe_tag_or_link(MMIOT *f)
 {
-    int c, size;
-    int maybetag = 1;
+    int c, size=0;
 
-    if ( f->flags & MKD_TAGTEXT )
+    if ( is_flag_set(f->flags, MKD_TAGTEXT) )
 	return 0;
 
-    for ( size=0; (c = peek(f, size+1)) != '>'; size++) {
-	if ( c == EOF )
-	    return 0;
-	else if ( c == '\\' ) {
-	    maybetag=0;
-	    if ( peek(f, size+2) != EOF )
-		size++;
+    c = peek(f, 1);
+
+
+    if ( is_a_strict_tag_prefix(c) ) {
+	/* By decree of Markdown.pl *this is a tag* and we want to absorb everything up
+	 * to the next '>', unless interrupted by another '<' OR a '`', at which point
+	 * we kick it back to the caller as plain old text.
+	 */
+	size=1;
+	while ( (c=peek(f,size+1)) != '>' ) {
+	    if ( c == EOF || c == '<' )
+		return 0;
+	    if ( is_flag_set(f->flags, MKD_STRICT) ) {
+		if ( c == '`' )
+		    return 0;
+	    }
+	    size++;
 	}
-	else if ( isspace(c) )
-	    break;
-#if WITH_GITHUB_TAGS
-	else if ( ! (c == '/' || c == '-' || c == '_' || isalnum(c) ) )
-#else
-	else if ( ! (c == '/' || isalnum(c) ) )
-#endif
-	    maybetag=0;
     }
 
-    if ( size ) {
-	if ( maybetag || (size >= 3 && strncmp(cursor(f), "!--", 3) == 0) ) {
+    if ( size > 0 ) {
+	if ( process_possible_link(f, size) ) {
+	    shift(f, size+1);
+	    return 1;
+	}
+	else {
+	    int i;
 
-	    /* It is not a html tag unless we find the closing '>' in
-	     * the same block.
-	     */
-	    while ( (c = peek(f, size+1)) != '>' )
-		if ( c == EOF )
-		    return 0;
-		else
-		    size++;
-	    
 	    if ( forbidden_tag(f) )
 		return 0;
 
-	    Qchar('<', f);
-	    while ( ((c = peek(f, 1)) != EOF) && (c != '>') )
-		Qchar(pull(f), f);
-	    return 1;
-	}
-	else if ( !isspace(c) && process_possible_link(f, size) ) {
+	    for ( i=0; i <= size+1; i++ ) {
+		c = peek(f,i);
+
+		if ( (c == '&') && (i > 0) )
+		    Qstring("&amp;", f);
+		else
+		    Qchar(c, f);
+	    }
+
 	    shift(f, size+1);
 	    return 1;
 	}
     }
-    
+
     return 0;
 }
 
@@ -1064,6 +1150,8 @@ maybe_autolink(MMIOT *f)
 	     if ( peek(f, size+2) != EOF )
 		++size;
 	}
+	else if ( c & 0x80 )	/* HACK: ignore utf-8 extended characters */
+	    continue;
 	else if ( isspace(c) || strchr("'\"()[]{}<>`", c) || c == MKD_EOLN )
 	    break;
     }
@@ -1164,7 +1252,9 @@ smartypants(int c, int *flags, MMIOT *f)
 {
     int i;
 
-    if ( f->flags & (MKD_NOPANTS|MKD_TAGTEXT|IS_LABEL) )
+    if ( is_flag_set(f->flags, MKD_NOPANTS) 
+      || is_flag_set(f->flags, MKD_TAGTEXT)
+      || is_flag_set(f->flags, IS_LABEL) )
 	return 0;
 
     for ( i=0; i < NRSMART; i++)
@@ -1208,6 +1298,27 @@ smartypants(int c, int *flags, MMIOT *f)
 } /* smartypants */
 
 
+/* process latex with arbitrary 2-character ( $$ .. $$, \[ .. \], \( .. \)
+ * delimiters
+ */
+static int
+mathhandler(MMIOT *f, int e1, int e2)
+{
+    int i = 0;
+
+    while(peek(f, ++i) != EOF) {
+        if (peek(f, i) == e1 && peek(f, i+1) == e2) {
+	    cputc(peek(f,-1), f);
+	    cputc(peek(f, 0), f);
+	    while ( i-- > -1 )
+		cputc(pull(f), f);
+            return 1;
+        }
+    }
+    return 0;
+}
+
+
 /* process a body of text encased in some sort of tick marks.   If it
  * works, generate the output and return 1, otherwise just return 0 and
  * let the caller figure it out.
@@ -1235,7 +1346,7 @@ tickhandler(MMIOT *f, int tickchar, int minticks, int allow_space, spanhandler s
     return 0;
 }
 
-#define tag_text(f)	(f->flags & MKD_TAGTEXT)
+#define tag_text(f)	is_flag_set(f->flags, MKD_TAGTEXT)
 
 
 static void
@@ -1246,7 +1357,7 @@ text(MMIOT *f)
     int smartyflags = 0;
 
     while (1) {
-        if ( (f->flags & MKD_AUTOLINK) && isalpha(peek(f,1)) && !tag_text(f) )
+        if ( is_flag_set(f->flags, MKD_AUTOLINK) && isalpha(peek(f,1)) && !tag_text(f) )
 	    maybe_autolink(f);
 
         c = pull(f);
@@ -1274,7 +1385,7 @@ text(MMIOT *f)
 		    else
 			Qchar(c, f);
 		    break;
-			
+
 	case '!':   if ( peek(f,1) == '[' ) {
 			pull(f);
 			if ( tag_text(f) || !linkylinky(1, f) )
@@ -1283,13 +1394,35 @@ text(MMIOT *f)
 		    else
 			Qchar(c, f);
 		    break;
+
 	case '[':   if ( tag_text(f) || !linkylinky(0, f) )
 			Qchar(c, f);
 		    break;
+
+#ifdef TYPORA
+	case '=': if ( is_flag_set(f->flags, MKD_NOSUPERSCRIPT)
+			 || is_flag_set(f->flags, MKD_STRICT)
+			 || is_flag_set(f->flags, MKD_TAGTEXT)
+			 || ! tickhandler(f,c,2,0, highlightspan))
+			Qchar(c, f);
+		    break;
+
+	/* A^B^ -> A<sup>B</sup> */
+	case '^':   if ( is_flag_set(f->flags, MKD_NOSUPERSCRIPT)
+			 || is_flag_set(f->flags, MKD_STRICT)
+			 || is_flag_set(f->flags, MKD_TAGTEXT)
+			 || ! tickhandler(f,c,1,0, supspan))
+			Qchar(c, f);
+		    break;
+#else /* !TYPORA */
 	/* A^B -> A<sup>B</sup> */
-	case '^':   if ( (f->flags & (MKD_NOSUPERSCRIPT|MKD_STRICT|MKD_TAGTEXT))
-				|| (isthisnonword(f,-1) && peek(f,-1) != ')')
-				|| isthisspace(f,1) )
+	case '^':   if ( is_flag_set(f->flags, MKD_NOSUPERSCRIPT)
+			    || is_flag_set(f->flags, MKD_STRICT)
+			    || is_flag_set(f->flags, MKD_TAGTEXT)
+			    || (f->last == 0)
+			    || ((ispunct(f->last) || isspace(f->last))
+						    && f->last != ')')
+			    || isthisspace(f,1) )
 			Qchar(c,f);
 		    else {
 			char *sup = cursor(f);
@@ -1320,11 +1453,11 @@ text(MMIOT *f)
 			Qstring("</sup>", f);
 		    }
 		    break;
+#endif /* TYPORA */
 	case '_':
 	/* Underscores don't count if they're in the middle of a word */
-		    if ( !(f->flags & (MKD_NORELAXED|MKD_STRICT))
-					&& isthisalnum(f,-1)
-					 && isthisalnum(f,1) ) {
+		    if ( !(is_flag_set(f->flags, MKD_NORELAXED) || is_flag_set(f->flags, MKD_STRICT))
+				&& isthisalnum(f,-1) && isthisalnum(f,1) ) {
 			Qchar(c, f);
 			break;
 		    }
@@ -1344,8 +1477,17 @@ text(MMIOT *f)
 			Qem(f,c,rep);
 		    }
 		    break;
-	
-	case '~':   if ( (f->flags & (MKD_NOSTRIKETHROUGH|MKD_TAGTEXT|MKD_STRICT)) || ! tickhandler(f,c,2,0, delspan) )
+
+#ifdef TYPORA
+#define ticktick(f,c) (tickhandler(f,c,2,0,delspan) || tickhandler(f,c,1,0,subspan))
+#else
+#define ticktick(f,c) tickhandler(f,c,2,0,delspan)
+#endif
+
+	case '~':   if ( is_flag_set(f->flags, MKD_NOSTRIKETHROUGH)
+			 || is_flag_set(f->flags, MKD_STRICT)
+			 || is_flag_set(f->flags, MKD_TAGTEXT)
+			 || !ticktick(f,c) )
 			Qchar(c, f);
 		    break;
 
@@ -1365,28 +1507,35 @@ text(MMIOT *f)
 				    Qchar('\\', f);
 				    shift(f, -1);
 				}
-				
+
 				break;
-		    case '^':   if ( f->flags & (MKD_STRICT|MKD_NOSUPERSCRIPT) ) {
+		    case '^':   if ( is_flag_set(f->flags, MKD_STRICT)
+					|| is_flag_set(f->flags, MKD_NOSUPERSCRIPT) ) {
 				    Qchar('\\', f);
 				    shift(f,-1);
 				    break;
 				}
 				Qchar(c, f);
 				break;
-				
+
 		    case ':': case '|':
-				if ( f->flags & MKD_NOTABLES ) {
+				if ( is_flag_set(f->flags, MKD_NOTABLES) ) {
 				    Qchar('\\', f);
 				    shift(f,-1);
 				    break;
 				}
 				Qchar(c, f);
 				break;
-				
+
 		    case EOF:	Qchar('\\', f);
 				break;
-				
+
+		    case '[':
+		    case '(':   if ( is_flag_set(f->flags, MKD_LATEX)
+				   && mathhandler(f, '\\', (c =='(')?')':']') )
+				    break;
+				/* else fall through to default */
+
 		    default:    if ( escaped(f,c) ||
 				     strchr(">#.-+{}]![*_\\()`", c) )
 				    Qchar(c, f);
@@ -1398,8 +1547,12 @@ text(MMIOT *f)
 		    }
 		    break;
 
-	case '<':   if ( !maybe_tag_or_link(f) )
-			Qstring("&lt;", f);
+	case '<':   if ( !maybe_tag_or_link(f) ) {
+			if ( is_flag_set(f->flags, MKD_STRICT) && is_a_strict_tag_prefix(peek(f,1)) )
+			    Qchar(c, f);
+			else
+			    Qstring("&lt;", f);
+		    }
 		    break;
 
 	case '&':   j = (peek(f,1) == '#' ) ? 2 : 1;
@@ -1412,7 +1565,16 @@ text(MMIOT *f)
 			Qchar(c, f);
 		    break;
 
-	default:    Qchar(c, f);
+	case '$':   if ( is_flag_set(f->flags, MKD_LATEX) && (peek(f, 1) == '$') ) {
+			pull(f);
+			if ( mathhandler(f, '$', '$') )
+			    break;
+			Qchar('$', f);
+		    }
+		    /* fall through to default */
+
+	default:    f->last = c;
+		    Qchar(c, f);
 		    break;
 	}
     }
@@ -1426,26 +1588,22 @@ text(MMIOT *f)
 static void
 printheader(Paragraph *pp, MMIOT *f)
 {
-#if WITH_ID_ANCHOR
-    Qprintf(f, "<h%d", pp->hnumber);
-    if ( f->flags & MKD_TOC ) {
-	Qstring(" id=\"", f);
-	mkd_string_to_anchor(T(pp->text->text),
-			     S(pp->text->text),
-			     (mkd_sta_function_t)Qchar, f, 1);
-	Qchar('"', f);
-    }
-    Qchar('>', f);
-#else
-    if ( f->flags & MKD_TOC ) {
-	Qstring("<a name=\"", f);
-	mkd_string_to_anchor(T(pp->text->text),
-			     S(pp->text->text),
-			     (mkd_sta_function_t)Qchar, f, 1);
-	Qstring("\"></a>\n", f);
+    if ( is_flag_set(f->flags, MKD_IDANCHOR) ) {
+	Qprintf(f, "<h%d", pp->hnumber);
+	if ( is_flag_set(f->flags, MKD_TOC) ) {
+	    Qstring(" id=\"", f);
+	    Qanchor(pp->text, f);
+	    Qchar('"', f);
+	}
+	Qchar('>', f);
+    } else {
+	if ( is_flag_set(f->flags, MKD_TOC) ) {
+	    Qstring("<a name=\"", f);
+	    Qanchor(pp->text, f);
+	    Qstring("\"></a>\n", f);
+	}
+	Qprintf(f, "<h%d>", pp->hnumber);
     }
-    Qprintf(f, "<h%d>", pp->hnumber);
-#endif
     push(T(pp->text->text), S(pp->text->text), f);
     text(f);
     Qprintf(f, "</h%d>", pp->hnumber);
@@ -1471,7 +1629,7 @@ splat(Line *p, char *block, Istring align, int force, MMIOT *f)
     ___mkd_tidy(&p->text);
     if ( T(p->text)[S(p->text)-1] == '|' )
 	--S(p->text);
-    
+
     Qstring("<tr>\n", f);
     while ( idx < S(p->text) ) {
 	first = idx;
@@ -1532,7 +1690,7 @@ printtable(Paragraph *pp, MMIOT *f)
     for (p=T(dash->text), start=dash->dle; start < S(dash->text); ) {
 	char first, last;
 	int end;
-	
+
 	last=first=0;
 	for (end=start ; (end < S(dash->text)) && p[end] != '|'; ++ end ) {
 	    if ( p[end] == '\\' )
@@ -1574,9 +1732,10 @@ printtable(Paragraph *pp, MMIOT *f)
 static int
 printblock(Paragraph *pp, MMIOT *f)
 {
-    Line *t = pp->text;
     static char *Begin[] = { "", "<p>", "<p style=\"text-align:center;\">"  };
     static char *End[]   = { "", "</p>","</p>" };
+    Line *t = pp->text;
+    int align = pp->align;
 
     while (t) {
 	if ( S(t->text) ) {
@@ -1596,9 +1755,9 @@ printblock(Paragraph *pp, MMIOT *f)
 	}
 	t = t->next;
     }
-    Qstring(Begin[pp->align], f);
+    Qstring(Begin[align], f);
     text(f);
-    Qstring(End[pp->align], f);
+    Qstring(End[align], f);
     return 1;
 }
 
@@ -1608,8 +1767,44 @@ printcode(Line *t, char *lang, MMIOT *f)
 {
     int blanks;
 
+    if ( f->cb->e_codefmt ) {
+	/* external code block formatter;  copy the text into a buffer,
+	 * call the formatter to style it, then dump that styled text
+	 * directly to the queue
+	 */
+	char *text;
+	char *fmt;
+	int size, copy_p;
+	Line *p;
+
+	for (size=0, p = t; p; p = p->next )
+	    size += 1+S(p->text);
+
+	text = malloc(1+size);
+
+	for ( copy_p = 0; t ; t = t->next ) {
+	    memcpy(text+copy_p, T(t->text), S(t->text));
+	    copy_p += S(t->text);
+	    text[copy_p++] = '\n';
+	}
+	text[copy_p] = 0;
+
+	fmt = (*(f->cb->e_codefmt))(text, copy_p, (lang && lang[0]) ? lang : 0);
+	free(text);
+
+	if ( fmt ) {
+	    Qwrite(fmt, strlen(fmt), f);
+	    if ( f->cb->e_free )
+		(*(f->cb->e_free))(fmt, f->cb->e_data);
+	    return;
+	}
+	/* otherwise the external formatter failed and we need to 
+	 * fall back to the traditional codeblock format
+	 */
+    }
+
     Qstring("<pre><code", f);
-    if (lang) {
+    if (lang && lang[0]) {
       Qstring(" class=\"", f);
       Qstring(lang, f);
       Qstring("\"", f);
@@ -1634,7 +1829,7 @@ static void
 printhtml(Line *t, MMIOT *f)
 {
     int blanks;
-    
+
     for ( blanks=0; t ; t = t->next )
 	if ( S(t->text) ) {
 	    for ( ; blanks; --blanks ) 
@@ -1649,17 +1844,57 @@ printhtml(Line *t, MMIOT *f)
 
 
 static void
-htmlify(Paragraph *p, char *block, char *arguments, MMIOT *f)
+htmlify_paragraphs(Paragraph *p, MMIOT *f)
 {
     ___mkd_emblock(f);
-    if ( block )
-	Qprintf(f, arguments ? "<%s %s>" : "<%s>", block, arguments);
-    ___mkd_emblock(f);
 
     while (( p = display(p, f) )) {
 	___mkd_emblock(f);
 	Qstring("\n\n", f);
     }
+}
+
+
+#ifdef GITHUB_CHECKBOX
+static void
+li_htmlify(Paragraph *p, char *arguments, mkd_flag_t flags, MMIOT *f)
+{
+    ___mkd_emblock(f);
+
+    Qprintf(f, "<li");
+    if ( arguments )
+	Qprintf(f, " %s", arguments);
+    if ( flags & GITHUB_CHECK )
+	Qprintf(f, " class=\"github_checkbox\"");
+    Qprintf(f, ">");
+#if CHECKBOX_AS_INPUT
+    if ( flags & GITHUB_CHECK ) {
+	Qprintf(f, "<input disabled=\"\" type=\"checkbox\"");
+	if ( flags & IS_CHECKED )
+	    Qprintf(f, " checked=\"checked\"");
+	Qprintf(f, "/>");
+    }
+#else
+    if ( flags & GITHUB_CHECK )
+	Qprintf(f, flags & IS_CHECKED ? "&#x2611;" : "&#x2610;");
+#endif
+
+    htmlify_paragraphs(p, f);
+
+     Qprintf(f, "</li>");
+    ___mkd_emblock(f);
+}
+#endif
+
+
+static void
+htmlify(Paragraph *p, char *block, char *arguments, MMIOT *f)
+{
+    ___mkd_emblock(f);
+    if ( block )
+	Qprintf(f, arguments ? "<%s %s>" : "<%s>", block, arguments);
+
+    htmlify_paragraphs(p, f);
 
     if ( block )
 	 Qprintf(f, "</%s>", block);
@@ -1701,7 +1936,11 @@ listdisplay(int typ, Paragraph *p, MMIOT* f)
 	Qprintf(f, ">\n");
 
 	for ( ; p ; p = p->next ) {
+#ifdef GITHUB_CHECKBOX
+	    li_htmlify(p->down, p->ident, p->flags, f);
+#else
 	    htmlify(p->down, "li", p->ident, f);
+#endif
 	    Qchar('\n', f);
 	}
 
@@ -1716,7 +1955,7 @@ static Paragraph*
 display(Paragraph *p, MMIOT *f)
 {
     if ( !p ) return 0;
-    
+
     switch ( p->typ ) {
     case STYLE:
     case WHITESPACE:
@@ -1725,15 +1964,15 @@ display(Paragraph *p, MMIOT *f)
     case HTML:
 	printhtml(p->text, f);
 	break;
-	
+
     case CODE:
 	printcode(p->text, p->lang, f);
 	break;
-	
+
     case QUOTE:
 	htmlify(p->down, p->ident ? "div" : "blockquote", p->ident, f);
 	break;
-	
+
     case UL:
     case OL:
     case AL:
@@ -1759,7 +1998,7 @@ display(Paragraph *p, MMIOT *f)
     case SOURCE:
 	htmlify(p->down, 0, 0, f);
 	break;
-	
+
     default:
 	printblock(p, f);
 	break;
@@ -1780,17 +2019,17 @@ mkd_extra_footnotes(MMIOT *m)
 	return;
 
     Csprintf(&m->out, "\n<div class=\"footnotes\">\n<hr/>\n<ol>\n");
-    
+
     for ( i=1; i <= m->footnotes->reference; i++ ) {
 	for ( j=0; j < S(m->footnotes->note); j++ ) {
 	    t = &T(m->footnotes->note)[j];
 	    if ( (t->refnumber == i) && (t->flags & REFERENCED) ) {
-		Csprintf(&m->out, "<li id=\"%s:%d\">\n<p>",
+		Csprintf(&m->out, "<li id=\"%s:%d\">\n",
 			    p_or_nothing(m), t->refnumber);
-		Csreparse(&m->out, T(t->title), S(t->title), 0);
+		htmlify(t->text, 0, 0, m);
 		Csprintf(&m->out, "<a href=\"#%sref:%d\" rev=\"footnote\">&#8617;</a>",
 			    p_or_nothing(m), t->refnumber);
-		Csprintf(&m->out, "</p></li>\n");
+		Csprintf(&m->out, "</li>\n");
 	    }
 	}
     }
@@ -1805,23 +2044,26 @@ int
 mkd_document(Document *p, char **res)
 {
     int size;
-    
+
     if ( p && p->compiled ) {
 	if ( ! p->html ) {
 	    htmlify(p->code, 0, 0, p->ctx);
-	    if ( p->ctx->flags & MKD_EXTRA_FOOTNOTE )
+	    if ( is_flag_set(p->ctx->flags, MKD_EXTRA_FOOTNOTE) )
 		mkd_extra_footnotes(p->ctx);
 	    p->html = 1;
+	    size = S(p->ctx->out);
+
+	    if ( (size == 0) || T(p->ctx->out)[size-1] ) {
+		/* Add a null byte at the end of the generated html,
+		 * but pretend it doesn't exist.
+		 */
+		EXPAND(p->ctx->out) = 0;
+		--S(p->ctx->out);
+	    }
 	}
 
-	size = S(p->ctx->out);
-	
-	if ( (size == 0) || T(p->ctx->out)[size-1] )
-	    EXPAND(p->ctx->out) = 0;
-	
 	*res = T(p->ctx->out);
-	return size;
+	return S(p->ctx->out);
     }
     return EOF;
 }
-
diff --git a/ext/gethopt.c b/ext/gethopt.c
new file mode 100644
index 0000000..4c6e4ce
--- /dev/null
+++ b/ext/gethopt.c
@@ -0,0 +1,286 @@
+/*
+ * gehopt;  options processing with both single-character and whole-word
+ *           options both introduced with -
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "gethopt.h"
+
+
+void
+hoptset(ctx, argc, argv)
+struct h_context *ctx;
+int argc;
+char **argv;
+{
+    memset(ctx, 0, sizeof *ctx);
+    ctx->argc = argc;
+    ctx->argv = argv;
+    ctx->optind = 1;
+}
+
+
+char *
+hoptarg(ctx)
+struct h_context *ctx;
+{
+    return ctx->optarg;
+}
+
+int
+hoptind(ctx)
+struct h_context *ctx;
+{
+    return ctx->optind;
+}
+
+char
+hoptopt(ctx)
+struct h_context *ctx;
+{
+    return ctx->optopt;
+}
+
+
+int
+hopterr(ctx,val)
+struct h_context *ctx;
+{
+    int old = ctx->opterr;
+    
+    ctx->opterr = !!val;
+    return old;
+}
+
+
+struct h_opt *
+gethopt(ctx, opts, nropts)
+struct h_context *ctx;
+struct h_opt *opts;
+int nropts;
+{
+    int i;
+    int dashes;
+    
+
+    if ( (ctx == 0) || ctx->optend || (ctx->optind >= ctx->argc) )
+	return 0;
+    
+    ctx->optarg = 0;
+    ctx->optopt = 0;
+    
+    if ( ctx->optchar == 0) {
+	/* check for leading -
+	 */
+	if ( ctx->argv[ctx->optind][0] != '-' ) {
+	    /* out of arguments */
+	    ctx->optend = 1;
+	    return 0;
+	}
+
+	if ( ctx->argv[ctx->optind][1] == 0
+	  || strcmp(ctx->argv[ctx->optind], "--") == 0 ) {
+	    /* option list finishes with - or -- token
+	     */
+	    ctx->optend = 1;
+	    ctx->optind++;
+	    return 0;
+	}
+
+	dashes = 1;
+	if ( ctx->argv[ctx->optind][dashes] == '-' ) {
+	    /* support GNU-style long option double-dash prefix
+	     * (if gethopt is passed an unknown option with a double-dash
+	     *  prefix, it won't match a word and then the second dash
+	     *  will be scanned as if it was a regular old single-character
+	     *  option.)
+	     */
+	    dashes = 2;
+	}
+	
+	for ( i=0; i < nropts; i++ ) {
+	    if ( ! opts[i].optword ) 
+		continue;
+
+	    if (strcmp(opts[i].optword, dashes+(ctx->argv[ctx->optind]) ) == 0 ) {
+		if ( opts[i].opthasarg ) {
+		    if ( ctx->argc > ctx->optind ) {
+			ctx->optarg = ctx->argv[ctx->optind+1];
+			ctx->optind += 2;
+		    }
+		    else {
+			/* word argument with required arg at end of
+			 *command line
+			 */
+			if ( ctx->opterr )
+			    fprintf(stderr,
+				    "%s: option requires an argument -- %s\n",
+				    ctx->argv[0], opts[i].optword);
+			ctx->optind ++;
+			return HOPTERR;
+		    }
+		}
+		else {
+		    ctx->optind ++;
+		}
+		return &opts[i];
+	    }
+	}
+	ctx->optchar = 1;
+    }
+
+    ctx->optopt = ctx->argv[ctx->optind][ctx->optchar++];
+
+    if ( !ctx->optopt ) {
+	/* fell off the end of this argument */
+	ctx->optind ++;
+	ctx->optchar = 0;
+	return gethopt(ctx, opts, nropts);
+    }
+
+    for ( i=0; i<nropts; i++ ) {
+	if ( opts[i].optchar == ctx->optopt ) {
+	    /* found a single-char option!
+	     */
+	    if ( opts[i].opthasarg ) {
+		if ( ctx->argv[ctx->optind][ctx->optchar] ) {
+		    /* argument immediately follows this options (-Oc)
+		     */
+		    ctx->optarg = &ctx->argv[ctx->optind][ctx->optchar];
+		    ctx->optind ++;
+		    ctx->optchar = 0;
+		}
+		else if ( ctx->optind < ctx->argc-1 ) {
+		    /* argument is next arg (-O c)
+		     */
+		    ctx->optarg = &ctx->argv[ctx->optind+1][0];
+		    ctx->optind += 2;
+		    ctx->optchar = 0;
+		}
+		else {
+		    /* end of arg string (-O); set optarg to null, return
+		     * (should it opterr on me?)
+		     */
+		    ctx->optarg = 0;
+		    ctx->optind ++;
+		    ctx->optchar = 0;
+		    if ( ctx->opterr )
+			fprintf(stderr,
+				"%s: option requires an argument -- %c\n",
+				ctx->argv[0], opts[i].optchar);
+		    return HOPTERR;
+		}
+	    }
+	    else {
+		if ( !ctx->argv[ctx->optind][ctx->optchar] ) {
+		    ctx->optind ++;
+		    ctx->optchar = 0;
+		}
+	    }
+	    return &opts[i];
+	}
+    }
+    if ( ctx->opterr )
+	fprintf(stderr, "%s: illegal option -- %c\n", ctx->argv[0], ctx->optopt);
+    return HOPTERR;
+}
+
+
+void
+hoptusage(char *pgm, struct h_opt opts[], int nropts, char *arguments)
+{
+    int i;
+    int optcount;
+    
+    fprintf(stderr, "usage: %s", pgm);
+
+    /* print out the options that don't have flags first */
+    
+    for ( optcount=i=0; i < nropts; i++ ) {
+	if ( opts[i].optchar && !opts[i].opthasarg) {
+	    if (optcount == 0 )
+		fputs(" [-", stderr);
+	    fputc(opts[i].optchar, stderr);
+	    optcount++;
+	}
+    }
+    if ( optcount )
+	fputc(']', stderr);
+
+    /* print out the options WITH flags */
+    for ( i = 0; i < nropts; i++ )
+	if ( opts[i].optchar && opts[i].opthasarg)
+	    fprintf(stderr, " [-%c %s]", opts[i].optchar, opts[i].opthasarg);
+
+    /* print out the long options */
+    for ( i = 0; i < nropts; i++ )
+	if ( opts[i].optword ) {
+	    fprintf(stderr, " [-%s", opts[i].optword);
+	    if ( opts[i].opthasarg )
+		fprintf(stderr, " %s", opts[i].opthasarg);
+	    fputc(']', stderr);
+	}
+
+    /* print out the arguments string, if any */
+
+    if ( arguments )
+	fprintf(stderr, " %s", arguments);
+
+    /* and we're done */
+    fputc('\n', stderr);
+}
+
+
+#if DEBUG
+struct h_opt opts[] = {
+    { 0, "css",    0,  1, "css file" },
+    { 1, "header", 0,  1, "header file" },
+    { 2, 0,       'a', 0, "option a (no arg)" },
+    { 3, 0,       'b', 1, "option B (with arg)" },
+    { 4, "help",  '?', 0, "help message" },
+} ;
+
+#define NROPT (sizeof opts/sizeof opts[0])
+
+
+int
+main(argc, argv)
+char **argv;
+{
+    struct h_opt *ret;
+    struct h_context ctx;
+    int i;
+
+
+    hoptset(&ctx, argc, argv);
+    hopterr(&ctx, 1);
+
+    while (( ret = gethopt(&ctx, opts, NROPT) )) {
+
+	if ( ret != HOPTERR ) {
+	    if ( ret->optword )
+		printf("%s", ret->optword);
+	    else
+		printf("%c", ret->optchar);
+
+	    if ( ret->opthasarg ) {
+		if ( hoptarg(&ctx) )
+		    printf(" with argument \"%s\"", hoptarg(&ctx));
+		else
+		    printf(" with no argument?");
+	    }
+	    printf(" (%s)\n", ret->optdesc);
+	}
+    }
+
+    argc -= hoptind(&ctx);
+    argv += hoptind(&ctx);
+
+    for ( i=0; i < argc; i++ )
+	printf("%d: %s\n", i, argv[i]);
+    return 0;
+}
+
+#endif /*DEBUG*/
diff --git a/ext/gethopt.h b/ext/gethopt.h
new file mode 100644
index 0000000..65817b1
--- /dev/null
+++ b/ext/gethopt.h
@@ -0,0 +1,43 @@
+/*
+ * gethopt;  options processing with both single-character and whole-work
+ *           options both introduced with -
+ */
+
+#ifndef __GETHOPT_D
+#define __GETHOPT_D
+
+#include <stdio.h>
+#include <string.h>
+
+
+struct h_opt {
+    int  option;
+    char *optword;
+    char optchar;
+    char *opthasarg;
+    char *optdesc;
+} ;
+
+#define HOPTERR	((struct h_opt*)-1)
+
+struct h_context {
+    char **argv;
+    int    argc;
+    int    optchar;
+    int    optind;
+    char  *optarg;
+    char   optopt;
+    int    opterr:1;
+    int    optend:1;
+} ;
+
+extern char *hoptarg(struct h_context *);
+extern int   hoptind(struct h_context *);
+extern char  hoptopt(struct h_context *);
+extern void  hoptset(struct h_context *, int, char **);
+extern int   hopterr(struct h_context *, int);
+extern struct h_opt *gethopt(struct h_context *, struct h_opt*, int);
+
+extern void hoptusage(char *, struct h_opt*, int, char *);
+
+#endif/*__GETHOPT_D*/
diff --git a/ext/github_flavoured.c b/ext/github_flavoured.c
index 12ed609..019f783 100644
--- a/ext/github_flavoured.c
+++ b/ext/github_flavoured.c
@@ -30,7 +30,7 @@ gfm_populate(getc_func getc, void* ctx, int flags)
 
     if ( !a ) return 0;
 
-    a->tabstop = (flags & MKD_TABSTOP) ? 4 : TABSTOP;
+    a->tabstop = is_flag_set(flags, MKD_TABSTOP) ? 4 : TABSTOP;
 
     CREATE(line);
 
@@ -59,16 +59,17 @@ gfm_populate(getc_func getc, void* ctx, int flags)
 
     DELETE(line);
 
-    if ( (pandoc == 3) && !(flags & (MKD_NOHEADER|MKD_STRICT)) ) {
+    if ( (pandoc == 3) && !(is_flag_set(flags, MKD_NOHEADER)
+     || is_flag_set(flags, MKD_STRICT)) ) {
 	/* the first three lines started with %, so we have a header.
 	 * clip the first three lines out of content and hang them
 	 * off header.
 	 */
 	Line *headers = T(a->content);
 
-	a->title = headers;             __mkd_header_dle(a->title);
-	a->author= headers->next;       __mkd_header_dle(a->author);
-	a->date  = headers->next->next; __mkd_header_dle(a->date);
+	a->title = headers;             __mkd_trim_line(a->title, 1);
+	a->author= headers->next;       __mkd_trim_line(a->author, 1);
+	a->date  = headers->next->next; __mkd_trim_line(a->date, 1);
 
 	T(a->content) = headers->next->next->next;
     }
@@ -80,7 +81,7 @@ gfm_populate(getc_func getc, void* ctx, int flags)
 /* convert a block of text into a linked list
  */
 Document *
-gfm_string(const char *buf, int len, DWORD flags)
+gfm_string(const char *buf, int len, mkd_flag_t flags)
 {
     struct string_stream about;
 
@@ -94,7 +95,7 @@ gfm_string(const char *buf, int len, DWORD flags)
 /* convert a file into a linked list
  */
 Document *
-gfm_in(FILE *f, DWORD flags)
+gfm_in(FILE *f, mkd_flag_t flags)
 {
     return gfm_populate((getc_func)fgetc, f, flags & INPUT_MASK);
 }
diff --git a/ext/h1title.c b/ext/h1title.c
new file mode 100644
index 0000000..89fd9d2
--- /dev/null
+++ b/ext/h1title.c
@@ -0,0 +1,36 @@
+#include <stdio.h>
+#include "markdown.h"
+
+static Paragraph *
+mkd_h1(Paragraph *p)
+{
+    Paragraph *found;
+
+    while ( p ) {
+	if ( p->typ == HDR && p->hnumber == 1 )
+	     return p;
+       if ( p->down && (found = mkd_h1(p->down)) )
+	   return found;
+	p = p->next;
+    }
+    return 0;
+}
+
+char *
+mkd_h1_title(Document *doc, int flags)
+{
+    Paragraph *title;
+
+    if (doc && (title = mkd_h1(doc->code)) ) {
+	  char *generated;
+	  int size;
+
+	  /* assert that a H1 header is one line long, so that's
+	   * the only thing needed
+	    */
+	  size = mkd_line(T(title->text->text),
+			  S(title->text->text), &generated, flags|MKD_TAGTEXT);
+	  if ( size ) return generated;
+    }
+    return 0;
+}
diff --git a/ext/html5.c b/ext/html5.c
index de91b90..870589c 100644
--- a/ext/html5.c
+++ b/ext/html5.c
@@ -13,7 +13,6 @@ mkd_with_html5_tags()
     mkd_define_tag("ASIDE", 0);
     mkd_define_tag("FOOTER", 0);
     mkd_define_tag("HEADER", 0);
-    mkd_define_tag("HGROUP", 0);
     mkd_define_tag("NAV", 0);
     mkd_define_tag("SECTION", 0);
     mkd_define_tag("ARTICLE", 0);
diff --git a/ext/markdown.c b/ext/markdown.c
index f43f514..81243d8 100644
--- a/ext/markdown.c
+++ b/ext/markdown.c
@@ -147,12 +147,12 @@ typedef struct _flo {
 #define floindex(x) (x.i)
 
 
-static int
+static unsigned int
 flogetc(FLO *f)
 {
     if ( f && f->t ) {
 	if ( f->i < S(f->t->text) )
-	    return T(f->t->text)[f->i++];
+	    return (unsigned char)T(f->t->text)[f->i++];
 	f->t = f->t->next;
 	f->i = 0;
 	return flogetc(f);
@@ -170,28 +170,26 @@ splitline(Line *t, int cutpoint)
 	tmp->next = t->next;
 	t->next = tmp;
 
-	tmp->dle = t->dle;
 	SUFFIX(tmp->text, T(t->text)+cutpoint, S(t->text)-cutpoint);
+	EXPAND(tmp->text) = 0;
+	S(tmp->text)--;
+	
 	S(t->text) = cutpoint;
     }
 }
 
 #define UNCHECK(l) ((l)->flags &= ~CHECKED)
 
-#ifdef WITH_FENCED_CODE
-# define UNLESS_FENCED(t) if (fenced) { \
+#define UNLESS_FENCED(t) if (fenced) { \
     other = 1; l->count += (c == ' ' ? 0 : -1); \
   } else { t; }
-#else
-# define UNLESS_FENCED(t) t;
-#endif
 
 /*
  * walk a line, seeing if it's any of half a dozen interesting regular
  * types.
  */
 static void
-checkline(Line *l)
+checkline(Line *l, mkd_flag_t flags)
 {
     int eol, i;
     int dashes = 0, spaces = 0,
@@ -210,23 +208,30 @@ checkline(Line *l)
 
     for (i=l->dle; i<eol; i++) {
 	register int c = T(l->text)[i];
+	int is_fence_char = 0;
 
 	if ( c != ' ' ) l->count++;
 
 	switch (c) {
 	case '-':  UNLESS_FENCED(dashes = 1); break;
 	case ' ':  UNLESS_FENCED(spaces = 1); break;
-	case '=':  equals = 1; break;
+	case '=':  UNLESS_FENCED(equals = 1); break;
 	case '_':  UNLESS_FENCED(underscores = 1); break;
 	case '*':  stars = 1; break;
-#if WITH_FENCED_CODE
-	case '~':  if (other) return; fenced = 1; tildes = 1; break;
-	case '`':  if (other) return; fenced = 1; backticks = 1; break;
-#endif
 	default:
-    other = 1;
-    l->count--;
-    if (!fenced) return;
+	    if ( is_flag_set(flags, MKD_FENCEDCODE) ) {
+		switch (c) {
+		case '~':  if (other) return; is_fence_char = 1; tildes = 1; break;
+		case '`':  if (other) return; is_fence_char = 1; backticks = 1; break;
+		}
+		if (is_fence_char) {
+		    fenced = 1;
+		    break;
+		}
+	    }
+	    other = 1;
+	    l->count--;
+	    if (!fenced) return;
 	}
     }
 
@@ -242,28 +247,32 @@ checkline(Line *l)
     if ( stars || underscores ) { l->kind = chk_hr; }
     else if ( dashes ) { l->kind = chk_dash; }
     else if ( equals ) { l->kind = chk_equal; }
-#if WITH_FENCED_CODE
     else if ( tildes ) { l->kind = chk_tilde; }
     else if ( backticks ) { l->kind = chk_backtick; }
-#endif
 }
 
 
 
+/* markdown only does special handling of comments if the comment end
+ * is at the end of a line
+ */
 static Line *
 commentblock(Paragraph *p, int *unclosed)
 {
     Line *t, *ret;
     char *end;
 
-    for ( t = p->text; t ; t = t->next) {
-	if ( end = strstr(T(t->text), "-->") ) {
-	    splitline(t, 3 + (end - T(t->text)) );
-	    ret = t->next;
-	    t->next = 0;
-	    return ret;
-	}
+       for ( t = p->text; t ; t = t->next) {
+	   if ( end = strstr(T(t->text), "-->") ) {
+	       if ( nextnonblank(t, 3 + (end - T(t->text))) < S(t->text) )
+		   continue;
+	       /*splitline(t, 3 + (end - T(t->text)) );*/
+	       ret = t->next;
+	       t->next = 0;
+	       return ret;
+	   }
     }
+
     *unclosed = 1;
     return t;
 
@@ -307,8 +316,8 @@ htmlblock(Paragraph *p, struct kw *tag, int *unclosed)
 	    else { 
 		if ( closing = (c == '/') ) c = flogetc(&f);
 
-		for ( i=0; i < tag->size; c=flogetc(&f) ) {
-		    if ( tag->id[i++] != toupper(c) )
+		for ( i=0; i < tag->size; i++, c=flogetc(&f) ) {
+		    if ( tag->id[i] != toupper(c) )
 			break;
 		}
 
@@ -372,10 +381,10 @@ iscode(Line *t)
 
 
 static inline int
-ishr(Line *t)
+ishr(Line *t, mkd_flag_t flags)
 {
     if ( ! (t->flags & CHECKED) )
-	checkline(t);
+	checkline(t, flags);
 
     if ( t->count > 2 )
 	return t->kind == chk_hr || t->kind == chk_dash || t->kind == chk_equal;
@@ -384,7 +393,7 @@ ishr(Line *t)
 
 
 static int
-issetext(Line *t, int *htyp)
+issetext(Line *t, int *htyp, mkd_flag_t flags)
 {
     Line *n;
     
@@ -394,7 +403,7 @@ issetext(Line *t, int *htyp)
 
     if ( (n = t->next) ) {
 	if ( !(n->flags & CHECKED) )
-	    checkline(n);
+	    checkline(n, flags);
 
 	if ( n->kind == chk_dash || n->kind == chk_equal ) {
 	    *htyp = SETEXT;
@@ -406,7 +415,7 @@ issetext(Line *t, int *htyp)
 
 
 static int
-ishdr(Line *t, int *htyp)
+ishdr(Line *t, int *htyp, mkd_flag_t flags)
 {
     /* ANY leading `#`'s make this into an ETX header
      */
@@ -417,27 +426,28 @@ ishdr(Line *t, int *htyp)
 
     /* And if not, maybe it's a SETEXT header instead
      */
-    return issetext(t, htyp);
+    return issetext(t, htyp, flags);
 }
 
 
 static inline int
-end_of_block(Line *t)
+end_of_block(Line *t, mkd_flag_t flags)
 {
     int dummy;
     
     if ( !t )
 	return 0;
 	
-    return ( (S(t->text) <= t->dle) || ishr(t) || ishdr(t, &dummy) );
+    return ( (S(t->text) <= t->dle) || ishr(t, flags) || ishdr(t, &dummy, flags) );
 }
 
 
 static Line*
-is_discount_dt(Line *t, int *clip)
+is_discount_dt(Line *t, int *clip, mkd_flag_t flags)
 {
-#if USE_DISCOUNT_DL
-    if ( t && t->next
+    if ( !is_flag_set(flags, MKD_NODLDISCOUNT)
+	   && t
+	   && t->next
 	   && (S(t->text) > 2)
 	   && (t->dle == 0)
 	   && (T(t->text)[0] == '=')
@@ -447,9 +457,8 @@ is_discount_dt(Line *t, int *clip)
 	    return t;
 	}
 	else
-	    return is_discount_dt(t->next, clip);
+	    return is_discount_dt(t->next, clip, flags);
     }
-#endif
     return 0;
 }
 
@@ -463,15 +472,15 @@ is_extra_dd(Line *t)
 
 
 static Line*
-is_extra_dt(Line *t, int *clip)
+is_extra_dt(Line *t, int *clip, mkd_flag_t flags)
 {
-#if USE_EXTRA_DL
-    
-    if ( t && t->next && S(t->text) && T(t->text)[0] != '='
+    if ( is_flag_set(flags, MKD_DLEXTRA)
+	   && t
+	   && t->next && S(t->text) && T(t->text)[0] != '='
 		      && T(t->text)[S(t->text)-1] != '=') {
 	Line *x;
     
-	if ( iscode(t) || end_of_block(t) )
+	if ( iscode(t) || end_of_block(t, flags) )
 	    return 0;
 
 	if ( (x = skipempty(t->next)) && is_extra_dd(x) ) {
@@ -479,52 +488,52 @@ is_extra_dt(Line *t, int *clip)
 	    return t;
 	}
 	
-	if ( x=is_extra_dt(t->next, clip) )
+	if ( x=is_extra_dt(t->next, clip, flags) )
 	    return x;
     }
-#endif
     return 0;
 }
 
 
 static Line*
-isdefinition(Line *t, int *clip, int *kind)
+isdefinition(Line *t, int *clip, int *kind, mkd_flag_t flags)
 {
     Line *ret;
 
     *kind = 1;
-    if ( ret = is_discount_dt(t,clip) )
+    if ( ret = is_discount_dt(t,clip,flags) )
 	return ret;
 
     *kind=2;
-    return is_extra_dt(t,clip);
+    return is_extra_dt(t,clip,flags);
 }
 
 
 static int
-islist(Line *t, int *clip, DWORD flags, int *list_type)
+islist(Line *t, int *clip, mkd_flag_t flags, int *list_type)
 {
     int i, j;
     char *q;
     
-    if ( end_of_block(t) )
+    if ( end_of_block(t, flags) )
 	return 0;
 
-    if ( !(flags & (MKD_NODLIST|MKD_STRICT)) && isdefinition(t,clip,list_type) )
+    if ( !(is_flag_set(flags, MKD_NODLIST) || is_flag_set(flags, MKD_STRICT))
+				      && isdefinition(t,clip,list_type,flags) )
 	return DL;
 
     if ( strchr("*-+", T(t->text)[t->dle]) && isspace(T(t->text)[t->dle+1]) ) {
 	i = nextnonblank(t, t->dle+1);
 	*clip = (i > 4) ? 4 : i;
 	*list_type = UL;
-	return AL;
+	return is_flag_set(flags, MKD_EXPLICITLIST) ? UL : AL;
     }
 
     if ( (j = nextblank(t,t->dle)) > t->dle ) {
 	if ( T(t->text)[j-1] == '.' ) {
 
-	    if ( !(flags & (MKD_NOALPHALIST|MKD_STRICT))
-				    && (j == t->dle + 2)
+	    if ( !(is_flag_set(flags, MKD_NOALPHALIST) || is_flag_set(flags, MKD_STRICT))
+			  && (j == t->dle + 2)
 			  && isalpha(T(t->text)[t->dle]) ) {
 		j = nextnonblank(t,j);
 		*clip = (j > 4) ? 4 : j;
@@ -602,8 +611,7 @@ codeblock(Paragraph *p)
     Line *t = p->text, *r;
 
     for ( ; t; t = r ) {
-	CLIP(t->text,0,4);
-	t->dle = mkd_firstnonblank(t);
+	__mkd_trim_line(t,4);
 
 	if ( !( (r = skipempty(t->next)) && iscode(r)) ) {
 	    ___mkd_freeLineRange(t,r);
@@ -615,12 +623,14 @@ codeblock(Paragraph *p)
 }
 
 
-#ifdef WITH_FENCED_CODE
 static int
-iscodefence(Line *r, int size, line_type kind)
+iscodefence(Line *r, int size, line_type kind, mkd_flag_t flags)
 {
+    if ( !is_flag_set(flags, MKD_FENCEDCODE) )
+	return 0;
+
     if ( !(r->flags & CHECKED) )
-	checkline(r);
+	checkline(r, flags);
 
     if ( kind )
 	return (r->kind == kind) && (r->count >= size);
@@ -628,42 +638,42 @@ iscodefence(Line *r, int size, line_type kind)
 	return (r->kind == chk_tilde || r->kind == chk_backtick) && (r->count >= size);
 }
 
+
 static Paragraph *
-fencedcodeblock(ParagraphRoot *d, Line **ptr)
+fencedcodeblock(ParagraphRoot *d, Line **ptr, mkd_flag_t flags)
 {
     Line *first, *r;
     Paragraph *ret;
 
     first = (*ptr);
-    
+
     /* don't allow zero-length code fences
-     */
-    if ( (first->next == 0) || iscodefence(first->next, first->count, 0) )
+    */
+    if ( (first->next == 0) || iscodefence(first->next, first->count, 0, flags) )
 	return 0;
 
     /* find the closing fence, discard the fences,
-     * return a Paragraph with the contents
-     */
+    * return a Paragraph with the contents
+    */
     for ( r = first; r && r->next; r = r->next )
-	if ( iscodefence(r->next, first->count, first->kind) ) {
+	if ( iscodefence(r->next, first->count, first->kind, flags) ) {
 	    (*ptr) = r->next->next;
 	    ret = Pp(d, first->next, CODE);
-      if (S(first->text) - first->count > 0) {
-        char *lang_attr = T(first->text) + first->count;
-        while ( *lang_attr != 0 && *lang_attr == ' ' ) lang_attr++;
-        ret->lang = strdup(lang_attr);
-      }
-      else {
-        ret->lang = 0;
-      }
-	    ___mkd_freeLine(first);
-	    ___mkd_freeLine(r->next);
-	    r->next = 0;
-	    return ret;
+	    if (S(first->text) - first->count > 0) {
+		char *lang_attr = T(first->text) + first->count;
+		while ( *lang_attr != 0 && *lang_attr == ' ' ) lang_attr++;
+		ret->lang = strdup(lang_attr);
+	    }
+	    else {
+		ret->lang = 0;
 	}
+	___mkd_freeLine(first);
+	___mkd_freeLine(r->next);
+	r->next = 0;
+	return ret;
+    }
     return 0;
 }
-#endif
 
 
 static int
@@ -685,11 +695,11 @@ centered(Line *first, Line *last)
 
 
 static int
-endoftextblock(Line *t, int toplevelblock, DWORD flags)
+endoftextblock(Line *t, int toplevelblock, mkd_flag_t flags)
 {
     int z;
 
-    if ( end_of_block(t) || isquote(t) )
+    if ( end_of_block(t, flags) || isquote(t) )
 	return 1;
 
     /* HORRIBLE STANDARDS KLUDGES:
@@ -705,7 +715,7 @@ endoftextblock(Line *t, int toplevelblock, DWORD flags)
 
 
 static Line *
-textblock(Paragraph *p, int toplevel, DWORD flags)
+textblock(Paragraph *p, int toplevel, mkd_flag_t flags)
 {
     Line *t, *next;
 
@@ -740,12 +750,12 @@ szmarkerclass(char *p)
 #define iscsschar(c) (isalpha(c) || (c == '-') || (c == '_') )
 
 static int
-isdivmarker(Line *p, int start, DWORD flags)
+isdivmarker(Line *p, int start, mkd_flag_t flags)
 {
     char *s;
     int last, i;
 
-    if ( flags & (MKD_NODIVQUOTE|MKD_STRICT) )
+    if ( is_flag_set(flags, MKD_NODIVQUOTE) || is_flag_set(flags, MKD_STRICT) )
 	return 0;
 
     start = nextnonblank(p, start);
@@ -779,7 +789,7 @@ isdivmarker(Line *p, int start, DWORD flags)
  * way the markdown sample web form at Daring Fireball works.
  */
 static Line *
-quoteblock(Paragraph *p, DWORD flags)
+quoteblock(Paragraph *p, mkd_flag_t flags)
 {
     Line *t, *q;
     int qp;
@@ -796,9 +806,8 @@ quoteblock(Paragraph *p, DWORD flags)
 	    /* clip next space, if any */
 	    if ( T(t->text)[qp] == ' ' )
 		qp++;
-	    CLIP(t->text, 0, qp);
+	    __mkd_trim_line(t,qp);
 	    UNCHECK(t);
-	    t->dle = mkd_firstnonblank(t);
 	}
 
 	q = skipempty(t->next);
@@ -840,16 +849,40 @@ typedef int (*linefn)(Line *);
  * marker, but multiple paragraphs need to start with a 4-space indent.
  */
 static Line *
-listitem(Paragraph *p, int indent, DWORD flags, linefn check)
+listitem(Paragraph *p, int indent, mkd_flag_t flags, linefn check)
 {
     Line *t, *q;
     int clip = indent;
     int z;
+#ifdef GITHUB_CHECKBOX
+    int firstpara = 1;
+    int ischeck;
+#define CHECK_NOT 0
+#define CHECK_NO 1
+#define CHECK_YES 2
+#endif
 
     for ( t = p->text; t ; t = q) {
-	CLIP(t->text, 0, clip);
 	UNCHECK(t);
-	t->dle = mkd_firstnonblank(t);
+	__mkd_trim_line(t, clip);
+
+#ifdef GITHUB_CHECKBOX
+	if ( firstpara ) {
+	    ischeck = CHECK_NOT;
+	    if ( strncmp(T(t->text)+t->dle, "[ ]", 3) == 0 )
+		ischeck = CHECK_NO;
+	    else if ( strncasecmp(T(t->text)+t->dle, "[x]", 3) == 0 )
+		ischeck = CHECK_YES;
+
+	    if ( ischeck != CHECK_NOT ) {
+		__mkd_trim_line(t, 3);
+		p->flags |= GITHUB_CHECK;
+		if ( ischeck == CHECK_YES )
+		    p->flags |= IS_CHECKED;
+	    }
+	    firstpara = 0;
+	}
+#endif
 
         /* even though we had to trim a long leader off this item,
          * the indent for trailing paragraphs is still 4...
@@ -878,9 +911,9 @@ listitem(Paragraph *p, int indent, DWORD flags, linefn check)
 	    indent = clip ? clip : 2;
 	}
 
-	if ( (q->dle < indent) && (ishr(q) || islist(q,&z,flags,&z)
+	if ( (q->dle < indent) && (ishr(q,flags) || islist(q,&z,flags,&z)
 					   || (check && (*check)(q)))
-			       && !issetext(q,&z) ) {
+			       && !issetext(q,&z,flags) ) {
 	    q = t->next;
 	    t->next = 0;
 	    return q;
@@ -902,7 +935,7 @@ definition_block(Paragraph *top, int clip, MMIOT *f, int kind)
 
     while (( labels = q )) {
 
-	if ( (q = isdefinition(labels, &z, &kind)) == 0 )
+	if ( (q = isdefinition(labels, &z, &kind, f->flags)) == 0 )
 	    break;
 
 	if ( (text = skipempty(q->next)) == 0 )
@@ -998,6 +1031,28 @@ tgood(char c)
 }
 
 
+/*
+ * eat lines for a markdown extra footnote
+ */
+static Line *
+extrablock(Line *p)
+{
+    Line *np;
+    
+    while ( p && p->next ) {
+	np = p->next;
+
+	if ( np->dle < 4 && np->dle < S(np->text) ) {
+	    p->next = 0;
+	    return np;
+	}
+	__mkd_trim_line(np,4);
+	p = np;
+    }
+    return 0;
+}
+
+
 /*
  * add a new (image or link) footnote to the footnote table
  */
@@ -1013,20 +1068,31 @@ addfootnote(Line *p, MMIOT* f)
     CREATE(foot->tag);
     CREATE(foot->link);
     CREATE(foot->title);
+    foot->text = 0;
     foot->flags = foot->height = foot->width = 0;
 
+    /* keep the footnote label */
     for (j=i=p->dle+1; T(p->text)[j] != ']'; j++)
 	EXPAND(foot->tag) = T(p->text)[j];
-
     EXPAND(foot->tag) = 0;
     S(foot->tag)--;
+
+    /* consume the closing ]: */
     j = nextnonblank(p, j+2);
 
-    if ( (f->flags & MKD_EXTRA_FOOTNOTE) && (T(foot->tag)[0] == '^') ) {
-	/* need to consume all lines until non-indented block? */
-	while ( j < S(p->text) )
-	    EXPAND(foot->title) = T(p->text)[j++];
-	goto skip_to_end;
+    if ( is_flag_set(f->flags, MKD_EXTRA_FOOTNOTE) && (T(foot->tag)[0] == '^') ) {
+	/* markdown extra footnote: All indented lines past this point;
+	 * the first line includes the footnote reference, so we need to
+	 * snip that out as we go.
+	 */
+	foot->flags |= EXTRA_FOOTNOTE;
+	__mkd_trim_line(p,j);
+
+	np = extrablock(p);
+
+	foot->text = compile(p, 0, f);
+
+	return np;
     }
 
     while ( (j < S(p->text)) && !isspace(T(p->text)[j]) )
@@ -1037,8 +1103,7 @@ addfootnote(Line *p, MMIOT* f)
 
     if ( T(p->text)[j] == '=' ) {
 	sscanf(T(p->text)+j, "=%dx%d", &foot->width, &foot->height);
-	while ( (j < S(p->text)) && !isspace(T(p->text)[j]) )
-	    ++j;
+	j = nextblank(p, j);
 	j = nextnonblank(p,j);
     }
 
@@ -1068,7 +1133,6 @@ addfootnote(Line *p, MMIOT* f)
 	--S(foot->title);
     }
 
-skip_to_end:
     ___mkd_freeLine(p);
     return np;
 }
@@ -1106,6 +1170,22 @@ consume(Line *ptr, int *eaten)
 }
 
 
+typedef ANCHOR(Line) Cache;
+
+static void
+uncache(Cache *cache, ParagraphRoot *d, MMIOT *f)
+{
+    Paragraph *p;
+
+    if ( T(*cache) ) {
+	E(*cache)->next = 0;
+	p = Pp(d, 0, SOURCE);
+	p->down = compile(T(*cache), 1, f);
+	T(*cache) = E(*cache) = 0;
+    }
+}
+
+
 /*
  * top-level compilation; break the document into
  * style, html, and source blocks with footnote links
@@ -1115,25 +1195,21 @@ static Paragraph *
 compile_document(Line *ptr, MMIOT *f)
 {
     ParagraphRoot d = { 0, 0 };
-    ANCHOR(Line) source = { 0, 0 };
+    Cache source = { 0, 0 };
     Paragraph *p = 0;
     struct kw *tag;
     int eaten, unclosed;
+    int previous_was_break = 1;
 
     while ( ptr ) {
-	if ( !(f->flags & MKD_NOHTML) && (tag = isopentag(ptr)) ) {
+	if ( !is_flag_set(f->flags, MKD_NOHTML) && (tag = isopentag(ptr)) ) {
 	    int blocktype;
 	    /* If we encounter a html/style block, compile and save all
 	     * of the cached source BEFORE processing the html/style.
 	     */
-	    if ( T(source) ) {
-		E(source)->next = 0;
-		p = Pp(&d, 0, SOURCE);
-		p->down = compile(T(source), 1, f);
-		T(source) = E(source) = 0;
-	    }
+	    uncache(&source, &d, f);
 	    
-	    if ( f->flags & MKD_NOSTYLE )
+	    if (is_flag_set(f->flags, MKD_NOSTYLE) )
 		blocktype = HTML;
 	    else
 		blocktype = strcmp(tag->id, "STYLE") == 0 ? STYLE : HTML;
@@ -1144,6 +1220,7 @@ compile_document(Line *ptr, MMIOT *f)
 		p->down = compile(p->text, 1, f);
 		p->text = 0;
 	    }
+	    previous_was_break = 1;
 	}
 	else if ( isfootnote(ptr) ) {
 	    /* footnotes, like cats, sleep anywhere; pull them
@@ -1151,23 +1228,27 @@ compile_document(Line *ptr, MMIOT *f)
 	     * later processing
 	     */
 	    ptr = consume(addfootnote(ptr, f), &eaten);
+	    previous_was_break = 1;
+	}
+	else if ( previous_was_break && iscodefence(ptr,3,0,f->flags)) {
+	    uncache(&source, &d, f);
+	    if ( !fencedcodeblock(&d, &ptr, f->flags) ) /* just source */
+		goto attach;
 	}
 	else {
+    attach:
 	    /* source; cache it up to wait for eof or the
 	     * next html/style block
 	     */
 	    ATTACH(source,ptr);
+	    previous_was_break = blankline(ptr);
 	    ptr = ptr->next;
 	}
     }
-    if ( T(source) ) {
-	/* if there's any cached source at EOF, compile
-	 * it now.
-	 */
-	E(source)->next = 0;
-	p = Pp(&d, 0, SOURCE);
-	p->down = compile(T(source), 1, f);
-    }
+    /* if there's any cached source at EOF, compile
+     * it now.
+     */
+    uncache(&source, &d, f);
     return T(d);
 }
 
@@ -1187,7 +1268,7 @@ actually_a_table(MMIOT *f, Line *pp)
     int c;
 
     /* tables need to be turned on */
-    if ( f->flags & (MKD_STRICT|MKD_NOTABLES) )
+    if ( is_flag_set(f->flags, MKD_STRICT) || is_flag_set(f->flags, MKD_NOTABLES) )
 	return 0;
 
     /* tables need three lines */
@@ -1242,10 +1323,11 @@ compile(Line *ptr, int toplevel, MMIOT *f)
     ptr = consume(ptr, &para);
 
     while ( ptr ) {
+
 	if ( iscode(ptr) ) {
 	    p = Pp(&d, ptr, CODE);
 	    
-	    if ( f->flags & MKD_1_COMPAT) {
+	    if ( is_flag_set(f->flags, MKD_1_COMPAT) ) {
 		/* HORRIBLE STANDARDS KLUDGE: the first line of every block
 		 * has trailing whitespace trimmed off.
 		 */
@@ -1254,11 +1336,9 @@ compile(Line *ptr, int toplevel, MMIOT *f)
 	    
 	    ptr = codeblock(p);
 	}
-#if WITH_FENCED_CODE
-	else if ( iscodefence(ptr,3,0) && (p=fencedcodeblock(&d, &ptr)) )
+	else if ( iscodefence(ptr,3,0,f->flags) && (p=fencedcodeblock(&d, &ptr, f->flags)) )
 	    /* yay, it's already done */ ;
-#endif
-	else if ( ishr(ptr) ) {
+	else if ( ishr(ptr, f->flags) ) {
 	    p = Pp(&d, 0, HR);
 	    r = ptr;
 	    ptr = ptr->next;
@@ -1280,18 +1360,44 @@ compile(Line *ptr, int toplevel, MMIOT *f)
 	    p->down = compile(p->text, 1, f);
 	    p->text = 0;
 	}
-	else if ( ishdr(ptr, &hdr_type) ) {
+	else if ( ishdr(ptr, &hdr_type, f->flags) ) {
 	    p = Pp(&d, ptr, HDR);
 	    ptr = headerblock(p, hdr_type);
 	}
 	else {
-	    p = Pp(&d, ptr, MARKUP);
-	    ptr = textblock(p, toplevel, f->flags);
-	    /* tables are a special kind of paragraph */
-	    if ( actually_a_table(f, p->text) )
-		p->typ = TABLE;
+	    /* either markup or an html block element
+	     */
+	    struct kw *tag;
+	    int unclosed = 1;
+
+	    p = Pp(&d, ptr, MARKUP);	/* default to regular markup,
+					 * then check if it's an html
+					 * block.   If it IS an html
+					 * block, htmlblock() will
+					 * populate this paragraph &
+					 * all we need to do is reset
+					 * the paragraph type to HTML,
+					 * otherwise the paragraph
+					 * remains empty and ready for
+					 * processing with textblock()
+					 */
+	    
+	    if ( !is_flag_set(f->flags, MKD_NOHTML) && (tag = isopentag(ptr)) ) {
+		/* possibly an html block
+		 */
+		
+		ptr = htmlblock(p, tag, &unclosed);
+		if ( ! unclosed ) {
+		    p->typ = HTML;
+		}
+	    }
+	    if ( unclosed ) {
+		ptr = textblock(p, toplevel, f->flags);
+		/* tables are a special kind of paragraph */
+		if ( actually_a_table(f, p->text) )
+		    p->typ = TABLE;
+	    }
 	}
-
 	if ( (para||toplevel) && !p->align )
 	    p->align = PARA;
 
@@ -1316,19 +1422,30 @@ compile(Line *ptr, int toplevel, MMIOT *f)
  * prepare and compile `text`, returning a Paragraph tree.
  */
 int
-mkd_compile(Document *doc, DWORD flags)
+mkd_compile(Document *doc, mkd_flag_t flags)
 {
     if ( !doc )
 	return 0;
 
-    if ( doc->compiled )
-	return 1;
+    flags &= USER_FLAGS;
+    
+    if ( doc->compiled ) {
+	if ( doc->ctx->flags == flags && !doc->dirty)
+	    return 1;
+	else {
+	    doc->compiled = doc->dirty = 0;
+	    if ( doc->code)
+		___mkd_freeParagraph(doc->code);
+	    if ( doc->ctx->footnotes )
+		___mkd_freefootnotes(doc->ctx);
+	}
+    }
 
     doc->compiled = 1;
     memset(doc->ctx, 0, sizeof(MMIOT) );
     doc->ctx->ref_prefix= doc->ref_prefix;
     doc->ctx->cb        = &(doc->cb);
-    doc->ctx->flags     = flags & USER_FLAGS;
+    doc->ctx->flags     = flags;
     CREATE(doc->ctx->in);
     doc->ctx->footnotes = malloc(sizeof doc->ctx->footnotes[0]);
     doc->ctx->footnotes->reference = 0;
diff --git a/ext/markdown.h b/ext/markdown.h
index 8edd302..5cb897f 100644
--- a/ext/markdown.h
+++ b/ext/markdown.h
@@ -1,22 +1,22 @@
 #ifndef _MARKDOWN_D
 #define _MARKDOWN_D
 
+#include "config.h"
 #include "cstring.h"
 
-/* reference-style links (and images) are stored in an array
- * of footnotes.
+#ifdef HAVE_INTTYPES_H
+#   include <inttypes.h>
+#elif HAVE_STDINT_H
+#   include <stdint.h>
+#endif
+
+/* flags, captured into a named type
  */
-typedef struct footnote {
-    Cstring tag;		/* the tag for the reference link */
-    Cstring link;		/* what this footnote points to */
-    Cstring title;		/* what it's called (TITLE= attribute) */
-    int height, width;		/* dimensions (for image link) */
-    int dealloc;		/* deallocation needed? */
-    int refnumber;
-    int flags;
-#define EXTRA_BOOKMARK	0x01
-#define REFERENCED	0x02
-} Footnote;
+typedef DWORD mkd_flag_t;
+
+#define is_flag_set(flags, item)	((flags) & (item))
+#define set_flag(flags, item)		((flags) |= (item))
+#define clear_flag(flags, item)		((flags) &= ~(item))
 
 /* each input line is read into a Line, which contains the line,
  * the offset of the first non-space character [this assumes 
@@ -49,16 +49,38 @@ typedef struct paragraph {
     struct paragraph *down;	/* recompiled contents of this paragraph */
     struct line *text;		/* all the text in this paragraph */
     char *ident;		/* %id% tag for QUOTE */
-    char *lang;         /* lang attribute for CODE */
+    char *lang;			/* lang attribute for CODE */
     enum { WHITESPACE=0, CODE, QUOTE, MARKUP,
 	   HTML, STYLE, DL, UL, OL, AL, LISTITEM,
 	   HDR, HR, TABLE, SOURCE } typ;
     enum { IMPLICIT=0, PARA, CENTER} align;
     int hnumber;		/* <Hn> for typ == HDR */
+#if GITHUB_CHECKBOX
+    int flags;
+#define GITHUB_CHECK		0x01
+#define IS_CHECKED		0x02
+#endif
 } Paragraph;
 
 enum { ETX, SETEXT };	/* header types */
 
+/* reference-style links (and images) are stored in an array
+ * of footnotes.
+ */
+typedef struct footnote {
+    Cstring tag;		/* the tag for the reference link */
+    Cstring link;		/* what this footnote points to */
+    Cstring title;		/* what it's called (TITLE= attribute) */
+    Paragraph *text;		/* EXTRA_FOOTNOTE content */
+    
+    int height, width;		/* dimensions (for image link) */
+    int dealloc;		/* deallocation needed? */
+    int refnumber;
+    int flags;
+#define EXTRA_FOOTNOTE	0x01
+#define REFERENCED	0x02
+} Footnote;
+
 
 typedef struct block {
     enum { bTEXT, bSTAR, bUNDER } b_type;
@@ -78,7 +100,9 @@ typedef struct callback_data {
     void *e_data;		/* private data for callbacks */
     mkd_callback_t e_url;	/* url edit callback */
     mkd_callback_t e_flags;	/* extra href flags callback */
+    mkd_callback_t e_anchor;	/* callback for anchor types */
     mkd_free_t e_free;		/* edit/flags callback memory deallocator */
+    mkd_callback_t e_codefmt;	/* codeblock formatter (for highlighting) */
 } Callback_data;
 
 
@@ -101,11 +125,12 @@ typedef struct mmiot {
     Cstring out;
     Cstring in;
     Qblock Q;
+    char last;	/* last text character added to out */
     int isp;
     struct escaped *esc;
     char *ref_prefix;
     struct footnote_list *footnotes;
-    DWORD flags;
+    mkd_flag_t flags;
 #define MKD_NOLINKS	0x00000001
 #define MKD_NOIMAGE	0x00000002
 #define MKD_NOPANTS	0x00000004
@@ -129,15 +154,23 @@ typedef struct mmiot {
 #define MKD_NODLIST	0x00100000
 #define MKD_EXTRA_FOOTNOTE 0x00200000
 #define MKD_NOSTYLE	0x00400000
-#define IS_LABEL	0x08000000
-#define USER_FLAGS	0x0FFFFFFF
+#define MKD_NODLDISCOUNT 0x00800000
+#define	MKD_DLEXTRA	0x01000000
+#define MKD_FENCEDCODE	0x02000000
+#define MKD_IDANCHOR	0x04000000
+#define MKD_GITHUBTAGS	0x08000000
+#define MKD_URLENCODEDANCHOR 0x10000000
+#define IS_LABEL	0x20000000
+#define MKD_LATEX	0x40000000
+#define MKD_EXPLICITLIST	0x80000000
+#define USER_FLAGS	0xFFFFFFFF
 #define INPUT_MASK	(MKD_NOHEADER|MKD_TABSTOP)
 
     Callback_data *cb;
 } MMIOT;
 
 
-#define MKD_EOLN	3
+#define MKD_EOLN	'\r'
 
 
 /*
@@ -156,6 +189,7 @@ typedef struct document {
     ANCHOR(Line) content;	/* uncompiled text, not valid after compile() */
     Paragraph *code;		/* intermediate code generated by compile() */
     int compiled;		/* set after mkd_compile() */
+    int dirty;			/* flags or callbacks changed */
     int html;			/* set after (internal) htmlify() */
     int tabstop;		/* for properly expanding tabs (ick) */
     char *ref_prefix;
@@ -175,7 +209,7 @@ struct string_stream {
 
 
 extern int  mkd_firstnonblank(Line *);
-extern int  mkd_compile(Document *, DWORD);
+extern int  mkd_compile(Document *, mkd_flag_t);
 extern int  mkd_document(Document *, char **);
 extern int  mkd_generatehtml(Document *, FILE *);
 extern int  mkd_css(Document *, char **);
@@ -184,19 +218,19 @@ extern int  mkd_generatecss(Document *, FILE *);
 extern int  mkd_xml(char *, int , char **);
 extern int  mkd_generatexml(char *, int, FILE *);
 extern void mkd_cleanup(Document *);
-extern int  mkd_line(char *, int, char **, DWORD);
-extern int  mkd_generateline(char *, int, FILE*, DWORD);
+extern int  mkd_line(char *, int, char **, mkd_flag_t);
+extern int  mkd_generateline(char *, int, FILE*, mkd_flag_t);
 #define mkd_text mkd_generateline
 extern void mkd_basename(Document*, char *);
 
 typedef int (*mkd_sta_function_t)(const int,const void*);
-extern void mkd_string_to_anchor(char*,int, mkd_sta_function_t, void*, int);
+extern void mkd_string_to_anchor(char*,int, mkd_sta_function_t, void*, int, MMIOT *);
 
-extern Document *mkd_in(FILE *, DWORD);
-extern Document *mkd_string(const char*,int, DWORD);
+extern Document *mkd_in(FILE *, mkd_flag_t);
+extern Document *mkd_string(const char*, int, mkd_flag_t);
 
-extern Document *gfm_in(FILE *, DWORD);
-extern Document *gfm_string(const char*,int, DWORD);
+extern Document *gfm_in(FILE *, mkd_flag_t);
+extern Document *gfm_string(const char*,int, mkd_flag_t);
 
 extern void mkd_initialize();
 extern void mkd_shlib_destructor();
@@ -214,14 +248,19 @@ extern void ___mkd_initmmiot(MMIOT *, void *);
 extern void ___mkd_freemmiot(MMIOT *, void *);
 extern void ___mkd_freeLineRange(Line *, Line *);
 extern void ___mkd_xml(char *, int, FILE *);
-extern void ___mkd_reparse(char *, int, int, MMIOT*, char*);
+extern void ___mkd_reparse(char *, int, mkd_flag_t, MMIOT*, char*);
 extern void ___mkd_emblock(MMIOT*);
 extern void ___mkd_tidy(Cstring *);
 
 extern Document *__mkd_new_Document();
 extern void __mkd_enqueue(Document*, Cstring *);
-extern void __mkd_header_dle(Line *);
+extern void __mkd_trim_line(Line *, int);
 
 extern int  __mkd_io_strget(struct string_stream *);
 
+/* utility function to do some operation and exit the current function
+ * if it fails
+ */
+#define DO_OR_DIE(op) if ( (op) == EOF ) return EOF; else 1
+
 #endif/*_MARKDOWN_D*/
diff --git a/ext/mkdio.c b/ext/mkdio.c
index fd6105d..fa44c1d 100644
--- a/ext/mkdio.c
+++ b/ext/mkdio.c
@@ -16,6 +16,7 @@
 
 typedef ANCHOR(Line) LineAnchor;
 
+
 /* create a new blank Document
  */
 Document*
@@ -73,13 +74,19 @@ __mkd_enqueue(Document* a, Cstring *line)
 }
 
 
-/* trim leading blanks from a header line
+/* trim leading characters from a line, then adjust the dle.
  */
 void
-__mkd_header_dle(Line *p)
+__mkd_trim_line(Line *p, int clip)
 {
-    CLIP(p->text, 0, 1);
-    p->dle = mkd_firstnonblank(p);
+    if ( clip >= S(p->text) ) {
+	S(p->text) = p->dle = 0;
+	T(p->text)[0] = 0;
+    }
+    else if ( clip > 0 ) {
+	CLIP(p->text, 0, clip);
+	p->dle = mkd_firstnonblank(p);
+    }
 }
 
 
@@ -88,7 +95,7 @@ __mkd_header_dle(Line *p)
 typedef int (*getc_func)(void*);
 
 Document *
-populate(getc_func getc, void* ctx, int flags)
+populate(getc_func getc, void* ctx, mkd_flag_t flags)
 {
     Cstring line;
     Document *a = __mkd_new_Document();
@@ -97,7 +104,7 @@ populate(getc_func getc, void* ctx, int flags)
 
     if ( !a ) return 0;
 
-    a->tabstop = (flags & MKD_TABSTOP) ? 4 : TABSTOP;
+    a->tabstop = is_flag_set(flags, MKD_TABSTOP) ? 4 : TABSTOP;
 
     CREATE(line);
 
@@ -121,16 +128,16 @@ populate(getc_func getc, void* ctx, int flags)
 
     DELETE(line);
 
-    if ( (pandoc == 3) && !(flags & (MKD_NOHEADER|MKD_STRICT)) ) {
+    if ( (pandoc == 3) && !(is_flag_set(flags, MKD_NOHEADER) || is_flag_set(flags, MKD_STRICT)) ) {
 	/* the first three lines started with %, so we have a header.
 	 * clip the first three lines out of content and hang them
 	 * off header.
 	 */
 	Line *headers = T(a->content);
 
-	a->title = headers;             __mkd_header_dle(a->title);
-	a->author= headers->next;       __mkd_header_dle(a->author);
-	a->date  = headers->next->next; __mkd_header_dle(a->date);
+	a->title = headers;             __mkd_trim_line(a->title, 1);
+	a->author= headers->next;       __mkd_trim_line(a->author, 1);
+	a->date  = headers->next->next; __mkd_trim_line(a->date, 1);
 
 	T(a->content) = headers->next->next->next;
     }
@@ -142,7 +149,7 @@ populate(getc_func getc, void* ctx, int flags)
 /* convert a file into a linked list
  */
 Document *
-mkd_in(FILE *f, DWORD flags)
+mkd_in(FILE *f, mkd_flag_t flags)
 {
     return populate((getc_func)fgetc, f, flags & INPUT_MASK);
 }
@@ -164,7 +171,7 @@ __mkd_io_strget(struct string_stream *in)
 /* convert a block of text into a linked list
  */
 Document *
-mkd_string(const char *buf, int len, DWORD flags)
+mkd_string(const char *buf, int len, mkd_flag_t flags)
 {
     struct string_stream about;
 
@@ -183,22 +190,20 @@ mkd_generatehtml(Document *p, FILE *output)
     char *doc;
     int szdoc;
 
-    if ( (szdoc = mkd_document(p, &doc)) != EOF ) {
-	if ( p->ctx->flags & MKD_CDATA )
-	    mkd_generatexml(doc, szdoc, output);
-	else
-	    fwrite(doc, szdoc, 1, output);
-	putc('\n', output);
-	return 0;
-    }
-    return -1;
+    DO_OR_DIE( szdoc = mkd_document(p,&doc) );
+    if ( is_flag_set(p->ctx->flags, MKD_CDATA) )
+	DO_OR_DIE( mkd_generatexml(doc, szdoc, output) );
+    else if ( fwrite(doc, szdoc, 1, output) != 1 )
+	return EOF;
+    DO_OR_DIE( putc('\n', output) );
+    return 0;
 }
 
 
 /* convert some markdown text to html
  */
 int
-markdown(Document *document, FILE *out, int flags)
+markdown(Document *document, FILE *out, mkd_flag_t flags)
 {
     if ( mkd_compile(document, flags) ) {
 	mkd_generatehtml(document, out);
@@ -209,55 +214,103 @@ markdown(Document *document, FILE *out, int flags)
 }
 
 
-/* write out a Cstring, mangled into a form suitable for `<a href=` or `<a id=`
+/* anchor_format a string, returning the formatted string in malloc()ed space
+ * MKD_URLENCODEDANCHOR is now perverted to being a html5 anchor
+ *
+ * !labelformat:  print all characters
+ * labelformat && h4anchor: prefix nonalpha label with L,
+ *                          expand all nonalnum, _, ':', '.' to hex
+ *                          except space which maps to -
+ * labelformat && !h4anchor:expand space to -, other isspace() & '%' to hex
  */
-void
-mkd_string_to_anchor(char *s, int len, mkd_sta_function_t outchar,
-				       void *out, int labelformat)
+static char *
+mkd_anchor_format(char *s, int len, int labelformat, mkd_flag_t flags)
 {
-#if WITH_URLENCODED_ANCHOR
-    static const unsigned char hexchars[] = "0123456789abcdef";
-#endif
+    char *res;
     unsigned char c;
+    int i, needed, out = 0;
+    int h4anchor = !is_flag_set(flags, MKD_URLENCODEDANCHOR);
+    static const unsigned char hexchars[] = "0123456789abcdef";
 
-    int i, size;
-    char *line;
+    needed = (labelformat ? (4*len) : len) + 2 /* 'L', trailing null */;
 
-    size = mkd_line(s, len, &line, IS_LABEL);
+    if ( (res = malloc(needed)) == NULL )
+	return NULL;
+
+    if ( h4anchor && labelformat && !isalpha(s[0]) )
+	res[out++] = 'L';
+	
     
-#if !WITH_URLENCODED_ANCHOR
-    if ( labelformat && (size>0) && !isalpha(line[0]) )
-	(*outchar)('L',out);
-#endif
-    for ( i=0; i < size ; i++ ) {
-	c = line[i];
+    for ( i=0; i < len ; i++ ) {
+	c = s[i];
 	if ( labelformat ) {
-	    if ( isalnum(c) || (c == '_') || (c == ':') || (c == '-') || (c == '.' ) )
-		(*outchar)(c, out);
-	    else
-#if WITH_URLENCODED_ANCHOR
-	    {
-		(*outchar)('%', out);
-		(*outchar)(hexchars[c >> 4 & 0xf], out);
-		(*outchar)(hexchars[c      & 0xf], out);
+	    if ( h4anchor
+		    ? (isalnum(c) || (c == '_') || (c == ':') || (c == '.' ) )
+		    : !(isspace(c) || c == '%') )
+		res[out++] = c;
+	    else if ( c == ' ' )
+		res[out++] = '-';
+	    else {
+		    res[out++] = h4anchor ? '-' : '%';
+		    res[out++] = hexchars[c >> 4 & 0xf];
+		    res[out++] = hexchars[c      & 0xf];
+		    if ( h4anchor )
+			res[out++] = '-';
 	    }
-#else
-		(*outchar)('.', out);
-#endif
 	}
 	else
-	    (*outchar)(c,out);
+	    res[out++] = c;
     }
-	
-    if (line)
-	free(line);
+    
+    res[out++] = 0;
+    return res;
+} /* mkd_anchor_format */
+
+
+/* write out a Cstring, mangled into a form suitable for `<a href=` or `<a id=`
+ */
+void
+mkd_string_to_anchor(char *s, int len, mkd_sta_function_t outchar,
+				       void *out, int labelformat,
+				       MMIOT *f)
+{
+    char *res;
+    char *line;
+    int size;
+
+    int i;
+
+    size = mkd_line(s, len, &line, IS_LABEL);
+
+    if ( !line )
+	return;
+
+    if ( f->cb->e_anchor )
+	res = (*(f->cb->e_anchor))(line, size, f->cb->e_data);
+    else
+	res = mkd_anchor_format(line, size, labelformat, f->flags);
+
+    free(line);
+
+    if ( !res )
+	return;
+
+    for ( i=0; res[i]; i++ )
+	(*outchar)(res[i], out);
+
+    if ( f->cb->e_anchor ) {
+	if ( f->cb->e_free )
+	    (*(f->cb->e_free))(res, f->cb->e_data);
+    }
+    else 
+	free(res);
 }
 
 
 /*  ___mkd_reparse() a line
  */
 static void
-mkd_parse_line(char *bfr, int size, MMIOT *f, int flags)
+mkd_parse_line(char *bfr, int size, MMIOT *f, mkd_flag_t flags)
 {
     ___mkd_initmmiot(f, 0);
     f->flags = flags & USER_FLAGS;
@@ -269,7 +322,7 @@ mkd_parse_line(char *bfr, int size, MMIOT *f, int flags)
 /* ___mkd_reparse() a line, returning it in malloc()ed memory
  */
 int
-mkd_line(char *bfr, int size, char **res, DWORD flags)
+mkd_line(char *bfr, int size, char **res, mkd_flag_t flags)
 {
     MMIOT f;
     int len;
@@ -277,15 +330,14 @@ mkd_line(char *bfr, int size, char **res, DWORD flags)
     mkd_parse_line(bfr, size, &f, flags);
 
     if ( len = S(f.out) ) {
-	/* kludge alert;  we know that T(f.out) is malloced memory,
-	 * so we can just steal it away.   This is awful -- there
-	 * should be an opaque method that transparently moves 
-	 * the pointer out of the embedded Cstring.
-	 */
 	EXPAND(f.out) = 0;
-	*res = T(f.out);
-	T(f.out) = 0;
-	S(f.out) = ALLOCATED(f.out) = 0;
+	/* strdup() doesn't use amalloc(), so in an amalloc()ed
+	 * build this copies the string safely out of our memory
+	 * paranoia arena.  In a non-amalloc world, it's a spurious
+	 * memory allocation, but it avoids unintentional hilarity
+	 * with amalloc()
+	 */
+	*res = strdup(T(f.out));
     }
     else {
 	 *res = 0;
@@ -299,18 +351,19 @@ mkd_line(char *bfr, int size, char **res, DWORD flags)
 /* ___mkd_reparse() a line, writing it to a FILE
  */
 int
-mkd_generateline(char *bfr, int size, FILE *output, DWORD flags)
+mkd_generateline(char *bfr, int size, FILE *output, mkd_flag_t flags)
 {
     MMIOT f;
+    int status;
 
     mkd_parse_line(bfr, size, &f, flags);
-    if ( flags & MKD_CDATA )
-	mkd_generatexml(T(f.out), S(f.out), output);
+    if ( is_flag_set(flags, MKD_CDATA) )
+	status = mkd_generatexml(T(f.out), S(f.out), output) != EOF;
     else
-	fwrite(T(f.out), S(f.out), 1, output);
+	status = fwrite(T(f.out), S(f.out), 1, output) == S(f.out);
 
     ___mkd_freemmiot(&f, 0);
-    return 0;
+    return status ? 0 : EOF;
 }
 
 
@@ -319,8 +372,11 @@ mkd_generateline(char *bfr, int size, FILE *output, DWORD flags)
 void
 mkd_e_url(Document *f, mkd_callback_t edit)
 {
-    if ( f )
+    if ( f ) {
+	if ( f->cb.e_url != edit )
+	    f->dirty = 1;
 	f->cb.e_url = edit;
+    }
 }
 
 
@@ -329,8 +385,24 @@ mkd_e_url(Document *f, mkd_callback_t edit)
 void
 mkd_e_flags(Document *f, mkd_callback_t edit)
 {
-    if ( f )
+    if ( f ) {
+	if ( f->cb.e_flags != edit )
+	    f->dirty = 1;
 	f->cb.e_flags = edit;
+    }
+}
+
+
+/* set the anchor formatter
+ */
+void
+mkd_e_anchor(Document *f, mkd_callback_t format)
+{
+    if ( f ) {
+	if ( f->cb.e_anchor != format )
+	    f->dirty = 1;
+	f->cb.e_anchor = format;
+    }
 }
 
 
@@ -339,8 +411,11 @@ mkd_e_flags(Document *f, mkd_callback_t edit)
 void
 mkd_e_free(Document *f, mkd_free_t dealloc)
 {
-    if ( f )
+    if ( f ) {
+	if ( f->cb.e_free != dealloc )
+	    f->dirty = 1;
 	f->cb.e_free = dealloc;
+    }
 }
 
 
@@ -349,8 +424,23 @@ mkd_e_free(Document *f, mkd_free_t dealloc)
 void
 mkd_e_data(Document *f, void *data)
 {
-    if ( f )
+    if ( f ) {
+	if ( f->cb.e_data != data )
+	    f->dirty = 1;
 	f->cb.e_data = data;
+    }
+}
+
+
+/* set the code block display callback
+ */
+void
+mkd_e_code_format(Document *f, mkd_callback_t codefmt)
+{
+    if ( f && (f->cb.e_codefmt != codefmt) ) {
+	f->dirty = 1;
+	f->cb.e_codefmt = codefmt;
+    }
 }
 
 
@@ -359,6 +449,9 @@ mkd_e_data(Document *f, void *data)
 void
 mkd_ref_prefix(Document *f, char *data)
 {
-    if ( f )
+    if ( f ) {
+	if ( f->ref_prefix != data )
+	    f->dirty = 1;
 	f->ref_prefix = data;
+    }
 }
diff --git a/ext/mkdio.h b/ext/mkdio.h
index 91d7fca..9775f2c 100644
--- a/ext/mkdio.h
+++ b/ext/mkdio.h
@@ -3,9 +3,11 @@
 
 #include <stdio.h>
 
+#include <inttypes.h>
+
 typedef void MMIOT;
 
-typedef unsigned int mkd_flag_t;
+typedef uint32_t mkd_flag_t;
 
 /* line builder for markdown()
  */
@@ -30,12 +32,10 @@ void mkd_cleanup(MMIOT*);
 
 /* markup functions
  */
-int mkd_dump(MMIOT*, FILE*, int, char*);
+int mkd_dump(MMIOT*, FILE*, mkd_flag_t, char*);
 int markdown(MMIOT*, FILE*, mkd_flag_t);
 int mkd_line(char *, int, char **, mkd_flag_t);
-typedef int (*mkd_sta_function_t)(const int,const void*);
-void mkd_string_to_anchor(char *, int, mkd_sta_function_t, void*, int);
-int mkd_xhtmlpage(MMIOT*,int,FILE*);
+int mkd_xhtmlpage(MMIOT*,mkd_flag_t,FILE*);
 
 /* header block access
  */
@@ -67,6 +67,8 @@ typedef void   (*mkd_free_t)(char*, void*);
 
 void mkd_e_url(void *, mkd_callback_t);
 void mkd_e_flags(void *, mkd_callback_t);
+void mkd_e_anchor(void *, mkd_callback_t);
+void mkd_e_code_format(void*, mkd_callback_t);
 void mkd_e_free(void *, mkd_free_t );
 void mkd_e_data(void *, void *);
 
@@ -106,6 +108,15 @@ void mkd_ref_prefix(MMIOT*, char*);
 #define MKD_NODLIST	0x00100000	/* forbid definition lists */
 #define MKD_EXTRA_FOOTNOTE 0x00200000	/* enable markdown extra-style footnotes */
 #define MKD_NOSTYLE	0x00400000	/* don't extract <style> blocks */
+#define MKD_NODLDISCOUNT 0x00800000	/* disable discount-style definition lists */
+#define	MKD_DLEXTRA	0x01000000	/* enable extra-style definition lists */
+#define MKD_FENCEDCODE	0x02000000	/* enabled fenced code blocks */
+#define MKD_IDANCHOR	0x04000000	/* use id= anchors for TOC links */
+#define MKD_GITHUBTAGS	0x08000000	/* allow dash and underscore in element names */
+#define MKD_URLENCODEDANCHOR 0x10000000 /* urlencode non-identifier chars instead of replacing with dots */
+#define MKD_LATEX	0x40000000	/* handle embedded LaTeX escapes */
+#define MKD_EXPLICITLIST 0x80000000	/* don't combine numbered/bulletted lists */
+
 #define MKD_EMBED	MKD_NOLINKS|MKD_NOIMAGE|MKD_TAGTEXT
 
 /* special flags for mkd_in() and mkd_string()
diff --git a/ext/mktags.c b/ext/mktags.c
index 0d4680a..f1c619e 100644
--- a/ext/mktags.c
+++ b/ext/mktags.c
@@ -3,6 +3,7 @@
 #include <stdio.h>
 
 #define __WITHOUT_AMALLOC 1
+#include "config.h"
 #include "cstring.h"
 #include "tags.h"
 
@@ -41,6 +42,7 @@ typedef int (*stfu)(const void*,const void*);
 
 /* load in the standard collection of html tags that markdown supports
  */
+int
 main()
 {
     int i;
@@ -65,6 +67,7 @@ main()
     KW("H6");
     KW("LISTING");
     KW("NOBR");
+    KW("FORM");
     KW("UL");
     KW("P");
     KW("OL");
diff --git a/ext/notspecial.c b/ext/notspecial.c
new file mode 100644
index 0000000..8e85410
--- /dev/null
+++ b/ext/notspecial.c
@@ -0,0 +1,44 @@
+/*
+ * check a filename to see if it's a (fifo|character special|socket) object
+ * (if a stat() function doesn't exist, we can't stat so we'll just return
+ *  true no matter what.)
+ */
+
+#include "config.h"
+
+#if HAVE_STAT && HAS_ISCHR && HAS_ISFIFO && HAS_ISSOCK
+#include <sys/stat.h>
+
+int
+notspecial(char *file)
+{
+    struct stat info;
+
+    if ( stat(file, &info) != 0 )
+	return 1;
+    
+    return !( S_ISCHR(info.st_mode) || S_ISFIFO(info.st_mode) || S_ISSOCK(info.st_mode) );
+}
+#else
+int
+notspecial(char *file)
+{
+    return 1;
+}
+#endif
+
+
+#if DEBUG
+
+#include <stdio.h>
+
+int
+main(argc, argv)
+char **argv;
+{
+    int i;
+
+    for ( i=1; i < argc; i++ )
+	printf("%s is %sspecial\n", argv[i], notspecial(argv[i]) ? "not " : "");
+}
+#endif
diff --git a/ext/pgm_options.c b/ext/pgm_options.c
index 161917e..3d364ac 100644
--- a/ext/pgm_options.c
+++ b/ext/pgm_options.c
@@ -8,7 +8,6 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <limits.h>
-#include <unistd.h>
 #include <mkdio.h>
 #include <errno.h>
 #include <string.h>
@@ -25,15 +24,15 @@ static struct _opt {
     char *name;
     char *desc;
     int off;
-    int skip;
+    int skip; /* this opt is a synonym */
     int sayenable;
     mkd_flag_t flag;
 } opts[] = {
     { "tabstop",       "default (4-space) tabstops", 0, 0, 1, MKD_TABSTOP  },
     { "image",         "images",                     1, 0, 1, MKD_NOIMAGE  },
     { "links",         "links",                      1, 0, 1, MKD_NOLINKS  },
-    { "relax",         "emphasis inside words",      1, 1, 1, MKD_STRICT   },
-    { "strict",        "emphasis inside words",      0, 0, 1, MKD_STRICT   },
+    { "relax",         "Markdown.pl compatibility",  1, 1, 1, MKD_STRICT   },
+    { "strict",        "Markdown.pl compatibility",  0, 0, 1, MKD_STRICT   },
     { "tables",        "tables",                     1, 0, 1, MKD_NOTABLES },
     { "header",        "pandoc-style headers",       1, 0, 1, MKD_NOHEADER },
     { "html",          "raw html",                   1, 0, 1, MKD_NOHTML   },
@@ -55,6 +54,15 @@ static struct _opt {
     { "footnotes",     "markdown extra footnotes",   0, 0, 1, MKD_EXTRA_FOOTNOTE },
     { "footnote",      "markdown extra footnotes",   0, 1, 1, MKD_EXTRA_FOOTNOTE },
     { "style",         "extract style blocks",       1, 0, 1, MKD_NOSTYLE },
+    { "dldiscount",    "discount-style definition lists", 1, 0, 1, MKD_NODLDISCOUNT },
+    { "dlextra",       "extra-style definition lists", 0, 0, 1, MKD_DLEXTRA },
+    { "fencedcode",    "fenced code blocks",         0, 0, 1, MKD_FENCEDCODE },
+    { "idanchor",      "id= anchors in TOC",         0, 0, 1, MKD_IDANCHOR },
+    { "githubtags",    "permit - and _ in element names", 0, 0, 0, MKD_GITHUBTAGS },
+    { "urlencodedanchor", "html5-style anchors", 0, 0, 0, MKD_URLENCODEDANCHOR },
+    { "html5anchor",   "html5-style anchors", 0, 1, 0, MKD_URLENCODEDANCHOR },
+    { "latex",         "handle LaTeX escapes",         0, 0, 1, MKD_LATEX },
+    { "explicitlist",  "do not merge adjacent numeric/bullet lists", 0, 0, 1, MKD_EXPLICITLIST },
 } ;
 
 #define NR(x)	(sizeof x / sizeof x[0])
@@ -76,7 +84,7 @@ sort_by_flag(struct _opt *a, struct _opt *b)
 
 
 void
-show_flags(int byname)
+show_flags(int byname, int verbose)
 {
     int i;
 
@@ -84,14 +92,14 @@ show_flags(int byname)
 	qsort(opts, NR(opts), sizeof(opts[0]), (stfu)sort_by_name);
     
 	for (i=0; i < NR(opts); i++)
-	    if ( ! opts[i].skip )
+	    if ( verbose || !opts[i].skip )
 		fprintf(stderr, "%16s : %s\n", opts[i].name, opts[i].desc);
     }
     else {
 	qsort(opts, NR(opts), sizeof(opts[0]), (stfu)sort_by_flag);
 	
 	for (i=0; i < NR(opts); i++)
-	    if ( ! opts[i].skip ) {
+	    if ( !opts[i].skip ) {
 		fprintf(stderr, "%08lx : ", (long)opts[i].flag);
 		if ( opts[i].sayenable )
 		    fprintf(stderr, opts[i].off ? "disable " : "enable ");
@@ -101,7 +109,7 @@ show_flags(int byname)
 }
     
 
-int
+char *
 set_flag(mkd_flag_t *flags, char *optionstring)
 {
     int i;
@@ -132,7 +140,7 @@ set_flag(mkd_flag_t *flags, char *optionstring)
 		*flags &= ~opts[i].flag;
 	}
 	else
-	    return 0;
+	    return arg;
     }
-    return 1;
+    return 0;
 }
diff --git a/ext/pgm_options.h b/ext/pgm_options.h
index b7544cd..566023f 100644
--- a/ext/pgm_options.h
+++ b/ext/pgm_options.h
@@ -3,7 +3,7 @@
 
 #include <mkdio.h>
 
-int set_flag(mkd_flag_t *flags, char *optionstring);
-void show_flags(int byname);
+char *set_flag(mkd_flag_t *flags, char *optionstring);
+void show_flags(int byname, int verbose);
 
 #endif/*PGM_OPTIONS_D*/
diff --git a/ext/rdiscount.c b/ext/rdiscount.c
index 3bef140..e7ade6c 100644
--- a/ext/rdiscount.c
+++ b/ext/rdiscount.c
@@ -14,6 +14,9 @@ typedef struct {
  * The following flags are handled specially:
  * - MKD_TABSTOP: Always set.
  * - MKD_NOHEADER: Always set.
+ * - MKD_DLEXTRA: Always set. (For compatibility with RDiscount 2.1.8 and earlier.)
+ * - MKD_FENCEDCODE: Always set. (For compatibility with RDiscount 2.1.8 and earlier.)
+ * - MKD_GITHUBTAGS: Always set. (For compatibility with RDiscount 2.1.8 and earlier.)
  * - MKD_NOPANTS: Set unless the "smart" accessor returns true.
  * 
  * See rb_rdiscount__get_flags() for the detailed implementation.
@@ -31,11 +34,36 @@ static AccessorFlagPair ACCESSOR_2_FLAG[] = {
     { "no_pseudo_protocols", MKD_NO_EXT },
     { "no_superscript", MKD_NOSUPERSCRIPT },
     { "no_strikethrough", MKD_NOSTRIKETHROUGH },
+    { "latex", MKD_LATEX },
+    { "explicitlist", MKD_EXPLICITLIST },
+    { "md1compat", MKD_1_COMPAT },
     { NULL, 0 }     /* sentinel */
 };
 
 static VALUE rb_cRDiscount;
 
+int rb_rdiscount__get_flags(VALUE ruby_obj)
+{
+    AccessorFlagPair *entry;
+    
+    /* compile flags */
+    int flags = MKD_TABSTOP | MKD_NOHEADER | MKD_DLEXTRA | MKD_FENCEDCODE | MKD_GITHUBTAGS;
+    
+    /* The "smart" accessor turns OFF the MKD_NOPANTS flag. */
+    if ( rb_funcall(ruby_obj, rb_intern("smart"), 0) != Qtrue ) {
+        flags = flags | MKD_NOPANTS;
+    }
+    
+    /* Handle standard flags declared in ACCESSOR_2_FLAG */
+    for ( entry = ACCESSOR_2_FLAG; entry->accessor_name; entry++ ) {
+        if ( rb_funcall(ruby_obj, rb_intern(entry->accessor_name), 0) == Qtrue ) {
+            flags = flags | entry->flag;
+        }
+    }
+    
+    return flags;
+}
+
 static VALUE
 rb_rdiscount_to_html(int argc, VALUE *argv, VALUE self)
 {
@@ -114,28 +142,6 @@ rb_rdiscount_toc_content(int argc, VALUE *argv, VALUE self)
     return buf;
 }
 
-int rb_rdiscount__get_flags(VALUE ruby_obj)
-{
-    AccessorFlagPair *entry;
-    
-    /* compile flags */
-    int flags = MKD_TABSTOP | MKD_NOHEADER;
-    
-    /* The "smart" accessor turns OFF the MKD_NOPANTS flag. */
-    if ( rb_funcall(ruby_obj, rb_intern("smart"), 0) != Qtrue ) {
-        flags = flags | MKD_NOPANTS;
-    }
-    
-    /* Handle standard flags declared in ACCESSOR_2_FLAG */
-    for ( entry = ACCESSOR_2_FLAG; entry->accessor_name; entry++ ) {
-        if ( rb_funcall(ruby_obj, rb_intern(entry->accessor_name), 0) == Qtrue ) {
-            flags = flags | entry->flag;
-        }
-    }
-    
-    return flags;
-}
-
 
 void Init_rdiscount()
 {
diff --git a/ext/resource.c b/ext/resource.c
index 9795db8..0b2dd06 100644
--- a/ext/resource.c
+++ b/ext/resource.c
@@ -65,6 +65,7 @@ ___mkd_freefootnote(Footnote *f)
     DELETE(f->tag);
     DELETE(f->link);
     DELETE(f->title);
+    if ( f->text) ___mkd_freeParagraph(f->text);
 }
 
 
diff --git a/ext/setup.c b/ext/setup.c
index 988a6aa..b889f18 100644
--- a/ext/setup.c
+++ b/ext/setup.c
@@ -31,7 +31,7 @@ mkd_initialize()
 }
 
 
-void
+void DESTRUCTOR
 mkd_shlib_destructor()
 {
     mkd_deallocate_tags();
diff --git a/ext/tags.c b/ext/tags.c
index 792ef92..7e7e15f 100644
--- a/ext/tags.c
+++ b/ext/tags.c
@@ -1,5 +1,7 @@
 /* block-level tags for passing html blocks through the blender
  */
+#include "config.h"
+
 #define __WITHOUT_AMALLOC 1
 #include "cstring.h"
 #include "tags.h"
diff --git a/ext/toc.c b/ext/toc.c
index 8037e36..1cdddec 100644
--- a/ext/toc.c
+++ b/ext/toc.c
@@ -25,12 +25,14 @@ mkd_toc(Document *p, char **doc)
     Cstring res;
     int size;
     int first = 1;
+    extern void Csreparse(Cstring *, char *, int, mkd_flag_t);
+    
     
     if ( !(doc && p && p->ctx) ) return -1;
 
     *doc = 0;
     
-    if ( ! (p->ctx->flags & MKD_TOC) ) return 0;
+    if ( ! is_flag_set(p->ctx->flags, MKD_TOC) ) return 0;
 
     CREATE(res);
     RESERVE(res, 100);
@@ -38,7 +40,7 @@ mkd_toc(Document *p, char **doc)
     for ( tp = p->code; tp ; tp = tp->next ) {
 	if ( tp->typ == SOURCE ) {
 	    for ( srcp = tp->down; srcp; srcp = srcp->next ) {
-		if ( srcp->typ == HDR && srcp->text ) {
+		if ( (srcp->typ == HDR) && srcp->text ) {
 	    
 		    while ( last_hnumber > srcp->hnumber ) {
 			if ( (last_hnumber - srcp->hnumber) > 1 )
@@ -62,11 +64,11 @@ mkd_toc(Document *p, char **doc)
 		    Csprintf(&res, "%*s<li><a href=\"#", srcp->hnumber, "");
 		    mkd_string_to_anchor(T(srcp->text->text),
 					 S(srcp->text->text),
-					 (mkd_sta_function_t)Csputc, &res,1);
+					 (mkd_sta_function_t)Csputc,
+					 &res,1,p->ctx);
 		    Csprintf(&res, "\">");
-		    mkd_string_to_anchor(T(srcp->text->text),
-					 S(srcp->text->text),
-					 (mkd_sta_function_t)Csputc, &res,0);
+		    Csreparse(&res, T(srcp->text->text),
+				    S(srcp->text->text), IS_LABEL);
 		    Csprintf(&res, "</a>");
 
 		    first = 0;
@@ -82,16 +84,12 @@ mkd_toc(Document *p, char **doc)
     }
 
     if ( (size = S(res)) > 0 ) {
+	/* null-terminate & strdup into a free()able memory chunk
+	 */
 	EXPAND(res) = 0;
-			/* HACK ALERT! HACK ALERT! HACK ALERT! */
-	*doc = T(res);  /* we know that a T(Cstring) is a character pointer
-			 * so we can simply pick it up and carry it away,
-			 * leaving the husk of the Ctring on the stack
-			 * END HACK ALERT
-			 */
+	*doc = strdup(T(res));
     }
-    else
-	DELETE(res);
+    DELETE(res);
     return size;
 }
 
diff --git a/ext/version.c b/ext/version.c
index 8e48410..0fbd81a 100644
--- a/ext/version.c
+++ b/ext/version.c
@@ -1,30 +1,13 @@
 #include "config.h"
 
-char markdown_version[] = VERSION
+char markdown_version[] = BRANCH VERSION
 #if 4 != 4
 		" TAB=4"
 #endif
 #if USE_AMALLOC
 		" DEBUG"
 #endif
-#if USE_DISCOUNT_DL
-# if USE_EXTRA_DL
-		" DL=BOTH"
-# else
-		" DL=DISCOUNT"
-# endif
-#elif USE_EXTRA_DL
-		" DL=EXTRA"
-#else
-		" DL=NONE"
-#endif
-#if WITH_ID_ANCHOR
-		" ID-ANCHOR"
-#endif
-#if WITH_GITHUB_TAGS
-		" GITHUB-TAGS"
-#endif
-#if WITH_FENCED_CODE
-		" FENCED-CODE"
+#if GITHUB_CHECKBOX
+		" GITHUB_CHECKBOX"
 #endif
 		;
diff --git a/ext/xml.c b/ext/xml.c
index 5e58389..f71b05c 100644
--- a/ext/xml.c
+++ b/ext/xml.c
@@ -47,9 +47,9 @@ mkd_generatexml(char *p, int size, FILE *out)
 	c = *p++;
 
 	if ( entity = mkd_xmlchar(c) )
-	    fputs(entity, out);
+	    DO_OR_DIE( fputs(entity, out) );
 	else
-	    fputc(c, out);
+	    DO_OR_DIE( fputc(c, out) );
     }
     return 0;
 }
@@ -74,9 +74,10 @@ mkd_xml(char *p, int size, char **res)
 	else
 	    Csputc(c, &f);
     }
-			/* HACK ALERT! HACK ALERT! HACK ALERT! */
-    *res = T(f);	/* we know that a T(Cstring) is a character pointer */
-			/* so we can simply pick it up and carry it away, */
-    return S(f);	/* leaving the husk of the Ctring on the stack */
-			/* END HACK ALERT */
+    /* null terminate, strdup() into a free()able memory block,
+     * and return the size of everything except the null terminator
+     */
+    EXPAND(f) = 0;
+    *res = strdup(T(f));
+    return S(f)-1;
 }
diff --git a/ext/xmlpage.c b/ext/xmlpage.c
index 96ed2b7..2003cdd 100644
--- a/ext/xmlpage.c
+++ b/ext/xmlpage.c
@@ -5,44 +5,39 @@
  * 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"
+#include <markdown.h>
 
 
 int
-mkd_xhtmlpage(Document *p, int flags, FILE *out)
+mkd_xhtmlpage(Document *p, mkd_flag_t flags, FILE *out)
 {
     char *title;
     extern char *mkd_doc_title(Document *);
     
     if ( mkd_compile(p, flags) ) {
-	fprintf(out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
-	fprintf(out, "<!DOCTYPE html "
-		     " PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\""
-		     " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n");
-
-	fprintf(out, "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n");
+	DO_OR_DIE( fprintf(out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+				"<!DOCTYPE html "
+				" PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\""
+				" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
+				"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n") );
 
-	fprintf(out, "<head>\n");
-	if ( title = mkd_doc_title(p) )
-	    fprintf(out, "<title>%s</title>\n", title);
-	mkd_generatecss(p, out);
-	fprintf(out, "</head>\n");
-	
-	fprintf(out, "<body>\n");
-	mkd_generatehtml(p, out);
-	fprintf(out, "</body>\n");
-	fprintf(out, "</html>\n");
+	DO_OR_DIE( fprintf(out, "<head>\n") );
+	DO_OR_DIE( fprintf(out, "<title>") );
+	if ( title = mkd_doc_title(p) ) {
+	    DO_OR_DIE( fprintf(out, "%s", title) );
+	}
+	DO_OR_DIE( fprintf(out, "</title>\n") );
+	DO_OR_DIE( mkd_generatecss(p, out) );
+	DO_OR_DIE( fprintf(out, "</head>\n"
+				"<body>\n") );
 	
-	mkd_cleanup(p);
+	DO_OR_DIE( mkd_generatehtml(p, out) );
+	DO_OR_DIE( fprintf(out, "</body>\n"
+				"</html>\n") );
 
 	return 0;
     }
-    return -1;
+    return EOF;
 }
diff --git a/lib/rdiscount.rb b/lib/rdiscount.rb
index 40e9053..f0f6de3 100644
--- a/lib/rdiscount.rb
+++ b/lib/rdiscount.rb
@@ -24,7 +24,7 @@
 #   end
 #
 class RDiscount
-  VERSION = '2.1.8'
+  VERSION = '2.2.7'
 
   # Original Markdown formatted text.
   attr_reader :text
@@ -75,6 +75,15 @@ class RDiscount
   # Disable strikethrough processing.
   attr_accessor :no_strikethrough
 
+  # Keep LaTeX inside $$ intact.
+  attr_accessor :latex
+
+  # Don't merge adjacent list into a single list.
+  attr_accessor :explicitlist
+
+  # Not documented: run in markdown 1 compat mode (only used for MarkdownTest1.0)
+  attr_accessor :md1compat
+
   # Create a RDiscount Markdown processor. The +text+ argument
   # should be a string containing Markdown text. Additional arguments may be
   # supplied to set various processing options:
@@ -95,6 +104,8 @@ class RDiscount
   # * <tt>:no_pseudo_protocols</tt> - Do not process pseudo-protocols.
   # * <tt>:no_superscript</tt> - Disable superscript processing.
   # * <tt>:no_strikethrough</tt> - Disable strikethrough processing.
+  # * <tt>:latex</tt> - Keep LaTeX inside $$ intact.
+  # * <tt>:explicitlist</tt> - Don't merge adjacent list into a single list.
   #
   def initialize(text, *extensions)
     @text  = text
diff --git a/metadata.yml b/metadata.yml
deleted file mode 100644
index 0459094..0000000
--- a/metadata.yml
+++ /dev/null
@@ -1,99 +0,0 @@
---- !ruby/object:Gem::Specification
-name: rdiscount
-version: !ruby/object:Gem::Version
-  version: 2.1.8
-platform: ruby
-authors:
-- Ryan Tomayko
-- David Loren Parsons
-- Andrew White
-- David Foster
-autorequire: 
-bindir: bin
-cert_chain: []
-date: 2015-02-01 00:00:00.000000000 Z
-dependencies: []
-description: 
-email: davidfstr@gmail.com
-executables:
-- rdiscount
-extensions:
-- ext/extconf.rb
-extra_rdoc_files:
-- COPYING
-files:
-- BUILDING
-- COPYING
-- README.markdown
-- Rakefile
-- bin/rdiscount
-- ext/Csio.c
-- ext/VERSION
-- ext/amalloc.c
-- ext/amalloc.h
-- ext/basename.c
-- ext/blocktags
-- ext/config.h
-- ext/css.c
-- ext/cstring.h
-- ext/docheader.c
-- ext/dumptree.c
-- ext/emmatch.c
-- ext/extconf.rb
-- ext/flags.c
-- ext/generate.c
-- ext/github_flavoured.c
-- ext/html5.c
-- ext/markdown.c
-- ext/markdown.h
-- ext/mkdio.c
-- ext/mkdio.h
-- ext/mktags.c
-- ext/pgm_options.c
-- ext/pgm_options.h
-- ext/rdiscount.c
-- ext/resource.c
-- ext/setup.c
-- ext/tags.c
-- ext/tags.h
-- ext/toc.c
-- ext/version.c
-- ext/xml.c
-- ext/xmlpage.c
-- lib/markdown.rb
-- lib/rdiscount.rb
-- man/markdown.7
-- man/rdiscount.1
-- man/rdiscount.1.ronn
-- rdiscount.gemspec
-- test/benchmark.rb
-- test/benchmark.txt
-- test/markdown_test.rb
-- test/rdiscount_test.rb
-homepage: http://dafoster.net/projects/rdiscount/
-licenses:
-- BSD
-metadata: {}
-post_install_message: 
-rdoc_options: []
-require_paths:
-- lib
-required_ruby_version: !ruby/object:Gem::Requirement
-  requirements:
-  - - "!="
-    - !ruby/object:Gem::Version
-      version: 1.9.2
-required_rubygems_version: !ruby/object:Gem::Requirement
-  requirements:
-  - - ">="
-    - !ruby/object:Gem::Version
-      version: '0'
-requirements: []
-rubyforge_project: wink
-rubygems_version: 2.2.2
-signing_key: 
-specification_version: 4
-summary: Fast Implementation of Gruber's Markdown in C
-test_files:
-- test/markdown_test.rb
-- test/rdiscount_test.rb
diff --git a/rdiscount.gemspec b/rdiscount.gemspec
index df75979..daa19cc 100644
--- a/rdiscount.gemspec
+++ b/rdiscount.gemspec
@@ -1,12 +1,11 @@
 Gem::Specification.new do |s|
   s.name = 'rdiscount'
-  s.version = '2.1.8'
+  s.version = '2.2.7'
   s.summary = "Fast Implementation of Gruber's Markdown in C"
-  s.date = '2015-02-01'
-  s.email = 'davidfstr@gmail.com'
+  s.email = 'david@dafoster.net'
   s.homepage = 'http://dafoster.net/projects/rdiscount/'
-  s.authors = ["Ryan Tomayko", "David Loren Parsons", "Andrew White", "David Foster"]
-  s.license = "BSD"
+  s.authors = ["Ryan Tomayko", "David Loren Parsons", "Andrew White", "David Foster", "l33tname"]
+  s.license = "BSD-3-Clause"
   # = MANIFEST =
   s.files = %w[
     BUILDING
@@ -15,13 +14,12 @@ Gem::Specification.new do |s|
     Rakefile
     bin/rdiscount
     discount
-    ext/Csio.c
-    ext/VERSION
     ext/amalloc.c
     ext/amalloc.h
     ext/basename.c
     ext/blocktags
     ext/config.h
+    ext/Csio.c
     ext/css.c
     ext/cstring.h
     ext/docheader.c
@@ -30,13 +28,17 @@ Gem::Specification.new do |s|
     ext/extconf.rb
     ext/flags.c
     ext/generate.c
+    ext/gethopt.c
+    ext/gethopt.h
     ext/github_flavoured.c
+    ext/h1title.c
     ext/html5.c
     ext/markdown.c
     ext/markdown.h
     ext/mkdio.c
     ext/mkdio.h
     ext/mktags.c
+    ext/notspecial.c
     ext/pgm_options.c
     ext/pgm_options.h
     ext/rdiscount.c
@@ -45,6 +47,7 @@ Gem::Specification.new do |s|
     ext/tags.c
     ext/tags.h
     ext/toc.c
+    ext/VERSION
     ext/version.c
     ext/xml.c
     ext/xmlpage.c
@@ -65,7 +68,4 @@ Gem::Specification.new do |s|
   s.extensions = ["ext/extconf.rb"]
   s.executables = ["rdiscount"]
   s.require_paths = ["lib"]
-  s.rubyforge_project = 'wink'
-  # Ruby 1.9.2 has a known bug in mkmf. Ruby 1.9.3 or later is fine.
-  s.required_ruby_version = '!= 1.9.2'
 end
diff --git a/test/markdown_test.rb b/test/markdown_test.rb
index f93b513..1291300 100644
--- a/test/markdown_test.rb
+++ b/test/markdown_test.rb
@@ -138,7 +138,6 @@ class MarkdownTest < Test::Unit::TestCase
   Dir["#{MARKDOWN_TEST_DIR}/Tests/*.text"].each do |text_file|
 
     basename = File.basename(text_file).sub(/\.text$/, '')
-    html_file = text_file.sub(/text$/, 'html')
     method_name = basename.gsub(/[-,()]/, '').gsub(/\s+/, '_').downcase
 
     define_method "test_#{method_name}" do
diff --git a/test/rdiscount_test.rb b/test/rdiscount_test.rb
index 73a9aa2..f54f812 100644
--- a/test/rdiscount_test.rb
+++ b/test/rdiscount_test.rb
@@ -7,7 +7,7 @@ require 'rdiscount'
 
 class RDiscountTest < Test::Unit::TestCase
   def test_that_version_looks_valid
-    if not /^[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?$/ =~ RDiscount::VERSION
+    if not (/^[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?$/) =~ RDiscount::VERSION
       assert false, 'Expected RDiscount::VERSION to be a 3 or 4 component version string but found ' + RDiscount::VERSION.to_s
     end
   end
@@ -54,24 +54,24 @@ class RDiscountTest < Test::Unit::TestCase
   def test_that_generate_toc_sets_toc_ids
     rd = RDiscount.new("# Level 1\n\n## Level 2", :generate_toc)
     assert rd.generate_toc
-    assert_equal %(<a name="Level.1"></a>\n<h1>Level 1</h1>\n\n<a name="Level.2"></a>\n<h2>Level 2</h2>\n), rd.to_html
+    assert_equal %(<a name="Level-1"></a>\n<h1>Level 1</h1>\n\n<a name="Level-2"></a>\n<h2>Level 2</h2>\n), rd.to_html
   end
 
   def test_should_get_the_generated_toc
     rd = RDiscount.new("# Level 1\n\n## Level 2", :generate_toc)
-    exp = %(<ul>\n <li><a href=\"#Level.1\">Level 1</a>\n <ul>\n  <li><a href=\"#Level.2\">Level 2</a></li>\n </ul>\n </li>\n</ul>)
+    exp = %(<ul>\n <li><a href=\"#Level-1\">Level 1</a>\n <ul>\n  <li><a href=\"#Level-2\">Level 2</a></li>\n </ul>\n </li>\n</ul>)
     assert_equal exp, rd.toc_content.strip
   end
   
   def test_toc_should_escape_apostropes
     rd = RDiscount.new("# A'B\n\n## C", :generate_toc)
-    exp = %(<ul>\n <li><a href=\"#A.B\">A'B</a>\n <ul>\n  <li><a href=\"#C\">C</a></li>\n </ul>\n </li>\n</ul>)
+    exp = %(<ul>\n <li><a href=\"#A-27-B\">A'B</a>\n <ul>\n  <li><a href=\"#C\">C</a></li>\n </ul>\n </li>\n</ul>)
     assert_equal exp, rd.toc_content.strip
   end
   
   def test_toc_should_escape_question_marks
     rd = RDiscount.new("# A?B\n\n## C", :generate_toc)
-    exp = %(<ul>\n <li><a href=\"#A.B\">A?B</a>\n <ul>\n  <li><a href=\"#C\">C</a></li>\n </ul>\n </li>\n</ul>)
+    exp = %(<ul>\n <li><a href=\"#A-3f-B\">A?B</a>\n <ul>\n  <li><a href=\"#C\">C</a></li>\n </ul>\n </li>\n</ul>)
     assert_equal exp, rd.toc_content.strip
   end
 
@@ -139,12 +139,6 @@ EOS
   end
 
   def test_that_tags_can_have_dashes_and_underscores
-    if RDiscount::VERSION.start_with? "2.0.7"
-      # Skip test for 2.0.7.x series due to upstream behavioral change in
-      # Discount 2.0.7. This test can be fixed in Discount 2.1.5 using the
-      # WITH_GITHUB_TAGS compile-time flag.
-      return
-    end
     rd = RDiscount.new("foo <asdf-qwerty>bar</asdf-qwerty> and <a_b>baz</a_b>")
     assert_equal "<p>foo <asdf-qwerty>bar</asdf-qwerty> and <a_b>baz</a_b></p>\n", rd.to_html
   end
@@ -249,7 +243,47 @@ EOS
 </dl>
 EOS
   end
-  
+
+  def test_latex_passtrough_dont_render_link
+    rd = RDiscount.new("$$[(1+2)*3-4](1-2)$$", :latex)
+    assert_equal "<p>$$[(1+2)*3-4](1-2)$$</p>\n", rd.to_html
+  end
+
+  def test_that_emphasis_beside_international_characters_detected
+    rd = RDiscount.new(%(*foo ä bar*))
+    assert_equal %(<p><em>foo ä bar</em></p>\n), rd.to_html
+
+    rd = RDiscount.new(%(*ä foobar*))
+    assert_equal %(<p><em>ä foobar</em></p>\n), rd.to_html
+
+    rd = RDiscount.new(%(*foobar ä*))
+    assert_equal %(<p><em>foobar ä</em></p>\n), rd.to_html
+  end
+
+  def test_taht
+    rd = RDiscount.new(<<EOS, :explicitlist)
+- Bullet
+- Bullet
+
+1. Numbered
+2. Numbered
+EOS
+
+    assert_equal <<EOS, rd.to_html
+<ul>
+<li>Bullet</li>
+<li>Bullet</li>
+</ul>
+
+
+<ol>
+<li>Numbered</li>
+<li>Numbered</li>
+</ol>
+
+EOS
+  end
+
   def test_that_extra_definition_lists_work
     rd = RDiscount.new(<<EOS)
 tag1
@@ -262,15 +296,4 @@ EOS
 </dl>
 EOS
   end
-  
-  def test_that_emphasis_beside_international_characters_detected
-    rd = RDiscount.new(%(*foo ä bar*))
-    assert_equal %(<p><em>foo ä bar</em></p>\n), rd.to_html
-    
-    rd = RDiscount.new(%(*ä foobar*))
-    assert_equal %(<p><em>ä foobar</em></p>\n), rd.to_html
-    
-    rd = RDiscount.new(%(*foobar ä*))
-    assert_equal %(<p><em>foobar ä</em></p>\n), rd.to_html
-  end
 end

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/debug/.build-id/53/cf3b3a61681b93c617594cf821918cdf9b56de.debug
-rw-r--r--  root/root   /usr/share/rubygems-integration/3.1.0/specifications/rdiscount-2.2.7.gemspec

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/debug/.build-id/9d/8098b2bb82cede82d261f9805eb2891ad3604f.debug
-rw-r--r--  root/root   /usr/share/rubygems-integration/3.1.0/specifications/rdiscount-2.1.8.gemspec

No differences were encountered between the control files of package ruby-rdiscount

Control files of package ruby-rdiscount-dbgsym: lines which differ (wdiff format)

  • Build-Ids: 9d8098b2bb82cede82d261f9805eb2891ad3604f 53cf3b3a61681b93c617594cf821918cdf9b56de

More details

Full run details