File Coverage

blib/lib/Rhetoric/Storage/File.pm
Criterion Covered Total %
statement 6 6 100.0
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 8 8 100.0


line stmt bran cond sub pod time code
1             package Rhetoric::Storage::File;
2 1     1   2609 use common::sense;
  1         2  
  1         8  
3 1     1   50 use aliased 'Squatting::H';
  1         2  
  1         6  
4              
5             use Data::Dump 'pp';
6             use DateTime;
7             use Cwd;
8             use File::Copy;
9             use File::Basename;
10             use File::Find::Rule;
11             use File::Path::Tiny;
12             use File::ShareDir ':ALL';
13             use IO::All;
14             use Method::Signatures::Simple;
15             use Ouch;
16              
17             use Rhetoric::Helpers ':all';
18              
19             # shortcuts for File::Path::Tiny
20             *mk = *File::Path::Tiny::mk;
21              
22             our $storage = H->new({
23              
24             init => method($config) {
25             $self->root($config->{'storage.file.path'});
26             $self->archive_format($config->{'archive_format'});
27             $self;
28             },
29              
30             install => method {
31             my $root = $self->root;
32             mk("$root/posts");
33             mk("$root/categories");
34             mk("$root/categories/Perl");
35              
36             my $share = File::ShareDir::dist_dir('Rhetoric');
37              
38             # TODO - Don't use system()
39              
40             # TODO - Move this to Rhetoric::Meta
41             mk("$root/menu");
42             system("cp '$share/menu/'* '$root/menu'");
43              
44             # TODO - Move this to Rhetoric::Meta
45             mk("$root/pages");
46             system("cp '$share/pages/'* '$root/pages'");
47              
48             # TODO - Move this to Rhetoric::Meta
49             #mk("$root/widgets");
50             system("rsync -a '$share/widgets/' '$root/widgets/'");
51             my $cwd = getcwd;
52             chdir "$root/widgets/sidebar";
53             symlink("init.pl", "00_init.pl");
54             symlink("search.pl", "01_search.pl");
55             symlink("content.pl", "02_about.pl");
56             symlink("categories.pl", "03_categories.pl");
57             symlink("archives.pl", "04_archives.pl");
58             symlink("cleanup.pl", "99_cleanup.pl");
59             chdir $cwd;
60              
61             # TODO - Move this to Rhetoric::Meta
62             $self->meta(title => "Rhetoric") unless (-e "$root/title");
63             $self->meta(subtitle => "Simple Blogging for Perl") unless (-e "$root/subtitle");
64             $self->meta('copy' => "COPYRIGHT (C) 2011 SOMESITE.COM. ALL RIGHTS RESERVED") unless (-e "$root/copy");
65             return 1;
66             },
67              
68             meta => method($k, $v) {
69             my $root = $self->root;
70             if (defined($v)) {
71             wl("$root/$k", "$v");
72             } else {
73             $v = rl("$root/$k");
74             }
75             return $v;
76             },
77              
78             new_post => method($post) {
79             ref($post) eq 'HASH' && H->bless($post);
80             my ($title, $body, $format, $schedule);
81             $title = $post->title;
82             $body = $post->body;
83             $format = $post->format || 'pod';
84             my ($Y, $M, $D, $h, $m, $s);
85             if ($schedule) {
86             # FIXME - use $post->posted_on instead of $schedule
87             ($Y, $M, $D, $h, $m, $s) = split('/', $schedule);
88             } else {
89             ($Y, $M, $D, $h, $m, $s) = now();
90             }
91             my $dt = DateTime->new(
92             year => $Y,
93             month => $M,
94             day => $D,
95             hour => $h,
96             minute => $m,
97             second => $s,
98             );
99             my $root = $self->root;
100             my $post_path = sprintf("$root/posts/%d/%02d/%02d/%02d/%02d/%02d", $Y, $M, $D, $h, $m, $s);
101             mk($post_path);
102             wl("$post_path/title", $title);
103             wl("$post_path/slug", slug($title));
104             wl("$post_path/body", $body);
105             wl("$post_path/format", $format);
106             $post->slug(slug($title));
107             $post->format($format);
108             $post->year($Y);
109             $post->month($M);
110             $post->posted_on($dt);
111             $post->author($ENV{USER});
112             return $post;
113             },
114              
115             # fetch a post
116             post => method($y, $m, $slug) {
117             my $root = $self->root;
118             my $partial_post_path = "$root/posts/$y/$m";
119             my @files = File::Find::Rule
120             ->file()
121             ->name('slug')
122             ->in($partial_post_path);
123             my ($file) = grep { my $test_slug = rl($_); $test_slug eq $slug } @files;
124             if ($file) {
125             my $post_path = dirname($file);
126             my $title = rl("$post_path/title");
127             my $format = rl("$post_path/format");
128             chomp($format);
129             my $body = $F->$format(io("$post_path/body")->all);
130             my @s = split('/', $post_path);
131             my ($Y, $M, $D, $h, $m, $s) = @s[-6 .. -1];
132             my $posted_on = DateTime->new(year => $Y, month => $M, day => $D, hour => $h, minute => $m, second => $s);
133             my $post = H->new({
134             title => $title,
135             slug => $slug,
136             body => $body,
137             format => $format,
138             posted_on => $posted_on,
139             year => $Y,
140             month => $M,
141             day => $D,
142             hour => $h,
143             minute => $m,
144             second => $s,
145             author => ($Rhetoric::CONFIG{user} // file_owner("$post_path/title")),
146             });
147             my @comment_files = glob("$post_path/comments/*");
148             my $comment_count = scalar(@comment_files);
149             $post->comment_count($comment_count);
150             return $post;
151             } else {
152             return undef;
153             }
154             },
155              
156             # FIXME - This implementation is not efficient,
157             # FIXME because it scans the entire post history every time.
158             posts => method($count, $page) {
159             my $root = $self->root;
160             my @all_posts = reverse sort (
161             File::Find::Rule
162             ->file()
163             ->name('slug')
164             ->in("$root/posts")
165             );
166             $count = (@all_posts < $count) ? scalar(@all_posts) : $count;
167             my $pager = Data::Page->new(
168             scalar(@all_posts), # total # of posts
169             $count, # posts per page
170             $page # current page
171             );
172             my @p = $pager->splice(\@all_posts);
173             my @posts = map {
174             my @d = (split('/', $_))[-7 .. -1]; # d for directory
175             my $slug = rl($_);
176             my ($y, $m) = ($d[0], $d[1]);
177             $self->post($y, $m, $slug);
178             } @p;
179             return (\@posts, $pager);
180             },
181              
182             categories => method {
183             my $root = $self->root;
184             my $category_path = "$root/categories";
185             my @c = sort (
186             map { basename($_) }
187             File::Find::Rule
188             ->directory()
189             ->mindepth(1)
190             ->maxdepth(1)
191             ->in($category_path)
192             );
193             },
194              
195             # TODO - list of category posts
196             category_posts => method($category) {
197             ([], undef);
198             },
199              
200             #
201             archives => method {
202             my $root = $self->root;
203             my $post_path = "$root/posts";
204             my @d = reverse sort (
205             File::Find::Rule
206             ->directory()
207             ->mindepth(2)
208             ->maxdepth(2)
209             ->in($post_path)
210             );
211             my @ad = map {
212             my $path = $_;
213             $path =~ s/^$post_path\///;
214             my ($year, $month) = split('/', $path);
215             my $name = DateTime
216             ->new(year => $year, month => $month)
217             ->strftime($self->archive_format);
218             my $archive = H->new({
219             year => $year,
220             month => $month,
221             name => $name,
222             });
223             } @d;
224             @ad;
225             },
226              
227             #
228             archive_posts => method($y, $m) {
229             my $root = $self->root;
230             my @all_posts = reverse sort (
231             File::Find::Rule
232             ->file()
233             ->name('slug')
234             ->in("$root/posts/$y/$m")
235             );
236             my @posts = map {
237             my @d = (split('/', $_))[-7 .. -1]; # d for directory
238             my $slug = rl($_);
239             my ($y, $m) = ($d[0], $d[1]);
240             $self->post($y, $m, $slug);
241             } @all_posts;
242             (\@posts, undef);
243             },
244              
245             #
246             comments => method($post) {
247             my $root = $self->root;
248             my $post_path = sprintf('%s/posts/%s/%s/%s/%s/%s/%s',
249             $root,
250             $post->year, $post->month, $post->day,
251             $post->hour, $post->minute, $post->second,
252             );
253             my @comment_files = sort glob("$post_path/comments/*");
254             my @comments = map {
255             my ($name,$email,$url,@body) = io($_)->slurp;
256             chomp($name, $email, $url);
257             my $body = join('', @body);
258             H->new({
259             name => $name,
260             email => $email,
261             url => $url,
262             body => $body,
263             });
264             } @comment_files;
265             \@comments;
266             },
267              
268             #
269             new_comment => method($year, $month, $slug, $comment) {
270             ref($comment) eq 'HASH' && H->bless($comment);
271             my @errors;
272             push @errors, ['name'] if (not $comment->name);
273             push @errors, ['email'] if (not $comment->email);
274             push @errors, ['body'] if (not $comment->body);
275             if (@errors) {
276             ouch('InvalidComment', \@errors);
277             }
278              
279             my $post = $self->post($year, $month, $slug);
280             my $root = $self->root;
281             my $post_path = sprintf('%s/posts/%s/%s/%s/%s/%s/%s',
282             $root,
283             $post->year, $post->month, $post->day,
284             $post->hour, $post->minute, $post->second,
285             );
286             warn("$post_path/comments");
287             mk("$post_path/comments");
288             my @comment_files = sort glob("$post_path/comments/*");
289             my $index = '001';
290             if (@comment_files) {
291             warn "previous comments existed";
292             my $last = (split('/', $comment_files[-1]))[-1];
293             $last =~ s/^0*//;
294             warn "last is $last";
295             $index = sprintf('%03d', $last + 1);
296             }
297             warn $index;
298             my $format = $comment->format || 'pod';
299             my $body = $F->$format($comment->body);
300             io("$post_path/comments/$index") < $comment->name . "\n";
301             io("$post_path/comments/$index") << $comment->email . "\n";
302             io("$post_path/comments/$index") << $comment->url . "\n";
303             io("$post_path/comments/$index") << $body . "\n";
304             $comment->success(1);
305             $comment;
306             },
307              
308             # XXX - this needs to take TT or perl expressions
309             menu => method($menu) {
310             my $root = $self->root;
311             my $menu_path = "$root/menu";
312             if (defined($menu)) {
313             my $i = 1;
314             for (@$menu) {
315             my $name = $_->name;
316             my $url = $_->url;
317             io(sprintf("$menu_path/%02d_%s", $i, $menu)) < $url;
318             $i++;
319             }
320             } else {
321             my @menu_files = sort glob("$menu_path/*");
322             $menu = [ map {
323             my $filename = $_;
324             my $url = rl($filename);
325             my ($name) = fileparse($filename);
326             chomp($url);
327             $name =~ s/^\d*_//;
328             H->new({ name => $name, url => $url });
329             } @menu_files ];
330             }
331             return $menu;
332             },
333             });
334              
335             1;
336              
337             __END__