File Coverage

blib/lib/App/WRT/Markup.pm
Criterion Covered Total %
statement 99 113 87.6
branch 14 26 53.8
condition 6 14 42.8
subroutine 19 19 100.0
pod 9 9 100.0
total 147 181 81.2


line stmt bran cond sub pod time code
1             package App::WRT::Markup;
2              
3 9     9   71 use strict;
  9         19  
  9         269  
4 9     9   44 use warnings;
  9         17  
  9         271  
5 9     9   48 use feature "state";
  9         17  
  9         812  
6              
7 9     9   56 use base qw(Exporter);
  9         12  
  9         900  
8             our @EXPORT_OK = qw(line_parse image_markup eval_perl);
9              
10 9     9   64 use App::WRT::Image qw(image_size);
  9         20  
  9         460  
11 9     9   57 use App::WRT::Util qw(file_get_contents);
  9         19  
  9         412  
12              
13 9     9   50 use Carp;
  9         16  
  9         510  
14 9     9   65 use File::Basename;
  9         32  
  9         634  
15 9     9   7538 use Text::Textile;
  9         264596  
  9         435  
16 9     9   4288 use Text::Markdown::Discount;
  9         8318  
  9         15553  
17              
18             # Some useful defaults:
19              
20             my %tags = (
21             retcon => q{div class="retcon"},
22             freeverse => 'p',
23             list => "ul>\n
24             );
25              
26             my %end_tags = (
27             retcon => 'div',
28             freeverse => 'p',
29             list => "li>\n
30             );
31              
32             my %blank_lines = (
33             freeverse => "

\n\n

",

34             list => "\n\n
  • "
  • 35             );
    36              
    37             my %newlines = (
    38             freeverse => "
    \n"
    39             );
    40              
    41             my %dashes = (
    42             freeverse => ' — '
    43             );
    44              
    45             =over
    46              
    47             =item eval_perl
    48              
    49             Evaluate embedded Perl in a string, replacing blocks enclosed with tags
    50             with whatever they return (well, evaluated in a scalar context). Returns the
    51             modified string.
    52              
    53             Also handles simple ${variables}, replacing them from the keys to $self.
    54              
    55             =cut
    56              
    57             sub eval_perl {
    58 253     253 1 492 my $self = shift;
    59 253         543 my ($text) = @_;
    60              
    61 253         1181 while ($text =~ m{(.*?)}s) {
    62 62         167 my $block = $1;
    63              
    64             # Run the $block, and include anything returned:
    65 62         4460 my $output = eval $block;
    66              
    67 62 50       293 if ($@) {
    68             # Errors - log and return an empty string:
    69 0         0 carp($@);
    70 0         0 $output = '';
    71             }
    72              
    73 62         1065 $text =~ s{\Q$block\E}{$output}s;
    74             }
    75              
    76             # Interpolate variables:
    77 253         1114 $text =~ s{
    78             \$\{ ([a-zA-Z_]+) \}
    79             }{
    80 1159 50       2733 if (defined $self->{$1}) {
    81 1159         4289 $self->{$1};
    82             } else {
    83             # TODO: Possibly this should be fatal.
    84 0         0 "UNDEFINED: $1";
    85             }
    86             }gex;
    87              
    88 253         920 return $text;
    89             }
    90              
    91             =item line_parse
    92              
    93             Performs substitutions on lines called by fragment_slurp, at least. Calls
    94             include_process(), image_markup(), textile_process(), markdown_process().
    95              
    96             Returns string.
    97              
    98             Parses some special markup. Specifically:
    99              
    100             print "hello world";
    101             ${variable} interpolation from the WRT object
    102              
    103             path/to/file/from/project/root
    104              
    105             - Text::Textile to HTML
    106             - Text::Markdown::Discount to HTML
    107              
    108             filename.ext
    109             optional alt tag
    110             optional title text
    111              
    112            
    113            
    114            
    115              
    116             =cut
    117              
    118             sub line_parse {
    119 253     253 1 395 my $self = shift;
    120 253         462 my ($everything, $file) = (@_);
    121              
    122             # Take care of , , , and tags:
    123 253         642 include_process($self, $everything);
    124 253         598 textile_process($everything);
    125 253         3573 markdown_process($everything);
    126 253         2001 $everything =~ s!(.*?)!$self->image_markup($file, $1)!seg;
      10         50  
    127              
    128 253         1142 foreach my $key (keys %tags) {
    129             # Set some replacements, unless they've been explicitly set already:
    130 759   33     1754 $end_tags{$key} ||= $tags{$key};
    131              
    132             # Transform blocks:
    133 759         13004 while ($everything =~ m| (<$key>\n?) (.*?) (\n?) |sx) {
    134 2         6 my $open = $1;
    135 2         5 my $block = $2;
    136 2         3 my $close = $3;
    137              
    138             # Save the bits between instances of the block:
    139 2         36 my (@interstices) = split /\Q$open$block$close\E/s, $everything;
    140              
    141             # Transform dashes, blank lines, and newlines:
    142 2 100       12 dashes($dashes{$key}, $block) if defined $dashes{$key};
    143 2 50       13 $block =~ s/\n\n/$blank_lines{$key}/gs if defined $blank_lines{$key};
    144 2 100       8 newlines($newlines{$key}, $block) if defined $newlines{$key};
    145              
    146             # Slap it all back together as $everything, with start and end
    147             # tags:
    148 2         9 $block = "<$tags{$key}>$block";
    149 2         16 $everything = join $block, @interstices;
    150             }
    151             }
    152              
    153 253         1465 return $everything;
    154             }
    155              
    156             =item newlines($replacement, $block)
    157              
    158             Inline replace single newlines (i.e., line ends) within the block, except those
    159             preceded by a double-quote, which probably indicates a still-open tag.
    160              
    161             =cut
    162              
    163             sub newlines {
    164 1     1 1 10 $_[1] =~ s/(?<=[^"\n]) # not a double-quote or newline
    165             # don't capture
    166              
    167             \n # end-of-line
    168              
    169             (?=[^\n]) # not a newline
    170             # don't capture
    171             /$_[0]/xgs;
    172             }
    173              
    174             =item dashes($replacement, $block)
    175              
    176             Inline replace double dashes in a block - " -- " - with a given replacement.
    177              
    178             =cut
    179              
    180             sub dashes {
    181 1     1 1 4 $_[1] =~ s/(\s+) # whitespace - no capture
    182             \-{2} # two dashes
    183             (\n|\s+|$) # newline, whitespace, or eol
    184             /$1$_[0]$2/xgs;
    185              
    186             }
    187              
    188             =item include_process
    189              
    190             Inline replace filename tags, replacing them with the
    191             contents of files.
    192              
    193             =cut
    194              
    195             sub include_process {
    196 253     253 1 321 my $wrt = shift;
    197              
    198 253         1133 $_[0] =~ s{
    199              
    200             # start tag
    201             (.*?) # anything (non-greedy)
    202             # end tag
    203              
    204             }{
    205 12         51 retrieve_include($wrt, $1);
    206             }xesg;
    207             }
    208              
    209             =item retrieve_include
    210              
    211             Get the contents of an included file. This probably needs a great
    212             deal more thought than I am presently giving it.
    213              
    214             =cut
    215              
    216             sub retrieve_include {
    217 12     12 1 24 my $wrt = shift;
    218 12         40 my ($file) = @_;
    219              
    220             # Trim leading and trailing spaces:
    221 12         37 $file =~ s/^\s+//;
    222 12         47 $file =~ s/\s+$//;
    223              
    224 12 50       47 if ($file =~ m{^ (/ | [.]/) }x) {
    225             # TODO: Leads with a slash or a ./
    226 0         0 croak('Tried to open an include path with a leading / or ./ - not yet supported.');
    227             } else {
    228             # Use the archive root as path.
    229 12         49 $file = $wrt->{root_dir} . '/' . $file;
    230             }
    231              
    232 12 50       43 if ($wrt->{cache_includes}) {
    233 0 0       0 if (defined $wrt->{include_cache}->{$file}) {
    234 0         0 return $wrt->{include_cache}->{$file};
    235             }
    236             }
    237              
    238 12 50       238 unless (-e $file) {
    239 0         0 carp "No such file: $file";
    240 0         0 return '';
    241             }
    242              
    243 12 50       157 if (-d $file) {
    244 0         0 carp("Tried to open a directory as an include path: $file");
    245 0         0 return '';
    246             }
    247              
    248 12 50       52 if ($wrt->{cache_includes}) {
    249 0         0 $wrt->{include_cache}->{$file} = file_get_contents($file);
    250 0         0 return $wrt->{include_cache}->{$file};
    251             } else {
    252 12         49 return file_get_contents($file);
    253             }
    254             }
    255              
    256             =item textile_process
    257              
    258             Inline replace markup in a string.
    259              
    260             =cut
    261              
    262             # This is exactly the kind of code that, even though it isn't doing anything
    263             # especially over the top, looks ghastly to people who don't read Perl, so I'll
    264             # try to explain a bit.
    265              
    266             sub textile_process {
    267              
    268             # First, there's a state variable here which can retain the Text::Textile
    269             # object between invocations of the function, saving us a bit of time on
    270             # subsequent calls. This should be equivalent to creating a closure around
    271             # the function and keeping a $textile variable there.
    272 253     253 1 304 state $textile;
    273              
    274             # Second, instead of unrolling the arguments to the function, we just act
    275             # directly on the first (0th) one. =~ more or less means "do a regexy
    276             # thing on this". It's followed by s, the substitution operator, which can
    277             # use curly braces as delimiters between pattern and replacement.
    278              
    279 253         1027 $_[0] =~ s{
    280              
    281             # Find tags...
    282              
    283             # start tag
    284             (.*?) # anything (non-greedy)
    285             # end tag
    286              
    287             }{
    288              
    289             # ...and replace them with the result of evaluating this block.
    290              
    291             # //= means "defined-or-equals"; if the var hasn't been defined yet,
    292             # then make a new Textile object:
    293 1   33     13 $textile //= Text::Textile->new();
    294              
    295             # Process the stuff we slurped out of our tags - this value will be
    296             # used to replace the entire match from above (in Perl, the last
    297             # expression evaluated is the return value of subs, evals, etc.):
    298 1         274 $textile->process($1);
    299              
    300             }xesg;
    301              
    302             # x: eXtended regexp - whitespace ignored by default, comments allowed
    303             # e: Execute the replacement as Perl code, and use its value
    304             # s: treat all lines of the search subject as a Single string
    305             # g: Globally replace all matches
    306              
    307             # For the genuinely concise version of this, see markdown_process().
    308             }
    309              
    310             =item markdown_process
    311              
    312             Inline replace markup in a string.
    313              
    314             =cut
    315              
    316             sub markdown_process {
    317 253     253 1 297 state $markdown;
    318              
    319 253         412 my $flags = Text::Markdown::Discount::MKD_EXTRA_FOOTNOTE();
    320              
    321 253         1131 $_[0] =~ s{
    322             (.*?)
    323             }{
    324 21   66     155 $markdown //= Text::Markdown::Discount->new;
    325 21         158 $markdown->markdown($1, $flags);
    326             }xesg;
    327             }
    328              
    329             =item image_markup
    330              
    331             Parse out an image tag and return the appropriate html.
    332              
    333             Relies on image_size from App::WRT::Image.
    334              
    335             =cut
    336              
    337             sub image_markup {
    338 10     10 1 23 my $self = shift;
    339 10         44 my ($file, $block) = @_;
    340              
    341             # Get a basename and directory for the file (entry) referencing the image:
    342 10         298 my ($basename, $dir) = fileparse($file);
    343              
    344             # Truncated file date that just includes date + sub docs:
    345 10         45 my ($file_date) = $dir =~ m{
    346             (
    347             [0-9]{4}/ # year
    348             [0-9]{1,2}/ # month
    349             [0-9]{1,2}/ # day
    350             ([a-z]*/)* # sub-entries
    351             )
    352             $
    353             }x;
    354              
    355             # Process the contents of the tag:
    356 10         67 my ($image_url, $alt_text, $title_text) = split /\n/, $block;
    357 10   50     38 $alt_text ||= q{};
    358 10   33     70 $title_text ||= $alt_text;
    359              
    360             # Resolve relative paths:
    361 10         17 my $image_file;
    362 10 50       367 if (-e "$dir/$image_url" ) {
        50          
    363             # The path is to an image file in the same directory as current entry:
    364 0         0 $image_file = "$dir/$image_url";
    365 0         0 $image_url = "${file_date}${image_url}";
    366             } elsif (-e $self->{entry_dir} . "/$image_url") {
    367             # The path is to an image file starting with the entry_dir, like
    368             # 2005/9/20/candles.jpg -> ./archives/2005/9/20/candles.jpg
    369 10         53 $image_file = $self->{entry_dir} . "/$image_url";
    370             }
    371              
    372             # Get width & height in pixels for known filetypes:
    373 10         71 my ($width, $height) = image_size($self->{root_dir_abs} . '/' . $image_file);
    374              
    375             # This probably relies on mod_rewrite working:
    376 10         2217 $image_url = $self->{image_url_root} . $image_url;
    377 10         115 return <<"IMG";
    378            
    379             width="$width"
    380             height="$height"
    381             alt="$alt_text"
    382             title="$title_text" />
    383             IMG
    384             }
    385              
    386             =back
    387              
    388             1;