package SVN::Web::action; our $VERSION = 0.49; use POSIX qw(); use Time::Local qw(timegm_nocheck); use Time::Zone qw(); use SVN::Core; =head1 NAME SVN::Web::action - base class for SVN::Web::actions =head1 DESCRIPTION This is the base class for all SVN::Web actions. It provides a constructor and some useful utility methods that actions may find useful. It also contains documentation for anyone interested in writing new SVN::Web actions. =head1 OVERVIEW SVN::Web actions are Perl modules loaded by SVN::Web. They are expected to retrieve some information from the Subversion repository, and return that information ready for the user's browser, optionally via formatting by a Template::Toolkit template. Action names are listed in the SVN::Web configuration file, F, in the C clause. Each entry specifies the class that implements the action, and any options that are set globally for that action. actions: ... new_action: class: Class::That::Implements::Action opts: option1: value1 option2: value2 ... Each action is a class that must implement a C method. =cut sub new { my $class = shift; my $self = bless {}, $class; %$self = @_; return $self; } =head1 SUBCLASSING Actions should derive from L. This gives them a default constructor that generates a hash based object. use base 'SVN::Web::action'; =head1 METHODS =head2 run() The C method is where the action carries out its work. =head3 Parameters The method is passed a single parameter, the standard C<$self> hash ref. This contains numerous useful keys. =over 4 =item $self->{opts} The options for this action from F. Using the example from the L, this would lead to: $self->{opts} = { 'option1' => 'value1', 'option2' => 'value2', }; =item $self->{cgi} An instance of a CGI object corresponding to the current request. This is normally an object from either the L or L modules, although it is possible to specify another class with the C directive in F. You can use this object to retrieve the values of any parameters passed to your action. For example, if your action takes a C parameter, indicating the repository revision to work on; my $rev = $self->{cgi}->param('rev'); =item $self->{path} The path in the repository that was passed to the action. =item $self->{navpaths} A reference to an array of path components, one for each directory (and possible final file) in $self->{path}. Equivalent to S{path}) ] >>> =item $self->{config} The config hash, as read by L from F. Directives from the config file are second level hash keys. For example, the C configuration directive contains a list of valid actions. my @valid_actions = @{ $self->{config}->{actions} }; =item $self->{reposname} The symbolic name of the repository being accessed. =item $self->{repos} A instance of the L class, corresponding to the repository being accessed. This repository has already been opened. For example, to find the youngest (i.e., most recent) revision of the repository; my $yr = $self->{repos}->fs()->youngest_rev(); =item $self->{action} The action that has been requested. It's possible for multiple action names to be mapped to a single class in the config file, and this lets you differentiate between them. =item $self->{script} The URL for the currently running script. =back =head3 Return value The return value from C determines how the data from the action is displayed. =head4 Using a template If C wants a template to be displayed containing formatted data from the method then the hash ref should contain two keys. =over 4 =item template This is the name of the template to return. By convention the template and the action share the same name. =item data This is a hash ref. The hash keys become variables of the same name in the template. =back The character set and MIME type can also be specified, in the C and C keys. If these values are not specified then they default to C and C respectively. E.g., for an action named C, using a template called C that looks like this:

The youngest interesting revision of [% file %] is [% rev %].

then this code would be appropriate. # $rev and $file set earlier in the method return { template => 'my_action', data => { rev => $rev, file => $file, }, }; =head4 Returning data with optional charset and MIME type If the action does not want to use a template and just wants to return data, but retain control of the character set and MIME type, C should return a hash ref. This should contain a key called C, the value of which will be sent directly to the browser. The character set and MIME type can also be specified, in the C and C keys. If these values are not specified then they default to C and C respectively. E.g., for an action that generates a PNG image from data in the repository (perhaps using L); # $png contains the PNG image, created earlier in the method return { mimetype => 'image/png', body => $png }; =head4 Returning HTML with default charset and MIME type If the action just wants to return HTML in UTF-8, it can return a single scalar that contains the HTML to be sent to the browser. return "

