Commits

Matthew Schinckel committed 5944c1a

Add doc-tools for documentation based on source code comments.

Comments (0)

Files changed (3)

     <script src="underscore.js"></script>
     <script src="garmin.js"></script>
 
-I'm currently using underscore.js, as it gives a nicer `.each()` function, and provides `_.union()` for arrays. That may change, as less dependencies is better.
+I'm currently using underscore.js, as it gives a nicer `.each()` function, and provides `_.union()` for arrays, and `_.range()`. That may change, as less dependencies is better. I'd also like to be able to remove the dependence on jQuery. I use the DOM selector a few times, but also construct objects, append to the DOM, and use the `$.parseXML()` function quite a lot. For now, I'm happy to have these as dependencies.
 
 ## Usage
 
           // data contains XML!
         });
         // Write workouts to the device.
-        g.writeWorkouts("<Valid XML data>");
+        g.writeWorkouts("Valid XML data");
       });
     </script>
     
+/**
+
+# Markdoc.js #
+
+Render .js files with markdown in comments as pretty documentation.
+
+**/
+
+// Parse a javascript file, looking for comments that contain markdown.
+
+var Documentator = function Documentator(data) {
+  
+  
+};
+//
+// showdown.js -- A javascript port of Markdown.
+//
+// Copyright (c) 2007 John Fraser.
+//
+// Original Markdown Copyright (c) 2004-2005 John Gruber
+//   <http://daringfireball.net/projects/markdown/>
+//
+// Redistributable under a BSD-style open source license.
+// See license.txt for more information.
+//
+// The full source distribution is at:
+//
+//              A A L
+//              T C A
+//              T K B
+//
+//   <http://www.attacklab.net/>
+//
+
+//
+// Wherever possible, Showdown is a straight, line-by-line port
+// of the Perl version of Markdown.
+//
+// This is not a normal parser design; it's basically just a
+// series of string substitutions.  It's hard to read and
+// maintain this way,  but keeping Showdown close to the original
+// design makes it easier to port new features.
+//
+// More importantly, Showdown behaves like markdown.pl in most
+// edge cases.  So web applications can do client-side preview
+// in Javascript, and then build identical HTML on the server.
+//
+// This port needs the new RegExp functionality of ECMA 262,
+// 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers
+// should do fine.  Even with the new regular expression features,
+// We do a lot of work to emulate Perl's regex functionality.
+// The tricky changes in this file mostly have the "attacklab:"
+// label.  Major or self-explanatory changes don't.
+//
+// Smart diff tools like Araxis Merge will be able to match up
+// this file with markdown.pl in a useful way.  A little tweaking
+// helps: in a copy of markdown.pl, replace "#" with "//" and
+// replace "$text" with "text".  Be sure to ignore whitespace
+// and line endings.
+//
+
+
+//
+// Showdown usage:
+//
+//   var text = "Markdown *rocks*.";
+//
+//   var converter = new Showdown.converter();
+//   var html = converter.makeHtml(text);
+//
+//   alert(html);
+//
+// Note: move the sample code to the bottom of this
+// file before uncommenting it.
+//
+
+
+//
+// Showdown namespace
+//
+var Showdown = {};
+
+//
+// converter
+//
+// Wraps all "globals" so that the only thing
+// exposed is makeHtml().
+//
+Showdown.converter = function() {
+
+//
+// Globals:
+//
+
+// Global hashes, used by various utility routines
+var g_urls;
+var g_titles;
+var g_html_blocks;
+
+// Used to track when we're inside an ordered or unordered list
+// (see _ProcessListItems() for details):
+var g_list_level = 0;
+
+
+this.makeHtml = function(text) {
+//
+// Main function. The order in which other subs are called here is
+// essential. Link and image substitutions need to happen before
+// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
+// and <img> tags get encoded.
+//
+
+    // Clear the global hashes. If we don't clear these, you get conflicts
+    // from other articles when generating a page which contains more than
+    // one article (e.g. an index page that shows the N most recent
+    // articles):
+    g_urls = new Array();
+    g_titles = new Array();
+    g_html_blocks = new Array();
+
+    // attacklab: Replace ~ with ~T
+    // This lets us use tilde as an escape char to avoid md5 hashes
+    // The choice of character is arbitray; anything that isn't
+    // magic in Markdown will work.
+    text = text.replace(/~/g,"~T");
+
+    // attacklab: Replace $ with ~D
+    // RegExp interprets $ as a special character
+    // when it's in a replacement string
+    text = text.replace(/\$/g,"~D");
+
+    // Standardize line endings
+    text = text.replace(/\r\n/g,"\n"); // DOS to Unix
+    text = text.replace(/\r/g,"\n"); // Mac to Unix
+
+    // Make sure text begins and ends with a couple of newlines:
+    text = "\n\n" + text + "\n\n";
+
+    // Convert all tabs to spaces.
+    text = _Detab(text);
+
+    // Strip any lines consisting only of spaces and tabs.
+    // This makes subsequent regexen easier to write, because we can
+    // match consecutive blank lines with /\n+/ instead of something
+    // contorted like /[ \t]*\n+/ .
+    text = text.replace(/^[ \t]+$/mg,"");
+
+    // Turn block-level HTML blocks into hash entries
+    text = _HashHTMLBlocks(text);
+
+    // Strip link definitions, store in hashes.
+    text = _StripLinkDefinitions(text);
+
+    text = _RunBlockGamut(text);
+
+    text = _UnescapeSpecialChars(text);
+
+    // attacklab: Restore dollar signs
+    text = text.replace(/~D/g,"$$");
+
+    // attacklab: Restore tildes
+    text = text.replace(/~T/g,"~");
+
+    return text;
+}
+
+
+var _StripLinkDefinitions = function(text) {
+//
+// Strips link definitions from text, stores the URLs and titles in
+// hash references.
+//
+
+    // Link defs are in the form: ^[id]: url "optional title"
+
+    /*
+        var text = text.replace(/
+                ^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1
+                  [ \t]*
+                  \n?               // maybe *one* newline
+                  [ \t]*
+                <?(\S+?)>?          // url = $2
+                  [ \t]*
+                  \n?               // maybe one newline
+                  [ \t]*
+                (?:
+                  (\n*)             // any lines skipped = $3 attacklab: lookbehind removed
+                  ["(]
+                  (.+?)             // title = $4
+                  [")]
+                  [ \t]*
+                )?                  // title is optional
+                (?:\n+|$)
+              /gm,
+              function(){...});
+    */
+    var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,
+        function (wholeMatch,m1,m2,m3,m4) {
+            m1 = m1.toLowerCase();
+            g_urls[m1] = _EncodeAmpsAndAngles(m2);  // Link IDs are case-insensitive
+            if (m3) {
+                // Oops, found blank lines, so it's not a title.
+                // Put back the parenthetical statement we stole.
+                return m3+m4;
+            } else if (m4) {
+                g_titles[m1] = m4.replace(/"/g,"&quot;");
+            }
+            
+            // Completely remove the definition from the text
+            return "";
+        }
+    );
+
+    return text;
+}
+
+
+var _HashHTMLBlocks = function(text) {
+    // attacklab: Double up blank lines to reduce lookaround
+    text = text.replace(/\n/g,"\n\n");
+
+    // Hashify HTML blocks:
+    // We only want to do this for block-level HTML tags, such as headers,
+    // lists, and tables. That's because we still want to wrap <p>s around
+    // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+    // phrase emphasis, and spans. The list of tags we're looking for is
+    // hard-coded:
+    var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
+    var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
+
+    // First, look for nested blocks, e.g.:
+    //   <div>
+    //     <div>
+    //     tags for inner block must be indented.
+    //     </div>
+    //   </div>
+    //
+    // The outermost tags must start at the left margin for this to match, and
+    // the inner nested divs must be indented.
+    // We need to do this before the next, more liberal match, because the next
+    // match will start at the first `<div>` and stop at the first `</div>`.
+
+    // attacklab: This regex can be expensive when it fails.
+    /*
+        var text = text.replace(/
+        (                       // save in $1
+            ^                   // start of line  (with /m)
+            <($block_tags_a)    // start tag = $2
+            \b                  // word break
+                                // attacklab: hack around khtml/pcre bug...
+            [^\r]*?\n           // any number of lines, minimally matching
+            </\2>               // the matching end tag
+            [ \t]*              // trailing spaces/tabs
+            (?=\n+)             // followed by a newline
+        )                       // attacklab: there are sentinel newlines at end of document
+        /gm,function(){...}};
+    */
+    text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement);
+
+    //
+    // Now match more liberally, simply from `\n<tag>` to `</tag>\n`
+    //
+
+    /*
+        var text = text.replace(/
+        (                       // save in $1
+            ^                   // start of line  (with /m)
+            <($block_tags_b)    // start tag = $2
+            \b                  // word break
+                                // attacklab: hack around khtml/pcre bug...
+            [^\r]*?             // any number of lines, minimally matching
+            .*</\2>             // the matching end tag
+            [ \t]*              // trailing spaces/tabs
+            (?=\n+)             // followed by a newline
+        )                       // attacklab: there are sentinel newlines at end of document
+        /gm,function(){...}};
+    */
+    text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement);
+
+    // Special case just for <hr />. It was easier to make a special case than
+    // to make the other regex more complicated.  
+
+    /*
+        text = text.replace(/
+        (                       // save in $1
+            \n\n                // Starting after a blank line
+            [ ]{0,3}
+            (<(hr)              // start tag = $2
+            \b                  // word break
+            ([^<>])*?           // 
+            \/?>)               // the matching end tag
+            [ \t]*
+            (?=\n{2,})          // followed by a blank line
+        )
+        /g,hashElement);
+    */
+    text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);
+
+    // Special case for standalone HTML comments:
+
+    /*
+        text = text.replace(/
+        (                       // save in $1
+            \n\n                // Starting after a blank line
+            [ ]{0,3}            // attacklab: g_tab_width - 1
+            <!
+            (--[^\r]*?--\s*)+
+            >
+            [ \t]*
+            (?=\n{2,})          // followed by a blank line
+        )
+        /g,hashElement);
+    */
+    text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement);
+
+    // PHP and ASP-style processor instructions (<?...?> and <%...%>)
+
+    /*
+        text = text.replace(/
+        (?:
+            \n\n                // Starting after a blank line
+        )
+        (                       // save in $1
+            [ ]{0,3}            // attacklab: g_tab_width - 1
+            (?:
+                <([?%])         // $2
+                [^\r]*?
+                \2>
+            )
+            [ \t]*
+            (?=\n{2,})          // followed by a blank line
+        )
+        /g,hashElement);
+    */
+    text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);
+
+    // attacklab: Undo double lines (see comment at top of this function)
+    text = text.replace(/\n\n/g,"\n");
+    return text;
+}
+
+var hashElement = function(wholeMatch,m1) {
+    var blockText = m1;
+
+    // Undo double lines
+    blockText = blockText.replace(/\n\n/g,"\n");
+    blockText = blockText.replace(/^\n/,"");
+    
+    // strip trailing blank lines
+    blockText = blockText.replace(/\n+$/g,"");
+    
+    // Replace the element text with a marker ("~KxK" where x is its key)
+    blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";
+    
+    return blockText;
+};
+
+var _RunBlockGamut = function(text) {
+//
+// These are all the transformations that form block-level
+// tags like paragraphs, headers, and list items.
+//
+    text = _DoHeaders(text);
+
+    // Do Horizontal Rules:
+    var key = hashBlock("<hr />");
+    text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
+    text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
+    text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
+
+    text = _DoLists(text);
+    text = _DoCodeBlocks(text);
+    text = _DoBlockQuotes(text);
+
+    // We already ran _HashHTMLBlocks() before, in Markdown(), but that
+    // was to escape raw HTML in the original Markdown source. This time,
+    // we're escaping the markup we've just created, so that we don't wrap
+    // <p> tags around block-level tags.
+    text = _HashHTMLBlocks(text);
+    text = _FormParagraphs(text);
+
+    return text;
+}
+
+
+var _RunSpanGamut = function(text) {
+//
+// These are all the transformations that occur *within* block-level
+// tags like paragraphs, headers, and list items.
+//
+
+    text = _DoCodeSpans(text);
+    text = _EscapeSpecialCharsWithinTagAttributes(text);
+    text = _EncodeBackslashEscapes(text);
+
+    // Process anchor and image tags. Images must come first,
+    // because ![foo][f] looks like an anchor.
+    text = _DoImages(text);
+    text = _DoAnchors(text);
+
+    // Make links out of things like `<http://example.com/>`
+    // Must come after _DoAnchors(), because you can use < and >
+    // delimiters in inline links like [this](<url>).
+    text = _DoAutoLinks(text);
+    text = _EncodeAmpsAndAngles(text);
+    text = _DoItalicsAndBold(text);
+
+    // Do hard breaks:
+    text = text.replace(/  +\n/g," <br />\n");
+
+    return text;
+}
+
+var _EscapeSpecialCharsWithinTagAttributes = function(text) {
+//
+// Within tags -- meaning between < and > -- encode [\ ` * _] so they
+// don't conflict with their use in Markdown for code, italics and strong.
+//
+
+    // Build a regex to find HTML tags and comments.  See Friedl's 
+    // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
+    var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
+
+    text = text.replace(regex, function(wholeMatch) {
+        var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");
+        tag = escapeCharacters(tag,"\\`*_");
+        return tag;
+    });
+
+    return text;
+}
+
+var _DoAnchors = function(text) {
+//
+// Turn Markdown link shortcuts into XHTML <a> tags.
+//
+    //
+    // First, handle reference-style links: [link text] [id]
+    //
+
+    /*
+        text = text.replace(/
+        (                           // wrap whole match in $1
+            \[
+            (
+                (?:
+                    \[[^\]]*\]      // allow brackets nested one level
+                    |
+                    [^\[]           // or anything else
+                )*
+            )
+            \]
+
+            [ ]?                    // one optional space
+            (?:\n[ ]*)?             // one optional newline followed by spaces
+
+            \[
+            (.*?)                   // id = $3
+            \]
+        )()()()()                   // pad remaining backreferences
+        /g,_DoAnchors_callback);
+    */
+    text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);
+
+    //
+    // Next, inline-style links: [link text](url "optional title")
+    //
+
+    /*
+        text = text.replace(/
+            (                       // wrap whole match in $1
+                \[
+                (
+                    (?:
+                        \[[^\]]*\]  // allow brackets nested one level
+                    |
+                    [^\[\]]         // or anything else
+                )
+            )
+            \]
+            \(                      // literal paren
+            [ \t]*
+            ()                      // no id, so leave $3 empty
+            <?(.*?)>?               // href = $4
+            [ \t]*
+            (                       // $5
+                (['"])              // quote char = $6
+                (.*?)               // Title = $7
+                \6                  // matching quote
+                [ \t]*              // ignore any spaces/tabs between closing quote and )
+            )?                      // title is optional
+            \)
+        )
+        /g,writeAnchorTag);
+    */
+    text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);
+
+    //
+    // Last, handle reference-style shortcuts: [link text]
+    // These must come last in case you've also got [link test][1]
+    // or [link test](/foo)
+    //
+
+    /*
+        text = text.replace(/
+        (                           // wrap whole match in $1
+            \[
+            ([^\[\]]+)              // link text = $2; can't contain '[' or ']'
+            \]
+        )()()()()()                 // pad rest of backreferences
+        /g, writeAnchorTag);
+    */
+    text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
+
+    return text;
+}
+
+var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
+    if (m7 == undefined) m7 = "";
+    var whole_match = m1;
+    var link_text   = m2;
+    var link_id  = m3.toLowerCase();
+    var url     = m4;
+    var title   = m7;
+    
+    if (url == "") {
+        if (link_id == "") {
+            // lower-case and turn embedded newlines into spaces
+            link_id = link_text.toLowerCase().replace(/ ?\n/g," ");
+        }
+        url = "#"+link_id;
+        
+        if (g_urls[link_id] != undefined) {
+            url = g_urls[link_id];
+            if (g_titles[link_id] != undefined) {
+                title = g_titles[link_id];
+            }
+        }
+        else {
+            if (whole_match.search(/\(\s*\)$/m)>-1) {
+                // Special case for explicit empty url
+                url = "";
+            } else {
+                return whole_match;
+            }
+        }
+    }   
+    
+    url = escapeCharacters(url,"*_");
+    var result = "<a href=\"" + url + "\"";
+    
+    if (title != "") {
+        title = title.replace(/"/g,"&quot;");
+        title = escapeCharacters(title,"*_");
+        result +=  " title=\"" + title + "\"";
+    }
+    
+    result += ">" + link_text + "</a>";
+    
+    return result;
+}
+
+
+var _DoImages = function(text) {
+//
+// Turn Markdown image shortcuts into <img> tags.
+//
+
+    //
+    // First, handle reference-style labeled images: ![alt text][id]
+    //
+
+    /*
+        text = text.replace(/
+        (                       // wrap whole match in $1
+            !\[
+            (.*?)               // alt text = $2
+            \]
+
+            [ ]?                // one optional space
+            (?:\n[ ]*)?         // one optional newline followed by spaces
+
+            \[
+            (.*?)               // id = $3
+            \]
+        )()()()()               // pad rest of backreferences
+        /g,writeImageTag);
+    */
+    text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);
+
+    //
+    // Next, handle inline images:  ![alt text](url "optional title")
+    // Don't forget: encode * and _
+
+    /*
+        text = text.replace(/
+        (                       // wrap whole match in $1
+            !\[
+            (.*?)               // alt text = $2
+            \]
+            \s?                 // One optional whitespace character
+            \(                  // literal paren
+            [ \t]*
+            ()                  // no id, so leave $3 empty
+            <?(\S+?)>?          // src url = $4
+            [ \t]*
+            (                   // $5
+                (['"])          // quote char = $6
+                (.*?)           // title = $7
+                \6              // matching quote
+                [ \t]*
+            )?                  // title is optional
+        \)
+        )
+        /g,writeImageTag);
+    */
+    text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);
+
+    return text;
+}
+
+var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
+    var whole_match = m1;
+    var alt_text   = m2;
+    var link_id  = m3.toLowerCase();
+    var url     = m4;
+    var title   = m7;
+
+    if (!title) title = "";
+    
+    if (url == "") {
+        if (link_id == "") {
+            // lower-case and turn embedded newlines into spaces
+            link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");
+        }
+        url = "#"+link_id;
+        
+        if (g_urls[link_id] != undefined) {
+            url = g_urls[link_id];
+            if (g_titles[link_id] != undefined) {
+                title = g_titles[link_id];
+            }
+        }
+        else {
+            return whole_match;
+        }
+    }   
+    
+    alt_text = alt_text.replace(/"/g,"&quot;");
+    url = escapeCharacters(url,"*_");
+    var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
+
+    // attacklab: Markdown.pl adds empty title attributes to images.
+    // Replicate this bug.
+
+    //if (title != "") {
+        title = title.replace(/"/g,"&quot;");
+        title = escapeCharacters(title,"*_");
+        result +=  " title=\"" + title + "\"";
+    //}
+    
+    result += " />";
+    
+    return result;
+}
+
+
+var _DoHeaders = function(text) {
+
+    // Setext-style headers:
+    //  Header 1
+    //  ========
+    //  
+    //  Header 2
+    //  --------
+    //
+    text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
+        function(wholeMatch,m1){return hashBlock("<h1>" + _RunSpanGamut(m1) + "</h1>");});
+
+    text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
+        function(matchFound,m1){return hashBlock("<h2>" + _RunSpanGamut(m1) + "</h2>");});
+
+    // atx-style headers:
+    //  # Header 1
+    //  ## Header 2
+    //  ## Header 2 with closing hashes ##
+    //  ...
+    //  ###### Header 6
+    //
+
+    /*
+        text = text.replace(/
+            ^(\#{1,6})              // $1 = string of #'s
+            [ \t]*
+            (.+?)                   // $2 = Header text
+            [ \t]*
+            \#*                     // optional closing #'s (not counted)
+            \n+
+        /gm, function() {...});
+    */
+
+    text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
+        function(wholeMatch,m1,m2) {
+            var h_level = m1.length;
+            return hashBlock("<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">");
+        });
+
+    return text;
+}
+
+// This declaration keeps Dojo compressor from outputting garbage:
+var _ProcessListItems;
+
+var _DoLists = function(text) {
+//
+// Form HTML ordered (numbered) and unordered (bulleted) lists.
+//
+
+    // attacklab: add sentinel to hack around khtml/safari bug:
+    // http://bugs.webkit.org/show_bug.cgi?id=11231
+    text += "~0";
+
+    // Re-usable pattern to match any entirel ul or ol list:
+
+    /*
+        var whole_list = /
+        (                                   // $1 = whole list
+            (                               // $2
+                [ ]{0,3}                    // attacklab: g_tab_width - 1
+                ([*+-]|\d+[.])              // $3 = first list item marker
+                [ \t]+
+            )
+            [^\r]+?
+            (                               // $4
+                ~0                          // sentinel for workaround; should be $
+            |
+                \n{2,}
+                (?=\S)
+                (?!                         // Negative lookahead for another list item marker
+                    [ \t]*
+                    (?:[*+-]|\d+[.])[ \t]+
+                )
+            )
+        )/g
+    */
+    var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
+
+    if (g_list_level) {
+        text = text.replace(whole_list,function(wholeMatch,m1,m2) {
+            var list = m1;
+            var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";
+
+            // Turn double returns into triple returns, so that we can make a
+            // paragraph for the last item in a list, if necessary:
+            list = list.replace(/\n{2,}/g,"\n\n\n");;
+            var result = _ProcessListItems(list);
+    
+            // Trim any trailing whitespace, to put the closing `</$list_type>`
+            // up on the preceding line, to get it past the current stupid
+            // HTML block parser. This is a hack to work around the terrible
+            // hack that is the HTML block parser.
+            result = result.replace(/\s+$/,"");
+            result = "<"+list_type+">" + result + "</"+list_type+">\n";
+            return result;
+        });
+    } else {
+        whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
+        text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {
+            var runup = m1;
+            var list = m2;
+
+            var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";
+            // Turn double returns into triple returns, so that we can make a
+            // paragraph for the last item in a list, if necessary:
+            var list = list.replace(/\n{2,}/g,"\n\n\n");;
+            var result = _ProcessListItems(list);
+            result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n";   
+            return result;
+        });
+    }
+
+    // attacklab: strip sentinel
+    text = text.replace(/~0/,"");
+
+    return text;
+}
+
+_ProcessListItems = function(list_str) {
+//
+//  Process the contents of a single ordered or unordered list, splitting it
+//  into individual list items.
+//
+    // The $g_list_level global keeps track of when we're inside a list.
+    // Each time we enter a list, we increment it; when we leave a list,
+    // we decrement. If it's zero, we're not in a list anymore.
+    //
+    // We do this because when we're not inside a list, we want to treat
+    // something like this:
+    //
+    //    I recommend upgrading to version
+    //    8. Oops, now this line is treated
+    //    as a sub-list.
+    //
+    // As a single paragraph, despite the fact that the second line starts
+    // with a digit-period-space sequence.
+    //
+    // Whereas when we're inside a list (or sub-list), that line will be
+    // treated as the start of a sub-list. What a kludge, huh? This is
+    // an aspect of Markdown's syntax that's hard to parse perfectly
+    // without resorting to mind-reading. Perhaps the solution is to
+    // change the syntax rules such that sub-lists must start with a
+    // starting cardinal number; e.g. "1." or "a.".
+
+    g_list_level++;
+
+    // trim trailing blank lines:
+    list_str = list_str.replace(/\n{2,}$/,"\n");
+
+    // attacklab: add sentinel to emulate \z
+    list_str += "~0";
+
+    /*
+        list_str = list_str.replace(/
+            (\n)?                           // leading line = $1
+            (^[ \t]*)                       // leading whitespace = $2
+            ([*+-]|\d+[.]) [ \t]+           // list marker = $3
+            ([^\r]+?                        // list item text   = $4
+            (\n{1,2}))
+            (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
+        /gm, function(){...});
+    */
+    list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
+        function(wholeMatch,m1,m2,m3,m4){
+            var item = m4;
+            var leading_line = m1;
+            var leading_space = m2;
+
+            if (leading_line || (item.search(/\n{2,}/)>-1)) {
+                item = _RunBlockGamut(_Outdent(item));
+            }
+            else {
+                // Recursion for sub-lists:
+                item = _DoLists(_Outdent(item));
+                item = item.replace(/\n$/,""); // chomp(item)
+                item = _RunSpanGamut(item);
+            }
+
+            return  "<li>" + item + "</li>\n";
+        }
+    );
+
+    // attacklab: strip sentinel
+    list_str = list_str.replace(/~0/g,"");
+
+    g_list_level--;
+    return list_str;
+}
+
+
+var _DoCodeBlocks = function(text) {
+//
+//  Process Markdown `<pre><code>` blocks.
+//  
+
+    /*
+        text = text.replace(text,
+            /(?:\n\n|^)
+            (                               // $1 = the code block -- one or more lines, starting with a space/tab
+                (?:
+                    (?:[ ]{4}|\t)           // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
+                    .*\n+
+                )+
+            )
+            (\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
+        /g,function(){...});
+    */
+
+    // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
+    text += "~0";
+    
+    text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
+        function(wholeMatch,m1,m2) {
+            var codeblock = m1;
+            var nextChar = m2;
+        
+            codeblock = _EncodeCode( _Outdent(codeblock));
+            codeblock = _Detab(codeblock);
+            codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
+            codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
+
+            codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
+
+            return hashBlock(codeblock) + nextChar;
+        }
+    );
+
+    // attacklab: strip sentinel
+    text = text.replace(/~0/,"");
+
+    return text;
+}
+
+var hashBlock = function(text) {
+    text = text.replace(/(^\n+|\n+$)/g,"");
+    return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";
+}
+
+
+var _DoCodeSpans = function(text) {
+//
+//   *  Backtick quotes are used for <code></code> spans.
+// 
+//   *  You can use multiple backticks as the delimiters if you want to
+//   include literal backticks in the code span. So, this input:
+//   
+//       Just type ``foo `bar` baz`` at the prompt.
+//   
+//     Will translate to:
+//   
+//       <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
+//   
+//  There's no arbitrary limit to the number of backticks you
+//  can use as delimters. If you need three consecutive backticks
+//  in your code, use four for delimiters, etc.
+//
+//  *  You can use spaces to get literal backticks at the edges:
+//   
+//       ... type `` `bar` `` ...
+//   
+//     Turns to:
+//   
+//       ... type <code>`bar`</code> ...
+//
+
+    /*
+        text = text.replace(/
+            (^|[^\\])                   // Character before opening ` can't be a backslash
+            (`+)                        // $2 = Opening run of `
+            (                           // $3 = The code block
+                [^\r]*?
+                [^`]                    // attacklab: work around lack of lookbehind
+            )
+            \2                          // Matching closer
+            (?!`)
+        /gm, function(){...});
+    */
+
+    text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
+        function(wholeMatch,m1,m2,m3,m4) {
+            var c = m3;
+            c = c.replace(/^([ \t]*)/g,""); // leading whitespace
+            c = c.replace(/[ \t]*$/g,"");   // trailing whitespace
+            c = _EncodeCode(c);
+            return m1+"<code>"+c+"</code>";
+        });
+
+    return text;
+}
+
+
+var _EncodeCode = function(text) {
+//
+// Encode/escape certain characters inside Markdown code runs.
+// The point is that in code, these characters are literals,
+// and lose their special Markdown meanings.
+//
+    // Encode all ampersands; HTML entities are not
+    // entities within a Markdown code span.
+    text = text.replace(/&/g,"&amp;");
+
+    // Do the angle bracket song and dance:
+    text = text.replace(/</g,"&lt;");
+    text = text.replace(/>/g,"&gt;");
+
+    // Now, escape characters that are magic in Markdown:
+    text = escapeCharacters(text,"\*_{}[]\\",false);
+
+// jj the line above breaks this:
+//---
+
+//* Item
+
+//   1. Subitem
+
+//            special char: *
+//---
+
+    return text;
+}
+
+
+var _DoItalicsAndBold = function(text) {
+
+    // <strong> must go first:
+    text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
+        "<strong>$2</strong>");
+
+    text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
+        "<em>$2</em>");
+
+    return text;
+}
+
+
+var _DoBlockQuotes = function(text) {
+
+    /*
+        text = text.replace(/
+        (                               // Wrap whole match in $1
+            (
+                ^[ \t]*>[ \t]?          // '>' at the start of a line
+                .+\n                    // rest of the first line
+                (.+\n)*                 // subsequent consecutive lines
+                \n*                     // blanks
+            )+
+        )
+        /gm, function(){...});
+    */
+
+    text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
+        function(wholeMatch,m1) {
+            var bq = m1;
+
+            // attacklab: hack around Konqueror 3.5.4 bug:
+            // "----------bug".replace(/^-/g,"") == "bug"
+
+            bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0");   // trim one level of quoting
+
+            // attacklab: clean up hack
+            bq = bq.replace(/~0/g,"");
+
+            bq = bq.replace(/^[ \t]+$/gm,"");       // trim whitespace-only lines
+            bq = _RunBlockGamut(bq);                // recurse
+            
+            bq = bq.replace(/(^|\n)/g,"$1  ");
+            // These leading spaces screw with <pre> content, so we need to fix that:
+            bq = bq.replace(
+                    /(\s*<pre>[^\r]+?<\/pre>)/gm,
+                function(wholeMatch,m1) {
+                    var pre = m1;
+                    // attacklab: hack around Konqueror 3.5.4 bug:
+                    pre = pre.replace(/^  /mg,"~0");
+                    pre = pre.replace(/~0/g,"");
+                    return pre;
+                });
+            
+            return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
+        });
+    return text;
+}
+
+
+var _FormParagraphs = function(text) {
+//
+//  Params:
+//    $text - string to process with html <p> tags
+//
+
+    // Strip leading and trailing lines:
+    text = text.replace(/^\n+/g,"");
+    text = text.replace(/\n+$/g,"");
+
+    var grafs = text.split(/\n{2,}/g);
+    var grafsOut = new Array();
+
+    //
+    // Wrap <p> tags.
+    //
+    var end = grafs.length;
+    for (var i=0; i<end; i++) {
+        var str = grafs[i];
+
+        // if this is an HTML marker, copy it
+        if (str.search(/~K(\d+)K/g) >= 0) {
+            grafsOut.push(str);
+        }
+        else if (str.search(/\S/) >= 0) {
+            str = _RunSpanGamut(str);
+            str = str.replace(/^([ \t]*)/g,"<p>");
+            str += "</p>"
+            grafsOut.push(str);
+        }
+
+    }
+
+    //
+    // Unhashify HTML blocks
+    //
+    end = grafsOut.length;
+    for (var i=0; i<end; i++) {
+        // if this is a marker for an html block...
+        while (grafsOut[i].search(/~K(\d+)K/) >= 0) {
+            var blockText = g_html_blocks[RegExp.$1];
+            blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs
+            grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);
+        }
+    }
+
+    return grafsOut.join("\n\n");
+}
+
+
+var _EncodeAmpsAndAngles = function(text) {
+// Smart processing for ampersands and angle brackets that need to be encoded.
+    
+    // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
+    //   http://bumppo.net/projects/amputator/
+    text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");
+    
+    // Encode naked <'s
+    text = text.replace(/<(?![a-z\/?\$!])/gi,"&lt;");
+    
+    return text;
+}
+
+
+var _EncodeBackslashEscapes = function(text) {
+//
+//   Parameter:  String.
+//   Returns:   The string, with after processing the following backslash
+//             escape sequences.
+//
+
+    // attacklab: The polite way to do this is with the new
+    // escapeCharacters() function:
+    //
+    //  text = escapeCharacters(text,"\\",true);
+    //  text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
+    //
+    // ...but we're sidestepping its use of the (slow) RegExp constructor
+    // as an optimization for Firefox.  This function gets called a LOT.
+
+    text = text.replace(/\\(\\)/g,escapeCharacters_callback);
+    text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);
+    return text;
+}
+
+
+var _DoAutoLinks = function(text) {
+
+    text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");
+
+    // Email addresses: <address@domain.foo>
+
+    /*
+        text = text.replace(/
+            <
+            (?:mailto:)?
+            (
+                [-.\w]+
+                \@
+                [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
+            )
+            >
+        /gi, _DoAutoLinks_callback());
+    */
+    text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
+        function(wholeMatch,m1) {
+            return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
+        }
+    );
+
+    return text;
+}
+
+
+var _EncodeEmailAddress = function(addr) {
+//
+//  Input: an email address, e.g. "foo@example.com"
+//
+//  Output: the email address as a mailto link, with each character
+//  of the address encoded as either a decimal or hex entity, in
+//  the hopes of foiling most address harvesting spam bots. E.g.:
+//
+//  <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
+//     x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
+//     &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
+//
+//  Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
+//  mailing list: <http://tinyurl.com/yu7ue>
+//
+
+    // attacklab: why can't javascript speak hex?
+    function char2hex(ch) {
+        var hexDigits = '0123456789ABCDEF';
+        var dec = ch.charCodeAt(0);
+        return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15));
+    }
+
+    var encode = [
+        function(ch){return "&#"+ch.charCodeAt(0)+";";},
+        function(ch){return "&#x"+char2hex(ch)+";";},
+        function(ch){return ch;}
+    ];
+
+    addr = "mailto:" + addr;
+
+    addr = addr.replace(/./g, function(ch) {
+        if (ch == "@") {
+            // this *must* be encoded. I insist.
+            ch = encode[Math.floor(Math.random()*2)](ch);
+        } else if (ch !=":") {
+            // leave ':' alone (to spot mailto: later)
+            var r = Math.random();
+            // roughly 10% raw, 45% hex, 45% dec
+            ch =  (
+                    r > .9  ?   encode[2](ch)   :
+                    r > .45 ?   encode[1](ch)   :
+                                encode[0](ch)
+                );
+        }
+        return ch;
+    });
+
+    addr = "<a href=\"" + addr + "\">" + addr + "</a>";
+    addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part
+
+    return addr;
+}
+
+
+var _UnescapeSpecialChars = function(text) {
+//
+// Swap back in all the special characters we've hidden.
+//
+    text = text.replace(/~E(\d+)E/g,
+        function(wholeMatch,m1) {
+            var charCodeToReplace = parseInt(m1);
+            return String.fromCharCode(charCodeToReplace);
+        }
+    );
+    return text;
+}
+
+
+var _Outdent = function(text) {
+//
+// Remove one level of line-leading tabs or spaces
+//
+
+    // attacklab: hack around Konqueror 3.5.4 bug:
+    // "----------bug".replace(/^-/g,"") == "bug"
+
+    text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width
+
+    // attacklab: clean up hack
+    text = text.replace(/~0/g,"")
+
+    return text;
+}
+
+var _Detab = function(text) {
+// attacklab: Detab's completely rewritten for speed.
+// In perl we could fix it by anchoring the regexp with \G.
+// In javascript we're less fortunate.
+
+    // expand first n-1 tabs
+    text = text.replace(/\t(?=\t)/g,"    "); // attacklab: g_tab_width
+
+    // replace the nth with two sentinels
+    text = text.replace(/\t/g,"~A~B");
+
+    // use the sentinel to anchor our regex so it doesn't explode
+    text = text.replace(/~B(.+?)~A/g,
+        function(wholeMatch,m1,m2) {
+            var leadingText = m1;
+            var numSpaces = 4 - leadingText.length % 4;  // attacklab: g_tab_width
+
+            // there *must* be a better way to do this:
+            for (var i=0; i<numSpaces; i++) leadingText+=" ";
+
+            return leadingText;
+        }
+    );
+
+    // clean up sentinels
+    text = text.replace(/~A/g,"    ");  // attacklab: g_tab_width
+    text = text.replace(/~B/g,"");
+
+    return text;
+}
+
+
+//
+//  attacklab: Utility functions
+//
+
+
+var escapeCharacters = function(text, charsToEscape, afterBackslash) {
+    // First we have to escape the escape characters so that
+    // we can build a character class out of them
+    var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])";
+
+    if (afterBackslash) {
+        regexString = "\\\\" + regexString;
+    }
+
+    var regex = new RegExp(regexString,"g");
+    text = text.replace(regex,escapeCharacters_callback);
+
+    return text;
+}
+
+
+var escapeCharacters_callback = function(wholeMatch,m1) {
+    var charCodeToEscape = m1.charCodeAt(0);
+    return "~E"+charCodeToEscape+"E";
+}
+
+} // end of Showdown.converter
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.