From 39318169e0b50551db511851829f9337c5fa6313 Mon Sep 17 00:00:00 2001 From: glenda Date: Sun, 15 Nov 2020 15:13:27 +0000 Subject: Import site to git --- README | 79 ++ apps/blagh/app.rc | 142 ++ apps/blagh/atom.tpl | 58 + apps/blagh/convert.rc | 20 + apps/blagh/jsonfeed.tpl | 35 + apps/blagh/new_post.tpl | 11 + apps/blagh/rss20.tpl | 43 + apps/bridge/app.rc | 103 ++ apps/bridge/comments_list.tpl | 13 + apps/bridge/foot.tpl | 37 + apps/dirdir/app.rc | 40 + apps/dirdir/edit.tpl | 25 + apps/dirdir/sidebar_controls.tpl | 3 + apps/duckduckgo/HOWTO | 20 + apps/duckduckgo/app.rc | 30 + apps/duckduckgo/footer.inc.sample | 3 + apps/hello/app.rc | 10 + apps/paste/app.rc | 45 + apps/wman/app.rc | 89 ++ apps/wman/man_page.tpl | 3 + apps/wman/page_list.tpl | 11 + apps/wman/search.tpl | 20 + apps/wman/section_list.tpl | 11 + bin/aux/addwuser.rc | 33 + bin/aux/bpst.rc | 64 + bin/aux/gensitemaptxt.rc | 14 + bin/aux/runtsts.rc | 16 + bin/cgilib.rc | 236 ++++ bin/contrib/fix-rc-scripts | 27 + bin/contrib/hgweb.config | 12 + bin/contrib/hgwebdir.cgi | 47 + bin/contrib/markdown.pl | 1447 ++++++++++++++++++++ bin/contrib/md2html.awk | 427 ++++++ bin/contrib/rc-httpd/handlers/authorize | 6 + bin/contrib/rc-httpd/handlers/cgi | 46 + bin/contrib/rc-httpd/handlers/dir-index | 111 ++ bin/contrib/rc-httpd/handlers/error | 43 + bin/contrib/rc-httpd/handlers/redirect | 30 + bin/contrib/rc-httpd/handlers/serve-static | 43 + bin/contrib/rc-httpd/handlers/static-or-cgi | 14 + bin/contrib/rc-httpd/handlers/static-or-index | 5 + bin/contrib/rc-httpd/lib/urldecode.awk | 39 + bin/contrib/rc-httpd/rc-httpd | 102 ++ bin/contrib/rc-httpd/select-handler | 20 + bin/contrib/tcp80 | 7 + bin/contrib/urldecode.awk | 39 + bin/contrib/urlencode.awk | 126 ++ bin/contrib/webserver.rc | 30 + bin/corehandlers.rc | 152 ++ bin/fltr_cache.rc | 37 + bin/template.awk | 55 + bin/werc.rc | 138 ++ bin/werc_errlog_wrap.rc | 5 + bin/wercconf.rc | 19 + bin/werclib.rc | 393 ++++++ etc/initrc | 39 + etc/users/GROUP_AND_USER_ACCOUNTS | 1 + lib/404.tpl | 3 + lib/default_master.tpl | 26 + lib/footer.inc | 7 + lib/headers.tpl | 29 + lib/top_bar.inc | 15 + pub/default_favicon.ico | Bin 0 -> 2150 bytes pub/style/imgs/sgl.png | Bin 0 -> 14763 bytes pub/style/sinorca-screen-alt.css | 292 ++++ pub/style/style.cat-v.css | 50 + pub/style/style.css | 11 + pub/style/style.werc140.css | 330 +++++ pub/style/style_old.css | 330 +++++ sites/pmikkelsen.com/_files/djv.tar | Bin 0 -> 1822720 bytes sites/pmikkelsen.com/_files/djvmono.tar | Bin 0 -> 993280 bytes sites/pmikkelsen.com/_werc/config | 3 + sites/pmikkelsen.com/_werc/lib/footer.inc | 1 + sites/pmikkelsen.com/_werc/lib/top_bar.inc | 10 + sites/pmikkelsen.com/_werc/pub/style.css | 67 + sites/pmikkelsen.com/contact.md | 3 + sites/pmikkelsen.com/haskell/quine.md | 94 ++ sites/pmikkelsen.com/images/2048.gif | Bin 0 -> 4157248 bytes sites/pmikkelsen.com/images/acme-in-action.png | Bin 0 -> 286658 bytes sites/pmikkelsen.com/images/cdude.png | Bin 0 -> 20432 bytes sites/pmikkelsen.com/images/cursed.png | Bin 0 -> 18433 bytes sites/pmikkelsen.com/images/discordgif.gif | Bin 0 -> 9196759 bytes sites/pmikkelsen.com/images/djvfonts.png | Bin 0 -> 138021 bytes sites/pmikkelsen.com/images/linuxreverse.gif | Bin 0 -> 201797 bytes sites/pmikkelsen.com/images/mordor.gif | Bin 0 -> 1620703 bytes sites/pmikkelsen.com/index.md | 28 + sites/pmikkelsen.com/linux/eduroam.md | 15 + sites/pmikkelsen.com/linux/wifi.md | 12 + .../pmikkelsen.com/me/how-i-started-using-acme.md | 117 ++ sites/pmikkelsen.com/me/stuff-i-use.md | 46 + .../opinions/favourite-programming-languages.md | 1 + sites/pmikkelsen.com/plan9/basic_9p_server.md | 58 + sites/pmikkelsen.com/plan9/discord.md | 83 ++ sites/pmikkelsen.com/plan9/dns.md | 41 + sites/pmikkelsen.com/plan9/fonts.md | 29 + sites/pmikkelsen.com/plan9/lets_encrypt.md | 59 + .../plan9/mounting-9p-over-drawterm.md | 58 + sites/pmikkelsen.com/plan9/network_booting.md | 30 + sites/pmikkelsen.com/plan9/trellofs.md | 347 +++++ sites/pmikkelsen.com/plan9/using_irc.md | 48 + sites/pmikkelsen.com/web/the-setup-of-this-site.md | 3 + tpl/_debug.tpl | 29 + tpl/_users/login.tpl | 18 + tpl/sitemap.tpl | 67 + 104 files changed, 7034 insertions(+) create mode 100644 README create mode 100644 apps/blagh/app.rc create mode 100644 apps/blagh/atom.tpl create mode 100755 apps/blagh/convert.rc create mode 100644 apps/blagh/jsonfeed.tpl create mode 100644 apps/blagh/new_post.tpl create mode 100644 apps/blagh/rss20.tpl create mode 100755 apps/bridge/app.rc create mode 100755 apps/bridge/comments_list.tpl create mode 100755 apps/bridge/foot.tpl create mode 100755 apps/dirdir/app.rc create mode 100755 apps/dirdir/edit.tpl create mode 100755 apps/dirdir/sidebar_controls.tpl create mode 100644 apps/duckduckgo/HOWTO create mode 100755 apps/duckduckgo/app.rc create mode 100644 apps/duckduckgo/footer.inc.sample create mode 100755 apps/hello/app.rc create mode 100755 apps/paste/app.rc create mode 100755 apps/wman/app.rc create mode 100755 apps/wman/man_page.tpl create mode 100755 apps/wman/page_list.tpl create mode 100755 apps/wman/search.tpl create mode 100755 apps/wman/section_list.tpl create mode 100755 bin/aux/addwuser.rc create mode 100755 bin/aux/bpst.rc create mode 100755 bin/aux/gensitemaptxt.rc create mode 100755 bin/aux/runtsts.rc create mode 100755 bin/cgilib.rc create mode 100755 bin/contrib/fix-rc-scripts create mode 100755 bin/contrib/hgweb.config create mode 100755 bin/contrib/hgwebdir.cgi create mode 100755 bin/contrib/markdown.pl create mode 100755 bin/contrib/md2html.awk create mode 100755 bin/contrib/rc-httpd/handlers/authorize create mode 100755 bin/contrib/rc-httpd/handlers/cgi create mode 100755 bin/contrib/rc-httpd/handlers/dir-index create mode 100755 bin/contrib/rc-httpd/handlers/error create mode 100755 bin/contrib/rc-httpd/handlers/redirect create mode 100755 bin/contrib/rc-httpd/handlers/serve-static create mode 100755 bin/contrib/rc-httpd/handlers/static-or-cgi create mode 100755 bin/contrib/rc-httpd/handlers/static-or-index create mode 100755 bin/contrib/rc-httpd/lib/urldecode.awk create mode 100755 bin/contrib/rc-httpd/rc-httpd create mode 100755 bin/contrib/rc-httpd/select-handler create mode 100755 bin/contrib/tcp80 create mode 100755 bin/contrib/urldecode.awk create mode 100755 bin/contrib/urlencode.awk create mode 100755 bin/contrib/webserver.rc create mode 100755 bin/corehandlers.rc create mode 100755 bin/fltr_cache.rc create mode 100755 bin/template.awk create mode 100755 bin/werc.rc create mode 100755 bin/werc_errlog_wrap.rc create mode 100755 bin/wercconf.rc create mode 100755 bin/werclib.rc create mode 100755 etc/initrc create mode 100644 etc/users/GROUP_AND_USER_ACCOUNTS create mode 100644 lib/404.tpl create mode 100644 lib/default_master.tpl create mode 100644 lib/footer.inc create mode 100644 lib/headers.tpl create mode 100644 lib/top_bar.inc create mode 100755 pub/default_favicon.ico create mode 100755 pub/style/imgs/sgl.png create mode 100755 pub/style/sinorca-screen-alt.css create mode 100644 pub/style/style.cat-v.css create mode 100644 pub/style/style.css create mode 100755 pub/style/style.werc140.css create mode 100755 pub/style/style_old.css create mode 100644 sites/pmikkelsen.com/_files/djv.tar create mode 100644 sites/pmikkelsen.com/_files/djvmono.tar create mode 100644 sites/pmikkelsen.com/_werc/config create mode 100644 sites/pmikkelsen.com/_werc/lib/footer.inc create mode 100644 sites/pmikkelsen.com/_werc/lib/top_bar.inc create mode 100644 sites/pmikkelsen.com/_werc/pub/style.css create mode 100644 sites/pmikkelsen.com/contact.md create mode 100644 sites/pmikkelsen.com/haskell/quine.md create mode 100644 sites/pmikkelsen.com/images/2048.gif create mode 100644 sites/pmikkelsen.com/images/acme-in-action.png create mode 100644 sites/pmikkelsen.com/images/cdude.png create mode 100644 sites/pmikkelsen.com/images/cursed.png create mode 100644 sites/pmikkelsen.com/images/discordgif.gif create mode 100644 sites/pmikkelsen.com/images/djvfonts.png create mode 100644 sites/pmikkelsen.com/images/linuxreverse.gif create mode 100644 sites/pmikkelsen.com/images/mordor.gif create mode 100644 sites/pmikkelsen.com/index.md create mode 100644 sites/pmikkelsen.com/linux/eduroam.md create mode 100644 sites/pmikkelsen.com/linux/wifi.md create mode 100644 sites/pmikkelsen.com/me/how-i-started-using-acme.md create mode 100644 sites/pmikkelsen.com/me/stuff-i-use.md create mode 100644 sites/pmikkelsen.com/opinions/favourite-programming-languages.md create mode 100644 sites/pmikkelsen.com/plan9/basic_9p_server.md create mode 100644 sites/pmikkelsen.com/plan9/discord.md create mode 100644 sites/pmikkelsen.com/plan9/dns.md create mode 100644 sites/pmikkelsen.com/plan9/fonts.md create mode 100644 sites/pmikkelsen.com/plan9/lets_encrypt.md create mode 100644 sites/pmikkelsen.com/plan9/mounting-9p-over-drawterm.md create mode 100644 sites/pmikkelsen.com/plan9/network_booting.md create mode 100644 sites/pmikkelsen.com/plan9/trellofs.md create mode 100644 sites/pmikkelsen.com/plan9/using_irc.md create mode 100644 sites/pmikkelsen.com/web/the-setup-of-this-site.md create mode 100644 tpl/_debug.tpl create mode 100644 tpl/_users/login.tpl create mode 100644 tpl/sitemap.tpl diff --git a/README b/README new file mode 100644 index 0000000..b4dc773 --- /dev/null +++ b/README @@ -0,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. diff --git a/apps/blagh/app.rc b/apps/blagh/app.rc new file mode 100644 index 0000000..c63689d --- /dev/null +++ b/apps/blagh/app.rc @@ -0,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 'Make a new post' + } + + if(~ $req_path $blagh_uri) { + handler_body_main=blagh_body + u=$blagh_uri'index' + extraHeaders=$"extraHeaders ^ \ +' + +' + } + 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 '

'$"blogTitle'

' + + # 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 '
(RSS Feed|Atom Feed)
' + + # 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' +} diff --git a/apps/blagh/atom.tpl b/apps/blagh/atom.tpl new file mode 100644 index 0000000..97c665f --- /dev/null +++ b/apps/blagh/atom.tpl @@ -0,0 +1,58 @@ + + +%{ +# 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}'}}} +%} + + + +% if(! ~ $"conf_blog_pubsubdub_hub '') { +% echo '' +% } + + + %($base_url^$req_path%) + + + <![CDATA[%($siteTitle%)]]> + + + %($fupdated%) + + +% for(f in `{get_post_list $blagh_root$blagh_dirs}) { +% statpost $f + + +% # Maybe we should be smarter, see: http://diveintomark.org/archives/2004/05/28/howto-atom-id, example: tag:intertwingly.net,2004:2899 + %($post_uri%) + + <![CDATA[%($title%)]]> +% # + + +
+ +
+ +% # rfc3339 date when entry was last updated. +% eupdated=`{ndate -a `{date `{mtime $f | awk '{print $1}'}}} + %($eupdated%) +
+ +% } + +
+ +% exit diff --git a/apps/blagh/convert.rc b/apps/blagh/convert.rc new file mode 100755 index 0000000..0640805 --- /dev/null +++ b/apps/blagh/convert.rc @@ -0,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 + +} diff --git a/apps/blagh/jsonfeed.tpl b/apps/blagh/jsonfeed.tpl new file mode 100644 index 0000000..fd97ed4 --- /dev/null +++ b/apps/blagh/jsonfeed.tpl @@ -0,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 + diff --git a/apps/blagh/new_post.tpl b/apps/blagh/new_post.tpl new file mode 100644 index 0000000..bd521c4 --- /dev/null +++ b/apps/blagh/new_post.tpl @@ -0,0 +1,11 @@ +
+% notices_handler +
+ Submit a new blog post +
+ + + + +
+
diff --git a/apps/blagh/rss20.tpl b/apps/blagh/rss20.tpl new file mode 100644 index 0000000..0cba818 --- /dev/null +++ b/apps/blagh/rss20.tpl @@ -0,0 +1,43 @@ + + +%{ +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} }} +} + +%} + + + + + <![CDATA[%($siteTitle%)]]> + %($base_url^$req_path%) + + en-us + +%{ + # uriel99+rss@gmail.com (Uriel) + # 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 ''$"lbd'' + # rfc2822 publication date for content in the channel. + pubdate=`{ndate -m} + for(f in `{get_post_list $blagh_root$blagh_dirs}){ + statpost $f +%} + + <![CDATA[%($title%)]]> + + %($post_uri%) + %($post_uri%) + %($pubdate%) + %($summary%) + +% } + + diff --git a/apps/bridge/app.rc b/apps/bridge/app.rc new file mode 100755 index 0000000..40477ba --- /dev/null +++ b/apps/bridge/app.rc @@ -0,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 '

To post a comment you need to login first.

' + } +} + +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 +} diff --git a/apps/bridge/comments_list.tpl b/apps/bridge/comments_list.tpl new file mode 100755 index 0000000..03e0ddc --- /dev/null +++ b/apps/bridge/comments_list.tpl @@ -0,0 +1,13 @@ +
+

Comments

+ +% for(c in `{ls $comments_dir/}) { +% if(test -s $c/body) { +
+
By: %(`{cat $c/user}%) (%(`{cat $c/posted}%)) +
+% cat $c/body | escape_html | sed 's,$,
,' +
+% } +% } + diff --git a/apps/bridge/foot.tpl b/apps/bridge/foot.tpl new file mode 100755 index 0000000..0dad21d --- /dev/null +++ b/apps/bridge/foot.tpl @@ -0,0 +1,37 @@ +
+ +% notices_handler +
+ +
+ + +% if(~ $#logged_user 0) { +% if(~ $#allow_new_user_comments 1) { + + + + + +
+ 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 login before posting. +
+% } +% if not if(~ $#bridge_anon_comments 1) { + +% } +% } +
diff --git a/apps/dirdir/app.rc b/apps/dirdir/app.rc new file mode 100755 index 0000000..1aa9cbd --- /dev/null +++ b/apps/dirdir/app.rc @@ -0,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 '$"req_path'!' +} diff --git a/apps/dirdir/edit.tpl b/apps/dirdir/edit.tpl new file mode 100755 index 0000000..1a5b206 --- /dev/null +++ b/apps/dirdir/edit.tpl @@ -0,0 +1,25 @@ +
+

Editing: %($req_path%)

+
+
+ +
+ + + DirDir documents are written using Markdown syntax. +
+
+ +% if(! ~ $"post_arg_dirdir_preview '') { +

Preview:

+
+% echo $post_arg_edit_text | $formatter +
+% } diff --git a/apps/dirdir/sidebar_controls.tpl b/apps/dirdir/sidebar_controls.tpl new file mode 100755 index 0000000..a897fc1 --- /dev/null +++ b/apps/dirdir/sidebar_controls.tpl @@ -0,0 +1,3 @@ +
+ +
diff --git a/apps/duckduckgo/HOWTO b/apps/duckduckgo/HOWTO new file mode 100644 index 0000000..8bb952c --- /dev/null +++ b/apps/duckduckgo/HOWTO @@ -0,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 + diff --git a/apps/duckduckgo/app.rc b/apps/duckduckgo/app.rc new file mode 100755 index 0000000..72dd0ec --- /dev/null +++ b/apps/duckduckgo/app.rc @@ -0,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 ' +

Site search

+

using DuckDuckGo

+
+ + + +
' + +} + diff --git a/apps/duckduckgo/footer.inc.sample b/apps/duckduckgo/footer.inc.sample new file mode 100644 index 0000000..4dd671d --- /dev/null +++ b/apps/duckduckgo/footer.inc.sample @@ -0,0 +1,3 @@ +
Powered by werc
+ +
diff --git a/apps/hello/app.rc b/apps/hello/app.rc new file mode 100755 index 0000000..e6faaa8 --- /dev/null +++ b/apps/hello/app.rc @@ -0,0 +1,10 @@ +fn hello_init { + if(~ $req_path /hello) { + handler_body_main='hello_body' + pageTitle='Hi title!' + } +} + +fn hello_body { + echo 'Hello world!' +} diff --git a/apps/paste/app.rc b/apps/paste/app.rc new file mode 100755 index 0000000..af0c76d --- /dev/null +++ b/apps/paste/app.rc @@ -0,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 '
+

pasted data is not publically indexed

+
+
+

+ (do not change) +
+
+ ' +} diff --git a/apps/wman/app.rc b/apps/wman/app.rc new file mode 100755 index 0000000..8f0a150 --- /dev/null +++ b/apps/wman/app.rc @@ -0,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 ' ' '|'}^')\)!&!g' \ + | awk '/^$/ {if(n != 1) print; n=1; next} /./ {n=0; print}' \ + | col -x +} + + diff --git a/apps/wman/man_page.tpl b/apps/wman/man_page.tpl new file mode 100755 index 0000000..945e23a --- /dev/null +++ b/apps/wman/man_page.tpl @@ -0,0 +1,3 @@ +
+% wman_page_gen $wman_page_file
+
diff --git a/apps/wman/page_list.tpl b/apps/wman/page_list.tpl new file mode 100755 index 0000000..b98600d --- /dev/null +++ b/apps/wman/page_list.tpl @@ -0,0 +1,11 @@ +% d=`{wman_get_section_desc $wman_cat} +

Manual pages - Section %($wman_cat%): %($"d%)

+ + + diff --git a/apps/wman/search.tpl b/apps/wman/search.tpl new file mode 100755 index 0000000..a6c59e4 --- /dev/null +++ b/apps/wman/search.tpl @@ -0,0 +1,20 @@ +
+
+ + + + +% if(! ~ $"post_arg_wman_search '') { +% if(~ $"wman_search_results '') { + No matches found for '%($post_arg_wman_search%)'. +% } +% if not { +
    +% echo $wman_search_results|awk -F/ '$(NF-1) ~ "^[0-9]+$" {printf "
  • %s(%s)
  • ", $(NF-1),$NF, $NF, $(NF-1)}' +
+% } +% } + +
+
+ diff --git a/apps/wman/section_list.tpl b/apps/wman/section_list.tpl new file mode 100755 index 0000000..299d613 --- /dev/null +++ b/apps/wman/section_list.tpl @@ -0,0 +1,11 @@ +

Manual Sections

+ + diff --git a/bin/aux/addwuser.rc b/bin/aux/addwuser.rc new file mode 100755 index 0000000..9364d39 --- /dev/null +++ b/bin/aux/addwuser.rc @@ -0,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 + } + diff --git a/bin/aux/bpst.rc b/bin/aux/bpst.rc new file mode 100755 index 0000000..e60d034 --- /dev/null +++ b/bin/aux/bpst.rc @@ -0,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! diff --git a/bin/aux/gensitemaptxt.rc b/bin/aux/gensitemaptxt.rc new file mode 100755 index 0000000..a1b349d --- /dev/null +++ b/bin/aux/gensitemaptxt.rc @@ -0,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 +} + +} diff --git a/bin/aux/runtsts.rc b/bin/aux/runtsts.rc new file mode 100755 index 0000000..b5b1df7 --- /dev/null +++ b/bin/aux/runtsts.rc @@ -0,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 + } +} diff --git a/bin/cgilib.rc b/bin/cgilib.rc new file mode 100755 index 0000000..4516bf9 --- /dev/null +++ b/bin/cgilib.rc @@ -0,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/&/\&/g; s//\>/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 + }' +} + + diff --git a/bin/contrib/fix-rc-scripts b/bin/contrib/fix-rc-scripts new file mode 100755 index 0000000..beb21c5 --- /dev/null +++ b/bin/contrib/fix-rc-scripts @@ -0,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 + } +} diff --git a/bin/contrib/hgweb.config b/bin/contrib/hgweb.config new file mode 100755 index 0000000..fba802b --- /dev/null +++ b/bin/contrib/hgweb.config @@ -0,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/ + diff --git a/bin/contrib/hgwebdir.cgi b/bin/contrib/hgwebdir.cgi new file mode 100755 index 0000000..5fe4b16 --- /dev/null +++ b/bin/contrib/hgwebdir.cgi @@ -0,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)) diff --git a/bin/contrib/markdown.pl b/bin/contrib/markdown.pl new file mode 100755 index 0000000..3758a87 --- /dev/null +++ b/bin/contrib/markdown.pl @@ -0,0 +1,1447 @@ +#!/usr/bin/env perl +# +# Markdown -- A text-to-HTML conversion tool for web writers +# +# Copyright (c) 2004 John Gruber +# +# +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 +# and 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]* + ? # 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/"/"/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

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.: + #

