diff -r -u --unidirectional-new-file wikit.vfs/lib/httpd/wikithttpd.tcl wikit-wiklets.vfs/lib/httpd/wikithttpd.tcl --- wikit.vfs/lib/httpd/wikithttpd.tcl 2005-03-02 07:12:49.000000000 +0000 +++ wikit-wiklets.vfs/lib/httpd/wikithttpd.tcl 2006-06-26 12:42:29.000000000 +0000 @@ -163,15 +163,19 @@ set pos [string first "\r\n\r\n" $cgiOutput] if { $pos > -1 } { - set contentType [string range $cgiOutput 0 [expr $pos -1]] + set contentType {} + set headers [string range $cgiOutput 0 [expr $pos -1]] + regexp -nocase -line {^Content-Type: (.*)$} $headers -> contentType + set contentType [string map [list "\r" {}] $contentType] + if {$contentType eq {}} {set contentType text/html} set html [string range $cgiOutput [expr $pos +4] end] } else { - set contentType "" + set contentType text/html set html $cgiOutput } - httpReturn $channel 200 text/html $html + httpReturn $channel 200 $contentType $html close $channel cleanupChannel $channel diff -r -u --unidirectional-new-file wikit.vfs/lib/wikit/format.tcl wikit-wiklets.vfs/lib/wikit/format.tcl --- wikit.vfs/lib/wikit/format.tcl 2005-07-23 20:44:08.000000000 +0000 +++ wikit-wiklets.vfs/lib/wikit/format.tcl 2006-06-26 15:14:58.000000000 +0000 @@ -4,7 +4,7 @@ namespace eval Wikit::Format { namespace export TextToStream StreamToTk StreamToHTML StreamToRefs \ - StreamToUrls Expand_HTML + StreamToUrls Expand_HTML Expand_Wiklet # In this file: # @@ -636,10 +636,37 @@ set vstate "" ; # Initial state of visual FSM set count 0 variable html_frag - + set doWiklets 1 ; # set to 0 for conventional (non-Wiklet-enabled) output + set newQ 0 + set resultCache "" + set doneJSblock1 0 foreach {mode text} $s { switch -exact -- $mode { - {} {append result [quote $text]} + {} { + if {$newQ} { + # Here we handle the text of the first line of a "Quoted" (
) block
+				if {$doWiklets} {
+					if {$text eq ""} {
+						# This implies either an empty line, or a line with only whitespace.
+						# An empty line before Quoted text gets interpreted as Quoted text, which is not helpful here.
+						# Retain the state held in $newQ and $resultCache, so the next line (if Quoted) will be treated as the first.  There would be endless corner cases except that most of the whitespace mangling has already been done in the generation of $s, and the only possibility we need to worry about is a single leading blank line classified as Q followed by a true-Q non-whitespace line.
+					} else {
+						# Examine the text of the first line to extract Wiklet instructions (if any) and
+						# inject Wiklet markup (if needed) and $resultCache
+						set resultList   [wikletInjector $text $resultCache $doneJSblock1]
+						append result    [lindex $resultList 0]
+						set doneJSblock1 [lindex $resultList 1]
+						set resultCache ""
+						set newQ 0
+					}
+				} else {
+					append result ${resultCache}
+					set resultCache ""
+					set newQ 0
+				}
+			} else {}
+			append result [quote $text]
+			}
 		b - i {append result $html_frag($mode$text)}
 		g {
 		    if {$cgi == ""} {
@@ -692,10 +719,23 @@
 				[incr count] $html_frag(_a) \]
 		    }
 		}
-		T - Q - I - D - U - O - H {
+		T - I - D - U - O - H {
 		    append result $html_frag($state$mode)
 		    set state $mode
 		}
+		Q {
+			# It's a pity to mess up this almost stateless system, but to implement Wiklets we need to put some markup before the html_frag for the first non-empty Q line, and we don't know exactly what we need to put until we've read the next pair of tokens.  So we cache the html_frag in resultCache, and we use newQ to warn the processor for token {} to take special action on the next call.  The token-{} processor will clear the {newQ, resultCache} state unless the Q line is empty - in that case, the state is preserved (here also) because we need to apply that state to the following Q line.
+			if {$newQ} {
+				# newQ was not reset because first 'Q' line was empty.  Keep the old $resultCache and $newQ.
+			} elseif {$state eq "Q"} {
+		    	append result $html_frag($state$mode)
+		    	set state $mode
+			} else {
+				set resultCache $html_frag($state$mode)
+		    	set state $mode
+				set newQ 1
+			}
+		}
 	    }
 	}
 	# Close off the last section.
@@ -758,6 +798,170 @@
 	tc {">}         i1 
     } ; # "
 
