chronicle-4.6/0000755000175000017500000000000011557771306011541 5ustar skxskxchronicle-4.6/tests/0000755000175000017500000000000011557771306012703 5ustar skxskxchronicle-4.6/tests/perl-syntax.t0000755000175000017500000000277311557771306015372 0ustar skxskx#!/usr/bin/perl -w # # Test that every perl file we have passes the syntax check. # # Steve # -- use strict; use File::Find; use Test::More qw( no_plan ); # # Find all the files beneath the current directory, # and call 'checkFile' with the name. # find( { wanted => \&checkFile, no_chdir => 1 }, '.' ); # # Check a file. # # If this is a perl file then call "perl -c $name", otherwise # return # sub checkFile { # The file. my $file = $File::Find::name; # We don't care about directories return if ( ! -f $file ); # Or makefiles return if ( $file =~ /Makefile/ ); # False positives return if ( ( $file =~ /modules.sh$/ ) || ( $file =~ /html-validator.t/ ) || ( $file =~ /blog/ ) || ( $file =~ /output/ ) ); # See if it is a perl file. my $isPerl = 0; # Read the file. open( INPUT, "<", $file ); foreach my $line ( ) { if ( $line =~ /\/usr\/bin\/perl/ ) { $isPerl = 1; } } close( INPUT ); # # Return if it wasn't a perl file. # return if ( ! $isPerl ); # # Now run 'perl -c $file' to see if we pass the syntax # check. We add a couple of parameters to make sure we're # really OK. # # use strict "vars"; # use strict "subs"; # my $retval = system( "perl -Mstrict=subs -Mstrict=vars -c $file 2>/dev/null >/dev/null" ); is( $retval, 0, "Perl file passes our syntax check: $file" ); } chronicle-4.6/tests/pod-check.t0000755000175000017500000000132011557771306014724 0ustar skxskx#!/usr/bin/perl -w # # Test that the POD we include in our scripts is valid, via the external # podcheck command. # # Steve # -- # use strict; use Test::More qw( no_plan ); foreach my $file ( qw! ./bin/chronicle ! ) { ok( -e $file, "$file" ); ok( -x $file, " File is executable: $file" ); ok( ! -d $file, " File is not a directory: $file" ); if ( ( -x $file ) && ( ! -d $file ) ) { # # Execute the command giving STDERR to STDOUT where we # can capture it. # my $cmd = "podchecker $file"; my $output = `$cmd 2>&1`; chomp( $output ); is( $output, "$file pod syntax OK.", " File has correct POD syntax: $file" ); } } chronicle-4.6/tests/pod.t0000644000175000017500000000045111557771306013652 0ustar skxskx#!/usr/bin/perl -w # # Test that the POD we use in our modules is valid. # use strict; use Test::More; eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; # # Run the test(s). # my @poddirs = qw( bin ); all_pod_files_ok( all_pod_files( @poddirs ) ); chronicle-4.6/tests/html-validator.t0000755000175000017500000000337311557771306016030 0ustar skxskx#!/usr/bin/perl -w # # Test that our output is validatored # # Steve # -- use File::Find; use HTML::Lint; use Test::More; # # Basically this test validates the HTML which would be produced if # a blog is compiled - if one is not present then we have nothing to # validate against. # if ( -d "./output/" ) { plan no_plan; } else { plan skip_all => 'There is no output directory to validate against!'; exit; } # # Find all the files beneath the current directory, # and call 'checkFile' with the name. # find( { wanted => \&checkFile, no_chdir => 1 }, './output/' ); # # Check a file. # # sub checkFile { # The file. my $file = $File::Find::name; # We don't care about directories return if ( ! -f $file ); # We only care about html files. return if ( $file !~ /\.html$/ ); my $lint = HTML::Lint->new; $lint->parse_file( $file ); my $error_count = $lint->errors; foreach my $error ( $lint->errors ) { if ( $error->as_string =~ // ) { $error_count -= 1; } if ( $error->as_string =~ /Invalid character/ ) { $error_count -= 1; } # print $error->as_string, "\n"; } is( $error_count, 0 , "There are no errors in $file" ); } # # Count and return the number of literal TAB characters contained # in the specified file. # sub countTabCharacters { my ( $file ) = (@_); my $count = 0; open( FILE, "<", $file ) or die "Cannot open $file - $!"; foreach my $line ( ) { # We will count multiple tab characters in a single line. while( $line =~ /(.*)\t(.*)/ ) { $count += 1; $line = $1 . $2; } } close( FILE ); return( $count ); } chronicle-4.6/tests/no-tabs.t0000755000175000017500000000371111557771306014440 0ustar skxskx#!/usr/bin/perl -w # # Test that none of our scripts contain any literal TAB characters. # # Steve # -- use strict; use File::Find; use Test::More qw( no_plan ); # # Find all the files beneath the current directory, # and call 'checkFile' with the name. # find( { wanted => \&checkFile, no_chdir => 1 }, '.' ); # # Check a file. # # sub checkFile { # The file. my $file = $File::Find::name; # We don't care about directories return if ( ! -f $file ); # Nor about backup files. return if ( $file =~ /~$/ ); # or Makefiles return if ( $file =~ /Makefile$/ ); # Nor about files which start with ./debian/ return if ( $file =~ /^\.\/debian\// ); return if ( $file =~ /^\.\/blog\// ); return if ( $file =~ /^\.\/output\// ); # See if it is a shell/perl file. my $isShell = 0; my $isPerl = 0; # Read the file. open( INPUT, "<", $file ); foreach my $line ( ) { if ( ( $line =~ /\/bin\/sh/ ) || ( $line =~ /\/bin\/bash/ ) ) { $isShell = 1; } if ( $line =~ /\/usr\/bin\/perl/ ) { $isPerl = 1; } } close( INPUT ); # # We don't care about files which are neither perl nor shell. # if ( $isShell || $isPerl ) { # # Count TAB characters # my $count = countTabCharacters( $file ); is( $count, 0, "Script has no tab characters: $file" ); } } # # Count and return the number of literal TAB characters contained # in the specified file. # sub countTabCharacters { my ( $file ) = (@_); my $count = 0; open( FILE, "<", $file ) or die "Cannot open $file - $!"; foreach my $line ( ) { # We will count multiple tab characters in a single line. while( $line =~ /(.*)\t(.*)/ ) { $count += 1; $line = $1 . $2; } } close( FILE ); return( $count ); } chronicle-4.6/tests/Makefile0000644000175000017500000000024511557771306014344 0ustar skxskx all: @cd ..; prove --shuffle tests/ verbose: @cd ..; prove --shuffle --verbose tests/ modules: .PHONY ./modules.sh > modules.t .PHONY: true clean: rm *~ chronicle-4.6/tests/modules.sh0000755000175000017500000000110411557771306014706 0ustar skxskx#!/bin/sh # # Automatically attempt to create a test which ensures all the modules # used in the code are availabe. # # Steve # -- # http://www.steve.org.uk/ # # cat < html then output. # # multimarkdown # -------- # The blog entries are converted from multimarkdown -> html then output. # # textile # ------- # The blog entries are converted from textile -> html then output. # # format = html # # # We can omit parts of the output if we wish # # no-tags = 1 # no-xrefs = 1 # no-archive = 1 # no-calendar = 1 no-comments = 1 # # # The maximum age of posts which are allowed to accept comments. # # comment-days = 10 # # The filename to use for tag links # # filename = index.html # # # Suffix to use for single entries. # # suffix = .html # # URL prefix, if any. # # url_prefix = http://example.com/ # # The title for the site. # # blog_title = Steve Kemp's Blog # blog_subtitle = Free Software & Debian # # A command to run pre-build. # # pre-build = cvs update -A -d # # A command to run post-build. # # post-build = scp -r output/* user@host:/some/path # # # Do we want static pages output in year/month directories? # # date-archive-path = 1 # # Do we want to send outgoing PINGS when we've rebuilt our blog? # # send-ping = 1 # # Do we want to send to a specific site, or just to the defaults? # # ping-server-1 = http://example.com/rpc2 # ping-server-2 = http://example.net/rpc # ping-server-N = http://..... # chronicle-4.6/themes/0000755000175000017500000000000011557771306013026 5ustar skxskxchronicle-4.6/themes/copyrighteous/0000755000175000017500000000000011557771306015732 5ustar skxskxchronicle-4.6/themes/copyrighteous/index.template0000644000175000017500000000705211557771306020602 0ustar skxskx copyrighteous: <!-- tmpl_var name='blog_title' -->

Permalink comments. | .,

Created by Chronicle v

Archives

Tags
chronicle-4.6/themes/copyrighteous/style.css0000644000175000017500000000200011557771306017574 0ustar skxskx#content { float:left; width:67%; background:#fff; margin-right:15px; margin-left:15px; padding-bottom:50px; vertical-align:top; } #rightmenu { border-style:dashed; border-width: 1px; background-color:#ccc; vertical-align:top; position:absolute; right:20px; width:172px; padding:10px; } #archivelist { padding: 0px; } .storybox { border: 1px dashed; padding:6px; margin-bottom:10px; } #title {border-bottom:2px solid #000;} body,td { font-family: Verdana,Geneva,Arial,Sans-serif; font-size: 12px; color: #111111; } a { text-decoration: none; } .title { font-size: 20pt; font-weight: bold; color: #666666; } .title a { text-decoration: none; color: #666666;} .rightMenu { font-size: 10pt; font-weight: bold; color: #336699; } .blosxomTitle {font-size: 13pt; font-weight: bold; color: #000000; } .blosxomTime { text-decoration: italicize; color: #336699; } img { margin: 2px; } pre { overflow: auto; background: rgb(230,230,230); border: solid; border-width: thin; padding: 5px 10px; } chronicle-4.6/themes/copyrighteous/vim.css0000644000175000017500000000077211557771306017245 0ustar skxskxbody { color: black; background: white } .synComment { color: #0000FF } .synConstant { color: #FF00FF } .synIdentifier { color: #008B8B } .synStatement { color: #A52A2A ; font-weight: bold } .synPreProc { color: #A020F0 } .synType { color: #2E8B57 ; font-weight: bold } .synSpecial { color: #6A5ACD } .synUnderlined { color: #000000 ; text-decoration: underline } .synError { color: #FFFFFF ; background: #FF0000 none } .synTodo { color: #0000FF ; background: #FFFF00 none } chronicle-4.6/themes/copyrighteous/entry.template0000644000175000017500000000642411557771306020636 0ustar skxskx copyrighteous: <!-- tmpl_var name='title' -->

comments. Permalink | .,
Archives

Tags
chronicle-4.6/themes/copyrighteous/comment-loop.inc0000644000175000017500000000056211557771306021041 0ustar skxskx

Comments On This Entry

chronicle-4.6/themes/copyrighteous/tags.template0000644000175000017500000000605411557771306020432 0ustar skxskx copyrighteous: Items tagged <!-- tmpl_var name='tagname' -->
Archives

Tags
chronicle-4.6/themes/copyrighteous/comment-form.inc0000644000175000017500000000135611557771306021035 0ustar skxskx

Add A Comment

Your Name
Your Email
Your Comment

Your submission will be ignored if any field is left blank. But your email address will not be displayed.

chronicle-4.6/themes/copyrighteous/month.template0000644000175000017500000000647711557771306020632 0ustar skxskx copyrighteous: Items from <!-- tmpl_var name='month_name' --> <!-- tmpl_var name='year' -->

comments. Permalink | .,
Archives

Tags
chronicle-4.6/themes/blocky/0000755000175000017500000000000011557771306014311 5ustar skxskxchronicle-4.6/themes/blocky/header.inc0000644000175000017500000000051411557771306016234 0ustar skxskx

Untitled Blog

chronicle-4.6/themes/blocky/index.template0000644000175000017500000000461111557771306017157 0ustar skxskx <!-- tmpl_var name='blog_title' -->
comments. Tags: ., No tags

RSS feed

chronicle-4.6/themes/blocky/style.css0000644000175000017500000000142511557771306016165 0ustar skxskxbody { padding-bottom: 1em; border-right: 1px solid rgb(128, 128, 128); background-color: white; color: black; margin: 0; } div.title { } div.title h1 { margin: 0px -10px; padding: 5px 10px 0px 10px; text-align: center; } div.title a { color: black !important; text-decoration: none !important; } div.title a:hover { color: black !important; text-decoration: none !important; } div.title h2 { margin: 0 0 1ex 0; padding: 0; text-align: right; } legend a { color: black !important; text-decoration: none !important; } legend a:hover { color: black !important; text-decoration: none !important; } legend { font-weight: bold; } pre { overflow: auto; background: rgb(230,230,230); border: solid; border-width: thin; padding: 5px 10px; } chronicle-4.6/themes/blocky/vim.css0000644000175000017500000000077211557771306015624 0ustar skxskxbody { color: black; background: white } .synComment { color: #0000FF } .synConstant { color: #FF00FF } .synIdentifier { color: #008B8B } .synStatement { color: #A52A2A ; font-weight: bold } .synPreProc { color: #A020F0 } .synType { color: #2E8B57 ; font-weight: bold } .synSpecial { color: #6A5ACD } .synUnderlined { color: #000000 ; text-decoration: underline } .synError { color: #FFFFFF ; background: #FF0000 none } .synTodo { color: #0000FF ; background: #FFFF00 none } chronicle-4.6/themes/blocky/entry.template0000644000175000017500000000352011557771306017207 0ustar skxskx Blog: <!-- tmpl_var name='title' -->
Tags: .,
chronicle-4.6/themes/blocky/comment-loop.inc0000644000175000017500000000056211557771306017420 0ustar skxskx

Comments On This Entry

chronicle-4.6/themes/blocky/xml.gif0000644000175000017500000000065511557771306015606 0ustar skxskxGIF89a$X]rূ^dɱcߞrsr3罡џAC4Ug#糑諂alw.n$|${3¡S!ѲD}3WP?Ȥf!,$ lH,ȣ%k:Ш4bNXو 2U,BXb&YUh( db9A1.bRqO" 11 .f+g1[N%g.2ft$ZQb1Mf gPN1#t.O)g.++*O2](N1& N2M..2N2 2eHE‹*\p ;chronicle-4.6/themes/blocky/footer.inc0000644000175000017500000000022411557771306016300 0ustar skxskx

Created by Chronicle v

chronicle-4.6/themes/blocky/sidebar.inc0000644000175000017500000000243711557771306016423 0ustar skxskx
Archive
Tags .,
chronicle-4.6/themes/blocky/tags.template0000644000175000017500000000474411557771306017015 0ustar skxskx Blog : Entries Tagged <!-- tmpl_var name='tagname' -->

Entries tagged "".

comments. Tags: .,

RSS feed

chronicle-4.6/themes/blocky/comment-form.inc0000644000175000017500000000135611557771306017414 0ustar skxskx

Add A Comment

Your Name
Your Email
Your Comment

Your submission will be ignored if any field is left blank. But your email address will not be displayed.

chronicle-4.6/themes/blocky/month.template0000644000175000017500000000523611557771306017201 0ustar skxskx Blog : Entries from <!-- tmpl_var name='month_name' --> <!-- tmpl_var name='year' -->

Entries from .

comments. Tags: ., No tags

RSS feed

