#!/usr/bin/perl -w # $Id: quikwiki.cgi,v 1.5 2007/06/26 04:26:53 kiesling Exp $ use warnings; my $httpheader =<QuikWiki! ENDTITLE my $endhtml=< ENDENDHTML # # Paths to Wiki pages if the site must keep QuikWiki in # another directory, like /cgi-bin. # my @wikipath = split ':', $ENV{WIKIPATH} if defined ($ENV{WIKIPATH}); # # If you don't want to add SetEnv or PassEnv statements to # httpd.conf, you can add paths here. # unshift @wikipath, qw(./); my $rcs_path = `which rcs`; #my $rcs_ok = ((-d 'RCS') && (length($rcs_path) && $rcs_path !~ /no rcs/))?1:0; my $rcs_ok = ((w_d ('RCS')) && (length($rcs_path) && $rcs_path !~ /no rcs/))?1:0; my $s = $ENV{SERVER_NAME}; my $q = $ENV{QUERY_STRING}; my $scriptname = $ENV{SCRIPT_NAME}; my $method = $ENV{REQUEST_METHOD}; my ($pboundary, $content, $content_length); my $referer = $ENV{HTTP_REFERER}; if ($method =~ /POST/) { ($pboundary) = ($ENV{CONTENT_TYPE} =~ /boundary=(.*)/); $content_length = $ENV{CONTENT_LENGTH}; binmode STDIN, ':crlf'; read STDIN, $content, $content_length; w_save ($content); } my ($word, $action, $data) = split '&', $q, 3; $word = 'HomePage' if ! $word; # $action = ($action and -f $word) ? $action : ((! -f $word ) ? 'edit' : 'view'); $action = ($action and w_f ($word)) ? $action : ((! w_f ($word)) ? 'edit' : 'view'); # my $page = (-f $word) ? w_read ($word) : "Describe $word here."; my $page = (w_f ($word)) ? w_read ($word) : "Describe $word here."; my $editor = < ENDEDIT if ($word =~ /doc|pod/) { print $httpheader . $starthtml; w_eval (w_f ('WikiHeader')) if w_f ('WikiHeader'); seek DATA, 0, 0; $d = join "", (); $dir = `pwd`; chomp $dir; chdir '/tmp' or do { w_pre ("chdir /tmp: $!\n"); return; }; open POD, "|pod2html" or do {w_pre ("doc: $!"); return; }; print POD $d; close POD; w_eval (w_f ('WikiFooter')) if w_f ('WikiFooter'); print $endhtml; unlink qw /pod2htmi.tmp pod2htmd.tmp/; chdir $dir or do { w_pre ("chdir $dir: $!\n"); return;}; exit 0; } if ($word =~ /self/) { seek DATA, 0, 0; w_pre( join "", ()); exit 0; } if ($word =~ /words/) { my $wordspage = undef; foreach my $d (@wikipath) { opendir DIR, $d or do {w_pre ("$d: $!");}; my @files = grep {/^[A-Z]/ && ! -d $_} readdir DIR; closedir DIR; @sortedwords = sort @files; foreach (@sortedwords) { if (filetype ("$d/$_") =~ /text/) { $wordspage .= qq{$_
}; } else { $wordspage .= qq{$_
}; } } } w_pre ($wordspage); exit 0; } if ($word =~ /image/) { no warnings; ($imword, $imname) = split /&/, $q; no warnings; $impage = qq{

$imname}; w_out ($impage); exit 0; } if ($action =~ /view/) { w_out ($page); exit 0; } if ($action =~ /edit/) { print $httpheader . $starthtml; print qq{Edit "$word"


}; print $editor . $endhtml; exit 0; } sub w_save { my $content = $_[0]; my ($newname) = ($content =~ /name=(.*?)&/); my ($text) = ($content =~ /.*?&text=(.*)&action=.*$/); $text =~ s/\+/ /g; $text =~ s/%(..)/pack ('c', hex ($1))/ge; $word=$newname; w_write ($newname, $text); w_out ($text); exit 0; } sub w_read { my $n = $_[0]; my $p; if (-f $n) { open WORD, $n or do {return "$n: $!"}; } elsif ($p = w_f ($n)) { open WORD, $p or do {return "$p: $!"}; } else { return "$p: $!"; } my $s = undef; while (defined ($l = )) { $s .= $l } close WORD; return $s; } sub w_f { foreach my $path (@wikipath) { return "$path/$_[0]" if -f "$path/$_[0]"; } return undef; } sub w_d { foreach my $path (@wikipath) { return "$path/$_[0]" if -d "$path/$_[0]"; } return undef; } sub w_write { my ($name, $text) = @_; my $rcspath = $filepath = $basepath = undef; $rcspath = w_d ('RCS') if w_d ('RCS'); $filepath = w_f ($name) if w_f ($name); ($basepath) = ($filepath =~ m|(.*/)|); if ($rcs_ok and ((! $filepath) or (! w_f ("$basepath/RCS/$name,v")))) { print STDERR "***\n"; `rcs -i -U -t-"Wiki Page $filepath." $filepath`; } rename $filepath, "$filepath.bak" if (-f $filepath && ! $rcs_ok); open OUT, ">$filepath" or do {w_pre ("Save $name: $!\n"); return 1; }; print OUT $text; print OUT "\n" if $text !~ /\n$/; close OUT; `ci -u -m'User revision.' $filepath` if $rcs_ok; } sub w_pre { my $page = $_[0]; print $httpheader . $starthtml; w_eval (w_f ('WikiHeader')) if w_f ('WikiHeader'); print qq{
$page
}; w_eval (w_f ('WikiFooter')) if w_f ('WikiFooter'); print $endhtml; } sub w_out { my $page = $_[0]; $page = words($page); $page = lines($page); print $httpheader . $starthtml; w_eval (w_f ('WikiHeader')) if w_f ('WikiHeader'); if ($page =~ /\<\%|\%\>/) {w_page_eval($page); } else {print qq|$page

|;} w_eval (w_f ('WikiFooter')) if w_f ('WikiFooter'); print $endhtml; exit 0; } sub w_eval { my $script = w_read ($_[0]); while ($script =~ /\<\%.*?\%\>/s) { my ($text, $expr, $rest) = ($script =~ /^(.*?)\<\%(.*?)\%\>(.*)$/s); my $l = lines ($text); print $l; eval $expr; print ('
'. $@.'
') if $@; $script = $rest; } print lines( $script); } sub w_page_eval { my $script = $_[0]; while ($script =~ /\<\%.*?\%\>/s) { my ($text, $expr, $rest) = ($script =~ /^(.*?)\<\%(.*?)\%\>(.*)$/s); print $text; eval $expr; print ('
'. $@.'
') if $@; $script = $rest; } print lines( $script); } sub words { $page = $_[0]; foreach my $st (split /\s|\r?\n|\'|\\|\"|\?|\.|\:|,|\*/, $page) { next if $st !~ /^[A-Z]/ || ! w_f ($st); $page =~ s/([^\w"<\?])$st([^\w"])/$1$st<\/a>$2/g if (filetype (w_f ($st)) =~ /text/); $page =~ s/([^\w"<\?])$st([^\w"])/$1$st$2/g if (filetype (w_f ($st)) =~ /png|jpeg/); } return $page; } sub filetype { my $path = $_[0]; return 0 if ! -f $path; my $c = w_read($path); return 'png' if (substr ($c, 1, 3) =~ /PNG/); return 'jpeg' if (substr ($c, 6, 4) =~ /JFIF/); return 'text'; } sub lines { my $page = $_[0]; my $expr = 0; # Replace blank lines with paragraph tags. # If line is indented, wrap in tags and
. $page =~ s/\n\s*?\n|\r\n\s*?\r\n/\n

\n/smg; # Bold and Italic # These are usually okay within expressions, because # they should be quoted in Perl. $page =~ s/\'\'\'(.*?)\'\'\'/$1<\/b>/smg; $page =~ s/\'\'(.*?)\'\'/$1<\/i>/smg; my $newpage = ''; # Other tags should be omitted within expressions. foreach my $l (split /(\n)/, $page) { $expr = 1 if ($l =~ /\<\%/); if ($expr) { $newpage .= $l; next; } $expr = 0 if ($l =~ /\%\>/); if ($l =~ /^(\t| )\*/) { $l =~ s/^(\t| )\*/

  • /; $newpage .= $l; } elsif (substr ($l, 0, 1) eq ' ') { local $i = 0; while ( substr($l, $i, 1) eq ' ') { substr($l, $i++, 1, ' '); } $newpage .= "$l
    \n"; } elsif ($l =~ /^----/) { $l =~ s/^----//; $newpage .= ('
    ' . $l); } else { $newpage .= "$l"; } } return $newpage; } __END__ =head1 NAME quikwiki.cgi - Simple Perl/CGI Wiki. =head1 SYNOPSIS # View the page named, "HomePage." http:///quikwiki/quikwiki.cgi?HomePage # Edit the page, "HomePage." http:///quikwiki/quikwiki.cgi?&action=edit # Create and edit page named, "NewPage." http:///quikwiki/quikwiki.cgi?NewPage&action=new # View the QuikReference page. http:///quikwiki/quikwiki.cgi?QuikReference # If quickwiki.cgi is renamed or symlinked to index.cgi: http:///quikwiki/?HomePage # Edit the page named, "MyPage." http:///quikwiki/?MyPage&action=edit # Create and edit page named, "NewPage." http:///quikwiki/?NewPage =head1 DESCRIPTION QuikWiki outputs HTML of text files ("pages") that contain Wiki markup tags (see, "L<"Text Markup">") and references to other pages and images ("words"). QuikWiki's words correspond to page and image files. When displayed, word references appear in HTML Ba hrefE> tags. By convention Wiki words start with uppercase letters and contain mixed-case alphanumeric characters. These are valid words. HomePage AreaCode916 TableOfContents Addresses "L<"Documentation Words">" and "L<"Action Words">," tell QuikWiki which action to take when displaying pages. URL parameters provide page and action words. If the URL does not contain arguments, B is the default page and B is the default action word. Refer to "L<"SYNOPSIS">," above. To display a list of the words that QuikWiki knows about, use the, "L<"words">," word. QuikWiki replaces JPEG and PNG words with the images and can display images separately with the L<"image"> word. Optionally, Revision Control System, if installed, can store page revisions. (See, "L<"Backups and Revisions">"). The template files, "WikiHeader," and, "WikiFooter," format page headers and footers. These files contain Perl code and are evaluated instead of displayed. (See, L<"Embedding Perl">.) =head2 Documentation Words These are the documentation words that QuikWiki recognizes. =head3 self Output the QuikWiki source code. To save in a text file, use a shell command similar to the following. lynx -dump -width 100 http:///?self >quikwiki.cgi =head3 doc | pod Print the POD documentation. =head3 words Display sorted list of words and links to pages. http:///?words =head2 Action Words These are the action words that QuikWiki recognizes. =head3 edit Display the editor form and edit a page. =head3 image Display an image on a separate page. http:///?image& =head3 new Create and edit a new page. =head3 view Display the page - this is the default. =head2 Text Markup =head3 Paragraphs Do not indent paragraphs. Separate paragraphs with a blank line. =head3 Character Emphasis B text begins and ends with three single quotes ('''). I (italic) text begins and ends with two single quotes (''). =head3 Preformatted Text Lines that begin with one or more spaces maintain their formatting and are printed in monospaced font. =head3 Horizontal Rules Lines that begin with four or more hyphens are displayed as rules. =head3 List Items List items begin with a tab and an asterisk, or eight spaces and an asterisk. =head2 Backups and Revisions QuikWiki uses RCS for revisions if the system has the B and B programs, and the document directory contains a B subdirectory. Make sure that the Web server can write to the RCS subdirectory and archive. QuikWiki uses non-strict locking for RCS revisions. Without RCS or a RCS subdirectory, QuikWiki renames previous versions of the page with a B<.bak> extension. =head2 Page Directories If Web site security requires that the QuikWiki CGI script be installed in a directory like cgi-bin, then you must tell QuikWiki where to find its pages. The Web server environment variable, I<$WIKIPATH,> contains a list of directories, separated by colons, that QuikWiki can search for its pages. Refer to the file, F for information about configuring, I<$WIKIPATH>. =head2 Embedding Perl Lines within, "<%," and, "%>," get evaluated by Perl and then displayed. Pages that contain embedded Perl code can generate dynamic content. Here is a page that displays, "Hello, World!" and the date and time in bold type. Hello, world! The date and time is: <% $t=localtime (); $a = qq{'''$t.'''}; print lines ($a); %> Values and operations are accessible via internal variables and functions. The variable B<$word,> for example, is the name of the currently displayed page, and B<$s> is the name of the HTTP server. The function B)> performs markup formatting. Variables that you define within a template script are local to that template. The templates, F, and, F, use embedded Perl to format page Headers and Footers. =head1 VERSION $Id: quikwiki.cgi,v 1.5 2007/06/26 04:26:53 kiesling Exp $ =head1 CREDITS The idea for QuikWiki, and a few of the coding tricks, came from Scott Walter's tinywiki, although the code is (slightly) less obfuscated. The text markup conventions come from WikiWiki. Written by Robert Kiesling, rkies@cpan.org. Copyright © 2003-2007 by Robert Kiesling. QuikWiki is licensed under the same terms as Perl. Refer to the file, "Artistic," for details. =cut __DATA__