+ #
+ # tags for inner block must be indented. + #
+ #
+ # + # 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 `
` and stop at the first `
`. + $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 + # 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` to `\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 + .* # 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
. 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: + + ) + [ \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 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 `` + # Must come after _DoAnchors(), because you can use < and > + # delimiters in inline links like [this](). + $text = _DoAutoLinks($text); + + $text = _EncodeAmpsAndAngles($text); + + $text = _DoItalicsAndBold($text); + + # Do hard breaks: + $text =~ s/ {2,}\n/ or 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
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 = "? # 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 = " 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/"/"/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 = "\"$alt_text\"";? # 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/"/"/g; + $title =~ s/"/"/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 = "\"$alt_text\"";" . _RunSpanGamut($1) . "\n\n"; + }egmx; + + $text =~ s{ ^(.+)[ \t]*\n-+[ \t]*\n+ }{ + "

" . _RunSpanGamut($1) . "

\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); + "" . _RunSpanGamut($2) . "\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 . "\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 . "\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); + } + + "
  • " . $item . "
  • \n"; + }egmx; + + $g_list_level--; + return $list_str; +} + + + +sub _DoCodeBlocks { +# +# Process Markdown `
    ` 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
    " . $codeblock . "\n
    \n\n"; + + $result; + }egmx; + + return $text; +} + + +sub _DoCodeSpans { +# +# * Backtick quotes are used for 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: +# +#

    Just type foo `bar` baz at the prompt.

    +# +# 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 `bar` ... +# + + my $text = shift; + + $text =~ s@ + (`+) # $1 = Opening run of ` + (.+?) # $2 = The code block + (?$c
    "; + @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/&/&/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/\$/$/g; + } + } + + + # Do the angle bracket song and dance: + s! < !<!gx; + s! > !>!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; + + # must go first: + $text =~ s{ (\*\*|__) (?=\S) (.+?[*_]*) (?<=\S) \1 } + {$2}gsx; + + $text =~ s{ (\*|_) (?=\S) (.+?) (?<=\S) \1 } + {$2}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
     content, so we need to fix that:
    +			$bq =~ s{
    +					(\s*
    .+?
    ) + }{ + my $pre = $1; + $pre =~ s/^ //mg; + $pre; + }egsx; + + "
    \n$bq\n
    \n\n"; + }egmx; + + + return $text; +} + + +sub _FormParagraphs { +# +# Params: +# $text - string to process with html

    tags +# + my $text = shift; + + # Strip leading and trailing lines: + $text =~ s/\A\n+//; + $text =~ s/\n+\z//; + + my @grafs = split(/\n{2,}/, $text); + + # + # Wrap

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

    /; + $_ .= "

    "; + } + } + + # + # 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+);)/&/g; + + # Encode naked <'s + $text =~ s{<(?![a-z/?\$!])}{<}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]+)>}{
    $1}gi; + + # Email addresses: + $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.: +# +# foo +# @example.com +# +# Based on a filter by Matthew Wickline, posted to the BBEdit-Talk +# mailing list: +# + + 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{$addr}; + $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 , 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. +# +# + + 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: ) | # 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: +# +# + 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 + + +=head1 SYNOPSIS + +B [ B<--html4tags> ] [ B<--version> ] [ B<-shortversion> ] + [ I ... ] + + +=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
    and 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.: + +
    + +instead of Markdown's default XHTML style tags, e.g.: + +
    + + +=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 + +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 diff --git a/bin/contrib/md2html.awk b/bin/contrib/md2html.awk new file mode 100755 index 0000000..81d1241 --- /dev/null +++ b/bin/contrib/md2html.awk @@ -0,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("&", "\\&", t); + gsub("<", "\\<", 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 = "—"; + return t1 tag nextil(t2); + } + # Inline Code + if(tag == "`"){ + if(sub(/^`/, "", t2)){ + if(!match(t2, /``/)) + return t1 "”" nextil(t2); + ilcode2 = !ilcode2; + } + else if(ilcode2) + return t1 tag nextil(t2); + tag = ""; + if(ilcode){ + t1 = eschtml(t1); + tag = ""; + } + 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 "" linktext "" 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 "<" 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 "&" 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 "\""" 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 "\""" 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 "" nextil(linktext) "" 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 "" nextil(linktext) "" 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 ""); + 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(""); + for(; !text && block[nl] == "blockquote"; nl--) + oprint(""); + 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 ">)|((^
    $)")) + html = 1; + if(html && match($0, /^
    $/ || +(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(""); + 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("
  • "); +} +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(""); + 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("
  • "); + oprint(""); + } +} +nnl > nl { + for(; nl < nnl; nl++){ + block[nl + 1] = nblock[nl + 1]; + oprint("<" block[nl + 1] ">"); + if(match(block[nl + 1], /[ou]l/)) + oprint("
  • "); + } +} +hr { + oprint("
    "); + next; +} + +# Code blocks +code && /^$/ { + if(blanK) + oprint(""); + blank = 1; + next; +} +!text && sub(/^( | )/, "") { + if(blanK) + oprint(""); + blank = 0; + if(!code) + oprint("
    ");
    +	code = 1;
    +	$0 = eschtml($0);
    +	oprint($0);
    +	next;
    +}
    +code {
    +	oprint("
    "); + 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(""); + code = 0; + } + printp(par); + for(; nl > 0; nl--){ + if(match(block[nl], /[ou]l/)) + oprint("
  • "); + oprint(""); + } + gsub(/<<[^\"]*/, "", otext); + print(otext); +} diff --git a/bin/contrib/rc-httpd/handlers/authorize b/bin/contrib/rc-httpd/handlers/authorize new file mode 100755 index 0000000..ea4db3e --- /dev/null +++ b/bin/contrib/rc-httpd/handlers/authorize @@ -0,0 +1,6 @@ +#!/bin/rc +if(~ $REMOTE_USER ''){ + extra_headers=($extra_headers 'WWW-Authenticate: Basic realm="'$"SERVER_NAME'"') + error 401 + exit +} diff --git a/bin/contrib/rc-httpd/handlers/cgi b/bin/contrib/rc-httpd/handlers/cgi new file mode 100755 index 0000000..2c9a9b9 --- /dev/null +++ b/bin/contrib/rc-httpd/handlers/cgi @@ -0,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 +} diff --git a/bin/contrib/rc-httpd/handlers/dir-index b/bin/contrib/rc-httpd/handlers/dir-index new file mode 100755 index 0000000..00ff8ce --- /dev/null +++ b/bin/contrib/rc-httpd/handlers/dir-index @@ -0,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 ' + +'^$title^' + + +' +echo '

    '^$title^'

    ' +if(! ~ $PATH_INFO /) + echo 'Parent directory' +echo '
    ' +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 "" + print "" + print "" + print "" + print "" + $1="" ; $2="" ; $3="" ; $4="" ; $5="" ; $6="" ; $7="" ; $8="" ; $9="" + sub("^ *?", "") + print "" + print "" + $0="" +} +/^d/ { + print "" + print "" + print "" + print "" + print "" + $1="" ; $2="" ; $3="" ; $4="" ; $5="" ; $6="" ; $7="" ; $8="" ; $9="" + sub("^ *?", "") + print "" + print "" +}' +echo '
    "hrsize($6)""$7""$8""$9""$0"
    "$7""$8""$9""$0"/
    + + +' diff --git a/bin/contrib/rc-httpd/handlers/error b/bin/contrib/rc-httpd/handlers/error new file mode 100755 index 0000000..282d870 --- /dev/null +++ b/bin/contrib/rc-httpd/handlers/error @@ -0,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 ' + +'^$1^' + + +

    '^$1^'

    ' + echo $2 + echo '

    rc-httpd at' $SERVER_NAME '' + echo ' + + + ' +} + +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 diff --git a/bin/contrib/rc-httpd/handlers/redirect b/bin/contrib/rc-httpd/handlers/redirect new file mode 100755 index 0000000..e223091 --- /dev/null +++ b/bin/contrib/rc-httpd/handlers/redirect @@ -0,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 '' +if(~ $#3 0) + echo 'Browser did not accept redirect.' +if not + echo $3 +echo 'Click here' +echo '' diff --git a/bin/contrib/rc-httpd/handlers/serve-static b/bin/contrib/rc-httpd/handlers/serve-static new file mode 100755 index 0000000..00cc70a --- /dev/null +++ b/bin/contrib/rc-httpd/handlers/serve-static @@ -0,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 diff --git a/bin/contrib/rc-httpd/handlers/static-or-cgi b/bin/contrib/rc-httpd/handlers/static-or-cgi new file mode 100755 index 0000000..4d8a2d4 --- /dev/null +++ b/bin/contrib/rc-httpd/handlers/static-or-cgi @@ -0,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 diff --git a/bin/contrib/rc-httpd/handlers/static-or-index b/bin/contrib/rc-httpd/handlers/static-or-index new file mode 100755 index 0000000..f0904f8 --- /dev/null +++ b/bin/contrib/rc-httpd/handlers/static-or-index @@ -0,0 +1,5 @@ +#!/bin/rc +if(~ $PATH_INFO */) + exec dir-index $params +if not + exec serve-static diff --git a/bin/contrib/rc-httpd/lib/urldecode.awk b/bin/contrib/rc-httpd/lib/urldecode.awk new file mode 100755 index 0000000..1dadd00 --- /dev/null +++ b/bin/contrib/rc-httpd/lib/urldecode.awk @@ -0,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 +} diff --git a/bin/contrib/rc-httpd/rc-httpd b/bin/contrib/rc-httpd/rc-httpd new file mode 100755 index 0000000..8e4fad9 --- /dev/null +++ b/bin/contrib/rc-httpd/rc-httpd @@ -0,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 diff --git a/bin/contrib/rc-httpd/select-handler b/bin/contrib/rc-httpd/select-handler new file mode 100755 index 0000000..ec819d4 --- /dev/null +++ b/bin/contrib/rc-httpd/select-handler @@ -0,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 diff --git a/bin/contrib/tcp80 b/bin/contrib/tcp80 new file mode 100755 index 0000000..ae111a0 --- /dev/null +++ b/bin/contrib/tcp80 @@ -0,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 diff --git a/bin/contrib/urldecode.awk b/bin/contrib/urldecode.awk new file mode 100755 index 0000000..bd791e3 --- /dev/null +++ b/bin/contrib/urldecode.awk @@ -0,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 +} diff --git a/bin/contrib/urlencode.awk b/bin/contrib/urlencode.awk new file mode 100755 index 0000000..d4d354d --- /dev/null +++ b/bin/contrib/urlencode.awk @@ -0,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 "" + } +' "$@" + diff --git a/bin/contrib/webserver.rc b/bin/contrib/webserver.rc new file mode 100755 index 0000000..8044565 --- /dev/null +++ b/bin/contrib/webserver.rc @@ -0,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 diff --git a/bin/corehandlers.rc b/bin/corehandlers.rc new file mode 100755 index 0000000..294b1a7 --- /dev/null +++ b/bin/corehandlers.rc @@ -0,0 +1,152 @@ +# Werc builtin handlers + +fn nav_tree { + if(! ~ $#sideBarNavTitle 0) + echo '

    '$"sideBarNavTitle':

    ' + # 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 "
      " } + { + d = "" + if(match($0, "/$")) + d = "/" + sub("/$", "") # Strip trailing / for dirs so NF is consistent + + p(NF, lNF, "
      • ") + p(lNF, NF, "
    • ") + 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 "
    • » " bname "
    • " + else + print "
    • › " bname "
    • " + } + END { p(lNF, 2, "
    "); print "" }' +} + +fn link_bar { + if(~ $1 -t) { + echo '

    '$2'

    ' + shift; shift + } + echo '
      ' + while(! ~ $#* 0) { + echo '
    • - '$1'
    • ' + shift; shift + } + echo '
    ' +} + +fn md_handler { $formatter $1 } + +fn tpl_handler { template $* } + +fn html_handler { + # body states: 0 = no found, 2 = after , 1 = after , -1 = after + awk 'gsub(".*<[Bb][Oo][Dd][Yy][^>]*>", "") > 0 {body=2} + gsub("]*>.*", "") > 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 '
    '
    +    sed 's//\>/g' < $1 | fmt -l 82 -j
    +    echo '
    ' +} + +fn dir_listing_handler { + d=`{basename -d $1} + if(~ $#d 0) + d='/' + echo $d|sed 's,.*//,,g; s,/$,,; s,/, / ,g; s,.*,

    &

      ,' + # 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,.*/([^/]+/?)$,
    • \1
    • ,' + echo '
    ' +} + +fn notices_handler { + for(type in notify_errors notify_notes notify_success) + for(n in $$type) + echo '
    '$"n'
    ' +} + +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-) } + + diff --git a/bin/fltr_cache.rc b/bin/fltr_cache.rc new file mode 100755 index 0000000..9394724 --- /dev/null +++ b/bin/fltr_cache.rc @@ -0,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 & +} diff --git a/bin/template.awk b/bin/template.awk new file mode 100755 index 0000000..8f02ebb --- /dev/null +++ b/bin/template.awk @@ -0,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") +} diff --git a/bin/werc.rc b/bin/werc.rc new file mode 100755 index 0000000..0d006a3 --- /dev/null +++ b/bin/werc.rc @@ -0,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/ +# +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='' +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 diff --git a/bin/werc_errlog_wrap.rc b/bin/werc_errlog_wrap.rc new file mode 100755 index 0000000..94bd18f --- /dev/null +++ b/bin/werc_errlog_wrap.rc @@ -0,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 diff --git a/bin/wercconf.rc b/bin/wercconf.rc new file mode 100755 index 0000000..bb3422d --- /dev/null +++ b/bin/wercconf.rc @@ -0,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 ) +} diff --git a/bin/werclib.rc b/bin/werclib.rc new file mode 100755 index 0000000..bcebf91 --- /dev/null +++ b/bin/werclib.rc @@ -0,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 { +# +#} diff --git a/etc/initrc b/etc/initrc new file mode 100755 index 0000000..f0b26c2 --- /dev/null +++ b/etc/initrc @@ -0,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' diff --git a/etc/users/GROUP_AND_USER_ACCOUNTS b/etc/users/GROUP_AND_USER_ACCOUNTS new file mode 100644 index 0000000..f563306 --- /dev/null +++ b/etc/users/GROUP_AND_USER_ACCOUNTS @@ -0,0 +1 @@ +This is just a dummy file to force hg to preserve this directory that is used to store user and group account information. diff --git a/lib/404.tpl b/lib/404.tpl new file mode 100644 index 0000000..f839439 --- /dev/null +++ b/lib/404.tpl @@ -0,0 +1,3 @@ +

    The requested document at '%($base_url$"req_path%)' doesn't exist

    +

    Or take a look at the sitemap.

    +
    diff --git a/lib/default_master.tpl b/lib/default_master.tpl new file mode 100644 index 0000000..6742c49 --- /dev/null +++ b/lib/default_master.tpl @@ -0,0 +1,26 @@ +
    + +

    %($"siteTitle%) %($"siteSubTitle%)

    +
    + +% if(! ~ $#handlers_bar_left 0) { + +% } + +
    +% run_handlers $handlers_body_head +% run_handler $handler_body_main +% run_handlers $handlers_body_foot +
    + +
    +% cat `{ get_lib_file footer.inc } +
    diff --git a/lib/footer.inc b/lib/footer.inc new file mode 100644 index 0000000..1eac7d8 --- /dev/null +++ b/lib/footer.inc @@ -0,0 +1,7 @@ + + + diff --git a/lib/headers.tpl b/lib/headers.tpl new file mode 100644 index 0000000..635f85e --- /dev/null +++ b/lib/headers.tpl @@ -0,0 +1,29 @@ + + + + + %($pageTitle%) + + + +% if(test -f $sitedir/_werc/pub/style.css) +% echo ' ' + + +% # Legacy charset declaration for backards compatibility with non-html5 browsers. + + +% if(! ~ $#meta_description 0) +% echo ' ' +% if(! ~ $#meta_keywords 0) +% echo ' ' + +% h = `{get_lib_file headers.inc} +% if(! ~ $#h 0) +% cat $h + + %($"extraHeaders%) + + + + diff --git a/lib/top_bar.inc b/lib/top_bar.inc new file mode 100644 index 0000000..cbb89b8 --- /dev/null +++ b/lib/top_bar.inc @@ -0,0 +1,15 @@ + + + + diff --git a/pub/default_favicon.ico b/pub/default_favicon.ico new file mode 100755 index 0000000..817f5fa Binary files /dev/null and b/pub/default_favicon.ico differ diff --git a/pub/style/imgs/sgl.png b/pub/style/imgs/sgl.png new file mode 100755 index 0000000..d68580e Binary files /dev/null and b/pub/style/imgs/sgl.png differ diff --git a/pub/style/sinorca-screen-alt.css b/pub/style/sinorca-screen-alt.css new file mode 100755 index 0000000..d11e9ad --- /dev/null +++ b/pub/style/sinorca-screen-alt.css @@ -0,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 diff --git a/pub/style/style.cat-v.css b/pub/style/style.cat-v.css new file mode 100644 index 0000000..3d3d4b6 --- /dev/null +++ b/pub/style/style.cat-v.css @@ -0,0 +1,50 @@ +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; } + +/* cut here to leave vanity behind */ + +body { margin:0; padding: 0; font-size: 84%; font-family: Helvetica, Verdana, Arial, 'Liberation Sans', FreeSans, sans-serif; } +a { text-decoration: none; color: } +a:hover { text-decoration: underline; } +.thisPage { color: black; } + +/* header and top bar */ +header nav { background-color: rgb(100,135,220); color: white; padding: 0.3em; border-bottom: 2px solid black; font-size: 91%; } +header h1 { background-color: #ff6d06; color: black; margin: 0; border-bottom: 2px solid black; font-weight: normal; padding: 0.25ex; font-size: 233%; } +header a:hover { text-decoration: none; } + +/* sidebar */ +body > nav { border-right: 1px solid #ddd; padding: 0; } +body > nav > div { border-bottom: 1px solid #ddd; } +body > nav > div a { color: rgb(0, 102, 204); display: block; text-transform: capitalize; font-weight: bold; padding: 0.25em 1ex 0.25em 2mm; font-size: 102%} +body > nav > div a:hover { color: white; background-color: rgb(100,135,220); border-left: black solid 0.2em; text-decoration: none; } +body > nav > div p { font-weight: bold; margin: 0 0 0.5em 2mm; padding: 1em 0 0 0; } + +/* main copy */ +article { padding: 0.5ex 0 5vh 1vw; } +article h1, article h2 { color: rgb(0,102,204); font-weight: bold; margin: 2em 0 0 0; border-bottom: 2px solid rgb(0,102,204); } +article h3, article h4, article h5 { color: rgb(0,102,204); font-weight: bold; margin: 2em 0 0 0; } +article h6, article h7, article h8 { color: rgb(0,102,204); font-weight: bold; margin: 2em 0 0 0; } +article a { color: rgb(0,102,204); } +article a:hover { color: rgb(100,135,220); } +article pre { font-size: 1.2em; } + +/* footer */ +footer { color: white; background-color: rgb(100,135,220); } +footer a { color: inherit; } +footer div { padding: 1em; } + +/* tables */ +table { border: 1px solid rgba(128,128,128,0.5); padding: 0; } +th { color: white; background-color: rgb(100,135,220); } +tr:nth-child(odd) { background-color: rgba(128,128,128,0.1) } + diff --git a/pub/style/style.css b/pub/style/style.css new file mode 100644 index 0000000..91f060e --- /dev/null +++ b/pub/style/style.css @@ -0,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; } diff --git a/pub/style/style.werc140.css b/pub/style/style.werc140.css new file mode 100755 index 0000000..e3261e9 --- /dev/null +++ b/pub/style/style.werc140.css @@ -0,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; +} diff --git a/pub/style/style_old.css b/pub/style/style_old.css new file mode 100755 index 0000000..e4a41fe --- /dev/null +++ b/pub/style/style_old.css @@ -0,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; +} + + diff --git a/sites/pmikkelsen.com/_files/djv.tar b/sites/pmikkelsen.com/_files/djv.tar new file mode 100644 index 0000000..5d60262 Binary files /dev/null and b/sites/pmikkelsen.com/_files/djv.tar differ diff --git a/sites/pmikkelsen.com/_files/djvmono.tar b/sites/pmikkelsen.com/_files/djvmono.tar new file mode 100644 index 0000000..1fb0c7d Binary files /dev/null and b/sites/pmikkelsen.com/_files/djvmono.tar differ diff --git a/sites/pmikkelsen.com/_werc/config b/sites/pmikkelsen.com/_werc/config new file mode 100644 index 0000000..85a49d9 --- /dev/null +++ b/sites/pmikkelsen.com/_werc/config @@ -0,0 +1,3 @@ +masterSite=pmikkelsen.com +siteTitle='Peter's website' +siteSubTitle='random notes' diff --git a/sites/pmikkelsen.com/_werc/lib/footer.inc b/sites/pmikkelsen.com/_werc/lib/footer.inc new file mode 100644 index 0000000..4cabbb1 --- /dev/null +++ b/sites/pmikkelsen.com/_werc/lib/footer.inc @@ -0,0 +1 @@ +Powered by werc © Peter Mikkelsen 2019-2020 \ No newline at end of file diff --git a/sites/pmikkelsen.com/_werc/lib/top_bar.inc b/sites/pmikkelsen.com/_werc/lib/top_bar.inc new file mode 100644 index 0000000..976c8b0 --- /dev/null +++ b/sites/pmikkelsen.com/_werc/lib/top_bar.inc @@ -0,0 +1,10 @@ + + +
    +
    + diff --git a/sites/pmikkelsen.com/_werc/pub/style.css b/sites/pmikkelsen.com/_werc/pub/style.css new file mode 100644 index 0000000..10935fb --- /dev/null +++ b/sites/pmikkelsen.com/_werc/pub/style.css @@ -0,0 +1,67 @@ +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; } + +/* cut here to leave vanity behind */ + +body { margin:0; padding: 0; font-size: 84%; font-family: Helvetica, Verdana, Arial, 'Liberation Sans', FreeSans, sans-serif; } +a { text-decoration: none; color: } +a:hover { text-decoration: underline; } +.thisPage { color: black; } + +/* header and top bar */ +header nav { background-color: rgb(100,135,220); color: white; padding: 0.3em; border-bottom: 2px solid black; font-size: 91%; } +header h1 { background-color: #ff6d06; color: black; margin: 0; border-bottom: 2px solid black; font-weight: normal; padding: 0.25ex; font-size: 233%; } +header a:hover { text-decoration: none; } + +/* sidebar */ +body > nav { border-right: 1px solid #ddd; padding: 0; } +body > nav > div { border-bottom: 1px solid #ddd; } +body > nav > div a { color: rgb(0, 102, 204); display: block; text-transform: capitalize; font-weight: bold; padding: 0.25em 1ex 0.25em 2mm; font-size: 102%} +body > nav > div a:hover { color: white; background-color: rgb(100,135,220); border-left: black solid 0.2em; text-decoration: none; } +body > nav > div p { font-weight: bold; margin: 0 0 0.5em 2mm; padding: 1em 0 0 0; } + +/* main copy */ +article { padding: 0.5ex 0 5vh 1vw; } +article h1, article h2 { color: rgb(0,102,204); font-weight: bold; margin: 2em 0 0 0; border-bottom: 2px solid rgb(0,102,204); } +article h3, article h4, article h5 { color: rgb(0,102,204); font-weight: bold; margin: 2em 0 0 0; } +article h6, article h7, article h8 { color: rgb(0,102,204); font-weight: bold; margin: 2em 0 0 0; } +article a { color: rgb(0,102,204); } +article a:hover { color: rgb(100,135,220); } +article pre { font-size: 1.2em; } + +/* footer */ +footer { color: white; background-color: rgb(100,135,220); } +footer a { color: inherit; } +footer div { padding: 1em; } + +/* tables */ +table { border: 1px solid rgba(128,128,128,0.5); padding: 0; } +th { color: white; background-color: rgb(100,135,220); } +tr:nth-child(odd) { background-color: rgba(128,128,128,0.1) } + +/* Modifications for pmikkelsen.com */ +img { + max-width: 100%; + border: 1px solid black; +} + +body { + background-color: #ffffea; +} + +header h1 { + background-color: #eaffea; +} + +html { + font-size: 1.2em; +} \ No newline at end of file diff --git a/sites/pmikkelsen.com/contact.md b/sites/pmikkelsen.com/contact.md new file mode 100644 index 0000000..c4dd043 --- /dev/null +++ b/sites/pmikkelsen.com/contact.md @@ -0,0 +1,3 @@ +You can reach me on email via **petermikkelsen10@gmail.com** + +On github, gitlab and most other places, my name is **pmikkelsen**. \ No newline at end of file diff --git a/sites/pmikkelsen.com/haskell/quine.md b/sites/pmikkelsen.com/haskell/quine.md new file mode 100644 index 0000000..09a3dcd --- /dev/null +++ b/sites/pmikkelsen.com/haskell/quine.md @@ -0,0 +1,94 @@ +Today I am going to go through the process of writing a small program +which prints it's own source when executed. These kinds of programs are +also called quines, and I suggest that you try it out on your own if you +haven't already done so, as it is a very fun little problem! So if you +don't want any spoilers, please leave this page now and come back later. + + +## First attempt + +Okay, so first of let's just write a small hello world program to get +things going. All our code will be in a file called `quine.hs`. The code +for hello world looks like this + + + main = putStrLn "Hello world!" + + +When this is run using runhaskell quine.hs, it produces the output + + + Hello world! + + +Well, not quite a quine yet but we are getting something to the screen +at least ;) + +## Second attempt + +Now we get the idea that we copy the entire program's source into a +string and print that string instead. To do this, the code could look +something like this + + + main = putStrLn code + where code = "main = putStrLn code\n where code = ???" + + +Here we run into a problem, since we can't include the entire code into +the string. Copying everything we can into the string just increases +the size of the string itself, which means that there is now even more +to copy! We start by only copying all the code upto the string and see +how it looks when executed. The code is now + + + main = putStrLn code + where code = "main = putStrLn code\n where code = " + + +And it outputs + + main = putStrLn code + where code = + +Quite good! Now if we could only include the string itself in its printed form, with the quotes and all, and not the interpreted form with `\n` shown as newlines. + +## Third attempt + +After looking through the haskell standard libraries for a bit, we find +a function that looks promising for what we want to do. This function is +`print`, which prints the output using the `show` function instead of +interpreting the string. We would want to both print it as before, and +to print it using `print`, so we change our main to be `main = putStrLn +code >> print code`, and update our string to include the print. The +code then becomes + + main = putStrLn code >> print code + where code = "main = putStrLn code >> print code\n where code = " + +And it outputs + + + main = putStrLn code >> print code + where code = + "main = putStrLn code >> print code\n where code = " + + +So close! The only problem is that the `putStrLn` append a newline after +the output, which we don't want in this case. + +## Final attempt + +The simple fix is just to use `putStrLn`'s little brother `putStr` +which doesn't print that newline. The final program is then + + + main = putStr code >> print code + where code = "main = putStr code >> print code\n where code = " + +And luckily it outputs exactly itself + + + main = putStr code >> print code + where code = "main = putStr code >> print code\n where code = " + diff --git a/sites/pmikkelsen.com/images/2048.gif b/sites/pmikkelsen.com/images/2048.gif new file mode 100644 index 0000000..19dd98f Binary files /dev/null and b/sites/pmikkelsen.com/images/2048.gif differ diff --git a/sites/pmikkelsen.com/images/acme-in-action.png b/sites/pmikkelsen.com/images/acme-in-action.png new file mode 100644 index 0000000..b74d9dc Binary files /dev/null and b/sites/pmikkelsen.com/images/acme-in-action.png differ diff --git a/sites/pmikkelsen.com/images/cdude.png b/sites/pmikkelsen.com/images/cdude.png new file mode 100644 index 0000000..66ee18d Binary files /dev/null and b/sites/pmikkelsen.com/images/cdude.png differ diff --git a/sites/pmikkelsen.com/images/cursed.png b/sites/pmikkelsen.com/images/cursed.png new file mode 100644 index 0000000..f92e06e Binary files /dev/null and b/sites/pmikkelsen.com/images/cursed.png differ diff --git a/sites/pmikkelsen.com/images/discordgif.gif b/sites/pmikkelsen.com/images/discordgif.gif new file mode 100644 index 0000000..207af41 Binary files /dev/null and b/sites/pmikkelsen.com/images/discordgif.gif differ diff --git a/sites/pmikkelsen.com/images/djvfonts.png b/sites/pmikkelsen.com/images/djvfonts.png new file mode 100644 index 0000000..86d58a4 Binary files /dev/null and b/sites/pmikkelsen.com/images/djvfonts.png differ diff --git a/sites/pmikkelsen.com/images/linuxreverse.gif b/sites/pmikkelsen.com/images/linuxreverse.gif new file mode 100644 index 0000000..6684116 Binary files /dev/null and b/sites/pmikkelsen.com/images/linuxreverse.gif differ diff --git a/sites/pmikkelsen.com/images/mordor.gif b/sites/pmikkelsen.com/images/mordor.gif new file mode 100644 index 0000000..fe13c09 Binary files /dev/null and b/sites/pmikkelsen.com/images/mordor.gif differ diff --git a/sites/pmikkelsen.com/index.md b/sites/pmikkelsen.com/index.md new file mode 100644 index 0000000..2713ad4 --- /dev/null +++ b/sites/pmikkelsen.com/index.md @@ -0,0 +1,28 @@ + +## This website + +My intention with this website it to share some ideas and thoughts on +programming and other computer related subjects. The things I care +about and find interesting at the moment are written below, and are +updated sometimes. Hopefully, most of the content will be about one of +those things. + +- Functional programming (Haskell, Erlang, Scheme) +- Concurrent programming (Erlang, Go) +- Plan 9 (both the 9front fork and plan9port) + +For information about how the website it setup, see +[this](/web/the-setup-of-this-site) post. + +## Me + +My name is Peter Mikkelsen and I am a computer science student at Aalborg University, Denmark. + +The following list contains other facts and links + +- *Age*: 22 +- *Favourite programming language*: See [this](/opinions/favourite-programming-languages) post +- *Current job*: None :) +- *Stuff I use*: See [this](/me/stuff-i-use) post. +- *Tabs vs spaces*: Tabs. +- *Emacs* vs *Vi* [None of them](/me/how-i-started-using-acme) diff --git a/sites/pmikkelsen.com/linux/eduroam.md b/sites/pmikkelsen.com/linux/eduroam.md new file mode 100644 index 0000000..1cdc830 --- /dev/null +++ b/sites/pmikkelsen.com/linux/eduroam.md @@ -0,0 +1,15 @@ +# Connecting to the eduroam network on AAU + +Goto net.aau.dk and create a new device. + +Connect to the eduroam network and set the correct wifi-security settings: + + security: WPA & WPA2 Enterprise + Authentication: PEAP + CA Cert: no CA certificate is required + PEAP Version: Automatic + Inner authentication: MSCHAPv2 + +And fill out the username and password from the website. + +No need for the stupid script. diff --git a/sites/pmikkelsen.com/linux/wifi.md b/sites/pmikkelsen.com/linux/wifi.md new file mode 100644 index 0000000..be3b1cc --- /dev/null +++ b/sites/pmikkelsen.com/linux/wifi.md @@ -0,0 +1,12 @@ +# Connecting to wifi on linux + +What I have to do: + + wpa_passphrase NameOfNetwork NetworkKey >> /etc/wpa_supplicant/wpa_supplicant-wlp4s0.conf + +The network card name might be different. + +Then reboot aaaand done :) + +This is on void linux, so it might not be the same for you. + diff --git a/sites/pmikkelsen.com/me/how-i-started-using-acme.md b/sites/pmikkelsen.com/me/how-i-started-using-acme.md new file mode 100644 index 0000000..9e37dd7 --- /dev/null +++ b/sites/pmikkelsen.com/me/how-i-started-using-acme.md @@ -0,0 +1,117 @@ +## The beginning + +My very first code editor was visual studio 2010 for windows 7. This was +when I didn't know what programming was, and a teacher I had suggested +that we used that, so we could start learning some C#. It was very fun and +all, but soon after, I decided that I wanted to learn another language, +and the whole idea of using an IDE which is specialized for a specific +programming language or environment just seemed very odd to me. Surely +there had to be better options, where I as a user could decide by myself +what languages I wanted support for. I tried out multiple different +small editors but didn't really like any of them. + +After some time, I decided that I wanted to try linux, and I installed +fedora or linux mint (don't remember at this point), and I liked it very +much. Comming from windows where it is normal just to go to a random +website and download the tools I needed, it was a great joy to be able +to just type a command in the terminal and watch my program get installed. + +## First try: vim + +After reading online for some time, it seemed that the editor which +all the cool linux users used was vim, so I decided to learn that. The +experience was very different from what I was used to, but I liked the +keyboard shortcuts and that it looked very cool. Of course I installed +every plugin that I had read about online and it all became a mess, +but I stuck with it for around 3-4 years. The things I liked the most +was that I felt it was very fast to do what I wanted, but unfortunately +the flow with multiple files was never something I could get used to, +and all the plugins made me sick. + +## Second try: emacs + +Just to give another very popular editor a try, I installed emacs sometime +2-3 years ago. Comming from vim, the keyboard shortcuts in emacs sucks, +but the editor itself is just so much more powerful, because plugins and +customisations could be written in a real programming language. I started, +as many other emacs users do, to use emacs for everything i could. I read +my mails, watched PDFs, managed my system, and even used it as my window +manager for a period. Around the same time that I started using emacs, +I also started using OpenBSD as a secondary operating system. I noticed +that many very good tools are available in unix, but emacs almost has +everything implemented again in elisp, which seemed stupid to me. But hey, +it works so I stuck with it. Until late 2019. + +## Now acme + +One day in school when one of my group mates asked if I could look at +something for him, I saw that he was using visual studio code, which +made it natural to use a mouse to click around in the file. Of course +this was also possible in emacs, but since I came from vim, it was never +something that I did. He argued that it was much faster to just click in +the file where he wanted to edit, than to navigate using the keyboard, and +I thought that maybe he was right, so I started digging. After some time +looking though the internet for some editors which made much use of the +mouse, I found acme and decided to install it. And wow it was different. + +[![A picture of acme in action][1]][1] + +Acme uses the mouse for everything, since there are very few keyboard +shortcuts (not even copy and paste). As shown on the picture, there are no +menus and no icons; everything is just text, and the different buttons on +the mouse can interpret that text in different ways. For example, middle +clicking on the text `New` wil create a new text frame, and right clicking +on the text `example` will search the file for the word. Actually, it is +more advanced than that since the right click will first send the selected +text to something called the plumber, which might do something fun like +opening the file which has that name. I suggest interrested readers to +read the page [here](http://acme.cat-v.org/) and some of the documents +linked from that site to learn more about acme. The video introduction +from Russ Cox [here](https://www.youtube.com/watch?v=dP1xVpMPn8M) is +also great. + + +## What I like about acme + +* **The colours**: they cannot be changed without modifying the source, +but luckily for me, I love those pale bright colours. In fact, there +are the very reason this website has the colours that it has. + +* **Writing commands**: since acme allows users to control it via the 9P +protocol (if you don't understand, read the links above), it is possible +to write "plugins" or commands in whatever language I want. After +just creating a program with the needed functionality, it is possible +to write the name somewhere and middle click on it, just like it was +always there. Not may editors makes it possible to use the environment +as much as acme does, which was also one of my wonders about emacs back +then. It allows me to turn it into *my* IDE using whatever tools I see fit, +instead of depending on support directly in the editor. + +* **Everything as text**: this means that if I run a gdb session via +the `win` command, it is totally possible for me to just scroll up in the +history and delete lines which are not important, and to write notes +as I debug. Also, since commands are just text which is clicked, it is +possible to have a document with commands which are useful in a given +project, which I can then open and click. + +* **Lack of customisation**: while this might seem strange, comming from +a vim and emacs background, it surely is wonderful to have an editor +which does *not* encourage the user to customise the hell out of it. I +have spent way too much time doing this in the past and it had to stop. + +* **Lack of syntax highlighting and auto completion**: some people love +it and can't live without it, but I very much prefer to learn the syntax +of the language based on the contents, not based on some colours an +editor throws at me. Also I feel like I learn and remember much better +when there is no auto completion. + +## Other editors I use sometimes + +Sometimes I have to do very small editing tasks, and sometimes as root, +in which case it is just simpler to open the file in vi. Note that this +is the small vi, and not the "improved" vim. I also sometimes edit my +text in sam with is another editor written by Rob Pike, and I quite like +that one too for smaller projects. You can read more about sam +[here](http://sam.cat-v.org/). + +[1]: /images/acme-in-action.png \ No newline at end of file diff --git a/sites/pmikkelsen.com/me/stuff-i-use.md b/sites/pmikkelsen.com/me/stuff-i-use.md new file mode 100644 index 0000000..08aa7e7 --- /dev/null +++ b/sites/pmikkelsen.com/me/stuff-i-use.md @@ -0,0 +1,46 @@ +* Note: Last updated on 2020/05/28 + +# Operating system + +I mostly use [guix][1] but sometimes I need software that is not yet +ported, and then I use [fedora][2]. If I could, I would use [9front][3] +alot more, since the system is much better and different in my opinion. I +also like [OpenBSD][4] alot. + +# Text editor + +Acme from plan9port. Sometimes I use emacs if I have to edit scheme code, since automatic closing of matching `(` and `)` makes life much easier. + +# Shell + +I use the [rc shell][5] since it works nicely in both acme and 9term from plan9port. Also I like its syntax alot more than the syntax of bash. + +# Web browser + +Mainy firefox (this page is only tested on firefox, so please let me +know if you have problems on other browsers). + +# Laptop + +I use a Lenovo Thinkpad E495 with an AMD ryzen 3700U and 16 gigabytes +of ram. It is a fairly good computer for the price, and OpenBSD is +supported out of the box with the exception of wifi. For this reason, I +have small usb wifi dongle that is constantly plugged in, which performs a +lot worse than what some people would like, but for me it is no big deal. + + +# Mouse and keyboard + +Since I have never been a fan of touchpads on laptops, I use an external +mouse which is a Logitech MX Master 3. I also have a keyboard that I +sometimes plug in if I have to do more writing than what I can comfortably +do on my laptop. The keyboard is from the coolermaster masterkeys lite L +bundle. Clicking in acme using the scroll wheel instead of using a true +3-button mouse is sometimes a bit annoying, but its OK. + + +[1]: https://guix.gnu.org/ +[2]: https://getfedora.org/ +[3]: http://9front.org/ +[4]: https://openbsd.org/ +[5]: https://plan9.io/sys/doc/rc.html \ No newline at end of file diff --git a/sites/pmikkelsen.com/opinions/favourite-programming-languages.md b/sites/pmikkelsen.com/opinions/favourite-programming-languages.md new file mode 100644 index 0000000..f80131c --- /dev/null +++ b/sites/pmikkelsen.com/opinions/favourite-programming-languages.md @@ -0,0 +1 @@ +To be written \ No newline at end of file diff --git a/sites/pmikkelsen.com/plan9/basic_9p_server.md b/sites/pmikkelsen.com/plan9/basic_9p_server.md new file mode 100644 index 0000000..7c1c514 --- /dev/null +++ b/sites/pmikkelsen.com/plan9/basic_9p_server.md @@ -0,0 +1,58 @@ +## Writing a basic 9P server + +On plan 9 the entire system is build around the idea of namespaces +and that _"everything is a file"_. For this reason it is very easy to write +a new 9P fileserver in C since all the boring tasks are implemented in +libraries. This note describes a minimal program which serves a folder to +`/mnt/hello9p` containing a single synthetic file with the contents "Hello from 9P!". + +## The code + + #include + #include + #include + #include + #include <9p.h> + + void + fsread(Req *r) + { + readstr(r, "Hello from 9P!\n"); + respond(r, nil); + } + + Srv fs = { + .read = fsread, + }; + + void + main(void) + { + Tree *tree; + + tree = alloctree(nil, nil, DMDIR|0555, nil); + fs.tree = tree; + createfile(tree->root, "hello", nil, 0555, nil); + + postmountsrv(&fs, nil, "/mnt/hello9p", MREPL | MCREATE); + } + +## Explanation + +The global variable `fs` is a structure which contains function pointers +to all the 9P handlers, but since I only plan on reading from the file, +only the `read` field is set. The fsread function calls two helper functions +from the 9p(2) library which will create a response with the given string as +the file contents. + +In `main` I start by allocating a new file tree, since this 9P server deals with +a fileserver that has a tree structure, and therefore I don't have to worry +about how directories are handled for example. A file is added with `createfile` +to the root of the tree. + +The call to `postmountsrv` will mount the 9P server under `/mnt/hello9p`. + +## Thats it + +This is not very complicated, but see the manpages at 9p(2) and 9pfile(2) +and intro(5) for more information about the libraries and 9P itself. diff --git a/sites/pmikkelsen.com/plan9/discord.md b/sites/pmikkelsen.com/plan9/discord.md new file mode 100644 index 0000000..8a9815f --- /dev/null +++ b/sites/pmikkelsen.com/plan9/discord.md @@ -0,0 +1,83 @@ +# Chatting on discord from 9front + +Sometimes you work with people who doesn't want to use the same communication +tools as you do. A sad example is when your team members wants to use discord, +and not something simpler like IRC. Oh well, gotta deal with it. + +This is the reason I wrote a "discord bot" (ugh) which can run on your 9front +system and allow you to chat on discord anyways. + +## Example gif + +The example below just opens two chat windows with different channels in each. +While it works both in acme and in the terminal, I like having everything +together inside acme. + +[![An animated gif showing discord chats from 9front][1]][1] + +## The code + +The project is made of a few scripts and a go program. The only thing that +actually communicates with discord is the go program, and the scripts just +makes it easier to send and recieve on the correct channels. The code is +available [here](https://git.sr.ht/~pmikkelsen/discordfront), and there is +a precompiled version of the go program included (compiled for 9front amd64). + +## Usage + +To use the service, you must first create a bot on discord and get the +access token. I personally found this step much more confusing then the +actual coding itself, which either tells you something about my google +skills, or about discord.. Oh, and then you must invite the bot to your +discord server.. + +Next, the server part of the bot must be started: + + `discordsrv YOURTOKEN` + +This will post a pipe in /srv/discordfront, where messages can be send +and recieved from, but you should not read from it directly. The +`discordsrv` script does this for you as well, and it takes each line +and dumps it in a nice readable format into +`$home/lib/discord/logs/$serverName/$channelName` +where the server name is the name of a discord server (or guild?), and +the channel name is, well, the name of the channel. + +Now that the server is started, you can either run `discordacme` which +opens an acme with the file `$home/lib/discord/channels` open. This file +is not handled by any of the scripts, so it is just a handy text file to +keep shortcuts to various channels (see the gif for an example). +Inside `discordacme`, the `openChat` command will open a new chat window, +using the simpler `discord` script. + +The `discord` script simply runs `tail -f` on the given channel's logfile +while it reads messages from standard input and sends them to +/srv/discordfront at the same time. + +## Using remotely + +Since this program does not fetch previous messages, it might be a good +idea to run the server part `discordsrv` on a machine that is always online, +so that all messages gets logged. Since this is plan9, it is almost trivial +to still use the client on your local machine, just by using `rimport` to +import the relevant file trees from the server. + +Here is what I do (in a seperate script which is run at startup): + + #!/bin/rc + + rfork + rimport -ac -p $serverhost /srv + rimport -c -p $serverhost /usr/glenda/lib/discord + discordacme + +I don't even notice it is not running locally. + +## Notes + +* Yes, the names of the scripts suck. +* Yes, there is a lot of missing functionality. +* No, this has not been tested very much, it just seems to do the job +well enough for now. + +[1]: /images/discordgif.gif diff --git a/sites/pmikkelsen.com/plan9/dns.md b/sites/pmikkelsen.com/plan9/dns.md new file mode 100644 index 0000000..04bd7b5 --- /dev/null +++ b/sites/pmikkelsen.com/plan9/dns.md @@ -0,0 +1,41 @@ +# Using 9front as an authoritative DNS server + +This note describes the steps I took to make the 9front server +at pmikkelsen.com the authoritative dns server for itself. + +First, I logged into my domain name registrar and changed the dns servers for +my domain to `ns1.pmikkelsen.com` and `ns2.pmikkelsen.com` (It would not let me have +just one, but I choose to live dangerously and point both of those domains at +the same server). + +To let the world know _where_ `ns1` and `ns2` can be found, +I added their ip on the registrar's website (since my own dns server cannot serve +them for good reasons). Anyways, this might not be the same for you since you +may not use the same provider. + +## Starting the dns server in "serve mode" + +First I added 1 line to the `/cfg/$sysname/cpurc` file to enable the dns server. + + ndb/dns -s + +After that I added a few lines in `/lib/ndb/local` to setup all the dns records I needed: + + dom=pmikkelsen.com soa= + ip=80.240.16.196 + mx=vps1.pmikkelsen.com pref=1 + txtrr="v=spf1 a mx ip:80.240.16.196 ~all" + + dom=p9auth.pmikkelsen.com soa= + ip=80.240.16.196 + + dom=vps1.pmikkelsen.com soa= + ip=80.240.16.196 + + dom=_dmarc.pmikkelsen.com soa= + txtrr="v=DMARC1; p=none" + +With this done, I now have A records, mx records and txt records in place, so my website, +mail and rcpu works as expected. + +Bye. \ No newline at end of file diff --git a/sites/pmikkelsen.com/plan9/fonts.md b/sites/pmikkelsen.com/plan9/fonts.md new file mode 100644 index 0000000..6cd6780 --- /dev/null +++ b/sites/pmikkelsen.com/plan9/fonts.md @@ -0,0 +1,29 @@ +# Fonts on 9front + +By default 9front uses a font called VGA but its too small for my eyes. + +The fonts I use (dejavu) look like this[![an image showing the fonts][1]][1] and are attached below: + +[proportional font](/_files/djv.tar) + +[monospace font](/_files/djvmono.tar) + +They can be recreated by having them installed on linux and doing the following from +linux with plan9port installed. + + fontsrv -m /tmp/fonts + cp -r /tmp/fonts/DejaVuSomeThing/14a/ /tmp/djv + +You can try a different font or font size if you want. + +Then connect to 9front via drawterm and run: + + mkdir /lib/font/bit/djv + dircp /mnt/term/tmp/djv /lib/font/bit/djv + +Finally change your `lib/profile` to use `/lib/font/bit/djv/font` :) + +Note that 9front includes truetypefs which allows you to use `.ttf` files +directly, but I find the results are better looking this way. + +[1]: /images/djvfonts.png \ No newline at end of file diff --git a/sites/pmikkelsen.com/plan9/lets_encrypt.md b/sites/pmikkelsen.com/plan9/lets_encrypt.md new file mode 100644 index 0000000..f0b1c07 --- /dev/null +++ b/sites/pmikkelsen.com/plan9/lets_encrypt.md @@ -0,0 +1,59 @@ +## How I get tls certificates for 9front + +First of all, I use linux and drawterm for this for now, but +I would like to be able to do it all from 9front at some point. + +## Generate the certificate + +Install certbot on linux and run the following command + + certbot certonly --manual -d pmikkelsen.com -d vps1.pmikkelsen.com + +and do the challenges, they should be easy. + +## Importing the cert and private key + +Start drawterm and login as the hostowner. After this, the filesystem of the linux +system is available at `/mnt/term`. Run the following: + + cd /sys/lib/tls/ + cp /mnt/term/etc/letsencrypt/live/pmikkelsen.com/privkey.pem ./ + cp /mnt/term/etc/letsencrypt/live/pmikkelsen.com/fullchain.pem ./cert + +Now the private key must be converted to one that can be loaded into factotum + + auth/pemdecode 'PRIVATE KEY' privkey.pem | auth/asn12rsa -t 'service=tls role=client' > key + rm privkey.pem + chmod 400 key + +Add the following to `/cfg/$sysname/cpurc` to load the private key on boot. + + cat /sys/lib/tls/key >> /mnt/factotum/ctl + +Done. + +## SMTP over TLS + +I have the following in `/bin/service.auth/tcp25` + + #!/bin/rc + + user=`{cat /dev/user} + exec upas/smtpd -c /sys/lib/tls/cert -n $3 + +Notice I had to put it in the `/bin/service.auth` folder so that it could find the private key. + +## Https with rc-httpd + +I have the following in `/bin/service.auth/tcp443` + + #!/bin/rc + + exec tlssrv -c /sys/lib/tls/cert -l /sys/log/https /bin/service/tcp80 $* + +Again, in the `/bin/service.auth` folder. It simply wraps the plain http service +in a tls wrapper which looks like this for me + + #!/bin/rc + PLAN9=/ + exec /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www diff --git a/sites/pmikkelsen.com/plan9/mounting-9p-over-drawterm.md b/sites/pmikkelsen.com/plan9/mounting-9p-over-drawterm.md new file mode 100644 index 0000000..414b9f2 --- /dev/null +++ b/sites/pmikkelsen.com/plan9/mounting-9p-over-drawterm.md @@ -0,0 +1,58 @@ +# Mounting a 9P connection over drawterm + +I sometimes use drawterm on linux to connect to my 9front server. +While it is possible to access the host system's files under `/mnt/term`, there is no builtin way to access the remote system's file under linux. +Now, why would anybody want to do this? +In my case, I often want to write some code under 9front, but for languages which aren't supported such as prolog in this case, so there are three options as I see it: + +* Store the files on the host machine, and access them under `/mnt/term`. +* Store the files on the server and somehow mount the server's filesystem on the host. +* Store the files on a third machine that both the host and server can access. + +Option number two seems best for me, so I asked around and it seems like the best tool to mount a 9P connection on linux is [9pfs](https://github.com/bunny351/9pfs). + +## How it works + +Before I could mount the connection, I had to serve it somehow. Normally I already serve 9P directly from my server's filesystem, but that requires some +authentication that 9pfs does not support, so I had to serve it without authentication. +Serving directly to the internet without authentication is of course pretty dumb since everyone can then access my files, so thanks to a hint from hiro, I figured +out that it is actually possible to use the host's network stack on the server by binding `/mnt/term/net` over `/net`. + +I'll just show the final script below and explain it afterwards: + + #!/bin/rc + + rfork n + bind /mnt/term/net /net + aux/listen1 -t tcp!*!12335 /bin/exportfs -r / & + os mkdir -p /tmp/drawterm + os 9pfs localhost -p 12335 /tmp/drawterm + +So the first thing that happens is that I bind the host's network in, and from that point on, every network connection in this namespace actually goes out +from the host instead of from the server! + +Then `exportfs` is started and it is serving the `/` directory over 9P at port 12335. +The `os` command runs a command on the host, so it just creates the folder that the system will be mounted to, and then uses `9pfs` to actually mount it. +The nice thing here is that `9pfs` just connects to localhost. + +## Using it + +As I said in the beginning, I did this to be able to edit files on the 9front server, and run compilers/interpreters on linux. +The `os` command goes a long way, but the following script makes it even easier (I have this installed as `linux`): + + #!/bin/rc + + dir=/tmp/drawterm/`{pwd} + os -d $dir $* + +This means I can just go into any directory on the server, type `linux ghci` and I get a haskell repl running in the correct directory, as seen in the gif below. + +[![An animated gif showing ghci loading a file on the 9front server][1]][1] + + +## Final notes + +The bind net trick still blows my mind a bit, since it is so trivial, yet so powerful. It is also fun to think about how one would do this +on other systems than plan9, which I can't even imagine. + +[1]: /images/linuxreverse.gif diff --git a/sites/pmikkelsen.com/plan9/network_booting.md b/sites/pmikkelsen.com/plan9/network_booting.md new file mode 100644 index 0000000..3bb51b9 --- /dev/null +++ b/sites/pmikkelsen.com/plan9/network_booting.md @@ -0,0 +1,30 @@ +## Network booting into the server at pmikkelsen.com + +Sometimes it is nice to be able to connect directly to a remote server +from my laptop and have the same root filesystem available. +This is possible by typing `tls` at the boot prompt and then +typing `pmikkelsen.com` for the fs and auth server. + +## Speed + +On my internet connection some things can be very slow +(such as compiling the entire system), because of fs access now happens +over the network. The easy fix is to just start a cpu connection to the server +where the fs access can happen fast since it is on the same machine. This is +done by typing + + rcpu -h pmikkelsen.com + +Of course the server has a pretty slow cpu.. + +## Server settings + +Some steps are needed to make this work: + +1. The user must be added to the fileserver and auth server. +2. The fileserver must be listening for remote connections. +3. The correct ndb entries must be set (in the beginning I forgot `fs` and I could +only connect via tcp, not tls) + +More information about all those steps can be found in section 7 of [the 9front FQA](http://fqa.9front.org/fqa7.html). + diff --git a/sites/pmikkelsen.com/plan9/trellofs.md b/sites/pmikkelsen.com/plan9/trellofs.md new file mode 100644 index 0000000..42e2cfd --- /dev/null +++ b/sites/pmikkelsen.com/plan9/trellofs.md @@ -0,0 +1,347 @@ +## A filesystem for viewing trello + +Since my last note [here](http://pmikkelsen.com/plan9/basic_9p_server) +was very simple, here comes +a small program (around 250 lines of C), which makes it +possible to view trello as a read-only filesystem. + +## Usage example + +First set the `trellokey` and `trellotoken` environment +variables to the API key and token from the trello website. + + term% trellokey=1298791723987937123..... + term% trellotoken=923748237497ab798798999999..... + +Then `webfs` must be started so the program can make +requests to the trello REST api. + + term% webfs + +Then `trellofs` itself must be started. It mounts the filesystem under `/mnt/trello`. + + term% trellofs + +If I now go and see which boards I have, I get the following + + term% cd /mnt/trello + term% ls + Beaglebone_Black + Fly + Fysik_Rapport + Guld_kobber + Ideer_til_2._produkt + Lektier + P0 + P1 + P2 + Projekt_Materialer + Spil + Welcome_Board + +The names of the folders are almost like the original board names, except +that that some characters have been replaced by `_`. Lets look at `P1` + + term% cd P1 + term% ls + Backlog + Done + Estimat + In-progress + Info + Master.C_Top_Down_Ting + Sprint_1 + Test + Test_sprint_1 + Todo + +Todo must have something worth looking at + + term% ls + Færdig_rapport_aflevering + Korrekturlæsning + +Those two items are the actual trello "cards", which I think of as tasks. The first +one means "Final report hand in", so let's have a look at that. + + term% cd Færdig_rapport_aflevering + term% ls + description + duedate + url + +There are always three files inside each card folder, but since the information in them +is not mandatory on trello, some of them will be empty. + + term% cat description + + term% cat duedate + 2019-12-18T13:00:00.000Z + term% cat url + https://trello.com/c/u23ZcNAm/9-f%C3%A6rdig-rapport-aflevering + +## Limitations + +The key and token are right now in environment variables, which is somewhat +awkward. Also I would like to be able to actually create and move cards directly +from the filesystem just by creating files, but that is not something I will implement +right now. + +## The code + +The code for the `trellofs` program is listed below. + + #include + #include + #include + #include + #include <9p.h> + #include + + #define MAX_RESPONSE_SIZE (1024 * 1024 * 8) + + char *key; + char *token; + + void + fsread(Req *r) + { + char *str; + + if (r->fid->file->aux == nil) { + readstr(r, ""); + } else { + str = malloc(strlen(r->fid->file->aux) + 2); + sprint(str, "%s\n", r->fid->file->aux); + readstr(r, str); + } + respond(r, nil); + } + + Srv fs = { + .read = fsread, + }; + + JSON * + trelloget(char *endpoint, char *params) + { + int ctlfd, bodyfd; + int n; + int err; + char *buf; + JSON *res; + + res = nil; + bodyfd = -1; + + buf = malloc(MAX_RESPONSE_SIZE + 1); + if(buf == nil) { + perror("malloc"); + return nil; + } + + ctlfd = open("/mnt/web/clone", ORDWR); + if(ctlfd < 0) { + perror("open"); + goto fail; + } + + if(read(ctlfd, buf, 32) < 0) { + perror("read"); + goto fail; + } + + n = atoi(buf); + + sprint(buf, "url https://api.trello.com/%s?key=%s&token=%s", endpoint, key, token); + if(params) + sprint(buf + strlen(buf), "\&%s\n", params); + else + sprint(buf + strlen(buf), "\n"); + + err = write(ctlfd, buf, strlen(buf)); + if(err < 0){ + perror("write"); + goto fail; + } + + sprint(buf, "/mnt/web/%d/body", n); + bodyfd = open(buf, OREAD); + if(bodyfd < 0){ + perror("open"); + goto fail; + } + + err = readn(bodyfd, buf, MAX_RESPONSE_SIZE); + if (err < 0) { + perror("read"); + goto fail; + } + + res = jsonparse(buf); + + fail: + close(ctlfd); + close(bodyfd); + free(buf); + + return res; + } + + char * + escapename(char *str) + { + char *new; + int i, len; + + len = strlen(str); + + new = malloc(len + 1); + for(i = 0; i <= len; i++){ + switch(str[i]){ + case '/': + case ' ': + case ',': + case ':': + case '"': + case '\'': + new[i] = '_'; + break; + default: + new[i] = str[i]; + } + } + return new; + } + + void + addcard(File *dir, JSON *card) + { + JSON *element; + char *filename; + File *carddir; + char *description; + char *url; + char *duedate; + + element = jsonbyname(card, "name"); + filename = escapename(element->s); + carddir = createfile(dir, filename, nil, DMDIR|0555, nil); + if(carddir == nil){ + perror("createfile"); + return; + } else { + element = jsonbyname(card, "desc"); + description = strdup(element->s); + element = jsonbyname(card, "url"); + url = strdup(element->s); + element = jsonbyname(card, "due"); + if (element->t == JSONString) + duedate = strdup(element->s); + else + duedate = nil; + createfile(carddir, "description", nil, 0444, description); + createfile(carddir, "url", nil, 0444, url); + createfile(carddir, "duedate", nil, 0444, duedate); + } + free(filename); + } + + void + addlist(File *dir, JSON *list) + { + JSON *element; + char *filename; + JSON *cards; + JSONEl *card; + char cardsEndpoint[128]; + File *listdir; + + element = jsonbyname(list, "name"); + + filename = escapename(element->s); + listdir = createfile(dir, filename, nil, DMDIR|0555, nil); + if(listdir == nil){ + perror("createfile"); + return; + } else { + element = jsonbyname(list, "id"); + sprint(cardsEndpoint, "1/lists/%s/cards", element->s); + cards = trelloget(cardsEndpoint, "fields=desc,name,url,due"); + if(cards == nil) + return; + + for(card = cards->first; card != nil; card = card->next){ + addcard(listdir, card->val); + } + } + jsonfree(cards); + free(filename); + } + + void + addboard(File *root, JSON *board) + { + JSON *element; + JSONEl *list; + char *filename; + File *boarddir; + + element = jsonbyname(board, "name"); + filename = escapename(element->s); + boarddir = createfile(root, filename, nil, DMDIR|0555, nil); + if(boarddir == nil) { + perror("createfile"); + return; + } + + element = jsonbyname(board, "lists"); + for(list = element->first; list != nil; list = list->next){ + addlist(boarddir, list->val); + } + + free(filename); + } + + void + trelloinit(File *root) + { + JSON *result; + JSONEl *board; + + result = trelloget("1/members/me/boards", "fields=name,lists&lists=open"); + + for(board = result->first; board != nil; board = board->next){ + addboard(root, board->val); + } + jsonfree(result); + } + + void + main(void) + { + JSONfmtinstall(); + + key = getenv("trellokey"); + token = getenv("trellotoken"); + + Tree *tree; + + tree = alloctree(nil, nil, DMDIR|0555, nil); + + fs.tree = tree; + trelloinit(tree->root); + + postmountsrv(&fs, nil, "/mnt/trello", MREPL | MCREATE); + } + +The program can be compiled by hand or by using the following `mkfile`. + + BIN=/usr/glenda/bin/amd64 + + TARG=trellofs + + OFILES=\ + main.$O\ + + + + + + + +%{ +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 '
    '
    +        evl | escape_html |[2] awk '{print ""$0""}' 
    +        echo '
    ' + } +%} +% } + +
    +% env | escape_html
    +

    + +% umask + diff --git a/tpl/_users/login.tpl b/tpl/_users/login.tpl new file mode 100644 index 0000000..5857188 --- /dev/null +++ b/tpl/_users/login.tpl @@ -0,0 +1,18 @@ +

    User login

    +
    +% if(check_user) { + You are logged in as: %($logged_user%) +% } +% if not { +% if (~ $REQUEST_METHOD POST) +% echo '
    Login failed!
    ' +
    +
    +
    +
    + +
    +
    +% } + +
    diff --git a/tpl/sitemap.tpl b/tpl/sitemap.tpl new file mode 100644 index 0000000..9505b6d --- /dev/null +++ b/tpl/sitemap.tpl @@ -0,0 +1,67 @@ +

    Site map

    + +%{ +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 '
      ' + + 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 '
    • '^$"n^'' $"desc '
    • ' + echo $base_url^$u >> $tmpfile + echo ''$base_url^$u''^`{get_mdate $i}^'' >> $tmpfilex + if(test -d $i) + @{ listDir $i } + } + echo '
    ' + } +} + + +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 ' +' + + cat $tmpfilex + rm $tmpfilex & + echo '' + # 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 + +%} -- cgit v1.2.3