File Coverage

blib/lib/Postfix/Parse/Mailq.pm
Criterion Covered Total %
statement 37 40 92.5
branch 9 16 56.2
condition 7 12 58.3
subroutine 5 5 100.0
pod 2 2 100.0
total 60 75 80.0


line stmt bran cond sub pod time code
1 1     1   22228 use strict;
  1         2  
  1         35  
2 1     1   5 use warnings;
  1         1  
  1         60  
3             package Postfix::Parse::Mailq;
4             # ABSTRACT: parse the output of the postfix mailq command
5             $Postfix::Parse::Mailq::VERSION = '1.004';
6 1     1   871 use Mixin::Linewise::Readers -readers;
  1         26617  
  1         7  
7              
8             # =head1 SYNOPSIS
9             #
10             # use Postfix::Parse::Mailq;
11             #
12             # my $mailq_output = `mailq`;
13             # my $entries = Postfix::Parse::Mailq->read_string($mailq_output);
14             #
15             # my $bytes = 0;
16             # for my $entry (@$entries) {
17             # next unless grep { /\@aol.com$/ } @{ $entry->{remaining_rcpts} };
18             # $bytes += $entry->{size};
19             # }
20             #
21             # print "$bytes bytes remain to send to AOL destinations\n";
22             #
23             # =head1 WARNING
24             #
25             # This code is really rough and the interface will change. Entries will be
26             # objects. There will be some more methods. Still, the basics are likely to
27             # keep working, or keep pretty close to what you see here now.
28             #
29             # =method read_file
30             #
31             # =method read_handle
32             #
33             # =method read_string
34             #
35             # my $entries = Postfix::Parse::Mailq->read_string($string, \%arg);
36             #
37             # This methods read the output of postfix's F from a file (by name), a
38             # filehandle, or a string, respectively. They return an arrayref of hashrefs,
39             # each hashref representing one entry in the queue as reported by F.
40             #
41             # Valid arguments are:
42             #
43             # spool - a hashref of { queue_id -> spool_name } pairs
44             # if given, this will be used to attempt to indicate in which
45             # spool messages currently are; it is not entirely reliable (race!)
46             #
47             # =cut
48              
49             sub read_handle {
50 2     2 1 4853 my ($self, $handle, $arg) = @_;
51 2   100     12 $arg ||= {};
52 2   100     11 $arg->{spool} ||= {};
53              
54 2         80 my $first = $handle->getline;
55              
56 2         100 chomp $first;
57 2 50       12 return [] if $first eq 'Mail queue is empty';
58              
59 2 50       17 Carp::confess("first line did not appear to be first line of mailq output")
60             unless $first =~ m{\A-+Queue ID-+};
61              
62 2         6 my @current;
63             my @entries;
64 2         51 LINE: while (my $line = $handle->getline) {
65 30 100       850 if ($line eq "\n") {
66 8         22 my $entry = $self->parse_block(\@current);
67 8 50       40 $entry->{spool} = $arg->{spool}{ $entry->{queue_id} } if $arg->{spool};
68 8         12 push @entries, $entry;
69 8         12 @current = ();
70 8         194 next LINE;
71             }
72              
73 22         492 push @current, $line;
74             }
75              
76 2 50 33     84 if (@current and $current[0] !~ /^-- \d+ .?bytes/i) {
77 0         0 my $entry = $self->parse_block(\@current);
78 0 0       0 $entry->{spool} = $arg->{spool}{ $entry->{queue_id} } if $arg->{spool};
79 0         0 push @entries, $entry;
80             }
81              
82 2         20 return \@entries;
83             }
84              
85             # =method parse_block
86             #
87             # my $entry = Mailq->parse_block(\@lines);
88             #
89             # Given all the lines in a single entry's block of lines in mailq output, this
90             # returns data about the entry.
91             #
92             # =cut
93              
94             my %STATUS_FOR = (
95             '!' => 'held',
96             '*' => 'active',
97             );
98              
99             sub parse_block {
100 8     8 1 14 my ($self, $block) = @_;
101              
102 8         15 chomp @$block;
103 8         12 my $first = shift @$block;
104 8 50 33     47 my $error = defined $block->[0] && $block->[0] =~ /\A\S/
105             ? (shift @$block)
106             : undef;
107 8         17 my @dest = map { s/^\s+//; $_; } @$block;
  12         48  
  12         35  
108              
109 8         62 my ($qid, $status_chr, $size, $date, $sender) = $first =~ m/
110             \A
111             ([A-F0-9]+|[0-9B-Zb-z]+)
112             ([*!])?
113             \s+
114             (\d+)
115             \s+
116             (.{19})
117             \s+
118             (\S.+)
119             \z
120             /x;
121              
122 8 100 50     30 my $status = $status_chr ? ($STATUS_FOR{$status_chr} || 'unknown') : 'queued';
123              
124             return {
125 8         55 queue_id => $qid,
126             status => $status,
127             size => $size,
128             date => $date,
129             sender => $sender,
130             error_string => $error,
131             remaining_rcpts => \@dest,
132             }
133             }
134              
135             1;
136              
137             __END__