File Coverage

blib/lib/Net/Trac/TicketHistoryEntry.pm
Criterion Covered Total %
statement 45 103 43.6
branch 5 36 13.8
condition 1 5 20.0
subroutine 9 10 90.0
pod 1 1 100.0
total 61 155 39.3


line stmt bran cond sub pod time code
1 1     1   773 use strict;
  1         2  
  1         35  
2 1     1   5 use warnings;
  1         2  
  1         39  
3              
4             package Net::Trac::TicketHistoryEntry;
5              
6 1     1   832 use Any::Moose;
  1         31012  
  1         21  
7 1     1   918 use Net::Trac::TicketPropChange;
  1         3  
  1         25  
8 1     1   1402 use DateTime;
  1         178877  
  1         39  
9 1     1   1589 use HTTP::Date;
  1         4585  
  1         72  
10 1     1   1872 use URI::Escape qw(uri_escape);
  1         1303  
  1         1470  
11              
12             =head1 NAME
13              
14             Net::Trac::TicketHistoryEntry - A single history entry for a Trac ticket
15              
16             =head1 DESCRIPTION
17              
18             This class represents a single item in a Trac ticket history.
19              
20             =head1 ACCESSORS
21              
22             =head2 connection
23              
24             Returns a L.
25              
26             =head2 author
27              
28             =head2 date
29              
30             Returns a L object.
31              
32             =head2 category
33              
34             =head2 content
35              
36             =head2 prop_changes
37              
38             Returns a hashref (property names as the keys) of
39             Ls associated with this history entry.
40              
41             =head2 attachment
42              
43             if there's attachment, return it, else, return undef
44              
45             =head2 ticket
46              
47             A weak reference to the ticket object for this ticket history entry
48              
49             =head2 is_create
50              
51             A boolean. Returns true if this is the transaction which created the ticket
52              
53             =cut
54              
55             has connection => (
56             isa => 'Net::Trac::Connection',
57             is => 'ro'
58             );
59              
60             has prop_changes => ( isa => 'HashRef', is => 'rw' );
61              
62             has is_create => ( isa => 'Bool', is => 'rw', default => 0 );
63             has author => ( isa => 'Str', is => 'rw' );
64             has date => ( isa => 'DateTime', is => 'rw' );
65             has category => ( isa => 'Str', is => 'rw' );
66             has content => ( isa => 'Str', is => 'rw' );
67             has attachment => ( isa => 'Net::Trac::TicketAttachment', is => 'rw' );
68             has ticket => ( isa => 'Net::Trac::Ticket', is => 'rw', weak_ref => 1 );
69              
70             =head1 METHODS
71              
72             =head2 parse_feed_entry
73              
74             Takes a feed entry from a ticket history feed and parses it to fill
75             out the fields of this class.
76              
77             =cut
78              
79             sub parse_feed_entry {
80 0     0 1 0 my $self = shift;
81 0         0 my $e = shift;
82              
83             # We use a reference to a copy of ticket state as it was after this feed
84             # entry to interpret what "x added, y removed" meant for absolute values
85             # of keywords
86              
87 0         0 my $ticket_state = shift;
88              
89 0 0       0 if ( $e =~ m|(.*?)|is ) {
90 0         0 my $author = $1;
91 0         0 $self->author($author);
92             }
93              
94 0 0       0 if ( $e =~ m|(.*?)|is ) {
95 0         0 my $date = $1;
96 0         0 $self->date( DateTime->from_epoch( epoch => str2time($date) ) );
97             }
98              
99 0 0       0 if ( $e =~ m|(.*?)|is ) {
100 0         0 my $c = $1;
101 0         0 $self->category($c);
102             }
103              
104 0 0       0 if ( $e =~ m|\s*(.*?)\s*|is ) {
105 0         0 my $desc = $1;
106              
107 0 0       0 if ( $desc =~ s|^\s*?<ul>(.*?)</ul>||is ) {
108 0         0 my $props = $1;
109 0         0 $self->prop_changes( $self->_parse_props( $props, $ticket_state ) );
110             }
111              
112 0         0 $desc =~ s/>/>/gi;
113 0         0 $desc =~ s/</
114 0         0 $desc =~ s/&/&/gi;
115 0         0 $self->content($desc);
116             }
117             }
118              
119             sub _parse_props {
120 1     1   875 my $self = shift;
121 1   50     5 my $raw = shift || '';
122 1         2 my $ticket_state = shift;
123 1         5 $raw =~ s/>/>/gi;
124 1         2 $raw =~ s/</
125 1         5 $raw =~ s/&/&/gi;
126              
127             # throw out the wrapping
  • 128 1         23 $raw =~ s|^\s*?
  • (.*)
  • \s*?$|$1|is;
    129 1         9 my @prop_lines = split( m#\s*
  • #s, $raw );
  • 130 1         2 my $props = {};
    131              
    132 1         2 foreach my $line (@prop_lines) {
    133 3         4 my ( $prop, $old, $new );
    134 3 50       8 if ( $line =~ m{attachment} ) {
    135 0         0 my ($name) = $line =~ m!(.*?)!;
    136 0 0       0 my $content =
    137             $self->connection->_fetch( "/attachment/ticket/"
    138             . $self->ticket->id . '/'
    139             . uri_escape($name) )
    140             or next;
    141              
    142 0 0       0 if ( $content =~ m{
    (.+?)
    }is ) {
    143 0         0 my $frag = $1;
    144 0         0 my $att = Net::Trac::TicketAttachment->new(
    145             connection => $self->connection,
    146             ticket => $self->ticket->id,
    147             filename => $name,
    148             );
    149 0         0 $att->_parse_html_chunk($frag);
    150 0         0 $self->attachment($att);
    151             }
    152              
    153 0         0 next;
    154             }
    155 3 50       10 if ( $line =~ m{description} ) {
    156              
    157             # We can't parse trac's crazy "go read a diff on a webpage handling
    158             # of descriptions
    159 0         0 next;
    160             }
    161 3 50       25 if ( $line =~ m{(keywords|cc)(.*)$}is ) {
        50          
        0          
        0          
        0          
    162 0         0 my $value_changes = $2;
    163 0         0 $prop = $1;
    164 0         0 my ( @added, @removed );
    165 0 0       0 if ( $value_changes =~ m{^\s*(.*?) added}is ) {
    166 0         0 my $added = $1;
    167 0         0 @added = split( m{\s*}is, $added );
    168             }
    169              
    170 0 0       0 if ( $value_changes =~ m{(?:^|added;)\s*(.*) removed}is ) {
    171 0         0 my $removed = $1;
    172 0         0 @removed = split( m{\s*?}is, $removed );
    173              
    174             }
    175              
    176 0         0 my @before = ();
    177 0   0     0 my @after = grep defined && length, split( /\s+/, $ticket_state->{keywords} );
    178 0         0 for my $value (@after) {
    179 0 0       0 next if grep { $_ eq $value } @added;
      0         0  
    180 0         0 push @before, $value;
    181             }
    182              
    183 0         0 $old = join( ' ', sort ( @before, @removed ) );
    184 0         0 $new = join( ' ', sort (@after) );
    185 0         0 $ticket_state->{$prop} = $old;
    186             } elsif ( $line =~ m{(.*?)\s+changed\s+from\s+(.*?)\s+to\s+(.*?)}is ) {
    187 3         5 $prop = $1;
    188 3         7 $old = $2;
    189 3         4 $new = $3;
    190             } elsif ( $line =~ m{(.*?)\s+set\s+to\s+(.*?)}is ) {
    191 0         0 $prop = $1;
    192 0         0 $old = '';
    193 0         0 $new = $2;
    194             } elsif ( $line =~ m{(.*?)\s+(.*?)\s+deleted}is ) {
    195 0         0 $prop = $1;
    196 0         0 $old = $2;
    197 0         0 $new = '';
    198             } elsif ( $line =~ m{(.*?)\s+deleted}is ) {
    199 0         0 $prop = $1;
    200 0         0 $new = '';
    201             } else {
    202 0         0 warn "could not parse " . $line;
    203             }
    204              
    205 3 50       6 if ($prop) {
    206 3         27 my $pc = Net::Trac::TicketPropChange->new(
    207             property => $prop,
    208             new_value => $new,
    209             old_value => $old
    210             );
    211 3         86 $props->{$prop} = $pc;
    212             } else {
    213 0         0 warn "I found no prop in $line";
    214             }
    215             }
    216 1         4 return $props;
    217             }
    218              
    219             =head1 LICENSE
    220              
    221             Copyright 2008-2009 Best Practical Solutions.
    222              
    223             This package is licensed under the same terms as Perl 5.8.8.
    224              
    225             =cut
    226              
    227             __PACKAGE__->meta->make_immutable;
    228 1     1   5 no Any::Moose;
      1         3  
      1         9  
    229              
    230             1;