+
+proc wikletInjector {text resultCacheIn doneJSblock1} {
+# Called by StreamToHTML and thus by the backend renderer Expand_HTML
+# First, set some constants, each of which is an HTML block consisting of a  element.
+
+set JSblock1 {}
+
+set JSblock2 {}
+
+set JSblock3 {}
+
+	# End of constants that contain Javascript, now back to pure Tcl
+	global env
+	set doWiklets 1     ; # set to 0 for conventional (non-Wiklet-enabled) output - use this to test the calling code
+	# Examine the first line of a "Quoted" (
) block; extract and act upon Wiklet Instruction (if any)
+	if {$doWiklets && [string range [string trim $text] 0 7] eq "# wiklet"} {
+		# The first line of the 
 block has an embedded wiklet command for this server
+		if {![catch {
+			set preCommand [string range [string trim $text] 8 end]
+			set preWikletNumber [lindex $preCommand 0]
+			set preWikletX      [lindex $preCommand 1]
+			set preWikletY      [lindex $preCommand 2]
+			set preWikletEmbed  [lindex $preCommand 3]
+			set pageNum         [string range $env(PATH_INFO) 1 end]
+			set preLen          [llength $preCommand]
+			set preNumLen       [llength $preWikletNumber]
+			}] && [validateInputs preWikletNumber preWikletX preWikletY preWikletEmbed pageNum preLen preNumLen] } {
+
+			if {$preLen > 2} {
+				# We have a Wiklet Instruction with X and Y values and they have been validated.  We need to write some JS.
+				if {!$doneJSblock1} {
+					append resultCacheOut $JSblock1
+					set doneJSblock1 1
+				} else {}
+				if {$preWikletEmbed eq "embed"} {
+					append resultCacheOut [string map [list XVALUE $preWikletX YVALUE $preWikletY NVALUE $preWikletNumber PAGEVALUE $pageNum] $JSblock2]
+				} else {}
+				append resultCacheOut [string map [list XVALUE $preWikletX YVALUE $preWikletY NVALUE $preWikletNumber PAGEVALUE $pageNum HTMLFRAG $resultCacheIn] $JSblock3]
+			} else {
+				# We have a valid Wiklet Instruction but without X and Y - so no JS to be written.
+				set    resultCacheOut $resultCacheIn
+			}
+		} else {
+			# Bad Wiklet Instruction
+			set    resultCacheOut $resultCacheIn
+			append resultCacheOut " # BAD WIKLET INSTRUCTION - ignored\n"
+		}
+	} else {
+		# No Wiklet Instruction, or $doWiklets is set to ignore them.
+		set resultCacheOut $resultCacheIn
+	}
+	return [list $resultCacheOut $doneJSblock1]
+}
+
+proc isPositiveInteger i {
+	expr {[string is integer -strict $i] && $i > 0}
+}
+
+proc validateInputs {preWikletNumberName preWikletXName preWikletYName preWikletEmbedName pageNumName preLenName preNumLenName} {
+	# Pass all arguments by reference using upvar.
+	# Test whether the values are satisfactory; overwrite some values in certain cases.
+	# Return 1 if inputs are valid, 0 if they are not.
+	upvar $preWikletNumberName preWikletNumber $preWikletXName preWikletX $preWikletYName preWikletY $preWikletEmbedName preWikletEmbed $pageNumName pageNum $preLenName preLen $preNumLenName preNumLen
+
+	if {$preNumLen > 1} {
+		# "Wiklet identifier" is a list of length > 1: it should be a list of integers
+		# We've extracted preNumLen in the calling code, so $preWikletNumber is a valid list.  No need to catch.
+		set testList $preWikletNumber
+		lappend testList $pageNum
+		set preLen 1
+		set preWikletX      {}
+		set preWikletY      {}
+		set preWikletEmbed  {}
+	} else {
+		if {$preLen > 3} {set preLen 3}
+		# Only check the ones that actually exist.  No need to decrement preLen because we've added $pageNum to the list
+		set testList [lrange [list $pageNum $preWikletNumber $preWikletX $preWikletY] 0 $preLen]
+	}
+
+	set allOK 1
+	foreach member $testList {
+		if {![isPositiveInteger $member]} {
+			set allOK 0
+			break
+		}
+	}
+	return $allOK
+}
+
     # =========================================================================
     # =========================================================================
 
@@ -798,6 +1002,71 @@
 	array get urls
     }
 
