~kris/9p

krisyotam.net

a15f9ce362ba4d456a781ce61e1046f8259a3ee0 — Kris Yotam 4 months ago
initial werc site scaffold
77 files changed, 5898 insertions(+), 0 deletions(-)

A README
A README.txt
A apps/blagh/app.rc
A apps/blagh/atom.tpl
A apps/blagh/convert.rc
A apps/blagh/jsonfeed.tpl
A apps/blagh/new_post.tpl
A apps/blagh/rss20.tpl
A apps/bridge/app.rc
A apps/bridge/comments_list.tpl
A apps/bridge/foot.tpl
A apps/dirdir/app.rc
A apps/dirdir/edit.tpl
A apps/dirdir/sidebar_controls.tpl
A apps/duckduckgo/HOWTO
A apps/duckduckgo/app.rc
A apps/duckduckgo/footer.inc.sample
A apps/hello/app.rc
A apps/paste/app.rc
A apps/wman/app.rc
A apps/wman/man_page.tpl
A apps/wman/page_list.tpl
A apps/wman/search.tpl
A apps/wman/section_list.tpl
A bin/aux/addwuser.rc
A bin/aux/bpst.rc
A bin/aux/gensitemaptxt.rc
A bin/aux/runtsts.rc
A bin/cgilib.rc
A bin/contrib/fix-rc-scripts
A bin/contrib/hgweb.config
A bin/contrib/hgwebdir.cgi
A bin/contrib/markdown.pl
A bin/contrib/md2html.awk
A bin/contrib/rc-httpd/handlers/authorize
A bin/contrib/rc-httpd/handlers/cgi
A bin/contrib/rc-httpd/handlers/dir-index
A bin/contrib/rc-httpd/handlers/error
A bin/contrib/rc-httpd/handlers/redirect
A bin/contrib/rc-httpd/handlers/serve-static
A bin/contrib/rc-httpd/handlers/static-or-cgi
A bin/contrib/rc-httpd/handlers/static-or-index
A bin/contrib/rc-httpd/lib/urldecode.awk
A bin/contrib/rc-httpd/rc-httpd
A bin/contrib/rc-httpd/select-handler
A bin/contrib/tcp80
A bin/contrib/urldecode.awk
A bin/contrib/urlencode.awk
A bin/contrib/webserver.rc
A bin/corehandlers.rc
A bin/fltr_cache.rc
A bin/template.awk
A bin/werc.rc
A bin/werc_errlog_wrap.rc
A bin/wercconf.rc
A bin/werclib.rc
A etc/initrc
A etc/users/GROUP_AND_USER_ACCOUNTS
A lib/404.tpl
A lib/default_master.tpl
A lib/footer.inc
A lib/headers.tpl
A lib/top_bar.inc
A pub/default_favicon.ico
A pub/style/imgs/sgl.png
A pub/style/sinorca-screen-alt.css
A pub/style/style.css
A pub/style/style.werc140.css
A pub/style/style_old.css
A sites/plan9.krisyotam.com/index.md
A sites/plan9.krisyotam.com/notes/index.md
A sites/plan9.krisyotam.com/patches/index.md
A sites/plan9.krisyotam.com/setup/index.md
A sites/plan9.krisyotam.com/tools/index.md
A tpl/_debug.tpl
A tpl/_users/login.tpl
A tpl/sitemap.tpl
A  => README +79 -0
@@ 1,79 @@
werc - a minimalist document management system
----------------------------------------------

Werc is a content management system and web (anti-)framework designed to be simple to
use, simple to setup, simple to hack on, and not get in the way while allowing
users easy customization.

For more information see the official website: http://werc.cat-v.org/


Installation
------------

Requirements:

* An http server that can handle CGIs
* Plan 9 from User Space: http://swtch.com/plan9port,
	Or 9base-tip: http://tools.suckless.org/9base,
	Or frontbase: http://openbsd.stanleylieber.com/frontbase

Note: Werc by default expects the Plan 9 tools to be installed under
/bin/, if you have installed them elsewhere you will need to edit the
#! line in bin/werc.rc and customize the $plan9port variable in your
etc/initrc.local.


Instructions:

Untar werc at your desired location, configure httpd to use
/path-to-your-werc-installation/bin/werc.rc as a cgi-script, it is recommended
that you make werc.rc handle all non-static files (this can be done by setting
it up as your 404 handler) and setup your virtual hosts to handle static files
by setting the document root for the domain to
/path-to-werc-installation/sites/yourdomain.com/, and create a directory for
your web site under sites/ where you can start adding content right away.

If you will want to allow updates via the web interface (eg., for wiki or
comments apps) make sure all files under sites/ are writable by the user your
cgi will run as, usually www-data, for example by doing: chown -R :www-data
sites/; chmod -R g+w sites/

If your Plan 9 binaries are located somewhere else than the standard /bin/ you
will need to edit the first line of bin/werc.rc (Note that p9p in particular is
picky about where it is located, once you run ./INSTALL you should *not* move
it to a different directory without running ./INSTALL again.)

For general configuration options copy etc/initrc to etc/initrc.local and
customize it as needed. Site (and directory) specific options can be set in a
sites/example.com/_werc/config file inside the site's directory. To customize
templates and included files you can store your own version of the files in
lib/ under sites/example.com/_werc/lib.

The source tree for the werc website is included under sites/werc.cat-v.org as
an example, feel free to use it as a template for your own site.

For more details see the documentation section of the website:
http://werc.cat-v.org/docs/


Contact
-------

For comments, suggestions, bug reports or patches join the werc mailing list
at: http://werc.cat-v.org or the irc channel #cat-v in irc.freenode.org

If you have a public website that uses werc I would love to hear about it and
get feedback about you experience setting it up.

Thanks
------

Garbeam, Kris Maglione, sqweek, soul9, mycroftiv, maht, yiyus, cinap_lenrek,
khm and many others for their ideas, patches, testing and other contributions.


License
-------

Werc is in the public domain.

A  => README.txt +38 -0
@@ 1,38 @@
PLAN9.KRISYOTAM.COM

Werc-powered site for Plan 9 tools, patches, notes, and
machine configuration. Served as a subdomain of krisyotam.com.


STRUCTURE

  sites/plan9.krisyotam.com/    Site content (markdown files)
    tools/                      Scripts and programs
    patches/                    Contributed patches
    notes/                      Reference material
    setup/                      Machine configs and install notes

  bin/                          Werc framework (rc shell)
  lib/                          Werc libraries
  tpl/                          HTML templates
  pub/                          Static assets (CSS, images)
  etc/                          Werc configuration
  apps/                         Werc apps (blog, hierarchies)


REQUIREMENTS

  Plan 9 from User Space (plan9port), or 9base, or frontbase
  A CGI-capable web server (nginx + fcgiwrap)


DEPLOY

  Configure nginx to pass requests to bin/werc.rc via CGI.
  The site directory name must match the Host header.


LICENSE

  Werc framework: MIT (Uriel Mangado)
  Site content: All rights reserved (Kris Yotam)

A  => apps/blagh/app.rc +142 -0
@@ 1,142 @@
fn conf_enable_blog {
    blagh_uri=$conf_wd
    blagh_dirs=$*
    if(~ $#blagh_dirs 0)
        blagh_dirs=( . )
    conf_enable_app blagh

    if(~ $"conf_blog_editors '')
        conf_blog_editors=blog-editors

    if(~ $"conf_max_posts_per_page '')
        conf_max_posts_per_page=32
}

fn blagh_init {
    if(~ $#blagh_dirs 0 && ~ $req_path */[bB]log/*) {
        blagh_uri=`{echo $req_path | sed 's,(/[bB]log/).*,\1,'}
        blagh_dirs=( . )
    }

    # Should not match sub-dirs!
    if(! ~ $#blagh_dirs 0) {
    # && test -d / `{echo '-a -d '^$blagh_root^$blagh_dirs}
        blagh_url=$base_url^$blagh_uri
        blagh_root=$sitedir^$blagh_uri
        if(check_user $conf_blog_editors) {
            editor_mode=on
            if(~ $"post_arg_date '')
                post_date=`{datei|sed 's,-,/,g'}
            if not
                post_date=$post_arg_date
            ll_add handlers_bar_left echo '<a href="'$blagh_uri'new_post">Make a new post</a>'
        }

        if(~ $req_path $blagh_uri) {
            handler_body_main=blagh_body
            u=$blagh_uri'index'
            extraHeaders=$"extraHeaders ^ \
'<link rel="alternate" type="application/atom+xml" title="ATOM" href="'$"u'.atom" />
<link rel="alternate" type="application/rss+xml" title="RSS" href="'$"u'.rss" />
<link rel="alternate" type="application/json" title="JSON" href="'$"blagh_uri'feed.json" />'
        }
        if not if(~ $req_path $blagh_uri^index.atom)
            blagh_setup_feed_handlers atom.tpl 'application/atom+xml'

        if not if(~ $req_path $blagh_uri^index.rss)
            blagh_setup_feed_handlers rss20.tpl 'text/xml; charset=utf-8'

				if not if(~ $req_path $blagh_uri^feed.json)
            blagh_setup_feed_handlers jsonfeed.tpl 'application/json; charset=utf-8'

        if not if(~ $req_path $blagh_uri^new_post && ! ~ $#editor_mode 0) {
            handler_body_main=( tpl_handler `{get_lib_file blagh/new_post.tpl apps/blagh/new_post.tpl} )
            if(~ $REQUEST_METHOD POST) {
                if(mkbpost $"post_arg_body $"post_date $"post_arg_title $post_arg_id)
                    post_redirect $blagh_uri
                if not
                    notify_errors=$status
            }
        }
        
    }
}

fn blagh_setup_feed_handlers {
    handler_body_main=NOT_USED_by_blagh_feeds
    res_tail=()
    http_content_type=$2
    headers=()
    master_template=apps/blagh/$1 # Should we allow tempalte override?
}

fn blagh_body {
    if (! ~ $"blogTitle '')
        echo '<h1>'$"blogTitle'</h1>'

    # Direct links to feeds are disabled because they are not very useful, add clutter and might waste pagerank.
    # An user can add this on their own using handlers_body_head anyway.
    #echo '<div style="text-align:right">(<a href="index.rss">RSS Feed</a>|<a href="index.atom">Atom Feed</a>)</div>'

    # XXX Not sure why this fixes issues with blog setup, probably bug in fltr_cache!
    for(p in `{get_post_list $blagh_root^$blagh_dirs}) {
        l=`{echo -n $p|sed 's!'$sitedir^'/?(.*)([0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9])(/[^/]+/)!\2 /\1\2\3!'}
        sed '1s!.*![&]('^$l(2)^') ('^$l(1)^')!' < $p/index.md 
        echo # Needed extra \n so markdown doesn't mess up the formatting, probably can be done in sed.
    } | $formatter
    # XXX BUG! Markdown [references] break because multiple markdown documents are merged. Should format each blog post independently.
    # TODO: use fltr_cache directly, that can fix the previous bug plus provide a perf boost by caching title generation.
}

fn get_post_list {
    # /./->/|/ done to sort -t| and order by date
    # Note: $paths in blagh_dirs should not contain '/./' or '|'
    ls -F $*^/./[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]/ >[2]/dev/null | sed -n '/'^$forbidden_uri_chars^'/d; s,/\./,/|/,; /\/$/p' | sort -r '-t|' +1 | sed -e 's,/+\|/+,/,' -e $conf_max_posts_per_page^'q'
}

fn mkbpost {
    bptext=$1
    bpdate=$2
    bptitle=$3
    bpid=$4
    _status=()
    if(~ $"bptext '')
        _status=($_status 'You need to provide a post body.')
    if(! ~ $"bpdate [0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9])
        _status=($_status 'Invalid date: '''^$"bpdate^'''') # XXX Should make semantic check.

    if(~ $#_status 0) {
        umask 002 # Let group write
        if(! ~ $"bpid '')
            bpid=`{echo -n '-'^$bpid | sed 's/'$forbidden_uri_chars'+/_/g; 1q'}
        
        ddir=$blagh_root^$bpdate^'/'
        n=`{ls $ddir >[2]/dev/null |wc -l}
        
        mkdir -p $ddir/$"n^$"bpid/
        {
            if(! ~ $"bptitle '') {
                echo $bptitle
                echo '========================================='
            }
            # TODO: Enable metadata
            #echo '* Posted:' `{date}
            #if(! ~ $#logged_user 0)
            #   echo '* Author: '$logged_user
            echo 
            echo $bptext
        }> $ddir/$"n^$"bpid/index.md 

        # Experimental support for http://pubsubhubbub.googlecode.com/
        if(! ~ $"conf_blog_pubsubdub_hub '') {
            ifs='' { p=`{echo $req_url|sed 's/new_post$/index.atom/'|url_encode } }
            dprint hget -p 'hub.mode=publish&hub.url='^$"p $conf_blog_pubsubdub_hub
            hget -d -h -p 'hub.mode=publish&hub.url='^$"p $conf_blog_pubsubdub_hub >[1=2] &
        }
    }
    status=$_status
}

fn strip_title_from_md_file {
    sed '1N; /^.*\n===*$/N; /.*\n===*\n$/d'
}

A  => apps/blagh/atom.tpl +58 -0
@@ 1,58 @@
<?xml version="1.0" encoding="utf-8"?>

%{
# See for more info:http://www.tbray.org/ongoing/When/200x/2005/07/27/Atomic-RSS
fn statpost {
    f = $1

    post_uri=$base_url^`{cleanname `{echo $f | sed -e 's!^'$sitedir'!!'}}^'/'
    title=`{read $f/index.md}
    by=`{ls -m $f | sed 's/^\[//g; s/].*$//g' >[2]/dev/null}
    ifs=() { summary=`{cat $f/index.md | strip_title_from_md_file | ifs=$difs {$formatter} } }
}
# rfc3339 date when feed was last updated.
fupdated = `{ndate -a `{date `{mtime `{ls $blagh_root$blagh_dirs/[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]/[0-9] | tail -1} | awk '{print $1}'}}}
%}

<feed xmlns="http://www.w3.org/2005/Atom"
    xmlns:thr="http://purl.org/syndication/thread/1.0">

% if(! ~ $"conf_blog_pubsubdub_hub '') {
%    echo '<link rel="hub" href="'$conf_blog_pubsubdub_hub'" />'
% }

    <link rel="self" href="%($base_url^$req_path%)"/>
    <id>%($base_url^$req_path%)</id>
    <icon><![CDATA[/favicon.ico]]></icon>

    <title><![CDATA[%($siteTitle%)]]></title>
    <subtitle><![CDATA[%($siteSubTitle%)]]></subtitle>

    <updated>%($fupdated%)</updated>
    <link href="."/>

% for(f in `{get_post_list $blagh_root$blagh_dirs}) {
%    statpost $f

    <entry>
% # Maybe we should be smarter, see: http://diveintomark.org/archives/2004/05/28/howto-atom-id, example: <id>tag:intertwingly.net,2004:2899</id>
        <id>%($post_uri%)</id>
        <link href="%($post_uri%)"/>
        <title><![CDATA[%($title%)]]></title>
% # <link rel="replies" href="2899.atom" thr:count="0"/>
        <author><name><![CDATA[%($by%)]]></name></author>

        <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
            <![CDATA[%($summary%)]]>
        </div></content>

% # rfc3339 date when entry was last updated.
% eupdated=`{ndate -a `{date `{mtime $f | awk '{print $1}'}}}
        <updated>%($eupdated%)</updated>
    </entry>

% }

</feed>

% exit 

A  => apps/blagh/convert.rc +20 -0
@@ 1,20 @@
#!/usr/bin/env rc

path=($PLAN9/bin/ $path)

for(p in *.md) {
    echo
    echo '========================='
    echo p $p
    pp=`{echo $p | sed 's/^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])[\-_](.*).md$/\1 \2 \3 \4/' }
    echo pp $pp

    d=$pp(1)^'/'^$pp(2)^'/'^$pp(3)^'/'^$pp(4)^'/'

    mkdir -p $d
    echo $pp(4) | sed -e 's/^[0-9]_//; s/_/ /g;' > $d/index.md
    echo '=================================' >> $d/index.md
    echo >> $d/index.md
    cat $p >> $d/index.md

}

A  => apps/blagh/jsonfeed.tpl +35 -0
@@ 1,35 @@
{
"version":       "https://jsonfeed.org/version/1",
"title":         "%($siteTitle%)",
"home_page_url": "%($"base_url%)",
"feed_url":      "%($"base_url^$"req_path%)",
"items": [
%{
fn statpost {
    f = $1
    post_uri=$base_url^`{cleanname `{echo $f | sed -e 's!^'$sitedir'!!'}}^'/'
    title=`{read $f/index.md}
    #ifs=() { summary=`{cat $f/index.md | crop_text 1024 ... | $formatter } }
    ifs=() { summary=`{cat $f/index.md | strip_title_from_md_file | ifs=$difs {$formatter| sed 's/"/\\"/g' | tr -d '\012' } } }
}
%}
% #for(f in `{get_post_list $blagh_root$blagh_dirs}) {
%
% postlist=`{get_post_list $blagh_root$blagh_dirs}
% postcount=0
% for(f in $postlist) {
%    statpost $f
        {
  "id": "%($post_uri%)",
  "url": "%($post_uri%)",
  "title": "%($title%)",
  "content_html": "%($summary%)"
  }
% postcount = `{echo $postcount  1+p | dc}
% if (! ~ $#postlist $postcount) { echo , }  
% }
]
}

% exit 


A  => apps/blagh/new_post.tpl +11 -0
@@ 1,11 @@
<div>
% notices_handler
<form method="POST"><fieldset>
    <legend>Submit a new blog post</legend>
    <textarea cols="94" rows=16" name="body">%($"post_arg_body%)</textarea><br />
    <label>Title: <input size="64" type="text" name="title" value="%($"post_arg_title%)" /></label>
    <label>Id: <input size="8" type="text" name="id" value="%($"post_arg_id%)" /></label>
    <label>Date: <input size="10" maxlength="10" type="text" name="date" value="%($"post_date%)" /></label>
    <input type="submit" value="Post" />
</fieldset></form>
</div>

A  => apps/blagh/rss20.tpl +43 -0
@@ 1,43 @@
<?xml version="1.0" encoding="UTF-8"?>

%{
fn statpost {
    f = $1
    post_uri = `{echo $f | sed 's,^'$sitedir',,'}
    title=`{read $f/index.md}
    post_uri=$base_url^`{cleanname `{echo $f | sed -e 's!^'$sitedir'!!'}}^'/'
    by=`{ls -m $f | sed 's/^\[//g; s/].*$//g' >[2]/dev/null}
    ifs=() {summary=`{ cat $f/index.md |strip_title_from_md_file| ifs=$difs {$formatter | escape_html} }}
}

%}

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <atom:link href="%($base_url^$req_path%)" rel="self" type="application/rss+xml" />
        <title><![CDATA[%($siteTitle%)]]></title>
        <link>%($base_url^$req_path%)</link>
        <description><![CDATA[%($blogDesc%)]]></description>
        <language>en-us</language>
        <generator><![CDATA[Tom Duff's rc, and Kris Maglione's clever hackery]]></generator>
%{
        # <webMaster>uriel99+rss@gmail.com (Uriel)</webMaster>
	# rfc2822 last time channel content changed.
	lbd=`{ndate -m `{date `{mtime `{ls $blagh_root$blagh_dirs/[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]/[0-9] | tail -1} | awk '{print $1}'}}}
	echo '<lastBuildDate>'$"lbd'</lastBuildDate>'
	# rfc2822 publication date for content in the channel.
	pubdate=`{ndate -m}
        for(f in `{get_post_list $blagh_root$blagh_dirs}){
            statpost $f
%}
        <item>
            <title><![CDATA[%($title%)]]></title>
            <author><![CDATA[%($by%)@noreply.cat-v.org (%($by%))]]></author>
            <link>%($post_uri%)</link>
            <guid isPermaLink="true">%($post_uri%)</guid>
            <pubDate>%($pubdate%)</pubDate>
            <description> %($summary%) </description>
        </item>
%        }
    </channel>
</rss>

A  => apps/bridge/app.rc +103 -0
@@ 1,103 @@
comment_file_types=(md html)

fn conf_enable_comments {
    if(~ $1 -n) {
        allow_new_user_comments=yes
        shift
    }
    if not if(~ $1 -a) {
        bridge_anon_comments=yes 
    }
    enable_comments=yes
    groups_allowed_comments=$*
    conf_enable_app bridge
}

