File Coverage

blib/lib/Pandoc/Filter/ImagesFromCode.pm
Criterion Covered Total %
statement 99 105 94.2
branch 12 28 42.8
condition 11 27 40.7
subroutine 20 20 100.0
pod 3 6 50.0
total 145 186 77.9


line stmt bran cond sub pod time code
1             package Pandoc::Filter::ImagesFromCode;
2 1     1   888 use strict;
  1         2  
  1         32  
3 1     1   5 use warnings;
  1         3  
  1         29  
4 1     1   6 use utf8;
  1         2  
  1         6  
5 1     1   674 use Encode;
  1         11414  
  1         90  
6 1     1   27 use 5.010;
  1         4  
7              
8             our $VERSION = '0.36';
9              
10 1     1   7 use Digest::MD5 'md5_hex';
  1         2  
  1         66  
11 1     1   7 use IPC::Run3;
  1         2  
  1         46  
12 1     1   567 use File::Spec::Functions;
  1         1056  
  1         81  
13 1     1   703 use File::stat;
  1         8214  
  1         10  
14 1     1   93 use Pandoc::Elements;
  1         2  
  1         275  
15 1     1   9 use Scalar::Util 'reftype';
  1         3  
  1         48  
16 1     1   6 use parent 'Pandoc::Filter', 'Exporter';
  1         3  
  1         13  
17              
18             our @EXPORT_OK = qw(read_file write_file);
19              
20             sub new {
21 1     1 1 21 my ($class, %opts) = @_;
22              
23 1   50     4 $opts{from} //= 'code';
24 1   50     4 $opts{dir} //= '.';
25 1         3 $opts{dir} =~ s!/$!!;
26             $opts{name} //= sub {
27 1 50   1   7 $_[0]->id =~ /^[a-z0-9_]+$/i ? $_[0]->id
28             : md5_hex( encode( 'utf8', $_[0]->content ) );
29 1   50     10 };
30              
31 1 50       4 die "missing option: to\n" unless $opts{to};
32              
33 1 50 33     7 if ('ARRAY' ne reftype $opts{run} or !@{$opts{run}}) {
  1         5  
34 0         0 die "missing or empty option: run\n";
35             }
36              
37 1         4 bless \%opts, $class;
38             }
39              
40             sub to {
41 1     1 1 2 my $to = $_[0]->{to};
42 1         3 my $format = $_[1];
43 1 50       18 if (ref $to) {
    50          
44 0         0 return $to->($format);
45             } elsif ($to) {
46 1         7 return $to;
47             } else {
48 0         0 return 'png';
49             }
50             }
51              
52             sub action {
53 1     1 1 3 my $self = shift;
54              
55             sub {
56 1     1   4 my ($e, $format, $m) = @_;
57              
58 1 50       9 return if $e->name ne 'CodeBlock';
59              
60 1         32 my $code = $e->content;
61 1         7 my $dir = $self->{dir};
62              
63             my %args = (
64             name => $self->{name}->($e),
65             from => $self->{from},
66 1         4 to => $self->to($format),
67             );
68 1         13 $args{infile} = catfile($self->{dir}, "$args{name}.$args{from}");
69 1         8 $args{outfile} = catfile($self->{dir}, "$args{name}.$args{to}");
70              
71             # TODO: document or remove this experimental code. If keep, expand args
72 1         10 my $kv = $e->keyvals;
73 1         57 my @options = $kv->get_all('option');
74 1         23 push @options, map { split /\s+/, $_ } $kv->get_all('options');
  0         0  
75              
76             # TODO: print args in debug mode?
77              
78             # skip transformation if nothing has changed
79 1         19 my $in = stat($args{infile});
80 1         121 my $out = stat($args{outfile});
81 1 0 33     74 if (!$self->{force} and $in and $out and $in->mtime <= $out->mtime) {
      33        
      33        
82 0 0       0 if ($code eq read_file($args{infile}, ':utf8')) {
83             # no need to rebuild the same outfile
84 0         0 return build_image($e, $args{outfile});
85             }
86             }
87              
88 1         5 write_file($args{infile}, $code, ':utf8');
89              
90 1         3 my ($stderr, $stdout);
91             my @command = map {
92 2         4 my $s = $_;
93             #if ($args{substr $s, 1, -1})
94 2   33     12 $s =~ s|\$([^\$]+)\$| $args{$1} // $1 |eg;
  1         8  
95 2         8 $s
96 1         4 } @{$self->{run}};
  1         3  
97 1         3 push @command, @options;
98              
99 1         12 run3 \@command, \undef, \$stdout, \$stderr,
100             {
101             binmode_stdin => ':utf8',
102             binmode_stdout => ':raw',
103             binmode_stderr => ':raw',
104             };
105              
106 1 50       8029 if ($self->{capture}) {
107 1         36 write_file($args{outfile}, $stdout, ':raw');
108             }
109              
110             # TODO: include $code or $stderr on error in debug mode
111             # TODO: skip error if requested
112 1 50       349 die $stderr if $stderr;
113              
114 1         23 return build_image($e, $args{outfile});
115             }
116 1         10 }
117              
118             # build_image( $element [, $filename ] )
119             #
120             # Maps an element to an L element with attributes
121             # from the given element. The attribute C
, if available, is transformed
122             # into image caption. This utility function is useful for filters that transform
123             # content to images. See graphviz, tikz, lilypond and similar filters in the
124             # L.
125              
126             sub build_image {
127 1     1 0 21 my $e = shift;
128 1   50     11 my $filename = shift // '';
129              
130 1         37 my $keyvals = $e->keyvals;
131 1   50     147 my $title = $keyvals->get('title') // '';
132 1         40 my $img = Image attributes { id => $e->id, class => $e->class },
133             [], [$filename, $title];
134              
135 1   50     16 my $caption = $keyvals->get('caption') // '';
136 1 50       18 if (defined $caption) {
137 1         2 push @{$img->content}, Str($caption);
  1         33  
138             }
139              
140 1         29 return Plain [ $img ];
141             }
142              
143             sub write_file {
144 2     2 0 14 my ($file, $content, $encoding) = @_;
145              
146 2 50       202 open my $fh, ">$encoding", $file
147             or die "failed to create file $file: $!\n";
148 2         47 print $fh $content;
149 2         144 close $fh;
150             }
151              
152             sub read_file {
153 2     2 0 1309 my ($file, $encoding) = @_;
154              
155 2 50       118 open my $fh, "<$encoding", $file
156             or die "failed to open file: $file: $!\n";
157              
158 2         14 my $content = do { local $/; <$fh> };
  2         18  
  2         72  
159 2 50       29 close $fh or die "failed to close file: $file: $!\n";
160              
161 2         35 return $content;
162             }
163              
164             1;
165              
166             __END__