+    # =========================================================================
+    # =========================================================================
+
+    ### Backend renderer                                 :: Stream ==> Wiklet (Tcl) ###
+
+    # =========================================================================
+
+    # expand a page string to a Wiklet
+    proc Expand_Wiklet {str {wikletNumber 1}} {
+      StreamToWiklet [TextToStream $str] $wikletNumber
+    }
+    
+    # =========================================================================
+
+    # Output specific conversion. Takes a token stream and converts this
+    # into Tcl. The result is a 2-element list. The first element is the
+    # Tcl to be served as a Wiklet. The second element is the empty string.
+
+	proc StreamToWiklet {stream {wikletNumber 1}} {
+		# derived from Brian Theado's Wiklet Reaper, http://wiki.tcl.tk/14325
+		# modified to reap only certain 
 blocks depending on instructions in the first line
+		set pre {}
+		set preState 0
+		set getThisBlock 0
+		while {[llength $stream] > 0} {
+			if {[lindex $stream 0] == "Q"} {
+				# We're in a 
 block.
+				set thisPreLine [lindex $stream 3]\n
+				if {!$preState} {
+					# It's the first line of a 
 block.
+					if {$thisPreLine eq "\n"} {
+						# The empty-line-before-Q-counts-as-Q problem.
+						# Don't alter the state () - then the next line (if Q) will be considered "first" also
+					} else {
+						if {[string range [string trim $thisPreLine] 0 7] eq "# wiklet"} {
+							# It's the first line of a 
 block and it has an embedded wiklet command for the server.  Determine whether this block is part of the wiklet that we want.
+							set preCommand [string range [string trim $thisPreLine] 8 end]
+							set getThisBlock 0
+							catch {
+								set preWikletNumber [lindex $preCommand 0]
+								set getThisBlock [expr {[lsearch -exact -integer $preWikletNumber $wikletNumber] != -1 } ]
+							}
+						} else {
+							# No wiklet command.  We don't want this block.
+							set getThisBlock 0
+						}
+						set preState 1
+					}
+				} else {
+					# It's not the first line of a 
 block so we don't want to change getThisBlock.
+				}
+				if {$getThisBlock} {
+					append pre $thisPreLine
+				} else {
+				}
+				set stream [lrange $stream 4 end]
+			} else {
+				# We're not in a 
 block.
+				set stream [lrange $stream 2 end]
+				set preState 0
+			}
+		}
+		return [list $pre {}]
+	}
+
 } ;# end of namespace
 
 ### Local Variables: ***
diff -r -u --unidirectional-new-file wikit.vfs/lib/wikit/web.tcl wikit-wiklets.vfs/lib/wikit/web.tcl
--- wikit.vfs/lib/wikit/web.tcl	2005-02-01 23:45:03.000000000 +0000
+++ wikit-wiklets.vfs/lib/wikit/web.tcl	2006-06-25 18:05:45.000000000 +0000
@@ -162,6 +162,7 @@
     set section ""
       # Updated 3Mar03, edit and references are now subdirs to allow
       # for site indexing. 
+      # When N is an integer that is not out of range, the regexp sets section, N and cmd, but the if-body is not executed
       if {![regexp {^/(edit/|references/)?([0-9]+)(.*)$} $path x section N cmd] || $N >= [mk::view size wdb.pages]} {
       set N 0
     
@@ -369,12 +370,37 @@
     
     append menu "$Search$Changes$Refs$About$Home$Help"
     
+
+    # now dispatch on the type of request
+    # Don't serve the minimal Tclet for a non-existent page.  Serve text/html search page instead.
+    # If the page did not exist, then N has been set to 2 (search) which has no Wiklets
+  if {$N != 2 && [regexp {^\.(\d+)\.tcl$} $cmd -> wikletNumber] && $wikletNumber > 0} {
+       # dispatch a wiklet.  Could modify cmd and integrate with the other cases in switch, except we don't want cgi_html to add its ... wrapper.
+       # path is /integer.integer.tcl
     cgi_http_head {
-      cgi_content_type
+      cgi_content_type application/x-tcl
       pragma no-cache
     }
 
-    # now dispatch on the type of request
+		cgi_put "# Page Title: $name
+# Page Date: $date
+# Wiklet Number on This Page: $wikletNumber
+# URL: http://${host}${path}
+ proc wm args {}
+
+"
+	    # proc wm because some programs try to set their title which the plugin doesn't allow
+
+	    set C [GetPage $N]
+	    set U ""
+	    foreach {C U} [Expand_Wiklet $C $wikletNumber] break
+	    cgi_put $C
+
+  } else {
+    cgi_http_head {
+      cgi_content_type
+      pragma no-cache
+    }
     
     cgi_html {
 
@@ -507,6 +533,7 @@
 	    cgi_puts ""
 	  }
 	}
+}
       }
     }
 	