chronicle-4.6/themes/xml/0000755000175000017500000000000011557771306013626 5ustar skxskxchronicle-4.6/themes/xml/comments.xml.template0000644000175000017500000000163011557771306020007 0ustar skxskx <!-- tmpl_var name='title' --> Comments on <!-- tmpl_var name="title" escape='html' --> chronicle-4.6/themes/xml/vim.css0000644000175000017500000000077211557771306015141 0ustar skxskxbody { color: black; background: white } .synComment { color: #0000FF } .synConstant { color: #FF00FF } .synIdentifier { color: #008B8B } .synStatement { color: #A52A2A ; font-weight: bold } .synPreProc { color: #A020F0 } .synType { color: #2E8B57 ; font-weight: bold } .synSpecial { color: #6A5ACD } .synUnderlined { color: #000000 ; text-decoration: underline } .synError { color: #FFFFFF ; background: #FF0000 none } .synTodo { color: #0000FF ; background: #FFFF00 none } chronicle-4.6/themes/xml/month.xml.template0000644000175000017500000000160511557771306017311 0ustar skxskx <!-- tmpl_var name='blog_title' --> - Entries from <!-- tmpl_var name='month_name' --> <!-- tmpl_var name='year' --> Entries from <!-- tmpl_var name="title" escape='html' --> chronicle-4.6/themes/xml/tags.xml.template0000644000175000017500000000154411557771306017124 0ustar skxskx <!-- tmpl_var name='blog_title' escape='html' --> - Entries tagged <!-- tmpl_var name='tagname' escape='html' --> Entries tagged <!-- tmpl_var name="title" escape='html' --> chronicle-4.6/themes/xml/index.xml.template0000644000175000017500000000166411557771306017300 0ustar skxskx <!-- tmpl_var name='blog_title' escape='html' --> <!-- tmpl_var name="title" escape='html' --> chronicle-4.6/themes/default/0000755000175000017500000000000011557771306014452 5ustar skxskxchronicle-4.6/themes/default/header.inc0000644000175000017500000000052311557771306016375 0ustar skxskx

Untitled Blog

chronicle-4.6/themes/default/index.template0000644000175000017500000000457311557771306017327 0ustar skxskx <!-- tmpl_var name='blog_title' -->
comments. Tags: ., No tags
Referenced by: ,

RSS feed

chronicle-4.6/themes/default/style.css0000644000175000017500000000402111557771306016321 0ustar skxskx body { padding: 0 10px; padding-top: 1px; padding-bottom: 1em; background-color: white; color: black; margin: 0; margin-right: 185px; border-right: 1px solid rgb(128, 128, 128); } div.title { } div.title h1 { margin: 0px -10px; padding: 5px 10px 0px 10px; text-align: center; } div.title a { color: black !important; text-decoration: none !important; } div.title a:hover { color: black !important; text-decoration: none !important; } div.title h2 { margin: 0 0 1ex 0; padding: 0; text-align: right; } /* * Special markup for weblog entries. */ div.entry { border-left: 1px solid rgb(128, 128, 128); border-right: 1px solid rgb(128, 128, 128); border-top: 1px solid rgb(128, 128, 128); border-bottom: 1px solid rgb(128, 128, 128); margin: 10px 0px; } div.padding { padding-top: 15px; padding-bottom: 15px; } div.entry div.body { padding: 10px 10px; } div.entry .title { background-color: #eee; border-bottom: 1px solid rgb(128, 128, 128); font-weight: bold; font-size: 120%; padding: 0.26ex 10px; } div.entry div.date { text-align: right; } div.entry div.title a { color: black !important; text-decoration: none !important; } div.entry div.title a:hover { color: black !important; text-decoration: none !important; } div.entry div.tags { border-top: 1px solid rgb(128, 128, 128); font-style: italic; font-family: Verdana, Georgia, Arial, sans-serif; font-size: 90%; text-align: right; } div.entry div.tags span.comments { padding-left: 5px; float: left; } div.entry div.xrefs { font-style: italic; font-family: Verdana, Georgia, Arial, sans-serif; font-size: 90%; text-align: right; } div#sidebar { position: absolute; top: 0px; right: 0px; width: 165px; font-family: sans-serif; font-size: 80%; text-align: justify; padding: 0 10px; background-color: white; border-left: 1px solid rgb(128, 128, 128); margin: 0; } pre { overflow: auto; background: rgb(230,230,230); border: solid; border-width: thin; padding: 5px 10px; } chronicle-4.6/themes/default/vim.css0000644000175000017500000000077211557771306015765 0ustar skxskxbody { color: black; background: white } .synComment { color: #0000FF } .synConstant { color: #FF00FF } .synIdentifier { color: #008B8B } .synStatement { color: #A52A2A ; font-weight: bold } .synPreProc { color: #A020F0 } .synType { color: #2E8B57 ; font-weight: bold } .synSpecial { color: #6A5ACD } .synUnderlined { color: #000000 ; text-decoration: underline } .synError { color: #FFFFFF ; background: #FF0000 none } .synTodo { color: #0000FF ; background: #FFFF00 none } chronicle-4.6/themes/default/entry.template0000644000175000017500000000370511557771306017355 0ustar skxskx <!-- tmpl_var name='blog_title' -->: <!-- tmpl_var name='title' -->
Tags: .,
Referenced by: ,
chronicle-4.6/themes/default/comment-loop.inc0000644000175000017500000000056211557771306017561 0ustar skxskx

Comments On This Entry

chronicle-4.6/themes/default/xml.gif0000644000175000017500000000065511557771306015747 0ustar skxskxGIF89a$X]rূ^dɱcߞrsr3罡џAC4Ug#糑諂alw.n$|${3¡S!ѲD}3WP?Ȥf!,$ lH,ȣ%k:Ш4bNXو 2U,BXb&YUh( db9A1.bRqO" 11 .f+g1[N%g.2ft$ZQb1Mf gPN1#t.O)g.++*O2](N1& N2M..2N2 2eHE‹*\p ;chronicle-4.6/themes/default/footer.inc0000644000175000017500000000022011557771306016435 0ustar skxskx

Created by Chronicle v

chronicle-4.6/themes/default/sidebar.inc0000644000175000017500000000155611557771306016565 0ustar skxskx

Archive

Tags

chronicle-4.6/themes/default/tags.template0000644000175000017500000000477511557771306017162 0ustar skxskx <!-- tmpl_var name='blog_title' -->: Entries Tagged <!-- tmpl_var name='tagname' -->

Entries tagged "".

comments. Tags: .,
Referenced by: ,

RSS Feed

chronicle-4.6/themes/default/comment-form.inc0000644000175000017500000000135611557771306017555 0ustar skxskx

Add A Comment

Your Name
Your EMail
Your Comment

Your submission will be ignored if any field is left blank. But your email address will not be displayed.

chronicle-4.6/themes/default/month.template0000644000175000017500000000520611557771306017337 0ustar skxskx <!-- tmpl_var name='blog_title' -->: entries from <!-- tmpl_var name='month_name' --> <!-- tmpl_var name='year' -->

Entries from .

comments. Tags: ., No tags
Referenced by: ,

RSS Feed

chronicle-4.6/themes/leftbar/0000755000175000017500000000000011557771306014445 5ustar skxskxchronicle-4.6/themes/leftbar/index.template0000644000175000017500000000662211557771306017317 0ustar skxskx <!-- tmpl_var name='blog_title' -->

Untitled Blog

comments. Tags: ., No tags

RSS feed

Created by Chronicle v

chronicle-4.6/themes/leftbar/style.css0000644000175000017500000000363311557771306016324 0ustar skxskx body { padding: 0 10px; padding-top: 1px; padding-bottom: 1em; background-color: white; color: black; margin: 0; margin-left: 185px; border-left: 1px solid rgb(128, 128, 128); } div.title { } div.title h1 { margin: 0px -10px; padding: 5px 10px 0px 10px; text-align: center; } div.title a { color: black !important; text-decoration: none !important; } div.title a:hover { color: black !important; text-decoration: none !important; } div.title h2 { margin: 0 0 1ex 0; padding: 0; text-align: right; } /* * Special markup for weblog entries. */ div.entry { border-left: 1px solid rgb(128, 128, 128); border-right: 1px solid rgb(128, 128, 128); border-top: 1px solid rgb(128, 128, 128); border-bottom: 1px solid rgb(128, 128, 128); margin: 10px 0px; } div.padding { padding-top: 15px; padding-bottom: 15px; } div.entry div.body { padding: 10px 10px; } div.entry .title { background-color: #eee; border-bottom: 1px solid rgb(128, 128, 128); font-weight: bold; font-size: 120%; padding: 0.26ex 10px; } div.entry div.date { text-align: right; } div.entry div.title a { color: black !important; text-decoration: none !important; } div.entry div.title a:hover { color: black !important; text-decoration: none !important; } div.entry div.tags { border-top: 1px solid rgb(128, 128, 128); font-style: italic; font-family: Verdana, Georgia, Arial, sans-serif; font-size: 90%; text-align: right; } div.entry div.tags span.comments { padding-left: 5px; float: left; } div#sidebar { position: absolute; top: 0px; left: 0px; width: 165px; float:left; font-family: sans-serif; font-size: 80%; text-align: justify; padding: 0 10px; background-color: white; border-right: 1px solid rgb(128, 128, 128); margin: 0; } pre { overflow: auto; background: rgb(230,230,230); border: solid; border-width: thin; padding: 5px 10px; } chronicle-4.6/themes/leftbar/vim.css0000644000175000017500000000077211557771306015760 0ustar skxskxbody { color: black; background: white } .synComment { color: #0000FF } .synConstant { color: #FF00FF } .synIdentifier { color: #008B8B } .synStatement { color: #A52A2A ; font-weight: bold } .synPreProc { color: #A020F0 } .synType { color: #2E8B57 ; font-weight: bold } .synSpecial { color: #6A5ACD } .synUnderlined { color: #000000 ; text-decoration: underline } .synError { color: #FFFFFF ; background: #FF0000 none } .synTodo { color: #0000FF ; background: #FFFF00 none } chronicle-4.6/themes/leftbar/entry.template0000644000175000017500000000531211557771306017344 0ustar skxskx <!-- tmpl_var name='blog_title' -->: <!-- tmpl_var name='title' -->

Untitled Blog

Tags: .,
chronicle-4.6/themes/leftbar/comment-loop.inc0000644000175000017500000000056211557771306017554 0ustar skxskx

Comments On This Entry

chronicle-4.6/themes/leftbar/xml.gif0000644000175000017500000000065511557771306015742 0ustar skxskxGIF89a$X]rূ^dɱcߞrsr3罡џAC4Ug#糑諂alw.n$|${3¡S!ѲD}3WP?Ȥf!,$ lH,ȣ%k:Ш4bNXو 2U,BXb&YUh( db9A1.bRqO" 11 .f+g1[N%g.2ft$ZQb1Mf gPN1#t.O)g.++*O2](N1& N2M..2N2 2eHE‹*\p ;chronicle-4.6/themes/leftbar/tags.template0000644000175000017500000000637011557771306017146 0ustar skxskx <!-- tmpl_var name='blog_title' -->: Entries Tagged <!-- tmpl_var name='tagname' -->

Untitled Blog

Entries tagged "".

comments. Tags: .,

RSS Feed

chronicle-4.6/themes/leftbar/comment-form.inc0000644000175000017500000000135611557771306017550 0ustar skxskx

Add A Comment

Your Name
Your EMail
Your Comment

Your submission will be ignored if any field is left blank. But your email address will not be displayed.

chronicle-4.6/themes/leftbar/month.template0000644000175000017500000000660111557771306017332 0ustar skxskx <!-- tmpl_var name='blog_title' -->: entries from <!-- tmpl_var name='month_name' --> <!-- tmpl_var name='year' -->

Untitled Blog

Entries from .

comments. Tags: ., No tags

RSS Feed

chronicle-4.6/themes/simple/0000755000175000017500000000000011557771306014317 5ustar skxskxchronicle-4.6/themes/simple/header.inc0000644000175000017500000000053211557771306016242 0ustar skxskx

Untitled Blog

chronicle-4.6/themes/simple/index.template0000644000175000017500000000515511557771306017171 0ustar skxskx <!-- tmpl_var name='blog_title' -->

chronicle-4.6/themes/simple/ajax.js0000644000175000017500000000600711557771306015603 0ustar skxskx// -*-mode: C++; style: K&R; c-basic-offset: 4 ; -*- */ // // Simple collection of Javascript for Ajax form submission. // // // Get an XMLHTTPRequest object. // function getXMLHTTPRequest() { req = false; if(window.XMLHttpRequest) { try { req = new XMLHttpRequest(); } catch(e) { req = false; } } else if(window.ActiveXObject) { try { req = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { try { req = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) { req = false; } } } return( req ); } // // Submit the comment. // function submitComment() { showProgress(); var xhr = getXMLHTTPRequest(); if(! xhr ) { hideProgress(); return; } xhr.onreadystatechange = function() { if(xhr.readyState == 4) { if(xhr.status == 200) { var o = document.getElementById( "output" ); o.innerHTML = xhr.responseText; } else { var o = document.getElementById( "output" ); o.innerHTML = "Failed HTTP code " + xhr.status + " " + xhr.responseText; } hideProgress(); } }; data = 'ajax=1'; data = data + '&id=' + escape(document.forms[0].id.value ); data = data + '&captcha=' + escape( document.forms[0].captcha.value ); data = data + '&id=' + escape(document.forms[0].id.value ); data = data + '&captcha=' + escape( document.forms[0].captcha.value ); data = data + '&name=' + escape( document.forms[0].name.value ); data = data + '&mail=' + escape( document.forms[0].mail.value ); data = data + '&body=' + escape( document.forms[0].body.value ); // // Make the request // xhr.open("POST", "/cgi-bin/comments.cgi", true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(data); } // // Show our progress marker. // function showProgress() { var i = document.getElementById( "progress" ); if ( i ) { i.style.display = 'block'; } } // // Hide our progress marker. // function hideProgress() { var i = document.getElementById( "progress" ); if ( i ) { i.style.display = 'none'; } } function showCloud() { var i = document.getElementById( "tags" ); if ( i ) { i.style.display = 'block'; } i = document.getElementById( "tags_toggle" ); if ( i ) { i.style.display = 'none'; } } function hideCloud() { var i = document.getElementById( "categories" ); if ( i ) { i.style.display = 'none'; } } function toggle_visibility(id) { var e = document.getElementById(id); if(e.style.display == 'block') e.style.display = 'none'; else e.style.display = 'block'; } chronicle-4.6/themes/simple/style.css0000644000175000017500000000013111557771306016164 0ustar skxskx@import url("reset.css"); @import url("screen-detail.css"); @import url("comments.css"); chronicle-4.6/themes/simple/vim.css0000644000175000017500000000077211557771306015632 0ustar skxskxbody { color: black; background: white } .synComment { color: #0000FF } .synConstant { color: #FF00FF } .synIdentifier { color: #008B8B } .synStatement { color: #A52A2A ; font-weight: bold } .synPreProc { color: #A020F0 } .synType { color: #2E8B57 ; font-weight: bold } .synSpecial { color: #6A5ACD } .synUnderlined { color: #000000 ; text-decoration: underline } .synError { color: #FFFFFF ; background: #FF0000 none } .synTodo { color: #0000FF ; background: #FFFF00 none } chronicle-4.6/themes/simple/entry.template0000644000175000017500000000527211557771306017223 0ustar skxskx <!-- tmpl_var name='blog_title' -->: <!-- tmpl_var name='title' -->

chronicle-4.6/themes/simple/screen-detail.css0000644000175000017500000000734411557771306017560 0ustar skxskx/* @override http://recursive.ca/hutch/wp-content/themes/hutch/screen-detail.css */ html { width: 100%; border-top: 3px solid black; border-bottom: 3px solid black; } body { width: 100%; font-family: Optima, Verdana, Helvetica, Arial, sans-serif; color: black; background-color: white; } a,a:link,a:visited,a:hover { color: #000; outline: none; text-decoration: underline; } a:hover{ background-color: #fff4a2; border: 1px solid #ffe944; } .access { display: none; } #blog-title { font-size: 4em; margin: 0; padding: 0; padding-top: 10px; padding-bottom: 20px; } #blog-title a, #blog-title a:hover, #blog-title a:visited, #blog-title a:link { text-decoration: none; } #blog-description{ width:100%; font-size: 2em; padding-left: 1em; padding-bottom: 1em; border-bottom: 1px solid black; } #menu{ padding: 0; margin: 0; width: 70%; padding-right: 1em; padding-left: 1.5em; border-right: 1px solid black; } div.hfeed { width: 70%; float: left; padding-right: 1em; padding-left: 1.5em; border-right: 1px solid black; } /* this is the box around the entry */ div.hentry{ height: 0px; clear: both } .entry-title { padding: 0; margin: 0; padding-top: 1em; font-weight: bold; font-size: large; } .entry-title a, .entry-title a:hover, .entry-title a:visited, .entry-title a:link { text-decoration: none; } .entry-date { font-style: italic; margin: 0; padding: 0; padding-left: 5em; padding-top: 0.25em; font-size: x-small; } .entry-content { padding-left: 2em; padding-top: 1em; padding-bottom: 2em; } .entry-content strong, .entry-content b { font-weight: bold; } .entry-content em, .entry-content i { font-style: italic; } .entry-content ul { list-style-type: disc; padding-left: 1em; padding-top: 1em; padding-bottom: 1em; } .entry-content ol { list-style-type: decimal; padding-left: 1em; padding-top: 1em; padding-bottom: 1em; } .entry-content blockquote.code { padding-left: 1em; overflow: auto; overflow: auto; border: none; font: small monaco, "Courier New", Courier, mono; } .entry-content blockquote { margin-top: 1em; padding-left: 2em; padding-right: 2em; margin-bottom: 1em; } .entry-content blockquote pre{ padding-top: 0.5em; padding-bottom: 0.5em; } .entry-content code { font: small monaco, "Courier New", Courier, mono; } .entry-meta { font-style: italic; float: right; padding-left: 5em; font-size: small; border-top: 1px solid black; } .sidebar{ width: 24%; float: right; } #home-link h3 { font-weight: bold; } #search h3 { padding-top: 1em; font-weight: bold; } #categories h3 { padding-top: 1em; font-weight: bold; } #categories ul { padding: 0; margin: 0; padding-left: 10px; font-size: small; line-height: 1.2em; } #archives h3 { padding-top: 1em; font-weight: bold; } #archives ul { padding: 0; margin: 0; padding-left: 10px; font-size: small; line-height: 1.2em; } .linkcat h3 { padding-top: 1em; font-weight: bold; } .linkcat ul { padding: 0; margin: 0; padding-left: 10px; font-size: small; line-height: 1.2em; } #rss-links h3 { padding-top: 1em; font-weight: bold; } #rss-links ul { padding: 0; margin: 0; padding-left: 10px; font-size: small; line-height: 1.2em; } #meta h3 { padding-top: 1em; font-weight: bold; } #meta ul { padding: 0; margin: 0; padding-left: 10px; font-size: small; line-height: 1.2em; } #nav-below { height: 0px; clear: both; padding-top: 1em; padding-bottom: 1em; } .nav-previous{ float: left; } .nav-next{ float: right; } #footer{ height: 0px; clear: both; padding-top: 1em; padding-bottom: 1em; font-size: small; } /* -------------------------- */ .hide { display: none; } #clear-both { height: 0px; clear: both } chronicle-4.6/themes/simple/reset.css0000644000175000017500000000165311557771306016160 0ustar skxskx/* http://meyerweb.com/eric/thoughts/2007/05/01/reset-reloaded/ */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, fieldset, form, label, legend, { margin: 0; padding: 0; border: 0; outline: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; } /* remember to define focus styles! */ :focus { outline: 0; } body { line-height: 1; color: black; background: white; } ol, ul { list-style: none; } blockquote:before, blockquote:after, q:before, q:after { content: ""; } blockquote, q { quotes: "" ""; } pre { background-color: #bbbbbb; color: black !important; font-weight: bold; padding: 2px; font-family: Lucidatypewriter,monospace; border: 1px solid grey; overflow: auto; } chronicle-4.6/themes/simple/comment-loop.inc0000644000175000017500000000135211557771306017424 0ustar skxskx

Comments On This Entry

  1. [gravitar]
chronicle-4.6/themes/simple/xml.gif0000644000175000017500000000065511557771306015614 0ustar skxskxGIF89a$X]rূ^dɱcߞrsr3罡џAC4Ug#糑諂alw.n$|${3¡S!ѲD}3WP?Ȥf!,$ lH,ȣ%k:Ш4bNXو 2U,BXb&YUh( db9A1.bRqO" 11 .f+g1[N%g.2ft$ZQb1Mf gPN1#t.O)g.++*O2](N1& N2M..2N2 2eHE‹*\p ;chronicle-4.6/themes/simple/footer.inc0000644000175000017500000000026611557771306016314 0ustar skxskxCreated by Chronicle, comment spam filtered by blogspam.net. chronicle-4.6/themes/simple/sidebar.inc0000644000175000017500000000440011557771306016421 0ustar skxskx
  • Archive

      • style="display: none;" id="year_">
      • ()
  • Tag List

    Show all

chronicle-4.6/themes/simple/progress.gif0000644000175000017500000002110011557771306016644 0ustar skxskxGIF89a$$! NETSCAPE2.0!,$$'d)n$]ṕP{y@u&0)ȒrI\vЬ:xXVK5&UAz1xM>WJwDyKh}j$#]^=m5Wx%}<.rG.˪NƱNN uNX T؀!þh0ܦ!x pȰ@ZCF(a0 0љЀ0)P  *р'-* B%4`+KmX@DгR0] Ze.ʶ_*]@& A^od`4yaƎ:xk:21-A3꾪!/@2 kQ~dݺ5` =7b!,$$'d)zhj,ٽojRkDz[)'d;=L)@n]1 x:[[eh]{8.C&zMZ(vTNzEG#D' ^"o6~+WX6 ȼRͣJӼi "}0NɁ~΅Pb~\/!XNʅ !Rbȗ KyrJ I`*ؤ0aa XsgED< : AK6jD+m  @?]  ˴CN`D Vm80A1cΏ6}:: ޞ 6Cn?@Fh,8~XP[!,$$'dٝhlg'$s]Fx:GYh!1,5Du!pxܞz~`TskCMf<|}#bLwo<"$K*<}h.+aI{}>θ#pзuRXH8}׈ŅD-NhTBǓ\(AȑX\(@ Dd` PIN#0@xj6maO8X@hb9dҥ`Êk6AڪX Xeөs,0{n^w*}xp]$@S7&lU>0`ּ1dB}h1Ա$|{.0x U0UT7t,!; NcHfS#!,$$'$ٕ^췽+vgkYL׷R.s|h3$`ϏnLSu $d-k-7$"A#Ʃ̿AJߥA 7J"vao֔ X8 =#BP@Ë !F40AAbǓXT HF@Б, 5"Ã-1I8@R CHWBe(lWdf{h#tb8|%smYzq/uU"p<;rR<I±  ַ9`>!s!  D< p XCB8pG(Q+Idȑ8€R @@@E\@D>I\R\@2 0қ)`ATE$8`n z3SRE`-۶p * bVM{Uq3-עŌ,y@ ;( (_Ȝ=r!Aiӏ! ; Z6|y@!ݱ!$0a $]@sPE}!!,$$'u[ve[sf]vӤt`keast*[%z{_ndVs*#__R*w" }TfX+Z&mw _,-" 1. Ͽ? \ ?-Nt A@ &Ņ@Lў/`I&ϊH䁗0$c b4P@BHtʳ@EP@h `1E 4}ʕt H68 9 @7@mumҡhW}Lw@'z"V/ NPgʋ/Pyĕ-_.eɠ`p ^W4w*T-[5]4hPoq CZb$@`B&!!,$$'XlS E]oNJ3RZd Ԅ4se$Fp<~p9|2iPǂ4 eth?AB`}~-uwDT$ ?O_|% eu>$ p# qĵ+ ɜ :}{ GbUU6|}UD\P=#(p`C" HXȱcG(0 pD 44I$@̞=?P^Dt$:x)T{+ ʵ+%0vCM[W޼0ʢܤu^ @8ἔ?d~-G<5Hpr[p7kVIк "&@ "!,$$'}Ve=Mpl6RRaEL.)S@ƣ-_B W2T+m*Z\@wR_b;d}~Z&vwlF{J/ k s.#qqZ^'**$ 1~"Ag"޽[ h"9ȗAE nx/?iHHQႆ!JHq @Nʢt1`K8iB Zt9 _4O~Ȱ=t#"@HbLxS*ҩaLeH~8Ёի%釷q Y5 X }B!@ƁB&+yrXc… 2̧#c`ҙ5.F-9  .IEL9b!,$$'d]ׇeeG=ˢ,@S$G";dG[!(,afiIy1Lr2Oa?tvk-n{|qWZ. |p7Gi%S su%TTuJ#x"i(NЯ?,"ܿO]()l6D1`ׯo0 ;I`+)5 \ `vȒa̍K8 >P0'A0 h0߂ j|(1GC^gaF Bʪ F?Y 8s⤠aʄ#2\JN!a8pc q /`85jT`k2p]>0C٫ݰ!mly=;B/nI 6XcØe0glsΞͶVԩ7v 8.vN%[tiU_0P?y JW!,$$'a%Mue,[DNa(l(P o;8}!AꖼN`S$a܄BqXh;&rstvxz o1IdY `&WNy %qe<;w|9sm?# Q&jo3ICƀ a6N0p`;aDLPX/D@ބ#@1@@!A% (lRed8F(B /~ iSƨ*K2:h ZV2`kئbz j^`6sś޶F-l] w F5ƂB[&0Em<2aðu؆-&bv4͸AOt?;vTG!,$$'$abuYWpaO 8qOz+BiAC 9bIqjx5 E}Hp>b  1Q{`F~t =QmacY %:ar #RG["BL$s <†,1 yC$\  k=\ i$d@C &((KJ hłM#.'ɏ!EZLaI0tQcetաgO. 'jRϣGh0O&5CLBs pkV"% o1Xpٲg'T+ o3d@xnݻx#/` k1?L0ݺ%hhYCȄ?.fQ0ufǦ!fB&p\h~_M'n8f~y͂!,$$'i5QԵpcN1=Ɠ GtX0Cʂҁ.j%/H)ucZ 1azT|Xu /$Ddt%nq9gwE Y ]f j2nO1ř Aaʶ 2^ ^P  2 : >:py`Ņ (@x7>|ɓ'J 4@6!O'lIT m95h0:b I.})T2` X.bjT aŖ@lZ2yp 6nY VU鄝%7 +m\1MqVR$6H/͐&}DeQwV:F˦/8~šw'7ּ,5&Λ/q 0]!,$$'e$Mue,Bx. œ v`cDxLZ aŪu~_etp3`pBAf`wyL0Ovcxz]X}] EL;e $2D ' dhP3"ő"z0 ۑM] Ƒ  iၿ*HHD` j؅$FRĦK"8!e+IdKp 5m͐Å.L 5TnE… ⎝P֬^& 6l\ s9!Ɔ|bn% T1ܿkWo ܀'^ ۆ {1iPPAsLS[np!'Ρc@cNpy!,$$'$]4Q֕p,{Y`Xsw=M,6G P6`Ax$;(gdg˃lF^6Ln.Gu_&B{| ?v"zoph"#M &h.^j3e".2>GGG PG2܂} P?248@9 +40m SbXC  G)-ta0cZ BN:/‚@4pQ I/TuBS8/EUW@S9jBrJucάjJpn`O# kr]qܹ Tf '4`|d(}?k2_ŷ;^~ ,l aj'TQ r!Created by P@olo5c.USSPCMT;chronicle-4.6/themes/simple/tags.template0000644000175000017500000000565211557771306017022 0ustar skxskx <!-- tmpl_var name='blog_title' -->: Entries Tagged <!-- tmpl_var name='tagname' -->

Entries tagged "".

chronicle-4.6/themes/simple/comment-form.inc0000644000175000017500000000217011557771306017415 0ustar skxskx

 

Add A Comment

Name:
Email:
Website:
Your Comment

Your submission will be ignored if the name, email, or comment field is left blank.

Your email address will never be displayed, but your homepage will be.

chronicle-4.6/themes/simple/month.template0000644000175000017500000000577411557771306017216 0ustar skxskx <!-- tmpl_var name='blog_title' -->: entries from <!-- tmpl_var name='month_name' --> <!-- tmpl_var name='year' -->

Entries from .

chronicle-4.6/themes/simple/comments.css0000644000175000017500000000203611557771306016657 0ustar skxskx h3#comments img { margin-top:4px; } ol.commentlist { margin-bottom:40px; font-size:0.93em; } ol.commentlist li.commento { padding:10px; margin-bottom:10px; overflow:hidden; height:1%; } ol.commentlist img.avatar { float:left; margin-right:10px; } ol.commentlist cite { margin-top:20px; font-style:normal; font:1.5em/150% helvetica,"trebuchet ms",arial,tahoma,verdana,sans-serif; font-weight:normal; } ol.commentlist cite a { text-decoration:none; color:#369; } small.commentmetadata { display:block; font-size:0.93em; border-top:1px dotted #666; color:#999; margin-bottom:10px; margin-top:10px; } small.commentmetadata a { color:#999; } ol.commentlist p { margin-bottom:10px; } ol.commentlist li.alt { } ol.commentlist li.author { background-color: #E2E2E2; border: 1px solid red; } ol.commentlist li.commento li { margin-left:40px; } ol.commentlist li.commento ul li { list-style-type:square; } ol.commentlist li.commento ol li { list-style-type:decimal; } .commentbody p { padding-top: 0.5em; padding-bottom: 0.5em; } chronicle-4.6/themes/blog.steve.org.uk/0000755000175000017500000000000011557771306016302 5ustar skxskxchronicle-4.6/themes/blog.steve.org.uk/index.template0000644000175000017500000000640711557771306021155 0ustar skxskx <!-- tmpl_var name='blog_title' -->

Untitled Blog

comments Tags: ., No tags

RSS Feed

Created by Chronicle v

chronicle-4.6/themes/blog.steve.org.uk/ajax.js0000644000175000017500000000473211557771306017571 0ustar skxskx// -*-mode: C++; style: K&R; c-basic-offset: 4 ; -*- */ // // Simple collection of Javascript for Ajax form submission. // // // Get an XMLHTTPRequest object. // function getXMLHTTPRequest() { req = false; if(window.XMLHttpRequest) { try { req = new XMLHttpRequest(); } catch(e) { req = false; } } else if(window.ActiveXObject) { try { req = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { try { req = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) { req = false; } } } return( req ); } // // Submit the comment. // function submitComment() { showProgress(); var xhr = getXMLHTTPRequest(); if(! xhr ) { hideProgress(); return; } xhr.onreadystatechange = function() { if(xhr.readyState == 4) { if(xhr.status == 200) { var o = document.getElementById( "output" ); o.innerHTML = xhr.responseText; } else { var o = document.getElementById( "output" ); o.innerHTML = "Failed HTTP code " + xhr.status + " " + xhr.responseText; } hideProgress(); } }; data = 'ajax=1'; data = data + '&id=' + escape(document.forms[0].id.value ); data = data + '&captcha=' + escape( document.forms[0].captcha.value ); data = data + '&id=' + escape(document.forms[0].id.value ); data = data + '&captcha=' + escape( document.forms[0].captcha.value ); data = data + '&name=' + escape( document.forms[0].name.value ); data = data + '&mail=' + escape( document.forms[0].mail.value ); data = data + '&body=' + escape( document.forms[0].body.value ); // // Make the request // xhr.open("POST", "/cgi-bin/comments.cgi", true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(data); } // // Show our progress marker. // function showProgress() { var i = document.getElementById( "progress" ); if ( i ) { i.style.display = 'block'; } } // // Hide our progress marker. // function hideProgress() { var i = document.getElementById( "progress" ); if ( i ) { i.style.display = 'none'; } } chronicle-4.6/themes/blog.steve.org.uk/box.inc0000644000175000017500000000053111557771306017564 0ustar skxskx

