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