hello, world

"; =head1 UTILITY METHODS The following methods are intended to share common code among actions. =head2 recent_interesting_rev($path, $rev) Given a repository path, and a revision number, returns the most recent interesting revision for the path that is the same as, or older (i.e., smaller) than the revision number. =cut sub recent_interesting_rev { my $self = shift; my $path = shift; my $rev = shift; my $fs = $self->{repos}->fs; my $root = $fs->revision_root($rev); my $hist = $root->node_history($path); $hist = $hist->prev(0); $rev = ($hist->location())[1]; return $rev; } =head2 get_revs() Returns a list of 4 items. In order, they are: =over =item Explicit rev The value of any CGI C parameter passed to the action ($exp_rev). =item Youngest rev The repository's youngest revision ($yng_rev) for the current path. This is not necessarily the same as the repositories youngest revision. =item Actual rev The actual revision ($act_rev) that will be acted on. This is the explicit rev, if it's defined, otherwise it's the youngest rev. =item Head A boolean value indicating whether or not we can be considered to be at the HEAD of the repository ($at_head). =back =cut sub get_revs { my $self = shift; my $path = $self->{path}; my $fs = $self->{repos}->fs(); my $exp_rev = $self->{cgi}->param('rev'); my $yng_rev = $fs->youngest_rev(); my $act_rev = defined $exp_rev ? $self->recent_interesting_rev($path, $exp_rev) : $self->recent_interesting_rev($path, $yng_rev); my $at_head = 0; if(! defined $exp_rev) { $at_head = 1; } else { if($exp_rev == $yng_rev) { $at_head = 1; } } return($exp_rev, $yng_rev, $act_rev, $at_head); } =head2 format_svn_timestamp() Given a cstring that represents a Subversion time, format the time using POSIX::strftime() and the current settings of the C and C configuration directives. =cut my $tz_offset = undef; # Cache the timezone offset sub format_svn_timestamp { my $self = shift; my $cstring = shift; # Note: Buggy on Solaris # my $time = SVN::Core::time_from_cstring($cstring) / 1_000_000; my(@time) = $cstring =~ /^(....)-(..)-(..)T(..):(..):(..)/; my $time = timegm_nocheck($time[5], $time[4], $time[3], $time[2], $time[1] - 1, $time[0]); if($self->{config}->{timezone} eq 'local') { return POSIX::strftime($self->{config}->{timedate_format}, localtime($time)); } if((not defined $tz_offset) and ($self->{config}->{timezone} ne '')) { $tz_offset = Time::Zone::tz_offset($self->{config}->{timezone}); $time += $tz_offset; } return POSIX::strftime($self->{config}->{timedate_format}, gmtime($time)); } =head1 CACHING If the output from the action can usefully be cached then consider implementing a C method. This method receives the same parameters as the C method, and must use those parameters to generate a unique key for the content generated by the C method. For example, consider the standard C action. This action only depends on a single parameter -- the repository revision number. So that makes a good cache key. sub cache_key { my $self = shift; return $self->{cgi}->param('rev'); } Other actions may have more complicated keys. =head1 ERRORS AND EXCEPTIONS If your action needs to fail for some reason -- perhaps the parameters passed to it are incorrect, or the user lacks the necessary permissions, then throw an exception. Exceptions, along with examples, are described in L. =cut # Diff.pm and Revision.pm need to munge the output in Text::Diff::HTML in # the same way. The code lives here for the time being, although it's not # optimal place to put it. sub _munge_html_diff { my $self = shift; my $html = shift; $html =~ s/^ / <\/span>/mg; $html =~ s/ / <\/span>/mg; $html =~ s/\+ /+ <\/span>/mg; $html =~ s/- /- <\/span>/mg; $html =~ s/<\/ins>/<\/span>/mg; $html =~ s/<\/del>/<\/span>/mg; $html =~ s/^- /- <\/span>/mg; $html =~ s/^\+ /+ <\/span>/mg; return $html; } 1;