File Coverage

lib/App/Followme/FileData.pm
Criterion Covered Total %
statement 100 102 98.0
branch 18 20 90.0
condition n/a
subroutine 20 21 95.2
pod 1 10 10.0
total 139 153 90.8


line stmt bran cond sub pod time code
1             package App::Followme::FileData;
2              
3 17     17   838 use 5.008005;
  17         76  
4 17     17   94 use strict;
  17         28  
  17         551  
5 17     17   99 use warnings;
  17         37  
  17         453  
6 17     17   92 use integer;
  17         38  
  17         120  
7 17     17   544 use lib '../..';
  17         55  
  17         102  
8              
9              
10 17     17   2182 use base qw(App::Followme::FolderData);
  17         36  
  17         7757  
11 17     17   129 use App::Followme::FIO;
  17         38  
  17         1556  
12 17     17   3120 use App::Followme::NestedText;
  17         46  
  17         1216  
13 17     17   3096 use App::Followme::Web;
  17         41  
  17         21431  
14              
15             our $VERSION = "2.03";
16              
17             #----------------------------------------------------------------------
18             # Read the default parameter values
19              
20             sub parameters {
21 344     344 1 568 my ($self) = @_;
22              
23             return (
24 344         1018 base_directory => '',
25             title_template => '',
26             );
27             }
28              
29             #----------------------------------------------------------------------
30             # Convert content represented in html format (stub)
31              
32             sub fetch_as_html {
33 10     10 0 23 my ($self, $content_block) = @_;
34 10         22 return $content_block;
35             }
36              
37             #----------------------------------------------------------------------
38             # Parse content as html to get fallback values for data
39              
40             sub fetch_content {
41 110     110 0 223 my ($self, $content_block) = @_;
42              
43             # Content within title template is title, rest of content is body
44              
45 110         197 my $metadata = [];
46 110         166 my $global = 0;
47 110         159 my $body;
48              
49              
50 110 100       243 if ($self->{title_template}) {
51              
52             my $title_parser = sub {
53 22     22   61 my ($metadata, @tokens) = @_;
54 22         58 my $text = web_only_text(@tokens);
55 22         53 push(@$metadata, 'title', $text);
56 22         48 return '';
57 23         101 };
58              
59             $body = web_substitute_tags($self->{title_template},
60 23         71 $content_block,
61             $title_parser,
62             $metadata,
63             $global
64             );
65             } else {
66 87         137 $body = $content_block;
67             }
68              
69 110         397 $body =~ s/^\s+//;
70 110         263 push(@$metadata, 'body', $body);
71 110         277 my %content = @$metadata;
72              
73             my $paragraph_parser = sub {
74 102     102   275 my ($paragraph, @tokens) = @_;
75 102         267 $$paragraph = web_only_text(@tokens);
76 102         227 return;
77 110         460 };
78              
79 110         184 my $paragraph;
80 110 100       333 if (web_match_tags('<p></p>',
81             $body,
82             $paragraph_parser,
83             \$paragraph,
84             $global)) {
85              
86             # Description is first sentence of first paragraph
87 102         493 $paragraph =~ /([^.!?\s][^.!?]*(?:[.!?](?!['"]?\s|$)[^.!?]*)*[.!?]?['"]?(?=\s|$))/;
88 102         328 $content{description} = $1;
89 102         233 $content{summary} = $paragraph;
90             }
91              
92 110         853 return %content;
93             }
94              
95             #----------------------------------------------------------------------
96             # Fetch data from all its possible sources
97              
98             sub fetch_data {
99 558     558 0 5273 my ($self, $name, $filename, $loop) = @_;
100              
101             # Check to see if you can get data without opening file
102 558         1755 $self->check_filename($name, $filename);
103 558         1482 my %data = $self->gather_data('get', $name, $filename, $loop);
104              
105             # Then open the file and try to read the data from it
106             %data = ($self->fetch_from_file($filename), %data)
107 558 100       1578 unless exists $data{$name};
108              
109             # If not found in the file, calculate from other fields
110             %data = (%data, $self->gather_data('calculate', $name, $filename, $loop))
111 558 100       1429 unless exists $data{$name};
112              
113 558         1784 return %data;
114             }
115              
116             #----------------------------------------------------------------------
117             # Look in the file for the data
118              
119             sub fetch_from_file {
120 125     125 0 265 my ($self, $filename) = @_;
121              
122 125         189 my (%metadata, %content);
123 125         349 my $text = fio_read_page($filename);
124 125 100       490 return () unless length $text;
125              
126 113         434 my $section = $self->fetch_sections($text);
127              
128             # First look in the metadata and then the content
129 113         433 %metadata = $self->fetch_metadata($section->{metadata});
130 113         484 %content = $self->fetch_content($section->{body});
131              
132 113         717 return (%content, %metadata);
133             }
134              
135             #----------------------------------------------------------------------
136             # Fetch metadata directly from file
137              
138             sub fetch_metadata {
139 59     59 0 122 my ($self, $metadata_block) = @_;
140              
141 59         211 my %metadata = nt_parse_almost_yaml_string($metadata_block);
142 59         169 return %metadata;
143             }
144              
145             #----------------------------------------------------------------------
146             # Split text into metadata and content sections
147              
148             sub fetch_sections {
149 60     60 0 2724 my ($self, $text) = @_;
150              
151 60         98 my %section;
152 60         294 my $divider = qr(\.{3,}\s*\n);
153              
154 60 100       494 if ($text =~ /^$divider/m) {
155 25         147 my @sections = split($divider, $text, 2);
156 25         70 $section{body} = $sections[1];
157 25         53 $section{metadata} = $sections[0];
158              
159             } else {
160 35         101 $section{body} = $text;
161 35         81 $section{metadata} = '';
162             }
163              
164 60 50       139 die "Could not fetch body of text\n" unless $section{body};
165 60         215 $section{body} = $self->fetch_as_html($section{body});
166 60         153860 return \%section;
167             }
168              
169             #----------------------------------------------------------------------
170             # Return author's name in sortable order
171              
172             sub format_author {
173 29     29 0 60 my ($self, $sorted_order, $author) = @_;
174              
175 29 100       65 if ($sorted_order) {
176 1         2 $author = lc($author);
177              
178             # no need to sort if last name is first
179 1 50       4 if ($author !~ /^\w+,/) {
180             # put last name first
181 1         2 $author =~ s/[^\w\s]//g;
182 1         5 my @names = split(' ', $author);
183 1         2 my $name = pop(@names);
184 1         2 unshift(@names, $name);
185              
186 1         4 $author = join(' ', @names);
187             }
188             }
189              
190 29         81 return $author;
191             }
192              
193             #----------------------------------------------------------------------
194             # Return title in sortable order
195              
196             sub format_title {
197 109     109 0 244 my ($self, $sorted_order, $title) = @_;
198              
199 109 100       244 if ($sorted_order) {
200 14         31 $title = lc($title);
201 14         33 $title =~ s/^a\s+//;
202 14         27 $title =~ s/^the\s+//;
203             }
204              
205 109         402 return $title;
206             }
207              
208             #----------------------------------------------------------------------
209             # Return the base directory defined in this object
210              
211             sub get_base_directory {
212 0     0 0   my ($self) = @_;
213 0           return $self->{base_directory};
214             }
215              
216             1;
217              
218             =pod
219              
220             =encoding utf-8
221              
222             =head1 NAME
223              
224             App::Followme::FileData
225              
226             =head1 SYNOPSIS
227              
228             use App::Followme::FileData;
229             my $data = App::Followme::FileData->new();
230             my $html = App::Followme::Template->new('example.htm', $data);
231              
232             =head1 DESCRIPTION
233              
234             This module extracts data from a file. It assumes the file is a text
235             file with the metadata in a nested text block preceding a content block
236             that is in html format or convertible to html format. The two sections
237             are separated by a line of three or more dots. These asumptions can be
238             overriden by overriding the methods in the class. Like the other data
239             classes, this class is normally called by the Template class and not
240             directly by user code.
241              
242             =head1 METHODS
243              
244             All data classes are first instantiated by calling new and the object
245             created is passed to a template object. It calls the build method with an
246             argument name to retrieve that data item, though for the sake of
247             efficiency, all the data are read from the file at once.
248              
249             =head1 VARIABLES
250              
251             The file metadata class can evaluate the following variables. When passing
252             a name to the build method, the sigil should not be used.
253              
254             =over 4
255              
256             =item $body
257              
258             The main content of the file.
259              
260             =item $description
261              
262             A one sentence description of the file contents
263              
264             =item $date
265              
266             The creation date of a file. The display format is controlled by the
267             configuration variable, data_format
268              
269             =item $summary
270              
271             A summary of the file's contents, by default the first paragraph.
272              
273             =item $title
274              
275             The title of the file, either derived from the content or the file metadata.
276              
277             =back
278              
279             =head1 CONFIGURATION
280              
281             This class has the following configuration variable:
282              
283             =over 4
284              
285             =item base_directory
286              
287             The top directory containing the files to be processed
288              
289             =item title_template
290              
291             A set of html tags that bound the title in the file's contents. The default
292             value is "<h1></h1>".
293              
294             =back
295              
296             =head1 LICENSE
297              
298             Copyright (C) Bernie Simon.
299              
300             This library is free software; you can redistribute it and/or modify
301             it under the same terms as Perl itself.
302              
303             =head1 AUTHOR
304              
305             Bernie Simon E<lt>bernie.simon@gmail.comE<gt>
306              
307             =cut