About This Site

This is a simple blog relating to Debian & Free Software issues.

chronicle-4.6/themes/blog.steve.org.uk/style.css0000644000175000017500000000372011557771306020156 0ustar skxskx body { padding: 0 10px; padding-top: 1px; padding-bottom: 1em; background-color: white; color: black; margin: 0; } div.title { } div.title h1 { margin: 0px -10px; padding: 5px 10px 0px 10px; text-align: center; } div.title a { color: black !important; text-decoration: none !important; } div.title a:hover { color: black !important; text-decoration: none !important; } div.title h2 { margin: 0 0 1ex 0; padding: 0; text-align: right; } /* * Special markup for weblog entries. */ div.entry { border-left: 1px solid rgb(128, 128, 128); border-right: 1px solid rgb(128, 128, 128); border-top: 1px solid rgb(128, 128, 128); border-bottom: 1px solid rgb(128, 128, 128); margin: 10px 0px; } div.padding { padding-top: 15px; padding-bottom: 15px; } div.entry div.body { padding: 10px 10px; } div.entry .title { background-color: #eee; border-bottom: 1px solid rgb(128, 128, 128); font-weight: bold; font-size: 120%; padding: 0.26ex 10px; } div.entry div.date { text-align: right; font-weight: bold; border-bottom: 1px solid black; } div.entry div.title a { color: black !important; text-decoration: none !important; } div.entry div.title a:hover { color: black !important; text-decoration: none !important; } div.entry div.tags { border-top: 1px solid rgb(128, 128, 128); font-family: Verdana, Georgia, Arial, sans-serif; text-align: right; } div.entry div.tags span.comments { padding-left: 5px; float: left; } div.entry div.body ul { list-style-type: disc; list-style-position: inside; margin: 0 0 1em 0; } ul { list-style-type: none; list-style-position: outside; margin: 0 0 1em 0; padding: 0; } li { margin: 1ex; padding: 0 0 1.25ex 0.75ex; font-size: 95%; } .box { border: 1px solid black; background-color: #eee; font-size: 80%; } pre { overflow: auto; background: rgb(230,230,230); border: solid; border-width: thin; padding: 5px 10px; } chronicle-4.6/themes/blog.steve.org.uk/vim.css0000644000175000017500000000077211557771306017615 0ustar skxskxbody { color: black; background: white } .synComment { color: #0000FF } .synConstant { color: #FF00FF } .synIdentifier { color: #008B8B } .synStatement { color: #A52A2A ; font-weight: bold } .synPreProc { color: #A020F0 } .synType { color: #2E8B57 ; font-weight: bold } .synSpecial { color: #6A5ACD } .synUnderlined { color: #000000 ; text-decoration: underline } .synError { color: #FFFFFF ; background: #FF0000 none } .synTodo { color: #0000FF ; background: #FFFF00 none } chronicle-4.6/themes/blog.steve.org.uk/entry.template0000644000175000017500000000666011557771306021210 0ustar skxskx <!-- tmpl_var name='blog_title' -->: <!-- tmpl_var name='title' -->

Untitled Blog

Tags: .,

Created by Chronicle v

chronicle-4.6/themes/blog.steve.org.uk/favicon.ico0000644000175000017500000001114611557771306020426 0ustar skxskx F h (>( @ʦ @ ` @@ @@@`@@@@`` `@`````` @` @` @` @`@@ @@@`@@@@@ @ @ @@ `@ @ @ @ @@@@ @@@@@`@@@@@@@@@`@` @`@@``@`@`@`@`@@ @@@`@@@@@@ @@@`@@@@@@ @@@`@@@@@@ @@@`@@@@ @` @ ` @@ @@@`@@@@`` `@`````` @` @` @` @` @` @ ` @@ @@@`@@@@`` `@`````` @` @` @`NFFNFFFNFFFFFFFNFFFFFFFWNNFFFFFFNNNFFFFNFFNFWFFFWNFFFFFNFFNFFFFFFFFFFFNFNWFNFFNFFFNFFFFWFFNFFFFNFFFFFFFFFNFFFFFFFFFFFFFNFFFFNFFFFFFFFFFFFFFFFFFFFFFFFFFNNFFNW??8?{?( @ ??|???( @ʦ @ ` @@ @@@`@@@@`` `@`````` @` @` @` @`@@ @@@`@@@@@ @ @ @@ `@ @ @ @ @@@@ @@@@@`@@@@@@@@@`@` @`@@``@`@`@`@`@@ @@@`@@@@@@ @@@`@@@@@@ @@@`@@@@@@ @@@`@@@@ @` @ ` @@ @@@`@@@@`` `@`````` @` @` @` @` @` @ ` @@ @@@`@@@@`` `@`````` @` @` @`NNNNFVFFNFFFNNFVNVNFFFNNFNFFNFVVNFVFFV?:+3J1XO(  < 3'ـ95chronicle-4.6/themes/blog.steve.org.uk/comment-loop.inc0000644000175000017500000000052611557771306021411 0ustar skxskx

Comments On This Entry

chronicle-4.6/themes/blog.steve.org.uk/xml.gif0000644000175000017500000000065511557771306017577 0ustar skxskxGIF89a$X]rূ^dɱcߞrsr3罡џAC4Ug#糑諂alw.n$|${3¡S!ѲD}3WP?Ȥf!,$ lH,ȣ%k:Ш4bNXو 2U,BXb&YUh( db9A1.bRqO" 11 .f+g1[N%g.2ft$ZQb1Mf gPN1#t.O)g.++*O2](N1& N2M..2N2 2eHE‹*\p ;chronicle-4.6/themes/blog.steve.org.uk/progress.gif0000644000175000017500000002110011557771306020627 0ustar skxskxGIF89a$$! NETSCAPE2.0!,$$'d)n$]ṕP{y@u&0)ȒrI\vЬ:xXVK5&UAz1xM>WJwDyKh}j$#]^=m5Wx%}<.rG.˪NƱNN uNX T؀!þh0ܦ!x pȰ@ZCF(a0 0љЀ0)P  *р'-* B%4`+KmX@DгR0] Ze.ʶ_*]@& A^od`4yaƎ:xk:21-A3꾪!/@2 kQ~dݺ5` =7b!,$$'d)zhj,ٽojRkDz[)'d;=L)@n]1 x:[[eh]{8.C&zMZ(vTNzEG#D' ^"o6~+WX6 ȼRͣJӼi "}0NɁ~΅Pb~\/!XNʅ !Rbȗ KyrJ I`*ؤ0aa XsgED< : AK6jD+m  @?]  ˴CN`D Vm80A1cΏ6}:: ޞ 6Cn?@Fh,8~XP[!,$$'dٝhlg'$s]Fx:GYh!1,5Du!pxܞz~`TskCMf<|}#bLwo<"$K*<}h.+aI{}>θ#pзuRXH8}׈ŅD-NhTBǓ\(AȑX\(@ Dd` PIN#0@xj6maO8X@hb9dҥ`Êk6AڪX Xeөs,0{n^w*}xp]$@S7&lU>0`ּ1dB}h1Ա$|{.0x U0UT7t,!; NcHfS#!,$$'$ٕ^췽+vgkYL׷R.s|h3$`ϏnLSu $d-k-7$"A#Ʃ̿AJߥA 7J"vao֔ X8 =#BP@Ë !F40AAbǓXT HF@Б, 5"Ã-1I8@R CHWBe(lWdf{h#tb8|%smYzq/uU"p<;rR<I±  ַ9`>!s!  D< p XCB8pG(Q+Idȑ8€R @@@E\@D>I\R\@2 0қ)`ATE$8`n z3SRE`-۶p * bVM{Uq3-עŌ,y@ ;( (_Ȝ=r!Aiӏ! ; Z6|y@!ݱ!$0a $]@sPE}!!,$$'u[ve[sf]vӤt`keast*[%z{_ndVs*#__R*w" }TfX+Z&mw _,-" 1. Ͽ? \ ?-Nt A@ &Ņ@Lў/`I&ϊH䁗0$c b4P@BHtʳ@EP@h `1E 4}ʕt H68 9 @7@mumҡhW}Lw@'z"V/ NPgʋ/Pyĕ-_.eɠ`p ^W4w*T-[5]4hPoq CZb$@`B&!!,$$'XlS E]oNJ3RZd Ԅ4se$Fp<~p9|2iPǂ4 eth?AB`}~-uwDT$ ?O_|% eu>$ p# qĵ+ ɜ :}{ GbUU6|}UD\P=#(p`C" HXȱcG(0 pD 44I$@̞=?P^Dt$:x)T{+ ʵ+%0vCM[W޼0ʢܤu^ @8ἔ?d~-G<5Hpr[p7kVIк "&@ "!,$$'}Ve=Mpl6RRaEL.)S@ƣ-_B W2T+m*Z\@wR_b;d}~Z&vwlF{J/ k s.#qqZ^'**$ 1~"Ag"޽[ h"9ȗAE nx/?iHHQႆ!JHq @Nʢt1`K8iB Zt9 _4O~Ȱ=t#"@HbLxS*ҩaLeH~8Ёի%釷q Y5 X }B!@ƁB&+yrXc… 2̧#c`ҙ5.F-9  .IEL9b!,$$'d]ׇeeG=ˢ,@S$G";dG[!(,afiIy1Lr2Oa?tvk-n{|qWZ. |p7Gi%S su%TTuJ#x"i(NЯ?,"ܿO]()l6D1`ׯo0 ;I`+)5 \ `vȒa̍K8 >P0'A0 h0߂ j|(1GC^gaF Bʪ F?Y 8s⤠aʄ#2\JN!a8pc q /`85jT`k2p]>0C٫ݰ!mly=;B/nI 6XcØe0glsΞͶVԩ7v 8.vN%[tiU_0P?y JW!,$$'a%Mue,[DNa(l(P o;8}!AꖼN`S$a܄BqXh;&rstvxz o1IdY `&WNy %qe<;w|9sm?# Q&jo3ICƀ a6N0p`;aDLPX/D@ބ#@1@@!A% (lRed8F(B /~ iSƨ*K2:h ZV2`kئbz j^`6sś޶F-l] w F5ƂB[&0Em<2aðu؆-&bv4͸AOt?;vTG!,$$'$abuYWpaO 8qOz+BiAC 9bIqjx5 E}Hp>b  1Q{`F~t =QmacY %:ar #RG["BL$s <†,1 yC$\  k=\ i$d@C &((KJ hłM#.'ɏ!EZLaI0tQcetաgO. 'jRϣGh0O&5CLBs pkV"% o1Xpٲg'T+ o3d@xnݻx#/` k1?L0ݺ%hhYCȄ?.fQ0ufǦ!fB&p\h~_M'n8f~y͂!,$$'i5QԵpcN1=Ɠ GtX0Cʂҁ.j%/H)ucZ 1azT|Xu /$Ddt%nq9gwE Y ]f j2nO1ř Aaʶ 2^ ^P  2 : >:py`Ņ (@x7>|ɓ'J 4@6!O'lIT m95h0:b I.})T2` X.bjT aŖ@lZ2yp 6nY VU鄝%7 +m\1MqVR$6H/͐&}DeQwV:F˦/8~šw'7ּ,5&Λ/q 0]!,$$'e$Mue,Bx. œ v`cDxLZ aŪu~_etp3`pBAf`wyL0Ovcxz]X}] EL;e $2D ' dhP3"ő"z0 ۑM] Ƒ  iၿ*HHD` j؅$FRĦK"8!e+IdKp 5m͐Å.L 5TnE… ⎝P֬^& 6l\ s9!Ɔ|bn% T1ܿkWo ܀'^ ۆ {1iPPAsLS[np!'Ρc@cNpy!,$$'$]4Q֕p,{Y`Xsw=M,6G P6`Ax$;(gdg˃lF^6Ln.Gu_&B{| ?v"zoph"#M &h.^j3e".2>GGG PG2܂} P?248@9 +40m SbXC  G)-ta0cZ BN:/‚@4pQ I/TuBS8/EUW@S9jBrJucάjJpn`O# kr]qܹ Tf '4`|d(}?k2_ŷ;^~ ,l aj'TQ r!Created by P@olo5c.USSPCMT;chronicle-4.6/themes/blog.steve.org.uk/tags.inc0000644000175000017500000000040711557771306017734 0ustar skxskx

Tags

chronicle-4.6/themes/blog.steve.org.uk/tags.template0000644000175000017500000000666211557771306021007 0ustar skxskx <!-- tmpl_var name='blog_title' -->: Entries Tagged <!-- tmpl_var name='tagname' -->

Untitled Blog

Entries tagged "".

comments Tags: .,

RSS feed

Created by Chronicle v

chronicle-4.6/themes/blog.steve.org.uk/rounded_corners.inc.js0000644000175000017500000006110011557771306022601 0ustar skxskx /**************************************************************** * * * curvyCorners * * ------------ * * * * This script generates rounded corners for your divs. * * * * Version 1.2.7 * * Copyright (c) 2006 Cameron Cooke * * By: Cameron Cooke and Tim Hutchison. * * * * * * Website: http://www.curvycorners.net * * Email: info@totalinfinity.com * * Forum: http://www.curvycorners.net/forum/ * * * * * * This library is free software; you can redistribute * * it and/or modify it under the terms of the GNU * * Lesser General Public License as published by the * * Free Software Foundation; either version 2.1 of the * * License, or (at your option) any later version. * * * * This library is distributed in the hope that it will * * be useful, but WITHOUT ANY WARRANTY; without even the * * implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU Lesser General Public * * License for more details. * * * * You should have received a copy of the GNU Lesser * * General Public License along with this library; * * Inc., 59 Temple Place, Suite 330, Boston, * * MA 02111-1307 USA * * * ****************************************************************/ var isIE = navigator.userAgent.toLowerCase().indexOf("msie") > -1; var isMoz = document.implementation && document.implementation.createDocument; var isSafari = ((navigator.userAgent.toLowerCase().indexOf('safari')!=-1)&&(navigator.userAgent.toLowerCase().indexOf('mac')!=-1))?true:false; function curvyCorners() { if(typeof(arguments[0]) != "object") throw newCurvyError("First parameter of curvyCorners() must be an object."); if(typeof(arguments[1]) != "object" && typeof(arguments[1]) != "string") throw newCurvyError("Second parameter of curvyCorners() must be an object or a class name."); if(typeof(arguments[1]) == "string") { var startIndex = 0; var boxCol = getElementsByClass(arguments[1]);} else { var startIndex = 1; var boxCol = arguments;} var curvyCornersCol = new Array(); for(var i = startIndex, j = boxCol.length; i < j; i++) { if(boxCol[i].tagName.toLowerCase() == "div") { curvyCornersCol[curvyCornersCol.length] = new curvyObject(arguments[0], boxCol[i]);} } this.objects = curvyCornersCol; this.applyCornersToAll = function() { for(var x = 0, k = this.objects.length; x < k; x++) { this.objects[x].applyCorners();} } } function curvyObject() { this.box = arguments[1]; this.settings = arguments[0]; this.topContainer = null; this.bottomContainer = null; this.masterCorners = new Array(); this.contentDIV = null; var boxHeight = get_style(this.box, "height", "height"); var boxWidth = get_style(this.box, "width", "width"); var borderWidth = get_style(this.box, "borderTopWidth", "border-top-width"); var borderColour = get_style(this.box, "borderTopColor", "border-top-color"); var boxColour = get_style(this.box, "backgroundColor", "background-color"); var backgroundImage = get_style(this.box, "backgroundImage", "background-image"); var boxPosition = get_style(this.box, "position", "position"); var boxPadding = get_style(this.box, "paddingTop", "padding-top"); this.boxHeight = parseInt(((boxHeight != "" && boxHeight != "auto" && boxHeight.indexOf("%") == -1)? boxHeight.substring(0, boxHeight.indexOf("px")) : this.box.scrollHeight)); this.boxWidth = parseInt(((boxWidth != "" && boxWidth != "auto" && boxWidth.indexOf("%") == -1)? boxWidth.substring(0, boxWidth.indexOf("px")) : this.box.scrollWidth)); this.borderWidth = parseInt(((borderWidth != "" && borderWidth.indexOf("px") !== -1)? borderWidth.slice(0, borderWidth.indexOf("px")) : 0)); this.boxColour = format_colour(boxColour); this.boxPadding = parseInt(((boxPadding != "" && boxPadding.indexOf("px") !== -1)? boxPadding.slice(0, boxPadding.indexOf("px")) : 0)); this.borderColour = format_colour(borderColour); this.borderString = this.borderWidth + "px" + " solid " + this.borderColour; this.backgroundImage = ((backgroundImage != "none")? backgroundImage : ""); this.boxContent = this.box.innerHTML; if(boxPosition != "absolute") this.box.style.position = "relative"; this.box.style.padding = "0px"; if(isIE && boxWidth == "auto" && boxHeight == "auto") this.box.style.width = "100%"; if(this.settings.autoPad == true && this.boxPadding > 0) this.box.innerHTML = ""; this.applyCorners = function() { for(var t = 0; t < 2; t++) { switch(t) { case 0: if(this.settings.tl || this.settings.tr) { var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var topMaxRadius = Math.max(this.settings.tl ? this.settings.tl.radius : 0, this.settings.tr ? this.settings.tr.radius : 0); newMainContainer.style.height = topMaxRadius + "px"; newMainContainer.style.top = 0 - topMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.topContainer = this.box.appendChild(newMainContainer);} break; case 1: if(this.settings.bl || this.settings.br) { var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var botMaxRadius = Math.max(this.settings.bl ? this.settings.bl.radius : 0, this.settings.br ? this.settings.br.radius : 0); newMainContainer.style.height = botMaxRadius + "px"; newMainContainer.style.bottom = 0 - botMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.bottomContainer = this.box.appendChild(newMainContainer);} break;} } if(this.topContainer) this.box.style.borderTopWidth = "0px"; if(this.bottomContainer) this.box.style.borderBottomWidth = "0px"; var corners = ["tr", "tl", "br", "bl"]; for(var i in corners) { var cc = corners[i]; if(!this.settings[cc]) { if(((cc == "tr" || cc == "tl") && this.topContainer != null) || ((cc == "br" || cc == "bl") && this.bottomContainer != null)) { var newCorner = document.createElement("DIV"); newCorner.style.position = "relative"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; if(this.backgroundImage == "") newCorner.style.backgroundColor = this.boxColour; else newCorner.style.backgroundImage = this.backgroundImage; switch(cc) { case "tl": newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.tr.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.left = -this.borderWidth + "px"; break; case "tr": newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.tl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; newCorner.style.left = this.borderWidth + "px"; break; case "bl": newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.br.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = -this.borderWidth + "px"; newCorner.style.backgroundPosition = "-" + (this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break; case "br": newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.bl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = this.borderWidth + "px" newCorner.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break;} } } else { if(this.masterCorners[this.settings[cc].radius]) { var newCorner = this.masterCorners[this.settings[cc].radius].cloneNode(true);} else { var newCorner = document.createElement("DIV"); newCorner.style.height = this.settings[cc].radius + "px"; newCorner.style.width = this.settings[cc].radius + "px"; newCorner.style.position = "absolute"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; var borderRadius = parseInt(this.settings[cc].radius - this.borderWidth); for(var intx = 0, j = this.settings[cc].radius; intx < j; intx++) { if((intx +1) >= borderRadius) var y1 = -1; else var y1 = (Math.floor(Math.sqrt(Math.pow(borderRadius, 2) - Math.pow((intx+1), 2))) - 1); if(borderRadius != j) { if((intx) >= borderRadius) var y2 = -1; else var y2 = Math.ceil(Math.sqrt(Math.pow(borderRadius,2) - Math.pow(intx, 2))); if((intx+1) >= j) var y3 = -1; else var y3 = (Math.floor(Math.sqrt(Math.pow(j ,2) - Math.pow((intx+1), 2))) - 1);} if((intx) >= j) var y4 = -1; else var y4 = Math.ceil(Math.sqrt(Math.pow(j ,2) - Math.pow(intx, 2))); if(y1 > -1) this.drawPixel(intx, 0, this.boxColour, 100, (y1+1), newCorner, -1, this.settings[cc].radius); if(borderRadius != j) { for(var inty = (y1 + 1); inty < y2; inty++) { if(this.settings.antiAlias) { if(this.backgroundImage != "") { var borderFract = (pixelFraction(intx, inty, borderRadius) * 100); if(borderFract < 30) { this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, 0, this.settings[cc].radius);} else { this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, -1, this.settings[cc].radius);} } else { var pixelcolour = BlendColour(this.boxColour, this.borderColour, pixelFraction(intx, inty, borderRadius)); this.drawPixel(intx, inty, pixelcolour, 100, 1, newCorner, 0, this.settings[cc].radius, cc);} } } if(this.settings.antiAlias) { if(y3 >= y2) { if (y2 == -1) y2 = 0; this.drawPixel(intx, y2, this.borderColour, 100, (y3 - y2 + 1), newCorner, 0, 0);} } else { if(y3 >= y1) { this.drawPixel(intx, (y1 + 1), this.borderColour, 100, (y3 - y1), newCorner, 0, 0);} } var outsideColour = this.borderColour;} else { var outsideColour = this.boxColour; var y3 = y1;} if(this.settings.antiAlias) { for(var inty = (y3 + 1); inty < y4; inty++) { this.drawPixel(intx, inty, outsideColour, (pixelFraction(intx, inty , j) * 100), 1, newCorner, ((this.borderWidth > 0)? 0 : -1), this.settings[cc].radius);} } } this.masterCorners[this.settings[cc].radius] = newCorner.cloneNode(true);} if(cc != "br") { for(var t = 0, k = newCorner.childNodes.length; t < k; t++) { var pixelBar = newCorner.childNodes[t]; var pixelBarTop = parseInt(pixelBar.style.top.substring(0, pixelBar.style.top.indexOf("px"))); var pixelBarLeft = parseInt(pixelBar.style.left.substring(0, pixelBar.style.left.indexOf("px"))); var pixelBarHeight = parseInt(pixelBar.style.height.substring(0, pixelBar.style.height.indexOf("px"))); if(cc == "tl" || cc == "bl"){ pixelBar.style.left = this.settings[cc].radius -pixelBarLeft -1 + "px";} if(cc == "tr" || cc == "tl"){ pixelBar.style.top = this.settings[cc].radius -pixelBarHeight -pixelBarTop + "px";} switch(cc) { case "tr": pixelBar.style.backgroundPosition = "-" + Math.abs((this.boxWidth - this.settings[cc].radius + this.borderWidth) + pixelBarLeft) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "tl": pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "bl": pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs((this.boxHeight + this.settings[cc].radius + pixelBarTop) -this.borderWidth) + "px"; break;} } } } if(newCorner) { switch(cc) { case "tl": if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "tr": if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "bl": if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break; case "br": if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break;} } } var radiusDiff = new Array(); radiusDiff["t"] = Math.abs(this.settings.tl.radius - this.settings.tr.radius) radiusDiff["b"] = Math.abs(this.settings.bl.radius - this.settings.br.radius); for(z in radiusDiff) { if(radiusDiff[z]) { var smallerCornerType = ((this.settings[z + "l"].radius < this.settings[z + "r"].radius)? z +"l" : z +"r"); var newFiller = document.createElement("DIV"); newFiller.style.height = radiusDiff[z] + "px"; newFiller.style.width = this.settings[smallerCornerType].radius+ "px" newFiller.style.position = "absolute"; newFiller.style.fontSize = "1px"; newFiller.style.overflow = "hidden"; newFiller.style.backgroundColor = this.boxColour; switch(smallerCornerType) { case "tl": newFiller.style.bottom = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.topContainer.appendChild(newFiller); break; case "tr": newFiller.style.bottom = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.topContainer.appendChild(newFiller); break; case "bl": newFiller.style.top = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.bottomContainer.appendChild(newFiller); break; case "br": newFiller.style.top = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.bottomContainer.appendChild(newFiller); break;} } var newFillerBar = document.createElement("DIV"); newFillerBar.style.position = "relative"; newFillerBar.style.fontSize = "1px"; newFillerBar.style.overflow = "hidden"; newFillerBar.style.backgroundColor = this.boxColour; newFillerBar.style.backgroundImage = this.backgroundImage; switch(z) { case "t": if(this.topContainer) { if(this.settings.tl.radius && this.settings.tr.radius) { newFillerBar.style.height = topMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.tl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.tr.radius - this.borderWidth + "px"; newFillerBar.style.borderTop = this.borderString; if(this.backgroundImage != "") newFillerBar.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; this.topContainer.appendChild(newFillerBar);} this.box.style.backgroundPosition = "0px -" + (topMaxRadius - this.borderWidth) + "px";} break; case "b": if(this.bottomContainer) { if(this.settings.bl.radius && this.settings.br.radius) { newFillerBar.style.height = botMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.bl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.br.radius - this.borderWidth + "px"; newFillerBar.style.borderBottom = this.borderString; if(this.backgroundImage != "") newFillerBar.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (topMaxRadius + this.borderWidth)) + "px"; this.bottomContainer.appendChild(newFillerBar);} } break;} } if(this.settings.autoPad == true && this.boxPadding > 0) { var contentContainer = document.createElement("DIV"); contentContainer.style.position = "relative"; contentContainer.innerHTML = this.boxContent; contentContainer.className = "autoPadDiv"; var topPadding = Math.abs(topMaxRadius - this.boxPadding); var botPadding = Math.abs(botMaxRadius - this.boxPadding); if(topMaxRadius < this.boxPadding) contentContainer.style.paddingTop = topPadding + "px"; if(botMaxRadius < this.boxPadding) contentContainer.style.paddingBottom = botMaxRadius + "px"; contentContainer.style.paddingLeft = this.boxPadding + "px"; contentContainer.style.paddingRight = this.boxPadding + "px"; this.contentDIV = this.box.appendChild(contentContainer);} } this.drawPixel = function(intx, inty, colour, transAmount, height, newCorner, image, cornerRadius) { var pixel = document.createElement("DIV"); pixel.style.height = height + "px"; pixel.style.width = "1px"; pixel.style.position = "absolute"; pixel.style.fontSize = "1px"; pixel.style.overflow = "hidden"; var topMaxRadius = Math.max(this.settings["tr"].radius, this.settings["tl"].radius); if(image == -1 && this.backgroundImage != "") { pixel.style.backgroundImage = this.backgroundImage; pixel.style.backgroundPosition = "-" + (this.boxWidth - (cornerRadius - intx) + this.borderWidth) + "px -" + ((this.boxHeight + topMaxRadius + inty) -this.borderWidth) + "px";} else { pixel.style.backgroundColor = colour;} if (transAmount != 100) setOpacity(pixel, transAmount); pixel.style.top = inty + "px"; pixel.style.left = intx + "px"; newCorner.appendChild(pixel);} } function insertAfter(parent, node, referenceNode) { parent.insertBefore(node, referenceNode.nextSibling);} function BlendColour(Col1, Col2, Col1Fraction) { var red1 = parseInt(Col1.substr(1,2),16); var green1 = parseInt(Col1.substr(3,2),16); var blue1 = parseInt(Col1.substr(5,2),16); var red2 = parseInt(Col2.substr(1,2),16); var green2 = parseInt(Col2.substr(3,2),16); var blue2 = parseInt(Col2.substr(5,2),16); if(Col1Fraction > 1 || Col1Fraction < 0) Col1Fraction = 1; var endRed = Math.round((red1 * Col1Fraction) + (red2 * (1 - Col1Fraction))); if(endRed > 255) endRed = 255; if(endRed < 0) endRed = 0; var endGreen = Math.round((green1 * Col1Fraction) + (green2 * (1 - Col1Fraction))); if(endGreen > 255) endGreen = 255; if(endGreen < 0) endGreen = 0; var endBlue = Math.round((blue1 * Col1Fraction) + (blue2 * (1 - Col1Fraction))); if(endBlue > 255) endBlue = 255; if(endBlue < 0) endBlue = 0; return "#" + IntToHex(endRed)+ IntToHex(endGreen)+ IntToHex(endBlue);} function IntToHex(strNum) { base = strNum / 16; rem = strNum % 16; base = base - (rem / 16); baseS = MakeHex(base); remS = MakeHex(rem); return baseS + '' + remS;} function MakeHex(x) { if((x >= 0) && (x <= 9)) { return x;} else { switch(x) { case 10: return "A"; case 11: return "B"; case 12: return "C"; case 13: return "D"; case 14: return "E"; case 15: return "F";} } } function pixelFraction(x, y, r) { var pixelfraction = 0; var xvalues = new Array(1); var yvalues = new Array(1); var point = 0; var whatsides = ""; var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x,2))); if ((intersect >= y) && (intersect < (y+1))) { whatsides = "Left"; xvalues[point] = 0; yvalues[point] = intersect - y; point = point + 1;} var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y+1,2))); if ((intersect >= x) && (intersect < (x+1))) { whatsides = whatsides + "Top"; xvalues[point] = intersect - x; yvalues[point] = 1; point = point + 1;} var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x+1,2))); if ((intersect >= y) && (intersect < (y+1))) { whatsides = whatsides + "Right"; xvalues[point] = 1; yvalues[point] = intersect - y; point = point + 1;} var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y,2))); if ((intersect >= x) && (intersect < (x+1))) { whatsides = whatsides + "Bottom"; xvalues[point] = intersect - x; yvalues[point] = 0;} switch (whatsides) { case "LeftRight": pixelfraction = Math.min(yvalues[0],yvalues[1]) + ((Math.max(yvalues[0],yvalues[1]) - Math.min(yvalues[0],yvalues[1]))/2); break; case "TopRight": pixelfraction = 1-(((1-xvalues[0])*(1-yvalues[1]))/2); break; case "TopBottom": pixelfraction = Math.min(xvalues[0],xvalues[1]) + ((Math.max(xvalues[0],xvalues[1]) - Math.min(xvalues[0],xvalues[1]))/2); break; case "LeftBottom": pixelfraction = (yvalues[0]*xvalues[1])/2; break; default: pixelfraction = 1;} return pixelfraction;} function rgb2Hex(rgbColour) { try{ var rgbArray = rgb2Array(rgbColour); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); var hexColour = "#" + IntToHex(red) + IntToHex(green) + IntToHex(blue);} catch(e){ alert("There was an error converting the RGB value to Hexadecimal in function rgb2Hex");} return hexColour;} function rgb2Array(rgbColour) { var rgbValues = rgbColour.substring(4, rgbColour.indexOf(")")); var rgbArray = rgbValues.split(", "); return rgbArray;} function setOpacity(obj, opacity) { opacity = (opacity == 100)?99.999:opacity; if(isSafari && obj.tagName != "IFRAME") { var rgbArray = rgb2Array(obj.style.backgroundColor); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); obj.style.backgroundColor = "rgba(" + red + ", " + green + ", " + blue + ", " + opacity/100 + ")";} else if(typeof(obj.style.opacity) != "undefined") { obj.style.opacity = opacity/100;} else if(typeof(obj.style.MozOpacity) != "undefined") { obj.style.MozOpacity = opacity/100;} else if(typeof(obj.style.filter) != "undefined") { obj.style.filter = "alpha(opacity:" + opacity + ")";} else if(typeof(obj.style.KHTMLOpacity) != "undefined") { obj.style.KHTMLOpacity = opacity/100;} } function inArray(array, value) { for(var i = 0; i < array.length; i++){ if (array[i] === value) return i;} return false;} function inArrayKey(array, value) { for(key in array){ if(key === value) return true;} return false;} function addEvent(elm, evType, fn, useCapture) { if (elm.addEventListener) { elm.addEventListener(evType, fn, useCapture); return true;} else if (elm.attachEvent) { var r = elm.attachEvent('on' + evType, fn); return r;} else { elm['on' + evType] = fn;} } function removeEvent(obj, evType, fn, useCapture){ if (obj.removeEventListener){ obj.removeEventListener(evType, fn, useCapture); return true;} else if (obj.detachEvent){ var r = obj.detachEvent("on"+evType, fn); return r;} else { alert("Handler could not be removed");} } function format_colour(colour) { var returnColour = "#ffffff"; if(colour != "" && colour != "transparent") { if(colour.substr(0, 3) == "rgb") { returnColour = rgb2Hex(colour);} else if(colour.length == 4) { returnColour = "#" + colour.substring(1, 2) + colour.substring(1, 2) + colour.substring(2, 3) + colour.substring(2, 3) + colour.substring(3, 4) + colour.substring(3, 4);} else { returnColour = colour;} } return returnColour;} function get_style(obj, property, propertyNS) { try { if(obj.currentStyle) { var returnVal = eval("obj.currentStyle." + property);} else { if(isSafari && obj.style.display == "none") { obj.style.display = ""; var wasHidden = true;} var returnVal = document.defaultView.getComputedStyle(obj, '').getPropertyValue(propertyNS); if(isSafari && wasHidden) { obj.style.display = "none";} } } catch(e) { } return returnVal;} function getElementsByClass(searchClass, node, tag) { var classElements = new Array(); if(node == null) node = document; if(tag == null) tag = '*'; var els = node.getElementsByTagName(tag); var elsLen = els.length; var pattern = new RegExp("(^|\s)"+searchClass+"(\s|$)"); for (i = 0, j = 0; i < elsLen; i++) { if(pattern.test(els[i].className)) { classElements[j] = els[i]; j++;} } return classElements;} function newCurvyError(errorMessage) { return new Error("curvyCorners Error:\n" + errorMessage) } chronicle-4.6/themes/blog.steve.org.uk/tip.gif0000644000175000017500000000010011557771306017554 0ustar skxskxGIF89a !,  ˜xINjz?_;chronicle-4.6/themes/blog.steve.org.uk/archive.inc0000644000175000017500000000062511557771306020421 0ustar skxskx

