File Coverage

blib/lib/Bryar/DataSource/FlatFile.pm
Criterion Covered Total %
statement 108 129 83.7
branch 22 44 50.0
condition 3 8 37.5
subroutine 16 17 94.1
pod 8 8 100.0
total 157 206 76.2


line stmt bran cond sub pod time code
1             package Bryar::DataSource::FlatFile;
2 3     3   840 use Cwd;
  3         11  
  3         299  
3 3     3   21 use File::Basename;
  3         5  
  3         242  
4 3     3   1004 use Bryar::Document;
  3         7  
  3         56  
5 3     3   3323 use File::Find::Rule;
  3         35538  
  3         29  
6 3     3   243 use 5.006;
  3         13  
  3         115  
7 3     3   17 use strict;
  3         7  
  3         440  
8 3     3   20 use warnings;
  3         6  
  3         127  
9 3     3   19 use Carp;
  3         5  
  3         9246  
10             our $VERSION = '1.2';
11              
12             my %UID_Cache;
13              
14             =head1 NAME
15              
16             Bryar::DataSource::FlatFile - Blog entries from flat files, a la blosxom
17              
18             =head1 SYNOPSIS
19              
20             $self->all_documents(...);
21             $self->search(...);
22             $self->add_comment(...);
23              
24             =head1 DESCRIPTION
25              
26             Just like C, this data source pulls blog entries out of flat
27             files in the file system.
28              
29             =head1 METHODS
30              
31             =head2 all_documents
32              
33             $self->all_documents
34              
35             Returns all documents making up the blog.
36              
37             =cut
38              
39             sub all_documents {
40             # my ($self, $config) = @_;
41             # croak "Must pass in a Bryar::Config object" unless UNIVERSAL::isa($config, "Bryar::Config");
42             # my $where = getcwd;
43             # chdir($config->datadir); # Damn you, F::F::R.
44             # my @docs = map { $self->make_document($_) }
45             # File::Find::Rule->file()
46             # ->name($self->entry_glob)
47             # ->maxdepth($config->depth)
48             # ->in(".");
49             # chdir($where);
50             # return @docs;
51 2     2 1 7 my ($self, $config) = @_;
52 2 50       15 croak "Must pass in a Bryar::Config object" unless UNIVERSAL::isa($config, "Bryar::Config");
53 2         13 my @docs = sort { $b->epoch() <=> $a->epoch() } $self->search($config);
  2         10  
54 2         23 return @docs;
55             }
56              
57             =head2 all_but_recent
58              
59             $self->all_but_recent
60              
61             Return all documented except recent() ones.
62              
63             =cut
64              
65             sub all_but_recent {
66 0     0 1 0 my ($self, $config) = @_;
67 0 0       0 croak "Must pass in a Bryar::Config object" unless UNIVERSAL::isa($config, "Bryar::Config");
68 0         0 my @docs = sort { $b->epoch() <=> $a->epoch() } $self->search($config);
  0         0  
69 0         0 return @docs[$config->recent() .. $#docs];
70             }
71              
72             =head2 entry_glob
73              
74             Returns a glob pattern which matches blog posts. This defaults to C<*.txt>.
75              
76             =cut
77              
78 3     3 1 19 sub entry_glob { "*.txt" }
79              
80             =head2 id_to_file
81              
82             Takes a Bryar ID, converts it to a file name.
83              
84             =head2 file_to_id
85              
86             Vice versa.
87              
88             =cut
89              
90 1     1 1 6 sub id_to_file { return $_[1].".txt" }
91 6     6 1 13 sub file_to_id { my $file = $_[1]; $file =~ s/.txt$//; $file; }
  6         34  
  6         20  
92              
93             =head2 search
94              
95             $self->search($bryar, $config, %params)
96              
97             A more advanced search for specific documents
98              
99             =cut
100              
101             sub search {
102 3     3 1 9 my ($self, $config, %params) = @_;
103 3 50       19 croak "Must pass in a Bryar::Config object" unless UNIVERSAL::isa($config, "Bryar::Config");
104 3         32 my $was = getcwd;
105 3         19 my $where = $config->datadir."/";
106 3 50       12 if ($params{subblog}) { $where .= $params{subblog}; }
  0         0  
107 3         87 chdir($where); # Damn you, F::F::R.
108            
109 3         120 my $find = File::Find::Rule->file();
110 3 50       183 if ($params{id}) { $find->name($self->id_to_file($params{id})) }
  0         0  
111 3         13 else { $find->name($self->entry_glob) }
112 3         465 $find->maxdepth($config->depth);
113 3 50       35 if ($params{since}) { $find->mtime(">".$params{since}) }
  0         0  
114 3 50       12 if ($params{before}) { $find->mtime("<".$params{before}) }
  0         0  
115 3         5 my @docs;
116 3         14 local $/;
117 3 100       19 if ($params{content}) { $find->grep(qr/\b\Q$params{content}\E\b/i) }
  1         26  
118              
119 3         51 @docs = sort { $b->epoch() <=> $a->epoch() } grep { $_->epoch() <= time () } map { $self->make_document($_) } $find->in(".");
  2         9  
  5         25  
  5         3829  
120 3   33     24 $params{limit} ||= @docs;
121 3         53 chdir($was);
122 3         17 return grep { defined } @docs[0..$params{limit}-1];
  5         55  
123             }
124              
125             =head2 make_document
126              
127             Turns a filename into a C, by parsing the file
128             blosxom-style.
129              
130             =cut
131              
132             sub make_document {
133 5     5 1 13 my ($self, $file) = @_;
134 5 50       17 return unless $file;
135 5 50       191 open(my($in), '<:utf8', $file) or return;
136 5         44 my $when = (stat $in)[9];
137 5         24 local $/ = "\n";
138 5         12 my $fileuid = (stat _)[4];
139 5         8 my $who;
140 5 100       21 if (exists $UID_Cache{$fileuid}) {
141 3         7 $who = $UID_Cache{$fileuid};
142             } else {
143 2         1844 $who = $UID_Cache{$fileuid} = getpwuid($fileuid);
144             }
145 5         61 my $title = <$in>;
146 5         16 chomp $title;
147 5         17 local $/;
148 5         55 my $content = <$in>;
149 5         1065 close $in;
150 5         36 my $id = $self->file_to_id($file);
151              
152 5         13 my $comments = [];
153 5 100       76 $comments = [_read_comments($id, $id.".comments") ]
154             if -e $id.".comments";
155              
156 5         210 my $dir = dirname($file);
157 5         17 $dir =~ s{^\./?}{};
158 5   50     32 my $category = $dir || "main";
159 5         44 return Bryar::Document->new(
160             title => $title,
161             content => $content,
162             epoch => $when,
163             author => $who,
164             id => $id,
165             category => $category,
166             comments => $comments
167             );
168             }
169              
170             sub _read_comments {
171 3     3   9 my ($id, $file) = @_;
172 3 50       130 open(COMMENTS, '<:utf8', $file) or die $!;
173 3         12 local $/;
174             # Watch carefully
175 3         62 my $stuff = ;
176 3         8 my @rv;
177 3         51 for (split /-----\n/, $stuff) {
178 109         409 push @rv,
179             Bryar::Comment->new(
180             id => $id,
181 28         123 map {/^(\w+): (.*)/; $1 => $2 } split /\n/, $_
  109         365  
182             )
183             }
184 3         23 return @rv;
185             }
186              
187             =head2 add_comment
188              
189             Class->add_comment($bryar,
190             document => $doc,
191             author => $author,
192             url => $url,
193             content => $content );
194              
195             Records the given comment details.
196              
197             =cut
198              
199             sub add_comment {
200 1     1 1 3 my ($self, $config) = (shift, shift);
201 1         25 my %params = @_;
202              
203 1         9 s/\n/\r/g for values %params;
204              
205 1         5 my @links = ("$params{url} $params{content}" =~ m!(http://)!g);
206 1 50       13 if(@links > 3) { # more than three links is definitely spam
    50          
    50          
207 0         0 $config->frontend->report_error('Comment failure', 'Attempt to spam the journal.');
208             } elsif(length($params{content}) < 1) { # real content always has, errm, content
209 0         0 $config->frontend->report_error('Comment failure', 'Attempt to post with no content.');
210             } elsif(@links) {
211 0         0 my($email, $author) = map { # kill funny chars to avoid remote
212 0         0 my $foo = $_; # execution in open(). Yuck.
213 0         0 $foo =~ s/[^\w @]/_/g;
214 0         0 $foo;
215             } @params{qw(email author)};
216 0 0       0 open(MAIL, "| mail -s \"$email $author maybe tried to spam the journal\" ".$config->email())
217             or $config->frontend->report_error('Comment failure', "Cannot send mail: $!");
218 0         0 print MAIL "$_: $params{$_}\n" for keys %params;
219 0         0 print MAIL "\nEnvironment\n";
220 0         0 print MAIL "$_: $ENV{$_}\n" for keys %ENV;
221 0 0       0 close MAIL
222             or $config->frontend->report_error('Comment failure', "Cannot send mail: $!");
223             # FIXME: this is not an error
224 0         0 $config->frontend->report_error("Your comment is being held for approval.");
225             } else {
226 1         7 my $file = $params{document}->id.".comments";
227 1 50 33     8 $params{url} = "http://".$params{url}
228             if($params{url} && $params{url} !~ /^http:\/\//);
229             # This probably fails with subblogs, but I don't use them.
230 1         6 chdir $config->datadir."/";
231 1 50       47 open(OUT, ">>:utf8", $file)
232             or $config->frontend->report_error("Cannot open $file", $!);
233 1         45 delete $params{document};
234 1         16 print OUT "$_: $params{$_}\n" for keys %params;
235 1         4 print OUT "-----\n";
236             # Looks a bit like blosxom, doesn't it?
237 1         52 close OUT;
238             # now send mail
239 1 50       6 open(MAIL, '| mail -s "Someone commented in the journal" '.$config->email())
240             or $config->frontend->report_error('Comment failure', "Cannot send mail: $!");
241 1         83 print MAIL "$_: $params{$_}\n" for keys %params;
242 1         11 print MAIL "\nEnvironment\n";
243 1         81 print MAIL "$_: $ENV{$_}\n" for keys %ENV;
244 1 50       1253 close MAIL
245             or $config->frontend->report_error('Comment failure', "Cannot send mail: $!");
246             }
247             }
248              
249             =head1 LICENSE
250              
251             This module is free software, and may be distributed under the same
252             terms as Perl itself.
253              
254             =head1 AUTHOR
255              
256             Copyright (C) 2003, Simon Cozens C
257              
258             some parts Copyright 2007 David Cantrell C
259              
260              
261             =cut
262              
263             1;