Index: modules/markdown/markdown.man ================================================================== --- modules/markdown/markdown.man +++ modules/markdown/markdown.man @@ -1,22 +1,53 @@ -[comment {-*- tcl -*- doctools manpage}] -[manpage_begin markdown n 1.0] -[moddesc {Markdown to HTML Converter}] -[titledesc {Converts Markdown text to HTML}] -[category {Text processing}] -[require Tcl 8.5] -[require textutil [opt 0.8]] -[description] - -The package [package Markdown] provides a command to convert -Markdown annotated text into HMTL. - -[list_begin definitions] -[call [cmd ::Markdown::convert] [arg "markdown"]] - -This command takes in a block of Markdown text, and returns a block -of HTML. -[list_end] - -[vset CATEGORY textutil] -[include ../doctools2base/include/feedback.inc] +[vset VERSION 1.1] +[comment {-*- tcl -*- doctools manpage}] +[manpage_begin markdown n [vset VERSION]] +[moddesc {Markdown to HTML Converter}] +[titledesc {Converts Markdown text to HTML}] +[category {Text processing}] +[require Tcl 8.5] +[require Markdown [vset VERSION]] +[require textutil [opt 0.8]] +[description] + +The package [package Markdown] provides a command to convert +Markdown annotated text into HMTL. + +[list_begin definitions] +[call [cmd ::Markdown::convert] [arg "markdown"]] + +This command takes in a block of Markdown text, and returns a block +of HTML. + +[para] The converter supports two types of syntax highlighting for +fenced code blocks: highlighting via a registered converter +(see [cmd ::Markdown::register]), or pure JavaScript highlighting, +e.g. via "highlight.js", where the language specifier used in the +markup is set as CSS class of the "code" element in the returned markup. + +[call [cmd ::Markdown::register] [arg "langspec"] [arg "converter"]] + +Register a language specific converter for prettifying a code block +(e.g. syntax highlighting). Markdown supports fenced code blocks with +an optional language specifier (e.g. "tcl"). When the markdown parser +processes such a code block and a converter for the specified langspec +is registered, the converter is called with the raw code block as +argument. The converter is supposed to return the markup of the code +block as result. The specified converter can be an arbitrary Tcl +command, the raw text block is added as last argument upon invocation. + +[call [cmd ::Markdown::get_lang_counter]] + +Return a dict of language specifier and number of occurrences in +fenced code blocks. This function can be used e.g. to detect, whether +some CSS or JavaScript headers should be included for rendering +without the need of postprocessing the rendered result. + +[call [cmd ::Markdown::reset_lang_counter]] + +Reset the language counters. + +[list_end] + +[vset CATEGORY textutil] +[include ../doctools2base/include/feedback.inc] [manpage_end] Index: modules/markdown/markdown.tcl ================================================================== --- modules/markdown/markdown.tcl +++ modules/markdown/markdown.tcl @@ -53,10 +53,34 @@ array set ::Markdown::_references [collect_references markdown] # PROCESS return [apply_templates markdown] } + + # + # Register a language specific converter. This converter can be + # used for fenced code blocks to transform the code block into a + # prettified HTML. + # + proc register {lang_specifier converter} { + set ::Markdown::converter($lang_specifier) $converter + } + + # + # Return a dict (attribute value pairs) of language specifiers and + # the number of occurrences as they were used in fenced code blocks. + # + proc get_lang_counter {} { + return [array get ::Markdown::lang_counter] + } + + # + # Reset the language counters of fenced code blocks. + # + proc reset_lang_counter {} { + array unset ::Markdown::lang_counter + } ## \private proc collect_references {markdown_var} { upvar $markdown_var markdown @@ -110,11 +134,11 @@ # PROCESS MARKDOWN while {$index < $no_lines} { set line [lindex $lines $index] - switch -regexp $line { + switch -regexp -matchvar matches -- $line { {^\s*$} { # EMPTY LINES if {![regexp {^\s*$} [lindex $lines [expr $index - 1]]]} { append result "\n\n" } @@ -229,19 +253,38 @@ } set code_result [join $code_result \n] append result
 $code_result \n 
} - {^(?:(?:`{3,})|(?:~{3,}))(?:\{?\S+\}?)?\s*$} { + {^(?:(?:`{3,})|(?:~{3,}))\{?(\S+)?\}?\s*$} { # FENCED CODE BLOCKS set code_result {} - if {[string index $line 0] eq {`}} { set end_match {^`{3,}\s*$} } else { set end_match {^~{3,}\s*$} } + # + # A language specifier might be provided + # immediately after the leading delimiters. + # + # ```tcl + # + # The language specifier is used for two purposes: + # a) As a CSS class name + # (useful e.g. for highlight.js) + # b) As a name for a source code to HTML converter. + # When such a converter is registered, + # the codeblock will be sent through this converter. + # + set lang_specifier [string tolower [lindex $matches end]] + if {$lang_specifier ne ""} { + set code_CCS_class " class='$lang_specifier'" + incr ::Markdown::lang_counter($lang_specifier) + } else { + set code_CCS_class "" + } while {$index < $no_lines} { incr index set line [lindex $lines $index] @@ -249,16 +292,30 @@ if {[regexp $end_match $line]} { incr index break } - lappend code_result [html_escape $line] + lappend code_result $line } set code_result [join $code_result \n] - append result
 $code_result 
+ # + # If there is a converter registered, apply it on + # the resulting snippet. + # + if {[info exists ::Markdown::converter($lang_specifier)]} { + set code_result [{*}$::Markdown::converter($lang_specifier) $code_result] + } else { + set code_result [html_escape $code_result] + } + append result \ + "
" \
+			 \
+			$code_result \
+			
} + {^[ ]{0,3}(?:\*|-|\+) |^[ ]{0,3}\d+\. } { # LISTS set list_result {} # continue matching same list type @@ -749,7 +806,7 @@ proc html_escape {text} { return [string map {& & < < > > \" "} $text] } } -package provide Markdown 1.0 +package provide Markdown 1.1 Index: modules/markdown/markdown.test ================================================================== --- modules/markdown/markdown.test +++ modules/markdown/markdown.test @@ -410,10 +410,40 @@
  • asterisk 1

  • } + +test mdtest-1.10 {fenced code block without language specifier} -body { + + convert { + Here comes a generic example: + + ``` + set x 1 + ``` + } +} -result { +

    Here comes a generic example:

    + +
    set x 1
    +} + +test mdtest-1.11 {fenced code block with language specifier} -body { + + convert { + Here comes a Tcl example: + + ```tcl + set x 1 + ``` + } +} -result { +

    Here comes a Tcl example:

    + +
    set x 1
    +} #------------------------------------------------------------------------- # Cleanup testsuiteCleanup Index: modules/markdown/pkgIndex.tcl ================================================================== --- modules/markdown/pkgIndex.tcl +++ modules/markdown/pkgIndex.tcl @@ -1,11 +1,1 @@ -# Tcl package index file, version 1.1 -# This file is generated by the "pkg_mkIndex" command -# and sourced either when an application starts up or -# by a "package unknown" script. It invokes the -# "package ifneeded" command to set up package-related -# information so that packages will be loaded automatically -# in response to "package require" commands. When this -# script is sourced, the variable $dir must contain the -# full path name of this file's directory. - -package ifneeded Markdown 1.0 [list source [file join $dir markdown.tcl]] +package ifneeded Markdown 1.1 [list source [file join $dir markdown.tcl]]