Archive

chronicle-4.6/themes/blog.steve.org.uk/comment-form.inc0000644000175000017500000000170611557771306021404 0ustar skxskx

Add A Comment

Your Name
Your Email
Your Comment

Your submission will be ignored if any field is left blank. But your email address will not be displayed.

chronicle-4.6/themes/blog.steve.org.uk/month.template0000644000175000017500000000660311557771306021171 0ustar skxskx <!-- tmpl_var name='blog_title' -->: entries from <!-- tmpl_var name='month_name' --> <!-- tmpl_var name='year' -->

Untitled Blog

Entries from .

comments Tags: ., No tags

Created by Chronicle v

chronicle-4.6/themes/blog.steve.org.uk/bubbles.css0000644000175000017500000000066111557771306020435 0ustar skxskx /* Normal Bubble */ div.bubble { width: auto; font-size: 0.75em; margin-bottom: 24px; } div.bubble blockquote { margin: 0px; padding: 0px; border: 1px solid #c9c2c1; background-color: #eee; } div.bubble blockquote p { margin: 10px; padding: 0px; } div.bubble cite { position: relative; margin: 0px; padding: 7px 0px 0px 15px; top: 6px; background: transparent url(tip.gif) no-repeat 20px 0; font-style: normal; } chronicle-4.6/README0000644000175000017500000001016111557771306012420 0ustar skxskx Homepage: http://www.steve.org.uk/Software/chronicle/ Mercurial Repository: http://chronicle.repository.steve.org.uk/ Sample Output: http://www.steve.org.uk/Software/chronicle/demo/ Real World Use: http://blog.steve.org.uk/ The chronicle blog compiler --------------------------- Chronicle is a tool which will convert a directory of simple text files into a static HTML weblog, (or blog if you prefer). The system is intentionally simple, but it does support: * Template based output. * Support for RSS feeds. * Support for tagged entries. * Support for forward-looking cross references. * Optional support for comments. Installation ------------ It is possible to run the software without installing it, just by placing blog entries in the ./blog directory and running ./bin/chronicle. However it is recommended you install the software system-wide by running "make install" as root. This will give you: /usr/share/chronicle <- The theme directories. /usr/bin/chronicle <- The main binary. /usr/bin/chronicle-entry-filter <- The script to convert input entries. /usr/bin/chronicle-spooler <- A simple helper. /etc/chroniclerc <- The global configuration file. If you wish to customise the templates it is recommended you make a copy of them with a new name, then edit that copy. This will prevent changes from being overwritten on upgrade. The configuration file may be copied to ~/.chroniclerc for per-user configuration. Blog Format ----------- The blog format is very simple. Each file should start like this: /-- title: The title of my post date: 12 August 2007 tags: foo, bar, baz The text of the actual entry goes here. However much there is of it. \-- The entry itself is prefixed by a small header, consisting of several pseudo-header fieilds. The header _MUST_ be separated from the body by at least a single empty line. Header values which are unknown are ignored, and no part of the header is included in the output which is generated. The following header values are recognised: Title: This holds the name of the post. ("Subject:" may be used as a synonym.) If neither "Title" or "Subject" are present the filename itself is used. Date: The date this entry was created. If not present the creation time of the file is used. Publish: If you make use of the spooler, to automatically post pre-written entries on particular days, this field will specify when an entry is made live. Tags: If any tags are present they will be used to categorise the entry. Xrefs: Optional comma seaparated list of filenames that this entry links to. For example, consider these two entries, named one.txt and two.txt: /-- one.txt title: The great debate date: 10 March 2010 tags: politics Our local candidate recently.... \-- /-- two.txt title: The debate continues date: 15 March 2010 tags: politics xrefs: one.txt As mentioned in my last entry, our local candidate... \-- This will add a link to two.txt to the bottom of one.txt, creating a convenient chain of links for readers to see a logical thread of posts, and allowing the author to update old popular posts with new thoughts without editing them. Entry Cutting ------------- If you wish you may truncate a long entry via the special tag, for example:. /--------------------------\ This is a line of text This is hidden So is this This is displayed. \==========================/ Or, with specific text: /-------------------------------------------------\ This is a line of text This is hidden So is this This is displayed. \=================================================/ Comment Support --------------- The system supports the submission of user-supplied comments upon posts, for more details please see the included file COMMENTS. Bugs? ----- Please report bugs to the author, where they will be fixed as quickly as possible. Steve -- chronicle-4.6/LICENSE0000644000175000017500000000115711557771306012552 0ustar skxskxCopyright (c) 2007-2010 by Steve Kemp. Addition contributions are listed in the supplied file AUTHORS. This program is free software; you can redistribute it and/or modify it under the terms of either: a) the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version, or b) the "Artistic License" which comes with Perl. On Debian GNU/Linux systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL' and the Artistic Licence in `/usr/share/common-licenses/Artistic'. chronicle-4.6/cgi-bin/0000755000175000017500000000000011557771306013051 5ustar skxskxchronicle-4.6/cgi-bin/comments.cgi0000755000175000017500000001016511557771306015370 0ustar skxskx#!/usr/bin/perl -w # # This is a simple script which is designed to accept comment requests, # and save the details to local text files upon the localhost. # # This code is very simple and should be easy to extend with anti-spam # at a later point. # # ### # # NOTE: If you wish to use this you must edit three things at the # top of the script. # # 1. The directory to save the comment data to. # # 2. The email address to notify. # # 3. The email address to use as the sender. # #### # # Steve # -- # use strict; use warnings; use CGI; use POSIX qw(strftime); # # The directory to store comments in. # # NOTE: This should be writeable to the www-data user, and shouldn't # be inside your web-root - or you open up security hole. # # my $COMMENT = "/home/www/comments/"; # my $COMMENT = $ENV{ 'DOCUMENT_ROOT' } . "../comments/"; # # The notification addresses - leave blank to disable # my $TO = 'weblog@steve.org.uk'; my $FROM = 'weblog@steve.org.uk'; # # Use textile? # my $TEXTILE = 1; # # Find sendmail # my $SENDMAIL = undef; foreach my $file (qw ! /usr/lib/sendmail /usr/sbin/sendmail !) { $SENDMAIL = $file if ( -x $file ); } # # Get the parameters from the request. # my $cgi = new CGI(); my $name = $cgi->param('name') || undef; my $mail = $cgi->param('mail') || undef; my $body = $cgi->param('body') || undef; my $id = $cgi->param('id') || undef; my $cap = $cgi->param('captcha') || undef; my $link = $cgi->param('link') || undef; my $ajax = $cgi->param("ajax") || 0; # # If any are missing just redirect back to the blog homepage. # if ( !defined($name) || !length($name) || !defined($mail) || !length($mail) || !defined($body) || !length($body) || !defined($id) || !length($id) ) { if ($ajax) { print "Content-type: text/html\n\n"; print "

Some of the files were empty; please try again.\n"; } else { print "Location: http://" . $ENV{ 'HTTP_HOST' } . "/\n\n"; } exit; } # # Does the captcha value contain text? If so spam. # if ( defined($cap) && length($cap) ) { if ($ajax) { print "Content-type: text/html\n\n"; print "Missing fields.\n"; } else { print "Location: http://" . $ENV{ 'HTTP_HOST' } . "/\n\n"; } exit; } # # Convert the message to HTML if textile is in use # if ($TEXTILE) { # # If we can load the module # my $test = "use Text::Textile;"; eval($test); # # There were no errors # if ( !$@ ) { # # Convert # my $textile = new Text::Textile; $body = $textile->process($body); } } # # Otherwise save them away. # # # ID. # if ( $id =~ /^(.*)[\/\\](.*)$/ ) { $id = $2; } # # Show the header # print "Content-type: text/html\n\n"; # # get the current time # my $timestr = strftime "%e-%B-%Y-%H:%M:%S", gmtime; # # Open the file. # my $file = $COMMENT . "/" . $id . "." . $timestr; $file =~ s/[^a-z0-9\/]/_/gi; open( FILE, ">", $file ); print FILE "Name: $name\n"; print FILE "Mail: $mail\n"; print FILE "User-Agent: $ENV{'HTTP_USER_AGENT'}\n"; print FILE "IP-Address: $ENV{'REMOTE_ADDR'}\n"; print FILE "Link: $link\n" if ($link); print FILE "\n"; print FILE $body; close(FILE); # # Send a mail. # if ( length($TO) && length($FROM) && defined($SENDMAIL) ) { open( SENDMAIL, "|$SENDMAIL -t -f $FROM" ); print SENDMAIL "To: $TO\n"; print SENDMAIL "From: $FROM\n"; print SENDMAIL "Subject: New Comment [$id]\n"; print SENDMAIL "\n\n"; print SENDMAIL $body; close(SENDMAIL); } # # Now show the user the thanks message.. # if ( $cgi->param("ajax") ) { print <Comment Submitted

Thanks for your comment, it will be made live when the queue is moderated next.

EOF exit; } else { print < Thanks For Your Comment

Thanks!

Your comment will be included the next time this blog is rebuilt.

Return to blog.

EOF } chronicle-4.6/bin/0000755000175000017500000000000011557771306012311 5ustar skxskxchronicle-4.6/bin/chronicle-spooler0000755000175000017500000002116511557771306015673 0ustar skxskx#!/usr/bin/perl -w =head1 NAME chronicle-spooler - Automatically post pre-written entries. =cut =head1 SYNOPSIS Path Options: --config Specify a configuration file to read. --spool-dir Specify where pending entries are located. --live-dir Specify where the entries should be moved to. Post-Spool Commands: --post-move Specify a command to execute once entries have been moved. Optional Features: --test Only report on what would be executed. Help Options: --help Show the help information for this script. --manual Read the manual for this script. =cut =head1 ABOUT chronicle-spooler is a companion scrip to the chronicle blog compiler. It is designed to facilitate posting new entries automatically upon particular dates. (ie. If you have ten written blog entries in a spool directory it will move them into place upon the date you've specified.) =cut =head1 DATE SPECIFICATION To specify the date a particular entry should be made live you must add another pseudo-header to your blog entry files, as follows: =for example begin Title: This is the title of the blog post Date: 2nd March 2007 Publish: 15th April 2008 Tags: one, two, three, long tag The text of your entry goes here. =for example end In this example we know that this entry will be made live upon the 15th April 2008, and not before. =cut =head1 AUTHOR Steve -- http://www.steve.org.uk/ =cut =head1 LICENSE Copyright (c) 2008-2010 by Steve Kemp. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The LICENSE file contains the full text of the license. =cut use strict; use warnings; use Date::Parse; use File::Copy; use Getopt::Long; use Pod::Usage; # # Release number # # NOTE: Set by 'make release'. # my $RELEASE = '4.6'; # # Our configuration options. # my %CONFIG; # # Read the global and per-user configuration files, if they exist. # readConfigurationFile("/etc/chroniclerc"); readConfigurationFile( $ENV{ 'HOME' } . "/.chroniclerc" ); # # Parse the command line arguments. # parseCommandLineArguments(); # # Another configuration file? # readConfigurationFile( $CONFIG{ 'config' } ) if ( defined $CONFIG{ 'config' } ); # # Make sure we have arguments which are sane. # # Specifically we need an input directory and an output directory. # # sanityCheckArguments(); # # Find the potentially pending entries. # my @files = findPendingPosts( $CONFIG{ 'spool-dir' } ); # # Process each entry # my $live = 0; foreach my $entry ( sort(@files) ) { if ( shouldBeLive($entry) ) { if ( $CONFIG{ 'test' } ) { print "test: make entry live: $entry\n"; } else { makeEntryLive($entry); $live += 1; } } } # # If we should run our command do so. # if ( ( $CONFIG{ 'post-move' } ) && $live ) { if ( $CONFIG{ 'test' } ) { print "test: should run: $CONFIG{'post-move'}\n"; } else { system( $CONFIG{ 'post-move' } ); } } # # All done. # exit; =begin doc Parse the command line arguments this script was given. =end doc =cut sub parseCommandLineArguments { my $HELP = 0; my $MANUAL = 0; my $VERSION = 0; # # Parse options. # if ( !GetOptions( # input / output "spool-dir=s", \$CONFIG{ 'spool-dir' }, "live-dir=s", \$CONFIG{ 'live-dir' }, # testing? "test", \$CONFIG{ 'test' }, # command? "post-move=s", \$CONFIG{ 'post-move' }, # Help options "help", \$HELP, "manual", \$MANUAL, "verbose", \$CONFIG{ 'verbose' }, "version", \$VERSION, ) ) { exit; } pod2usage(1) if $HELP; pod2usage( -verbose => 2 ) if $MANUAL; if ($VERSION) { print("chronicle release $RELEASE\n"); exit; } } =begin doc Read the specified configuration file if it exists. =end doc =cut sub readConfigurationFile { my ($file) = (@_); # # If it doesn't exist ignore it. # return if ( !-e $file ); my $line = ""; open my $handle, "<", $file or die "Cannot read file '$file' - $!"; while ( defined( $line = <$handle> ) ) { chomp $line; if ( $line =~ s/\\$// ) { $line .= ; redo unless eof(FILE); } # Skip lines beginning with comments next if ( $line =~ /^([ \t]*)\#/ ); # Skip blank lines next if ( length($line) < 1 ); # Strip trailing comments. if ( $line =~ /(.*)\#(.*)/ ) { $line = $1; } # Find variable settings if ( $line =~ /([^=]+)=([^\n]+)/ ) { my $key = $1; my $val = $2; # Strip leading and trailing whitespace. $key =~ s/^\s+//; $key =~ s/\s+$//; $val =~ s/^\s+//; $val =~ s/\s+$//; # command expansion? if ( $val =~ /(.*)`([^`]+)`(.*)/ ) { # store my $pre = $1; my $cmd = $2; my $post = $3; # get output my $output = `$cmd`; chomp($output); # build up replacement. $val = $pre . $output . $post; } # Store value. $CONFIG{ $key } = $val; } } close($handle); } =begin doc Sanity check our arguments, and setup to make sure there is nothing obviously broken. =end doc =cut sub sanityCheckArguments { if ( ( !$CONFIG{ 'spool-dir' } ) || ( !-d $CONFIG{ 'spool-dir' } ) ) { print <) { if ( ( $line =~ /^Publish:(.*)/i ) && ( !length($header) ) ) { $header = $1; # Strip leading and trailing whitespace. $header =~ s/^\s+//; $header =~ s/\s+$//; } } close($handle); # # No header? Not to be published # return 0 if ( length($header) < 1 ); # # OK we got a header - is it current / past? # my $today = time; if ( !defined($today) ) { print "FAILED TO FIND TODAY\n"; return 0; } # # Date of entry # my $ent = str2time($header); if ( !defined($ent) ) { print "FAILED TO PARSE: '$header'\n"; return 0; } # # Do the date test. # if ( $ent < $today ) { return 1; } else { return 0; } } =begin doc Move the specified file into our "live" directory. =end doc =cut sub makeEntryLive { my ($file) = (@_); if ( -d $CONFIG{ 'live-dir' } ) { # # Is there already a file there with that name? # # If so don't truncate it. # my $dir = $file; my $base = $file; if ( $base =~ /^(.*)[\\\/](.*)$/ ) { $dir = $1; $base = $2; } while ( -e "$CONFIG{'live-dir'}/$base" ) { $base = "x$base"; } # # Moving # File::Copy::move( $file, $CONFIG{ 'live-dir' } . "/" . $base ); } else { print "Weirdness $CONFIG{'live-dir'} is not a directory!\n"; exit; } } chronicle-4.6/bin/check-titles0000755000175000017500000000300411557771306014613 0ustar skxskx#!/usr/bin/perl -w # # A simple utility which will process each "*.txt" file inside a named # directory. # # Any entries with a duplicate title will be alerted. # # Steve # use strict; use warnings; # # Get the directory and ensure it exists. # my $dir = shift; die "Usage: $0 directory" unless ( defined($dir) ); die "Not a directory : $dir" unless ( -d $dir ); # # The hash of arrays # my $titles; # # Find the files. # foreach my $file ( sort( glob( $dir . "/*" ) ) ) { # # Skip non-files # next if ( -d $file ); next if ( $file =~ /~$/ ); # # Title for this entry. # my $title = undef; # # Read each file, and look for the title. # open my $handle, "<", $file or die "Failed to read $file - $!"; foreach my $line (<$handle>) { $title = $2 if ( !defined($title) && ( $line =~ /^(Subject|Title):(.*)/i ) ); } close($handle); # # Ignore entries with no title. # next if ( !$title ); # # Get current entries we might have found with # this title; and add on the new one. # my $a = $titles->{ $title }; push( @$a, $file ); $titles->{ $title } = $a; } # # Now look for dupes # my $fail = 0; foreach my $title ( keys %$titles ) { my $a = $titles->{ $title }; if ( ( scalar @$a ) > 1 ) { print "Title: $title\n"; foreach my $file (@$a) { print "\t" . $file . "\n"; } $fail = 1; } } exit($fail); chronicle-4.6/bin/chronicle-ping0000755000175000017500000001152011557771306015137 0ustar skxskx#!/usr/bin/perl -w =head1 NAME chronicle-ping - Send outgoing PING requests for new pages. =cut =head1 SYNOPSIS Help Options --help Show a brief help overview. --version Show the version of this script. Mandatory Options --url The URL of the entry to ping. --service The URL of the service to ping. Misc Options --name The name of the blog. =cut =head1 ABOUT This script is designed to receive an URL as its argument, and make an outgoing ping for it. If a service URL (pointing to an XML-RPC end-point) is specified it will be used, otherwise the ping will be made to a number of default sites. =cut =head1 AUTHOR Steve -- http://www.steve.org.uk/ =cut =head1 LICENSE Copyright (c) 2010 by Steve Kemp. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The LICENSE file contains the full text of the license. =cut use strict; use warnings; use Getopt::Long; use Pod::Usage; use XMLRPC::Lite; # # The default sites to ping if no service is specified. # my %default = ( # See http://codex.wordpress.org/Update_Services for # a more comprehensive list. 'Blog People' => 'http://www.blogpeople.net/servlet/weblogUpdates', 'Blog Update' => 'http://blogupdate.org/ping/', 'BlogRolling' => 'http://rpc.blogrolling.com/pinger/', 'Google' => 'http://blogsearch.google.com/ping/RPC2', 'Moreover' => 'http://api.moreover.com/RPC2', 'NewsGator' => 'http://services.newsgator.com/ngws/xmlrpcping.aspx', 'Ping-o-Matic!' => 'http://rpc.pingomatic.com/', 'Syndic8' => 'http://ping.syndic8.com/xmlrpc.php', 'Weblogs.com' => 'http://rpc.weblogs.com/RPC2', ); # # Parse command line arguments # my %CONFIG = parseCommandLineArguments(); # # Validate and update our arguments # validateOptions(); # # Perform the ping # ping( $CONFIG{ 'service' } ); =begin doc Parse the command line options we expect to receive. =end doc =cut sub parseCommandLineArguments { my $HELP = 0; my $MANUAL = 0; my %options; if ( !GetOptions( # help options "help", \$HELP, "manual", \$MANUAL, "verbose", \$options{ 'verbose' }, # mandatory options "url=s", \$options{ 'url' }, "service=s", \$options{ 'service' }, # misc "name=s", \$options{ 'name' }, ) ) { exit 1; } pod2usage(1) if $HELP; pod2usage( -verbose => 2 ) if $MANUAL; return (%options); } =begin doc Ensure our options look sane. =end doc =cut sub validateOptions { # # Ensure we received the mandatory arguments # if ( !defined( $CONFIG{ 'url' } ) ) { print "Missing argument --url\n"; exit 1; } # # If we don't have a title use the name # if ( !$CONFIG{ 'name' } ) { $CONFIG{ 'name' } = $CONFIG{ 'url' }; } # # If we have a service make sure it looks like an URL # if ( ( $CONFIG{ 'service' } ) && ( $CONFIG{ 'service' } !~ /^https?:\/\// ) ) { print "Your ping service looks unlike an URL\n"; exit 1; } } =begin doc Send a ping for the given service, reverting to all our known ping sites if one isn't specified. =end doc =cut sub ping { my ($service) = (@_); # # If we don't have a service specified then use each of our # default ones in turn. # if ( !defined($service) ) { foreach my $name ( keys %default ) { $CONFIG{ 'verbose' } && print "Sending ping via $name\n"; send_ping( $default{ $name }, $CONFIG{ 'url' } ); } } else { $CONFIG{ 'verbose' } && print "Sending ping via $service\n"; send_ping( $service, $CONFIG{ 'url' } ); } } =begin doc Send a ping for the given URL to the specified RPC end-point. Report errors liberally. =end doc =cut sub send_ping { my ( $rpc, $url ) = (@_); print "Sending to $rpc\n"; my $xmlrpc = XMLRPC::Lite->proxy($rpc); my $call; eval { $call = $xmlrpc->call( 'weblogUpdates.ping', $CONFIG{ 'name' }, $url ); }; if ($@) { chomp $@; warn "\tPing to $url failed: '$@'\n"; next; } unless ( defined $call ) { warn "\tPing $url failed for an unknown reason\n"; next; } if ( $call->fault ) { chomp( my $message = $call->faultstring ); warn "\tPing to $url failed: '$message'\n"; next; } my $result = $call->result; if ( $result->{ flerror } ) { warn "\tPing to $url returned the following error: '", $result->{ message }, "'\n"; next; } print "\tPing $url returned: '$result->{ message }'\n"; } chronicle-4.6/bin/chronicle-entry-filter0000755000175000017500000002531511557771306016635 0ustar skxskx#!/usr/bin/perl -w =head1 NAME chronicle-entry-filter - Convert blog files to HTML, if required. =cut =head1 SYNOPSIS Help Options --help Show a brief help overview. --version Show the version of this script. Options --format The global format of all entries. --filename The name of the single file to process. Filters --pre-filter A filter to run before convertion to HTML. --post-filter A filter to run after HTML conversion. =cut =head1 ABOUT This script is designed to receive a filename and a global formatting type upon the command line. The formatting type specifies how the blog entry file will be processed: 1. If the format is "textile" the file will be converted from textile to HTML. 2. If the format is "markdown" the file will be converted from markdown to HTML. The related format "multimarkdown" is also recognised. 3. If the format is "html" no changes will be made. Once the conversion has been applied the code will also be scanned for tags to expand via the B module, if it is installed, which allows the pretty-printing of source code. To enable the syntax highlighting of code fragments you should format your code samples as follows: =for example begin Subject: Some highlighted code. Date: 25th December 2009 Tags: chronicle, perl, blah

