I'm a big fan of the Tao te Ching, and after finding a free version (called GNL), I decided to write a tool that would give me a new chapter each day I ran it, in order, and start over when I reached the end.

I personally use this in a cron job to change my /etc/motd message.

When run for the first time with either the --daily or --chapter parameters, it will retrieve a copy of the GNL from the web, parse it into its chapters (saving the license to 'license.txt'), and store a YAML cache of that work. If you're running from behind a proxy, you can use some other proxy-enabled tool to save the GNL from the link in the first paragraph to a file named 'taote-v4.html' -- when put in the exec directory, the app will prefer that local copy.

--today causes the program to print the next chapter of the TTC if it's not the same day as the last time it was run.

--chapter lets you get a specific chapter (see usage for details)

To clean up and start fresh, use the --reset parameter.

Run without parameters or with '--usage', '--help', '-h', or '-?' for a usage message.

Depends on CPAN modules YAML-Syck (fast libsyck) or YAML (slower, pure-perl), and HTML-TokeParser-Simple (for parsing the HTML version of GNL). The last dependency is only needed for parsing the HTML document -- it is not needed once tao.yaml exists.

#!/usr/local/bin/perl
use strict;
use warnings;
BEGIN {
# Try to load YAML::Syck (faster, preferred), fall back to YAML othe+rwise
# thanks to PerlMonks' shmem for pointing out potential YAML::Syck t+rouble
eval {
require YAML::Syck;
YAML::Syck->import( qw[LoadFile DumpFile] );
};
if ($@) {
# fall back to YAML
require YAML;
YAML->import( qw[LoadFile DumpFile] );
}
}
use Getopt::Long;
# how do we want to operate?
usage() unless @ARGV;
GetOptions(
'help|usage|?|h' => \&usage,
'reset' => sub { for (<*.yaml>) { unlink while -f } },
'today' => \&daily_tao,
'chapter' => \&my_tao,
'license' => sub { print join('',<DATA>); exit; },
);
#=====================================================================+===
# load_tao - loads or regenerates cache of tao chapters, returns as li+st
sub load_tao {
if ( -f 'tao.yaml') {
# load chapters from disk cache, if we have them
return @{ LoadFile('tao.yaml') };
}
else {
# otherwise, get them from the web and build the cache
return @{ get_web_tao() };
}
}
#=====================================================================+===
# daily_tao - shows a new dao chapter if the date has advanced by at
# least one day since the last run. Uses 'day.yaml' for
# cache.
sub daily_tao {
my @chapter = load_tao();
my $day_index = 0;
my $time = time;
my $now = $time;
# load last run info, if any.
if ( -f 'day.yaml' ) {
($day_index, $time) = @{ LoadFile('day.yaml') }
}
my @now = localtime($now);
my @last = localtime($time);
if ( $last[7] != $now[7] || $last[5] != $now[5]) {
# it's not the same day as last we ran
$day_index++;
if ($day_index > $#chapter) { $day_index = 0 } # roll over
$time = $now; # we will record the *current* time
}
printf "%s: %s\n%s", @{ $chapter[$day_index] };
DumpFile('day.yaml', [ $day_index, $time ]);
}
#=====================================================================+===
# my_tao - shows the chapter specified on the command line ($ARGV[0])
# with no parameter, shows the first chapter
# with a numeric parameter, shows chap. for that record numbe+r
# with a paramter ending in '.', shows record with that chap.+ id
# Dies if there's an invalid chap. id or spec.
sub my_tao {
my @chapter = load_tao();
my $chapter = (@ARGV ? shift @ARGV : 0);
if ($chapter =~ /\./) {
# this is a chapter spec
my $chindex = 0;
for (0..$#chapter) {
$chindex = $_;
last if "$chapter[$_][0]." eq $chapter;
}
die "$chapter is not a valid chapter spec\n"
if "$chapter[$chindex][0]." ne $chapter;
$chapter = $chindex;
}
elsif ($chapter =~ /\D/) {
# doesn't look like a spec, but isn't just a number - invalid
die "'$chapter' is neither a valid spec nor a valid numeric id\n";
}
die "The last record is no.$#chapter, but you asked for no.$chapter\+n"
if $chapter > $#chapter;
printf "%s: %s\n%s", @{$chapter[$chapter]};
}
#=====================================================================+===
# get_web_tao - retrieves the "GNL" tao te ching from the web and
# creates the disk cache of the chapters in 'tao.yaml'
# if a local file 'taote-v4.html' exists, will use that
# instead of fetching from the web
sub get_web_tao {
require HTML::TokeParser::Simple;
my $p = HTML::TokeParser::Simple->new( -f 'taote-v4.html'
?(file => 'taote-v4.html')
:(url => 'http://acc6.its.brooklyn.cuny.edu/~phalsall/texts/taote-+v4.html')
);
# skip notices and such, but record them to license.txt
my $LIC;
while (my $token = $p->get_token) {
last if $token->is_end_tag('h2');
if ($token->is_start_tag('pre')) {
# start of license text and copyright notice
open $LIC, '>license.txt' or die ("Can't write license.txt: $!")+;
}
elsif ($token->is_end_tag('pre')) {
# end of license text and copyright notice
close $LIC; undef $LIC;
}
elsif ($token->is_text && defined $LIC) {
# body text for license and copyright
print $LIC $token->as_is;
}
}
# add chapters to data struct in form [id, title, text]
my @chapter;
while (my $token = $p->get_token)
{
next unless $token->is_start_tag('h3'); #title start
(my $title = $p->peek(1)) =~ s{^(.+?)\.\s}{}g;
my $num = $1;
my $text = '';
#finish end of title
while ($token = $p->get_token) { last if $token->is_end_tag('h3') +}
#grab text *as* text
while ($token = $p->get_token) {
last if $p->peek =~ /<h3>/i;
next if $token->is_tag;
$text.=$token->as_is;
}
push @chapter, [ $num, $title, $text ];
}
DumpFile('tao.yaml', \@chapter);
return \@chapter;
}
#=====================================================================+===
# usage - prints a nice usage message and exits the app
sub usage {
print <<USAGE_DOC;
Daily Tao te Ching chapter printer.
Uses the GNL version of the Tao Te Ching (TTC) from:
http://acc6.its.brooklyn.cuny.edu/~phalsall/texts/taote-v4.html
See the file license.txt after first run for the GNL license.
$0 [--today] [--chapter id|spec] [--reset]
$0 --license
$0 [--help] [--usage] [-h] [-?]
--chapter Display a specific chapter from the TTC, either by recor+d id
(0-81) or by chapter number followed by a period. For ex+ample,
to see TTC chapter 64b, use --chapter 64b. (including th+e
period). Displays the last chapter if an invalid chapte+r
number is specified.
--today If the date has advanced at least one day since the last+ time
we got a new chapter, display the next chapter from the +TTC.
--reset Delete all the disk caches: this will cause the next run+ (or
this run, if followed by one of the other options) to st+art
at the beginning of the TTC and fetch a new copy from th+e web
(or from the local file taote-v4.html)
--license Display the license for this application (license for th+e GNL
text is saved to license.txt once it is retrieved)
--usage Display this message: aliases are --help, -h, and -?
USAGE_DOC
exit;
}
__DATA__
Copyright (c) 2006 RadiantMatrix (http://radiantmatrix.org)
Permission is hereby granted, free of charge, to any person obtaining +a copy
of this software and associated documentation files (the "Software"), +to deal
in the Software without restriction, including without limitation the +rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or s+ell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be include+d in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRES+S OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILIT+Y,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHAL+L THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISIN+G FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS+ IN
THE SOFTWARE.
((See license.txt once the GNL has been retrieved for the GNL license +terms))