random changes
[urisagit/CMS-Simple.git] / CMS / Simple.pm
CommitLineData
9e609156 1package CMS::Simple ;
2
3use warnings ;
4use strict ;
5
6use Carp ;
0db1d626 7use File::Path;
9e609156 8use Data::Dumper ;
9
10use CMS::Simple::Parse ;
11use Template::Simple ;
12use File::Slurp ;
13
14
15our $VERSION = '0.01' ;
16
17
18my %defaults = (
19
20###
21# use File::Path and other modules to make paths clean and portable
22###
23
24 working_dir => '.',
25 content_paths => ['content'],
26 templates_dir => 'templates',
27 output_dir => 'output',
28
29# this is for timestamps
30
31 published_dir => 'published',
32
33 template_args => {},
34) ;
35
36my %parsers = (
37
38 cont => \&CMS::Simple::Parse::parse_content,
39 csv => \&CMS::Simple::Parse::parse_csv,
40 pl => \&_parse_perl,
41# yaml => \&parse_yaml,
42) ;
43
44sub new {
45
46 my( $class, $args ) = @_ ;
47
48 my $self = bless { %defaults, %{$args} }, $class ;
49
50 $self->_invert_filter_tags() ;
51
52 $self->_make_dirs() ;
53
54# rename/clean dirs??
55
56 $self->_load_content() ;
57
58####
59# add template path arg
60
61 $self->{tmpl_obj} = Template::Simple->new( %{$self->{template_args}} ) ;
62
63 return $self ;
64}
65
0db1d626 66sub _find_content_file {
67
68 my( $self, $file ) = @_ ;
69
70 my @dirs = @{$self->{content_paths}} ;
71
72 foreach my $dir ( @dirs ) {
73
74 my $file_path = "$dir/$file" ;
75
76 return $file_path if -e $file_path ;
77 }
78
79 croak "can't file content file '$file' in [@dirs]" ;
80}
81
9e609156 82
83sub _load_content {
84
85 my( $self ) = @_ ;
86
87 my $contents = $self->{contents} ;
88
89#print Dumper $contents ;
90
91 while( my( $name, $file ) = each %{$contents} ) {
92
93######
94# ADD BETTER DIRECTORY STUFF
95######
96
0db1d626 97 my $file_path = $self->_find_content_file( $file ) ;
9e609156 98
99 my $content_text = read_file( $file_path ) ;
100
0db1d626 101#print "FILE $file_path\n$content_text" if $file_path =~ /location/;
102
9e609156 103 my ($suffix) = $file_path =~ /\.(\w+)$/ ;
104
105 my $parser = $parsers{ $suffix } ;
106
0db1d626 107 $parser or croak "unknown suffix '$suffix'" ;
9e609156 108
109 my $parsed = $parser->( $content_text ) ;
110
111 $contents->{$name} = {
112
113 file => $file,
114 text => $content_text,
115 parsed => $parsed,
116 } ;
117 }
118#print Dumper $contents ;
119
120}
121
122sub build_all_pages {
123
124 my( $self ) = @_ ;
125
126 foreach my $page_name ( keys %{$self->{pages}} ) {
127
128 $self->build_page( $page_name ) ;
129 }
130}
131
132
133sub build_page {
134
135 my( $self, $page_name ) = @_ ;
136
137#print "BUILD $page_name\n" ;
138
139 my $page = $self->{pages}{$page_name} ;
140
141 return if $page->{skip} ;
142
143 $page->{name} = $page_name ;
144
145 $self->_get_page_content( $page ) ;
146
147#print Dumper $page ;
148
149 $self->_filter_page_content( $page ) ;
150
0db1d626 151 if( my $filter = $page->{filter} ) {
152 $filter->( $page ) ;
153 }
9e609156 154
155 $self->_render_page( $page ) ;
156#print ${$page->{rendered}} ;
157
158 $self->_output_page( $page ) ;
159}
160
161sub _get_page_content {
162
163 my( $self, $page ) = @_ ;
164
165#######
166# FIX so a page contents can override values and not just whole maps
167# ADD contents maps to have multilevel keys
168#######
169
170 my $all_contents = $self->{contents} ;
171
172 my $page_contents = $page->{contents} || {} ;
173
0db1d626 174 $page->{contents_map} ||= { $page->{name} => '' } ;
175
9e609156 176# loop over the default (common) and page specific content maps
177
178 foreach my $contents_map (
179 $self->{default_contents_map},
180 $page->{contents_map} ) {
181
0db1d626 182print "MAP ", Dumper $contents_map if $page->{dump_content} ;
183
184 if ( ref $contents_map eq 'ARRAY' ) {
9e609156 185
0db1d626 186 for( my $i = 0 ; $i < @$contents_map ; $i += 2 ) {
9e609156 187
188# get the contents for this content name
0db1d626 189 my( $name, $location ) =
190 @{$contents_map}[$i, $i+1] ;
191
192 my $contents = $all_contents->{$name}{parsed} ;
193
194 $self->_add_page_contents(
195 $page_contents,
196 $location,
197 $contents
198 ) ;
199
200#print "CONT ", Dumper $page_contents if $page->{dump_content} ;
9e609156 201
0db1d626 202 }
203 }
204 else {
205 while( my( $name, $location ) =
206 each %{$contents_map} ) {
207
208 # get the contents for this content name
209
210 my $contents = $all_contents->{$name}{parsed} ;
9e609156 211
0db1d626 212 #print "CONT ", Dumper $contents if $page->{dump_content} ;
213
214 $self->_add_page_contents(
215 $page_contents,
216 $location,
217 $contents
218 ) ;
219 }
9e609156 220 }
221 }
222
0db1d626 223 print "ALL CONT ", Dumper $page_contents if $page->{dump_content} ;
9e609156 224
225 $page->{contents} = $page_contents ;
226}
227
228sub _add_page_contents {
229
230 my( $self, $page_contents, $location, $contents ) = @_ ;
231
232#########
233# this needs to handle multilevel content location
234#########
235
236# if we have a location, just store the contents there
237
238 if ( $location ) {
239
240 my @loc_keys = split /:/, $location ;
241#print "LOC @loc_keys\n" ;
242
243 my $loc_ref = \$page_contents->{ shift @loc_keys } ;
244
245#print "LOC $loc_ref\n" ;
246
247# descend into the page contents based on the location keys
248
249 $loc_ref = \${$loc_ref}->{$_} for @loc_keys ;
250
251 ${$loc_ref} = deep_copy( $contents ) ;
252
253 return ;
254 }
255
256# no location so store all the top level contents in the top level of
257# the page
258
259 @{$page_contents}{keys %{$contents}} = values %{$contents} ;
260}
261
262sub _filter_page_content {
263
264 my( $self, $page ) = @_ ;
265
266# NOTE content must be a hash at the top
267
268 $self->_filter_content_hash( $page->{contents} ) ;
269
0db1d626 270 print "DONE\n", Dumper $page->{contents} if $page->{dump_filtered} ;
9e609156 271}
272
273sub _filter_content_hash {
274
275 my( $self, $href, $path ) = @_ ;
276
277 while( my( $tag, $val ) = each %{$href} ) {
278
279 my @new_val =
280 $self->_filter_content_tag( $tag, $val, $path ) ;
281
282 next unless @new_val ;
283 $href->{$tag} = $new_val[0] ;
0db1d626 284
285#print "NEW\n", Dumper \@new_val if $tag =~ /location/ ;
286
9e609156 287 }
288}
289
290sub _filter_content_array {
291
292 my( $self, $tag, $aref, $path ) = @_ ;
293
294#print "ARRAY: ", Dumper \$tag, $aref ;
295
296 my @new_vals ;
297
298 foreach my $val ( @{$aref} ) {
299
300 push @new_vals,
301 $self->_filter_content_tag( $tag, $val, $path ) ;
302 }
303
304 @{$aref} = @new_vals ;
305
306#print Dumper $aref ;
307
308}
309
310sub _filter_content_tag {
311
312 my( $self, $tag, $val, $path ) = @_ ;
313
0db1d626 314#print "TAG1 $tag\n" ;
315#print "TAG $tag\n" if $tag =~ /location/ ;
316
9e609156 317 my $ref_type = ref $val ;
0db1d626 318
319 if( my @new_val =
320 $self->_filter_content_value( $tag, $val, $path ) ) {
321
322 $val = $new_val[0] ;
323
324#print "VAL $tag\n" if $tag =~ /location/ ;
325
326# handle case where the filter changed the type of the value
327
328 $ref_type = ref $val ;
329 }
9e609156 330
331 if ( $ref_type eq 'HASH' ) {
332
0db1d626 333#print "HASH $tag\n" if $tag =~ /location/ ;
334
9e609156 335 $self->_filter_content_hash( $val, $path ) ;
336 return $val ;
337 }
338
339 if ( $ref_type eq 'ARRAY' ) {
340
341 $self->_filter_content_array( $tag, $val, $path ) ;
342 return $val ;
343 }
344
9e609156 345 return $val ;
346}
347
348sub _filter_content_value {
349
350 my( $self, $tag, $val, $path ) = @_ ;
351
0db1d626 352#print "VAL TAG $tag\n" if $tag =~ /location/ ;
353
9e609156 354 my $filters = $self->{tag_to_filters}{$tag} ;
355
356 return unless $filters ;
357
358 my @new_val ;
359
360 foreach my $filter ( @{$filters} ) {
361
362#print "FILTER $filter->{name}\n" ;
363
364#print "TAG $tag [$val]\n" unless defined $val;
365
366$val = '' unless defined $val || $tag ne 'text' ;
367
368 @new_val = $filter->{code}->( $tag, $val, $path ) ;
369
370 next unless @new_val ;
371
372 $val = $new_val[0] ;
373 }
374
375#print "TAG: $tag: ", Dumper \$val ;
376
377# return if nothing was changed
378
379 return unless @new_val ;
380
381 return $val ;
382}
383
384sub _render_page {
385
386 my( $self, $page ) = @_ ;
387
388 my $tmpl_obj = $self->{tmpl_obj} ;
389
390# NOTE: using internal method. will fix template::simple to expose it
391
392 my $tmpl_name = $page->{template} || $self->{default_template} ;
393
394 my $template = $tmpl_obj->_get_template( $tmpl_name ) ;
395
396#print Dumper $page->{contents} ;
397
398 my $rendered = $tmpl_obj->render( $template, $page->{contents} ) ;
399
400 $page->{rendered} = $rendered ;
401}
402
403
404
405sub _output_page {
406
407 my( $self, $page ) = @_ ;
408
409##########
410# use file::path stuff to make this portable
411##########
412
0db1d626 413 my $sub_dir = $page->{sub_dir} || '' ;
414 $sub_dir .= '/' if $sub_dir ;
415
416 my $suffix = $page->{'output_suffix'} || $self->{'output_suffix'} ;
417
418 my $output_dir = "$self->{'output_dir'}/$sub_dir" ;
419
420 mkpath( $output_dir ) unless -d $output_dir ;
421
422 my $output_path = "$output_dir$page->{name}$suffix" ;
9e609156 423
424 $page->{'output_path'} = $output_path ;
425
426 write_file( $output_path, $page->{rendered} ) ;
427}
428
429sub publish_output {
430
431 my( $self ) = @_ ;
432
433 while ( my($name, $page) = each %{$self->{'pages'}} ) {
434
435 my $output_file = $self->{'pages'}{$name}{'output_file'} ;
436
437 my $remote_host = $self->{'remote_host'} ;
438 my $remote_user = $self->{'remote_user'} ;
439 my $remote_directory = $self->{'remote_directory'} ;
440
441 # Strip trailing slash if there is one, then replace it...
442 # so that dir always ends in slash whether or not one is passed:
443 # (Note: not portable outside Linux/unix!)
444
445 $remote_directory =~ s/^(.*)\/$/$1/ ;
446
447 my $scp = Net::SCP->new() ;
448
449 die "Unable to construct remote destination" unless
450 ( $remote_host && $remote_user && $remote_directory ) ;
451
452 # Construct remote destination from class attributes:
453 my $destination = "${remote_user}\@${remote_host}:${remote_directory}/" ;
454
455 # Use 'iscp' for interactive scp:
456 $scp->iscp( $output_file, $destination ) or die $scp->{errstr};
457
458 }
459}
460
461sub _parse_perl {
462
463 my( $text ) = @_ ;
464
465 return eval $text ;
466}
467
468
469# change
470sub deep_copy {
471
472 my( $val ) = @_ ;
473
474 return $val unless ref $val ;
475
476 return [ map deep_copy( $_ ), @{$val} ] if ref $val eq 'ARRAY' ;
477
478 return { map { $_, deep_copy( $val->{$_} ) } keys %{$val} }
479 if ref $val eq 'HASH' ;
480
481 die "$val is not a scalar, ARRAY or HASH" ;
482}
483
484
485sub _make_dirs {
486
487 my( $self ) = @_ ;
488
489############
490# use File::Path to make deep dirs
491###########
492
493 mkdir( $self->{output_dir} ) ;
494 mkdir( $self->{published_dir} ) ;
495}
496
497sub _invert_filter_tags {
498
499 my( $self) = @_ ;
500
501 my %tag_to_filters ;
502
503 foreach my $filter ( @{$self->{filters}} ) {
504
505 push @{$tag_to_filters{$_}}, $filter for @{$filter->{tags}} ;
506 }
507
508#print Dumper \%tag_to_filters ;
509
510 $self->{tag_to_filters} = \%tag_to_filters ;
511}
512
5131 ;