#!/usr/bin/env tclsh
# pandoc-tcl-filter - standalone application and pandoc filter
# for literate programming
# Author: Detlef Groth, Schwielowsee, Germany
# Version: 0.8.1 - 2023-01-22
package provide pandoc 0.8.2
if {[llength $argv] > 0 && ([lsearch -exact $argv -v] >= 0 || [lsearch -exact $argv --version] >= 0)} {
puts "[package present pandoc]"
exit 0
}
if {[llength $argv] > 0 && [lsearch -regex $argv -h] >= 0} {
puts "Usage (filter): pandoc \[arguments\] --filter $argv0 \[arguments\]"
puts " This is the pandoc Tcl filter which should be run as filter"
puts " for the pandoc document converter with a syntax like shown above."
puts " This filter allows you to embed Tcl code within"
puts " ```{.tcl} ... ``` code blocks."
puts " For a list of other filters which are available see below."
puts "Version: [package present pandoc]"
puts "Homepage: https://github.com/mittelmark/DGTcl"
puts "Readme: http://htmlpreview.github.io/?https://github.com/mittelmark/DGTcl/blob/master/pandoc-tcl-filter/Readme.html"
puts "Filters: "
puts " - ```{.tcl} Tcl code```"
puts " - ```{.abc} ABC music notation code```"
puts " - ```{.cmd} Command line application code```"
puts " - ```{.dot} GraphViz dot/neato code```"
puts " - ```{.eqn} EQN equations```"
puts " - ```{.mmd} Mermaid diagram code```"
puts " - ```{.mtex} LaTeX equations```"
puts " - ```{.pic} PIC diagram code```"
puts " - ```{.pik} Pikchr diagram code```"
puts " - ```{.pipe} Embed Python, R or Octave code```"
puts " - ```{.puml} PlantUML diagram code```"
puts " - ```{.rplot} R plot code```"
puts " - ```{.sqlite} SQLite3 code code```\n"
puts " - ```{.tcrd} Songs with embedded chords```\n"
puts " - ```{.tdot} Tcl package tdot code```\n"
puts " - ```{.tsvg} Tcl package tsvg code```\n"
puts "Usage (standalone): $argv0 infile outfile"
puts " converting infile to outfile"
puts " if infile is a source code file like .tcl .py"
puts " it is assumed that it contains mkdoc documentation"
puts " mkdoc documentation is embedded Markdown markup after a #' comment"
puts " in case pandoc is installed the pandoc-tcl-filter will be used after wards"
puts " $argv0 --help - display this help page"
puts " $argv0 --version - display the version"
puts " $argv0 infile --tangle .tcl - extract all code from .tcl chunks"
puts "\nUsage (GUI) : $argv0 --gui \[infile]"
puts " supported infiles: abc, dot, eqn, mmd, mtex, pic, pik, puml, rplot, tdot, tsvg"
puts "Example: "
puts " ./pandoc-tcl-filter.tcl pandoc-tcl-filter.tcl pandoc-tcl-filter.html -s --css mini.css"
puts " will extract the documentation from itself and create a HTML file executing all filters available"
puts " all pandoc options can be passed after the output file name"
puts "Author: Detlef Groth, University of Potsdam, Germany"
exit 0
}
if {[llength $argv] > 1 && [lsearch -regex $argv -tangle] > -1} {
if {[file exists [lindex $argv 0]]} {
set filename [lindex $argv 0]
if {[llength $argv] == 3} {
set mode [lindex $argv 2]
} else {
set mode .tcl
}
if [catch {open $filename r} infh] {
puts stderr "Cannot open $filename: $infh"
exit
} else {
set flag false
while {[gets $infh line] >= 0} {
if {[regexp "^\[> \]\{0,2\}``` {0,2}\{$mode" $line]} {
set l [regsub {.+```} $line ""]
puts stdout "# $l"
set flag true
} elseif {$flag && [regexp "^\[> \]\{0,2\}```" $line]} {
set flag false
} elseif {[regexp "^\s*#' \[> \]\{0,2\}``` {0,2}\{$mode" $line]} {
set l [regsub {.+```} $line ""]
puts stdout "# $l"
set flag true
} elseif {$flag && [regexp "^\s*#' \[> \]\{0,2\}```" $line]} {
set flag false
} elseif {$flag} {
set line [regsub {^\s*#' } $line ""]
puts stdout $line
}
}
close $infh
}
}
return
}
set css {
html {
overflow-y: scroll;
}
body {
color: #444;
font-family: Georgia, Palatino, 'Palatino Linotype', Times, 'Times New Roman', serif;
line-height: 1.2;
padding: 1em;
margin: auto;
max-width: 900px;
}
h1, h2, h3, h4, h5, h6 {
color: #111;
line-height: 115%;
margin-top: 1em;
font-weight: normal;
}
h1 {
text-align: center;
font-size: 120%;
}
h2.author, h2.date {
text-align: center;
font-size: 110%;
}
a {
color: #0645ad;
text-decoration: none;
}
a:visited { color: #0b0080; }
a:hover { color: #06e; }
a:active { color: #faa700; }
a:focus { outline: thin dotted; }
p { margin: 0.5em 0; }
p.author, p.date {
font-size: 110%;
text-align: center;
}
img { max-width: 100%; }
figure { text-align: center ; }
pre, blockquote pre {
border-top: 0.1em #9ac solid;
background: #e9f6ff;
padding: 10px;
border-bottom: 0.1em #9ac solid;
}
pre, code, kbd, samp {
color: #000;
font-family: Monaco, 'courier new', monospace;
font-size: 90%;
}
pre code.tclinn {
color: #ff2222;
}
pre code.tclout {
color: #3366ff;
}
code.r {
color: #770000;
}
pre {
white-space: pre;
white-space: pre-wrap;
word-wrap: break-word;
}
code span.kw { color: #007020; font-weight: normal; }
pre.sourceCode { background: #fff6f6; }
blockquote {
margin: 0;
padding-left: 3em;
}
hr {
display: block;
height: 2px;
border: 0;
border-top: 1px solid #aaa;
border-bottom: 1px solid #eee;
margin: 1em 0;
padding: 0;
}
table {
border-collapse: collapse;
border-bottom: 2px solid;
}
table thead tr th {
background-color: #fde9d9;
text-align: left;
padding: 10px;
border-top: 2px solid;
border-bottom: 2px solid;
}
table td {
background-color: #fff9e9;
text-align: left;
padding: 10px;
}
}
# some helper functions
# some generic helper functions
proc luniq {L} {
# removes duplicates without sorting the input list
set t {}
foreach i $L {if {[lsearch -exact $t $i]==-1} {lappend t $i}}
return $t
}
# allow loading Tcl packages and filters
set appdir [file dirname [info script]]
if {[file exists [file join $appdir lib]]} {
lappend auto_path [file normalize [file join $appdir lib]]
package require tsvg
}
# interp create mdi
#
# mdi eval "set auto_path \[list [luniq $auto_path]\]"
catch {
# if available load filters
package require tclfilters
}
# load other tcl based filters
foreach file [glob -nocomplain [file join [file dirname [info script]] filter filter*.tcl]] {
catch {
source $file
}
}
proc debug {jsonData} {
puts [::rl_json::json keys $jsonData]
}
proc lineFilter {argv} {
if {[info exists ::env(FILTEREVAL)]} {
set evalvar $::env(FILTEREVAL)
} else {
set evalvar true
}
set args [split $argv " "]
set infile [lindex $args 0]
set outfile [lindex $args 1]
set mode md
if {![regexp {.+\.[a-zA-Z]*md$} $outfile] && ![regexp -nocase {.+\.html} $outfile]} {
puts "Error: currently only conversion from Markdown to Markdown or to HTML is possible!"
exit 0
}
if {[regexp -nocase {.+\.html} $outfile]} {
set mode html
set outfile [regsub {\.html} $outfile "-out.md"]
}
if {![file exists $infile]} {
puts "Error: $infile does not exists"
exit 0
}
if [catch {open $infile r} infh] {
puts stderr "Cannot open $infile: $infh"
exit
} else {
set out [open $outfile w 0600]
set i 0
set n 0
set flag false
set yamlflag false
set yamltext ""
set filt "xxx"
set ind ""
set ddef [dict create echo true results show eval $evalvar]
set yamldict [dict create]
set pre false
while {[gets $infh line] >= 0} {
set line [regsub {```tcl} $line {```.tcl}]
incr n
# TODO: simple YAML parsing
if {$n < 5 && !$yamlflag && [regexp {^---} $line]} {
set yamlflag true
} elseif {$yamlflag && [regexp {^---} $line]} {
set yamldict [yaml::yaml2dict $yamltext]
set yamlflag false
set yamltext ""
} elseif {$yamlflag} {
append yamltext "$line\n"
}
# TODO: indentation parsing "> ```"
if {[regexp {^>? ?\s{0,2}```} $line]} {
if {$pre} {
set pre false
} else {
set pre true
}
}
if {[regexp {^>? ?\s{0,2}```\{\.} $line]} {
set dchunk [dict create]
set dchunk [dict merge $ddef $dchunk]
set ind ""
if {[regexp {^> } $line]} {
set ind "> "
}
regexp {```\{\.([a-zA-Z0-9]+)\s*(.*).*\}.*} $line -> filt options
if {[dict exists $yamldict $filt]} {
set dchunk [dict merge $dchunk [dict get $yamldict $filt]]
}
foreach {op} [split $options " "] {
foreach {key val} [split $op "="] {
set val [regsub -all {"} $val ""] ;#"
dict set dchunk $key $val
}
}
set flag true
set cont ""
} elseif {$flag && [regexp {^>? ?\s{0,2}```} $line]} {
set flag false
if {[info procs filter-$filt] ne ""} {
puts "processing chunk filter[incr i] $filt $options"
set res [filter-$filt $cont $dchunk]
if {[dict get $dchunk echo]} {
# TODO: indentation adding if was there"
puts $out "$ind```${filt}inn\n$cont\n$ind```"
}
if {[lindex $res 0] ne ""} {
if {[dict get $dchunk results] eq "show"} {
# TODO: indentation adding if was there"
# remove trailing newline as we add our own
set r [regsub {\n$} [lindex $res 0] ""]
puts $out "\n$ind```${filt}out\n$r\n$ind```"
} elseif {[dict get $dchunk results] eq "asis"} {
puts $out "\n[lindex $res 0]\n"
}
}
if {[lindex $res 1] ne ""} {
set title ""
if {[dict exists $dchunk title]} {
set title [dict get $dchunk title]
}
puts $out "\n!\[$title\]([lindex $res 1])"
}
}
set cont ""
} elseif {$flag} {
append cont "$line\n"
} else {
# TODO: more than one inline code per line
if {!$pre} {
if {[regexp {`\.?[a-z]{2,4} .+`} $line]} {
set filt [regsub {.*`\.?([a-z]{2,4}) .+`.+} $line "\\1"]
if {[info procs filter-$filt] eq "filter-$filt"} {
set code [regsub {.*`\.?[a-z]{2,4} ([^`]+)`.+} $line "\\1"]
puts "processing inline code $code"
set res [lindex [filter-$filt $code [dict create eval $evalvar results show echo false]] 0]
set line [regsub {(.*)`\.?[a-z]{2,4} ([^`]+)`(.+)} $line "\\1$res\\3"]
}
}
}
puts $out $line
}
}
close $infh
}
close $out
if {$mode eq "html"} {
mkdoc::mkdoc $outfile [regsub -- {-out.md} $outfile ".html"] {*}[lrange $argv 2 end]
}
}
# Gui mode
if {[llength $argv] > 0 && [lsearch $argv --gui] > -1} {
package require fview
fview::gui
set file false
foreach arg $argv {
if {[file exists $arg]} {
set ext [string range [file extension $arg] 1 end]
#puts $ext
fview::fileOpen $arg
fview::fileSave $arg
set file true
break
}
}
if {!$file} {
set ftemp [file temp].tsvg
set out [open $ftemp w 0600]
puts $out {package require tsvg
tsvg set code "" ;
tsvg set width 400
tsvg set height 400
tsvg rect -x 10 -y 10 -width 380 -height 380 \
-fill cornsilk
tsvg circle -cx 200 -cy 200 -r 120 -stroke black -stroke-width 2 -fill #eeffee
tsvg text -x 155 -y 180 "Hello TSVG"
tsvg text -x 135 -y 220 "Filter View World!"
Hello
}
close $out
fview::fileOpen $ftemp
fview::fileSave $ftemp
file delete $ftemp
}
return
}
# Standalone processing
# calling pandoc eventually itself
if {[llength $argv] > 1 && [file exists [lindex $argv 0]]} {
set pandoc true
if {[lsearch $argv --no-pandoc] > 1} {
package require yaml
package require mkdoc::mkdoc
set pandoc false
} elseif {[auto_execok pandoc] eq ""} {
puts "Error: Document conversion needs pandoc installed"
exit 0
}
if {[file extension [lindex $argv 1]] eq ".html" && [lsearch [lrange $argv 1 end] --css] == -1} {
if {![file exists pandoc-filter.css]} {
set out [open pandoc-filter.css w 0600]
puts $out $css
close $out
}
lappend argv --css
lappend argv pandoc-filter.css
}
if {[file extension [lindex $argv 0]] in [list .tcl .tm .py .R .r .c .cxx .cpp .m .pl .pm .h .hpp .hxx]} {
set tempfile [file tempfile].md
set filename [lindex $argv 0]
set infile [lindex $argv 0]
set out [open $tempfile w 0600]
if [catch {open $filename r} infh] {
puts stderr "Cannot open $filename: $infh"
exit
} else {
while {[gets $infh line] >= 0} {
if {[regexp {^\s*#' ?} $line]} {
set line [regsub {^\s*#' ?} $line ""]
puts $out $line
}
}
close $infh
}
close $out
if {$pandoc} {
exec pandoc $tempfile --filter $argv0 -o {*}[lrange $argv 1 end]
} else {
set argv [lset argv 0 $tempfile]
lineFilter $argv
}
file delete $tempfile
puts "converting $infile to [lindex $argv 1] done"
exit 0
} else {
if {$pandoc} {
exec pandoc [lindex $argv 0] --filter $argv0 -o {*}[lrange $argv 1 end]
} else {
lineFilter $argv
}
puts "converting [lindex $argv 0] to [lindex $argv 1] done"
}
exit 0
}
package require rl_json
#' ---
#' title: pandoc-tcl-filter documentation - 0.7.0
#' author: Detlef Groth, Schwielowsee, Germany
#' date: 2022-01-22
#' tcl:
#' echo: "true"
#' results: show
#' ---
#'
#' ------
#'
#' ```{.tcl results="asis" echo=false}
#' include header.md
#' ```
#' ------
#'
#' ## NAME
#'
#' _pandoc-tcl-filter.tcl_ - filter application for the pandoc command line
#' application to convert Markdown files into other formats. The filter allows you to embed Tcl code into your Markdown
#' documentation and offers a plugin architecture to add other command line filters easily using Tcl
#' and the `exec` command. As examples are given in the filter folder of the project:
#'
#' * Tcl filter {.tcl}: `filter-tcl.tcl` [filter/filter-tcl.html](filter/filter-tcl.html)
#' * ABC music filter {.abc}: `filter-abc.tcl` [filter/filter-abc.html](filter/filter-abc.html)
#' * command line application filter {.cmd}: `filter-cmd.tcl` [filter/filter-abc.html](filter/filter-cmd.html)
#' * Graphviz dot filter {.dot}: `filter-dot.tcl` [filter/filter-dot.html](filter/filter-dot.html)
#' * EQN filter plugin for equations written in the EQN language {.eqn}: `filter-eqn` [filter/filter-eqn.html](filter/filter-eqn.html)
#' * Math TeX filter for single line equations {.mtex}: `filter-mtex.tcl` [filter/filter-mtex.html](filter/filter-mtex.html)
#' * Mermaid filter for diagrams {.mmd}: `filter-mmd.tcl` [filter/filter-mmd.html](filter/filter-mmd.html)
#' * Pikchr filter plugin for diagram creation {.pikchr}: `filter-pik.tcl` [filter/filter-pik.html](filter/filter-pik.html)
#' * PIC filter plugin for diagram creation (older version) {.pic}: `filter-pic.tcl` [filter/filter-pic.html](filter/filter-pic.html)
#' * pipe filter for R, Python and Octave {.pipe}: `filter-pipe.tcl` [filter/filter-pipe.html](filter/filter-pipe.html)
#' * PlantUMLfilter plugin for diagram creation {.puml}: `filter-puml.tcl` [filter/filter-puml.html](filter/filter-puml.html)
#' * R plot filter plugin for displaying plots in the R statistical language {.rplot}: `filter-rplot.tcl` [filter/filter-rplot.html](filter/filter-rplot.html)
#' * sqlite3 filter plugin to evaluate SQL code {.sqlite}: `filter-sqlite.tcl` [filter/filter-sqlite.html](filter/filter-sqlite.html)
#' * tcrd filter for music songs with chords {.tcrd}: `filter-tcrd.tcl` [filter/filter-tcrd.html](filter/filter-tcrd.html)
#' * tdot package filter {.tsvg}: `filter-tdot.tcl` [filter/filter-tdot.html](filter/filter-tdot.html)
#' * tsvg package filter {.tsvg}: `filter-tsvg.tcl` [filter/filter-tsvg.html](filter/filter-tsvg.html)
#'
#' ## SYNOPSIS
#'
#' ```
#' # standalone application
#' pandoc-tcl-filter.tapp infile outfile ?options?
#' # as filter
#' pandoc infile --filter pandoc-tcl-filter.tapp ?options?
#' # as graphics user interface
#' pandoc-tcl-filter.tapp --gui
#' ```
#'
#' Where options for the filter and the standalone mode are the usual pandoc options.
#' For HTML conversion you should use for instance:
#'
#' ```
#' pandoc-tcl-filter.tapp infile.md outfile.html --css style.css -s --toc
#' ```
#'
#' ## Code embedding
#' Embed code either inline or in form of code chunks like here (triple ticks):
#'
#' ```
#' ```{.tcl}
#' set x 4
#' incr x
#' set x
#' ```
#'
#' Hello this is Tcl `tcl package provide Tcl`!
#' ```
#'
#' ## Filter Overview
#'
#' The markers for the other filters are:
#'
#' `{.abc}, `{.dot}`, `{.eqn}`, `{.mmd}`, `{.mtex}`, `{.pic}`,
#' `{.pikchr}, `{.puml}`, `{.rplot}`,`{.sqlite}` and `{.tsvg}`.
#'
#' For details on how to use them have a look at the manual page links on top.
#'
#' You can combine all filters in one document just by using the appropiate markers.
#'
#' Here an overview about the required tools to use a filter:
#'
#'
#'
#' | filter | tool | svg | png | pdf | comment |
#' | ------ | ----- | ---- | ---- | ---- | ---- |
#' | .tcl | tclsh | tsvg | cairosvg | cairosvg | programming |
#' | .abc | abcm2ps | abcm2ps | cairosvg | cairosvg | music |
#' | .dot | dot | native | native | native | diagrams |
#' | .eqn | eqn2graph | no | convert | no | math |
#' | .mmd | mermaid-cli (mmdc) | native | native | native | diagrams |
#' | .mtex | latex | dvisgm | dvipng | dvipdfm | math, diagrams, games |
#' | .pic | pic2graph | no | convert | no | diagrams |
#' | .pik | fossil | native | cairosvg | cairosvg | diagrams |
#' | .pipe | R / python / octave | native | native | native | Statistics, Programming |
#' | .puml | plantuml | native | native | native | diagrams |
#' | .rplot | R | native | native | native | statistics, graphics |
#' | .tcrd | tclsh | no | no | no | music, songs with chords |
#' | .tdot | tclsh/dot | native | native | native | diagrams |
#' | .tsvg | tclsh | native | cairosvg | cairosvg | graphics |
#'
#'
#'
#' The Markdown document within this file could be extracted and converted as follows:
#'
#' ```
#' pandoc-tcl-filter.tapp pandoc-tcl-filter.tcl pandoc-tcl-filter.html \
#' --css mini.css -s
#' ```
#'
#' ## Example Tcl Filter
#'
#' #### Tcl-filter
#'
#' ```
#' ```{.tcl}
#' set x 1
#' puts $x
#' ```
#' ```
#'
#' And here the output:
#'
#' ```{.tcl}
#' set x 1
#' puts $x
#' ```
#'
#' Does indented code blocks works as well?
#'
#' > ```{.tcl}
#' set x 2
#' puts $x
#' > ```
#'
#' > Yes, since version 0.7.0!!
#'
#' There is as well the possibility to inline Tcl code like here:
#'
#' ```
#' This document was processed using Tcl `tcl set tcl_patchLevel`!
#' ```
#'
#' will produce:
#'
#' This document was processed using Tcl `tcl set tcl_patchLevel`!
#'
#' This works as well in nested structures like lists or quotes.
#'
#' > This document was processed using Tcl `tcl set tcl_patchLevel`!
#'
#' > - This document was processed using Tcl `tcl set tcl_patchLevel`!
#'
#' ## Filter - Plugins
#'
#' The pandoc-tcl-filter.tcl application allows to create custom filters for other
#' command line application quite easily. The Tcl files has to be named `filter-NAME.tcl`
#' where NAME hast to match the code chunk marker. Below an example:
#'
#' ```
#' ` ``{.dot label=dotgraph}
#' digraph G {
#' main -> parse -> execute;
#' main -> init;
#' main -> cleanup;
#' execute -> make_string;
#' execute -> printf
#' init -> make_string;
#' main -> printf;
#' execute -> compare;
#' }
#'
#' ![](dotgraph.svg)
#' ` ``
#' ```
#'
#' The main script `pandoc-tcl-filter.tcl` evaluates if in the same folder as the script is,
#' if there any other files named `filter/filter-NAME.tcl` and sources them. In case of the dot
#' filter the file is named `filter-dot.tcl` and its filter function `filter-dot` is
#' executed. Below is the simplified code: of this file `filter-dot.tcl`:
#'
#' ```{.tcl eval=false results="hide"}
#' # a simple pandoc filter using Tcl
#' # the script pandoc-tcl-filter.tcl
#' # must be in the same filter directoy of the pandoc-tcl-filter.tcl file
#' proc filter-dot {cont dict} {
#' global n
#' incr n
#' set def [dict create app dot results show eval true fig true
#' label null ext svg width 400 height 400 \
#' include true imagepath images]
#' # fuse code chunk options with defaults
#' set dict [dict merge $def $dict]
#' set ret ""
#' if {[dict get $dict label] eq "null"} {
#' set fname dot-$n
#' } else {
#' set fname [dict get $dict label]
#' }
#' # save dot file
#' set out [open $fname.dot w 0600]
#' puts $out $cont
#' close $out
#' # TODO: error catching
#' set res [exec [dict get $dict app] -Tsvg $fname.dot -o $fname.svg]
#' if {[dict get $dict results] eq "show"} {
#' # should be usually empty
#' set res $res
#' } else {
#' set res ""
#' }
#' set img ""
#' if {[dict get $dict fig]} {
#' if {[dict get $dict include]} {
#' set img $fname.svg
#' }
#' }
#' return [list $res $img]
#' }
#' ```
#'
#' Using the label and the option `include=false` we could create an image link manually using Markdown syntax. The
#' The image filename should be then images/label.svg for instance.
#'
#' ## Dot Example
#'
#' Here a longer dot-example where the code is include in
#'
#' ```{.dot}
#' digraph G {
#' margin=0.1;
#' node[fontname="Linux Libertine";fontsize=18];
#' node[shape=box,style=filled;fillcolor=skyblue,width=1.2,height=0.9];
#' { rank=same; Rst[group=g0,fillcolor=salmon] ;
#' Docx [group=g1,fillcolor=salmon]
#' }
#' { rank=same; Md[group=g0,fillcolor=salmon] ;
#' pandoc ; AST1 ; filter[fillcolor=cornsilk] ; AST2 ; pandoc2;
#' Html[group=g1,fillcolor=salmon]
#' }
#' { rank=same; Tex[group=g0,fillcolor=salmon] ;
#' Pdf[group=g1,fillcolor=salmon]; filters[fillcolor=cornsilk];
#' }
#' node[fillcolor=cornsilk];
#' { rank=same; dot ; eqn; mtex; pic; pik; rplot; tsvg;}
#' Rst -> pandoc -> AST1 -> filter -> AST2 -> pandoc2 -> Html ;
#' Md -> pandoc;
#' Tex -> pandoc;
#' Rst -> Md -> Tex -> dot[style=invis] ;
#' pandoc2 -> Docx;
#' pandoc2 -> Pdf ;
#' Docx -> Html -> Pdf -> tsvg[style=invis];
#' pandoc2[label=pandoc];
#' filter[label="pandoc-\ntcl-\nfilter"];
#' filter->filters;
#' filters -> dot ;
#' filters -> eqn ;
#' filters -> mtex;
#' filters -> pic ;
#' filters -> pik ;
#' filters -> rplot;
#' filters -> tsvg;
#' }
#' ```
#'
#' ## Creating Markdown Code
#'
#' Since version 0.5.0 it is as well possible to create Markup code within code blocks and to return it.
#' To achieve this you to set use code chunk option results like this: `results="asis"` -
#' which should be usually used together with `echo=false`. Here an example:
#'
#' ```
#' ```{.tcl echo=false results="asis"}
#' return "**this is bold** and _this is italic_ text!"
#' ```
#' ```
#'
#' which gives this output:
#'
#' ```{.tcl echo=false results="asis"}
#' return "**this is bold** and _this is italic_ text!"
#'
#' ```
#'
#' This can be as well used to include other Markup files. Here an example:
#'
#' ```
#' ```{.tcl results="asis"}
#' include tests/inc.md
#' ```
#' ```
#'
#' And here is the result:
#'
#' ```{.tcl results="asis"}
#' include tests/inc.md
#' ```
#'
#' Please note, that currently no filters are applied on the included files.
#' You should process them before using the pandoc filters and choose output format Markdown to include them later
#' in your master document.
#'
#' To just show some file content as it is, remove the results="asis",
#' this can be as well useful to display some source code, let's here simply show here the content of *tests/inc.md* without interpreting it as Markdown in a source code block:
#'
#' ```
#' ```{.tcl results="show"}
#' include tests/inc.md
#' ```
#' ```
#'
#' And here is the result:
#'
#' ```{.tcl results="show"}
#' include tests/inc.md
#' ```
#'
#' ## Documentation
#'
#' To use this pipeline and to create pandoc-tcl-filter.html out of the code documentation
#' in pandoc-tcl-filter.tapp your command in the terminal is still just:
#'
#' ```
#' pandoc-tcl-filter.tapp pandoc-tcl-filter.tcl pandoc-tcl-filter.html -s --css mini.css
#' ```
#'
#' The result should be the file which you are looking currently.
#'
#' ## ChangeLog
#'
#' * 2021-08-22 Version 0.1
#' * 2021-08-28 Version 0.2
#' * adding custom filters structure (dot, tsvg examples)
#' * adding attributes label, width, height, results
#' * 2021-08-31 Version 0.3
#' * moved filters into filter folder
#' * plugin example mtex
#' * default image path _images_
#' * 2021-11-03 Version 0.3.1
#' * fix for parray and "puts stdout"
#' * 2021-11-15 Version 0.3.2
#' * --help argument support
#' * --version argument support
#' * filters for Pikchr, PIC and EQN
#' * 2021-11-30 Version 0.3.3
#' * filter for R plots: `.rplot`
#' * 2021-12-04 Version 0.4.0
#' * pandoc-tcl-filter can be as well directly used for conversion
#' being then a frontend which calls pandoc internally with
#' itself as a filter ...
#' * 2021-12-12 Version 0.5.0
#' * support for Markdown code creation in the Tcl filter with results="asis"
#' * adding list2mdtab proc to the Tcl filter
#' * adding include proc to the Tcl filter with results='asis' other Markdown files can be included.
#' * support for `pandoc-tcl-filter.tcl infile --tangle .tcl` to extract code chunks to the terminal
#' * support for Mermaid diagrams
#' * support for PlantUML diagrams
#' * support for ABC music notation
#' * bug fix for Tcl filters for `eval=false`
#' * documentation improvements for the filters and for the pandoc-tcl-filter
#' * 2022-01-09 - version 0.6.0
#' * adding filter-cmd.tcl for shell scripts for all type of programming languages and tools
#' * filter-mtex.tcl with more examples for different LaTeX packages like tikz, pgfplot, skak, sudoku, etc.
#' * adding filter-tdot.tcl for tdot Tcl package
#' * adding filter-tcrd.tcl for writing music chords above song lyrics
#' * 2022-02-05 - version 0.7.0
#' * graphical user interface to the graphical filters (abc, dot, eqn, mmd, mtex, pic, pikchr, puml, rplot, tdot, tsvg) using the command line option *--gui*
#' * can now as well work without pandoc standalone for conversion of Markdown with code chunks into
#' Markdown with evaluated code chunks and HTML code using the
#' Markdown library of tcllib
#' * that way it deprecates the use of tmdoc::tmdoc and mkdoc::mkdoc as it contains now the same functionality
#' * support for inline code evaluations for Tcl, Python (pipe filter) and R (pipe filter) statements as well in nested paragraphs, lists and headers
#' * support for indented code blocks with evaluation
#' * new - filter-pipe:
#' * initial support for R code block features and inline evaluation and error catching
#' * initial support for Python with code block features and inline evaluation and error catching
#' * initial support for Octave with code block features and error checking
#' * more examples filter-mtex, filter-puml, filter-pikchr
#' * fix for filter-tcl making variables chunk and res namespace variables, avoiding variable collisions
#'
#' ## SEE ALSO
#'
#' * [Readme.html](Readme.html) - more information and small tutorial
#' * [Examples](examples/example-eqn.html) - more examples for the filters
#' * [Tclers Wiki page](https://wiki.tcl-lang.org/page/pandoc%2Dtcl%2Dfilter) - place for discussion
#' * [Pandoc filter documentation](https://pandoc.org/filters.html) - more background and information on how to implement filters in Haskell and Markdown
#' * [Lua filters on GitHub](https://github.com/pandoc/lua-filters)
#' * [Plotting filters on Github](https://github.com/LaurentRDC/pandoc-plot)
#' * [Github Pandoc filter list](https://github.com/topics/pandoc-filter)
#'
#' ## AUTHOR
#'
#' Dr. Detlef Groth, Caputh-Schwielowsee, Germany, detlef(_at_)dgroth(_dot_).de
#'
#' ## LICENSE
#'
#' *MIT License*
#'
#' Copyright (c) 2021-2023 Dr. Detlef Groth, Caputh-Schwielowsee, Germany
#'
#' Permission is hereby granted, free of charge, to any person obtaining a copy
#' of this software and associated documentation files (the "Software"), to deal
#' in the Software without restriction, including without limitation the rights
#' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#' copies of the Software, and to permit persons to whom the Software is
#' furnished to do so, subject to the following conditions:
#'
#' The above copyright notice and this permission notice shall be included in all
#' copies or substantial portions of the Software.
#'
#' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#' SOFTWARE.
#'
## Actual filter code for Tcl and infratstucture to add other filters by writing
## proc filter-NAME function in a file filter/filter-NAME.tcl
## Global variables
set n 0
set jsonData {}
while {[gets stdin line] > 0} {
append jsonData $line
}
# parse Meta data
proc getMetaDict {meta fkey} {
set d [dict create]
if {[rl_json::json exists $meta $fkey c]} {
foreach key [rl_json::json keys $meta $fkey c] {
dict set d $key [rl_json::json get $meta $fkey c $key c 0 c]
}
}
return $d
}
# walk and search for inlineCodes
set inlineCodes [list]
proc walk {key {ind 1}} {
global jsonData
global inlineCodes
#puts "key: {*}$key"
set sind [string repeat " " [expr {$ind*4}]]
set type [::rl_json::json type $jsonData blocks {*}$key]
set l 0
if {$type eq "array"} {
set l [llength [rl_json::json get $jsonData blocks {*}$key]]
} elseif {$type eq "object"} {
set l [llength [rl_json::json get $jsonData blocks {*}$key]]
}
#puts "$sind type: $type length $l"
#puts "$sind cnt: [::rl_json::json get $jsonData blocks {*}$key]"
incr ind
if {$type eq "array"} {
for {set j 0} {$j < $l} {incr j} {
set nkey $key
lappend nkey $j
walk $nkey $ind
}
}
if {$type eq "object"} {
set tkey $key
set ckey $key
if {$l > 2} {
lappend tkey t
lappend ckey c
if {[::rl_json::json exists $jsonData blocks {*}$tkey]} {
set t [::rl_json::json get $jsonData blocks {*}$tkey]
if {$t eq "Code"} {
lappend ckey 1
#lappend ckey
set code [::rl_json::json get $jsonData blocks {*}$ckey]
#puts "$sind code: $code"
if {[regexp {^[a-zA-Z0-9]{1,3} .+} $code]} {
lappend inlineCodes [list $ckey $code]
}
}
} else {
walk $ckey $ind
}
}
}
return
}
proc codeBlock {} {
uplevel 1 {
set type [rl_json::json get $jsonData blocks {*}$tkey] ;#type
set attr [rl_json::json get $jsonData blocks {*}$akey] ;# attributes
set a [dict create echo true results show eval $evalvar]
set d [getMetaDict $meta $type]
set a [dict merge $a $d]
#puts stderr $a
if {[llength $attr] > 0} {
foreach el $attr {
dict set a [lindex $el 0] [lindex $el 1]
}
#puts [dict keys $a]
}
set cont [rl_json::json get $jsonData blocks {*}$ckey]
set cblock "[::rl_json::json extract $jsonData blocks {*}$bkey]"
if {[dict get $a echo]} {
append blocks "[::rl_json::json extract $jsonData blocks $i]\n"
} else {
#rl_json::json unset jsonData blocks $i
# add an empty paragraph instead
append blocks {{"t":"Para","c":[{"t":"Str","c":""}]}}
#append blocks [::rl_json::json extract $jsonData blocks $i]\n"
}
if {$type ne ""} {
if {[info command filter-$type] eq "filter-$type"} {
set res [filter-$type $cont $a]
if {[llength $res] >= 1} {
set code [lindex $res 0]
if {$code ne ""} {
if {[dict get $a results] ne "asis"} {
if {$blockType eq "CodeBlock"} {
rl_json::json set cblock c 0 1 [rl_json::json array [list string ${type}out]]
rl_json::json set cblock c 1 [rl_json::json string $code]
append blocks ",[::rl_json::json extract $cblock]"
} elseif {$blockType eq "BlockQuote"} {
rl_json::json set cblock c 0 c 0 1 [rl_json::json array [list string ${type}out]]
rl_json::json set cblock c 0 c 1 [rl_json::json string $code]
append blocks ",[::rl_json::json extract $cblock]"
}
} else {
set cres $code
set mdfile [file tempfile].md
set out [open $mdfile w 0600]
puts $out $code
close $out
set cres [exec pandoc -t json $mdfile]
file delete $mdfile
# pandoc 2.9 (block first then meta)
set cres [regsub {^.+"blocks":\[(.+)\],"pandoc-api-version".+} $cres "\\1"]
# pandoc 2.12++ (meta first, then block)
set cres [regsub {^\{"pandoc-api-version".+"blocks":\[(.+)\]\}} $cres "\\1"]
append blocks ,
append blocks $cres
}
}
if {[llength "$res"] == 2} {
set img [lindex $res 1]
if {$img ne ""} {
rl_json::json set jsonImg c 0 c 2 0 "$img"
append blocks ",[::rl_json::json extract $jsonImg]"
}
}
}
}
}
}
}
# the main method parsing the json data
proc main {jsonData} {
if {[info exists ::env(FILTEREVAL)]} {
set evalvar $::env(FILTEREVAL)
} else {
set evalvar true
}
#puts stderr "Ecal should be $evalvar"
set blocks ""
set jsonImg {
{
"t": "Para",
"c": [
{
"t": "Image",
"c": [
[
"",
[],
[]
],
[],
[
"image.svg",
""
]
]
}
]
}
}
set meta [rl_json::json extract $jsonData meta]
for {set i 0} {$i < [llength [::rl_json::json get $jsonData blocks]]} {incr i} {
if {$i > 0} {
append blocks ","
}
set blockType [::rl_json::json get $jsonData blocks $i t]
if {$blockType eq "CodeBlock"} {
set tkey [list $i c 0 1]
set akey [list $i c 0 2]
set ckey [list $i c 1]
set bkey [list $i]
codeBlock
} elseif {$blockType in [list BulletList Header Para BlockQuote]} {
if {$blockType eq "BlockQuote" && [::rl_json::json get $jsonData blocks $i c 0 t] eq "CodeBlock"} {
set tkey [list $i c 0 c 0 1]
set akey [list $i c 0 c 0 2]
set ckey [list $i c 0 c 1]
set bkey [list $i]
codeBlock
continue
}
set ::inlineCodes [list]
set k [llength [::rl_json::json get $jsonData blocks $i c]]
#puts stderr $blockType
#puts stderr [::rl_json::json get $jsonData blocks $i c]
if {$blockType eq "Header" && $k == 3} {
walk [list $i c 2]
} else {
for {set j 0} {$j < $k} {incr j} {
walk [list $i c $j]
}
}
foreach item $::inlineCodes {
foreach {key code} $item {
set ckey [lrange $key 0 end-1]
set tkey [lrange $key 0 end-2]
lappend tkey t
set c [regsub {^[^ ]+} $code ""]
if {[regexp {\.?tcl } $code]} {
if {[catch {
set ::errorInfo {}
set res [interp eval mdi $c]
}]} {
set res "error: $c"
set res "Error: [regsub {\n +invoked.+} $::errorInfo {}]"
}
set jsonData [rl_json::json set jsonData blocks {*}$ckey [rl_json::json string "$res"]]
set jsonData [rl_json::json set jsonData blocks {*}$tkey Str]
} elseif {[regexp -nocase {\.?R } $code]} {
set d [dict create results show echo false pipe R]
set res [lindex [filter-pipe $c $d] 0]
set res [regsub {^>.+\[1\]} $res ""]
set jsonData [rl_json::json set jsonData blocks {*}$ckey [rl_json::json string "$res"]]
set jsonData [rl_json::json set jsonData blocks {*}$tkey Str]
} elseif {[regexp -nocase {\.?oc } $code]} {
set d [dict create results show echo false pipe octave]
set res [regsub {.+?> } [lindex [filter-pipe "$c\n" $d] 0] ""]
set jsonData [rl_json::json set jsonData blocks {*}$ckey [rl_json::json string "$res"]]
set jsonData [rl_json::json set jsonData blocks {*}$tkey Str]
} elseif {[regexp {\.?py } $code]} {
# this does not work (yet)
set d [dict create pipe python3 terminal true]
set res [regsub {.+> } [lindex [filter-pipe [string trim $c] $d] 0] ""]
#set res [regsub {^>.+\[1\]} $res ""]
set jsonData [rl_json::json set jsonData blocks {*}$ckey [rl_json::json string "$res"]]
set jsonData [rl_json::json set jsonData blocks {*}$tkey Str]
}
}
}
append blocks "[::rl_json::json extract $jsonData blocks $i]\n"
} else {
append blocks "[::rl_json::json extract $jsonData blocks $i]\n"
}
}
set ret "\"blocks\":\[$blocks\]"
append ret ",\"pandoc-api-version\":[::rl_json::json extract $jsonData pandoc-api-version]"
append ret ",\"meta\":[::rl_json::json extract $jsonData meta]"
#set out [open out.json w 0600]
#puts $out "{$ret}"
#close $out
return "{$ret}"
}
# just demo code from the Tclers wiki (not used):
proc incrHeader {jsonData} {
for {set i 0} {$i < [llength [::rl_json::json get $jsonData blocks]]} {incr i} {
set blockType [::rl_json::json get $jsonData blocks $i t]
if {$blockType eq "Header"} {
set headerLevel [::rl_json::json get $jsonData blocks $i c 0]
set jsonData [::rl_json::json set jsonData blocks $i c 0 [expr {$headerLevel + 1}]]
}
}
return $jsonData
}
# give the modified document back to Pandoc again:
puts -nonewline [main $jsonData]