File Coverage

blib/lib/App/XML/DocBook/Docmake.pm
Criterion Covered Total %
statement 154 198 77.7
branch 39 62 62.9
condition 11 17 64.7
subroutine 31 39 79.4
pod 2 2 100.0
total 237 318 74.5


line stmt bran cond sub pod time code
1             package App::XML::DocBook::Docmake;
2             $App::XML::DocBook::Docmake::VERSION = '0.1003';
3 1     1   118024 use 5.014;
  1         13  
4 1     1   5 use strict;
  1         3  
  1         38  
5 1     1   6 use warnings;
  1         2  
  1         29  
6              
7 1     1   740 use Getopt::Long qw/ GetOptionsFromArray /;
  1         10931  
  1         4  
8 1     1   173 use File::Path qw/ mkpath /;
  1         2  
  1         46  
9 1     1   537 use Pod::Usage qw/ pod2usage /;
  1         49331  
  1         99  
10              
11              
12             use Class::XSAccessor {
13 1         9 accessors => [
14             qw(
15             _base_path
16             _has_output
17             _input_path
18             _make_like
19             _mode
20             _output_path
21             _stylesheet
22             _trailing_slash
23             _verbose
24             _real_mode
25             _xslt_mode
26             _xslt_stringparams
27             )
28             ]
29 1     1   570 };
  1         2317  
30              
31             use Test::Trap
32 1     1   698 qw( trap $trap :flow:stderr(systemsafe):stdout(systemsafe):warn );
  1         3  
  1         10  
