ADDED examples/httpd/htdocs/example.md Index: examples/httpd/htdocs/example.md ================================================================== --- /dev/null +++ examples/httpd/htdocs/example.md @@ -0,0 +1,157 @@ +An h1 header +============ + +Paragraphs are separated by a blank line. + +2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists +look like: + + * this one + * that one + * the other one + +Note that --- not considering the asterisk --- the actual text +content starts at 4-columns in. + +> Block quotes are +> written like so. +> +> They can span multiple paragraphs, +> if you like. + +Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all +in chapters 12--14"). Three dots ... will be converted to an ellipsis. +Unicode is supported. ☺ + + + +An h2 header +------------ + +Here's a numbered list: + + 1. first item + 2. second item + 3. third item + +Note again how the actual text starts at 4 columns in (4 characters +from the left side). Here's a code sample: + + # Let me re-iterate ... + for i in 1 .. 10 { do-something(i) } + +As you probably guessed, indented 4 spaces. By the way, instead of +indenting the block, you can use delimited blocks, if you like: + +~~~ +define foobar() { + print "Welcome to flavor country!"; +} +~~~ + +(which makes copying & pasting easier). You can optionally mark the +delimited block for Pandoc to syntax highlight it: + +~~~python +import time +# Quick, count to ten! +for i in range(10): + # (but not *too* quick) + time.sleep(0.5) + print i +~~~ + + + +### An h3 header ### + +Now a nested list: + + 1. First, get these ingredients: + + * carrots + * celery + * lentils + + 2. Boil some water. + + 3. Dump everything in the pot and follow + this algorithm: + + find wooden spoon + uncover pot + stir + cover pot + balance wooden spoon precariously on pot handle + wait 10 minutes + goto first step (or shut off burner when done) + + Do not bump wooden spoon or it will fall. + +Notice again how text always lines up on 4-space indents (including +that last line which continues item 3 above). + +Here's a link to [a website](http://foo.bar), to a [local +doc](local-doc.html), and to a [section heading in the current +doc](#an-h2-header). Here's a footnote [^1]. + +[^1]: Footnote text goes here. + +Tables can look like this: + +size material color +---- ------------ ------------ +9 leather brown +10 hemp canvas natural +11 glass transparent + +Table: Shoes, their sizes, and what they're made of + +(The above is the caption for the table.) Pandoc also supports +multi-line tables: + +-------- ----------------------- +keyword text +-------- ----------------------- +red Sunsets, apples, and + other red or reddish + things. + +green Leaves, grass, frogs + and other things it's + not easy being. +-------- ----------------------- + +A horizontal rule follows. + +*** + +Here's a definition list: + +apples + : Good for making applesauce. +oranges + : Citrus! +tomatoes + : There's no "e" in tomatoe. + +Again, text is indented 4 spaces. (Put a blank line between each +term/definition pair to spread things out more.) + +Here's a "line block": + +| Line one +| Line too +| Line tree + +and images can be specified like so: + + + +Inline math equations go in like so: $\omega = d\phi / dt$. Display +math should get its own line and be put in in double-dollarsigns: + +$$I = \int \rho R^{2} dV$$ + +And note that you can backslash-escape any punctuation characters +which you wish to be displayed literally, ex.: \`foo\`, \*bar\*, etc. ADDED examples/httpd/htdocs/index.html Index: examples/httpd/htdocs/index.html ================================================================== --- /dev/null +++ examples/httpd/htdocs/index.html @@ -0,0 +1,14 @@ + +
+
+while trying to obtain $data(url)
+ "
+ my puts "
+"
+ }
+
+ method content {} {
+ set docroot [my
+while trying to obtain $data(url)
+ "
+ } else {
+ my puts "
+Guru meditation #[clock seconds]
+
+The server encountered an internal error:
+
+
+For deeper understanding:
+
+$msg
+$::errorInfo
+"
+ }
+ my puts "
+"
+ my output
+ }
+
+
+ ###
+ # REPLACE ME:
+ # This method is the "meat" of your application.
+ # It writes to the result buffer via the "puts" method
+ # and can tweak the headers via "meta put header_reply"
+ ###
+ method content {} {
+ my puts ""
+ my puts ""
+ my puts "HELLO WORLD!
"
+ my puts ""
+ my puts ""
+ }
+
+ ###
+ # Transform this object to another class
+ ###
+ method morph newclass {
+ set newclass ::[string trimleft $newclass :]
+ if {$newclass eq [info object class [self]]} {
+ return
+ }
+ my MorphExit
+ oo::objdefine [self] class $newclass
+ my MorphEnter
+ }
+
+ ###
+ # Actions to perform as the new class when
+ # we morph into it
+ ###
+ method MorphEnter {} {
+
+ }
+
+ ###
+ # Actions to perform as our present class
+ # prior to changing to our new class
+ ###
+ method MorphExit {} {
+
+ }
+
+ ###
+ # Output the result or error to the channel
+ # and destroy this object
+ ###
+ method output {} {
+ my variable reply_body
+ set headers [my meta get reply_headers]
+ set result "HTTP/1.0 [my meta get reply_status]\n"
+ foreach {key value} $headers {
+ append result "$key $value" \n
+ }
+ append result "Content-length: [string length $reply_body]" \n \n
+ append result $reply_body
+ my variable chan
+ puts $chan $result
+ flush $chan
+ my destroy
+ }
+
+ ###
+ # Append to the result buffer
+ ###
+ method puts line {
+ my variable reply_body
+ append reply_body $line \n
+ }
+
+ ###
+ # Read out the contents of the POST
+ ###
+ method query_body {} {
+ my variable query_body
+ return $query_body
+ }
+
+ ###
+ # Read the request from the client
+ # This code was adapted from the HttpdRead procedure in
+ # tclhttpd
+ ###
+ method RequestRead {} {
+ my variable chan
+ my variable data
+
+ if {[catch {gets $chan line} readCount]} {
+ my
"
+ incr index
+ }
+ {^[ ]{0,3}#{1,6}} {
+ # ATX STYLE HEADINGS
+ set h_level 0
+ set h_result {}
+
+ while {$index < $no_lines && ![is_empty_line $line]} {
+ incr index
+
+ if {!$h_level} {
+ regexp {^\s*#+} $line m
+ set h_level [string length [string trim $m]]
+ }
+
+ lappend h_result $line
+
+ set line [lindex $lines $index]
+ }
+
+ set h_result [\
+ parse_inline [\
+ regsub -all {^\s*#+\s*|\s*#+\s*$} [join $h_result \n] {} \
+ ]\
+ ]
+
+ append result "\n \
+ [apply_templates bq_result] \
+ \n
+ }
+ {^\s{4,}\S+} {
+ # CODE BLOCKS
+ set code_result {}
+
+ while {$index < $no_lines} {
+ incr index
+
+ lappend code_result [html_escape [\
+ regsub {^ } $line {}]\
+ ]
+
+ set eoc 0
+ for {set peek $index} {$peek < $no_lines} {incr peek} {
+ set line [lindex $lines $peek]
+
+ if {![is_empty_line $line]} {
+ if {![regexp {^\s{4,}} $line]} {
+ set eoc 1
+ }
+ break
+ }
+ }
+
+ if {$eoc} { break }
+
+ set line [lindex $lines $index]
+ }
+ set code_result [join $code_result \n]
+
+ append result
+ }
+ {^(?:(?:`{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*$}
+ }
+
+ while {$index < $no_lines} {
+ incr index
+
+ set line [lindex $lines $index]
+
+ if {[regexp $end_match $line]} {
+ incr index
+ break
+ }
+
+ lappend code_result [html_escape $line]
+ }
+ set code_result [join $code_result \n]
+
+ append result $code_result \n
+ }
+ {^[ ]{0,3}(?:\*|-|\+) |^[ ]{0,3}\d+\. } {
+ # LISTS
+ set list_result {}
+
+ # continue matching same list type
+ if {[regexp $ol_match $line]} {
+ set list_type ol
+ set list_match $ol_match
+ } else {
+ set list_type ul
+ set list_match $ul_match
+ }
+
+ set last_line AAA
+
+ while {$index < $no_lines} \
+ {
+ if {![regexp $list_match [lindex $lines $index]]} {
+ break
+ }
+
+ set item_result {}
+ set in_p 1
+ set p_count 1
+
+ if {[is_empty_line $last_line]} {
+ incr p_count
+ }
+
+ set last_line $line
+ set line [regsub "$list_match\\s*" $line {}]
+
+ # prevent recursion on same line
+ set line [regsub {\A(\d+)\.(\s+)} $line {\1\\.\2}]
+ set line [regsub {\A(\*|\+|-)(\s+)} $line {\\\1\2}]
+
+ lappend item_result $line
+
+ for {set peek [expr $index + 1]} {$peek < $no_lines} {incr peek} {
+ set line [lindex $lines $peek]
+
+ if {[is_empty_line $line]} {
+ set in_p 0
+ }\
+ elseif {[regexp {^ } $line]} {
+ if {!$in_p} {
+ incr p_count
+ }
+ set in_p 1
+ }\
+ elseif {[regexp $list_match $line]} {
+ if {!$in_p} {
+ incr p_count
+ }
+ break
+ }\
+ elseif {!$in_p} {
+ break
+ }
+
+ set last_line $line
+ lappend item_result [regsub {^ } $line {}]
+ }
+
+ set item_result [join $item_result \n]
+
+ if {$p_count > 1} {
+ set item_result [apply_templates item_result li]
+ } else {
+ if {[regexp -lineanchor \
+ {(\A.*?)((?:^[ ]{0,3}(?:\*|-|\+) |^[ ]{0,3}\d+\. ).*\Z)} \
+ $item_result \
+ match para rest]} \
+ {
+ set item_result [parse_inline $para]
+ append item_result [apply_templates rest]
+ } else {
+ set item_result [parse_inline $item_result]
+ }
+ }
+
+ lappend list_result " $code_result
\n"
+ append result "\n"
+ append result "
\n"
+ }
+ default {
+ append result "\n"
+ append result "\n"
+ }
+ }
+
+ break
+ }
+ }
+ }
+ default {
+ # PARAGRAPHS AND SETTEXT STYLE HEADERS
+ set p_type p
+ set p_result {}
+
+ while {($index < $no_lines) && ![is_empty_line $line]} \
+ {
+ incr index
+
+ switch -regexp $line {
+ {^[ ]{0,3}=+$} {
+ set p_type h1
+ break
+ }
+ {^[ ]{0,3}-+$} {
+ set p_type h2
+ break
+ }
+ {^[ ]{0,3}(?:\*|-|\+) |^[ ]{0,3}\d+\. } {
+ if {$parent eq {li}} {
+ incr index -1
+ break
+ } else {
+ lappend p_result $line
+ }
+ }
+ {^[ ]{0,3}-[ ]*-[ ]*-[- ]*$} -
+ {^[ ]{0,3}_[ ]*_[ ]*_[_ ]*$} -
+ {^[ ]{0,3}\*[ ]*\*[ ]*\*[\* ]*$} -
+ {^[ ]{0,3}#{1,6}} \
+ {
+ incr index -1
+ break
+ }
+ default {
+ lappend p_result $line
+ }
+ }
+
+ set line [lindex $lines $index]
+ }
+
+ set p_result [\
+ parse_inline [\
+ string trim [join $p_result \n]\
+ ]\
+ ]
+
+ if {[is_empty_line [regsub -all {} $p_result {}]]} {
+ # Do not make a new paragraph for just comments.
+ append result $p_result
+ } else {
+ append result "<$p_type>$p_result$p_type>"
+ }
+ }
+ }
+ }
+
+ return $result
+ }
+
+ ## \private
+ proc parse_inline {text} {
+ set text [regsub -all -lineanchor {[ ]{2,}$} $text \n"
+
+ if {$cell_align ne {}} {
+ set num_cols [llength $cell_align]
+ } else {
+ set num_cols [llength $row_cols]
+ }
+
+ for {set i 0} {$i < $num_cols} {incr i} \
+ {
+ if {[set align [lindex $cell_align $i]] ne {}} {
+ append result " \n"
+ append result "\n"
+ } else {
+ if {$row_count == 1} {
+ append result "\n"
+ }
+
+ append result " "
+ } else {
+ append result " "
+ }
+
+ append result [parse_inline [string trim \
+ [lindex $row_cols $i]]] "\n"
+ }
+
+ append result " \n"
+
+ if {$cell_align ne {}} {
+ set num_cols [llength $cell_align]
+ } else {
+ set num_cols [llength $row_cols]
+ }
+
+ for {set i 0} {$i < $num_cols} {incr i} \
+ {
+ if {[set align [lindex $cell_align $i]] ne {}} {
+ append result " \n"
+ }
+
+ incr row_count
+
+ set line [lindex $lines [incr index]]
+
+ if {![regexp {(?:^\s{0,3}|[^\\]+)\|} $line]} {
+ switch $row_count {
+ 1 {
+ append result ""
+ } else {
+ append result " "
+ }
+
+ append result [parse_inline [string trim \
+ [lindex $row_cols $i]]] "\n"
+ }
+
+ append result "
]
+
+ set index 0
+ set result {}
+
+ set re_backticks {\A`+}
+ set re_whitespace {\s}
+ set re_inlinelink {\A\!?\[((?:[^\]]|\[[^\]]*?\])+)\]\s*\(\s*((?:[^\s\)]+|\([^\s\)]+\))+)?(\s+([\"'])(.*)?\4)?\s*\)}
+ set re_reflink {\A\!?\[((?:[^\]]|\[[^\]]*?\])+)\](?:\s*\[((?:[^\]]|\[[^\]]*?\])*)\])?}
+ set re_htmltag {\A?\w+\s*>|\A<\w+(?:\s+\w+=(?:\"[^\"]+\"|\'[^\']+\'))*\s*/?>}
+ set re_autolink {\A<(?:(\S+@\S+)|(\S+://\S+))>}
+ set re_comment {\A}
+ set re_entity {\A\&\S+;}
+
+ while {[set chr [string index $text $index]] ne {}} {
+ switch $chr {
+ "\\" {
+ # ESCAPES
+ set next_chr [string index $text [expr $index + 1]]
+
+ if {[string first $next_chr {\`*_\{\}[]()#+-.!>|}] != -1} {
+ set chr $next_chr
+ incr index
+ }
+ }
+ {_} -
+ {*} {
+ # EMPHASIS
+ if {[regexp $re_whitespace [string index $result end]] &&
+ [regexp $re_whitespace [string index $text [expr $index + 1]]]} \
+ {
+ #do nothing
+ } \
+ elseif {[regexp -start $index \
+ "\\A(\\$chr{1,3})((?:\[^\\$chr\\\\]|\\\\\\$chr)*)\\1" \
+ $text m del sub]} \
+ {
+ switch [string length $del] {
+ 1 {
+ append result "[parse_inline $sub]"
+ }
+ 2 {
+ append result "[parse_inline $sub]"
+ }
+ 3 {
+ append result "[parse_inline $sub]"
+ }
+ }
+
+ incr index [string length $m]
+ continue
+ }
+ }
+ {`} {
+ # CODE
+ regexp -start $index $re_backticks $text m
+ set start [expr $index + [string length $m]]
+
+ if {[regexp -start $start -indices $m $text m]} {
+ set stop [expr [lindex $m 0] - 1]
+
+ set sub [string trim [string range $text $start $stop]]
+
+ append result "[html_escape $sub]
"
+ set index [expr [lindex $m 1] + 1]
+ continue
+ }
+ }
+ {!} -
+ {[} {
+ # LINKS AND IMAGES
+ if {$chr eq {!}} {
+ set ref_type img
+ } else {
+ set ref_type link
+ }
+
+ set match_found 0
+
+ if {[regexp -start $index $re_inlinelink $text m txt url ign del title]} {
+ # INLINE
+ incr index [string length $m]
+
+ set url [html_escape [string trim $url {<> }]]
+ set txt [parse_inline $txt]
+ set title [parse_inline $title]
+
+ set match_found 1
+ } elseif {[regexp -start $index $re_reflink $text m txt lbl]} {
+ if {$lbl eq {}} {
+ set lbl [regsub -all {\s+} $txt { }]
+ }
+
+ set lbl [string tolower $lbl]
+
+ if {[info exists ::Markdown::_references($lbl)]} {
+ lassign $::Markdown::_references($lbl) url title
+
+ set url [html_escape [string trim $url {<> }]]
+ set txt [parse_inline $txt]
+ set title [parse_inline $title]
+
+ # REFERENCED
+ incr index [string length $m]
+ set match_found 1
+ }
+ }
+
+ # PRINT IMG, A TAG
+ if {$match_found} {
+ if {$ref_type eq {link}} {
+ if {$title ne {}} {
+ append result "$txt"
+ } else {
+ append result "$txt"
+ }
+ } else {
+ if {$title ne {}} {
+ append result ""
+ } else {
+ append result "
"
+ }
+ }
+
+ continue
+ }
+ }
+ {<} {
+ # HTML TAGS, COMMENTS AND AUTOLINKS
+ if {[regexp -start $index $re_comment $text m]} {
+ append result $m
+ incr index [string length $m]
+ continue
+ } elseif {[regexp -start $index $re_autolink $text m email link]} {
+ if {$link ne {}} {
+ set link [html_escape $link]
+ append result "$link"
+ } else {
+ set mailto_prefix "mailto:"
+ if {![regexp "^${mailto_prefix}(.*)" $email mailto email]} {
+ # $email does not contain the prefix "mailto:".
+ set mailto "mailto:$email"
+ }
+ append result "$email"
+ }
+ incr index [string length $m]
+ continue
+ } elseif {[regexp -start $index $re_htmltag $text m]} {
+ append result $m
+ incr index [string length $m]
+ continue
+ }
+
+ set chr [html_escape $chr]
+ }
+ {&} {
+ # ENTITIES
+ if {[regexp -start $index $re_entity $text m]} {
+ append result $m
+ incr index [string length $m]
+ continue
+ }
+
+ set chr [html_escape $chr]
+ }
+ {>} -
+ {'} -
+ "\"" {
+ # OTHER SPECIAL CHARACTERS
+ set chr [html_escape $chr]
+ }
+ default {}
+ }
+
+ append result $chr
+ incr index
+ }
+
+ return $result
+ }
+
+ ## \private
+ proc is_empty_line {line} {
+ return [regexp {^\s*$} $line]
+ }
+
+ ## \private
+ proc html_escape {text} {
+ return [string map {& & < < > > \" "} $text]
+ }
+}
+
+package provide Markdown 1.0
+
ADDED modules/markdown/pkgIndex.tcl
Index: modules/markdown/pkgIndex.tcl
==================================================================
--- /dev/null
+++ modules/markdown/pkgIndex.tcl
@@ -0,0 +1,11 @@
+# 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]]
ADDED modules/ooutil/oometa.tcl
Index: modules/ooutil/oometa.tcl
==================================================================
--- /dev/null
+++ modules/ooutil/oometa.tcl
@@ -0,0 +1,245 @@
+###
+# Author: Sean Woods, yoda@etoyoc.com
+##
+# TclOO routines to implement property tracking by class and object
+###
+package require oo::util
+
+namespace eval ::oo::meta {
+ variable dirty_classes {}
+}
+
+if {[::info command ::tcl::dict::getnull] eq {}} {
+ proc ::tcl::dict::getnull {dictionary args} {
+ if {[exists $dictionary {*}$args]} {
+ get $dictionary {*}$args
+ }
+ }
+
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] getnull ::tcl::dict::getnull]
+}
+
+proc ::oo::meta::args_to_dict args {
+ if {[llength $args]==1} {
+ return [lindex $args 0]
+ }
+ return $args
+}
+
+proc ::oo::meta::args_to_options args {
+ set result {}
+ foreach {var val} [args_to_dict {*}$args] {
+ lappend result [string trimleft $var -] $val
+ }
+ return $result
+}
+
+proc ::oo::meta::ancestors class {
+ set thisresult {}
+ set result {}
+ set queue $class
+ while {[llength $queue]} {
+ set tqueue $queue
+ set queue {}
+ foreach qclass $tqueue {
+ foreach aclass [::info class superclasses $qclass] {
+ if { $aclass in $result } continue
+ if { $aclass in $queue } continue
+ lappend queue $aclass
+ }
+ foreach aclass [::info class mixins $qclass] {
+ if { $aclass in $result } continue
+ if { $aclass in $queue } continue
+ lappend queue $aclass
+ }
+ }
+ foreach item $tqueue {
+ if { $item ni $result } {
+ set result [linsert $result 0 $item]
+ }
+ }
+ }
+ return $result
+}
+
+proc ::oo::meta::info {class submethod args} {
+ switch $submethod {
+ rebuild {
+ if {$class ni $::oo::meta::dirty_classes} {
+ lappend ::oo::meta::dirty_classes $class
+ }
+ }
+ is {
+ set info [properties $class]
+ return [string is [lindex $args 0] -strict [dict getnull $info {*}[lrange $args 1 end]]]
+ }
+ for -
+ map {
+ set info [properties $class]
+ puts [list [dict get $info {*}[lrange $args 1 end-1]]]
+ return [uplevel 1 [list ::dict $submethod [lindex $args 0] [dict get $info {*}[lrange $args 1 end-1]] [lindex $args end]]]
+ }
+ with {
+ upvar 1 TEMPVAR info
+ set info [properties $class]
+ return [uplevel 1 [list ::dict with TEMPVAR {*}$args]]
+ }
+ append -
+ incr -
+ lappend -
+ set -
+ unset -
+ update {
+ if {$class ni $::oo::meta::dirty_classes} {
+ lappend ::oo::meta::dirty_classes $class
+ }
+ ::dict $submethod ::oo::meta::local_property($class) {*}$args
+ }
+ dump {
+ set info [properties $class]
+ return $info
+ }
+ default {
+ set info [properties $class]
+ return [::dict $submethod $info {*}$args]
+ }
+ }
+}
+
+proc ::oo::meta::properties class {
+ ###
+ # Destroy the cache of all derivitive classes
+ ###
+ variable dirty_classes
+ foreach dclass $dirty_classes {
+ foreach {cclass cancestors} [array get ::oo::meta::cached_hierarchy] {
+ if {$dclass in $cancestors} {
+ unset -nocomplain ::oo::meta::cached_property($cclass)
+ unset -nocomplain ::oo::meta::cached_hierarchy($cclass)
+ }
+ }
+ }
+
+ ###
+ # If the cache is available, use it
+ ###
+ variable cached_property
+ if {[::info exists cached_property($class)]} {
+ return $cached_property($class)
+ }
+ ###
+ # Build a cache of the hierarchy and the
+ # aggregate properties for this class and store
+ # them for future use
+ ###
+ variable cached_hierarchy
+ set properties {}
+ set stack {}
+ variable local_property
+ set cached_hierarchy($class) [::oo::meta::ancestors $class]
+ foreach aclass $cached_hierarchy($class) {
+ if {[::info exists local_property($aclass)]} {
+ lappend stack $local_property($aclass)
+ }
+ }
+ if {[llength $stack]} {
+ set properties [dict merge {*}$stack]
+ } else {
+ set properties {}
+ }
+ set cached_property($class) $properties
+ return $properties
+}
+
+###
+# Add properties and option handling
+###
+proc ::oo::define::property {args} {
+ set class [lindex [::info level -1] 1]
+ ::oo::meta::info $class set {*}$args
+}
+
+oo::define oo::class {
+
+ method meta {submethod args} {
+ set class [self]
+ switch $submethod {
+ is {
+ set info [::oo::meta::properties $class]
+ return [string is [lindex $args 0] -strict [dict getnull $info {*}[lrange $args 1 end]]]
+ }
+ for -
+ map {
+ set info [::oo::meta::properties $class]
+ return [uplevel 1 [list dict $submethod [lindex $args 0] [dict get $info {*}[lrange $args 1 end-1]] [lindex $args end]]]
+ }
+ with {
+ upvar 1 TEMPVAR info
+ set info [::oo::meta::properties $class]
+ return [uplevel 1 [list dict with TEMPVAR {*}$args]]
+ }
+ dump {
+ return [::oo::meta::properties $class]
+ }
+ append -
+ incr -
+ lappend -
+ set -
+ unset -
+ update {
+ ::oo::meta::info $class rebuild
+ return [dict $submethod config {*}$args]
+ }
+ default {
+ set info [::oo::meta::properties $class]
+ return [dict $submethod $info {*}$args]
+ }
+ }
+ }
+
+}
+
+oo::define oo::object {
+
+ method meta {submethod args} {
+ my variable config
+ if {![::info exists config]} {
+ set config {}
+ }
+ set class [::info object class [self object]]
+ switch $submethod {
+ is {
+ set info [dict merge [::oo::meta::properties $class] $config]
+ return [string is [lindex $args 0] -strict [dict getnull $info {*}[lrange $args 1 end]]]
+ }
+ for -
+ map {
+ set info [dict merge [::oo::meta::properties $class] $config]
+ return [uplevel 1 [list dict $submethod [lindex $args 0] [dict get $info {*}[lrange $args 1 end-1]] [lindex $args end]]]
+ }
+ with {
+ upvar 1 TEMPVAR info
+ set info [dict merge [::oo::meta::properties $class] $config]
+ return [uplevel 1 [list dict with TEMPVAR {*}$args]]
+ }
+ dump {
+ return [dict merge [::oo::meta::properties $class] $config]
+ }
+ append -
+ incr -
+ lappend -
+ set -
+ unset -
+ update {
+ return [dict $submethod config {*}$args]
+ }
+ default {
+ set info [dict merge [::oo::meta::properties $class] $config]
+ return [dict $submethod $info {*}$args]
+ }
+ }
+ }
+}
+
+package provide oo::meta 0.1
ADDED modules/ooutil/oooption.tcl
Index: modules/ooutil/oooption.tcl
==================================================================
--- /dev/null
+++ modules/ooutil/oooption.tcl
@@ -0,0 +1,167 @@
+###
+# Option handling for TclOO
+###
+package require oo::meta
+
+oo::define oo::object {
+
+ ###
+ # topic: 3c4893b65a1c79b2549b9ee88f23c9e3
+ # description:
+ # Provide a default value for all options and
+ # publically declared variables, and locks the
+ # pipeline mutex to prevent signal processing
+ # while the contructor is still running.
+ # Note, by default an odie object will ignore
+ # signals until a later call to my lock remove pipeline
+ ###
+ method InitializePublic {} {
+ my variable config
+ if {![info exists config]} {
+ set config {}
+ }
+ set dat [my meta getnull option]
+ foreach {var info} $dat {
+ if {[dict exists $info set-command]} {
+ if {[catch {my cget $var} value]} {
+ dict set config $var [my cget $var default]
+ } else {
+ if { $value eq {} } {
+ dict set config $var [my cget $var default]
+ }
+ }
+ }
+ if {![dict exists $config $var]} {
+ dict set config $var [my cget $var default]
+ }
+ }
+ foreach {var info} [my meta getnull variable] {
+ if { $var eq "config" } continue
+ my variable $var
+ if {![info exists $var]} {
+ if {[dict exists $info default]} {
+ set $var [dict get $info default]
+ } else {
+ set $var {}
+ }
+ }
+ }
+ foreach {var info} [my meta getnull array] {
+ if { $var eq "config" } continue
+ my variable $var
+ if {![info exists $var]} {
+ if {[dict exists $info default]} {
+ array set $var [dict get $info default]
+ } else {
+ array set $var {}
+ }
+ }
+ }
+ }
+
+ ###
+ # topic: 86a1b968cea8d439df87585afdbdaadb
+ ###
+ method cget {field {default {}}} {
+ my variable config
+ set field [string trimleft $field -]
+ set dat [my meta getnull option]
+
+ if {[my meta is true options_strict] && ![dict exists $dat $field]} {
+ error "Invalid option -$field. Valid: [dict keys $dat]"
+ }
+ set info [dict getnull $dat $field]
+ if {$default eq "default"} {
+ set getcmd [dict getnull $info default-command]
+ if {$getcmd ne {}} {
+ return [{*}[string map [list %field% $field %self% [namespace which my]] $getcmd]]
+ } else {
+ return [dict getnull $info default]
+ }
+ }
+ if {[dict exists $dat $field]} {
+ set getcmd [dict getnull $info get-command]
+ if {$getcmd ne {}} {
+ return [{*}[string map [list %field% $field %self% [namespace which my]] $getcmd]]
+ }
+ if {![dict exists $config $field]} {
+ set getcmd [dict getnull $info default-command]
+ if {$getcmd ne {}} {
+ dict set config $field [{*}[string map [list %field% $field %self% [namespace which my]] $getcmd]]
+ } else {
+ dict set config $field [dict getnull $info default]
+ }
+ }
+ if {$default eq "varname"} {
+ set varname [my varname visconfig]
+ set ${varname}($field) [dict get $config $field]
+ return "${varname}($field)"
+ }
+ return [dict get $config $field]
+ }
+ if {[dict exists $config $field]} {
+ return [dict get $config $field]
+ }
+ return [my meta get $field]
+ }
+
+ ###
+ # topic: 73e2566466b836cc4535f1a437c391b0
+ ###
+ method configure args {
+ # Will be removed at the end of "configurelist_triggers"
+ set dictargs [::oo::meta::args_to_options {*}$args]
+ if {[llength $dictargs] == 1} {
+ return [my cget [lindex $dictargs 0]]
+ }
+ my configurelist $dictargs
+ my configurelist_triggers $dictargs
+ }
+
+ ###
+ # topic: dc9fba12ec23a3ad000c66aea17135a5
+ ###
+ method configurelist dictargs {
+ my variable config
+ set dat [my meta getnull option]
+ if {[my meta is true options_strict]} {
+ foreach {field val} $dictargs {
+ if {![dict exists $dat $field]} {
+ error "Invalid option $field. Valid: [dict keys $dat]"
+ }
+ }
+ }
+ ###
+ # Validate all inputs
+ ###
+ foreach {field val} $dictargs {
+ set script [dict getnull $dat $field validate-command]
+ if {$script ne {}} {
+ {*}[string map [list %field% [list $field] %value% [list $val] %self% [namespace which my]] $script]
+ }
+ }
+ ###
+ # Apply all inputs with special rules
+ ###
+ foreach {field val} $dictargs {
+ dict set config $field $val
+ }
+ }
+
+ ###
+ # topic: 543c936485189593f0b9ed79b5d5f2c0
+ ###
+ method configurelist_triggers dictargs {
+ set dat [my meta getnull option]
+ ###
+ # Apply all inputs with special rules
+ ###
+ foreach {field val} $dictargs {
+ set script [dict getnull $dat $field set-command]
+ if {$script ne {}} {
+ {*}[string map [list %field% [list $field] %value% [list $val] %self% [namespace which my]] $script]
+ }
+ }
+ }
+}
+package provide oo::option 0.1
Index: modules/ooutil/ooutil.test
==================================================================
--- modules/ooutil/ooutil.test
+++ modules/ooutil/ooutil.test
@@ -10,11 +10,13 @@
testsNeedTcltest 2
testsNeed TclOO 1
testing {
- useLocal ooutil.tcl oo::util
+ useLocal ooutil.tcl oo::util
+ useLocal oometa.tcl oo::meta
+ useLocal oooption.tcl oo::option
}
# -------------------------------------------------------------------------
test ooutil-ticket-b3577ed586 {test scoping of delegation in oo::class.Delegate } -setup {
@@ -28,13 +30,130 @@
}
} -cleanup {
namespace delete ooutiltest
rename animal {}
} -result {::ooutiltest::dog}
+
+# Test properties
+
+oo::class create foo {
+ property color blue
+
+ constructor args {
+ my InitializePublic
+ my configure {*}$args
+ }
+}
+
+oo::class create bar {
+ superclass ::foo
+ property shape oval
+ property option color {
+ default green
+ }
+}
+
+test oo-class-meta-001 {Test accessing properties} {
+ foo meta get color
+} blue
+
+test oo-class-meta-002 {Test accessing properties} {
+ bar meta get color
+} blue
+
+test oo-class-meta-003 {Test accessing properties} {
+ bar meta get shape
+} oval
+
+bar create cheers -color pink
+test oo-object-meta-001 {Test accessing properties} {
+ cheers meta get color
+} pink
+
+test oo-object-meta-002 {Test accessing properties} {
+ cheers meta get shape
+} oval
+
+test oo-object-meta-003 {Test accessing properties} {
+ cheers cget color
+} pink
+
+bar create moes
+test oo-object-meta-004 {Test accessing properties} {
+ moes meta get color
+} green
+
+test oo-object-meta-005 {Test accessing properties} {
+ moes meta get shape
+} oval
+
+test oo-object-meta-006 {Test accessing properties} {
+ moes cget color
+} green
+
+test oo-object-meta-007 {Test the CGET retrieves a property if an option doesn't exist} {
+ moes cget shape
+} oval
+
+###
+# Test altering a property
+###
+
+oo::define ::foo property woozle whoop
+
+test oo-modclass-meta-001 {Test accessing properties of an altered class} {
+ foo meta get woozle
+} whoop
+
+test oo-modclass-meta-002 {Test accessing properties of the descendent of an altered class} {
+ bar meta get woozle
+} whoop
+
+test oo-modobject-meta-001 {Test the accessing of properties of an instance of an altered class} {
+ moes meta get woozle
+} whoop
+
+test obj-meta-for-001 {Test object meta for} {
+ set result {}
+ moes meta for {key value} option {
+ lappend result $key $value
+ }
+ set result
+} {color {
+ default green
+ }}
+
+test obj-meta-with-001 {Test object meta with} {
+ set result {}
+ moes meta with option {}
+ set color
+} {
+ default green
+ }
+
+test obj-meta-for-001 {Test class meta for} {
+ set result {}
+ bar meta for {key value} option {
+ lappend result $key $value
+ }
+ set result
+} {color {
+ default green
+ }}
+
+test obj-meta-with-001 {Test class meta with} {
+ set result {}
+ bar meta with option {}
+ set color
+} {
+ default green
+ }
# -------------------------------------------------------------------------
+
+
testsuiteCleanup
# Local variables:
# mode: tcl
# indent-tabs-mode: nil
# End:
Index: modules/ooutil/pkgIndex.tcl
==================================================================
--- modules/ooutil/pkgIndex.tcl
+++ modules/ooutil/pkgIndex.tcl
@@ -3,5 +3,7 @@
if {![package vsatisfies [package provide Tcl] 8.5]} {
# PRAGMA: returnok
return
}
package ifneeded oo::util 1.2.1 [list source [file join $dir ooutil.tcl]]
+package ifneeded oo::meta 0.1 [list source [file join $dir oometa.tcl]]
+package ifneeded oo::option 0.1 [list source [file join $dir oooption.tcl]]
ADDED modules/scgi/pkgIndex.tcl
Index: modules/scgi/pkgIndex.tcl
==================================================================
--- /dev/null
+++ modules/scgi/pkgIndex.tcl
@@ -0,0 +1,12 @@
+# 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 scgi::app 0.1 [list source [file join $dir scgi-application.tcl]]
+package ifneeded scgi::server 0.1 [list source [file join $dir scgi-server.tcl]]
ADDED modules/scgi/scgi-app.man
Index: modules/scgi/scgi-app.man
==================================================================
--- /dev/null
+++ modules/scgi/scgi-app.man
ADDED modules/scgi/scgi-app.tcl
Index: modules/scgi/scgi-app.tcl
==================================================================
--- /dev/null
+++ modules/scgi/scgi-app.tcl
@@ -0,0 +1,135 @@
+###
+# Author: Sean Woods, yoda@etoyoc.com
+###
+# This file provides the "application" side of the SCGI protocol
+###
+
+package require html
+package require TclOO
+package require oo::meta
+
+namespace eval ::scgi {}
+
+proc ::scgi::decode_headers {rawheaders} {
+ #
+ # Take the tokenized header data and place the usual CGI headers into $env,
+ # and transform the HTTP_ variables to their original HTTP header field names
+ # as best as possible.
+ #
+ foreach {name value} $rawheaders {
+ if {[regexp {^HTTP_(.*)$} $name {} nameSuffix]} {
+ set nameParts [list]
+ foreach namePart [split $nameSuffix _] {
+ lappend nameParts [string toupper [string tolower $namePart] 0 0]
+ }
+ dict set headers [join $nameParts -] $value
+ } else {
+ dict set env $name $value
+ }
+ }
+
+ #
+ # Store CONTENT_LENGTH as an HTTP header named Content-Length, too.
+ #
+ set contentLength [dict get $env CONTENT_LENGTH]
+
+ if {$contentLength > 0} {
+ dict set headers Content-Length $contentLength
+ }
+ return [list env $enc headers $headers]
+}
+
+oo::class create ::scgi::reply {
+ superclass ::httpd::reply
+
+ property socket buffersize 32768
+ property socket blocking 0
+ property socket translation {binary binary}
+
+
+ method RequestRead {} {
+ my variable chan
+ my variable data
+ my variable inbuffer
+ set rawdata [read $chan]
+ append inbuffer $rawdata
+ if {[eof $chan]} {
+ my destroy
+ return
+ }
+ if {$data(state) == "start"} {
+ set colonIdx [string first : $inbuffer]
+ if {$colonIdx == -1} {
+ # we don't have the headers length yet
+ return
+ } else {
+ set length [string range $inbuffer 0 $colonIdx-1]
+ set inbuffer [string range $inbuffer $colonIdx+1 end]
+ set data(state) headers
+ set data(length) $length
+ }
+ }
+ if {$data(state) == "headers" } {
+ if {[string length $inbuffer] < $data(length)+1} {
+ # we don't have the complete headers yet, wait for more
+ return
+ }
+ set headers [string range $inbuffer 0 $data(length)-1]
+ set headers [lrange [split $headers \0] 0 end-1]
+ my variable query_body
+ set inbuffer [string range $inbuffer $data(length)+1 end]
+ set data(content_length) [dict get $headers CONTENT_LENGTH]
+ my meta set query_headers $headers
+ set data(state) body
+ }
+
+ if {[string length $inbuffer] < $data(content_length)} {
+ return
+ }
+ my variable query_body
+ set query_body $inbuffer
+
+ # Dispatch to the URL implementation.
+ if [catch {
+ set code [my