#!/usr/bin/perl use warnings; use strict; =head1 NAME daizu - command line interface to Daizu CMS =head1 SYNOPSIS export DAIZU_CONFIG=/etc/my-daizu-config.xml # Get a working copy daizu checkout # Bring it up to date with new revisions daizu update # Publish any new stuff daizu publish # Load new revisions (done automatically when updating # a working copy, so not normally needed) daizu load-revisions # Manually reload articles (not normally necessary) daizu update-article example.com/blog/article.html daizu update-all-articles # Manual URL updates (not normally necessary) daizu update-urls example.com/blog daizu update-all-urls # Manual publishing (not normally necessary) daizu publish-url http://example.com/page.html daizu publish-site http://example.com/ # Publish to stdout (for testing) daizu url-content http://example.com/ >homepage.html =head1 DESCRIPTION This program allows you to operate Daizu, getting it to load content from the Subversion repository (by checking out or updating working copies), and publish the URLs generated by that content. A Daizu configuration file is required. You can specify where yours is by setting the C environment variable to its path. You can also provide the value in the C<-c> option when you run C. The following subcommands are available: =over =cut use Getopt::Std qw( getopts ); use DateTime; use Carp::Assert qw( assert DEBUG ); use Daizu; use Daizu::Wc; use Daizu::File; use Daizu::Publish qw( update_live_sites publish_urls publish_redirect_map publish_gone_map ); use Daizu::Util qw( like_escape db_row_exists db_row_id db_select db_insert transactionally instantiate_generator update_all_file_urls ); my %opt; getopts('c:r:', \%opt) or usage(); usage() unless @ARGV; my $command = shift @ARGV; my %COMMANDS = ( #add => \&cmd_add, #mkdir => \&cmd_mkdir, #replace => \&cmd_replace, 'checkout' => \&cmd_checkout, 'load-revisions' => \&cmd_load_revisions, 'publish' => \&cmd_publish, 'publish-site' => \&cmd_publish_site, 'publish-url' => \&cmd_publish_url, 'update' => \&cmd_update, 'update-all-articles' => \&cmd_update_all_articles, 'update-all-urls' => \&cmd_update_all_urls, 'update-article' => \&cmd_update_article, 'update-urls' => \&cmd_update_urls, 'url-content' => \&cmd_url_content, ); usage() unless exists $COMMANDS{$command}; my $cms = Daizu->new($opt{c}); $COMMANDS{$command}->($cms, \%opt, @ARGV); =item load-revisions Load new revisions from the content repository, up to the latest revision. If the C<-r> option is given then it specifies a revision number to load up to instead. Revisions are automatically loaded when working copies are checked out and updated, so you won't normally need to do this. =cut sub cmd_load_revisions { my ($cms, $opt) = @_; my $revnum = $cms->load_revision($opt{r}); print "Loaded revisions up to r$revnum\n"; } =item checkout [branch] Create a new working copy in the database and bring it up to the latest revision of the content repository, or to the revision specified by the C<-r> option. An additional argument can be specified, which should identify a branch to check out from. It can be either the path of the branch in the repository (something like I) or the ID number of a branch in the database. The default is I. =cut sub cmd_checkout { my ($cms, $opt, $branch) = @_; $branch = 'trunk' unless defined $branch; my $wc = Daizu::Wc->checkout($cms, $branch, $opt{r}); print "Checked out working copy ", $wc->id, "\n"; } =item update [wc-id] Bring a working copy up to date with the latest revision, or the revision specified by the C<-r> option. If the extra argument is given then the working copy specified by that ID number is updated (it should be the C column of the C table). By default the live working copy is updated. =cut sub cmd_update { my ($cms, $opt, $wc_id) = @_; my $wc = Daizu::Wc->new($cms, $wc_id); my $new_revnum = $wc->update($opt{r}); print "Updated working copy ", $wc->id, " to revision $new_revnum\n"; } =item publish Bring the live website (or sites) up to date with the latest changes in the live working copy in the database. After committing a new revision and using the C command to update the live WC, running this should do everything necessary to update the sites (except in exceptional circumstances like when you've changed custom templates). If you provide a C<-r> option with a revision number, then that revision will be used as the starting point for figuring out what changes need to be published. The default is to start from the last revision which was published in this way. There is no way to change the revision which the output is updated I, it's always whatever is in the live working copy. =cut sub cmd_publish { my ($cms, $opt) = @_; update_live_sites($cms, $opt{r}); } =item publish-url url [wc-id] Publish the given URL, writing its output however is specified by the configuration file. This will fail if there isn't a suitable C element in the configuration file to specify the document root. The extra argument gives the ID number of the working copy to get the content from, and defaults to the live working copy. =cut sub cmd_publish_url { my ($cms, $opt, $url, $wc_id) = @_; my $db = $cms->db; my $wc = Daizu::Wc->new($cms, $wc_id); $wc_id = $wc->id; my $url_info = $db->selectrow_hashref(q{ select * from url where wc_id = ? and url = ? and status = 'A' }, undef, $wc_id, $url); die "no url '$url' found\n" unless defined $url_info; $url_info = { %$url_info }; publish_guid_urls($cms, $wc_id, $url_info->{guid_id}, $url_info->{generator}, $url_info->{method}, [ $url_info ]); } # This has never worked. Working copies can't be edited without corrupting them. sub cmd_add { my ($cms, $opt, $wc_id, $path, $filename) = @_; my $data = load_file($filename); my $wc = Daizu::Wc->new($cms, $wc_id); my $file_id = $wc->add_file($path, \$data); print "Saved new file with ID $file_id\n"; } # This has never worked. Working copies can't be edited without corrupting them. sub cmd_mkdir { my ($cms, $opt, $wc_id, $path) = @_; my $wc = Daizu::Wc->new($cms, $wc_id); my $file_id = $wc->add_directory($path); print "Created new directory with ID $file_id\n"; } # This has never worked. Working copies can't be edited without corrupting them. sub cmd_replace { my ($cms, $opt, $wc_id, $path, $filename) = @_; my $data = load_file($filename); my $wc = Daizu::Wc->new($cms, $wc_id); my $file_id = db_row_id($cms->{db}, 'wc_file', path => $path, is_dir => 0); die "$0: file '$path' doesn't exist, or is a directory\n" unless defined $file_id; $wc->change_file_content($file_id, \$data); print "Replaced content for file with ID $file_id\n"; } =item update-urls path [wc-id] Generate the URLs for file at the given path. The resulting URLs are stored in the database and assumed to have been published, so you'd better actually publish any new ones straight after doing this. The extra argument specifies the ID number of the working copy to generate URLs for, and defaults to the live working copy. =cut sub cmd_update_urls { my ($cms, $opt, $path, $wc_id) = @_; my $wc = Daizu::Wc->new($cms, $wc_id); my $file = $wc->file_at_path($path); $file->update_urls_in_db; } =item update-all-urls [wc-id] Same as C above, but generates URLs for all files which currently exist in the working copy. Note that currently URLs which are no longer attached to an extant file will not be marked 'gone' as they should be. Also note that if this fails half-way through it may leave the database partially updated. =cut sub cmd_update_all_urls { my ($cms, $opt, $wc_id) = @_; my $db = $cms->db; my $wc = Daizu::Wc->new($cms, $wc_id); my ($new_redirects, $new_gone) = update_all_file_urls($cms, $wc->{id}); # TODO - new return vals for update_all_file_urls _update_redirect_maps($cms, $wc->id) if $new_redirects; _update_gone_maps($cms, $wc->id) if $new_gone; } sub _update_redirect_maps { my ($cms, $wc_id) = @_; while (my ($url, $config) = each %{$cms->{output}}) { next unless defined $config->{redirect_map}; publish_redirect_map($cms, $wc_id, $config); } } sub _update_gone_maps { my ($cms, $wc_id) = @_; while (my ($url, $config) = each %{$cms->{output}}) { next unless defined $config->{gone_map}; publish_gone_map($cms, $wc_id, $config); } } =item update-article path [wc-id] Reload the article at the specified path, using the appropriate article loader plugin, and cache the resulting content and metadata. This should normally be done automatically when a working copy is updated. The extra argument specifies the ID number of the working copy in which to look for the path, and defaults to the live working copy. =cut sub cmd_update_article { my ($cms, $opt, $path, $wc_id) = @_; my $wc = Daizu::Wc->new($cms, $wc_id); my $file = $wc->file_at_path($path); die "Can't update '$path', not an article\n" unless $file->{article}; $file->update_loaded_article_in_db; } =item update-all-articles [wc-id] Same as C above, but reloads all article files which currently exist in the working copy. The whole thing is done in a single database transaction. =cut sub cmd_update_all_articles { my ($cms, $opt, $wc_id) = @_; transactionally($cms->db, sub { my $wc = Daizu::Wc->new($cms, $wc_id); my $sth = $cms->db->prepare(q{ select id from wc_file where wc_id = ? and article order by path }); $sth->execute($wc->id); while (my ($file_id) = $sth->fetchrow_array) { Daizu::File->new($cms, $file_id)->update_loaded_article_in_db; } }); } =item url-content url [wc-id] Generate the content for the specified URL, and print it to the standard output. Doesn't update the database or publish the content anywhere. Takes the content from the specified working copy, or the live working copy by default. =cut sub cmd_url_content { my ($cms, $opt, $url, $wc_id) = @_; my $db = $cms->db; my $wc = Daizu::Wc->new($cms, $wc_id); my ($guid_id, $method, $argument, $type, $status, $redir_id) = db_select($db, url => { wc_id => $wc->id, url => $url }, qw( guid_id method argument content_type status redirect_to_id ), ); die "$0: URL '$url' does not exist in working copy " . $wc->id . "\n" unless defined $guid_id; die "$0: URL '$url' previously existed but no longer has content\n" if $status eq 'G'; if ($status eq 'R') { my ($redir_url) = db_select($db, url => $redir_id, 'url'); die "$0: URL '$url' redirects to '$redir_url'\n"; } my ($file_id) = db_row_id($db, 'wc_file', wc_id => $wc->id, guid_id => $guid_id, ); die "$0: URL '$url' marked active, but it's content no longer exists\n" unless defined $file_id; my $file = Daizu::File->new($cms, $file_id); my $generator = $file->generator; die "$0: URL '$url' has generator 'none', so it shouldn't exist\n" unless defined $generator; die "$0: generator for '$url' is missing method '$method'\n" unless $generator->can($method); binmode STDOUT or die "error setting binmode on STDOUT: $!"; $generator->$method($file, [ { url => URI->new($url), generator => $file->{generator}, method => $method, argument => $argument, type => $type, fh => \*STDOUT, } ]); } sub publish_guid_urls { my ($cms, $wc_id, $guid_id, $gen_class, $method, $urls) = @_; my $db = $cms->db; my ($file_id, $root_file_id) = db_select($db, 'wc_file', { wc_id => $wc_id, guid_id => $guid_id }, qw( id root_file_id ), ); die "$0: URLs of GUID $guid_id marked active, but file no longer exists\n" unless defined $file_id; my $file = Daizu::File->new($cms, $file_id); my $root_file = $file; $root_file = Daizu::File->new($cms, $root_file_id) if defined $root_file_id; my $generator = instantiate_generator($cms, $gen_class, $root_file); die "$0: generator '$gen_class' is missing method '$method'\n" unless $generator->can($method); publish_urls($cms, $file, $generator, $method, $urls); } =item publish-site base-url [wc-id] Generates content for all URLs which start with the base URL given. For example, if base-url is L then all of the URLs on that domain will be generated. The content is written to the proper output location as for the C command. =cut sub cmd_publish_site { my ($cms, $opt, $base_url, $wc_id) = @_; my $db = $cms->db; my $wc = Daizu::Wc->new($cms, $wc_id); $wc_id = $wc->id; my $sth = $db->prepare(q{ select * from url where wc_id = ? and url like ? and status = 'A' order by guid_id, generator, method }); $sth->execute($wc_id, like_escape($base_url) . '%'); my $cur_guid_id; my $cur_generator; my $cur_method; my @urls; while (my $r = $sth->fetchrow_hashref) { if (@urls && ($r->{guid_id} != $cur_guid_id || $r->{generator} ne $cur_generator || $r->{method} ne $cur_method)) { publish_guid_urls($cms, $wc_id, $cur_guid_id, $cur_generator, $cur_method, \@urls); @urls = (); } $cur_guid_id = $r->{guid_id}; $cur_generator = $r->{generator}; $cur_method = $r->{method}; push @urls, { %$r }; } publish_guid_urls($cms, $wc_id, $cur_guid_id, $cur_generator, $cur_method, \@urls) if @urls; _update_redirect_maps($cms, $wc->id); _update_gone_maps($cms, $wc->id); } sub load_file { my ($filename) = @_; return do { open my $fh, '<', $filename or die "$0: error opening input file '$filename': $!\n"; binmode $fh; local $/; <$fh>; }; } sub usage { print STDERR "Usage: $0 [OPTIONS] COMMAND [ARGS...]\n"; exit 1; } =back =head1 COPYRIGHT This software is copyright 2006 Geoff Richards Egeoff@laxan.comE. For licensing information see this page: L =cut # vi:ts=4 sw=4 expandtab