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 8     8   52 use strict;
  8         13  
  8         220  
4 8     8   36 use warnings;
  8         15  
  8         189  
5 8     8   36 use feature "state";
  8         26  
  8         568  
6              
7 8     8   42 use base qw(Exporter);
  8         12  
  8         670  
8             our @EXPORT_OK = qw(line_parse image_markup eval_perl);
9              
10 8     8   61 use App::WRT::Image qw(image_size);
  8         19  
  8         370  
11 8     8   54 use App::WRT::Util qw(file_get_contents);
  8         13  
  8         346  
12              
13 8     8   42 use Carp;
  8         17  
  8         386  
14 8     8   44 use File::Basename;
  8         12  
  8         534  
15 8     8   6049 use Text::Textile;
  8         211162  
  8         339  
16 8     8   3340 use Text::Markdown::Discount;
  8         6374  
  8         12481  
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 251     251 1 412 my $self = shift;
    59 251         417 my ($text) = @_;
    60              
    61 251         1037 while ($text =~ m{(.*?)}s) {
    62 61         149 my $block = $1;
    63              
    64             # Run the $block, and include anything returned:
    65 61         3925 my $output = eval $block;
    66              
    67 61 50       256 if ($@) {
    68             # Errors - log and return an empty string:
    69 0         0 carp($@);
    70 0         0 $output = '';
    71             }
    72              
    73 61         927 $text =~ s{\Q$block\E}{$output}s;
    74             }
    75              
    76             # Interpolate variables:
    77 251         1032 $text =~ s{
    78             \$\{ ([a-zA-Z_]+) \}
    79             }{
    80 1140 50       2402 if (defined $self->{$1}) {
    81 1140         3805 $self->{$1};
    82             } else {
    83             # TODO: Possibly this should be fatal.
    84 0         0 "UNDEFINED: $1";
    85             }
    86             }gex;
    87              
    88 251         717 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 251     251 1 331 my $self = shift;
    120 251         387 my ($everything, $file) = (@_);
    121              
    122             # Take care of , , , and tags:
    123 251         510 include_process($self, $everything);
    124 251         496 textile_process($everything);
    125 251         3537 markdown_process($everything);
    126 251         1797 $everything =~ s!(.*?)!$self->image_markup($file, $1)!seg;
      10         44  
    127              
    128 251         723 foreach my $key (keys %tags) {
    129             # Set some replacements, unless they've been explicitly set already:
    130 753   33     1535 $end_tags{$key} ||= $tags{$key};
    131              
    132             # Transform blocks:
    133 753         11270 while ($everything =~ m| (<$key>\n?) (.*?) (\n?) |sx) {
    134 2         6 my $open = $1;
    135 2         5 my $block = $2;
    136 2         4 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       15 $block =~ s/\n\n/$blank_lines{$key}/gs if defined $blank_lines{$key};
    144 2 100       13 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         10 $block = "<$tags{$key}>$block";
    149 2         16 $everything = join $block, @interstices;
    150             }
    151             }
    152              
    153 251         1251 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 251     251 1 276 my $wrt = shift;
    197              
    198 251         1037 $_[0] =~ s{
    199              
    200             # start tag
    201             (.*?) # anything (non-greedy)
    202             # end tag
    203              
    204             }{
    205 12         41 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 20 my $wrt = shift;
    218 12         35 my ($file) = @_;
    219              
    220             # Trim leading and trailing spaces:
    221 12         33 $file =~ s/^\s+//;
    222 12         43 $file =~ s/\s+$//;
    223              
    224 12 50       45 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         46 $file = $wrt->{root_dir} . '/' . $file;
    230             }
    231              
    232 12 50       36 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       219 unless (-e $file) {
    239 0         0 carp "No such file: $file";
    240 0         0 return '';
    241             }
    242              
    243 12 50       140 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       51 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         54 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 251     251 1 252 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 251         902 $_[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     16 $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         253 $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 251     251 1 236 state $markdown;
    318              
    319 251         301 my $flags = Text::Markdown::Discount::MKD_EXTRA_FOOTNOTE();
    320              
    321 251         1051 $_[0] =~ s{
    322             (.*?)
    323             }{
    324 21   66     125 $markdown //= Text::Markdown::Discount->new;
    325 21         143 $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 20 my $self = shift;
    339 10         47 my ($file, $block) = @_;
    340              
    341             # Get a basename and directory for the file (entry) referencing the image:
    342 10         304 my ($basename, $dir) = fileparse($file);
    343              
    344             # Truncated file date that just includes date + sub docs:
    345 10         42 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         66 my ($image_url, $alt_text, $title_text) = split /\n/, $block;
    357 10   50     34 $alt_text ||= q{};
    358 10   33     66 $title_text ||= $alt_text;
    359              
    360             # Resolve relative paths:
    361 10         18 my $image_file;
    362 10 50       318 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         44 $image_file = $self->{entry_dir} . "/$image_url";
    370             }
    371              
    372             # Get width & height in pixels for known filetypes:
    373 10         73 my ($width, $height) = image_size($self->{root_dir_abs} . '/' . $image_file);
    374              
    375             # This probably relies on mod_rewrite working:
    376 10         2062 $image_url = $self->{image_url_root} . $image_url;
    377 10         108 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;