fn bridge_init {
    if(~ $#enable_comments 1 && ! ~ `{ls $local_path.$comment_file_types >[2]/dev/null|wc -l} 0) {

        comments_dir=$sitedir$req_path'_werc/comments'
        if(~ $REQUEST_METHOD GET && test -d $comments_dir)
            ll_add handlers_body_foot template apps/bridge/comments_list.tpl

        if(check_user $groups_allowed_comments || {~ $#logged_user 0 && ~ 1 $#allow_new_user_comments $#bridge_anon_comments}) {

            if(~ $#post_arg_bridge_post 1) {
                ll_add handlers_body_foot template apps/bridge/foot.tpl

                if(mk_new_comment $comments_dir)
                    post_redirect $base_url^$req_path
                if not
                    saved_comment_text=$post_arg_comment_text
            }
            if not if(~ $REQUEST_METHOD GET)
                ll_add handlers_body_foot template apps/bridge/foot.tpl
        }
        if not if(~ $REQUEST_METHOD GET)
            ll_add handlers_body_foot echo '<hr><p>To post a comment you need to <a href="/_users/login">login</a> first.</p>'
    }
}

fn validate_new_user {
    usr=$1; pass=$2; pass2=$3
    _status=()

    if(~ $"usr '' || ! echo $usr |sed 1q|grep -s '^'$allowed_user_chars'+$')
        _status='Requested user name is invalid, must match: '^$allowed_user_chars^'+'
    if not if(test -d etc/users/$usr)
        _status='Sorry, user name '''^$usr^''' already taken, please pick a different one.'

    if(~ $"pass '' || ! ~ $"pass $"pass2)
        _status=($_status 'Provided passwords don''t match.')

    status=$_status
}


fn mk_new_comment {
    _status=()
    dir=$1
    if(~ $"post_arg_comment_text '')
        _status='Provide a comment!'
    if not if(~ $#logged_user 0) {
        if(! ~ $#allow_new_user_comments 0) {
            if(validate_new_user $"post_arg_comment_user $post_arg_comment_passwd $post_arg_comment_passwd2) {
                u=$post_arg_comment_user':'$post_arg_comment_passwd
                dir=$comments_dir^'_pending'
                # XXX: This doesn't work because we then do a redirect. 
                notify_notes='Saved comment and registration info, they will be enabled when approved by an admin.'
            }
            if not
                _status=$status
        }
        if not if(! ~ $#bridge_anon_comments 0) {
            if(~ $"post_arg_ima_robot 'not')
                u='Glenda' # Anonymous
            if not
                _status='You are a robot!'
        }
        if not
            _status='You need to log in to comment.'
    }
    if not if(check_user $groups_allowed_comments)
        u=$logged_user
    if not
        _status='You are not a member of a group allowed to comment.'

    if(~ $#_status 0) {
        umask 002

        dir=$dir'/'`{date -n} # FIXME Obvious race
        mkdir -m 775 -p $dir &&
            echo $u > $dir/user &&
            echo $current_date_time > $dir/posted &&
            echo $post_arg_comment_text > $dir/body
        _s=$status
        if(! ~ $"_s '') {
           dprint 'ERROR XXX: Could not create comment: ' $_s 
            _status='Could not post comment due internal error, sorry.'
        }
    }
    notify_errors=$_status
    status=$_status
}

A  => apps/bridge/comments_list.tpl +13 -0
@@ 1,13 @@
<hr>
<h2>Comments</h2>

% for(c in `{ls $comments_dir/}) {
%    if(test -s $c/body) {
        <div class="comment">
            <h5>By: <i>%(`{cat $c/user}%)</i></b> (%(`{cat $c/posted}%))
        </h5>
%       cat $c/body | escape_html | sed 's,$,<br>,'
        <hr></div>
%    }
% }


A  => apps/bridge/foot.tpl +37 -0
@@ 1,37 @@
<hr>

% notices_handler
<form action="" method="post">
    <textarea name="comment_text" id="comment_text" cols="80" rows="16">%($"saved_comment_text%)</textarea>
    <br>
    <input type="submit" name="bridge_post" value="Post a comment">

% if(~ $#logged_user 0) {
%   if(~ $#allow_new_user_comments 1) {
    <label>New user name:
        <input type="text" name="comment_user" value="%($"post_arg_comment_user%)">
    </label>

    <label>Password:
        <input type="password" name="comment_passwd" value="">
    </label>

    <label>Repeat password:
        <input type="password" name="comment_passwd2" value="">
    </label>
    <div style="font-size: 70%">
    Enter your desired user name/password and after your comment has been reviewed by an admin it will be posted and your account will be enabled. If you are already registered please <a href="/_users/login">login</a> before posting.
    </div>
%   }
%   if not if(~ $#bridge_anon_comments 1) {
    <label>Is <a href="http://glenda.cat-v.org">Glenda a cute bunny</a>?
        <select name='ima_robot'>
        <option value="yes">No</option>
        <option value="not">Yes</option>
        <option value="foobar">I hate bunnies!</option>
        <option value="robot">I'm a robot!</option>
        </select>
    </label>
%   }
% }
</form>

A  => apps/dirdir/app.rc +40 -0
@@ 1,40 @@
fn conf_enable_wiki {
    enable_wiki=yes
    wiki_editors_groups=$* 
    conf_enable_app dirdir
}

fn dirdir_init {
    if(! ~ $#enable_wiki 0 && check_user $wiki_editors_groups) {
        lp=$local_path
        # werc.rc doesn't append /index when $local_path doesn't exist
        # maybe it should, but for now we can fix it up here.
        if(~ $lp */) 
            lp=$lp^'index'
        dirdir_file=$lp.md
        dirdir_dir=$dirdir_file^'_werc/dirdir/'
 
        if(~ 1 $#post_arg_dirdir_edit $#post_arg_dirdir_preview)
            handler_body_main=(tpl_handler `{get_lib_file dirdir/edit.tpl apps/dirdir/edit.tpl})

        if not if(! ~ '' $"post_arg_dirdir_save $"post_arg_edit_text)
            save_page

        if not if(~ $"handler_body_main  '' || {~ $REQUEST_METHOD GET && test -f $local_path.md})
            ll_add handlers_bar_left tpl_handler apps/dirdir/sidebar_controls.tpl
    }
}

fn save_page {
    dirdir_verdir=$dirdir_dir/^`{date -n}^/
    mkdir -p $dirdir_verdir
    umask 002

    # XXX Use a tmp file and  mv(1) to ensure updates are atomic?
    echo $logged_user > $dirdir_verdir/author
    echo $post_arg_edit_text > $dirdir_verdir/data 
    echo $post_arg_edit_text > $dirdir_file

    post_redirect $base_url^$req_path
    #notify_notes='Saved <a href="'$"req_path'">'$"req_path'</a>!'
}

A  => apps/dirdir/edit.tpl +25 -0
@@ 1,25 @@
<div>
    <h1>Editing: <a href="%($req_path%)">%($req_path%)</a></h1>
    <br>
    <form action="" method="POST">
        <textarea name="edit_text" id="edit_text" cols="80" rows="43">%{
# FIXME Extra trailing new lines get added to the content somehow, should avoid it.
            if(~ $#post_arg_edit_text 0 && test -f $dirdir_file)
                cat $dirdir_file | escape_html
            if not
                echo -n $post_arg_edit_text | escape_html

        %}</textarea>
        <br>
        <input type="submit" name="dirdir_save" value="Save">
        <input type="submit" name="dirdir_preview" value="Preview">
        <small>DirDir documents are written using <a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a>.</small>
    </form>
</div>

% if(! ~ $"post_arg_dirdir_preview '') {
            <h2>Preview:</h2>
            <div id="preview">
%               echo $post_arg_edit_text | $formatter
            </div>
% }

A  => apps/dirdir/sidebar_controls.tpl +3 -0
@@ 1,3 @@
<form action="" method="POST">
<input type="submit" name="dirdir_edit" value="Edit page" />
</form>

A  => apps/duckduckgo/HOWTO +20 -0
@@ 1,20 @@
The default path for site search is /_search/.  Assuming you want to keep
that default, you could enable site search like so:


mkdir -p /www/werc/sites/MYSITE/_search/_werc/
echo 'conf_enable_duckduckgo' > /www/werc/sites/MYSITE/_search/_werc/config
mkdir -p /www/werc/sites/MYSITE/_werc/lib/
cp /www/werc/apps/duckduckgo/footer.inc.sample /www/werc/sites/MYSITE/_werc/lib/footer.inc

Searches will POST to /_search/ and from there get redirected to Duck Duck 
Go with a site:$SERVER_NAME prefix.  To have the search path URL be some-
thing different, you'll have to edit line 23 of app.rc to point to the new
path.

TODO: 
* Make it automatically work no matter which directory the app is enabled in.
* OR make the search path a configuration option.
* Provide a template for non-footer deployment
* Enable the search path itself to serve a search form to GET requests


A  => apps/duckduckgo/app.rc +30 -0
@@ 1,30 @@
fn conf_enable_duckduckgo {
    enable_duckduckgo=yes
    conf_enable_app duckduckgo
		pageTitle='Site Search'
}


fn duckduckgo_init {
	get_post_args q
	if (! ~ $#q 0) {
		redirect_string = 'https://duckduckgo.com/?q=site:'$SERVER_NAME^'+'^$"q
		http_redirect $redirect_string '302 Found'
	}
	if not {
		handler_body_main='duckduckgo_body'
	}
}

fn duckduckgo_body {
    echo '
<h1>Site search</h1>
<h2>using DuckDuckGo</h2>
<form action="/_search/" method="POST">
<label for="searchtext">Site search:</label> 
<input type="text" id="searchtext" name="q" placeholder="Search text...">
<input type="submit" value="Search">
</form>'

}


A  => apps/duckduckgo/footer.inc.sample +3 -0
@@ 1,3 @@
<div><a href="http://werc.cat-v.org">Powered by werc</a></div>

<div><form action="/_search/" method="POST"><label for="searchtext">Site search:</label> <input type="text" id="searchtext" name="q" placeholder="Enter search text..."><input type="submit" display="Search"></form></div>

A  => apps/hello/app.rc +10 -0
@@ 1,10 @@
fn hello_init {
    if(~ $req_path /hello) {
        handler_body_main='hello_body'
        pageTitle='Hi title!'
    }
}

fn hello_body {
    echo 'Hello world!'
}

A  => apps/paste/app.rc +45 -0
@@ 1,45 @@
fn conf_enable_wercpaste {
	paste_url=$conf_wd
	if (~ $#paste_dir 0) { paste_dir=`{pwd} }
	conf_enable_app wercpaste
}

fn wercpaste_init {
	if (~ $REQUEST_METHOD POST && ~ $post_arg_url url && ~ $req_path $paste_url ) { # incoming paste 
		now=`{ date -n }
		cksum=`{ echo $"post_arg_paste | sum | awk '{ print $1 }' }
		if (~ $cksum '1715a8eb' ) { # empty paste; discard
			post_redirect $base_url^$paste_url
		}
		if not {  # save and redirect
			# TODO: stop using echo
			# env var size limit is 16kb, this thing dies with larger input.
			echo $"post_arg_paste > $paste_dir^/^$now^.^$cksum
			# uncomment the following line to redirect to the pasted file
			#post_redirect $base_url^$paste_url^$now^.^$cksum
			# uncomment the following line instead to just return the url
			echo 'Content-type: text/plain'; echo ''; exec echo $base_url^$paste_url^$now^.^$cksum
		}
	} 
	if not { # show a paste if there is one
		if (test -r $werc_root/$local_path && ~ $QUERY_STRING raw ) { 
				echo 'Content-type: text/plain'; echo ''; exec cat $werc_root/$local_path
			} 
	}

# drop a textbox 
	if (~ $REQUEST_METHOD GET ) { handler_body_main='begforpaste' }

}

fn begforpaste {
	echo '<article class="pastebox">
	<h3 style="text-align: center">pasted data is not publically indexed</h3>
	<form action="'$paste_url'" method="post" style="margin:2em">
	<textarea name="paste" cols="120" rows="20" required style="display: block; margin: 0 auto 0 auto" ></textarea><br>
	<input type="submit" name="submit" value="SUBMIT" style="display: block; margin: 0 auto 0 auto" ><br><br>
	<span style="display: none"><input type="text" name="url" value="url" > (do not change) </span>
	</form>
	</article>
	'
}

A  => apps/wman/app.rc +89 -0
@@ 1,89 @@
fn conf_enable_wman {
    wman_tmac=an
    wman_base_uri=$conf_wd
    wman_man_path=$*
    if(~ $#wman_man_path 0)
        wman_man_path=$wman_base_uri
    conf_enable_app wman
}

wman_junk_filter='/(\/(INDEX|\.cvsignore|_.*)|\.9p|\.html)$/d; s!/man([0-9]+/[^/]+)$!/\1!; '
fn wman_ls_pages {
    ls $* \
        | sed $dirfilter^$wman_junk_filter^' s/\.([0-9]|9p)$//; s!/0intro$!/intro!' \
        | sort -u
}
fn wman_init {
    ifs=$ifs^'/' { p=`{echo $req_path | sed 's!^'^$wman_base_uri^'!!'} }
    wman_cat=$p(1)
    wman_page=$p(2)
    if(~ $#wman_unix_mode 1) {
        wman_cp='man'
        wman_pe=.^$"wman_cat
    }

    if(! ~ $"wman_cat '') {
        wman_cat_path=$wman_man_path^/^$"wman_cp^$p(1)
        if(! ~ $"wman_page '') {
            wman_page_file=$wman_page^$"wman_pe
            # Hack to handle 0intro files.
            if(~ $wman_page intro && test -f $wman_cat_path^/0^$"wman_page_file)
                 wman_page_file=0^$"wman_page_file
            wman_page_file=$wman_cat_path^/^$"wman_page_file
            x=`{echo $"req_path|sed 's%.*/([^/]+)/'$"wman_cat'/'^$"wman_page^'%\1%; s%_% %g'}
            pageTitle=$wman_page' page from Section '$wman_cat' of the '^$"x' manual'
        }
    }

    wman_cat_list=`{ls -F $wman_man_path/*/  \
        | sed -e $wman_junk_filter -e 's!.*/([^/]+)/[^/]+$!\1!; /[0-9]+/!d' \
        | sort -un}

    synth_paths=($wman_base_uri$wman_cat_list'/')

    if(~ $req_path $wman_base_uri && ~ $"handler_body_main '')
       handler_body_main=(tpl_handler apps/wman/section_list.tpl)
    if not if(~ $req_path $wman_base_uri^*) {
        #^*/[a-z0-9]*[a-z]* $wman_base_uri^*/*[a-z]*[a-z0-9] $wman_base_uri^*/[a-z])
        if(echo $req_path | grep -s '^'^$wman_base_uri^'/*[0-9]+/[0-9a-z\-\+\.]+$')
           if(test -f $wman_page_file) # Check for 404
               handler_body_main=(tpl_handler apps/wman/man_page.tpl)
        if not if(~ $req_path $wman_base_uri^*/)
           handler_body_main=(tpl_handler apps/wman/page_list.tpl)
        if not if(~ $p(2) [A-Z]* [0-9][A-Z]*) # Correct badly capitalized links
            perm_redirect $wman_base_uri^$p(1)^/^`{echo $p(2) |tr 'A-Z' 'a-z'}
    }

    # Search
    ll_add handlers_body_head tpl_handler apps/wman/search.tpl
    if(! ~ $"post_arg_wman_search '') {
        s=`{echo $post_arg_wman_search | sed 's/[^a-zA-Z0-9\-\.]+//g; s/\.+/./g; 1q'}
        ifs='' { wman_search_results=`{wman_ls_pages $wman_man_path/*/*^$"s^*} }
        if(! ~ $"post_arg_go '' && ~ `{echo -n $wman_search_results|wc -l} 1)
            post_redirect $wman_base_uri^`{echo $wman_search_results|awk -F/ '{print $(NF-1)"/"$NF}'}
    }

}

fn wman_get_section_desc { 
    cat $wman_man_path/^$"wman_cp^$1/0intro* >[2]/dev/null| sed '1,2d; s!intro \\- [Ii]ntroduction to !!; 3q;' 
}

fn wman_page_gen {
    #troff -manhtml $1| troff2html -t 'Plan 9 from User Space'
    troff -N -m$wman_tmac $1 | wman_out_filter
}

fn wman_out_filter {
    wman_default_out_filter
}

fn wman_default_out_filter {
	# col -x syntax is the same for UNIX and Plan 9.
    escape_html \
    | sed 's!([\.\-a-zA-Z0-9]+)\(('^`{echo $wman_cat_list|tr ' ' '|'}^')\)!<a href="../\2/\1">&</a>!g' \
    | awk '/^$/ {if(n != 1) print; n=1; next} /./ {n=0; print}' \
    | col -x
}



A  => apps/wman/man_page.tpl +3 -0
@@ 1,3 @@
<pre>
% wman_page_gen $wman_page_file
</pre>

A  => apps/wman/page_list.tpl +11 -0
@@ 1,11 @@
% d=`{wman_get_section_desc $wman_cat} 
<h1>Manual pages - Section %($wman_cat%): %($"d%)</h1>

<ul style="float:left">
%{
wman_ls_pages $wman_cat_path \
    | awk -F/ '{ print "<li><a href=\""$(NF)"\">"$(NF)"</a></li>" }
    NR%20 == 0 { print "</ul><ul style=\"float: left\">" }'
%}
</ul>


A  => apps/wman/search.tpl +20 -0
@@ 1,20 @@
<form action="" method="POST">
<fieldset>
    <input type="text" name="wman_search" value="%($"s%)" />
    <input type="submit" name="go" value="Feel Lucky" />
    <input type="submit" value="Search" />

% if(! ~ $"post_arg_wman_search '') {
%   if(~ $"wman_search_results '') {
        No matches found for <i>'%($post_arg_wman_search%)'</i>.
%   }
%   if not {
        <ul>
%       echo $wman_search_results|awk -F/ '$(NF-1) ~ "^[0-9]+$" {printf "<li><a href=\"'$wman_base_uri'%s/%s\" />%s(%s)</a></li>", $(NF-1),$NF, $NF, $(NF-1)}'
        </ul>
%   }
% }

</fieldset>
</form>


A  => apps/wman/section_list.tpl +11 -0
@@ 1,11 @@
<h1>Manual Sections</h1>

<ul style="text-transform: capitalize;">
% for(c in $wman_cat_list) {
    <li><a href="%($c%)/"><b>Section: %($c%)</b></a>
%   wman_get_section_desc $c 
%   if(~ $status '' '|')
%       echo '(<a href="'$c'/intro">intro</a>)'
    </li>
% }
</ul>

A  => bin/aux/addwuser.rc +33 -0
@@ 1,33 @@
#!/bin/rc

if(! ~ $#werc_root 0)
    cd $werc_root

fn usage {
    if(! ~ $#* 0)
        echo $0: $* >[1=2]
    echo 'Usage:' $0 'user_name user_password [groups ...]' >[1=2]
    exit usage
}

if(! test -d etc/users/)
    usage 'Run for root of werc installation or set $werc_root'

user_name=$1
shift
user_pass=$1
shift
user_groups=$*

if(~ $"user_name '' || ~ $"user_pass '')
    usage 

mkdir etc/users/$user_name
echo $user_pass > etc/users/$user_name/password

if(! ~ $#user_groups 0)
    for(g in $user_groups) {
        mkdir -p etc/users/$g
        echo $user_name >> etc/users/$g/members
    }
  

A  => bin/aux/bpst.rc +64 -0
@@ 1,64 @@
#!/bin/rc

path=( $PLAN9/bin  $path )
base=.

if(~ $#user 0)
    user=`{whoami}

file=(); title=();
bloguser=$user
while(! ~ $#* 0) {
    switch($1) {
    case -u
        base=/gsoc/www/people/$user/blog/
    case -b
	shift
        base=$1
    case -f
	shift
        file=$1
    }
    shift
}

if(~ $"EDITOR '')
    EDITOR=vi

if(~ $#file 0 || ! test -f $file) {
    file=/tmp/blogtmp.$pid
    rm $file >[2]/dev/null
    touch $file
}

$EDITOR $file
ispell $file
rm $file.bak >[2]/dev/null

fn mkbpost {
        umask 002 # Let group write
        bptext=$1
        if(! ~ $#2 0)
            bpid=`{echo -n '-'^$"bpid | sed 's/'$forbidden_uri_chars'+/_/g; 1q'}
        d=`{/bin/date +%F|sed 's,-,/,g'}
        
        ddir=$blagh_root^$d^'/'
        n=`{ls $ddir >[2]/dev/null |wc -l}
        
        mkdir -p $ddir/$"n^$"bpid/
        {
            # TODO: Enable metadata
            #echo '* Posted:' `{date}
            #if(! ~ $#logged_user 0)
            #   echo '* Author: '$logged_user
            cat $bptext 
        }> $ddir/$"n^$"bpid/index.md 
}

forbidden_uri_chars='[^a-zA-Z0-9_+\-\/\.]'
blagh_root=$base

if(test -s $file)
    mkbpost $file
if not
    echo Empty file!

A  => bin/aux/gensitemaptxt.rc +14 -0
@@ 1,14 @@
#!/bin/rc
# DEPRECATED: sitemap.tpl now generates and updates a sitemap.txt when requested, and is also more smart than this simplistic script.

for(d in sites/*/) {
echo $d
9 du -a $d | awk '/\.(md|html)$/ { print $2 }; {}' | 9 sed -e 's/\.(md|html)$//' -e 's,/index$,/,' -e 's,^sites/,http://,' > $d/sitemap.txt

if(! test -f $d/robots.txt) {
	echo generating missing robots.txt for $d
	echo $d|sed 's,sites/,Sitemap: http://,; s/$/sitemap.txt/;' > $d/robots.txt
	cat $d/robots.txt
}

}

A  => bin/aux/runtsts.rc +16 -0
@@ 1,16 @@
#!/bin/rc

tstdom='http://test.cat-v.org'

cd sites/tst.cat-v.org

tstfiles=`{du -a |awk '/\.tst$/ { print $2 }; {} ' | sed 's/^\.//; s/\.tst$//'}

for(f in $tstfiles) {
    ifs='
' { tsts=`{cat ./$f.tst} }
    
    for(t in $tsts) {
        echo tst $t
    }
}

A  => bin/cgilib.rc +236 -0
@@ 1,236 @@
# Useful CGI stuff

fn dprint { echo $* >[1=2] }
fn dprintv { { for(v in $*) { echo -n $v^'#'^$#$v^'=' $$v '; '  }; echo } >[1=2] }
fn echo {if(! ~ $1 -n || ! ~ $2 '') /bin/echo $*}
fn escape_html { sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g' $* }

fn http_redirect {
    if(~ $1 http://* https://*)
        t=$1
    if not if(~ $1 /*)
        t=$"base_url^$1
    if not
        t=$"base_url^$"req_path^$1
    exec /bin/echo 'Status: '^$2^'
Location: '^$t^'

'
    exit
}
fn perm_redirect { http_redirect $1 '301 Moved Permanantly' }
fn post_redirect { http_redirect $1 '303 See Other' }


# Note: should check if content type is application/x-www-form-urlencoded?
# Should compare with http://www.shelldorado.com/scripts/cmds/urlgetopt.txt
fn load_post_args {
    if(~ $REQUEST_METHOD POST && ~ $#post_args 0) {
        ifs='&
'       for(pair in `{cat}) {
            ifs='=' { pair=`{echo -n $pair} }
            n='post_arg_'^`{echo $pair(1)|nurldecode|tr -cd 'a-zA-Z0-9_'}
            post_args=( $post_args $n )
            ifs=() { $n=`{echo -n $pair(2)|nurldecode|tr -d '
'} }
        }
        pair=()
    }
    if not
        status='No POST or post args already loaded'
}
# Status is () if at least one arg is found. DEPRECATED: access vars directly.
fn get_post_args {
    load_post_args
    _status='No post arg matches'
    for(n in $*) {
        v=post_arg_$n
        if(! ~ $#$v 0) {
            $n=$$v
            _status=()
        }
    }
    status=$_status
}

# This seems slightly improve performance, but might depend on httpd buffering behavior.
fn awk_buffer {
    awk '{
        buf = buf $0"\n"
        if(length(buf) > 1400) {
            printf "%s", buf
            buf = ""
        }
    }
    END { printf "%s", buf }'
}

fn nurldecode { urlencode -d || url_decode}	# GROSS

fn url_decode {
awk '
BEGIN {
    hextab ["0"] = 0; hextab ["8"] = 8;
    hextab ["1"] = 1; hextab ["9"] = 9;
    hextab ["2"] = 2; hextab ["A"] = hextab ["a"] = 10
    hextab ["3"] = 3; hextab ["B"] = hextab ["b"] = 11;
    hextab ["4"] = 4; hextab ["C"] = hextab ["c"] = 12;
    hextab ["5"] = 5; hextab ["D"] = hextab ["d"] = 13;
    hextab ["6"] = 6; hextab ["E"] = hextab ["e"] = 14;
    hextab ["7"] = 7; hextab ["F"] = hextab ["f"] = 15;
}
{
    decoded = ""
    i = 1
    len = length ($0)
    while ( i <= len ) {
        c = substr ($0, i, 1)
        if ( c == "%" ) {
            if ( i+2 <= len ) {
                c1 = substr ($0, i+1, 1)
                c2 = substr ($0, i+2, 1)
                if ( hextab [c1] == "" || hextab [c2] == "" ) {
                    print "WARNING: invalid hex encoding: %" c1 c2 | "cat >&2"
                } else {
                    code = 0 + hextab [c1] * 16 + hextab [c2] + 0
                    c = sprintf ("%c", code)
                    i = i + 2
                }
            } else {
                print "WARNING: invalid % encoding: " substr ($0, i, len - i)
            }
        } else if ( c == "+" ) {
            c = " "
        }
        decoded = decoded c
        ++i
    }
    printf "%s", decoded
}
'
}

fn nurlencode { urlencode || url_encode }	# GROSS

fn url_encode {
    awk '
    BEGIN {
    # We assume an awk implementation that is just plain dumb.
    # We will convert an character to its ASCII value with the
    # table ord[], and produce two-digit hexadecimal output
    # without the printf("%02X") feature.

    EOL = "%0A"     # "end of line" string (encoded)
    split ("1 2 3 4 5 6 7 8 9 A B C D E F", hextab, " ")
    hextab [0] = 0
    for ( i=1; i<=255; ++i ) ord [ sprintf ("%c", i) "" ] = i + 0
    if ("'^$"EncodeEOL^'" == "yes") EncodeEOL = 1; else EncodeEOL = 0
    }
    {
    encoded = ""
    for ( i=1; i<=length ($0); ++i ) {
        c = substr ($0, i, 1)
        if ( c ~ /[a-zA-Z0-9.-]/ ) {
        encoded = encoded c     # safe character
        } else if ( c == " " ) {
        encoded = encoded "+"   # special handling
        } else {
        # unsafe character, encode it as a two-digit hex-number
        lo = ord [c] % 16
        hi = int (ord [c] / 16);
        encoded = encoded "%" hextab [hi] hextab [lo]
        }
    }
    if ( EncodeEOL ) {
        printf ("%s", encoded EOL)
    } else {
        print encoded
    }
    }
    END {
        #if ( EncodeEOL ) print ""
    }
' $* 
}

# Cookies
fn set_cookie {
    # TODO: should check input values more carefully
    name=$1
    val=$2
    extraHttpHeaders=( $extraHttpHeaders 'Set-cookie: '^$"name^'='^$"val^'; path=/;' )
}
fn get_cookie {
    ifs=';' { co=`{echo $HTTP_COOKIE} }

    # XXX: we might be adding a trailing new line?
    # The ' ?' is needed to deal with '; ' inter-cookie delimiter
    { for(c in $co) echo $c } | sed -n 's/^ ?'$1'=//p' 
}


fn static_file {
    echo -n 'Content-Type: '
    select_mime $1
    echo
    exec cat $1
}

fn select_mime {
    m='text/plain'
    if(~ $1 *.css)
        m='text/css'
    if not if(~ $1 *.ico)
        m='image/x-icon'
    if not if(~ $1 *.png)
        m='image/png'
    if not if(~ $1 *.jpg *.jpeg)
        m='image/jpeg'
    if not if(~ $1 *.gif)
        m='image/gif'
    if not if(~ $1 *.pdf)
        m='application/pdf'
    echo $m
}

##############################################
# Generic rc programming helpers

# Manage nested lists
fn ll_add {
    _l=$1^_^$#$1
    $_l=$*(2-)
    $1=( $$1 $_l )
}
# Add to the head: dangerous if you shrink list by hand!
fn ll_addh {
    _l=$1^_^$#$1
    $_l=$*(2-)
    $1=( $_l $$1 )
}


NEW_LINE='
'

# crop_text [max_lenght [ellipsis]] 
# TODO: Option to crop only at word-delimiters.
fn crop_text {
    m=512
    e='...'
    if(! ~ $#1 0)
        m=$1
    if(! ~ $#2 0)
        e=$2

    awk -v 'max='^$"m -v 'ellipsis='$e '
    {
        nc += 1 + length;
        if(nc > max) {
            print substr($0, 1, nc - max) " " ellipsis
            exit
        }
        print
    }' 
}



A  => bin/contrib/fix-rc-scripts +27 -0
@@ 1,27 @@
#!/usr/local/plan9/bin/rc

# Fix rc shell scripts to find rc without launching env every time.
# Invoke with rc and plan9 versions of grep and ed in $PATH

# If your system lacks which (e.g. some gnu/linux)
# substitute the full path to rc in this line:
rc=/usr/local/plan9/bin/rc
firstline='#!'$"rc

if(~ $#* 0)	files = *
if not	files = $*

myname = `{basename $0}

for(file in $files) {
	if(test -d $file) $0 $file/*
	if not if(~ $file *$myname) {}
	if not if(sed 1q $file | grep '^#!/.*[/	 ]rc$' > /dev/null) {
		{
			echo 1c
			echo $firstline
			echo .
			echo wq
		} | ed $file > /dev/null
	}
}

A  => bin/contrib/hgweb.config +12 -0
@@ 1,12 @@
[web]
style = gitweb
allow_archive = bz2

#[paths]
#w9 = /gsoc/hg/w9/

[collections]
#allow_archive = bz2 zip
/gsoc/hg = /gsoc/hg/
#/var/hg = /var/hg/


A  => bin/contrib/hgwebdir.cgi +47 -0
@@ 1,47 @@
#!/usr/bin/env python
#
# An example CGI script to export multiple hgweb repos, edit as necessary

# send python tracebacks to the browser if an error occurs:
import cgitb
cgitb.enable()

# adjust python path if not a system-wide install:
#import sys
#sys.path.insert(0, "/path/to/python/lib")

# If you'd like to serve pages with UTF-8 instead of your default
# locale charset, you can do so by uncommenting the following lines.
# Note that this will cause your .hgrc files to be interpreted in
# UTF-8 and all your repo files to be displayed using UTF-8.
#
#import os
#os.environ["HGENCODING"] = "UTF-8"

from mercurial.hgweb.hgwebdir_mod import hgwebdir
from mercurial.hgweb.request import wsgiapplication
import mercurial.hgweb.wsgicgi as wsgicgi

# The config file looks like this.  You can have paths to individual
# repos, collections of repos in a directory tree, or both.
#
# [paths]
# virtual/path = /real/path
# virtual/path = /real/path
#
# [collections]
# /prefix/to/strip/off = /root/of/tree/full/of/repos
#
# collections example: say directory tree /foo contains repos /foo/bar,
# /foo/quux/baz.  Give this config section:
#   [collections]
#   /foo = /foo
# Then repos will list as bar and quux/baz.
#
# Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
# or use a dictionary with entries like 'virtual/path': '/real/path'

def make_web_app():
    return hgwebdir("hgweb.config")

wsgicgi.launch(wsgiapplication(make_web_app))

A  => bin/contrib/markdown.pl +1447 -0
@@ 1,1447 @@
#!/usr/bin/env perl
#
# Markdown -- A text-to-HTML conversion tool for web writers
#
# Copyright (c) 2004 John Gruber
# <http://daringfireball.net/projects/markdown/>
#
package Markdown;
require 5.006_000;
use strict;
use warnings;

use Digest::MD5 qw(md5_hex);
use vars qw($VERSION);
$VERSION = '1.0.1';
# Tue 14 Dec 2004

## Disabled; causes problems under Perl 5.6.1:
# use utf8;
# binmode( STDOUT, ":utf8" );  # c.f.: http://acis.openlib.org/dev/perl-unicode-struggle.html


#
# Global default settings:
#
my $g_empty_element_suffix = " />";     # Change to ">" for HTML output
my $g_tab_width = 4;


#
# Globals:
#

# Regex to match balanced [brackets]. See Friedl's
# "Mastering Regular Expressions", 2nd Ed., pp. 328-331.
my $g_nested_brackets;
$g_nested_brackets = qr{
	(?> 								# Atomic matching
	   [^\[\]]+							# Anything other than brackets
	 | 
	   \[
		 (??{ $g_nested_brackets })		# Recursive set of nested brackets
	   \]
	)*
}x;


# Table of hash values for escaped characters:
my %g_escape_table;
foreach my $char (split //, '\\`*_{}[]()>#+-.!') {
	$g_escape_table{$char} = md5_hex($char);
}


# Global hashes, used by various utility routines
my %g_urls;
my %g_titles;
my %g_html_blocks;

# Used to track when we're inside an ordered or unordered list
# (see _ProcessListItems() for details):
my $g_list_level = 0;


#### Blosxom plug-in interface ##########################################

# Set $g_blosxom_use_meta to 1 to use Blosxom's meta plug-in to determine
# which posts Markdown should process, using a "meta-markup: markdown"
# header. If it's set to 0 (the default), Markdown will process all
# entries.
my $g_blosxom_use_meta = 0;

sub start { 1; }
sub story {
	my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;

	if ( (! $g_blosxom_use_meta) or
	     (defined($meta::markup) and ($meta::markup =~ /^\s*markdown\s*$/i))
	     ){
			$$body_ref  = Markdown($$body_ref);
     }
     1;
}


#### Movable Type plug-in interface #####################################
eval {require MT};  # Test to see if we're running in MT.
unless ($@) {
    require MT;
    import  MT;
    require MT::Template::Context;
    import  MT::Template::Context;

	eval {require MT::Plugin};  # Test to see if we're running >= MT 3.0.
	unless ($@) {
		require MT::Plugin;
		import  MT::Plugin;
		my $plugin = new MT::Plugin({
			name => "Markdown",
			description => "A plain-text-to-HTML formatting plugin. (Version: $VERSION)",
			doc_link => 'http://daringfireball.net/projects/markdown/'
		});
		MT->add_plugin( $plugin );
	}

	MT::Template::Context->add_container_tag(MarkdownOptions => sub {
		my $ctx	 = shift;
		my $args = shift;
		my $builder = $ctx->stash('builder');
		my $tokens = $ctx->stash('tokens');

		if (defined ($args->{'output'}) ) {
			$ctx->stash('markdown_output', lc $args->{'output'});
		}

		defined (my $str = $builder->build($ctx, $tokens) )
			or return $ctx->error($builder->errstr);
		$str;		# return value
	});

	MT->add_text_filter('markdown' => {
		label     => 'Markdown',
		docs      => 'http://daringfireball.net/projects/markdown/',
		on_format => sub {
			my $text = shift;
			my $ctx  = shift;
			my $raw  = 0;
		    if (defined $ctx) {
		    	my $output = $ctx->stash('markdown_output'); 
				if (defined $output  &&  $output =~ m/^html/i) {
					$g_empty_element_suffix = ">";
					$ctx->stash('markdown_output', '');
				}
				elsif (defined $output  &&  $output eq 'raw') {
					$raw = 1;
					$ctx->stash('markdown_output', '');
				}
				else {
					$raw = 0;
					$g_empty_element_suffix = " />";
				}
			}
			$text = $raw ? $text : Markdown($text);
			$text;
		},
	});

	# If SmartyPants is loaded, add a combo Markdown/SmartyPants text filter:
	my $smartypants;

	{
		no warnings "once";
		$smartypants = $MT::Template::Context::Global_filters{'smarty_pants'};
	}

	if ($smartypants) {
		MT->add_text_filter('markdown_with_smartypants' => {
			label     => 'Markdown With SmartyPants',
			docs      => 'http://daringfireball.net/projects/markdown/',
			on_format => sub {
				my $text = shift;
				my $ctx  = shift;
				if (defined $ctx) {
					my $output = $ctx->stash('markdown_output'); 
					if (defined $output  &&  $output eq 'html') {
						$g_empty_element_suffix = ">";
					}
					else {
						$g_empty_element_suffix = " />";
					}
				}
				$text = Markdown($text);
				$text = $smartypants->($text, '1');
			},
		});
	}
}
else {
#### BBEdit/command-line text filter interface ##########################
# Needs to be hidden from MT (and Blosxom when running in static mode).

    # We're only using $blosxom::version once; tell Perl not to warn us:
	no warnings 'once';
    unless ( defined($blosxom::version) ) {
		use warnings;

		#### Check for command-line switches: #################
		my %cli_opts;
		use Getopt::Long;
		Getopt::Long::Configure('pass_through');
		GetOptions(\%cli_opts,
			'version',
			'shortversion',
			'html4tags',
		);
		if ($cli_opts{'version'}) {		# Version info
			print "\nThis is Markdown, version $VERSION.\n";
			print "Copyright 2004 John Gruber\n";
			print "http://daringfireball.net/projects/markdown/\n\n";
			exit 0;
		}
		if ($cli_opts{'shortversion'}) {		# Just the version number string.
			print $VERSION;
			exit 0;
		}
		if ($cli_opts{'html4tags'}) {			# Use HTML tag style instead of XHTML
			$g_empty_element_suffix = ">";
		}


		#### Process incoming text: ###########################
		my $text;
		{
			local $/;               # Slurp the whole file
			$text = <>;
		}
        print Markdown($text);
    }
}



sub Markdown {
#
# Main function. The order in which other subs are called here is
# essential. Link and image substitutions need to happen before
# _EscapeSpecialChars(), so that any *'s or _'s in the <a>
# and <img> tags get encoded.
#
	my $text = shift;

	# Clear the global hashes. If we don't clear these, you get conflicts
	# from other articles when generating a page which contains more than
	# one article (e.g. an index page that shows the N most recent
	# articles):
	%g_urls = ();
	%g_titles = ();
	%g_html_blocks = ();


	# Standardize line endings:
	$text =~ s{\r\n}{\n}g; 	# DOS to Unix
	$text =~ s{\r}{\n}g; 	# Mac to Unix

	# Make sure $text ends with a couple of newlines:
	$text .= "\n\n";

	# Convert all tabs to spaces.
	$text = _Detab($text);

	# Strip any lines consisting only of spaces and tabs.
	# This makes subsequent regexen easier to write, because we can
	# match consecutive blank lines with /\n+/ instead of something
	# contorted like /[ \t]*\n+/ .
	$text =~ s/^[ \t]+$//mg;

	# Turn block-level HTML blocks into hash entries
	$text = _HashHTMLBlocks($text);

	# Strip link definitions, store in hashes.
	$text = _StripLinkDefinitions($text);

	$text = _RunBlockGamut($text);

	$text = _UnescapeSpecialChars($text);

	return $text . "\n";
}


sub _StripLinkDefinitions {
#
# Strips link definitions from text, stores the URLs and titles in
# hash references.
#
	my $text = shift;
	my $less_than_tab = $g_tab_width - 1;

	# Link defs are in the form: ^[id]: url "optional title"
	while ($text =~ s{
						^[ ]{0,$less_than_tab}\[(.+)\]:	# id = $1
						  [ \t]*
						  \n?				# maybe *one* newline
						  [ \t]*
						<?(\S+?)>?			# url = $2
						  [ \t]*
						  \n?				# maybe one newline
						  [ \t]*
						(?:
							(?<=\s)			# lookbehind for whitespace
							["(]
							(.+?)			# title = $3
							[")]
							[ \t]*
						)?	# title is optional
						(?:\n+|\Z)
					}
					{}mx) {
		$g_urls{lc $1} = _EncodeAmpsAndAngles( $2 );	# Link IDs are case-insensitive
		if ($3) {
			$g_titles{lc $1} = $3;
			$g_titles{lc $1} =~ s/"/&quot;/g;
		}
	}

	return $text;
}


sub _HashHTMLBlocks {
	my $text = shift;
	my $less_than_tab = $g_tab_width - 1;

	# Hashify HTML blocks:
	# We only want to do this for block-level HTML tags, such as headers,
	# lists, and tables. That's because we still want to wrap <p>s around
	# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
	# phrase emphasis, and spans. The list of tags we're looking for is
	# hard-coded:
	my $block_tags_a = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del/;
	my $block_tags_b = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math/;

	# First, look for nested blocks, e.g.:
	# 	<div>
	# 		<div>
	# 		tags for inner block must be indented.
	# 		</div>
	# 	</div>
	#
	# The outermost tags must start at the left margin for this to match, and
	# the inner nested divs must be indented.
	# We need to do this before the next, more liberal match, because the next
	# match will start at the first `<div>` and stop at the first `</div>`.
	$text =~ s{
				(						# save in $1
					^					# start of line  (with /m)
					<($block_tags_a)	# start tag = $2
					\b					# word break
					(.*\n)*?			# any number of lines, minimally matching
					</\2>				# the matching end tag
					[ \t]*				# trailing spaces/tabs
					(?=\n+|\Z)	# followed by a newline or end of document
				)
			}{
				my $key = md5_hex($1);
				$g_html_blocks{$key} = $1;
				"\n\n" . $key . "\n\n";
			}egmx;


	#
	# Now match more liberally, simply from `\n<tag>` to `</tag>\n`
	#
	$text =~ s{
				(						# save in $1
					^					# start of line  (with /m)
					<($block_tags_b)	# start tag = $2
					\b					# word break
					(.*\n)*?			# any number of lines, minimally matching
					.*</\2>				# the matching end tag
					[ \t]*				# trailing spaces/tabs
					(?=\n+|\Z)	# followed by a newline or end of document
				)
			}{
				my $key = md5_hex($1);
				$g_html_blocks{$key} = $1;
				"\n\n" . $key . "\n\n";
			}egmx;
	# Special case just for <hr />. It was easier to make a special case than
	# to make the other regex more complicated.	
	$text =~ s{
				(?:
					(?<=\n\n)		# Starting after a blank line
					|				# or
					\A\n?			# the beginning of the doc
				)
				(						# save in $1
					[ ]{0,$less_than_tab}
					<(hr)				# start tag = $2
					\b					# word break
					([^<>])*?			# 
					/?>					# the matching end tag
					[ \t]*
					(?=\n{2,}|\Z)		# followed by a blank line or end of document
				)
			}{
				my $key = md5_hex($1);
				$g_html_blocks{$key} = $1;
				"\n\n" . $key . "\n\n";
			}egx;

	# Special case for standalone HTML comments:
	$text =~ s{
				(?:
					(?<=\n\n)		# Starting after a blank line
					|				# or
					\A\n?			# the beginning of the doc
				)
				(						# save in $1
					[ ]{0,$less_than_tab}
					(?s:
						<!
						(--.*?--\s*)+
						>
					)
					[ \t]*
					(?=\n{2,}|\Z)		# followed by a blank line or end of document
				)
			}{
				my $key = md5_hex($1);
				$g_html_blocks{$key} = $1;
				"\n\n" . $key . "\n\n";
			}egx;


	return $text;
}


sub _RunBlockGamut {
#
# These are all the transformations that form block-level
# tags like paragraphs, headers, and list items.
#
	my $text = shift;

	$text = _DoHeaders($text);

	# Do Horizontal Rules:
	$text =~ s{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
	$text =~ s{^[ ]{0,2}([ ]? -[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
	$text =~ s{^[ ]{0,2}([ ]? _[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;

	$text = _DoLists($text);

	$text = _DoCodeBlocks($text);

	$text = _DoBlockQuotes($text);

	# We already ran _HashHTMLBlocks() before, in Markdown(), but that
	# was to escape raw HTML in the original Markdown source. This time,
	# we're escaping the markup we've just created, so that we don't wrap
	# <p> tags around block-level tags.
	$text = _HashHTMLBlocks($text);

	$text = _FormParagraphs($text);

	return $text;
}


sub _RunSpanGamut {
#
# These are all the transformations that occur *within* block-level
# tags like paragraphs, headers, and list items.
#
	my $text = shift;

	$text = _DoCodeSpans($text);

	$text = _EscapeSpecialChars($text);

	# Process anchor and image tags. Images must come first,
	# because ![foo][f] looks like an anchor.
	$text = _DoImages($text);
	$text = _DoAnchors($text);

	# Make links out of things like `<http://example.com/>`
	# Must come after _DoAnchors(), because you can use < and >
	# delimiters in inline links like [this](<url>).
	$text = _DoAutoLinks($text);

	$text = _EncodeAmpsAndAngles($text);

	$text = _DoItalicsAndBold($text);

	# Do hard breaks:
	$text =~ s/ {2,}\n/ <br$g_empty_element_suffix\n/g;

	return $text;
}


sub _EscapeSpecialChars {
	my $text = shift;
	my $tokens ||= _TokenizeHTML($text);

	$text = '';   # rebuild $text from the tokens
# 	my $in_pre = 0;	 # Keep track of when we're inside <pre> or <code> tags.
# 	my $tags_to_skip = qr!<(/?)(?:pre|code|kbd|script|math)[\s>]!;

	foreach my $cur_token (@$tokens) {
		if ($cur_token->[0] eq "tag") {
			# Within tags, encode * and _ so they don't conflict
			# with their use in Markdown for italics and strong.
			# We're replacing each such character with its
			# corresponding MD5 checksum value; this is likely
			# overkill, but it should prevent us from colliding
			# with the escape values by accident.
			$cur_token->[1] =~  s! \* !$g_escape_table{'*'}!gx;
			$cur_token->[1] =~  s! _  !$g_escape_table{'_'}!gx;
			$text .= $cur_token->[1];
		} else {
			my $t = $cur_token->[1];
			$t = _EncodeBackslashEscapes($t);
			$text .= $t;
		}
	}
	return $text;
}


sub _DoAnchors {
#
# Turn Markdown link shortcuts into XHTML <a> tags.
#
	my $text = shift;

	#
	# First, handle reference-style links: [link text] [id]
	#
	$text =~ s{
		(					# wrap whole match in $1
		  \[
		    ($g_nested_brackets)	# link text = $2
		  \]

		  [ ]?				# one optional space
		  (?:\n[ ]*)?		# one optional newline followed by spaces

		  \[
		    (.*?)		# id = $3
		  \]
		)
	}{
		my $result;
		my $whole_match = $1;
		my $link_text   = $2;
		my $link_id     = lc $3;

		if ($link_id eq "") {
			$link_id = lc $link_text;     # for shortcut links like [this][].
		}

		if (defined $g_urls{$link_id}) {
			my $url = $g_urls{$link_id};
			$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
			$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
			$result = "<a href=\"$url\"";
			if ( defined $g_titles{$link_id} ) {
				my $title = $g_titles{$link_id};
				$title =~ s! \* !$g_escape_table{'*'}!gx;
				$title =~ s!  _ !$g_escape_table{'_'}!gx;
				$result .=  " title=\"$title\"";
			}
			$result .= ">$link_text</a>";
		}
		else {
			$result = $whole_match;
		}
		$result;
	}xsge;

	#
	# Next, inline-style links: [link text](url "optional title")
	#
	$text =~ s{
		(				# wrap whole match in $1
		  \[
		    ($g_nested_brackets)	# link text = $2
		  \]
		  \(			# literal paren
		  	[ \t]*
			<?(.*?)>?	# href = $3
		  	[ \t]*
			(			# $4
			  (['"])	# quote char = $5
			  (.*?)		# Title = $6
			  \5		# matching quote
			)?			# title is optional
		  \)
		)
	}{
		my $result;
		my $whole_match = $1;
		my $link_text   = $2;
		my $url	  		= $3;
		my $title		= $6;

		$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
		$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
		$result = "<a href=\"$url\"";

		if (defined $title) {
			$title =~ s/"/&quot;/g;
			$title =~ s! \* !$g_escape_table{'*'}!gx;
			$title =~ s!  _ !$g_escape_table{'_'}!gx;
			$result .=  " title=\"$title\"";
		}

		$result .= ">$link_text</a>";

		$result;
	}xsge;

	return $text;
}


sub _DoImages {
#
# Turn Markdown image shortcuts into <img> tags.
#
	my $text = shift;

	#
	# First, handle reference-style labeled images: ![alt text][id]
	#
	$text =~ s{
		(				# wrap whole match in $1
		  !\[
		    (.*?)		# alt text = $2
		  \]

		  [ ]?				# one optional space
		  (?:\n[ ]*)?		# one optional newline followed by spaces

		  \[
		    (.*?)		# id = $3
		  \]

		)
	}{
		my $result;
		my $whole_match = $1;
		my $alt_text    = $2;
		my $link_id     = lc $3;

		if ($link_id eq "") {
			$link_id = lc $alt_text;     # for shortcut links like ![this][].
		}

		$alt_text =~ s/"/&quot;/g;
		if (defined $g_urls{$link_id}) {
			my $url = $g_urls{$link_id};
			$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
			$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
			$result = "<img src=\"$url\" alt=\"$alt_text\"";
			if (defined $g_titles{$link_id}) {
				my $title = $g_titles{$link_id};
				$title =~ s! \* !$g_escape_table{'*'}!gx;
				$title =~ s!  _ !$g_escape_table{'_'}!gx;
				$result .=  " title=\"$title\"";
			}
			$result .= $g_empty_element_suffix;
		}
		else {
			# If there's no such link ID, leave intact:
			$result = $whole_match;
		}

		$result;
	}xsge;

	#
	# Next, handle inline images:  ![alt text](url "optional title")
	# Don't forget: encode * and _

	$text =~ s{
		(				# wrap whole match in $1
		  !\[
		    (.*?)		# alt text = $2
		  \]
		  \(			# literal paren
		  	[ \t]*
			<?(\S+?)>?	# src url = $3
		  	[ \t]*
			(			# $4
			  (['"])	# quote char = $5
			  (.*?)		# title = $6
			  \5		# matching quote
			  [ \t]*
			)?			# title is optional
		  \)
		)
	}{
		my $result;
		my $whole_match = $1;
		my $alt_text    = $2;
		my $url	  		= $3;
		my $title		= '';
		if (defined($6)) {
			$title		= $6;
		}

		$alt_text =~ s/"/&quot;/g;
		$title    =~ s/"/&quot;/g;
		$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
		$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
		$result = "<img src=\"$url\" alt=\"$alt_text\"";
		if (defined $title) {
			$title =~ s! \* !$g_escape_table{'*'}!gx;
			$title =~ s!  _ !$g_escape_table{'_'}!gx;
			$result .=  " title=\"$title\"";
		}
		$result .= $g_empty_element_suffix;

		$result;
	}xsge;

	return $text;
}


sub _DoHeaders {
	my $text = shift;

	# Setext-style headers:
	#	  Header 1
	#	  ========
	#  
	#	  Header 2
	#	  --------
	#
	$text =~ s{ ^(.+)[ \t]*\n=+[ \t]*\n+ }{
		"<h1>"  .  _RunSpanGamut($1)  .  "</h1>\n\n";
	}egmx;

	$text =~ s{ ^(.+)[ \t]*\n-+[ \t]*\n+ }{
		"<h2>"  .  _RunSpanGamut($1)  .  "</h2>\n\n";
	}egmx;


	# atx-style headers:
	#	# Header 1
	#	## Header 2
	#	## Header 2 with closing hashes ##
	#	...
	#	###### Header 6
	#
	$text =~ s{
			^(\#{1,6})	# $1 = string of #'s
			[ \t]*
			(.+?)		# $2 = Header text
			[ \t]*
			\#*			# optional closing #'s (not counted)
			\n+
		}{
			my $h_level = length($1);
			"<h$h_level>"  .  _RunSpanGamut($2)  .  "</h$h_level>\n\n";
		}egmx;

	return $text;
}


sub _DoLists {
#
# Form HTML ordered (numbered) and unordered (bulleted) lists.
#
	my $text = shift;
	my $less_than_tab = $g_tab_width - 1;

	# Re-usable patterns to match list item bullets and number markers:
	my $marker_ul  = qr/[*+-]/;
	my $marker_ol  = qr/\d+[.]/;
	my $marker_any = qr/(?:$marker_ul|$marker_ol)/;

	# Re-usable pattern to match any entirel ul or ol list:
	my $whole_list = qr{
		(								# $1 = whole list
		  (								# $2
			[ ]{0,$less_than_tab}
			(${marker_any})				# $3 = first list item marker
			[ \t]+
		  )
		  (?s:.+?)
		  (								# $4
			  \z
			|
			  \n{2,}
			  (?=\S)
			  (?!						# Negative lookahead for another list item marker
				[ \t]*
				${marker_any}[ \t]+
			  )
		  )
		)
	}mx;

	# We use a different prefix before nested lists than top-level lists.
	# See extended comment in _ProcessListItems().
	#
	# Note: There's a bit of duplication here. My original implementation
	# created a scalar regex pattern as the conditional result of the test on
	# $g_list_level, and then only ran the $text =~ s{...}{...}egmx
	# substitution once, using the scalar as the pattern. This worked,
	# everywhere except when running under MT on my hosting account at Pair
	# Networks. There, this caused all rebuilds to be killed by the reaper (or
	# perhaps they crashed, but that seems incredibly unlikely given that the
	# same script on the same server ran fine *except* under MT. I've spent
	# more time trying to figure out why this is happening than I'd like to
	# admit. My only guess, backed up by the fact that this workaround works,
	# is that Perl optimizes the substition when it can figure out that the
	# pattern will never change, and when this optimization isn't on, we run
	# afoul of the reaper. Thus, the slightly redundant code to that uses two
	# static s/// patterns rather than one conditional pattern.

	if ($g_list_level) {
		$text =~ s{
				^
				$whole_list
			}{
				my $list = $1;
				my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol";
				# Turn double returns into triple returns, so that we can make a
				# paragraph for the last item in a list, if necessary:
				$list =~ s/\n{2,}/\n\n\n/g;
				my $result = _ProcessListItems($list, $marker_any);
				$result = "<$list_type>\n" . $result . "</$list_type>\n";
				$result;
			}egmx;
	}
	else {
		$text =~ s{
				(?:(?<=\n\n)|\A\n?)
				$whole_list
			}{
				my $list = $1;
				my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol";
				# Turn double returns into triple returns, so that we can make a
				# paragraph for the last item in a list, if necessary:
				$list =~ s/\n{2,}/\n\n\n/g;
				my $result = _ProcessListItems($list, $marker_any);
				$result = "<$list_type>\n" . $result . "</$list_type>\n";
				$result;
			}egmx;
	}


	return $text;
}


sub _ProcessListItems {
#
#	Process the contents of a single ordered or unordered list, splitting it
#	into individual list items.
#

	my $list_str = shift;
	my $marker_any = shift;


	# The $g_list_level global keeps track of when we're inside a list.
	# Each time we enter a list, we increment it; when we leave a list,
	# we decrement. If it's zero, we're not in a list anymore.
	#
	# We do this because when we're not inside a list, we want to treat
	# something like this:
	#
	#		I recommend upgrading to version
	#		8. Oops, now this line is treated
	#		as a sub-list.
	#
	# As a single paragraph, despite the fact that the second line starts
	# with a digit-period-space sequence.
	#
	# Whereas when we're inside a list (or sub-list), that line will be
	# treated as the start of a sub-list. What a kludge, huh? This is
	# an aspect of Markdown's syntax that's hard to parse perfectly
	# without resorting to mind-reading. Perhaps the solution is to
	# change the syntax rules such that sub-lists must start with a
	# starting cardinal number; e.g. "1." or "a.".

	$g_list_level++;

	# trim trailing blank lines:
	$list_str =~ s/\n{2,}\z/\n/;


	$list_str =~ s{
		(\n)?							# leading line = $1
		(^[ \t]*)						# leading whitespace = $2
		($marker_any) [ \t]+			# list marker = $3
		((?s:.+?)						# list item text   = $4
		(\n{1,2}))
		(?= \n* (\z | \2 ($marker_any) [ \t]+))
	}{
		my $item = $4;
		my $leading_line = $1;
		my $leading_space = $2;

		if ($leading_line or ($item =~ m/\n{2,}/)) {
			$item = _RunBlockGamut(_Outdent($item));
		}
		else {
			# Recursion for sub-lists:
			$item = _DoLists(_Outdent($item));
			chomp $item;
			$item = _RunSpanGamut($item);
		}

		"<li>" . $item . "</li>\n";
	}egmx;

	$g_list_level--;
	return $list_str;
}



sub _DoCodeBlocks {
#
#	Process Markdown `<pre><code>` blocks.
#	

	my $text = shift;

	$text =~ s{
			(?:\n\n|\A)
			(	            # $1 = the code block -- one or more lines, starting with a space/tab
			  (?:
			    (?:[ ]{$g_tab_width} | \t)  # Lines must start with a tab or a tab-width of spaces
			    .*\n+
			  )+
			)
			((?=^[ ]{0,$g_tab_width}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
		}{
			my $codeblock = $1;
			my $result; # return value

			$codeblock = _EncodeCode(_Outdent($codeblock));
			$codeblock = _Detab($codeblock);
			$codeblock =~ s/\A\n+//; # trim leading newlines
			$codeblock =~ s/\s+\z//; # trim trailing whitespace

			$result = "\n\n<pre><code>" . $codeblock . "\n</code></pre>\n\n";

			$result;
		}egmx;

	return $text;
}


sub _DoCodeSpans {
#
# 	*	Backtick quotes are used for <code></code> spans.
# 
# 	*	You can use multiple backticks as the delimiters if you want to
# 		include literal backticks in the code span. So, this input:
#     
#         Just type ``foo `bar` baz`` at the prompt.
#     
#     	Will translate to:
#     
#         <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
#     
#		There's no arbitrary limit to the number of backticks you
#		can use as delimters. If you need three consecutive backticks
#		in your code, use four for delimiters, etc.
#
#	*	You can use spaces to get literal backticks at the edges:
#     
#         ... type `` `bar` `` ...
#     
#     	Turns to:
#     
#         ... type <code>`bar`</code> ...
#

	my $text = shift;

	$text =~ s@
			(`+)		# $1 = Opening run of `
			(.+?)		# $2 = The code block
			(?<!`)
			\1			# Matching closer
			(?!`)
		@
 			my $c = "$2";
 			$c =~ s/^[ \t]*//g; # leading whitespace
 			$c =~ s/[ \t]*$//g; # trailing whitespace
 			$c = _EncodeCode($c);
			"<code>$c</code>";
		@egsx;

	return $text;
}


sub _EncodeCode {
#
# Encode/escape certain characters inside Markdown code runs.
# The point is that in code, these characters are literals,
# and lose their special Markdown meanings.
#
    local $_ = shift;

	# Encode all ampersands; HTML entities are not
	# entities within a Markdown code span.
	s/&/&amp;/g;

	# Encode $'s, but only if we're running under Blosxom.
	# (Blosxom interpolates Perl variables in article bodies.)
	{
		no warnings 'once';
    	if (defined($blosxom::version)) {
    		s/\$/&#036;/g;	
    	}
    }


	# Do the angle bracket song and dance:
	s! <  !&lt;!gx;
	s! >  !&gt;!gx;

	# Now, escape characters that are magic in Markdown:
	s! \* !$g_escape_table{'*'}!gx;
	s! _  !$g_escape_table{'_'}!gx;
	s! {  !$g_escape_table{'{'}!gx;
	s! }  !$g_escape_table{'}'}!gx;
	s! \[ !$g_escape_table{'['}!gx;
	s! \] !$g_escape_table{']'}!gx;
	s! \\ !$g_escape_table{'\\'}!gx;

	return $_;
}


sub _DoItalicsAndBold {
	my $text = shift;

	# <strong> must go first:
	$text =~ s{ (\*\*|__) (?=\S) (.+?[*_]*) (?<=\S) \1 }
		{<strong>$2</strong>}gsx;

	$text =~ s{ (\*|_) (?=\S) (.+?) (?<=\S) \1 }
		{<em>$2</em>}gsx;

	return $text;
}


sub _DoBlockQuotes {
	my $text = shift;

	$text =~ s{
		  (								# Wrap whole match in $1
			(
			  ^[ \t]*>[ \t]?			# '>' at the start of a line
			    .+\n					# rest of the first line
			  (.+\n)*					# subsequent consecutive lines
			  \n*						# blanks
			)+
		  )
		}{
			my $bq = $1;
			$bq =~ s/^[ \t]*>[ \t]?//gm;	# trim one level of quoting
			$bq =~ s/^[ \t]+$//mg;			# trim whitespace-only lines
			$bq = _RunBlockGamut($bq);		# recurse

			$bq =~ s/^/  /g;
			# These leading spaces screw with <pre> content, so we need to fix that:
			$bq =~ s{
					(\s*<pre>.+?</pre>)
				}{
					my $pre = $1;
					$pre =~ s/^  //mg;
					$pre;
				}egsx;

			"<blockquote>\n$bq\n</blockquote>\n\n";
		}egmx;


	return $text;
}


sub _FormParagraphs {
#
#	Params:
#		$text - string to process with html <p> tags
#
	my $text = shift;

	# Strip leading and trailing lines:
	$text =~ s/\A\n+//;
	$text =~ s/\n+\z//;

	my @grafs = split(/\n{2,}/, $text);

	#
	# Wrap <p> tags.
	#
	foreach (@grafs) {
		unless (defined( $g_html_blocks{$_} )) {
			$_ = _RunSpanGamut($_);
			s/^([ \t]*)/<p>/;
			$_ .= "</p>";
		}
	}

	#
	# Unhashify HTML blocks
	#
	foreach (@grafs) {
		if (defined( $g_html_blocks{$_} )) {
			$_ = $g_html_blocks{$_};
		}
	}

	return join "\n\n", @grafs;
}


sub _EncodeAmpsAndAngles {
# Smart processing for ampersands and angle brackets that need to be encoded.

	my $text = shift;

	# Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
	#   http://bumppo.net/projects/amputator/
 	$text =~ s/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/&amp;/g;

	# Encode naked <'s
 	$text =~ s{<(?![a-z/?\$!])}{&lt;}gi;

	return $text;
}


sub _EncodeBackslashEscapes {
#
#   Parameter:  String.
#   Returns:    The string, with after processing the following backslash
#               escape sequences.
#
    local $_ = shift;

    s! \\\\  !$g_escape_table{'\\'}!gx;		# Must process escaped backslashes first.
    s! \\`   !$g_escape_table{'`'}!gx;
    s! \\\*  !$g_escape_table{'*'}!gx;
    s! \\_   !$g_escape_table{'_'}!gx;
    s! \\\{  !$g_escape_table{'{'}!gx;
    s! \\\}  !$g_escape_table{'}'}!gx;
    s! \\\[  !$g_escape_table{'['}!gx;
    s! \\\]  !$g_escape_table{']'}!gx;
    s! \\\(  !$g_escape_table{'('}!gx;
    s! \\\)  !$g_escape_table{')'}!gx;
    s! \\>   !$g_escape_table{'>'}!gx;
    s! \\\#  !$g_escape_table{'#'}!gx;
    s! \\\+  !$g_escape_table{'+'}!gx;
    s! \\\-  !$g_escape_table{'-'}!gx;
    s! \\\.  !$g_escape_table{'.'}!gx;
    s{ \\!  }{$g_escape_table{'!'}}gx;

    return $_;
}


sub _DoAutoLinks {
	my $text = shift;

	$text =~ s{<((https?|ftp):[^'">\s]+)>}{<a href="$1">$1</a>}gi;

	# Email addresses: <address@domain.foo>
	$text =~ s{
		<
        (?:mailto:)?
		(
			[-.\w]+
			\@
			[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
		)
		>
	}{
		_EncodeEmailAddress( _UnescapeSpecialChars($1) );
	}egix;

	return $text;
}


sub _EncodeEmailAddress {
#
#	Input: an email address, e.g. "foo@example.com"
#
#	Output: the email address as a mailto link, with each character
#		of the address encoded as either a decimal or hex entity, in
#		the hopes of foiling most address harvesting spam bots. E.g.:
#
#	  <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
#       x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
#       &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
#
#	Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
#	mailing list: <http://tinyurl.com/yu7ue>
#

	my $addr = shift;

	srand;
	my @encode = (
		sub { '&#' .                 ord(shift)   . ';' },
		sub { '&#x' . sprintf( "%X", ord(shift) ) . ';' },
		sub {                            shift          },
	);

	$addr = "mailto:" . $addr;

	$addr =~ s{(.)}{
		my $char = $1;
		if ( $char eq '@' ) {
			# this *must* be encoded. I insist.
			$char = $encode[int rand 1]->($char);
		} elsif ( $char ne ':' ) {
			# leave ':' alone (to spot mailto: later)
			my $r = rand;
			# roughly 10% raw, 45% hex, 45% dec
			$char = (
				$r > .9   ?  $encode[2]->($char)  :
				$r < .45  ?  $encode[1]->($char)  :
							 $encode[0]->($char)
			);
		}
		$char;
	}gex;

	$addr = qq{<a href="$addr">$addr</a>};
	$addr =~ s{">.+?:}{">}; # strip the mailto: from the visible part

	return $addr;
}


sub _UnescapeSpecialChars {
#
# Swap back in all the special characters we've hidden.
#
	my $text = shift;

	while( my($char, $hash) = each(%g_escape_table) ) {
		$text =~ s/$hash/$char/g;
	}
    return $text;
}


sub _TokenizeHTML {
#
#   Parameter:  String containing HTML markup.
#   Returns:    Reference to an array of the tokens comprising the input
#               string. Each token is either a tag (possibly with nested,
#               tags contained therein, such as <a href="<MTFoo>">, or a
#               run of text between tags. Each element of the array is a
#               two-element array; the first is either 'tag' or 'text';
#               the second is the actual value.
#
#
#   Derived from the _tokenize() subroutine from Brad Choate's MTRegex plugin.
#       <http://www.bradchoate.com/past/mtregex.php>
#

    my $str = shift;
    my $pos = 0;
    my $len = length $str;
    my @tokens;

    my $depth = 6;
    my $nested_tags = join('|', ('(?:<[a-z/!$](?:[^<>]') x $depth) . (')*>)' x  $depth);
    my $match = qr/(?s: <! ( -- .*? -- \s* )+ > ) |  # comment
                   (?s: <\? .*? \?> ) |              # processing instruction
                   $nested_tags/ix;                   # nested tags

    while ($str =~ m/($match)/g) {
        my $whole_tag = $1;
        my $sec_start = pos $str;
        my $tag_start = $sec_start - length $whole_tag;
        if ($pos < $tag_start) {
            push @tokens, ['text', substr($str, $pos, $tag_start - $pos)];
        }
        push @tokens, ['tag', $whole_tag];
        $pos = pos $str;
    }
    push @tokens, ['text', substr($str, $pos, $len - $pos)] if $pos < $len;
    \@tokens;
}


sub _Outdent {
#
# Remove one level of line-leading tabs or spaces
#
	my $text = shift;

	$text =~ s/^(\t|[ ]{1,$g_tab_width})//gm;
	return $text;
}


sub _Detab {
#
# Cribbed from a post by Bart Lateur:
# <http://www.nntp.perl.org/group/perl.macperl.anyperl/154>
#
	my $text = shift;

	$text =~ s{(.*?)\t}{$1.(' ' x ($g_tab_width - length($1) % $g_tab_width))}ge;
	return $text;
}


1;

__END__


=pod

=head1 NAME

B<Markdown>


=head1 SYNOPSIS

B<Markdown.pl> [ B<--html4tags> ] [ B<--version> ] [ B<-shortversion> ]
    [ I<file> ... ]


=head1 DESCRIPTION

Markdown is a text-to-HTML filter; it translates an easy-to-read /
easy-to-write structured text format into HTML. Markdown's text format
is most similar to that of plain text email, and supports features such
as headers, *emphasis*, code blocks, blockquotes, and links.

Markdown's syntax is designed not as a generic markup language, but
specifically to serve as a front-end to (X)HTML. You can  use span-level
HTML tags anywhere in a Markdown document, and you can use block level
HTML tags (like <div> and <table> as well).

For more information about Markdown's syntax, see:

    http://daringfireball.net/projects/markdown/


=head1 OPTIONS

Use "--" to end switch parsing. For example, to open a file named "-z", use:

	Markdown.pl -- -z

=over 4


=item B<--html4tags>

Use HTML 4 style for empty element tags, e.g.:

    <br>

instead of Markdown's default XHTML style tags, e.g.:

    <br />


=item B<-v>, B<--version>

Display Markdown's version number and copyright information.


=item B<-s>, B<--shortversion>

Display the short-form version number.


=back



=head1 BUGS

To file bug reports or feature requests (other than topics listed in the
Caveats section above) please send email to:

    support@daringfireball.net

Please include with your report: (1) the example input; (2) the output
you expected; (3) the output Markdown actually produced.


=head1 VERSION HISTORY

See the readme file for detailed release notes for this version.

1.0.1 - 14 Dec 2004

1.0 - 28 Aug 2004


=head1 AUTHOR

    John Gruber
    http://daringfireball.net

    PHP port and other contributions by Michel Fortin
    http://michelf.com


=head1 COPYRIGHT AND LICENSE

Copyright (c) 2003-2004 John Gruber   
<http://daringfireball.net/>   
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.

* Neither the name "Markdown" nor the names of its contributors may
  be used to endorse or promote products derived from this software
  without specific prior written permission.

This software is provided by the copyright holders and contributors "as
is" and any express or implied warranties, including, but not limited
to, the implied warranties of merchantability and fitness for a
particular purpose are disclaimed. In no event shall the copyright owner
or contributors be liable for any direct, indirect, incidental, special,
exemplary, or consequential damages (including, but not limited to,
procurement of substitute goods or services; loss of use, data, or
profits; or business interruption) however caused and on any theory of
liability, whether in contract, strict liability, or tort (including
negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.

=cut

A  => bin/contrib/md2html.awk +427 -0
@@ 1,427 @@
#!/bin/awk -f
#
# by: Jesus Galan (yiyus) 2009
#
# Usage: md2html.awk file.md > file.html
# See: http://4l77.com/src/md2html.awk

function eschtml(t) {
	gsub("&", "\\&amp;", t);
	gsub("<", "\\&lt;", t);
	return t;
}

function oprint(t){
	if(nr == 0)
		print t;
	else
		otext = otext "\n" t;
}

function subref(id){
	for(; nr > 0 && sub("<<" id, ref[id], otext); nr--);
	if(nr == 0 && otext) {
		print otext;
		otext = "";
	}
}

function nextil(t) {
	if(!match(t, /[`<&\[*_\\-]|(\!\[)/))
		return t;
	t1 = substr(t, 1, RSTART - 1);
	tag = substr(t, RSTART, RLENGTH);
	t2 = substr(t, RSTART + RLENGTH);
	if(ilcode && tag != "`")
		return eschtml(t1 tag) nextil(t2);
	# Backslash escaping
	if(tag == "\\"){
		if(match(t2, /^[\\`*_{}\[\]()#+\-\.!]/)){
			tag = substr(t2, 1, 1);
			t2 = substr(t2, 2);
		}
		return t1 tag nextil(t2);
	}
	# Dashes
	if(tag == "-"){
		if(sub(/^-/, "", t2))
			tag = "&#8212;";
		return t1 tag nextil(t2);
	}
	# Inline Code
	if(tag == "`"){
		if(sub(/^`/, "", t2)){
			if(!match(t2, /``/))
				return t1 "&#8221;" nextil(t2);
			ilcode2 = !ilcode2;
		}
		else if(ilcode2)
			return t1 tag nextil(t2);
		tag = "<code>";
		if(ilcode){
			t1 = eschtml(t1);
			tag = "</code>";
		}
		ilcode = !ilcode;
		return t1 tag nextil(t2);
	}
	if(tag == "<"){
	# Autolinks
		if(match(t2, /^[^ 	]+[\.@][^ 	]+>/)){
			url = eschtml(substr(t2, 1, RLENGTH - 1));
			t2 = substr(t2, RLENGTH + 1);
			linktext = url;
			if(match(url, /@/) && !match(url, /^mailto:/))
				url = "mailto:" url;
			return t1 "<a href=\"" url "\">" linktext "</a>" nextil(t2);
		}
	# Html tags
		if(match(t2, /^[A-Za-z\/!][^>]*>/)){
			tag = tag substr(t2, RSTART, RLENGTH);
			t2 = substr(t2, RLENGTH + 1);
			return t1 tag nextil(t2);
		}
		return t1 "&lt;" nextil(t2);
	}
	# Html special entities
	if(tag == "&"){
		if(match(t2, /^#?[A-Za-z0-9]+;/)){
			tag = tag substr(t2, RSTART, RLENGTH);
			t2 = substr(t2, RLENGTH + 1);
			return t1 tag nextil(t2);
		}
		return t1 "&amp;" nextil(t2);
	}
	# Images
	if(tag == "!["){
		if(!match(t2, /(\[.*\])|(\(.*\))/))
			return t1 tag nextil(t2);
		match(t2, /^[^\]]*/);
		alt = substr(t2, 1, RLENGTH);
		t2 = substr(t2, RLENGTH + 2);
		if(match(t2, /^\(/)){
			# Inline
			sub(/^\(/, "", t2);
			match(t2, /^[^\)]+/);
			url = eschtml(substr(t2, 1, RLENGTH));
			t2 = substr(t2, RLENGTH + 2);
			title = "";
			if(match(url, /[ 	]+\".*\"[ 	]*$/)) {
				title = substr(url, RSTART, RLENGTH);
				url = substr(url, 1, RSTART - 1);
				match(title, /\".*\"/);
				title = " title=\"" substr(title, RSTART + 1, RLENGTH - 2) "\"";
			}
			if(match(url, /^<.*>$/))
				url = substr(url, 2, RLENGTH - 2);
			return t1 "<img src=\"" url "\" alt=\"" alt "\"" title " />" nextil(t2);
		}
		else{
			# Referenced
			sub(/^ ?\[/, "", t2);
			id = alt;
			if(match(t2, /^[^\]]+/))
				id = substr(t2, 1, RLENGTH);
			t2 = substr(t2, RLENGTH + 2);
			if(ref[id])
				r = ref[id];
			else{
				r = "<<" id;
				nr++;
			}
			return t1 "<img src=\"" r "\" alt=\"" alt "\" />" nextil(t2);
		}
	}
	# Links
	if(tag == "["){
		if(!match(t2, /(\[.*\])|(\(.*\))/))
			return t1 tag nextil(t2);
		match(t2, /^[^\]]*(\[[^\]]*\][^\]]*)*/);
		linktext = substr(t2, 1, RLENGTH);
		t2 = substr(t2, RLENGTH + 2);
		if(match(t2, /^\(/)){
			# Inline
			match(t2, /^[^\)]+(\([^\)]+\)[^\)]*)*/);
			url = substr(t2, 2, RLENGTH - 1);
			pt2 = substr(t2, RLENGTH + 2);
			title = "";
			if(match(url, /[ 	]+\".*\"[ 	]*$/)) {
				title = substr(url, RSTART, RLENGTH);
				url = substr(url, 1, RSTART - 1);
				match(title, /\".*\"/);
				title = " title=\"" substr(title, RSTART + 1, RLENGTH - 2) "\"";
			}
			if(match(url, /^<.*>$/))
				url = substr(url, 2, RLENGTH - 2);
			url = eschtml(url);
			return t1 "<a href=\"" url "\"" title ">" nextil(linktext) "</a>" nextil(pt2);
		}
		else{
			# Referenced
			sub(/^ ?\[/, "", t2);
			id = linktext;
			if(match(t2, /^[^\]]+/))
				id = substr(t2, 1, RLENGTH);
			t2 = substr(t2, RLENGTH + 2);
			if(ref[id])
				r = ref[id];
			else{
				r = "<<" id;
				nr++;
			}
			pt2 = t2;
			return t1 "<a href=\"" r "\" />" nextil(linktext) "</a>" nextil(pt2);
		}
	}
	# Emphasis
	if(match(tag, /[*_]/)){
		ntag = tag;
		if(sub("^" tag, "", t2)){
			if(stag[ns] == tag && match(t2, "^" tag))
				t2 = tag t2;
			else
				ntag = tag tag
		}
		n = length(ntag);
		tag = (n == 2) ? "strong" : "em";
		if(match(t1, / $/) && match(t2, /^ /))
			return t1 tag nextil(t2);
		if(stag[ns] == ntag){
			tag = "/" tag;
			ns--;
		}
		else
			stag[++ns] = ntag;
		tag = "<" tag ">";
		return t1 tag nextil(t2);
	}
}

function inline(t) {
	ilcode = 0;
	ilcode2 = 0;
	ns = 0;
	
	return nextil(t);
}

function printp(tag) {
	if(!match(text, /^[ 	]*$/)){
		text = inline(text);
		if(tag != "")
			oprint("<" tag ">" text "</" tag ">");
		else
			oprint(text);
	}
	text = "";
}

BEGIN {
	blank = 0;
	code = 0;
	hr = 0;
	html = 0;
	nl = 0;
	nr = 0;
	otext = "";
	text = "";
	par = "p";
}

# References
!code && /^ *\[[^\]]*\]:[ 	]+/ {
	sub(/^ *\[/, "");
	match($0, /\]/);
	id = substr($0, 1, RSTART - 1);
	sub(id "\\]:[ 	]+", "");
	title = "";
	if(match($0, /\".*\"$/))
		title = "\" title=\"" substr($0, RSTART + 1, RLENGTH - 2);
	sub(/[ 	]+\".*\"$/, "");
	url = eschtml($0);
	ref[id] = url title;

	subref(id);
	next;
}

# html
!html && /^<(address|blockquote|center|dir|div|dl|fieldset|form|h[1-6r]|\
isindex|menu|noframes|noscript|ol|p|pre|table|ul|!--)/ {
	if(code)
		oprint("</pre></code>");
	for(; !text && block[nl] == "blockquote"; nl--)
		oprint("</blockquote>");
	match($0, /^<(address|blockquote|center|dir|div|dl|fieldset|form|h[1-6r]|\
	isindex|menu|noframes|noscript|ol|p|pre|table|ul|!--)/);
	htag = substr($0, 2, RLENGTH - 1);
	if(!match($0, "(<\\/" htag ">)|((^<hr ?\\/?)|(--)>$)"))
		html = 1;
	if(html && match($0, /^<hr/))
		hr = 1;
	oprint($0);
	next;
}

html && (/(^<\/(address|blockquote|center|dir|div|dl|fieldset|form|h[1-6r]|\
isindex|menu|noframes|noscript|ol|p|pre|table|ul).*)|(--)>$/ ||
(hr && />$/)) {
	html = 0;
	hr = 0;
	oprint($0);
	next;
}

html {
	oprint($0);
	next;
}

# List and quote blocks

#   Remove indentation
{
	for(nnl = 0; nnl < nl; nnl++)
		if((match(block[nnl + 1], /[ou]l/) && !sub(/^(    |	)/, "")) || \
		(block[nnl + 1] == "blockquote" && !sub(/^> ?/, "")))
			break;
}
nnl < nl && !blank && text && ! /^ ? ? ?([*+-]|([0-9]+\.)+)( +|	)/ { nnl = nl; }
#   Quote blocks
{ 
	while(sub(/^> /, ""))
		nblock[++nnl] = "blockquote";
}
#   Horizontal rules
{ hr = 0; }
(blank || (!text && !code)) && /^ ? ? ?([-*_][ 	]*)([-*_][ 	]*)([-*_][ 	]*)+$/ {
	if(code){
		oprint("</pre></code>");
		code = 0;
	}
	blank = 0;
	nnl = 0;
	hr = 1;
}
#   List items
block[nl] ~ /[ou]l/ && /^$/ {
	blank = 1;
	next;
}
{ newli = 0; }
!hr && (nnl != nl || !text || block[nl] ~ /[ou]l/) && /^ ? ? ?[*+-]( +|	)/ {
	sub(/^ ? ? ?[*+-]( +|	)/, "");
	nnl++;
	nblock[nnl] = "ul";
	newli = 1;
}
(nnl != nl || !text || block[nl] ~ /[ou]l/) && /^ ? ? ?([0-9]+\.)+( +|	)/ {
	sub(/^ ? ? ?([0-9]+\.)+( +|	)/, "");
	nnl++;
	nblock[nnl] = "ol";
	newli = 1;
}
newli { 
	if(blank && nnl == nl && !par)
		par = "p";
	blank = 0;
	printp(par);
	if(nnl == nl && block[nl] == nblock[nl])
		oprint("</li><li>");
}
blank && ! /^$/ {
	if(match(block[nnl], /[ou]l/) && !par)
		par = "p";
	printp(par);
	par = "p";
	blank = 0;
}
		
# Close old blocks and open new ones
nnl != nl || nblock[nl] != block[nl] {
	if(code){
		oprint("</pre></code>");
		code = 0;
	}
	printp(par);
	b = (nnl > nl) ? nblock[nnl] : block[nnl];
	par = (match(b, /[ou]l/)) ? "" : "p";
}
nnl < nl || (nnl == nl && nblock[nl] != block[nl]) {
	for(; nl > nnl || (nnl == nl && pblock[nl] != block[nl]); nl--){
		if(match(block[nl], /[ou]l/))
			oprint("</li>");
		oprint("</" block[nl] ">");
	}
}
nnl > nl {
	for(; nl < nnl; nl++){
		block[nl + 1] = nblock[nl + 1];
		oprint("<" block[nl + 1] ">");
		if(match(block[nl + 1], /[ou]l/))
			oprint("<li>");
	}
}
hr {
	oprint("<hr>");
	next;
}

# Code blocks
code && /^$/ { 
	if(blanK)
		oprint("");
	blank = 1;
	next;
}
!text && sub(/^(	|    )/, "") {
	if(blanK)
		oprint("");
	blank = 0;
	if(!code)
		oprint("<code><pre>");
	code = 1;
	$0 = eschtml($0);
	oprint($0);
	next;
}
code {
	oprint("</pre></code>");
	code = 0;
}

# Setex-style Headers
text && /^=+$/ {printp("h1"); next;}
text && /^-+$/ {printp("h2"); next;} 

# Atx-Style headers
/^#+/ && (!newli || par=="p" || /^##/) {
	for(n = 0; n < 6 && sub(/^# */, ""); n++)
		sub(/#$/, "");
	par = "h" n;
}

# Paragraph	
/^$/ {
	printp(par);
	par = "p";
	next;
}

# Add text
{ text = (text ? text " " : "") $0; }

END {
	if(code){
		oprint("</pre></code>");
		code = 0;
	}
	printp(par);
	for(; nl > 0; nl--){
		if(match(block[nl], /[ou]l/))
			oprint("</li>");
		oprint("</" block[nl] ">");
	}
	gsub(/<<[^\"]*/, "", otext);
	print(otext);
}

A  => bin/contrib/rc-httpd/handlers/authorize +6 -0
@@ 1,6 @@
#!/bin/rc
if(~ $REMOTE_USER ''){
	extra_headers=($extra_headers 'WWW-Authenticate: Basic realm="'$"SERVER_NAME'"')
	error 401
	exit
}

A  => bin/contrib/rc-httpd/handlers/cgi +46 -0
@@ 1,46 @@
#!/bin/rc
fn filter_headers{
	response=(200 OK)
	lines=''
	done=false
	while(~ $done false){
		line=`{getline}
		head=`{echo $line | awk '{print tolower($1)}'}
		if(~ $head status:*)
			response=`{echo $line | awk '{$1="" ; print}'}
		if not if(~ $line '')
			done=true
		if not
			lines=$"lines^$"line^$cr^'
'
	}
	echo 'HTTP/1.1' $"response^$cr
	echo -n $"lines
	do_log $response(1)
}

fn run_cgi {
	path=$cgi_path exec $"cgi_bin $params || echo 'Status: 500'
}

cgi_bin=$1
cgi_dir=.
if(! ~ $#* 1)
	cgi_dir=$*($#*)
if not if(~ $"cgi_bin /*){
	cgi_dir=`{basename -d $"cgi_bin}
	cgi_dir=$"cgi_dir
}
if(! ~ $"cgi_bin */*)
	cgi_bin=./$"cgi_bin
if(! builtin cd $"cgi_dir >[2]/dev/null || ! test -x $"cgi_bin){
	error 500
	exit
}

run_cgi | {
	filter_headers
	emit_extra_headers
	echo $cr
	exec cat
}

A  => bin/contrib/rc-httpd/handlers/dir-index +111 -0
@@ 1,111 @@
#!/bin/rc
PATH_INFO=`{echo $PATH_INFO | urldecode.awk}
full_path=$"FS_ROOT^$"PATH_INFO
full_path=$"full_path
if(! test -d $full_path){
	error 404
	exit
}
if(! test -r $full_path -x $full_path){
	error 503
	exit
}
do_log 200
builtin cd $full_path
if(~ $"NOINDEXFILE ^ $"NOINDEX ''){
	ifile=index.htm*
	if(! ~ $ifile(1) *'*'){
		PATH_INFO=$ifile(1)
		FS_ROOT=''
		exec serve-static
	}
}
title=`{echo $SITE_TITLE | sed s,%s,^$"PATH_INFO^,}
title=$"title
lso=()
switch($2){
case size
	# ls has no option to sort by size
	# could pipe it through sort, I suppose
case date
	lso=-t
}
echo 'HTTP/1.1 200 OK'^$cr
emit_extra_headers
echo 'Content-type: text/html'^$cr
echo $cr
echo '<html>
<head>
<title>'^$title^'</title>
<style type="text/css">
	.size {
		text-align: right;
		padding-right: 4pt;
	}
	.day {
		text-align: right;
		padding-right: 3pt;
	}
	.datetime {
		text-align: right;
	}
	.name {
		text-align: right;
		padding-left: 3pt;
	}
</style>
</head>
<body>'
echo '<h1>'^$title^'</h1>'
if(! ~ $PATH_INFO /)
	echo '<a href="../">Parent directory</a>'
echo '<table>'
ls -lQ $lso | awk '
function urlencode(loc){
	# very minimal encoding, just enough for our static-file purposes
	url=loc
	gsub("%", "%25", url)		# this one first!
	gsub("\\$", "%24", url)
	gsub("&", "%26", url)
	gsub("\\+", "%2B", url)
	gsub("\\?", "%3F", url)
	gsub(" ", "%20", url)
	gsub("\"", "%22", url)
	gsub("#", "%23", url)
	return url
}
function hrsize(size){
	if(size > 1073741824) return sprintf("%.1fGB", size/1073741824)
	if(size > 10485760) return sprintf("%iMB", size/1048576)
	if(size > 1048576) return sprintf("%.1fMB", size/1048576)
	if(size > 10240) return sprintf("%iKB", size/1024)
	if(size > 1024) return sprintf("%.1fKB", size/1024)
	return sprintf("%iB", size)
}
/^(-|a)/ {
	print "<tr>"
	print "<td class=\"size\">"hrsize($6)"</td>"
	print "<td class=\"month\">"$7"</td>"
	print "<td class=\"day\">"$8"</td>"
	print "<td class=\"datetime\">"$9"</td>"
	$1="" ; $2="" ; $3="" ; $4="" ; $5="" ; $6="" ; $7="" ; $8="" ; $9=""
	sub("^ *?", "")
	print "<td><a class=\"file name\" href=\""urlencode($0)"\">"$0"</a></td>"
	print "</tr>"
	$0=""
}
/^d/ {
	print "<tr>"
	print "<td class=\"size\"> </td>"
	print "<td class=\"month\">"$7"</td>"
	print "<td class=\"day\">"$8"</td>"
	print "<td class=\"datetime\">"$9"</td>"
	$1="" ; $2="" ; $3="" ; $4="" ; $5="" ; $6="" ; $7="" ; $8="" ; $9=""
	sub("^ *?", "")
	print "<td><a class=\"dir name\" href=\""urlencode($0)"/\">"$0"/</a></td>"
	print "</tr>"
}'
echo '</table>

</body>
</html>'

A  => bin/contrib/rc-httpd/handlers/error +43 -0
@@ 1,43 @@
#!/bin/rc
# DO NOT make this script callable directly from the web!
fn do_error{
	echo 'HTTP/1.1 '^$1^$cr
	emit_extra_headers
	echo 'Content-type: text/html'^$cr
	echo $cr
	echo '<html>
<head>
<title>'^$1^'</title>
</head>
<body>
<h1>'^$1^'</h1>'
	echo $2
	echo '<p><i>rc-httpd at' $SERVER_NAME '</i>'
	echo '
	</body>
	</html>
	'
}

fn 401{
	do_error '401 Unauthorized' \
	'The requested path '^$"location^' requires authorization.'
}

fn 404{
	do_error '404 Not Found' \
	'The requested path '^$"location^' was not found on this server.'
}

fn 500{
	do_error '500 Internal Server Error' \
	'The server has encountered an internal misconfiguration and is unable to satisfy your request.'
}

fn 503{
	do_error '503 Forbidden' \
	'You do not have permission to access '^$"location^' on this server.'
}

do_log $1
$1

A  => bin/contrib/rc-httpd/handlers/redirect +30 -0
@@ 1,30 @@
#!/bin/rc
if(~ $#2 0){
	error 500
	exit
}
switch($1){
case perm*
	do_log 301
	echo 'HTTP/1.1 301 Moved Permanently'^$cr
case temp*
	do_log 302
	echo 'HTTP/1.1 302 Moved Temporarily'^$cr
case seeother
	do_log 303
	echo 'HTTP/1.1 303 See Other'^$cr
case *
	error 500
	exit
}
echo 'Location: ' ^ $2 ^ $cr
emit_extra_headers
echo 'Content-type: text/html'^$cr
echo $cr
echo '<html><body>'
if(~ $#3 0)
	echo 'Browser did not accept redirect.'
if not
	echo $3
echo '<a href="'^$"location^'/">Click here</a>'
echo '</body></html>'

A  => bin/contrib/rc-httpd/handlers/serve-static +43 -0
@@ 1,43 @@
#!/bin/rc
full_path=`{echo $"FS_ROOT^$"PATH_INFO | urldecode.awk}
full_path=$"full_path
if(~ $full_path */)
	error 503
if(test -d $full_path){
	redirect perm $"location^'/' \
		'URL not quite right, and browser did not accept redirect.'
	exit
}
if(! test -e $full_path){
	error 404
	exit
}
if(! test -r $full_path){
	error 503
	exit
}
do_log 200
switch($full_path){
case *.html *.htm
        type=text/html
case *.css
        type=text/css
case *.txt
        type='text/plain; charset=utf-8'
case *.jpg *.jpeg
        type=image/jpeg
case *.gif
        type=image/gif
case *.png
        type=image/png
case *
		type=`{file -m $full_path || file -i $full_path}        # GROSS
}
max_age=3600	# 1 hour
echo 'HTTP/1.1 200 OK'^$cr
emit_extra_headers
echo 'Content-type: '^$type^'; charset=utf-8'^$cr
echo 'Content-length: '^`{ls -l $full_path | awk '{print $6}'}
echo 'Cache-control: max-age='^$max_age^$cr
echo $cr
exec cat $full_path

A  => bin/contrib/rc-httpd/handlers/static-or-cgi +14 -0
@@ 1,14 @@
#!/bin/rc
cgiargs=$*

fn error{
	if(~ $1 404)
		exec cgi $cgiargs
	if not
		$rc_httpd_dir/handlers/error $1
}

if(~ $location */)
	exec cgi $cgiargs
if not
	exec serve-static

A  => bin/contrib/rc-httpd/handlers/static-or-index +5 -0
@@ 1,5 @@
#!/bin/rc
if(~ $PATH_INFO */)
	exec dir-index $params
if not
	exec serve-static

A  => bin/contrib/rc-httpd/lib/urldecode.awk +39 -0
@@ 1,39 @@
# taken from werc
BEGIN {
    hextab ["0"] = 0; hextab ["8"] = 8;
    hextab ["1"] = 1; hextab ["9"] = 9;
    hextab ["2"] = 2; hextab ["A"] = hextab ["a"] = 10
    hextab ["3"] = 3; hextab ["B"] = hextab ["b"] = 11;
    hextab ["4"] = 4; hextab ["C"] = hextab ["c"] = 12;
    hextab ["5"] = 5; hextab ["D"] = hextab ["d"] = 13;
    hextab ["6"] = 6; hextab ["E"] = hextab ["e"] = 14;
    hextab ["7"] = 7; hextab ["F"] = hextab ["f"] = 15;
}
{
    decoded = ""
    i = 1
    len = length ($0)
    while ( i <= len ) {
        c = substr ($0, i, 1)
        if ( c == "%" ) {
            if ( i+2 <= len ) {
                c1 = substr ($0, i+1, 1)
                c2 = substr ($0, i+2, 1)
                if ( hextab [c1] == "" || hextab [c2] == "" ) {
                    print "WARNING: invalid hex encoding: %" c1 c2 | "cat >&2"
                } else {
                    code = 0 + hextab [c1] * 16 + hextab [c2] + 0
                    c = sprintf ("%c", code)
                    i = i + 2
                }
            } else {
                print "WARNING: invalid % encoding: " substr ($0, i, len - i)
            }
        } else if ( c == "+" ) {
            c = " "
        }
        decoded = decoded c
        ++i
    }
    printf "%s", decoded
}

A  => bin/contrib/rc-httpd/rc-httpd +102 -0
@@ 1,102 @@
#!/bin/rc
rc_httpd_dir=/home/sl/www/werc/bin/contrib/rc-httpd
libdir = $rc_httpd_dir/lib
path=($PLAN9/bin $rc_httpd_dir/handlers $PATH)
cgi_path=$PLAN9/bin
SERVER_PORT=80 # default for CGI scripts, may be overridden by the Host header
extra_headers='Server: rc-httpd'
cr=

fn do_log{
	echo `{date} :: $SERVER_NAME :: $request :: \
	$HTTP_USER_AGENT :: $1 :: $HTTP_REFERER >[1=2]
}

fn emit_extra_headers{
	for(header in $extra_headers)
		echo $"header^$cr
}

fn getline{ read | sed 's/'^$"cr^'$//g' }

fn terminate{
	echo `{date} connection terminated >[1=2]
	exit terminate
}

fn trim_input{ dd -bs 1 -count $CONTENT_LENGTH }

request=`{getline}
if(~ $#request 0)
	terminate
REQUEST_METHOD=$request(1)
REQUEST_URI=$request(2)
reqlines=''
HTTP_COOKIE=''
REMOTE_USER=''
done=false
chunked=no
while(~ $"done false){
	line=`{getline}
	if(~ $#line 0)
		done=true
	reqlines=$"reqlines$"line'
'
	h=`{echo $line | awk '{print tolower($1)}'}
	switch($h){
	case ''
		done=true
	case host:
		SERVER_NAME=$line(2)
	case referer:
		HTTP_REFERER=$line(2)
	case user-agent:
		HTTP_USER_AGENT=`{echo $line | sed 's;[^:]+:[ 	]+;;'}
	case content-length:
		CONTENT_LENGTH=$line(2)
	case content-type:
		CONTENT_TYPE=$line(2)
	case cookie:
		cookie=`{echo $line | sed 's;^[^:]+:[ 	]*;;'}
		HTTP_COOKIE=$"HTTP_COOKIE^$"cookie^'; '
	case authorization:
		REMOTE_USER=`{auth/httpauth $line(3)}
	case transfer-encoding:
		~ $line(2) chunked && chunked=yes
	}
}
if(~ $REQUEST_URI *://* //*){
	SERVER_NAME=`{echo $REQUEST_URI | sed '
		s;^[^:]+:;;
		s;^//([^/]+).*;\1;'}
	REQUEST_URI=`{echo $REQUEST_URI | sed '
		s;^[^:]+:;;
		s;^//[^/]+/?;/;'}
}
QUERY_STRING=`{echo $REQUEST_URI | sed 's;[^?]*\??;;'}
params=`{echo $QUERY_STRING | sed 's;\+; ;g'}
location=`{echo $REQUEST_URI | sed 's;\?.*;;'}
location=`{echo $location | sed '
	s;[^/]+/\.\./;/;g
	s;/\./;/;g
	s;//+;/;g
'}
SERVER_NAME=`{echo $SERVER_NAME | sed 's;^(\[[^\]]+\]|[^:]+)\:([0-9]+)$;\1 \2;'}
if(~ $#SERVER_NAME 2){
	SERVER_PORT=$SERVER_NAME(2)
	SERVER_NAME=$SERVER_NAME(1)
}
if(~ $REQUEST_METHOD (PUT POST)){
	if(! ~ $"CONTENT_LENGTH '')
		trim_input | exec $rc_httpd_dir/select-handler
	if not{
		if(~ $chunked yes){
			echo 'HTTP/1.1 411 Length required'^$cr
			echo $cr
			exit
		}
		exec $rc_httpd_dir/select-handler
	}
}
if not
	. $rc_httpd_dir/select-handler

A  => bin/contrib/rc-httpd/select-handler +20 -0
@@ 1,20 @@
#!/bin/rc
rfork n

# Route requests to werc.
# Change paths to match your system.

if(~ $SERVER_NAME 9base.werc.cat-v.org)
	PLAN9=/usr/local/9base
if(~ $SERVER_NAME frontbase.werc.cat-v.org)
	PLAN9=/usr/local/plan9front
if(~ $SERVER_NAME plan9port.werc.cat-v.org)
	PLAN9=/usr/local/plan9

if(~ $SERVER_NAME *){
	PATH_INFO=$location
	FS_ROOT=/home/sl/www/werc/sites/$SERVER_NAME
	exec static-or-cgi /home/sl/www/werc/bin/werc.rc
}
if not
	error 503

A  => bin/contrib/tcp80 +7 -0
@@ 1,7 @@
#!/bin/rc
# For use with listen(8).
# Change paths to match your system.
# Eitdit rc-httpd/rc-httpd to match your system.
PLAN9=/usr/local/plan9
PATH=($PATH /home/sl/www/werc/bin/contrib)
exec /home/sl/www/werc/bin/contrib/rc-httpd/rc-httpd >>[2]/var/log/rc-httpd

A  => bin/contrib/urldecode.awk +39 -0
@@ 1,39 @@
#!/bin/awk -f
BEGIN {
	hextab ["0"] = 0; hextab ["8"] = 8;
	hextab ["1"] = 1; hextab ["9"] = 9;
	hextab ["2"] = 2; hextab ["A"] = hextab ["a"] = 10
	hextab ["3"] = 3; hextab ["B"] = hextab ["b"] = 11;
	hextab ["4"] = 4; hextab ["C"] = hextab ["c"] = 12;
	hextab ["5"] = 5; hextab ["D"] = hextab ["d"] = 13;
	hextab ["6"] = 6; hextab ["E"] = hextab ["e"] = 14;
	hextab ["7"] = 7; hextab ["F"] = hextab ["f"] = 15;
}
{
	decoded = ""
	i   = 1
	len = length ($0)
	while ( i <= len ) {
		c = substr ($0, i, 1)
		if ( c == "%" ) {
			if ( i+2 <= len ) {
				c1 = substr ($0, i+1, 1)
				c2 = substr ($0, i+2, 1)
				if ( hextab [c1] == "" || hextab [c2] == "" ) {
					print "WARNING: invalid hex encoding: %" c1 c2 | "cat >&2"
				} else {
					code = 0 + hextab [c1] * 16 + hextab [c2] + 0
					c = sprintf ("%c", code)
					i = i + 2
				}
			} else {
				print "WARNING: invalid % encoding: " substr ($0, i, len - i)
			}
		} else if ( c == "+" ) {
			c = " "
		}
		decoded = decoded c
		++i
	}
	print decoded
}

A  => bin/contrib/urlencode.awk +126 -0
@@ 1,126 @@
# Taken from http://www.shelldorado.com/scripts/cmds/urlencode
##########################################################################
# Title      :  urlencode - encode URL data
# Author     :  Heiner Steven (heiner.steven@odn.de)
# Date       :  2000-03-15
# Requires   :  awk
# Categories :  File Conversion, WWW, CGI
# SCCS-Id.   :  @(#) urlencode  1.4 06/10/29
##########################################################################
# Description
#   Encode data according to
#       RFC 1738: "Uniform Resource Locators (URL)" and
#       RFC 1866: "Hypertext Markup Language - 2.0" (HTML)
#
#   This encoding is used i.e. for the MIME type
#   "application/x-www-form-urlencoded"
#
# Notes
#    o  The default behaviour is not to encode the line endings. This
#   may not be what was intended, because the result will be
#   multiple lines of output (which cannot be used in an URL or a
#   HTTP "POST" request). If the desired output should be one
#   line, use the "-l" option.
#
#    o  The "-l" option assumes, that the end-of-line is denoted by
#   the character LF (ASCII 10). This is not true for Windows or
#   Mac systems, where the end of a line is denoted by the two
#   characters CR LF (ASCII 13 10).
#   We use this for symmetry; data processed in the following way:
#       cat | urlencode -l | urldecode -l
#   should (and will) result in the original data
#
#    o  Large lines (or binary files) will break many AWK
#       implementations. If you get the message
#       awk: record `...' too long
#        record number xxx
#   consider using GNU AWK (gawk).
#
#    o  urlencode will always terminate it's output with an EOL
#       character
#
# Thanks to Stefan Brozinski for pointing out a bug related to non-standard
# locales.
#
# See also
#   urldecode
##########################################################################

PN=`basename "$0"`          # Program name
VER='1.4'

: ${AWK=awk}

Usage () {
    echo >&2 "$PN - encode URL data, $VER
usage: $PN [-l] [file ...]
    -l:  encode line endings (result will be one line of output)

The default is to encode each input line on its own."
    exit 1
}

Msg () {
    for MsgLine
    do echo "$PN: $MsgLine" >&2
    done
}

Fatal () { Msg "$@"; exit 1; }

set -- `getopt hl "$@" 2>/dev/null` || Usage
[ $# -lt 1 ] && Usage           # "getopt" detected an error

EncodeEOL=no
while [ $# -gt 0 ]
do
    case "$1" in
        -l) EncodeEOL=yes;;
    --) shift; break;;
    -h) Usage;;
    -*) Usage;;
    *)  break;;         # First file name
    esac
    shift
done

LANG=C  export LANG
$AWK '
    BEGIN {
    # We assume an awk implementation that is just plain dumb.
    # We will convert an character to its ASCII value with the
    # table ord[], and produce two-digit hexadecimal output
    # without the printf("%02X") feature.

    EOL = "%0A"     # "end of line" string (encoded)
    split ("1 2 3 4 5 6 7 8 9 A B C D E F", hextab, " ")
    hextab [0] = 0
    for ( i=1; i<=255; ++i ) ord [ sprintf ("%c", i) "" ] = i + 0
    if ("'"$EncodeEOL"'" == "yes") EncodeEOL = 1; else EncodeEOL = 0
    }
    {
    encoded = ""
    for ( i=1; i<=length ($0); ++i ) {
        c = substr ($0, i, 1)
        if ( c ~ /[a-zA-Z0-9.-]/ ) {
        encoded = encoded c     # safe character
        } else if ( c == " " ) {
        encoded = encoded "+"   # special handling
        } else {
        # unsafe character, encode it as a two-digit hex-number
        lo = ord [c] % 16
        hi = int (ord [c] / 16);
        encoded = encoded "%" hextab [hi] hextab [lo]
        }
    }
    if ( EncodeEOL ) {
        printf ("%s", encoded EOL)
    } else {
        print encoded
    }
    }
    END {
        #if ( EncodeEOL ) print ""
    }
' "$@"


A  => bin/contrib/webserver.rc +30 -0
@@ 1,30 @@
#!/bin/rc

# A web server in rc by maht
# Originally from http://www.proweb.co.uk/~matt/rc/webserver.rc

ifs=' '
request=`{sed 1q}

url=$request(2)
file=`{echo $url | sed 's/http:\/\/[^\/]*//' | tr -d \012}

if(test -d $file){
	file=$file ^'/index.html'
}
if(test -e $file) {
	response='200'
}
if not {
	response='404'
	file='404.html'
}

echo 'HTTP/1.1 ' ^$response
echo 'Date: ' `{date}
echo 'Server: rc shell'
echo 'Content-Length: ' `{cat $file | wc -c | tr -d ' '}
echo 'Content-Type: ' `{file -i $file  | awk '{ print $2 }'}
echo 'Connection: close'
echo
cat $file

A  => bin/corehandlers.rc +152 -0
@@ 1,152 @@
# Werc builtin handlers

fn nav_tree {
    if(! ~ $#sideBarNavTitle 0)
        echo '<p class="sideBarTitle">'$"sideBarNavTitle':</p>'
    # Ignore stderr, last path element might be a file that doesn't exist (eg., foo for foo.md)
    # /./ to deal with p9p's ls failure to follow dir symlinks otherwise
    ls -F $sitedir/./$req_paths_list >[2]/dev/null \
        | { 
            sed $dirfilter'/\/[^_.\/][^\/]*(\.(md|txt|html)|\/)$/!d; s!^'$sitedir'!!; '$dirclean 
            if(! ~ $#synth_paths 0) echo $synth_paths | tr ' ' $NEW_LINE 
        } | sort -u | awk -F/ ' 
    function p(x, y, s) { for(i=0; i < x-y; i+=1) print s }
    BEGIN { lNF=2; print "<ul>" }
    { 
        d = ""
        if(match($0, "/$"))
            d = "/"
        sub("/$", "") # Strip trailing / for dirs so NF is consistent 

        p(NF, lNF, "<li><ul>")
        p(lNF, NF, "</ul></li>")
        lNF = NF

        bname = $NF d
        path = $0 d
        gsub(/[\-_]/, " ", bname)

        # To avoid false matches add trailing / even for plain files to act as delimiter
        pa = path
        gsub(/[^\/]$/, "&/", pa) 

        if(index(ENVIRON["req_path"] "/", pa) == 1)
            print "<li><a href=\"" path "\" class=\"thisPage\">&raquo;<i> " bname "</i></a></li>"
        else 
            print "<li><a href=\"" path "\">&rsaquo; " bname "</a></li>"
    }
    END { p(lNF, 2, "</ul></li>"); print "</ul>" }'
}

fn link_bar {
    if(~ $1 -t) {
        echo '<p class="sideBarTitle">'$2'</p>'
        shift; shift
    }
    echo '<ul>'
    while(! ~ $#* 0) {
        echo '<li><a href="'$2'">- '$1'</a></li>'
        shift; shift
    }
    echo '</ul>'
}

fn md_handler { $formatter $1 }

fn tpl_handler { template $* }

fn html_handler {
    # body states: 0 = no <body> found, 2 = after <body>, 1 = after <body></body>, -1 = after </body>
    awk 'gsub(".*<[Bb][Oo][Dd][Yy][^>]*>", "") > 0 {body=2}
        gsub("</ *[Bb][Oo][Dd][Yy][^>]*>.*", "") > 0 {print; body=body-1}
        body==2 {print}
        body==0 {buf=buf "\n" $0}
        END {if(body<=0) {print buf}}' < $1
}

fn txt_handler {
    # Note: Words are not broken, even if they are way beyond 82 chars long
    echo '<pre>'
    sed 's/</\&lt;/g; s/>/\&gt;/g' < $1 | fmt -l 82 -j
    echo '</pre>'
}

fn dir_listing_handler {
    d=`{basename -d $1}
    if(~ $#d 0)
        d='/'
    echo $d|sed 's,.*//,,g; s,/$,,; s,/, / ,g; s,.*,<h1 class="dir-list-head">&</h1> <ul class="dir-list">,'
    # Symlinks suck: '/.' forces ls to list the linked dir if $d is a symlink.
    ls -F $dir_listing_ls_opts $sitedir$d/. | sed $dirfilter$dirclean' s,.*/([^/]+/?)$,<li><a href="\1">\1</a></li>,'
    echo '</ul>'
}

fn notices_handler {
    for(type in notify_errors notify_notes notify_success)
        for(n in $$type)
            echo '<div class="'$type'"><b>'$"n'</b></div>'
}

fn setup_handlers {

    if(test -f $local_path.md) {
        local_file=$local_path.md
        handler_body_main=(md_handler $local_file)
    }
    if not if(test -f $local_path.tpl) {
        local_file=$local_path.tpl
        handler_body_main=(tpl_handler $local_file)
    }
    if not if(test -f $local_path.html) {
        local_file=$local_path.html
        handler_body_main=(html_handler $local_file)
    }
    # Global tpl (eg sitemap.tpl), should take precedence over txt handler!
    if not if(test -f tpl^$req_path^.tpl)
        # XXX Should we set $local_file for global .tpls?
        handler_body_main=(tpl_handler tpl^$req_path^.tpl)
    if not if(test -f $local_path.txt) {
        local_file=$local_path.txt
        handler_body_main=(txt_handler $local_file)
    }

    # XXX Should check that $enabled_apps exist in $werc_apps?
    # XXX Should split init of apps that provide main handler (eg., blog) and apps that don't (eg., comments)?
    if(! ~ $#enabled_apps 0)
        for(a in $enabled_apps)
            $a^'_init'

    if(! ~ $#handler_body_main 0)
        { } # We are done
    # Dir listing
    if not if(~ $local_path */index) {
        handler_body_main=(dir_listing_handler $req_path)
        if(test -f $sitedir$req_path'_header.md')
            ll_add handlers_body_head md_handler $sitedir$req_path'_header.md'            
        if(test -f $sitedir$req_path'_footer.md')
            ll_add handlers_body_foot md_handler $sitedir$req_path'_footer.md'            
    }
    # Canonize explicit .html urls, the web server might handle this first!
    if not if(~ $local_path *.html && test -f $local_path)
        perm_redirect `{ echo $req_path|sed 's/.html$//' }
    # Fallback static file handler
    if not if(test -f $local_path)
        static_file $local_path
    if not if(~ $req_path /pub/* && test -f .$req_path)
        static_file .$req_path
    # File not found
    if not
        setup_404_handler
}

# This function allows config files to define their own 404 handlers.
fn setup_404_handler {
    handler_body_main=(tpl_handler `{get_lib_file 404.tpl})
    echo 'Status: 404 Not Found'
    dprint 'NOT FOUND: '$SERVER_NAME^$"REQUEST_URI^' - '^$"HTTP_REFERER^' - '^$"HTTP_USER_AGENT
}

fn run_handlers { for(h in $*) run_handler $$h }
fn run_handler { $*(1) $*(2-) }



A  => bin/fltr_cache.rc +37 -0
@@ 1,37 @@
#!/bin/rc

fn fltr_cache {
    a=()
    tmpf=()

    proc=$1
    shift

    if(~ $#* 0) {
        tmpf=/tmp/fmttmp.$pid
        f=$tmpf
        score=`{{tee $tmpf || exit 1} | sha1sum}
    }
    if not {
        f=$1
        if(~ $f */) {
            score=`{du -an $f | sha1sum || exit 1} # XXX using -n(bytes) instead of -t(lastmod) because sitemap proc touches files in tree.
            a=$f
            f=/dev/null
        }
        if not {
            score=`{sha1sum $f || exit 1}
            score=$score(1)
        }
    }
    cachedir=/tmp/fltr_cache/$score
    mkdir -p $cachedir >[2]/dev/null

    if(test -s $cachedir/$proc)
        cat $cachedir/$proc
    if not
        if($proc $a < $f | tee $cachedir/$pid)
            mv $cachedir/$pid $cachedir/$proc

    rm $tmpf $cachedir/$pid >[2]/dev/null &
}

A  => bin/template.awk +55 -0
@@ 1,55 @@
#!/bin/awk -f
function pr(str) {
	if(lastc !~ "[{(]")
		gsub(/'/, "''", str)
	printf "%s", str
}
function trans(c) {
	printf "%s", end

	lastc = c
	end = "\n"
	if(c == "%")
		end = ""
	else if(c == "(")
		printf "echo -n "
	else if(c ~ "[})]") {
		end = "'\n"
		printf "echo -n '"
	}
}

BEGIN {
	lastc = "{"
	trans("}")
}
END {
	print end
}

/^%/ && $0 !~ /^%[{()}%]/ && lastc !~ /[({]/ {
	trans("%")
	print substr($0, 2)
	next
}
{
	if(lastc == "%")
		trans("}")
	n = split($0, a, "%")
	pr(a[1])
	for(i=2; i<=n; i++) {
		c = substr(a[i], 1, 1)
		rest = substr(a[i], 2)

		if((lastc !~ "[({]" && c ~ "[({]") ||
		   (lastc == "{" && c == "}") ||
		   (lastc == "(" && c == ")"))
			trans(c)
		else if(c == "%")
			pr("%")
		else
			pr("%" c)
		pr(rest)
	}
	pr("\n")
}

A  => bin/werc.rc +138 -0
@@ 1,138 @@
#!/bin/rc
. ./cgilib.rc
. ./werclib.rc
. ./wercconf.rc
. ./corehandlers.rc
. ./fltr_cache.rc
cd ..

forbidden_uri_chars='[^a-zA-Z0-9_+\-\/\.,:]'
difs=$ifs # Used to restore default ifs when needed 

# Expected input: ls -F style, $sitedir/path/to/files/
#          <ls -F+x><symlink hack><Useless?><hiden files  >
dirfilter='s/\*$//; s,/+\./+,/,g; s,^\./,,; /\/[._][^\/]/d; /'$forbidden_uri_chars'/d; /\/sitemap\.xml$/d; /\/index\.(md|html|txt|tpl)$/d; /\/(robots|sitemap)\.txt$/d; /_werc\/?$/d; '
dirclean=' s/\.(md|html|txt)$//; '

# Careful, the proper p9p path might not be set until initrc.local is sourced
path=(. /bin ./bin)

res_tail='</body></html>'
http_content_type='text/html'
ll_add handlers_bar_left nav_tree
werc_apps=( apps/* )
werc_root=`{pwd}
sitesdir=sites

 . ./etc/initrc

if(test -f etc/initrc.local)
    . ./etc/initrc.local

for(a in $werc_apps)
    . ./$a/app.rc

fn werc_exec_request {
    site=$SERVER_NAME
    base_url=http://$site:$SERVER_PORT
    sitedir=$sitesdir/$site
    headers=`{get_lib_file headers.tpl}
    master_template=`{get_lib_file default_master.tpl}
    current_date_time=`{date}

    # Note: $REQUEST_URI is not officially in CGI 1.1, but seems to be de-facto
    # Note: We only urldecode %5F->'_' because some sites (stackoverflow.com?) urlencode it in their links,
    # perhaps we should completel urldecode the whole url.
    req_path=`{echo -n $REQUEST_URI | sed 's/\?.*//; s!//+!/!g; s/%5[Ff]/_/g; s/'^$forbidden_uri_chars^'//g; s/\.\.*/./g; 1q'}
    req_url=$base_url^$req_path
    local_path=$sitedir$req_path
    local_file=''
    ifs='/' { args=`{echo -n $req_path} }

    # Preload post args for templates where cgi's stdin is not accessible
    if(~ $REQUEST_METHOD POST) {
        load_post_args
        login_user
    }

    if(~ $req_path */index)
        perm_redirect `{echo $req_path | sed 's,/index$,/,'}

    if(~ $local_path */) {
        if(test -d $local_path)
            local_path=$local_path^'index'
        # XXX: This redir might step on apps with synthetic dirs.
        if not if(ls `{basename -d $local_path}^* >/dev/null >[2]/dev/null)
            perm_redirect `{echo $req_path|sed 's,/+$,,'}
    }
    if not if(~ $req_path *'.' *',' *';' *':')
        perm_redirect `{echo $req_path | sed 's/[.,;:)]$//'}
    if not if(test -d $local_path)
        perm_redirect $req_path^'/'

    if(! ~ $#args 0)
        ifs=$NEW_LINE { pageTitle=`{ echo $args|sed -e 's/ / - /g' -e 's/([a-z])-([a-z])/\1 \2/g' -e 's/_/ /g' } }

    cd $sitedir
    req_paths_list='/' # Note: req_paths_list doesn't include 'stnythetic' dirs.
    conf_wd='/' # Used in config files to know where we are in the document tree.
    if(test -f _werc/config)
        . _werc/config
    for(i in $args) {
        conf_wd=$conf_wd^$i
        req_paths_list=($req_paths_list $conf_wd)
        if(test -d $i) {
            conf_wd=$conf_wd'/'
            cd $i
            if(test -f _werc/config)
                . _werc/config
        }
    }
    cd $werc_root

    if(~ $#perm_redir_to 1)
        perm_redirect $perm_redir_to
    for(l in $perm_redir_patterns) {
        p=$$l
        r=$p(1)
        # If target is absolute, then patern must match whole string
        if(~ $p(2) http://* https://*)
            r='^'$r 
        t=`{ echo $req_path | sed 's!'^$r^'!'^$p(2)^'!' } # Malicious danger!

        if(! ~ $"t '' $req_path)
            perm_redirect $t
    }

    setup_handlers


    # Set Page title
    if(! ~ $local_file '') {
        t=`{get_file_title $local_file}
        if(! ~ $"t '')
            pageTitle=$t
    }

    # XXX Is this never true? because we set pageTitle earlier based on url.
    if(~ $"pageTitle '') 
        pageTitle=$"siteTitle' '$"siteSubTitle
#    if not
#        pageTitle=$"pageTitle' | '$"siteTitle' '$"siteSubTitle

    for(h in $extraHttpHeaders)
        echo $h
    echo Content-Type: $http_content_type
    echo # End of HTTP headers

    if(! ~ $#debug 0)
		dprint $"SERVER_NAME^$"REQUEST_URI - $"HTTP_USER_AGENT - $"REQUEST_METHOD - $"handler_body_main - $"master_template

    if(~ $REQUEST_METHOD HEAD)
        exit

    template $headers $master_template # | awk_buffer
    echo $res_tail
}

werc_exec_request

A  => bin/werc_errlog_wrap.rc +5 -0
@@ 1,5 @@
#!/bin/rc

# This is a wrapper script for broken http servers like recent lighttpd versions which throw away cgi's stderr.

./werc.rc >>[2]/tmp/wlog.txt

A  => bin/wercconf.rc +19 -0
@@ 1,19 @@
# To be used from config files
fn conf_perm_redirect {
    if(~ $#* 1)
        perm_redir_to=$1
    if not
        ll_addh perm_redir_patterns $1 $2
}

fn conf_hide_paths {
    for(i in $*)
       dirfilter=$dirfilter^'/'^`{echo $sitedir$conf_wd$i|sed 's!/+!\\/!g'}^'/d; '
}

# Usually will be called from within conf_enable_foo 
fn conf_enable_app {
    # Note: maybe we should add test -d apps/$1/?
    if(! ~ $1 $enabled_apps)
	enabled_apps=( $enabled_apps $1 )
}

A  => bin/werclib.rc +393 -0
@@ 1,393 @@
fn get_lib_file {
    if(! ~ $#sitedir 0 && test -f $sitedir/_werc/lib/$1)
        echo -n $sitedir/_werc/lib/$1
    if not if(! ~ $#masterSite 0 && test -f $sitesdir/$masterSite/_werc/lib/$1)
        echo -n $sitesdir/$masterSite/_werc/lib/$1
    if not if(test -f lib/$1)
        echo -n lib/$1
    if not if(~ $#* 2)
        echo -n $2
    if not
        status='Can''t find lib file: '$1
}

fn template { awk -f bin/template.awk $* | rc $rcargs }

# Auth code
# TODO: check http://cookies.lcs.mit.edu/pubs/webauth:tr.pdf
allowed_user_chars='[a-zA-Z0-9_]'
# Cookie format: WERC_USER: name:timestamp:hash(name.timestamp.password)
# login_user can't be used from a template because it sets a cookie 
fn login_user {
    # Note: we set the cookie even if it is already there.
    if(get_user $*)
        set_cookie werc_user $"logged_user^':0:'^$"logged_password
}

# Check login status, if called with group arg we check membership too
fn check_user {
    get_user
    g=($* admin)
    _status=$status
    if(! ~ $"_status '')
        _status=(Not logged in: $"_status)
    if not if(! ~ $#* 0 && ! ~ $logged_user $* && ! grep -s '^'^$logged_user^'$' $werc_root/etc/users/$g/members >[2]/dev/null)
        _status=(User $logged_user not in: $*)
    status=$_status
}

# If not logged in, try to get user login info from POST or from cookie
fn get_user {
    if(~ $#logged_user 0) {
        if(~ $#* 2) {
            user_name=$1 
            user_password=$2
        }
        if not if(~ $REQUEST_METHOD POST)
            get_post_args user_name user_password

        if(~ $#user_name 0) { 
            ifs=':' { cu=`{ifs=$difs {get_cookie werc_user} | tr -d $NEW_LINE} }
            if(! ~ $#cu 0) {
                user_name=$cu(1) 
                user_password=$cu(3)
            }
        }
        auth_user $user_name $user_password
    }
    if not
        status=()
}

# Check if user_name and user_password represent a valid user account
# If valid, 'log in' by setting logged_user
fn auth_user {
    user_name=$1
    user_password=$2

    pfile=$werc_root/etc/users/$"user_name/password
    if(~ $#user_name 0 || ~ $#user_password 0)
        status=('Auth: missing user name or pass: '^$"user_name^' / '^$"user_password)
    if not if(! test -f $pfile)
        status=('Auth: cant find '^$pfile)
    if not if(! test -s $pfile || ! ~ $user_password `{cat $pfile})
        status=('Auth: Pass '$user_password' doesnt match '^`{cat $pfile})
    if not {
        logged_user=$user_name
        logged_password=$user_password
        dprint Auth: success
        status=()
    }
}

fn user_controls {
    echo User: $"logged_user
}


# .md '(meta-)data' extract
fn get_md_file_attr {
    sed -n '/^\* '$2': /p; /^\* '$2': /q; /^$/q' < $1
}


# File title extraction
fn get_md_title {
    #sed 's/^(................................................................[^ ]*).*$/\1/g; 1q' < $1 
    sed -n -e '1N; /^.*\n===*$/N; /.*\n===*\n *$/!b' -e 's/\n==*\n//p' < $1
}

fn get_html_title {
    t=`{sed -n '32q; s/^.*<[Tt][Ii][Tt][Ll][Ee]> *([^<]+) *(<\/[Tt][Ii][Tt][Ll][Ee]>.*)?$/\1/p' < $1}

    # As a backup we might want to pick the first 'non-tag' text in the file with:
    if(~ $"t '')
        t=`{sed -n -e 's/^(<[^>]+>)*([^<]+).*/\2/p; 32q' < $1 | sed 1q}

    echo $t
}

fn get_file_title {
    if (~ $1 *.md)
        get_md_title $1
    if not if(~ $1 *.html)
        get_html_title $1
    if not if(~ $1 */) {
        if(test -f $1/index.md)
            get_md_title $1/index.md
        if not if(test -f $1/index.html)
            get_html_title $1/index.html
    }
}

fn ndate {
	if(~ $#* 7)
		date=$*(2-)
	if not
		date=`{date}
	switch($date(2)){
	case Jan;	mo=01
	case Feb;	mo=02
	case Mar;	mo=03
	case Apr;	mo=04
	case May;	mo=05
	case Jun;	mo=06
	case Jul;	mo=07
	case Aug;	mo=08
	case Sep;	mo=09
	case Oct;	mo=10
	case Nov;	mo=11
	case Dec;	mo=12
	}
	switch($date(3)){
	case [0-9]
		da=0^$date(3)
	case *
		da=$date(3)
	}
	switch($date(5)){
	case A;	tz=+0100
	case ADT;	tz=-0300
	case AFT;	tz=+430
	case AKDT;	tz=-0800
	case AKST;	tz=-0900
	case ALMT;	tz=+0600
	case AMST;	tz=-0300
	case AMT;	tz=-0400
	case ANAST;	tz=+1200
	case ANAT;	tz=+1200
	case AQTT;	tz=+0500
	case ART;	tz=-0300
	case AST;	tz=-0400
	case AZOST;	tz=+0000
	case AZOT;	tz=-0100
	case AZST;	tz=+0500
	case AZT;	tz=+0400
	case B;	tz=+0200
	case BNT;	tz=+0800
	case BOT;	tz=-0400
	case BRST;	tz=-0200
	case BRT;	tz=-0300
	case BST;	tz=+0100
	case BTT;	tz=+0600
	case C;	tz=+0300
	case CAST;	tz=+0800
	case CAT;	tz=+0200
	case CCT;	tz=+0630
	case CDT;	tz=-0500
	case CEST;	tz=+0200
	case CET;	tz=+0100
	case CHADT;	tz=+1345
	case CHAST;	tz=+1245
	case CKT;	tz=-1000
	case CLST;	tz=-0300
	case CLT;	tz=-0400
	case COT;	tz=-0500
	case CST;	tz=-0600
	case CVT;	tz=-0100
	case CXT;	tz=+0700
	case ChST;	tz=+1000
	case D;	tz=+0400
	case DAVT;	tz=+0700
	case E;	tz=+0500
	case EASST;	tz=-0500
	case EAST;	tz=-0600
	case EAT;	tz=+0300
	case ECT;	tz=-0500
	case EDT;	tz=-0400
	case EEST;	tz=+0300
	case EET;	tz=+0200
	case EGST;	tz=+0000
	case EGT;	tz=-0100
	case EST;	tz=-0500
	case ET;	tz=-0500
	case F;	tz=+0600
	case FJST;	tz=+1300
	case FJT;	tz=+1200
	case FKST;	tz=-0300
	case FKT;	tz=-0400
	case FNT;	tz=-0200
	case G;	tz=+0700
	case GALT;	tz=-0600
	case GAMT;	tz=-0900
	case GET;	tz=+0400
	case GFT;	tz=-0300
	case GILT;	tz=+1200
	case GMT;	tz=+0000
	case GST;	tz=+0400
	case GYT;	tz=-0400
	case H;	tz=+0800
	case HAA;	tz=-0300
	case HAC;	tz=-0500
	case HADT;	tz=-0900
	case HAE;	tz=-0400
	case HAP;	tz=-0700
	case HAR;	tz=-0600
	case HAST;	tz=-1000
	case HAT;	tz=-0230
	case HAY;	tz=-0800
	case HKT;	tz=+0800
	case HLV;	tz=-0430
	case HNA;	tz=-0400
	case HNC;	tz=-0600
	case HNE;	tz=-0500
	case HNP;	tz=-0800
	case HNR;	tz=-0700
	case HNT;	tz=-0330
	case HNY;	tz=-0900
	case HOVT;	tz=+0700
	case I;	tz=+0900
	case ICT;	tz=+0700
	case IDT;	tz=+0300
	case IOT;	tz=+0600
	case IRDT;	tz=+0430
	case IRKST;	tz=+0900
	case IRKT;	tz=+0800
	case IRST;	tz=+0330
	case IST;	tz=+0200
	case JST;	tz=+0900
	case K;	tz=+1000
	case KGT;	tz=+0600
	case KRAST;	tz=+0800
	case KRAT;	tz=+0700
	case KST;	tz=+0900
	case KUYT;	tz=+0400
	case L;	tz=+1100
	case LHDT;	tz=+1100
	case LHST;	tz=+1030
	case LINT;	tz=+1400
	case M;	tz=+1200
	case MAGST;	tz=+1200
	case MAGT;	tz=+1100
	case MART;	tz=-0930
	case MAWT;	tz=+0500
	case MDT;	tz=-0600
	case MHT;	tz=+1200
	case MMT;	tz=+0630
	case MSD;	tz=+0400
	case MSK;	tz=+0300
	case MST;	tz=-0700
	case MUT;	tz=+0400
	case MVT;	tz=+0500
	case MYT;	tz=+0800
	case N;	tz=-0100
	case NCT;	tz=+1100
	case NDT;	tz=-0230
	case NFT;	tz=+1130
	case NOVST;	tz=+0700
	case NOVT;	tz=+0600
	case NPT;	tz=+0545
	case NST;	tz=-0330
	case NUT;	tz=-1100
	case NZDT;	tz=+1300
	case NZST;	tz=+1200
	case O;	tz=-0200
	case OMSST;	tz=+0700
	case OMST;	tz=+0600
	case P;	tz=-0300
	case PDT;	tz=-0700
	case PET;	tz=-0500
	case PETST;	tz=+1200
	case PETT;	tz=+1200
	case PGT;	tz=+1000
	case PHOT;	tz=+1300
	case PHT;	tz=+0800
	case PKT;	tz=+0500
	case PMDT;	tz=-0200
	case PMST;	tz=-0300
	case PONT;	tz=+1100
	case PST;	tz=-0800
	case PT;	tz=-0800
	case PWT;	tz=+0900
	case PYST;	tz=-0300
	case PYT;	tz=-0400
	case Q;	tz=-0400
	case R;	tz=-0500
	case RET;	tz=+0400
	case S;	tz=-0600
	case SAMT;	tz=+0400
	case SAST;	tz=+0200
	case SBT;	tz=+1100
	case SCT;	tz=+0400
	case SGT;	tz=+0800
	case SRT;	tz=-0300
	case SST;	tz=-1100
	case T;	tz=-0700
	case TAHT;	tz=-1000
	case TFT;	tz=+0500
	case TJT;	tz=+0500
	case TKT;	tz=-1000
	case TLT;	tz=+0900
	case TMT;	tz=+0500
	case TVT;	tz=+1200
	case U;	tz=-0800
	case ULAT;	tz=+0800
	case UYST;	tz=-0200
	case UYT;	tz=-0300
	case UZT;	tz=+0500
	case V;	tz=-0900
	case VET;	tz=-0430
	case VLAST;	tz=+1100
	case VLAT;	tz=+1000
	case VUT;	tz=+1100
	case W;	tz=-1000
	case WAST;	tz=+0200
	case WAT;	tz=+0100
	case WDT;	tz=+0900
	case WEST;	tz=+0100
	case WET;	tz=+0000
	case WFT;	tz=+1200
	case WGST;	tz=-0200
	case WGT;	tz=-0300
	case WIB;	tz=+0700
	case WIT;	tz=+0900
	case WITA;	tz=+0800
	case WST;	tz=+0800
	case WT;	tz=+0000
	case X;	tz=-1100
	case Y;	tz=-1200
	case YAKST;	tz=+1000
	case YAKT;	tz=+0900
	case YAPT;	tz=+1000
	case YEKST;	tz=+0600
	case YEKT;	tz=+0500
	case Z;	tz=+0000
	}
	switch($1){
	case -a	# rfc3339
		tz=`{echo $tz | sed 's/00$/:00/'}
		echo $date(6)^-$mo-$da^T^$date(4)^$tz
	case -i	# iso-8601 lite
		echo $date(6)^-$mo-$da
	case -m	# rfc2822
		echo $date(1)^, $da $date(2) $date(6) $date(4) $tz
	case -t	# iso-8601
		echo $date(6)^-$mo-$da^T^$date(4)^$tz
	}
}

##########################################################################
##########################################################################
#app_blog_methods = ( _post index.rss )
#fn app_blog__post {
#    echo
#}
#
#app_blog___default {
#    if (~ $blog)
#    call_app blogpost
#}
#
## --
#app_blogpost_methods = ( comment  _edit )
#
#fn app_blogpost_comment {
#    call_app comments
#}
#
## --
#app_comments_methods = ( _post _edit )
#
#fn app_comments___default {
#
#}

A  => etc/initrc +39 -0
@@ 1,39 @@
# This file contains the default werc settings.
#
# DO NOT EDIT, to customize copy to etc/initrc.local and edit at will.
#
# Some settings can also be set for a specific site or directory in their
# respective _werc/config or their $masterSite/_werc/config file.

# General options

# Location of your Plan 9 from User Space installation (usually /usr/local/plan9)
plan9port=$PLAN9

# If you use 9base, it should point to your 9base root, try for example:
#plan9port=/usr/lib/9base # This is the default 9base install path in Debian.

# If rc has not been copied to /bin/rc you will also need to change
# the #! line in bin/*.rc!

# Path, make sure the plan9port /bin directory is included before /bin
# Keep '.' in path! It is needed.
path=($plan9port/bin . ./bin ./bin/contrib /bin /usr/bin)

# Set this to your favorite markdown formatter, eg., markdown.pl (fltr_cache
# takes as an argument a filter, in the default configuration markdown.pl, that
# caches output) Note that some werc components assume a markdown-like
# formatter, but all major functionality should should be formatter agnostic.
#formatter=(fltr_cache markdown.pl)
formatter=(fltr_cache md2html.awk)	# no perl for old men

# Enable debugging, to disable set to ()
debug=true

# Globally enabled apps
enabled_apps=()

# Default site variables, must be set in initrc.local or _werc/config, only siteTitle is required.
#masterSite=cat-v.org # Not required!
#siteTitle='cat-v'
#siteSubTitle='Considered harmful'

A  => etc/users/GROUP_AND_USER_ACCOUNTS +1 -0
@@ 1,1 @@
This is just a dummy file to force hg to preserve this directory that is used to store user and group account information.

A  => lib/404.tpl +3 -0
@@ 1,3 @@
<h1>The requested document at '<i>%($base_url$"req_path%)</i>' doesn't exist</h1>
<h4>Or take a look at the <a href="/sitemap">sitemap</a>.</h4>
<hr>

A  => lib/default_master.tpl +26 -0
@@ 1,26 @@
<header>
    <nav>
% cat `{ get_lib_file top_bar.inc }
    </nav>
    <h1><a href="/">%($"siteTitle%) <span id="headerSubTitle">%($"siteSubTitle%)</span></a></h1>
</header>

% if(! ~ $#handlers_bar_left 0) {
    <nav id="side-bar">
%   for(h in $handlers_bar_left) {
        <div>
%       run_handler $$h
        </div>
%   }
    </nav>
% }

<article>
% run_handlers $handlers_body_head
% run_handler $handler_body_main
% run_handlers $handlers_body_foot
</article>

<footer>
% cat `{ get_lib_file footer.inc }
</footer>

A  => lib/footer.inc +7 -0
@@ 1,7 @@
<div><a href="http://werc.cat-v.org/">Powered by werc</a></div>

<div><a href="/_users/login">User Login</a>
<!-- TODO: add duckduckgo site search
<form action="/_search" method=get><input type=text name=q />
-->
</div>

A  => lib/headers.tpl +29 -0
@@ 1,29 @@
<!DOCTYPE HTML>
<html>
<head>

    <title>%($pageTitle%)</title>

    <link rel="stylesheet" href="/pub/style/style.css" type="text/css" media="screen, handheld" title="default">
    <link rel="shortcut icon" href="/favicon.ico" type="image/vnd.microsoft.icon">
% if(test -f $sitedir/_werc/pub/style.css)
%    echo '    <link rel="stylesheet" href="/_werc/pub/style.css" type="text/css" media="screen" title="default">'

    <meta charset="UTF-8">
% # Legacy charset declaration for backards compatibility with non-html5 browsers.
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 

% if(! ~ $#meta_description 0)
%   echo '    <meta name="description" content="'$"meta_description'">'
% if(! ~ $#meta_keywords 0)
%   echo '    <meta name="keywords" content="'$"meta_keywords'">'

% h = `{get_lib_file headers.inc}
% if(! ~ $#h 0)
%   cat $h

    %($"extraHeaders%)

</head>
<body>


A  => lib/top_bar.inc +15 -0
@@ 1,15 @@
    <div>
      <a href="http://gsoc.cat-v.org">gsoc</a> |
      <a href="http://doc.cat-v.org">doc archive</a> |
      <a href="http://repo.cat-v.org">software repo</a> |
      <a href="http://ninetimes.cat-v.org">ninetimes</a> |
      <a href="http://harmful.cat-v.org">harmful</a> |
      <a href="http://9p.cat-v.org/">9P</a> |
      <a href="http://cat-v.org">cat-v.org</a>
    </div>

    <div>
      <a href="http://cat-v.org/update_log">site updates</a> |
      <a href="/sitemap">site map</a>
    </div>


A  => pub/default_favicon.ico +0 -0
A  => pub/style/imgs/sgl.png +0 -0
A  => pub/style/sinorca-screen-alt.css +292 -0
@@ 1,292 @@
/***********************************************
 * TITLE: Sinorca Alterative Screen Stylesheet *
 * URI  : sinorca/sinorca-screen-alt.css       *
 * MODIF: 2003-May-13 18:48 +0800              *
 ***********************************************/


/* ##### Common Styles ##### */

body {
  color: black;
  background-color: white;
  font-family: verdana, helvetica, arial, sans-serif;
  font-size: 71%;  /* Enables font size scaling in MSIE */
  margin: 0;
  padding: 0;
}

html > body {
  font-size: 8.5pt;
}

acronym, .titleTip {
  border-bottom: 1px dotted rgb(153,153,153);
  cursor: help;
  margin: 0;
  padding: 0 0 0.4px 0;
}

.doNotDisplay {
  display: none;
}

.smallCaps {
  font-size: 110%;
  font-variant: small-caps;
}


/* ##### Header ##### */

.superHeader {
  color: white;
  background-color: rgb(100,135,220);
  height: 2em;
}

.superHeader a {
  color: white;
  background-color: transparent;
  text-decoration: none;
  font-size: 91%;
  margin: 0;
  padding: 0 0.5ex 0 0.25ex;
}

.superHeader a:hover {
  text-decoration: underline;
}

.superHeader .left {
  position: absolute;
  left: 1.5mm;
  top: 0.75ex;
}

.superHeader .right {
  position: absolute;
  right: 1.5mm;
  top: 0.75ex;
}

.midHeader {
  color: rgb(39,78,144);
  background-color: rgb(140,170,230);
}

.headerTitle {
  font-size: 337%;
  font-weight: normal;
  margin: 0 0 0 4mm;
  padding: 0.25ex 0;
}

.subHeader {
  color: white;
  background-color: rgb(0,51,153);
  margin: 0;
  padding: 1ex 1ex 1ex 1.5mm;
}

.subHeader a {
  color: white;
  background-color: transparent;
  text-decoration: none;
  font-weight: bold;
  margin: 0;
  padding: 0 0.75ex 0 0.5ex;
}  

.subHeader a:hover {
  text-decoration: underline;
}

.superHeader .highlight, .subHeader .highlight {
  color: rgb(253,160,91);
  background-color: transparent;
}


/* ##### Side Boxes ##### */

#side-bar {
  width: 14em;
  margin: 2.5em 0 0 1.25mm;
  float: left;
  clear: left;
}

body > #side-bar {
  margin-left: 2.5mm;  /* Circumvents a rendering bug in MSIE (6.0) */
}

.sideBarTitle {
  color: white;
  background-color: rgb(100,135,220);
  font-weight: bold;
  margin: 0;
  padding: 0.4ex 0 0.4ex 0.6ex;
}

#side-bar ul {
  list-style-type: none;
  list-style-position: outside;
  margin: 0;
  padding: 0 0 2.25em 0;
}

#side-bar li {
  margin: 0;
  padding: 0.1ex 0;  /* Circumvents a rendering bug (?) in MSIE (6.0) */
}

#side-bar a, .thisPage {
  color: rgb(0,102,204);
  background-color: transparent;
  text-decoration: none;
  font-weight: bold;
  margin: 0;
  padding: 1.3ex 2ex;
  display: block;
}

.thisPage {
  color: black;
  background-color: transparent;
}

#side-bar a:hover {
  color: white;
  background-color: rgb(100,135,220);
  text-decoration: none;
}

.sideBarText {
  line-height: 1.5em;
  margin: 0 0 2.5em 0;
  padding: 1ex 0.5ex 0 0.5ex;
  display: block;
}

.sideBarText + .sideBarText {  /* Not recognised by MSIE (6.0) */
  margin-top: -1.5em;
}

#side-bar .sideBarText a {
  text-decoration: underline;
  font-weight: normal;
  margin: 0;
  padding: 0;
  display: inline;
}

#side-bar .sideBarText a:hover {
  color: rgb(0,102,204);
  background-color: transparent;
  text-decoration: none;
}


/* ##### Main Copy ##### */

#main-copy {
  color: black;
  background-color: transparent;
  text-align: justify;
  line-height: 1.5em;
  margin: -1em 0 0 15em;
  padding: 0.5mm 5mm 5mm 5mm;
}

#bodyText {
  margin: 0 0 0 15.5em;
  padding: 2mm 5mm 2mm 5mm;
}


#main-copy p {
  margin: 1em 1ex 2em 1ex;
  padding: 0;
}

#main-copy a {
  color: rgb(0,102,204);
  background-color: transparent;
  text-decoration: underline;
}

#main-copy a:hover {
  text-decoration: none;
}

#main-copy h1 {
  color: rgb(0,102,204);
  background-color: transparent;
  font-size: 145.5%;
  font-weight: bold;
  margin: 2em 0 0 0;
  padding: 0.5ex 0 0.5ex 0.6ex;
  border-bottom: 1px solid rgb(0,102,204);
}

#main-copy .topOfPage {
  color: rgb(0,102,204);
  background-color: transparent;
  font-size: 91%;
  font-weight: bold;
  text-decoration: none;
  margin: 3ex 1ex 0 0;
  padding: 0;
  float: right;
}

dl {
  margin: 1em 1ex 2em 1ex;
  padding: 0;
}

dt {
  font-weight: bold;
  margin: 0 0 0 0;
  padding: 0;
}

dd {
  margin: 0 0 2em 2em;
  padding: 0;
}


/* ##### Footer ##### */

#footer {
  color: white;
  background-color: rgb(100,135,220);
  font-size: 91%;
  margin: 0;
  padding: 1em 2.5mm 2.5ex 2.5mm;
  clear: both;
}

#footer .left {
  text-align: left;
  line-height: 1.45em;
  float: left;
  clear: left;
}

#footer .right {
  text-align: right;
  line-height: 1.45em;
}

#footer a {
  color: white;
  background-color: transparent;
  text-decoration: underline;
}

#footer a:hover {
  text-decoration: none;
}
\ No newline at end of file

A  => pub/style/style.css +11 -0
@@ 1,11 @@
body { display: flex; flex-wrap: wrap; font-family: sans; }
header { flex-basis: 100%; flex-shrink: 0; }
article { flex-basis: 60%; padding-left: 1em; }
footer { flex-basis: 100%; flex-shrink: 0; }
header nav { display: flex; justify-content: space-between; }
nav a, header a { text-decoration: none ; color: inherit; }
header h1 span { margin-left: 1em; font-size: 50%; font-style: italic; }
body > nav { flex-basis: content; padding-right: 1vw; min-width: 16em; }
nav ul { display: flex; flex-direction: column; list-style-type: none; list-style-position: outside; padding-left: 0;  }
nav li ul { padding-left: 0.6em }
footer { display: flex; justify-content: space-between; }

A  => pub/style/style.werc140.css +330 -0
@@ 1,330 @@
/* Default werc style */

body {
  color: black;
  background-color: white;
  font-family: Helvetica, Verdana, Arial, 'Liberation Sans', FreeSans, sans-serif;
  font-size: 84%;  /* Enables font size scaling in MSIE */
  margin: 0;
  padding: 0;
}


/* # Header # */
.superHeader {
  color: white;
  background-color: rgb(100,135,220);
  height: 1.6em;
}

.superHeader img { vertical-align: bottom; }

.superHeader a {
  color: white;
  background-color: transparent;
  font-size: 91%;
  margin: 0;
  padding: 0 0.5ex 0 0.25ex;
}

a { text-decoration: none; }
a:hover { text-decoration: underline; }

.superHeader div {
  position: absolute;
  top: 0.40ex;
}

.superHeader .left { left: 0.4em; }
.superHeader .right { right: 0.4em; }

.midHeader {
  color: rgb(39,78,144);
  background-color: rgb(140,170,230);
  background-color: #ff6d06;
  border: solid 0 black;
  border-width: 2px 0;
}

.headerTitle {
  color: black;
  font-size: 233%;
  font-weight: normal;
  margin: 0 0 0 4mm;
  padding: 0.25ex 0;
}
#headerSubTitle {
  font-size: 50%;
  font-style: italic;
  margin-left: 1em;
}

.headerTitle a { color: black; }
.headerTitle a:hover { text-decoration: none; }

.subHeader {
  display: none;
  color: white;
  background-color: rgb(0,51,153);
  margin: 0;
  padding: 1ex 1ex 1ex 1.5mm;
}

.subHeader a {
  color: white;
  background-color: transparent;
  font-weight: bold;
  margin: 0;
  padding: 0 0.75ex 0 0.5ex;
}  

.superHeader .highlight, .subHeader .highlight {
  color: rgb(253,160,91);
  background-color: transparent;
}


/* # Side # */
#side-bar {
  width: 16em;
  float: left;
  clear: left;
  border-right: 1px solid #ddd;
}

#side-bar div {
  border-bottom: 1px solid #ddd;
}

.sideBarTitle {
  font-weight: bold;
  margin: 0 0 0.5em 2mm;
  padding: 1em 0 0 0;
}

#side-bar ul {
  list-style-type: none;
  list-style-position: outside;
  margin: 0;
  padding: 0 0 0.3em 0;
}

li ul {
  padding-left: 0.6em !important;
}

#side-bar li {
  margin: 0;
  padding: 0.1ex 0;  /* Circumvents a rendering bug (?) in MSIE 6.0  XXX should move to iehacks.css, this causes an ugly gap */
}

#side-bar a {
  color: rgb(0,102,204);
  background-color: transparent;
  margin: 0;
  padding: 0.25em 1ex 0.25em 2mm;
  display: block;
  text-transform: capitalize;
  font-weight: bold!important;
  font-size: 102%;
  border-left: white solid 0.2em;
}

.thisPage, .thisPage a {
  color: black!important;
  background-color: white;
  padding-left: 5mm;
}

#side-bar a:hover {
  color: white;
  background-color: rgb(100,135,220);
  border-left: black solid 0.2em;
  text-decoration: none;
}

.sideBarText {
  line-height: 1.5em;
  margin: 0 0 1em 0;
  padding: 0 1.5ex 0 2.5mm;
  display: block;
}

#side-bar .sideBarText a {
  margin: 0;
  padding: 0;
  display: inline;
}

#side-bar .sideBarText a:hover {
  color: rgb(0,102,204);
  background-color: transparent;
  text-decoration: none;
}


/* # Main Copy # */
#main-copy {
  max-width: 70em;
  color: black;
  background-color: transparent;
  text-align: justify;
  line-height: 1.5em;
  margin: 0em 0 0 16em;
  padding: 0.5mm 5mm 5mm 5mm;
  border-left: 1px solid #ddd;
}

#bodyText {
  margin: 0 0 0 15.5em;
  padding: 2mm 5mm 2mm 5mm;
}

#main-copy p {
  margin: 1em 1ex 1em 1ex !important; /* Need !important so troff-generated pages don't look totally squezed */
  padding: 0;
}

#main-copy a {
  color: rgb(0,102,204);
  background-color: transparent;
}

#main-copy a:hover {
  color:  rgb(100,135,220);
}

#main-copy h1, #main-copy h2 {
  color: rgb(0,102,204);
  background-color: transparent;
  font-size: 145.5%;
  font-weight: bold;
  margin: 2em 0 0 0;
  padding: 0.5ex 0 0.5ex 0.6ex;
  border-bottom: 2px solid rgb(0,102,204);
}

#main-copy h2 {
  font-size: 115.5%;
  border-bottom: 1px solid rgb(0,102,204);
}

#main-copy .topOfPage {
  color: rgb(0,102,204);
  background-color: transparent;
  font-size: 91%;
  font-weight: bold;
  text-decoration: none;
  margin: 3ex 1ex 0 0;
  padding: 0;
  float: right;
}

dl {
  margin: 1em 1ex 2em 1ex;
  padding: 0;
}

dt {
  font-weight: bold;
  margin: 0 0 0 0;
  padding: 0;
}

dd {
  margin: 0 0 2em 2em;
  padding: 0;
}


/* # Footer # */
#footer {
  color: white;
  background-color: rgb(100,135,220);
  padding: 1em;
  clear: both;
}

#footer .left {
  text-align: left;
  line-height: 1.55em;
  float: left;
  clear: left;
}

#footer .right {
  text-align: right;
  line-height: 1.45em;
}

#footer a {
  color: white;
  background-color: transparent;
}


/* GENERAL */

table {
  border: solid 1px black;
}
th {
  background-color: #abc;
  border: solid 1px black;
   text-align: center;
}
td {
  background-color: #def;
  border: solid 1px black;
}

hr {
  border-width: 0px 0px 0.1em 0px;
  border-color: black;
}

acronym, .titleTip {
  border-bottom: 1px solid #ddd;
  cursor: help;
  margin: 0;
  padding: 0 0 0.4px 0;
}

pre {
  margin-left: 2em; 
  font-size: 1.2em;
}

blockquote {
  border-left: 1px solid blue;
  font-style: italic;
}

.smallCaps {
  font-size: 110%;
  font-variant: small-caps;
}

.doNotDisplay { display: none; }


.notify_errors,
.notify_notes,
.notify_success { padding: .8em; margin-bottom: 1em; border: 2px solid #ddd; }
 
.notify_errors { background: #FBE3E4; color: #8a1f11; border-color: #FBC2C4; }
.notify_notes { background: #FFF6BF; color: #514721; border-color: #FFD324; }
.notify_success { background: #E6EFC2; color: #264409; border-color: #C6D880; }
.notify_errors a { color: #8a1f11; }
.notify_notes a { color: #514721; }
.notify_success a { color: #264409; }


/* # Page/Handler specific # */
h1.dir-list-head, ul.dir-list {
  text-transform: capitalize;
  font-weight: bold;
}
ul.sitemap-list a {
  text-transform: capitalize;
}

A  => pub/style/style_old.css +330 -0
@@ 1,330 @@
/* Old Default style */
/* ##### Common Styles ##### */

body {
  color: black;
  XXXbackground-color: rgb(240,240,240);
  background-color: white;
  font-family: verdana, helvetica, arial, sans-serif;
  font-size: 71%;  /* Enables font size scaling in MSIE */
  margin: 0;
  padding: 0;
}

html > body {
  font-size: 8.5pt;
}

acronym, .titleTip {
  border-bottom: 1px dotted rgb(153,153,153);
  cursor: help;
  margin: 0;
  padding: 0 0 0.4px 0;
}

.doNotDisplay {
  display: none;
}

.smallCaps {
  font-size: 110%;
  font-variant: small-caps;
}


/* ##### Header ##### */

.superHeader {
  color: white;
  background-color: rgb(100,135,220);
  height: 2em;
}

.superHeader a {
  color: white;
  background-color: transparent;
  text-decoration: none;
  font-size: 91%;
  margin: 0;
  padding: 0 0.5ex 0 0.25ex;
}

.superHeader a:hover {
  text-decoration: underline;
}

.superHeader .left {
  position: absolute;
  left: 1.5mm;
  top: 0.75ex;
}

.superHeader .right {
  position: absolute;
  right: 1.5mm;
  top: 0.75ex;
}

.midHeader {
  color: rgb(39,78,144);
  background-color: rgb(140,170,230);
  border: solid 0 black;
  border-width: 0.3em 0;
}

.headerTitle {
  color: black;
  font-size: 337%;
  font-weight: normal;
  margin: 0 0 0 4mm;
  padding: 0.25ex 0;
}
#headerSubTitle {
  font-size: 50%;
  font-style: italic;
}

.subHeader {
display: none;
  color: white;
  background-color: rgb(0,51,153);
  margin: 0;
  padding: 1ex 1ex 1ex 1.5mm;
}

.subHeader a {
  color: white;
  background-color: transparent;
  text-decoration: none;
  font-weight: bold;
  margin: 0;
  padding: 0 0.75ex 0 0.5ex;
}  

.subHeader a:hover {
  text-decoration: underline;
}

.superHeader .highlight, .subHeader .highlight {
  color: rgb(253,160,91);
  background-color: transparent;
}


/* ##### Side Bar ##### */

#side-bar {
  width: 15em;
  float: left;
  clear: left;
  border-right: 1px solid rgb(153,153,153);
}

#side-bar div {
  border-bottom: 1px solid rgb(153,153,153);
}

.sideBarTitle {
  font-weight: bold;
  margin: 0 0 0.5em 2.5mm;
  padding: 1em 0 0 0;
}

#side-bar ul {
  list-style-type: none;
  list-style-position: outside;
  margin: 0;
  padding: 0 0 1.1em 0;
}

#side-bar li {
  margin: 0;
  padding: 0.1ex 0;  /* Circumvents a rendering bug (?) in MSIE 6.0 */
}

#side-bar a, .thisPage {
  color: rgb(0,102,204);
  background-color: transparent;
  XXXtext-decoration: none;
  margin: 0;
  padding: 0.55em 1ex 0.55em 5mm;
  display: block;
}

.thisPage {
  color: black;
  background-color: white;
  padding-left: 5mm;
  XXXborder-top: 1px solid rgb(153,153,153);
  XXXborder-bottom: 1px solid rgb(153,153,153);
  font-weight: 600;
}

#side-bar a:hover {
  color: white;
  background-color: rgb(100,135,220);
  text-decoration: none;
}

.sideBarText {
  line-height: 1.5em;
  margin: 0 0 1em 0;
  padding: 0 1.5ex 0 2.5mm;
  display: block;
}

#side-bar .sideBarText a {
  text-decoration: underline;
  margin: 0;
  padding: 0;
  display: inline;
}

#side-bar .sideBarText a:hover {
  color: rgb(0,102,204);
  background-color: transparent;
  text-decoration: none;
}

.lighterBackground {
  color: inherit;
  background-color: white;
}


/* ##### Main Copy ##### */

#main-copy {
  max-width: 90em;
  color: black;
  background-color: white;
  text-align: justify;
  line-height: 1.5em;
  margin: 0 0 0 15em;
  padding: 0.5mm 5mm 5mm 5mm;
  border-left: 1px solid rgb(153,153,153);
}

#main-copy p {
  margin: 1em 1ex 2em 1ex;
  padding: 0;
}

#main-copy a {
  color: rgb(0,102,204);
  background-color: transparent;
  text-decoration: underline;
}

#main-copy a:hover {
  text-decoration: none;
}

#main-copy h1 {
  color: white;
  background-color: rgb(100,135,220);
  font-size: 100%;
  font-weight: bold;
  margin: 3em 0 0 0;
  padding: 0.5ex 0 0.5ex 1ex;
}

#main-copy .topOfPage {
  color: white;
  background-color: transparent;
  font-size: 91%;
  font-weight: bold;
  text-decoration: none;
  margin: 2.5ex 1ex 0 0;  /* For MSIE */
  padding: 0;
  float: right;
}

#main-copy > .topOfPage {
  margin: 2.75ex 1ex 0 0;  /* For fully standards-compliant user agents */
}

dl {
  margin: 1em 1ex 2em 1ex;
  padding: 0;
}

dt {
  font-weight: bold;
  margin: 0 0 0 0;
  padding: 0;
}

dd {
  margin: 0 0 2em 2em;
  padding: 0;
}


/* ##### Footer ##### */

#footer {
  color: white;
  background-color: rgb(100,135,220);
  font-size: 91%;
  margin: 0;
  padding: 1em 2.5mm 2.5ex 2.5mm;
  clear: both;
}

#footer .left {
  line-height: 1.45em;
  float: left;
  clear: left;
}

#footer .right {
  text-align: right;
  line-height: 1.45em;
}

#footer a {
  color: white;
  background-color: transparent;
  text-decoration: underline;
}

#footer a:hover {
  text-decoration: none;
}


/* GENERAL */
/* Spam */
.spam {
    text-align: center;
}

/* Tables */
table {
    border: solid 1px black;
}
th {
    background-color: #abc;
    border: solid 1px black;
}
td {
    background-color: #def;
    border: solid 1px black;
}

hr {
    border-width: 0px 0px 0.1em 0px;
    border-color: black;
}

.spam table, .spam th, .spam td {
	border: none;
}

/* Code */
pre {
    margin-left: 2em; 
}



A  => sites/plan9.krisyotam.com/index.md +12 -0
@@ 1,12 @@
kris yotam
==========

Plan 9, 9front, rc, and related work.

Tools, patches, notes, and configuration for Plan 9 and
Plan 9-adjacent systems.

- [tools](/tools/) — scripts and programs
- [patches](/patches/) — contributed patches
- [notes](/notes/) — setup guides and reference
- [setup](/setup/) — machine configs and install notes

A  => sites/plan9.krisyotam.com/notes/index.md +5 -0
@@ 1,5 @@
notes
=====

Reference notes on Plan 9 internals, namespaces,
the rc shell, and related topics.

A  => sites/plan9.krisyotam.com/patches/index.md +4 -0
@@ 1,4 @@
patches
=======

Patches for 9front and related software.

A  => sites/plan9.krisyotam.com/setup/index.md +4 -0
@@ 1,4 @@
setup
=====

Machine configurations and install notes.

A  => sites/plan9.krisyotam.com/tools/index.md +4 -0
@@ 1,4 @@
tools
=====

Plan 9 and rc shell tools.

A  => tpl/_debug.tpl +29 -0
@@ 1,29 @@
% if(! ~ $#debug_shell 0) {
<form method="POST" name="prompt">
<input size="80" type="text" name="command" value="%($"post_arg_command%)">
<input type="submit" Value="Run">
</form>
<script language="javascript"><!--
document.prompt.command.focus()
//--></script>

%{
fn evl {
    # Buffering is probably messing this up:
    #rc -c 'flag x +;{'^$post_arg_command'} |[2] awk ''{print ">> "$0}'''
    rc -c 'flag s +; flag x +;'^$post_arg_command
}
    if(! ~ $#post_arg_command 0 && ! ~ $#post_arg_command '') {
        echo '<hr><pre>'
        evl | escape_html |[2] awk '{print "<b>"$0"</b>"}' 
        echo '</pre>'
    }
%}
% }

