File Coverage

blib/lib/Log/Dispatch/Atom.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             # @(#) $Id: Atom.pm 1102 2005-12-07 14:33:19Z dom $
2              
3             package Log::Dispatch::Atom;
4              
5             $VERSION = 0.03;
6              
7 1     1   37552 use warnings;
  1         3  
  1         39  
8 1     1   6 use strict;
  1         2  
  1         36  
9              
10 1     1   5 use Carp qw( carp croak );
  1         1  
  1         54  
11 1     1   6 use Fcntl qw( :flock );
  1         1  
  1         139  
12 1     1   961 use POSIX qw( strftime );
  1         12529  
  1         6  
13 1     1   2812 use Params::Validate qw( validate SCALAR HASHREF );
  1         12798  
  1         111  
14 1     1   1028 use Sys::Hostname qw( hostname );
  1         1251  
  1         78  
15 1     1   1603 use XML::Atom 0.15; # We need add_entry(mode=>insert).
  0            
  0            
16             use XML::Atom::Entry;
17             use XML::Atom::Feed;
18             use XML::Atom::Person;
19              
20             use base qw( Log::Dispatch::Output );
21              
22             # We don't want complaints about this; classes below will check the
23             # remaining args.
24             Params::Validate::validation_options( allow_extra => 1 );
25              
26             sub new {
27             my $class = shift;
28             my %p = @_;
29              
30             my $self = bless {}, $class;
31             # Log::Dispatch setup.
32             $self->_basic_init( %p );
33             # Our setup.
34             $self->_init( %p );
35             return $self;
36             }
37              
38             sub _init {
39             my $self = shift;
40             my %p = validate(
41             @_,
42             {
43             file => { type => SCALAR },
44             feed_id => { type => SCALAR, optional => 1 },
45             feed_title => { type => SCALAR, optional => 1 },
46             feed_author => { type => HASHREF, optional => 1 },
47             }
48             );
49             $self->{ file } = $p{ file };
50             $self->{ feed_id } = $p{ feed_id } if $p{ feed_id };
51             $self->{ feed_title } = $p{ feed_title } if $p{ feed_title };
52             $self->{ feed_author } = $p{ feed_author } if $p{ feed_author };
53             return;
54             }
55              
56             sub _now_datetime { strftime "%Y-%m-%dT%H:%M:%SZ", gmtime }
57             sub _now_date { strftime "%Y-%m-%d", gmtime }
58              
59             sub log_message {
60             my $self = shift;
61             my %p = validate(
62             @_,
63             {
64             id => { type => SCALAR, default => $self->_default_id },
65             author => { type => HASHREF, optional => 1 },
66             }
67             );
68             my $fh = eval {
69             my $fh = $self->_lock_and_open();
70             my $feed = $self->_get_feed_from_handle( $fh );
71             $self->_new_entry( $feed, \%p );
72             $self->_write_feed( $fh, $feed );
73             return $fh;
74             };
75              
76             # Take care to avoid clobbering $@.
77             my $err = $@;
78             eval { $self->_unlock_and_close( $fh ) };
79             die $err if $err;
80             return;
81             }
82              
83             {
84             my $i;
85             sub _default_id {
86             my $self = shift;
87             return sprintf( "tag:%s,%s:%d/%d/%d",
88             hostname(), $self->_now_date(), time(), $$, ++$i );
89             }
90             }
91              
92             sub _get_feed_from_handle {
93             my $self = shift;
94             my ( $fh ) = @_;
95              
96             my $size = ( stat( $fh ) )[7];
97             if ( $size > 0 ) {
98             return XML::Atom::Feed->new( $fh );
99             }
100             else {
101             return $self->_new_feed();
102             }
103             }
104              
105             sub _new_feed {
106             my $self = shift;
107             my $feed = XML::Atom::Feed->new( Version => '1.0' );
108             $feed->id( $self->{ feed_id } ) if $self->{ feed_id };
109             $feed->title( $self->{ feed_title } ) if $self->{ feed_title };
110             if ( my $author = $self->{ feed_author } ) {
111             $feed->author( $self->_new_person( $author ) );
112             }
113             return $feed;
114             }
115              
116             sub _new_person {
117             my $self = shift;
118             my ( $args ) = @_;
119             my $person = XML::Atom::Person->new( Version => '1.0' );
120             $person->name( $args->{ name } ) if $args->{ name };
121             $person->email( $args->{ email } ) if $args->{ email };
122             $person->uri( $args->{ uri } ) if $args->{ uri };
123             return $person;
124             }
125              
126             sub _new_entry {
127             my $self = shift;
128             my ( $feed, $args ) = @_;
129             my $entry = XML::Atom::Entry->new( Version => '1.0' );
130             $entry->title( "$args->{message}" );
131             $entry->content( "$args->{message}" );
132             $entry->id( "$args->{id}" );
133              
134             my $now = _now_datetime();
135             $feed->updated( $now );
136             $entry->updated( $now );
137              
138             if ( $args->{ author } ) {
139             $entry->author( $self->_new_person( $args->{ author } ) );
140             }
141              
142             $feed->add_entry( $entry, { mode => 'insert' } );
143             return $entry;
144             }
145              
146             sub _write_feed {
147             my $self = shift;
148             my ( $fh, $feed ) = @_;
149              
150             seek $fh, 0, 0;
151             print $fh $feed->as_xml();
152             return;
153             }
154              
155             sub _lock_and_open {
156             my $self = shift;
157             open my $fh, '+<', $self->{ file }
158             or croak "open($self->{file}): $!";
159             flock( $fh, LOCK_EX )
160             or croak "flock($self->{file}): $!";
161             return $fh;
162             }
163              
164             sub _unlock_and_close {
165             my $self = shift;
166             my ( $fh ) = @_;
167             return unless $fh;
168              
169             flock( $fh, LOCK_UN )
170             or carp "unlock($self->{file}): $!";
171             close( $fh )
172             or carp "close($self->{file}): $!";
173             return;
174             }
175              
176             1;
177             __END__