Here is some code which will look pretty ..

#!/usr/bin/perl -w ... .. =for example end Notice the use of lang="perl", which provides a hint as to the type of syntax highlighting to apply. Additionally you may make use of the pre-filter and post-filter pseudo-headers which allow you to transform the entry in further creative fashions. For example you might wish the blog to be upper-case only for some reason, and this could be achieved via: =for example begin Subject: I DONT LIKE LOWER CASE Tags: meta, random, silly Date: 25th December 2009 Pre-Filter: perl -pi -e "s/__USER__/`whoami`/g" Post-filter: tr [a-z] [A-Z]

This post, written by __USER__ will have no lower-case values.

Notice how my username was inserted too?

=for example end You may chain arbitrarily complex filters together via the filters. Each filter should read the entry on STDIN and return the updated content to STDOUT. (If you wish to apply a global filter simply pass that as an argument to chronicle, or in your chroniclerc file.) =cut =head1 AUTHOR Steve -- http://www.steve.org.uk/ =cut =head1 LICENSE Copyright (c) 2009-2010 by Steve Kemp. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The LICENSE file contains the full text of the license. =cut use strict; use warnings; use Getopt::Long; use IPC::Open2; use Pod::Usage; use Symbol; # # Release number # # NOTE: Set by 'make release'. # my $RELEASE = '4.6'; # # Dispatch table of input type converters. # # Each entry will have (up to) the following two keys: # # module => Any optional modules required - multiple comma-separated # values are permissable. # # routine => The routine to convert the input to the HTML output. # my %dispatch = ( "html" => { routine => \&do_html, }, "markdown" => { module => "Text::Markdown", routine => \&do_markdown, }, "multimarkdown" => { module => "Text::MultiMarkdown", routine => \&do_multimarkdown, }, "textile" => { module => "Text::Textile", routine => \&do_textile, } ); # # Parse the command line options. # my %CONFIG = parseCommandLineArguments(); # # If we don't have a filename then it is game over. # if ( !$CONFIG{ 'filename' } ) { print "Mandatory filename missing: Help?!\n"; exit 1; } # # Read the input from the file # my ( $text, %headers ) = readInputFile( $CONFIG{ 'filename' } ); # # Pre-filter? # my $pre = $CONFIG{ 'pre-filter' } || $headers{ 'pre-filter' } || undef; if ( defined($pre) ) { $text = runFilter( $pre, $text ); } # # At this point we need to work out how to format the entry. # # We might have (in order of precedence): # # a. A per-entry format # b. A global format. # c. The default format (html) # my $format = $headers{ 'format' } || $CONFIG{ 'format' } || "html"; # # Lookup details to use in the dispatch table. # my $obj = $dispatch{ lc $format }; if ( !$obj ) { print "The input method $format is unknown.\n"; exit 1; } # # Do we have to load an optional module? # if ( $obj->{ 'module' } ) { loadOptionalModules( $obj->{ 'module' }, $format ); } # # Now convert # my $html = $obj->{ 'routine' }->($text); # # Do code formatting # $html =~ s{(.*?)()} {"" . highlightCode($2, $1) . $3}msegi; # # Post-filter? # my $post = $CONFIG{ 'post-filter' } || $headers{ 'post-filter' } || undef; if ( defined($post) ) { $html = runFilter( $post, $html ); } # # Finally output the result such that chronicle can include it # in the blog. # # Ensure we're UTF-8 clean. # binmode STDOUT, ":utf8"; print $html; # # All over :) # exit 0; =begin doc Parse the two command line options we expect to receive. TODO: Add help/version/manual/etc =end doc =cut sub parseCommandLineArguments { my $HELP = 0; my $MANUAL = 0; my $VERSION = 0; my %options; if ( !GetOptions( # help options "help", \$HELP, "manual", \$MANUAL, "verbose", \$options{ 'verbose' }, "version", \$VERSION, # filename "filename=s", \$options{ 'filename' }, # global format "format=s", \$options{ 'format' }, # filters "pre-filter=s", \$options{ 'pre-filter' }, "post-filter=s", \$options{ 'post-filter' }, ) ) { exit 1; } pod2usage(1) if $HELP; pod2usage( -verbose => 2 ) if $MANUAL; if ($VERSION) { print("chronicle-entry-filter release $RELEASE\n"); exit; } return (%options); } =begin doc Read the specified blog file, and return both the input format and the body of the file. Ignore all other header values. =end doc =cut sub readInputFile { my ($filename) = (@_); # # Open the specified file. # open my $handle, "<:utf8", $filename or die "Failed to open file\n"; # # Parse the header and body into these values # my %headers; my $body; # # Read the file. # my $header = 1; foreach my $line (<$handle>) { if ($header) { # # If the header is "foo:bar" then record that # if ( $line =~ /^([^:]+):(.*)/ ) { my $key = $1; my $val = $2; $key = lc($key); $val =~ s/^\s+|\s+$//g; $headers{ $key } = $val if ( length($val) && !$headers{ $key } ); } # # End of the header? # # NOTE: Slight hack for working under Cygwin on # Microsoft Windows where \r and \n roam wild. # $header = 0 if ( $line =~ /^([\r|\n]*)$/ ); } else { $body .= $line; } } close($handle); return ( $body, %headers ); } =begin doc Run the text we've got through the specified command. The command will receive the text on STDIN and should return the (potentially modified) text to STDOUT. =end doc =cut sub runFilter { my ( $cmd, $text ) = (@_); my $WTR = gensym(); my $RDR = gensym(); $CONFIG{ 'verbose' } && print "Running filter: $cmd\n"; my $pid = open2( $RDR, $WTR, $cmd ); print $WTR $text; close($WTR); my $result = ""; while (<$RDR>) { $result .= $_; } waitpid $pid, 0; return $result; } =begin doc Load an optional module. =end doc =cut sub loadOptionalModules { my ( $module, $format ) = (@_); foreach my $mod ( split( /,/, $module ) ) { # # Strip space, and empty modules # $mod =~ s/^\s+|\s+$//g; next if ( !length($mod) ); # # Make sure we have the module installed. Use eval to # avoid making this mandatory. # my $test = "use $mod;"; # # Test loading the module. # ## no critic (Eval) eval($test); ## use critic if ($@) { my $package = "lib" . lc($mod) . "-perl"; $package =~ s/::/-/g; print <charset( $CONFIG{ 'charset' } ); } # # Now return HTML # my $html = $textile->process($text); return ($html); } =begin doc Attempt to highlight the given text with the given language bindings. Note that this relies upon Text::VimColor... =end doc =cut sub highlightCode { my ( $text, $lang ) = (@_); # # Make sure we have the Text::VimColor module installed. Use eval to # avoid making this mandatory. # my $test = "use Text::VimColor;"; # # Test loading the module. # ## no critic (Eval) eval($test); ## use critic # # If there was an error then we'll ignore the highlighting. # if ($@) { return $text; } my $syntax = Text::VimColor->new( string => $text, filetype => $lang, stylesheet => 1, ); return ( $syntax->html ); } chronicle-4.6/bin/chronicle0000755000175000017500000021470011557771306014211 0ustar skxskx#!/usr/bin/perl -w =head1 NAME chronicle - A simple blog compiler. =cut =head1 SYNOPSIS chronicle [options] Path Options: --comments Specify the path to the optional comments directory. --config Specify a configuration file to read. --input Specify the input directory to use. --output Specify the directory to write output to. --theme-dir Specify the path to the theme templates. --theme Specify the theme to use. --pattern Specify the pattern of files to work with. --url-prefix Specify the prefix to the live blog. --sitemap-prefix Specify the prefix for the site map. Blog Entry Options: --format Specify the format of your entries, HTML/textile/markdown. Pre & Post-Build Commands: --pre-build Specify a command to execute prior to building the blog. --post-build Specify a command to execute once the blog has been built. --pre-filter A command to filter each blog entry before HTML conversion. --post-filter A command to filter each blog entry after HTML conversion. Sorting Options: --recent-dates-first Show recent entries first in the archive view. --recent-tags-first Show recent entries first in the tag view. Counting Options: --entry-count=N Number of posts to show on the index. --rss-count=N Number of posts to include on the RSS index feed. Optional Features: --author Specify the author's email address --comment-days Specify the number maximum age of posts to accept comments. --date-archive-path Include the date in the archive. --force Force the copying of static files from the blog theme. --lang Specify the language to use for formatting dates. --lower-case Lower-case all filenames which are output. --no-archive Don't create an archive page. --no-cache Don't use the optional memcached features, even if available. --no-calendar Don't use the optional calendar upon the index. --no-comments Don't allow comments to be posted. --no-sitemap Don't generate a sitemap. --no-tags Don't produce any tag pages. --no-xrefs Don't produce any cross references. Help Options: --help Show the help information for this script. --manual Read the manual for this script. --verbose Show useful debugging information. --version Show the version number and exit. =cut =head1 ABOUT Chronicle is a simple tool to convert a collection of text files, located within a single directory, into a blog consisting of static HTML files. It supports only the bare minimum of features which are required to be useful: * Tagging support. * RSS support. * Archive support. The obvious deficiencies are: * Lack of support for instant commenting. * Lack of pingback/trackback support. Having said that it is a robust, stable, and useful system. =cut =head1 BLOG FORMAT The format of the text files we process is critical to the output pages. Each entry should look something like this: =for example begin Title: This is the title of the blog post Date: 2nd March 2007 Tags: one, two, three, long tag The text of your entry goes here. =for example end NOTE: The header MUST be separated from the body of the entry by at least a single empty line. In this example we can see that the entry itself has been prefaced with a small header. An entry header is contains three optional lines, if these are not present then there are sensible defaults as described below. The formatting of the output dates may be changed via the use of the B<--lang> command line option (or the matching "lang=french" option in the configuration file), but the date of the entry itself should be specified in English. =over 8 =item Title: Describes the title of the post. If not present the filename of the entry is used instead. "Subject:" may also be used. =item Subject: This is a synonym for 'Title:'. =item Date: The date the post was written. If not present the creation time of the file is used instead. =item Publish: This header is removed from all entries, and is used by the chronicle-spooler script. =item Tags: Any tags which should be associated with the entry, separated by commas. =back The format of the entry is assumed to be HTML, however there is support for writing your entries in both textile and markdown formats. The format of entries is specified via the B<--format> argument, or via a "format: foo" setting in your chroniclerc file. The format of entries is assumed to be global; that is all your entries will be assumed to be in the same format. However you can add a "format: foo" pseudo header to specific entries if you wish to write specific entries in a different format. To allow flexibility in the handling of entries each blog entry will be passed through the filter script B which allows you to modify this handling in a single location. This script allows entries to be updated via filters both before and after the conversion to HTML. For further details please see the manpage for that script. =cut =head1 CONFIGURATION The configuration of the software is minimal, and generally performed via the command line arguments. However it is possible to save settings either in the file global /etc/chroniclerc or the per-user ~/.chroniclerc file. If you wish you may pass the name of another configuration file to the script with the B<--config> flag. This will be read after the previous two files, and may override any settings which are present. The configuration file contains lines like these: =for example begin input = /home/me/blog output = /var/www/blog format = markdown =for example end Keys which are unknown are ignored. =cut =head1 OPTIONAL CACHING To speed the rebuilding of a large blog the compiler may use a local Memcached daemon, if installed and available. To install this, under a Debian GNU/Linux system please run: =for example begin apt-get update apt-get install memcached libcache-memcached-perl =for example end You may disable this caching behaviour with --no-cache, and see the effect with --verbose. =cut =head1 OPTIONAL CALENDAR If the 'HTML::CalendarMonthSimple' module is available each blog will contain a simple month-view of the current month upon the index. To disable this invoke the program with '--no-calendar'. =cut =head1 OPTIONAL COMMENTING Included with the chronicle code you should find the file cgi-bin/comments.cgi. This file is designed to write submitted comments to the local filesystem of your web-server. If you install that, and edit the path at the start of the script you should be able to include comments in your blog. In short there are three things you need to do: =over 8 =item Install the CGI script and edit the path at the start. =item Copy the output comments to your local blog source. =item Run this script again with --comments=./path/to/comments =back This should include the comments in the static output. More explicit instructions are provided within the file 'COMMENTS' included within the distribution. =cut =head1 AUTHOR Steve -- http://www.steve.org.uk/ =cut =head1 LICENSE Copyright (c) 2007-2010 by Steve Kemp. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The LICENSE file contains the full text of the license. =cut use strict; use warnings; use Date::Format; use Date::Language; use Date::Parse; use Digest::MD5 qw(md5_hex); use Encode; use File::Basename; use File::Copy; use File::Path; use Getopt::Long; use HTML::Template; use Pod::Usage; use Time::Local; # # Release number # # NOTE: Set by 'make release'. # my $RELEASE = '4.6'; # # Setup default options. # my %CONFIG = setupDefaultOptions(); # # Know when we are running, so that we can tell the difference between # cached data and data this process generated. # my $RUNNING_TIME = time(); # # Read the global and per-user configuration files, if they exist. # readConfigurationFile("/etc/chroniclerc"); readConfigurationFile( $ENV{ 'HOME' } . "/.chroniclerc" ); # # Parse the command line arguments. # parseCommandLineArguments(); # # Another configuration file? # readConfigurationFile( $CONFIG{ 'config' } ) if ( defined $CONFIG{ 'config' } ); # # Make sure we have arguments which are sane. # # Specifically we want to cope with the "new", "theme-dir", and "theme" # arguments. # # sanityCheckArguments(); # # Setup the cache if we can. # setupCache() unless ( $CONFIG{ 'no-cache' } ); # # The names of the months we'll use. # my @names = getMonthNames(); # # Listing themes? # if ( $CONFIG{ 'list-themes' } ) { listThemes( $CONFIG{ 'theme-dir' } ); exit; } # # Should we run something before we start? # if ( $CONFIG{ 'pre-build' } ) { $CONFIG{ 'verbose' } && print "Running command: $CONFIG{'pre-build'}\n"; system( $CONFIG{ 'pre-build' } ); } # # Parse each of the given text files, and build up a data-structure # we can use to create our pages. # # The data-structure is a hash of arrays. The hash key is the blog # entry's filename, and the array stored as the hash's value has # keys such as: # # tags => [ 'test', 'testing' ] # date => '1st july 2007' # title => 'Some title' # # my %data = createDataStructure(); # # Find all cross references and build a hash of them. This hash # contains arrays of filenames that the key is linked by. i.e. each # filename in the array refers to the key filename. # my %all_xrefs; %all_xrefs = findAllXRefs() unless ( $CONFIG{ 'no-xrefs' } ); # # Include cross-reference data, unless disabled # if ( !$CONFIG{ 'no-xrefs' } ) { buildXRefPairs(); } # # Find each unique tag used within our entries. # my %all_tags; %all_tags = findAllTags() unless ( $CONFIG{ 'no-tags' } ); # # Find each unique month + year we've used. # my %all_dates; %all_dates = findAllMonths() unless ( $CONFIG{ 'no-archive' } ); # # Now create the global tag + date loops which are used for our # sidebar. # my %CLOUD; $CLOUD{ 'tag' } = createTagCloud(%all_tags) unless ( $CONFIG{ 'no-tags' } ); $CLOUD{ 'archive' } = createDateCloud(%all_dates) unless ( $CONFIG{ 'no-archive' } ); # # Create the output directories. # mkpath( $CONFIG{ 'output' }, 0, oct(755) ) if ( !-d $CONFIG{ 'output' } ); foreach my $tag ( keys %all_tags ) { mkpath( "$CONFIG{'output'}/tags/$tag", 0, oct(755) ); } foreach my $date ( keys %all_dates ) { next unless ( $date =~ /^([0-9]{4})-([0-9]{2})/ ); mkpath( "$CONFIG{'output'}/archive/$1/$2", 0, oct(755) ); } # # Output each static page. # $CONFIG{ 'verbose' } && print "Creating static pages:\n"; foreach my $file ( keys %data ) { outputStaticPage($file); } # # Build an output page for every tag which has ever been used. # foreach my $tagName ( sort keys %all_tags ) { outputTagPage($tagName); } # # Now build the archive pages. # foreach my $date ( keys(%all_dates) ) { outputArchivePage($date); } # # Finally out the most recent entries for the front-page. # outputIndexPage(); # # Copy any static files into place. # copyStaticFiles(); # # Output the static sitemap unless: # if ( !$CONFIG{ 'no-sitemap' } ) { $CONFIG{ 'verbose' } && print "Outputing sitemap\n"; outputSiteMap(); $CONFIG{ 'verbose' } && print "Done.\n"; } # # Post-build command? # if ( $CONFIG{ 'post-build' } ) { $CONFIG{ 'verbose' } && print "Running command: $CONFIG{'post-build'}\n"; system( $CONFIG{ 'post-build' } ); } # # Sending a ping? # if ( $CONFIG{ 'send-ping' } ) { $CONFIG{ 'verbose' } && print "Preparing to send pings\n"; # # Do we have a list of sites to send to? # my $count = 1; my $sent = 0; while ( defined $CONFIG{ 'ping-server-$count' } ) { my $ping = $CONFIG{ 'ping-server-$count' }; $CONFIG{ 'verbose' } && print "Sending to: $ping\n"; # # Send to just that specific service # system( "chronicle-ping", "--service=$ping", "--url=$CONFIG{'prefix'}", "--name=$CONFIG{'blog-title'}" ); # find next server $count += 1; # we've sent something now. $sent += 1; } # # If we didn't send then we're going to use the default servers. # if ( !$sent ) { $CONFIG{ 'verbose' } && print "Sending to: default ping servers\n"; system( "chronicle-ping", "--url=$CONFIG{'prefix'}", "--name=$CONFIG{'blog-title'}" ); } $CONFIG{ 'verbose' } && print "Pings complete.\n"; } # # All done. # exit; =begin doc Setup the default options we'd expect into our global configuration hash. =end doc =cut sub setupDefaultOptions { my %CONFIG; # # Text directory. # $CONFIG{ 'input' } = "./blog"; $CONFIG{ 'comments' } = ''; # # Output directory. # $CONFIG{ 'output' } = "./output"; # # Theme setup # $CONFIG{ 'theme-dir' } = "./themes/"; $CONFIG{ 'theme' } = "default"; # # prefix for all links. # $CONFIG{ 'url-prefix' } = ""; # # Default input format # $CONFIG{ 'format' } = 'html'; # # Entries per-page for the index. # $CONFIG{ 'entry-count' } = 10; # # Entries for the RSS index count. # $CONFIG{ 'rss-count' } = 10; # # Don't overwrite files by default # $CONFIG{ 'force' } = 0; # # UTF-8 for textile # $CONFIG{ 'charset' } = 'utf-8'; # # Comments enabled globally. # $CONFIG{ 'comment-days' } = 0; # # No output prefix -- static pages in top level dir # $CONFIG{ 'date-archive-path' } = 0; # # Default to making a sitemap # $CONFIG{ 'no-sitemap' } = 0; # # English language by default # $CONFIG{ 'lang' } = 'english'; # # English date format by default # $CONFIG{ 'date-format' } = '%o %B %Y'; return (%CONFIG); } =begin doc Parse the command line arguments this script was given. =end doc =cut sub parseCommandLineArguments { my $HELP = 0; my $MANUAL = 0; my $VERSION = 0; # # Parse options. # if ( !GetOptions( # Help options "help", \$HELP, "manual", \$MANUAL, "verbose", \$CONFIG{ 'verbose' }, "version", \$VERSION, "list-themes", \$CONFIG{ 'list-themes' }, # paths "comments=s", \$CONFIG{ 'comments' }, "config=s", \$CONFIG{ 'config' }, "input=s", \$CONFIG{ 'input' }, "output=s", \$CONFIG{ 'output' }, "theme-dir=s", \$CONFIG{ 'theme-dir' }, "theme=s", \$CONFIG{ 'theme' }, "pattern=s", \$CONFIG{ 'pattern' }, # limits "entry-count=s", \$CONFIG{ 'entry-count' }, "rss-count=s", \$CONFIG{ 'rss-count' }, # output "date-archive-path", \$CONFIG{ 'date-archive-path' }, # optional "author=s", \$CONFIG{ 'author' }, "lang=s", \$CONFIG{ 'lang' }, "force", \$CONFIG{ 'force' }, "no-xrefs", \$CONFIG{ 'no-xrefs' }, "no-tags", \$CONFIG{ 'no-tags' }, "no-cache", \$CONFIG{ 'no-cache' }, "no-calendar", \$CONFIG{ 'no-calendar' }, "no-sitemap", \$CONFIG{ 'no-sitemap' }, "no-comments", \$CONFIG{ 'no-comments' }, "no-archive", \$CONFIG{ 'no-archive' }, "lower-case", \$CONFIG{ 'lower-case' }, "comment-days=s", \$CONFIG{ 'comment-days' }, # sorting "recent-tags-first", \$CONFIG{ 'recent-tags-first' }, "recent-dates-first", \$CONFIG{ 'recent-dates-first' }, # input format. "format=s", \$CONFIG{ 'format' }, "charset=s", \$CONFIG{ 'charset' }, # prefix "url-prefix=s", \$CONFIG{ 'url_prefix' }, "sitemap-prefix=s", \$CONFIG{ 'sitemap_prefix' }, # commands "pre-build=s", \$CONFIG{ 'pre-build' }, "post-build=s", \$CONFIG{ 'post-build' }, "pre-filter=s", \$CONFIG{ 'pre-filter' }, "post-filter=s", \$CONFIG{ 'post-filter' }, "send-ping", \$CONFIG{ 'send-ping' }, # title "blog-title=s", \$CONFIG{ 'blog_title' }, "blog-subtitle=s", \$CONFIG{ 'blog_subtitle' }, ) ) { exit; } pod2usage(1) if $HELP; pod2usage( -verbose => 2 ) if $MANUAL; if ($VERSION) { print("chronicle release $RELEASE\n"); exit; } } =begin doc Create our global data-structure, by reading each of the blog files and extracting: 1. The title of the entry. 2. Any tags which might be present. 3. The date upon which it was made. =end doc =cut sub createDataStructure { my %results; # # Did the user override the default pattern? # my $pattern = $CONFIG{ 'pattern' } || "*"; # # Find the filenames. # foreach my $file ( sort( glob( $CONFIG{ 'input' } . "/" . $pattern ) ) ) { # # Ignore directories. # next if ( -d $file ); # # Read the entry and store all the data away as a # hash element keyed upon the (unique) filename. # my $result = readBlogEntry($file); $results{ $file } = $result if ($result); } # # Make sure we found some entries. # if ( scalar( keys(%results) ) < 1 ) { print <{ 'title' }; next if ( !defined($title) ); # # If we've seen this already then abort # if ( $titles{ $title } ) { print "Non-unique title: $title\n"; print "Shared by (at least):\n"; print "\t" . $key . "\n"; print "\t" . $titles{ $title } . "\n"; print "\nAborting\n"; exit; } else { $titles{ $title } = $key; } } return %results; } =begin doc For each src file in %data that has references in %all_xrefs, create a new key in %data called 'xrefpairs' that contains an array of hashes: { xreftitle => ref->title, xreflink => ref->link } Finally, update the 'running_time' on each referenced page if the referencing page is itself dirty. =end doc =cut sub buildXRefPairs { if ( !$CONFIG{ 'no-xrefs' } ) { my $xrefs; foreach my $file ( keys %data ) { my $hash = $data{ $file }; my $refpair; if ( $all_xrefs{ $file } ) { $xrefs = undef; # # Add array of hashes of links and titles to each file # that has references pointing to it. # foreach my $ref ( @{ $all_xrefs{ $file } } ) { $refpair = undef; $refpair->{ 'xreftitle' } = $data{ $ref }->{ 'title' }; $refpair->{ 'xreflink' } = $data{ $ref }->{ 'link' }; push( @$xrefs, $refpair ); # # If referencing page is dirty, so must all # the referenced pages be. # if ( $data{ $ref }->{ 'running_time' } == $RUNNING_TIME ) { $hash->{ 'running_time' } = $RUNNING_TIME; } } $hash->{ 'xrefpairs' } = $xrefs; } } } } =begin doc Build a cross reference hash... for each key of the referenced filename, and an array of files that reference it. =end doc =cut sub findAllXRefs { my %allRefs; foreach my $file ( keys %data ) { my $hash = $data{ $file }; my $xrefs = $hash->{ 'xrefs' } || undef; foreach my $x (@$xrefs) { push( @{ $allRefs{ $x } }, $file ); } } return (%allRefs); } =begin doc Find each distinct tag which has been used within blog entries, and the number of times each one has been used. =end doc =cut sub findAllTags { my %allTags; foreach my $file ( keys %data ) { my $hash = $data{ $file }; my $tags = $hash->{ 'tags' } || undef; foreach my $t (@$tags) { if ( $t->{ 'tag' } ) { $allTags{ $t->{ 'tag' } } += 1; } } } return (%allTags); } =begin doc Create a structure for a tag cloud. =end doc =cut sub createTagCloud { my (%unique) = (@_); my $results; foreach my $key ( sort keys(%unique) ) { # count. my $count = $unique{ $key }; # size for the HTML. my $size = 10 + ( $count * 5 ); $size = 40 if ( $size >= 40 ); push( @$results, { tag => $key, count => $count, size => $size } ); } return $results; } =begin doc Find each of the distinct Month + Year pairs for entries which have been created. =end doc =cut sub findAllMonths { my %allDates; foreach my $file ( keys %data ) { my $hash = $data{ $file }; next if ( !$hash ); my $date = $hash->{ 'date' }; # # Not a date? Use the ctime of the file. # if ( !defined($date) || !length($date) ) { # # Test the file for creation time. # my ( $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks ) = stat($file); $date = localtime($ctime); } $date = time2str( "%Y-%m", str2time($date) ); $allDates{ $date } += 1; } return (%allDates); } =begin doc Create a data structure which can be used for our archive layout. This is a little messy. Mostly because we want to have a nested loop so that we can place our entries in a nice manner. =end doc =cut sub createDateCloud { my (%entry_dates) = (@_); my $results; my $year; my $months; foreach my $date ( sort keys %entry_dates ) { next unless ( $date =~ /^([0-9]{4})-([0-9]{2})/ ); if ( $year and $1 ne $year ) { push( @$results, { year => $year, months => $months } ); undef $months; } $year = $1; push( @$months, { month => $2, month_name => $names[$2 - 1], count => $entry_dates{ $date } } ); } push( @$results, { year => $year, months => $months } ); # # Make sure this is sorted by reverse chronological order. # my @sorted = sort {$b->{ 'year' } <=> $a->{ 'year' }} @$results; return \@sorted; } =begin doc Sort by date. =end doc =cut sub bywhen { # # Parse and return the date # my ( $ss1, $mm1, $hh1, $day1, $month1, $year1, $zone1 ) = strptime( $a->{ 'pubdate' } ); my ( $ss2, $mm2, $hh2, $day2, $month2, $year2, $zone2 ) = strptime( $b->{ 'pubdate' } ); # # Abort if we didn't work. # die "Couldn't find first year: $a->{'date'}" unless defined($year1); die "Couldn't find second year: $b->{'date'}" unless defined($year2); # # Convert to compare # my $c = timelocal( $ss1, $mm1, $hh1, $day1, $month1, $year1 + 1900 ); my $d = timelocal( $ss2, $mm2, $hh2, $day2, $month2, $year2 + 1900 ); return $d <=> $c; } =begin doc Output the index page + index RSS feed. =end doc =cut sub outputIndexPage { # # Holder for the blog entries. # my $entries; # # Find all the entries and sort them to be most recent first. # my $tmp; foreach my $file ( keys(%data) ) { my $blog = $data{ $file }; if ( keys(%$blog) ) { if ( $blog->{ 'body' } =~ /{ 'body' } = processCut( $blog->{ 'body' }, $blog->{ 'link' } ); } push( @$tmp, $blog ); } } my @tmp2 = sort bywhen @$tmp; # # The number of entries to display upon the index. # my $max = $CONFIG{ 'entry-count' }; foreach my $f (@tmp2) { my %copy = %{ $f }; $copy{ 'date' } = dateI18n( $copy{ 'date' } ); push( @$entries, \%copy ) if ( $max > 0 ); $max -= 1; } # # Open the index template. # my $template = loadTemplate( "index.template", die_on_bad_params => 0 ); # # create the calendar if we can. # my $calendar = createCalendar(); if ( defined($calendar) ) { my $text = $calendar->as_HTML(); $text =~ s/<\/?b>//g; $text =~ s/<\/?p>//g; $template->param( calendar => 1, calendar_month => $text ); } # # The entries. # $template->param( entries => $entries ) if ($entries); # # The clouds # $template->param( tagcloud => $CLOUD{ 'tag' } ) if ( $CLOUD{ 'tag' } ); $template->param( datecloud => $CLOUD{ 'archive' } ) if ( $CLOUD{ 'archive' } ); # # Blog title and subtitle, if present. # $template->param( blog_title => $CONFIG{ 'blog_title' } ) if ( $CONFIG{ 'blog_title' } ); $template->param( blog_subtitle => $CONFIG{ 'blog_subtitle' } ) if ( $CONFIG{ 'blog_subtitle' } ); $template->param( release => $RELEASE ); # # Page to use # my $index = $CONFIG{ 'filename' } || "index.html"; outputTemplate( $template, $index ); # # Output the RSS feed # # We repeat this loop because --rss-count and --entry-count # might be different. # $entries = undef; $max = $CONFIG{ 'rss-count' }; foreach my $f (@tmp2) { push( @$entries, $f ) if ( $max > 0 ); $max -= 1; } $template = loadTemplate( "index.xml.template", die_on_bad_params => 0 ); $template->param( blog_title => $CONFIG{ 'blog_title' } ) if ( $CONFIG{ 'blog_title' } ); $template->param( blog_subtitle => $CONFIG{ 'blog_subtitle' } ) if ( $CONFIG{ 'blog_subtitle' } ); $template->param( entries => $entries ) if ($entries); outputTemplate( $template, "index.rss" ); } =begin doc Write out a /tags/$foo/index.html containing each blog entry which has the tag '$foo'. =end doc =cut sub outputTagPage { my ($tagName) = (@_); my $dir = "tags/$tagName"; # # So we have %data being a hash of entries. # # The key is a filename. # # The value is a bunch of data as a hash reference. # # We want to traverse this hash and reference each entry # which has a tag which matches the given tag - we'll store # that in the local array called '$matching' # # We also write the filenames to a pointback file while we're # at it. # my $matching; # # read pointback cache first # my $pointbacks = undef; my %newpointbacks; $newpointbacks{ 'mtime' } = $RUNNING_TIME; my $cache = $CONFIG{ 'cache' } || undef; my $c_key = $CONFIG{ 'output' } . "/$dir/$tagName.pb"; if ($cache) { $pointbacks = $cache->get($c_key); } # # If one of the files matching our tag was just generated # (RUNNING_TIME == 'running_time') then this tag must be generated. # my $dirty_file_forced = 0; # # Iterate over the data-structure. # foreach my $file ( keys %data ) { # get the hash my $hash = $data{ $file }; # get the tags from that hash my $tags = $hash->{ 'tags' } || undef; my $matched = 0; # # for each tag, if it is a match then we save the data ref away. # foreach my $t (@$tags) { my $name = $t->{ 'tag' }; if ( $name eq $tagName ) { push( @$matching, $hash ); $matched = 1; } } # # and add to the pointback list # if ($matched) { push( @{ $newpointbacks{ 'files' } }, $file ); if ( $hash->{ 'running_time' } == $RUNNING_TIME ) { $dirty_file_forced = 1; } } } # # If $dirty_file_forced is false, we are close to being able to skip # this tag. But if one of the files was edited in order to remove # a tag, then we need to check for that too. It will be listed in # the pointback list. If one of the files in the pointback list # is newer than the pointback file itself, then we are still dirty. # if ( $dirty_file_forced == 0 ) { foreach my $pbname ( @{ $pointbacks->{ 'files' } } ) { my $hash = $data{ $pbname }; # If the pb file doesn't exist (!$pbmtime) # or if the src file doesn't exist anymore (!defined($hash)) # or if the src file is newer than the tag, # then we are dirty. if ( ( !$pointbacks->{ 'mtime' } ) || !defined($hash) || $hash->{ 'mtime' } > $pointbacks->{ 'mtime' } ) { $dirty_file_forced = 1; } } } # # Final check... if dirty_file_forced is false, no need to rebuild # if ( $dirty_file_forced == 0 ) { $CONFIG{ 'verbose' } && print "Skipping clean tag: $tagName\n"; return; } $CONFIG{ 'verbose' } && print "Creating tag page : $tagName\n"; # # We're dirty, so save a new pointback file # if ( defined($cache) ) { $cache->set( $c_key, \%newpointbacks ); } my $entries; # # Now for each matching entries. # foreach my $blog (@$matching) { if ( keys(%$blog) ) { if ( $blog->{ 'body' } =~ /{ 'body' } = processCut( $blog->{ 'body' }, $blog->{ 'link' } ); } my %copy = %{ $blog }; $copy{ 'date' } = dateI18n( $copy{ 'date' } ); push( @$entries, \%copy ); } } # # Sort the entries by date # my @sortedEntries; if ( $CONFIG{ 'recent-tags-first' } ) { @sortedEntries = sort bywhen @$entries; } else { @sortedEntries = reverse sort bywhen @$entries; } # # Now write the output as a HTML page. # my $template = loadTemplate( "tags.template", die_on_bad_params => 0 ); # # The entries. # $template->param( entries => \@sortedEntries ) if (@sortedEntries); $template->param( tagname => $tagName ); $template->param( release => $RELEASE ); # # The clouds # $template->param( tagcloud => $CLOUD{ 'tag' } ) if ( $CLOUD{ 'tag' } ); $template->param( datecloud => $CLOUD{ 'archive' } ) if ( $CLOUD{ 'archive' } ); # # Blog title and subtitle, if present. # $template->param( blog_title => $CONFIG{ 'blog_title' } ) if ( $CONFIG{ 'blog_title' } ); $template->param( blog_subtitle => $CONFIG{ 'blog_subtitle' } ) if ( $CONFIG{ 'blog_subtitle' } ); # # Page to use # my $index = $CONFIG{ 'filename' } || "index.html"; outputTemplate( $template, "$dir/$index" ); # # Now output the .xml file # $template = loadTemplate( "tags.xml.template", die_on_bad_params => 0 ); $template->param( blog_title => $CONFIG{ 'blog_title' } ) if ( $CONFIG{ 'blog_title' } ); $template->param( entries => \@sortedEntries ) if (@sortedEntries); $template->param( tagname => $tagName ) if ($tagName); outputTemplate( $template, "$dir/$tagName.rss" ); } =begin doc Output the archive page for the given Month + Year. This function is a *mess* and iterates over the data structure much more often than it needs to. =end doc =cut sub outputArchivePage { my ($date) = (@_); # # Should we abort? # if ( $CONFIG{ 'no-archive' } ) { $CONFIG{ 'verbose' } && print "Ignoring archive page, as instructed.\n"; return; } # # The year & month for which we're going to output a page. # # Entries dated on other month+year pairs will be ignored. # my $year = ''; my $month = ''; if ( $date =~ /^([0-9]{4})-([0-9]{2})/ ) { $year = $1; $month = $2; } else { die "Internal error - failed to parse month+year to output!\n"; } # # The path to which we're going to output our entries to. # my $dir = "archive/$year/$month"; # # read pointback cache first # my $pointbacks = undef; my %newpointbacks; $newpointbacks{ 'mtime' } = $RUNNING_TIME; my $cache = $CONFIG{ 'cache' } || undef; my $c_key = $CONFIG{ 'output' } . "/$dir/pb"; if ($cache) { $pointbacks = $cache->get($c_key); } # # If one of the files matching our archive date was just generated # (RUNNING_TIME == 'running_time') then this date must be generated. # my $dirty_file_forced = 0; # # So we have %data being a hash of entries. # # The key is a filename. # # The value is a bunch of data as a hash reference. # # We want to traverse this hash and reference each entry # which has a tag which matches the given tag - we'll store # that in the local array called '$matching' # my $matching; foreach my $filename ( keys %data ) { # # Get the date. # my $hash = $data{ $filename }; my $date = $hash->{ 'date' }; my $matched = 0; # # Not a date? Use the file's modification date. # if ( !defined($date) || !length($date) ) { # # Test the file for creation time. # my ( $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks ) = stat($filename); $date = localtime($ctime); } $date = time2str( "%Y-%m", str2time($date) ); # # If the date matches then we'll save it # if ( $date eq "$year-$month" ) { push( @$matching, $hash ); $matched = 1; } if ($matched) { push( @{ $newpointbacks{ 'files' } }, $filename ); if ( $hash->{ 'running_time' } == $RUNNING_TIME ) { $dirty_file_forced = 1; } } } # # If $dirty_file_forced is false, we are close to being able to skip # this date. But if one of the files was edited in order to change the # date, then we need to check for that too. It will be listed in # the pointback list. If one of the files in the pointback list # is newer than the pointback file itself, then we are still dirty. # if ( $dirty_file_forced == 0 ) { foreach my $pbname ( @{ $pointbacks->{ 'files' } } ) { my $hash = $data{ $pbname }; # If the pb file doesn't exist (!$pbmtime) # or if the src file doesn't exist anymore (!defined($hash)) # or if the src file is newer than the tag, # then we are dirty. if ( ( !$pointbacks->{ 'mtime' } ) || !defined($hash) || $hash->{ 'mtime' } > $pointbacks->{ 'mtime' } ) { $dirty_file_forced = 1; } } } # # Final check... if dirty_file_forced is false, no need to rebuild # if ( $dirty_file_forced == 0 ) { $CONFIG{ 'verbose' } && print "Skipping clean archive: $date\n"; return; } # # We're dirty, so save our new pointback list # if ( defined($cache) ) { $cache->set( $c_key, \%newpointbacks ); } # # The entries. # my $entries; # # # Now process each matching entry # foreach my $blog (@$matching) { if ( keys(%$blog) ) { # # Test for the cut in a quick fashion. # if ( $blog->{ 'body' } =~ /{ 'body' } = processCut( $blog->{ 'body' }, $blog->{ 'link' } ); } my %copy = %{ $blog }; $copy{ 'date' } = dateI18n( $copy{ 'date' } ); push( @$entries, \%copy ); } } # # Sort the entries by date # my @sortedEntries; if ( $CONFIG{ 'recent-dates-first' } ) { @sortedEntries = sort bywhen @$entries; } else { @sortedEntries = reverse sort bywhen @$entries; } $CONFIG{ 'verbose' } && print "Creating archive page : $date\n"; # # Now write the output as a HTML page. # my $template = loadTemplate( "month.template", die_on_bad_params => 0 ); $template->param( release => $RELEASE ); # # The entries # $template->param( entries => \@sortedEntries ) if (@sortedEntries); # # Output the month + year. # $template->param( year => $year, month => $month ); $template->param( month_name => $names[$month - 1] ); # # The clouds # $template->param( tagcloud => $CLOUD{ 'tag' } ) if ( $CLOUD{ 'tag' } ); $template->param( datecloud => $CLOUD{ 'archive' } ) if ( $CLOUD{ 'archive' } ); # # Blog title and subtitle, if present. # $template->param( blog_title => $CONFIG{ 'blog_title' } ) if ( $CONFIG{ 'blog_title' } ); $template->param( blog_subtitle => $CONFIG{ 'blog_subtitle' } ) if ( $CONFIG{ 'blog_subtitle' } ); # # Page to use # my $index = $CONFIG{ 'filename' } || "index.html"; outputTemplate( $template, "$dir/$index" ); # # Now the RSS page. # $template = loadTemplate( "month.xml.template", die_on_bad_params => 0 ); $template->param( blog_title => $CONFIG{ 'blog_title' } ) if ( $CONFIG{ 'blog_title' } ); $template->param( entries => \@sortedEntries ) if (@sortedEntries); $template->param( month => $month, year => $year ); $template->param( month_name => $names[$month - 1] ); outputTemplate( $template, "$dir/$month.rss" ); } =begin doc Process the body for any present cut tags which might be present. The cut text looks like this: =for example begin Blah blah This is visible This is hidden So is this This is visible =for example end =end doc =cut sub processCut { my ( $body, $link ) = (@_); my $url = $CONFIG{ 'url_prefix' } || ""; $url .= $link; my $cut_text = ""; if ( $body =~ /(.*)]*)>(.*)<\/cut>(.*)/gis ) { my $pre = $1; my $cut = $2; my $hid = $3; my $post = $4; # # See if they supplied text="xxxxx" # if ( defined($cut) && ( $cut =~ /text=['"]([^'"]+)['"]/i ) ) { $cut_text = $1; } $body = $pre; if ( !length($cut_text) ) { $cut_text = "This entry has been cut, click to read on."; } $body .= " $cut_text "; $body .= $post; } return ($body); } =begin doc Output static page. =end doc =cut sub outputStaticPage { my ($filename) = (@_); # # Just the name of the file. # my $basename = basename($filename); # # Read the entry # my $static = $data{ $filename }; # # If the running time of this file is not the same as this process's, # then it was cached, and we don't need to write it again. # if ( $static->{ 'running_time' } != $RUNNING_TIME ) { $CONFIG{ 'verbose' } && print "\tSkipping: $filename\n"; return; } # # Load the template # my $template = loadTemplate( "entry.template", die_on_bad_params => 0 ); $template->param( release => $RELEASE ); # # Get the pieces of information. # my $title = $static->{ 'title' } || $basename; my $tags = $static->{ 'tags' }; my $body = $static->{ 'body' }; my $date = $static->{ 'date' }; if ( !defined($date) ) { my ( $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks ) = stat($filename); $date = localtime($ctime); } $CONFIG{ 'verbose' } && print "\t$filename\n"; # # Convert to suitable filename. # my $file = fileToTitle($title); # # Update the basename # $basename = basename($file); # # Include the comments in the output if they weren't disabled # and are present. # if ( !$CONFIG{ 'no-comments' } ) { $template->param( comments => $static->{ 'comments' }, comment_count => $static->{ 'comment_count' }, comment_plural => $static->{ 'comment_plural' } ) if ( ( defined $static->{ 'comment_count' } > 0 ) && ( $static->{ 'comment_count' } > 0 ) ); # # If we have no date restrictions then enable comments # if ( $CONFIG{ 'comment-days' } == 0 ) { $template->param( comments_enabled => 1 ); } else { # # The number of seconds past the epoch of today and the # date the blog is supposed to be published is used. # my $time = str2time($date); my $today = time; # # The number of days that should be allowd. # my $days = $CONFIG{ 'comment-days' } * 60 * 60 * 24; if ( ( $time + $days ) > $today ) { $CONFIG{ 'verbose' } && print "Comments allowed on post dated $date.\n"; $template->param( comments_enabled => 1 ); } } } # # The entry. # $template->param( title => $title ); $template->param( tags => $tags ) if ($tags); $template->param( date => dateI18n($date) ) if ($date); $template->param( body => $body ); $template->param( link => $static->{ 'link' } ); $basename =~ s/\.(html|htm)$//g; $template->param( basename => $basename ); $template->param( xrefpairs => $static->{ 'xrefpairs' } ) if ( $static->{ 'xrefpairs' } ); # # Our clouds # $template->param( tagcloud => $CLOUD{ 'tag' } ) if ( $CLOUD{ 'tag' } ); $template->param( datecloud => $CLOUD{ 'archive' } ) if ( $CLOUD{ 'archive' } ); # # Blog title and subtitle, if present. # $template->param( blog_title => $CONFIG{ 'blog_title' } ) if ( $CONFIG{ 'blog_title' } ); $template->param( blog_subtitle => $CONFIG{ 'blog_subtitle' } ) if ( $CONFIG{ 'blog_subtitle' } ); # # If comments are disabled then set that. # $template->param( no_comments => 1 ) if ( $CONFIG{ 'no-comments' } ); # # Output the entry now. # if ( $CONFIG{ 'date-archive-path' } ) { my $dir = "archive/" . time2str( "%Y/%m", str2time($date) ); outputTemplate( $template, "${dir}/${file}" ); } else { outputTemplate( $template, $file ); } # # Create comment feed, unless comments are disabled. # if ( !$CONFIG{ 'no-comments' } ) { $CONFIG{ 'verbose' } && print "\tCreating comment feed\n"; # # Load the template # my $comments = loadTemplate( "comments.xml.template", die_on_bad_params => 0 ); # # Setup the loop # $comments->param( title => $title ); $comments->param( comments => $static->{ 'comments' } ) if ( $static->{ 'comments' } ); my $xml = undef; if ( $CONFIG{ 'date-archive-path' } ) { my $dir = "archive/" . time2str( "%Y/%m", str2time($date) ); $xml = "${dir}/${file}"; } else { $xml = $file; } # # Give an .RSS suffix # $xml =~ s/\.html?$/.rss/g; outputTemplate( $comments, $xml ); } } =begin doc Output a static sitemap, unless there is no URL or sitemap prefix setup. Note: We do this after all other things have been created, by reading the files in our output directory. This is either sleazy or clever depending upon your point of view. =end doc =cut sub outputSiteMap { my $dir = $CONFIG{ 'output' }; my $file = $dir . "/sitemap.xml"; my $home = ''; if ( $CONFIG{ 'sitemap_prefix' } ) { $home = $CONFIG{ 'sitemap_prefix' }; } elsif ( $CONFIG{ 'url_prefix' } ) { $home = $CONFIG{ 'url_prefix' }; } else { $CONFIG{ 'verbose' } && print "No prefix defined - skipping sitemap\n"; return; } # # Print the header # open my $handle, ">", $file or die "Failed to write to $file $!"; print $handle < $home 0.75 daily EOF # # Find the files # my $suffix = $CONFIG{ 'suffix' } || ".html"; foreach my $page ( glob( $dir . "/*" . $suffix ) ) { next if ( -d $page ); $page = basename($page); print $handle < $home$page 0.50 weekly EOF } # # Print the footer # print $handle < EOF # # All done # close($handle); } =begin doc Return a hash of interesting data from our blog file. =end doc =cut sub readBlogEntry { my ($filename) = (@_); my %entry; # # Do we have the memcached module available and setup? # my $cache = $CONFIG{ 'cache' } || undef; my $c_key = ""; if ($cache) { # # Get the data from the cache - which will include the # mtime of the file to allow natural expirey. # # This way if the file is modified the cache will be flushed # appropriately. # $c_key = $filename . "_" . ( stat($filename) )[9]; my $cached = $cache->get($c_key); if ( defined($cached) ) { $CONFIG{ 'verbose' } && print "memcache-get: $filename\n"; return ( \%$cached ); } } # # Here we store the header values from the entry. # # The body will be read & returned via `chronicle-entry-filter`, and # we don't care about it here. # my %meta; my $inHeader = 1; # # Set running time to current process # $meta{ 'running_time' } = $RUNNING_TIME; # # Read the actual entry # open my $handle, "<:utf8", $filename or die "Failed to read $filename $!"; while ( my $line = <$handle> ) { if ($inHeader) { # # If the line has the form of "key: value" # if ( $line =~ /^([^:]+):(.*)/ ) { my $key = $1; my $val = $2; $key = lc($key); $key =~ s/^\s+|\s+$//g; $val =~ s/^\s+|\s+$//g; # # "subject" is a synonym for "title". # $key = "title" if ( $key eq "subject" ); # # Update the value if there is one present, # and we've not already saved that one away. # $meta{ $key } = $val if ( defined($val) && length($val) && !$meta{ $key } ); } } else { # # Empty line == end of header # $inHeader = 0 if ( $line =~ /^$/ ); } } close($handle); # # MJR - embargo the entry until the publish if it # is in the future. # # Steve prefers using chronicle-spooler, but I want # uploaded files to stay where I put them, else I get # my local copy confused. # if ( ( $meta{ 'publish' } ) && ( str2time( $meta{ 'publish' } ) > time() ) ) { $CONFIG{ 'verbose' } && print "Skipping future entry $filename - due to be published on $meta{'publish'}\n"; return 0; } # # Specify the global format if we didn't get one in this file # if ( !$meta{ 'format' } ) { $meta{ 'format' } = ( $CONFIG{ 'format' } || "html" ); } # # Did we find tags? # # If so replace them with the array of hashses that we'll use for # the template expansion. # if ( $meta{ 'tags' } ) { my @tmpTags; foreach my $tag ( split( /,/, $meta{ 'tags' } ) ) { # strip leading and trailing space. $tag =~ s/^\s+//; $tag =~ s/\s+$//; # skip empty tags. next if ( !length($tag) ); # tags are lowercase. $tag = lc($tag); # store the tag away - so we can have a sorted list later. push( @tmpTags, $tag ); } # # Remove the tag list, so that we can replace it with an # array of hashes. # $meta{ 'tags' } = undef; # # Push the sorted tags into the entry # foreach my $t ( sort @tmpTags ) { push( @{ $meta{ 'tags' } }, { tag => $t } ); } } # # Did we find cross references? # # If so, replace them with the array of full filename keys we'll # use for xref generation. # if ( $meta{ 'xrefs' } ) { my @tmpRefs; foreach my $ref ( split( /,/, $meta{ 'xrefs' } ) ) { # strip leading and trailing space. $ref =~ s/^\s+//; $ref =~ s/\s+$//; # skip empty tags. next if ( !length($ref) ); # get full filename key $ref = $CONFIG{ 'input' } . "/" . $ref; # store the ref away, only if the file exists if ( -e "$ref" ) { push( @tmpRefs, $ref ); } else { print "Warning: cross reference not found: $ref\n"; } } # # Remove the xrefs list, so that we can replace it with an # array. # $meta{ 'xrefs' } = undef; # # Push the sorted xrefs into the entry # foreach my $r ( sort @tmpRefs ) { push( @{ $meta{ 'xrefs' } }, $r ); } } # # Find all the details from the file. # my ( $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks ) = stat($filename); # # If the date isn't set then use the mtime of the file. # if ( !defined( $meta{ 'date' } ) || ( !length( $meta{ 'date' } ) ) ) { my @lt = gmtime($mtime); $meta{ 'date' } = strftime( "%d %B %Y", @lt ); } # # Store that mtime for later comparison # $meta{ 'mtime' } = $mtime; # # Store the values from our pseudo-headers in our entry # foreach my $key ( keys %meta ) { $entry{ $key } = $meta{ $key }; } # # Get the Hours::Mins::Seconds from the Date: pseudo-header # my $time = str2time( $meta{ 'date' } ); my $hms = time2str( "%H:%M:%S", $time ); my $tz = time2str( "%z", $time ); # # If the time is missing from the Date: pseudo-header # we need to magic them up. Use the mtime of the file. # if ( $hms eq '00:00:00' ) { $hms = time2str( "%H:%M:%S", $mtime, "GMT" ); $tz = "GMT"; } # # If we found a time then we're OK, and can store away the various # XML versions of the date. # if ($time) { # # RSS 2 # $entry{ 'pubdate' } = time2str( "%a, %e %b %Y $hms $tz", $time ); $entry{ 'pubdate' } =~ s/ +/ /g; # # Store the W3C date form # http://www.w3.org/TR/NOTE-datetime # my $w3ctz = $tz; $w3ctz =~ s/([-+]\d\d)(\d\d)/$1:$2/; $entry{ 'w3cdate' } = time2str( "%Y-%m-%dT${hms}$w3ctz", $time ); } else { print "Failed to parse date: '$meta{'date'}' to generate pubDate of entry.\n"; } # # We now want to read the body of the entry from our filter program. # # The filter program will read the specified file and return the # HTML output to STDOUT. # my $cmd = "chronicle-entry-filter "; # # Propogate any global options to the command. # foreach my $k (qw! format pre-filter post-filter !) { if ( $CONFIG{ $k } ) { $cmd .= "--$k='$CONFIG{$k}' "; } } $cmd .= " --filename=$filename"; # # Show what we're running. # $CONFIG{ 'verbose' } && print "Running: $cmd\n"; # # Run the command, reading stdout. # open my $han, "-|", $cmd or die "Failed to run filter: $!\n"; # # UTF-8 must be preserved # binmode( $han, ":utf8" ); my $body = ""; while (<$han>) { $body .= $_; } close($han); # # Store the returned body, if any. # $entry{ 'body' } = $body if ($body); # # No title? # if ( !defined( $entry{ 'title' } ) || !length( $entry{ 'title' } ) ) { my $basename = $filename; if ( $basename =~ /(.*)\/(.*)/ ) { $basename = $2; } if ( $basename =~ /^(.*)\.(.*)$/ ) { $basename = $1; } $entry{ 'title' } = $basename; } # # Get the link - after ensuring we have a title. # my $link = fileToTitle( $entry{ 'title' } ); $entry{ 'link' } = $link; # # Rewrite the link if we're using a date-archive. # if ( $CONFIG{ 'date-archive-path' } ) { my $date = $entry{ 'date' }; $entry{ 'link' } = "archive/" . time2str( "%Y/%m", str2time($date) ) . "/$entry{ 'link' }"; } # # Find the comments associated with this entry, if any. # unless ( $CONFIG{ 'no-comments' } ) { my $name = fileToTitle( $meta{ 'title' } ); my $comments = getComments( $CONFIG{ 'comments' }, $name ); if ( defined($comments) ) { my $count = scalar(@$comments); my $plural = 1; $plural = 0 if ( $count == 1 ); $entry{ 'comments' } = $comments; $entry{ 'comment_count' } = $count; $entry{ 'comment_plural' } = $plural; } } # # Store the data in the cache if we have it enabled - unless # there are comments associated with the entry which might have # changed behind our back. # # It won't help this time, but the next run will rock .. # if ( defined($cache) ) { my $update = 1; if ( ( $entry{ 'comment_count' } ) && ( $entry{ 'comment_count' } ) > 0 ) { # # The number of seconds past the epoch of today and the # date the blog is supposed to be published is used. # my $time = str2time( $entry{ 'date' } ); my $today = time; # # The number of days that should be allowd. # my $days = $CONFIG{ 'comment-days' } * 60 * 60 * 24; if ( ( $time + $days ) > $today ) { $update = 0; } } # # If we're updating, then store in the cache. # # We don't store in the cache if: # # * The entry has comments. # * The entry is "recent", as set by "--comment-days". # # if ($update) { $CONFIG{ 'verbose' } && print "memcache-set: $filename\n"; $cache->set( $c_key, \%entry ); } else { $CONFIG{ 'verbose' } && print "NOT Updating cache for $filename\n"; } } return \%entry; } =begin doc Create a filename for an URL which does not contain unsafe characters. =end doc =cut sub fileToTitle { my ($file) = (@_); # # Get rid of non-alphanumeric Al characters # $file =~ s/[^a-z0-9]/_/gi; my $suffix = $CONFIG{ 'suffix' } || ".html"; $file .= $suffix; # # Lower case? # $file = lc($file) if ( $CONFIG{ 'lower-case' } ); return ($file); } =begin doc Look for comments, for the given entry. Return any found in a format suitable for the insertion into the output templates. =end doc =cut sub getComments { my ( $dir, $title ) = (@_); my $results; if ( $title =~ /^(.*)\.([^.]+)$/ ) { $title = $1; } # # Find each comment file. # my @entries; foreach my $file ( sort( glob( $dir . "/" . $title . "*" ) ) ) { push( @entries, $file ); } # # Sort them into order. # @entries = sort {( stat($a) )[9] <=> ( stat($b) )[9]} @entries; # # Now process them. # foreach my $file (@entries) { my $date = ""; my $name = ""; my $link = ""; my $body = ""; my $mail = ""; my $pubdate = ""; if ( $file =~ /^(.*)\.([^.]+)$/ ) { $date = $2; if ( $date =~ /(.*)-([0-9:]+)/ ) { my $d = $1; my $t = $2; $d =~ s/-/ /g; $date = "Submitted at $t on $d"; } } open my $comment, "<:utf8", $file or next; foreach my $line (<$comment>) { next if ( !defined($line) ); chomp($line); next if ( $line =~ /^IP-Address:/ ); next if ( $line =~ /^User-Agent:/ ); if ( !length($name) && $line =~ /^Name: (.*)/i ) { $name = $1; } elsif ( !length($mail) && $line =~ /^Mail: (.*)/i ) { $mail = $1; } elsif ( !length($link) && $line =~ /^Link: (.*)/i ) { $link = $1; } else { $body .= $line . "\n"; } } close($comment); if ( length($name) && length($mail) && length($body) ) { # # Add a gravitar link to the comment in case the # theme wishes to use it. # my $default = ""; my $size = 32; my $gravitar = "http://www.gravatar.com/avatar.php?gravatar_id=" . md5_hex( lc $mail ) . "&size=" . $size; # # A comment which was submitted by the blog author might # have special theming. # my $author = 0; $author = 1 if ( $CONFIG{ 'author' } && ( lc($mail) eq lc( $CONFIG{ 'author' } ) ) ); # # Store the comment # push( @$results, { name => $name, author => $author, gravitar => $gravitar, link => $link, mail => $mail, body => $body, date => $date, } ); } else { $CONFIG{ 'verbose' } && print "I didn't like length of \$name ($name), \$mail ($mail) or \$body ($body)\n"; } } return ($results); } =begin doc Load a template file. =end doc =cut sub loadTemplate { my ( $file, %params ) = (@_); # # Get the directory. # my $dir = $CONFIG{ 'theme-dir' }; # # XML files go in theme-dir/xml/ # if ( $file =~ /\.xml\./i ) { $dir .= "/xml/"; } else { $dir .= "/" . $CONFIG{ 'theme' } . "/"; } # # Make sure the file exists. # if ( !-e $dir . $file ) { print <new( filename => $file, path => $dir, loop_context_vars => 1, global_vars => 1, search_path_on_include => 1, %params ); return ($t); } =begin doc Set URL for top directory and output a template. =end doc =cut sub outputTemplate { my ( $template, $path ) = (@_); my $reltop = $path; $reltop =~ s'[^/]+/'../'g; $reltop =~ s'[^/]*$''; $template->param( reltop => $reltop ); # # Select relative/absolute URL prefix. # my $top; if ( $CONFIG{ 'url_prefix' } ) { $top = $CONFIG{ 'url_prefix' }; } else { $top = $reltop; } $template->param( top => $top ); if ( $CONFIG{ 'date-archive-path' } ) { ( -d dirname("$CONFIG{'output'}/${path}") ) || mkdir( dirname("$CONFIG{'output'}/${path}") ); } open my $handle, ">:utf8", "$CONFIG{'output'}/$path" or die "Failed to write output template to $CONFIG{'output'}/$path - $!"; print $handle $template->output(); close($handle); } =begin doc Read the specified configuration file if it exists. =end doc =cut sub readConfigurationFile { my ($file) = (@_); # # If it doesn't exist ignore it. # return if ( !-e $file ); my $line = ""; open my $handle, "<:utf8", $file or die "Cannot read file '$file' - $!"; while ( defined( $line = <$handle> ) ) { chomp $line; if ( $line =~ s/\\$// ) { $line .= ; redo unless eof(FILE); } # Skip lines beginning with comments next if ( $line =~ /^([ \t]*)\#/ ); # Skip blank lines next if ( length($line) < 1 ); # Strip trailing comments. if ( $line =~ /(.*)\#(.*)/ ) { $line = $1; } # Find variable settings if ( $line =~ /([^=]+)=([^\n]+)/ ) { my $key = $1; my $val = $2; # Strip leading and trailing whitespace. $key =~ s/^\s+//; $key =~ s/\s+$//; $val =~ s/^\s+//; $val =~ s/\s+$//; # command expansion? if ( $val =~ /(.*)`([^`]+)`(.*)/ ) { # store my $pre = $1; my $cmd = $2; my $post = $3; # get output my $output = `$cmd`; chomp($output); # build up replacement. $val = $pre . $output . $post; } # Store value. $CONFIG{ $key } = $val; } } close($handle); } =begin doc Sanity check our arguments, and setup to make sure there is nothing obviously broken. =end doc =cut sub sanityCheckArguments { # # Make sure we have an input directory. # if ( !-d $CONFIG{ 'input' } ) { print < ["localhost:11211"] }; $CONFIG{ 'verbose' } && print "Cache setup.\n"; } } =begin doc Copy any static files from the theme directory into the "live" location in the output. This only works for a top-level target directory. Unless --force is specified we skip copying files which already exist, or that are older than the target. =end doc =cut sub copyStaticFiles { # # Source and destination for the copy # my $input = $CONFIG{ 'theme-dir' } . "/" . $CONFIG{ 'theme' }; my $output = $CONFIG{ 'output' }; foreach my $pattern (qw! *.css *.jpg *.gif *.png *.js *.ico !) { foreach my $file ( glob( $input . "/" . $pattern ) ) { # # Get the name of the file. # if ( $file =~ /(.*)\/(.*)/ ) { $file = $2; } if ( $CONFIG{ 'force' } || ( !-e "$output/$file" ) || ( stat("$input/$file") )[9] > ( stat("$output/$file") )[9] ) { $CONFIG{ 'verbose' } && print "Copying static file: $file\n"; copy( "$input/$file", "$output/$file" ); } } } } =begin doc List the names of all globally installed themes. =end doc =cut sub listThemes { my ($dir) = (@_); $CONFIG{ 'verbose' } && print "Listing themes beneath : $dir\n"; foreach my $name ( sort( glob( $dir . "/*" ) ) ) { next unless ( -d $name ); next if ( $name =~ /\/xml$/ ); if ( $name =~ /^(.*)\/([^\/\\]*)$/ ) { print $2 . "\n"; } } } =begin doc Create and configure a calendar for the index, if and only iff the HTML::CalendarMonthSimple module is installed. =end doc =cut sub createCalendar { # # Attempt to load the module. # my $test = "use HTML::CalendarMonthSimple;"; ## no critic (Eval) eval($test); ## use critic # # If there was an error, or the calendar is disabled then # return undef. # if ( ($@) || ( $CONFIG{ 'no-calendar' } ) ) { return; } # # Continue # my $cal = new HTML::CalendarMonthSimple(); # configuration of the calendar $cal->border(0); $cal->weekstartsonmonday(1); $cal->showweekdayheaders(1); $cal->sunday('Sun'); $cal->saturday('Sat'); $cal->weekdays( 'Mo', 'Tue', 'We', 'Thu', 'Fr' ); # get 4th element from localtime aka month in form of (0..11) my $curmonth = (localtime)[4] + 1; foreach my $f (%data) { my $h = $data{ $f }; next if ( !$h ); my $entrydate = $h->{ 'date' }; if ( !$entrydate ) { my ( $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks ) = stat($f); $entrydate = localtime($ctime); } my $date = time2str( "%Y-%m-%d", str2time($entrydate) ); my ( $year, $month, $day ) = split( /-/, $date ); if ( $month eq $curmonth ) { $cal->setdatehref( $day, fileToTitle( $data{ $f }->{ 'title' } ) ); } } return ($cal); } =begin doc Return the appropriate Data::Language instance according to the lang config parameter. =end doc =cut sub getDateLanguageModule { my $modulename = ucfirst lc $CONFIG{ 'lang' }; my $fullmodulename = sprintf( 'Date::Language::%s', $modulename ); ## no critic (Eval) eval "require $fullmodulename" or die("Not supported language"); ## use critic return Date::Language->new($modulename); } =begin doc Translate a date in the appropriate language. =end doc =cut sub dateI18n { my $date = shift; my $lang = getDateLanguageModule; my $time = str2time($date); # # Format timestamp to readable date, using the date-format config parameter # $date = $lang->time2str( $CONFIG{ 'date-format' }, $time ); # # Data::Language outputs iso-8859-1 string, # we force the conversion to UTF-8 # # $date = &Encode::encode_utf8($date); return $date; } =begin doc Return array of month names. =end doc =cut sub getMonthNames { # # Find the appropriate Data::Language module name # my $lang = getDateLanguageModule; my $langmodulename = ref($lang); # # Get month names using the @MoY variable of the module # my $names_var = sprintf( '%s::MoY', $langmodulename ); my @names; { ## no critic (ProhibitNoStrict) no strict 'refs'; @names = @{ $names_var }; use strict 'refs'; ## use critic } # @names = map {&Encode::encode_utf8($_)} @names; return @names; } chronicle-4.6/bin/chronicle-spam-test0000755000175000017500000000704411557771306016125 0ustar skxskx#!/usr/bin/perl -w # # This script is a simple example which will submit comments for testing # against the blogspam.net API. # # The files are assumed to be those written by the comments.cgi script # included in the chronicle distribution - and it is expected you'll # test the comments via this script, or manual inspection prior to making # them live. # # Sample output: # # $ chronicle-spam-test * # i_bet_you_re_a_real_tiger_in_disguise_.html.5-July-2009-18:27:09 - OK # i_bet_you_re_a_real_tiger_in_disguise_.html.6-July-2009-07:17:25 - OK # i_bet_you_re_a_real_tiger_in_disguise_.html.6-July-2009-16:54:26 - OK # i_bet_you_re_a_real_tiger_in_disguise_.html.6-July-2009-17:07:05 - OK # $ # # # Steve # -- # use strict; use warnings; use Getopt::Long; require RPC::XML; require RPC::XML::Client; # # Config # my %CONFIG; # # Default server # $CONFIG{ 'server' } = "http://test.blogspam.net:8888/"; $CONFIG{ 'options' } = "exclude=bayasian"; # # Parse our options # exit if ( !GetOptions( "server=s", \$CONFIG{ 'server' }, "options=s", \$CONFIG{ 'options' } ) ); # # Make sure the server is valid # if ( $CONFIG{ 'server' } !~ /^http:\/\// ) { $CONFIG{ 'server' } = "http://" . $CONFIG{ 'server' }; } if ( $CONFIG{ 'server' } !~ /:([0-9]+)/ ) { $CONFIG{ 'server' } .= ":8888/"; } # # If we're using a file then test that, otherwise # test all files in the current directory. # while ( defined( my $file = shift ) ) { testTheFile($file); } # # All done # exit; =begin doc Make a request against the RPC server with the given file providing both input and the expected result - the latter coming from the filename. =end doc =cut sub testTheFile { my ($file) = (@_); if ( !-e $file ) { print "File not found: $file\n"; return; } # # Params we send to the server. # # Note we send "test" so that the spam isn't logged. # my %params = ( 'test' => '1', options => $CONFIG{ 'options' } ); # # Read the file. # open( my $handle, "<", $file ) or die "Failed to open $file - $!"; while ( my $line = <$handle> ) { if ( $line =~ /^IP.*: (.*)/i ) { $params{ 'ip' } = $1; } elsif ( $line =~ /^User-Agent: (.*)/i ) { $params{ 'agent' } = $1; } elsif ( $line =~ /^mail: (.*)/i ) { $params{ 'email' } = $1; } elsif ( $line =~ /^Name: (.*)/i ) { $params{ 'name' } = $1; } elsif ( $line =~ /^Subject: (.*)/i ) { $params{ 'subject' } = $1; } else { $params{ 'comment' } .= $line; } } close($handle); # # The result we obtained # my $result = undef; # # Make the result # eval { my $client = RPC::XML::Client->new( $CONFIG{ 'server' } ); my $req = RPC::XML::request->new( 'testComment', \%params ); my $res = $client->send_request($req); $result = $res->value(); }; if ($@) { print "Connection failed to $CONFIG{'server'}\n"; print "Or there was some other error.\n"; print "Aborting.\n"; exit; } # # Did we get the result we wanted? # my $literal = $result; $result = $1 if ( $result =~ /^([^:]+):(.*)/ ); $result = uc($result); if ( $result =~ /ok/i ) { print "$file - OK\n"; } else { print "$file - SPAM [$literal]\n"; } } chronicle-4.6/bin/chronicle-rss-importer0000755000175000017500000001163111557771306016653 0ustar skxskx#!/usr/bin/perl -w # vim: expandtab tabstop=4 =head1 NAME chronicle-rss-importer - Import entries from an RSS feed to chronicle =cut =head1 SYNOPSIS General Options: --output Specify the directory to write entries to. --feed Specify the URL of the feed. --sequential Specify that entries should be numbered rather than named. Help Options: --help Show the help information for this script. --manual Read the manual for this script. --verbose Show useful debugging information. =cut =head1 ABOUT Chronicle is a simple tool to convert a collection of text files, located within a single directory, into a blog consisting of static HTML files. This importer script will create a directory of input files from a given RSS feed, by downloading it and writing out each entry to a single text file. The output files will be named after the entries titles, or if B<--sequential> was used each entry will be numbered numerically. =cut =head1 AUTHOR Steve -- http://www.steve.org.uk/ =cut =head1 LICENSE Copyright (c) 2007-2010 by Steve Kemp. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The LICENSE file contains the full text of the license. =cut use strict; use warnings; use File::Path; use Getopt::Long; use HTML::Entities; use LWP; use Pod::Usage; use XML::RSSLite; # # Configuration variables # my %CONFIG; # # Parse command line arguments. # parseCommandLineArguments(); # # Validate any arguments. # validateCommandLineArguments(); # # Fetch the feed. # my $content = fetchRSSFeed( $CONFIG{ 'feed' } ); # # Parse the feed # my %rssHash; parseRSS( \%rssHash, \$content ); # # Now import # processEntries(%rssHash); # # All done. # =begin doc Parse the command line arguments, if any. =end doc =cut sub parseCommandLineArguments { my $HELP = 0; my $MANUAL = 0; if ( !GetOptions( # Help options "help", \$HELP, "manual", \$MANUAL, "verbose", \$CONFIG{ 'verbose' }, # General options "feed=s", \$CONFIG{ 'feed' }, "output=s", \$CONFIG{ 'output' }, "sequential", \$CONFIG{ 'sequential' }, ) ) { exit; } pod2usage(1) if $HELP; pod2usage( -verbose => 2 ) if $MANUAL; } =begin doc Ensure we received the arguments we need, and that those arguments look OK. =end doc =cut sub validateCommandLineArguments { # # We need an output dir # if ( !$CONFIG{ 'output' } ) { print "Output directory is mandatory.\n"; print "Please specificy via --output=...\n"; exit; } if ( !-d $CONFIG{ 'output' } ) { $CONFIG{ 'verbose' } && print "Creating output directory: $CONFIG{'output'}\n"; mkpath( $CONFIG{ 'output' }, 0, oct(755) ); } # # We need a feed # if ( !$CONFIG{ 'feed' } ) { print "Please specify a feed to import, via --feed=http:/....\n"; exit; } } =begin doc Fetch the remote RSS feed. =end doc =cut sub fetchRSSFeed { my ($uri) = (@_); my $ua = LWP::UserAgent->new(); $ua->timeout(10); $ua->agent('chronicle-importer'); $CONFIG{ 'verbose' } && print "Fetching: $uri\n"; my $response = $ua->get($uri); if ( $response->is_success ) { $CONFIG{ 'verbose' } && print "\tFetched successfully\n"; return ( $response->content() ); } else { print "Failed to fetch feed: $uri\n"; print "\n"; print $response->message() . "\n"; exit; } } =begin doc Iterate over the items in our feed and write each one out to a single file. =end doc =cut sub processEntries { my (%entries) = (@_); my $count = 1; foreach my $item ( @{ $rssHash{ 'item' } } ) { # # Get details from the feed. # my $title = $item->{ 'title' } || "no title"; my $date = $item->{ 'pubDate' } || $item->{ 'dc:date' } || undef; my $body = $item->{ 'description' } || $item->{ 'content:encoded' } || undef; my $filename; # # Build up a suitable filename. # if ( $CONFIG{ 'sequential' } ) { $filename = $count . ".txt"; } else { $filename = $title; $filename =~ s/[^a-z0-9]/_/gi; $filename .= ".txt"; } # # Naive expansion. # if ( $body =~ m/</ ) { $body = decode_entities($body); } $filename = $CONFIG{ 'output' } . "/" . $filename; open( my $handle, ">", $filename ) or die "Failed to write to $filename - $!"; print $handle <{'comments'} = readEntryComments( $file, $blog->{'title'} ); } Essentially we need to have two things for finding the comments for an entry: * The filename. * The title. Sigh. Bad mistake. We want to fetch the comments after the entry, so that we can implement caching in a sensible way. In short comments associated with an entry < $comment-days in the past shouldn't change. chronicle-4.6/blog/0000755000175000017500000000000011557771306012464 5ustar skxskxchronicle-4.6/AUTHORS0000644000175000017500000000345111557771306012614 0ustar skxskx Primary Author -------------- Steve Kemp Contributions ------------- Listed alphabetically by surname. Daniel [varrho [at] com.gmail.com] - Added support for Multimarkdown. Saint Aardvark the Carpeted [aardvark [ta] com.saintaardvarkthecarpeted] - Added the ability to output archives in a data-base directory hierarchy Byron Clark [byron [at] name.theclarkfamily.name] - Support non-GMT timezones Chris Frey [cdfrey [@] net.foursquare] - Added "point-back" support, when running with memcached to significantly cut down on rebuild-time. Josh Grams [josh [@] com.qualdan] - Added better base URL detection. - Added chronological sorting for months & shorter month-URLs. Sascha Korzen [korzen [@] .de.fh-luh.cc] - Added the optional calendar. Matthias Lang [matthias [@] es.corelatus.se] - Made code formatting apply to all code snippets in an entry. Thomas Martin [thomas [@] .org.oopss] - i18n support for date names. - URL prefix support for /sitemap.xml generation. MJ Ray [mjr [@] coop.phonecoop] - Added RSSv1 date format. - Added reltop. Felipe Sateler [fsateler [@] com.gmail] - Produced a Debian package for the software. - Provided fixup for the default comment handling of the default theme. George Schils [bean [at] com.georgeschils] - Initial XHTML cleanup of templates, and suggestion of common .inc files. Quentin Stievenart [quentin.stievenart [at] com.gmail.com] - Provided initial implementation of the syntax-colouring code - Provided useful feedback for the way things developed with the use of filters. Bob Walker [bob [@] uk.org.randomness] - Added titles to RSS feeds Nicolau Werneck [nwerneck [@] com.gmail] - Added UTF-8 support for textile formatted input files. chronicle-4.6/Makefile0000644000175000017500000001412711557771306013206 0ustar skxskx# # Utility makefile for people working with chronicle # # The targets are intended to be useful for people who are using # the remote repository - but it also contains other useful targets. # # Steve # -- # http://www.steve.org.uk/ # # # Only used to build distribution tarballs. # DIST_PREFIX = ${TMP} VERSION = 4.6 BASE = chronicle # # Installation prefix, useful for the Debian package. # prefix= nop: @echo "Valid targets are (alphabetically) :" @echo " " @echo " clean = Remove bogus files and any local output." @echo " demos[*] = Generate and upload demo blogs." @echo " diff = See the local changes." @echo " install = Install upon the local system." @echo " release [*] = Generate a release tarball." @echo " test = Run our simple test cases." @echo " test-verbose = Run our simple test cases, verbosely." @echo " tidy [*] = Tidy the code via perltidy." @echo " update = Update from the remote repository." @echo " " @echo "[*] - These targets are Steve-specific. Probably." @echo " " # # Delete all temporary files, recursively. # clean: @find . -name '.*~' -exec rm \{\} \; @find . -name '.#*' -exec rm \{\} \; @find . -name '*~' -exec rm \{\} \; @find . -name '*.1' -exec rm \{\} \; @find . -name '*.bak' -exec rm \{\} \; @find . -name '*.tmp' -exec rm \{\} \; @if [ -e .version ]; then rm -f .version; fi @if [ -d comments ]; then rm -rf comments; fi @if [ -d output ]; then rm -rf output; fi @if [ -e build-stamp ]; then rm -f build-stamp; fi # # Run perlcritic on our scripts # critic: perlcritic bin/* # # Show what has been changed in the local copy vs. the remote repository. # diff: hg diff # # Install to /usr/bin # # Install the themes without hardwiring a list of them. # install: mkdir -p ${prefix}/etc cp ./etc/chroniclerc ${prefix}/etc/chroniclerc mkdir -p ${prefix}/usr/bin cp ./bin/chronicle ${prefix}/usr/bin cp ./bin/chronicle-ping ${prefix}/usr/bin cp ./bin/chronicle-spooler ${prefix}/usr/bin cp ./bin/chronicle-entry-filter ${prefix}/usr/bin cp ./bin/chronicle-rss-importer ${prefix}/usr/bin mkdir -p ${prefix}/usr/share/chronicle/themes/xml cp -r ./themes/xml/*.* ${prefix}/usr/share/chronicle/themes/xml for i in themes/*/; do \ mkdir -p ${prefix}/usr/share/chronicle/themes/$$(basename $$i) ;\ cp -r ./themes/$$(basename $$i)/*.* ${prefix}/usr/share/chronicle/themes/$$(basename $$i)/ ;\ done # # Make a new release tarball, and make a GPG signature. # release: tidy clean rm -rf $(DIST_PREFIX)/$(BASE)-$(VERSION) rm -f $(DIST_PREFIX)/$(BASE)-$(VERSION).tar.gz cp -R . $(DIST_PREFIX)/$(BASE)-$(VERSION) perl -pi.bak -e "s/UNRELEASED/$(VERSION)/g" $(DIST_PREFIX)/$(BASE)-$(VERSION)/bin/chronicle perl -pi.bak -e "s/UNRELEASED/$(VERSION)/g" $(DIST_PREFIX)/$(BASE)-$(VERSION)/bin/chronicle-entry-filter perl -pi.bak -e "s/UNRELEASED/$(VERSION)/g" $(DIST_PREFIX)/$(BASE)-$(VERSION)/bin/chronicle-spooler rm $(DIST_PREFIX)/$(BASE)-$(VERSION)/bin/*.bak find $(DIST_PREFIX)/$(BASE)-$(VERSION) -name ".hg*" -print | xargs rm -rf find $(DIST_PREFIX)/$(BASE)-$(VERSION) -name ".release" -print | xargs rm -rf find $(DIST_PREFIX)/$(BASE)-$(VERSION)/blog -name "*.txt" -print | xargs rm -rf cd $(DIST_PREFIX) && tar -cvf $(DIST_PREFIX)/$(BASE)-$(VERSION).tar $(BASE)-$(VERSION)/ gzip $(DIST_PREFIX)/$(BASE)-$(VERSION).tar mv $(DIST_PREFIX)/$(BASE)-$(VERSION).tar.gz . rm -rf $(DIST_PREFIX)/$(BASE)-$(VERSION) gpg --armour --detach-sign $(BASE)-$(VERSION).tar.gz echo $(VERSION) > .version # # Tidy the code # tidy: if [ -x ~/bin/perltidy ]; then \ ~/bin/perltidy ./bin/chronicle-ping ./bin/chronicle-entry-filter ./bin/chronicle-spam-test ./bin/chronicle-spooler ./bin/chronicle-rss-importer ./bin/chronicle ./cgi-bin/comments.cgi \ ; fi # # Run the test suite. # test: prove --shuffle tests/ # # Run the test suite verbosely. # test-verbose: prove --shuffle --verbose tests/ # # Update the local copy from the remote repository. # update: hg pull --update # # Generate the demo blogs. # demos: ./bin/chronicle --theme-dir=./themes --theme=default --url-prefix=http://www.steve.org.uk/Software/chronicle/demo/ --pre-build="/bin/rm -rf ./output" --post-build="rsync -q -r output/* s-steve@www.steve.org.uk:htdocs/Software/chronicle/demo/" --no-comments --no-cache --blog-title="Sample Blog" --blog-subtitle="Created by Chronicle" --date-archive-path ./bin/chronicle --theme-dir=./themes --theme=copyrighteous --url-prefix=http://www.steve.org.uk/Software/chronicle/demo2/ --pre-build="/bin/rm -rf ./output" --post-build="rsync -q -r output/* s-steve@www.steve.org.uk:htdocs/Software/chronicle/demo2/" --no-comments --no-cache --blog-title="Sample Blog" --blog-subtitle="Created by Chronicle" ./bin/chronicle --date-archive-path --theme-dir=./themes --theme=blocky --url-prefix=http://www.steve.org.uk/Software/chronicle/demo3/ --pre-build="/bin/rm -rf ./output" --post-build="rsync -q -r output/* s-steve@steve.org.uk:htdocs/Software/chronicle/demo3/" --no-comments --no-cache --blog-title="Sample Blog" --blog-subtitle="Created by Chronicle" ./bin/chronicle --theme-dir=./themes --theme=leftbar --url-prefix=http://www.steve.org.uk/Software/chronicle/demo4/ --pre-build="/bin/rm -rf ./output" --post-build="rsync -q -r output/* s-steve@www.steve.org.uk:htdocs/Software/chronicle/demo4/" --no-comments --no-cache --blog-title="Sample Blog" --blog-subtitle="Created by Chronicle" ./bin/chronicle --theme-dir=./themes --theme=simple --url-prefix=http://www.steve.org.uk/Software/chronicle/demo5/ --pre-build="/bin/rm -rf ./output" --post-build="rsync -q -r output/* s-steve@www.steve.org.uk:htdocs/Software/chronicle/demo5/" --no-comments --no-cache --blog-title="Sample Blog" --blog-subtitle="Created by Chronicle" ./bin/chronicle --theme-dir=./themes --theme=simple --url-prefix=http://www.steve.org.uk/Software/chronicle/demo6/ --pre-build="/bin/rm -rf ./output" --post-build="rsync -q -r output/* s-steve@www.steve.org.uk:htdocs/Software/chronicle/demo6/" --no-comments --no-cache --blog-title="Sample Blog" --blog-subtitle="Created by Chronicle [With --lang=French]" --lang=french chronicle-4.6/MIGRATING0000644000175000017500000000104211557771306013002 0ustar skxskx Migrating to Chronicle ---------------------- Included with this release is the utility script `chronicle-rss-importer` which will allow you to import any entries which are available as an RSS feed from your current platform. Usage is as simple as: chronicle-rss-importer --feed=http://blog.steve.org.uk/index.rss \ --output=/path/to/write/entries/to [--sequential] This will fetch the remote feed, parse it into individual entries and write each one out to a new file beneath the specified output directory. Steve --