File Coverage

blib/lib/App/XML/DocBook/Docmake.pm
Criterion Covered Total %
statement 154 191 80.6
branch 39 58 67.2
condition 14 20 70.0
subroutine 33 41 80.4
pod 2 2 100.0
total 242 312 77.5


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