33              
34              
35             my %modes = (
36             'fo' => {},
37             'help' => {
38             standalone => 1,
39             },
40             'manpages' => {},
41             'xhtml' => {},
42             'xhtml-1_1' => {
43             real_mode => "xhtml",
44             },
45             'xhtml5' => {
46             real_mode => "xhtml",
47             },
48             'rtf' => {
49             xslt_mode => "fo",
50             },
51             'pdf' => {
52             xslt_mode => "fo",
53             },
54             );
55              
56             sub new
57             {
58 12     12 1 12142 my $class = shift;
59 12         25 my $self = {};
60              
61 12         25 bless $self, $class;
62              
63 12         45 $self->_init(@_);
64              
65 12         35 return $self;
66             }
67              
68             sub _init
69             {
70 12     12   23 my ( $self, $args ) = @_;
71              
72 12         38 my $argv = $args->{'argv'};
73              
74 12         19 my $output_path;
75 12         19 my $verbose = 0;
76 12         29 my $stylesheet;
77             my @in_stringparams;
78 12         0 my $base_path;
79 12         18 my $make_like = 0;
80 12         20 my ( $help, $man );
81 12         18 my $trailing_slash = 1;
82              
83 12         49 my $ret = GetOptionsFromArray(
84             $argv,
85             "o=s" => \$output_path,
86             "v|verbose" => \$verbose,
87             "x|stylesheet=s" => \$stylesheet,
88             "stringparam=s" => \@in_stringparams,
89             "basepath=s" => \$base_path,
90             "make" => \$make_like,
91             'help|h' => \$help,
92             'man' => \$man,
93             'trailing-slash=i' => \$trailing_slash,
94             );
95              
96 12 50       10867 if ( !$ret )
97             {
98 0         0 pod2usage(2);
99             }
100 12 50       29 if ($help)
101             {
102 0         0 pod2usage(1);
103             }
104 12 50       26 if ($man)
105             {
106 0         0 pod2usage( -exitstatus => 0, -verbose => 2 );
107             }
108              
109 12         17 my @stringparams;
110 12         26 foreach my $param (@in_stringparams)
111             {
112 6 50       33 if ( $param =~ m{\A([^=]+)=(.*)\z}ms )
113             {
114 6         29 push @stringparams, [ $1, $2 ];
115             }
116             else
117             {
118 0         0 die "Wrong stringparam argument '$param'! Does not contain a '='!";
119             }
120             }
121              
122 12 100       75 $self->_has_output( $self->_output_path($output_path) ? 1 : 0 );
123              
124 12         28 $self->_verbose($verbose);
125 12         27 $self->_stylesheet($stylesheet);
126 12         29 $self->_xslt_stringparams( \@stringparams );
127 12         27 $self->_make_like($make_like);
128 12         27 $self->_base_path($base_path);
129 12         46 $self->_trailing_slash($trailing_slash);
130              
131 12         24 my $mode = shift(@$argv);
132              
133 12         29 my $mode_struct = $modes{$mode};
134              
135 12 50       27 if ($mode_struct)
136             {
137 12         29 $self->_mode($mode);
138              
139             my $assign_secondary_mode = sub {
140 24     24   47 my ( $struct_field, $attr ) = @_;
141 24   66     146 $self->$attr( $mode_struct->{$struct_field} || $mode );
142 12         53 };
143              
144 12         34 $assign_secondary_mode->( 'real_mode', '_real_mode' );
145 12         24 $assign_secondary_mode->( 'xslt_mode', '_xslt_mode' );
146             }
147             else
148             {
149 0         0 die "Unknown mode \"$mode\"";
150             }
151              
152 12         26 my $input_path = shift(@$argv);
153              
154 12 50 66     41 if ( !( defined($input_path) || $mode_struct->{standalone} ) )
155             {
156 0         0 die "Input path not specified on command line";
157             }
158             else
159             {
160 12         32 $self->_input_path($input_path);
161             }
162              
163 12         28 return;
164             }
165              
166              
167             sub _exec_command
168             {
169 0     0   0 my ( $self, $args ) = @_;
170              
171 0         0 my $cmd = $args->{cmd};
172              
173 0 0       0 if ( $self->_verbose() )
174             {
175 0         0 print( join( " ", @$cmd ), "\n" );
176             }
177              
178 0         0 my $exit_code;
179             trap
180             {
181 0     0   0 local $ENV{LC_ALL} = "C.utf-8";
182 0         0 $exit_code = system(@$cmd);
183 0         0 };
184              
185 0         0 my $stderr = $trap->stderr();
186              
187 0 0 0     0 if ( not( ( defined($exit_code) ) and ( !$exit_code ) ) )
188             {
189 0 0       0 if ( $stderr =~ m#Attempt to load network entity# )
190             {
191 0 0       0 if ( $args->{xsltproc} )
192             {
193 0         0 die <<"EOF";
194             Running xsltproc failed due to lacking local DocBook 5/XSL stylesheets and data.
195             See: https://github.com/shlomif/fortune-mod/issues/45 and
196             https://github.com/docbook/wiki/wiki/DocBookXslStylesheets
197              
198             Command was <<@$cmd>>;
199             EOF
200             }
201             }
202 0         0 die qq/$stderr\n<<@$cmd>> failed./;
203             }
204              
205 0         0 return 0;
206             }
207              
208             sub run
209             {
210 11     11 1 8994 my $self = shift;
211              
212 11         31 my $real_mode = $self->_real_mode();
213              
214 11         27 my $mode_func = '_run_mode_' . $self->_real_mode;
215              
216 11         47 return $self->$mode_func(@_);
217             }
218              
219             sub _run_mode_help
220             {
221 1     1   3 my $self = shift;
222              
223 1         44 print <<"EOF";
224             Docmake version $App::XML::DocBook::Docmake::VERSION
225             A tool to convert DocBook/XML to other formats
226              
227             Available commands:
228              
229             help - this help screen.
230              
231             fo - convert to XSL-FO.
232             manpages - convert to Unix manpage (nroff)
233             rtf - convert to RTF (MS Word).
234             pdf - convert to PDF (Adobe Acrobat).
235             xhtml - convert to XHTML.
236             xhtml-1_1 - convert to XHTML-1.1.
237             xhtml5 - convert to XHTML5
238             EOF
239             }
240              
241             sub _is_older
242             {
243 0     0   0 my $self = shift;
244              
245 0         0 my $file1 = shift;
246 0         0 my $file2 = shift;
247              
248 0         0 my @stat1 = stat($file1);
249 0         0 my @stat2 = stat($file2);
250              
251 0 0       0 if ( !@stat2 )
    0          
252             {
253 0         0 die "Input file '$file1' does not exist.";
254             }
255             elsif ( !@stat1 )
256             {
257 0         0 return 1;
258             }
259             else
260             {
261 0         0 return ( $stat1[9] <= $stat2[9] );
262             }
263             }
264              
265             sub _should_update_output
266             {
267 0     0   0 my $self = shift;
268 0         0 my $args = shift;
269              
270 0         0 return $self->_is_older( $args->{output}, $args->{input} );
271             }
272              
273             sub _run_mode_fo
274             {
275 0     0   0 my $self = shift;
276 0         0 return $self->_run_xslt();
277             }
278              
279             sub _mkdir
280             {
281 0     0   0 my ( $self, $dir ) = @_;
282              
283 0         0 mkpath($dir);
284             }
285              
286             sub _run_mode_manpages
287             {
288 0     0   0 my $self = shift;
289              
290             # Create the directory, because xsltproc requires it.
291 0 0       0 if ( $self->_trailing_slash )
292             {
293             # die "don't add trailing_slash";
294              
295             # $self->_mkdir( $self->_output_path() );
296             }
297              
298 0         0 return $self->_run_xslt();
299             }
300              
301             sub _run_mode_xhtml
302             {
303 6     6   11 my $self = shift;
304              
305             # Create the directory, because xsltproc requires it.
306 6 100       20 if ( $self->_trailing_slash )
307             {
308 5         17 $self->_mkdir( $self->_output_path() );
309             }
310              
311 6         21 return $self->_run_xslt();
312             }
313              
314             sub _calc_default_xslt_stylesheet
315             {
316 9     9   16 my $self = shift;
317              
318 9         19 my $mode = $self->_xslt_mode();
319              
320             return
321 9         29 "http://docbook.sourceforge.net/release/xsl/current/${mode}/docbook.xsl";
322             }
323              
324             sub _is_xhtml
325             {
326 24     24   38 my $self = shift;
327              
328 24   100     132 return ( ( $self->_mode() eq "xhtml" )
329             || ( $self->_mode() eq "xhtml-1_1" )
330             || ( $self->_mode() eq "xhtml5" ) );
331             }
332              
333             sub _calc_output_param_for_xslt
334             {
335 16     16   25 my $self = shift;
336 16         22 my $args = shift;
337              
338 16         33 my $output_path = $self->_output_path();
339 16 100       33 if ( defined( $args->{output_path} ) )
340             {
341 6         12 $output_path = $args->{output_path};
342             }
343              
344 16 50       33 if ( !defined($output_path) )
345             {
346 0         0 die "Output path not specified!";
347             }
348              
349             # If it's XHTML, then it's a directory and xsltproc requires that
350             # it will have a trailing slash.
351 16 100       33 if ( $self->_is_xhtml )
352             {
353 10 100       24 if ( $self->_trailing_slash )
354             {
355 8 50       21 if ( $output_path !~ m{/\z} )
356             {
357 8         13 $output_path .= "/";
358             }
359             }
360             }
361              
362 16         51 return $output_path;
363             }
364              
365             sub _calc_make_output_param_for_xslt
366             {
367 8     8   14 my $self = shift;
368 8         12 my $args = shift;
369              
370 8         16 my $output_path = $self->_calc_output_param_for_xslt($args);
371              
372             # If it's XHTML, then we need to compare against the index.html
373             # because the directory is freshly made.
374 8 100       32 if ( $self->_is_xhtml )
375             {
376 5         14 $output_path .= "index.html";
377             }
378              
379 8         31 return $output_path;
380             }
381              
382             sub _pre_proc_command
383             {
384 10     10   19 my ( $self, $args ) = @_;
385              
386 10         16 my $input_file = $args->{input};
387 10         17 my $output_file = $args->{output};
388 10         14 my $template = $args->{template};
389 10   50     24 my $xsltproc = ( $args->{xsltproc} // ( die "no xsltproc key" ) );
390              
391             return +{
392             xsltproc => $xsltproc,
393             cmd => [
394             map {
395 10         21 ( ref($_) eq '' ) ? $_
396             : $_->is_output() ? $output_file
397             : $_->is_input() ? $input_file
398              
399             # Not supposed to happen
400 74 50       227 : do { die "Unknown Argument in Command Template."; }
  0 100       0  
    100          
401             } @$template
402             ]
403             };
404             }
405              
406             sub _run_input_output_cmd
407             {
408 12     12   37 my $self = shift;
409 12         19 my $args = shift;
410              
411 12         22 my $input_file = $args->{input};
412 12         16 my $output_file = $args->{output};
413 12         22 my $make_output_file = $args->{make_output};
414              
415 12 100       60 if ( !defined($make_output_file) )
416             {
417 4         8 $make_output_file = $output_file;
418             }
419              
420 12 100 100     50 if (
421             ( !$self->_make_like() )
422             || $self->_should_update_output(
423             {
424             input => $input_file,
425             output => $make_output_file,
426             }
427             )
428             )
429             {
430 10         66 $self->_exec_command( $self->_pre_proc_command($args), );
431             }
432             }
433              
434             sub _on_output
435             {
436 18     18   35 my ( $self, $meth, $args ) = @_;
437              
438 18 100       62 return $self->_has_output() ? $self->$meth($args) : ();
439             }
440              
441             sub _calc_output_params
442             {
443 8     8   18 my ( $self, $args ) = @_;
444              
445             return (
446 8         23 output => $self->_calc_output_param_for_xslt($args),
447             make_output => $self->_calc_make_output_param_for_xslt($args),
448             );
449             }
450              
451             sub _calc_template_o_flag
452             {
453 8     8   16 my ( $self, $args ) = @_;
454              
455 8         25 return ( "-o", $self->_output_cmd_comp() );
456             }
457              
458             sub _calc_template_string_params
459             {
460 9     9   17 my ($self) = @_;
461              
462 6         36 return [ map { ( "--stringparam", @$_ ) }
463 9         14 @{ $self->_xslt_stringparams() } ];
  9         31  
464             }
465              
466             sub _run_xslt
467             {
468 9     9   15 my $self = shift;
469 9         14 my $args = shift;
470              
471 9         22 my @stylesheet_params = ( $self->_calc_default_xslt_stylesheet() );
472              
473 9 100       28 if ( defined( $self->_stylesheet() ) )
474             {
475 1         4 @stylesheet_params = ( $self->_stylesheet() );
476             }
477              
478 9         17 my @base_path_params = ();
479              
480 9 100       22 if ( defined( $self->_base_path() ) )
481             {
482 1         6 @base_path_params =
483             ( "--path", ( $self->_base_path() . '/' . $self->_xslt_mode() ), );
484             }
485              
486             return $self->_run_input_output_cmd(
487             {
488             input => $self->_input_path(),
489             $self->_on_output( '_calc_output_params', $args ),
490             xsltproc => 1,
491             template => [
492             "xsltproc",
493             "--nonet",
494             $self->_on_output( '_calc_template_o_flag', $args ),
495 9         33 @{ $self->_calc_template_string_params() },
  9         20  
496             @base_path_params,
497             @stylesheet_params,
498             $self->_input_cmd_comp(),
499             ],
500             },
501             );
502             }
503              
504             sub _run_xslt_and_from_fo
505             {
506 4     4   7 my $self = shift;
507 4         9 my $args = shift;
508              
509 4         10 my $xslt_output_path = $self->_output_path();
510              
511 4 100       11 if ( !defined($xslt_output_path) )
512             {
513 1         10 die "No -o flag was specified. See the help.";
514             }
515              
516             # TODO : do something meaningful if a period (".") is not present
517 3 100       14 if ( $xslt_output_path !~ m{\.}ms )
518             {
519 1         4 $xslt_output_path .= ".fo";
520             }
521             else
522             {
523 2         14 $xslt_output_path =~ s{\.([^\.]*)\z}{\.fo}ms;
524             }
525              
526 3         17 $self->_run_xslt( { output_path => $xslt_output_path } );
527              
528             return $self->_run_input_output_cmd(
529             {
530             input => $xslt_output_path,
531             output => $self->_output_path(),
532             template => [
533 3         63 "fop", ( "-" . $args->{fo_out_format} ),
534             $self->_output_cmd_comp(), $self->_input_cmd_comp(),
535             ],
536             xsltproc => 0,
537             },
538             );
539             }
540              
541             sub _run_mode_pdf
542             {
543 4     4   10 my $self = shift;
544              
545 4         19 return $self->_run_xslt_and_from_fo(
546             {
547             fo_out_format => "pdf",
548             },
549             );
550             }
551              
552             sub _run_mode_rtf
553             {
554 0     0   0 my $self = shift;
555              
556 0         0 return $self->_run_xslt_and_from_fo(
557             {
558             fo_out_format => "rtf",
559             },
560             );
561             }
562              
563             sub _input_cmd_comp
564             {
565 12     12   21 my $self = shift;
566              
567 12         31 return App::XML::DocBook::Docmake::CmdComponent->new(
568             {
569             is_input => 1,
570             is_output => 0,
571             }
572             );
573             }
574              
575             sub _output_cmd_comp
576             {
577 11     11   20 my $self = shift;
578              
579 11         58 return App::XML::DocBook::Docmake::CmdComponent->new(
580             {
581             is_input => 0,
582             is_output => 1,
583             }
584             );
585             }
586              
587             package App::XML::DocBook::Docmake::CmdComponent;
588             $App::XML::DocBook::Docmake::CmdComponent::VERSION = '0.1003';
589             use Class::XSAccessor {
590 1         13 accessors => [
591              
592             qw(
593             is_input
594             is_output
595             )
596             ]
597 1     1   2725 };
  1         3  
598              
599             sub new
600             {
601 23     23   44 my ( $class, $self ) = @_;
602 23         101 return bless $self, $class;
603             }
604              
605             1;
606              
607              
608             1;
609              
610             __END__