<hr><pre>
% env | escape_html
</pre><hr>

% umask


A  => tpl/_users/login.tpl +18 -0
@@ 1,18 @@
<h1>User login</h1>
<br />
% if(check_user) {
    You are logged in as: <b>%($logged_user%)</b>
% }
% if not {
%    if (~ $REQUEST_METHOD POST)
%        echo '<div class="notify_errors">Login failed!</div>'
<form method="POST" action="" style="text-align: right; float: left;">
<fieldset>
    <label>User name: <input type="text" name="user_name" value="%($"post_arg_user_name%)"/></label><br>
    <label>User password: <input type="password" name="user_password"></label><br>
    <input name="s" type="submit" value="Login">
</fieldset>
</form>
% }

<br style="clear:left">

A  => tpl/sitemap.tpl +67 -0
@@ 1,67 @@
<h1>Site map</h1>

%{
tmpfile=/tmp/werc_sitemap_$pid.txt
tmpfilex=/tmp/werc_sitemapx_$pid.txt
saveddf=$dirfilter

MON2NUM='s/Jan/01/; s/Feb/02/; s/Mar/03/; s/Apr/04/; s/May/05/; s/Jun/06/; s/Jul/07/; s/Aug/08/; s/Sep/09/; s/Oct/10/; s/Nov/11/; s/Dec/12/;'

fn get_mdate {
    t=`{mtime $1}
    t=`{date $t(1) | sed -e $MON2NUM -e 's/  ([0-9]) / 0\1 /g'} # Make sure day of the month is two digits.
    echo $t(6)^'-'^$t(2)^'-'^$t(3)
}

fn listDir {
    d=$1
    dirfilter=$saveddf
    if(test -f $d/_werc/config)
        . $d/_werc/config

    if(~ $#perm_redir_to 0) {
        echo '<ul class="sitemap-list">'

        for(i in `{ls -dF $d^*/ $d^*.md $d^*.html $d^*.txt >[2]/dev/null | sed $dirfilter}) {
            desc=`{get_file_title $i}
            u=`{echo $i|sed 's!'$sitedir'!!; '$dirclean's!/index$!/!; '}
            if(! ~ $#desc 0 && ! ~ $desc '')
                desc=' - '$"desc
            n=`{echo /$u|sed 's/[\-_]/ /g; s,.*/([^/]+)/?$,\1,'}
            echo '<li><a href="'$u'">'^$"n^'</a>' $"desc '</li>' 
            echo $base_url^$u >> $tmpfile
            echo '<url><loc>'$base_url^$u'</loc><lastmod>'^`{get_mdate $i}^'</lastmod></url>' >> $tmpfilex
            if(test -d $i)
                @{ listDir $i }
        }
        echo '</ul>'
    }
}


fltr_cache listDir $sitedir/

if(test -s $tmpfile) {
    mv $tmpfile $sitedir/sitemap.txt &
}
if not if(test -f $tmpfile)
    rm $tmpfile

if(test -s $tmpfilex) {
    {
        echo '<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'

        cat $tmpfilex
        rm $tmpfilex &
        echo '</urlset>'
        # TODO Enable automaic search engine update notification.
        #hget 'http://google.com/ping?sitemap='^`{url_encode $base_url'/sitemap.gz'} > /dev/null
    
    } | gzip > $sitedir/sitemap.gz &
    #} > $sitedir/sitemap.xml &
}
if not if(test -f $tmpfilex)
    rm $tmpfilex

%}