diff -r -u --unidirectional-new-file wikit.vfs/wiklets.README wikit-wiklets.vfs/wiklets.README
--- wikit.vfs/wiklets.README	1970-01-01 00:00:00.000000000 +0000
+++ wikit-wiklets.vfs/wiklets.README	2006-06-26 17:41:59.000000000 +0000
@@ -0,0 +1,57 @@
+For an example page generated by this modified Wikit, see http://www.kerlin.net/22
+
+A full explanation of Wiklets is given at http://wiki.tcl.tk/16092
+
+##### Patched Wikit #####
+
+At 2006-06-25 17:00 UTC the current CVS is identical to (the Wikit part of) the downloadable starkit http://www.equi4.com/pub/sk/wikit.kit
+
+The patch is valid against either.
+
+Wikit has been modified so that it:
+
+(1) serves a Wiklet (instead of HTML) when it receives a request for the appropriate URL (http://domain/integer1.integer2.tcl where http://domain/integer1 is a valid Wiki page and integer2 is positive). The Wiklet is Tcl source and its Content-type is 'application/x-tcl'.
+
+(2) inserts extra code into the HTML Wiki pages, to enable execution of the Wiklets. This code is HTML with embedded Javascript, and is served where necessary instead of '
'.
+
+Design goals for the patch
+- serve and enable Wiklets (obviously)
+- make no changes to the data stored in the database, or to the way that data is edited - i.e. by using the web browser to edit the Wiki markup for a text/html Wiki page.  Therefore an existing Wiki may be Wiklet-enabled by using the patched Wikit server, and it may revert to the standard Wikit server at any time with no need to change the contents of the database, and with no loss of information
+- make changes only to the information that is served in response to a HTTP request
+- wherever possible, integrate with the Wikit way of doing things
+
+##### Modifications to Wikit, in more detail #####
+
+(1a) - File wikit.vfs/lib/httpd/wikithttpd.tcl
+	Set Content-Type to be the same as in CGI mode
+(1b) - File wikit.vfs/lib/wikit/format.tcl
+	add a new backend renderer - proc Expand_Wiklet which calls StreamToWiklet.  Expand_Wiklet is exported from the namespace and is called from wikit.vfs/lib/wikit/web.tcl
+(1c) - File wikit.vfs/lib/wikit/web.tcl
+	if the $cmd (everything in the URI after the integer that gives the page) is '.integer.tcl', for a positive integer, then instead of text/html, serve a Wiklet:
+		send a header for content-type application/x-tcl
+		send a header of Tcl comments that describe the Wiklet, followed by 'proc wm' definition and a newline
+		fetch the page and expand it through the backend renderer Expand_Wiklet to filter the requested Wiklet
+
+(2) - File wikit.vfs/lib/wikit/format.tcl
+	Modifications to enable the dispatch of wiklet-enabled HTML pages
+	-  'set doWiklets 1' in two places (one is in the new proc wikletInjector) to enable the dispatch of wiklet-enabled HTML pages.  Set to 0 in either place to recover conventional Wikit HTML output
+	-  initialise variables newQ, resultCache, doneJSblock1 which store extra state for the page
+	-  modify the 'switch -exact -- $mode' statement for cases 'Q' and ''
+	-  add a new proc wikletInjector whose purpose is to return suitable HTML (with embedded Javascript) to use in place of $html_frag($state$mode) when the latter includes '
' and when this is necessary to Wiklet-enable the page
+	-  add new procs isPositiveInteger and validateInputs which are called only by wikletInjector
+
+The Javascript that is served is crude, but works (tested in Firefox 1.5.0.1/Linux, Firefox 1.5.0.4/Windows, Opera 9.00/Linux, Opera 9.00/Windows, MSIE 6.00/Windows, Konqueror 3.5.2/Linux).  Improvements will be welcomed.
+
+The modified Wikit has been tested both via wikithttpd, and as a CGI (from the command line with relevant environment variables set).
+
+##### What does the modified Wikit serve? #####
+
+Only URLs ending '/integer.integer.tcl' get special treatment.  URLs ending '.tcl@' etc get no special treatment.  Wiklets must be edited by editing their Wiki page in the usual way.
+
+An HTTP request for a Wiklet with a valid URL, and a Wiki page that exists, and a Wiklet identifier that exists, will be served the Wiklet.
+
+An HTTP request for a Wiklet with a valid URL, and a Wiki page that exists, but a Wiklet identifier that does not exist, will be served an almost-null Wiklet: the result of scraping the page and finding no Wiklet code.  The only reason for doing this (instead of sending a 404) is to avoid caching the string that is served by HTTP until the server discovers whether the Wiklet exists.
+
+An HTTP request for a Wiklet with a valid URL, and a Wiki page that does not exist, will be treated in the same way as any other request for a non-existent Wiki page: the "command" is ignored, the page served is the same as /2, and has Content-Type text/html.
+
+All other HTTP requests for /integer*.tcl and /integer*.tcl* are passed to the conventional Wikit code, and so are handled in the same way as at present:  the *.tcl or *.tcl* is treated as a bad command and is ignored; /integer is served if it it exists, /2 is served if it does not, and the Content-Type is text/html.