File Coverage

File:lib/Yukki/Web/Plugin/YukkiText.pm
Coverage:39.4%

linestmtbrancondsubpodtimecode
1package Yukki::Web::Plugin::YukkiText;
2
3
1
1
500
3
use v5.24;
4
1
1
1
3
2
3
use utf8;
5
1
1
1
10
4
2
use Moo;
6
7
1
1
1
184
1
6
use Type::Utils;
8
1
1
1
986
1
6
use Types::Standard qw( HashRef Str );
9
10extends 'Yukki::Web::Plugin';
11
12# ABSTRACT: format text/yukki files using markdown, etc.
13
14
1
1
1
442
2
26
use Text::MultiMarkdown;
15
1
1
1
2
3
24
use Try::Tiny;
16
17
1
1
1
2
1
5
use namespace::clean;
18
19 - 49
=head1 SYNOPSIS

  # Plugins are not used directly...

  my $repo = $self->model('Repository', { name => 'main' });
  my $file = $repo->file({ full_path => "some-file.yukki' });
  my $html = $file->fetch_formatted($ctx);

=head1 DESCRIPTION

Yukkitext formatting is based on Multi-Markdown, which is an extension to regular markdown that adds tables, metadata, and a few other tidbits. In addition to this, yukkitext adds linking using double-bracket notation:

  [[ A Link ]]
  [[ ./A Sub-Page Link ]]
  [[ ./A Sub-Dir/Sub-Page Link ]]
  [[ ./a-sub-dir/sub-page-link.pdf | Sub-Page PDF ]]

This link format is based loosely upon the format used by MojoMojo, which I was using prior to developing Yukki.

It also adds support for format helpers usinga  double-curly brace notation:

  {{attachment:Path/To/Attachment.pdf}}
  {{=:5 + 5}}

=head1 ATTRIBUTES

=head2 html_formatters

This returns the yukkitext formatter for "text/yukki".

=cut
50
51has html_formatters => (
52    is          => 'ro',
53    isa         => HashRef[Str],
54    required    => 1,
55    default     => sub { +{
56        'text/yukki'    => 'yukkitext',
57    } },
58);
59
60with 'Yukki::Web::Plugin::Role::Formatter';
61
62 - 69
=head2 markdown

This is the L<Text::MultiMarkdown> object for rendering L</yukkitext>. Do not
use.

Provides a C<format_markdown> method delegated to C<markdown>. Do not use.

=cut
70
71has markdown => (
72    is          => 'ro',
73    isa         => class_type('Text::MultiMarkdown'),
74    required    => 1,
75    lazy        => 1,
76    builder     => '_build_markdown',
77    handles     => {
78        'format_markdown' => 'markdown',
79    },
80);
81
82sub _build_markdown {
83
1
40
    Text::MultiMarkdown->new(
84        markdown_in_html_blocks => 1,
85        heading_ids             => 0,
86        strip_metadata          => 1,
87    );
88}
89
90 - 96
=head1 METHODS

=head2 yukkilink

Used to help render yukkilinks. Do not use.

=cut
97
98sub yukkilink {
99
0
1
0
    my ($self, $params) = @_;
100
101
0
0
    my $file       = $params->{file};
102
0
0
    my $ctx        = $params->{context};
103
0
0
    my $repository = $file->repository_name;
104
0
0
    my $link       = $params->{link};
105
0
0
    my $label      = $params->{label};
106
107
0
0
0
0
    $link =~ s/^\s+//; $link =~ s/\s+$//;
108
109
0
0
    my ($repo_name, $local_link) = split /:/, $link, 2 if $link =~ /:/;
110
0
0
    if (defined $repo_name and defined $self->app->settings->{repositories}{$repo_name}) {
111
0
0
        $repository = $repo_name;
112
0
0
        $link       = $local_link;
113    }
114
115    # If we did not get a label, make the label into the link
116
0
0
    if (not defined $label) {
117
0
0
        ($label) = $link =~ m{([^/]+)$};
118
0
0
        $link = $self->app->munge_label($link);
119    }
120
121
0
0
    my @base_name;
122
0
0
    if ($file->full_path) {
123
0
0
        $base_name[0] = $file->full_path;
124
0
0
        $base_name[0] =~ s/\.yukki$//g;
125    }
126
127
0
0
    $link = join '/', @base_name, $link if $link =~ m{^\./};
128
0
0
    $link =~ s{^/}{};
129
0
0
    $link =~ s{/\./}{/}g;
130
131
0
0
0
0
    $label =~ s/^\s*//; $label =~ s/\s*$//;
132
133
0
0
0
0
    my $b = sub { $ctx->rebase_url($_[0]) };
134
135
0
0
    my $link_repo = $self->model('Repository', { name => $repository });
136
0
0
    my $link_file = $link_repo->file({ full_path => $link });
137
138
0
0
    my $class = $link_file->exists ? 'exists' : 'not-exists';
139
0
0
    return qq{<a class="$class" href="}.$b->("page/view/$repository/$link").qq{">$label</a>};
140}
141
142 - 146
=head2 yukkiplugin

Used to render plugged in markup. Do not use.

=cut
147
148sub yukkiplugin {
149
0
1
0
    my ($self, $params) = @_;
150
151
0
0
    my $ctx         = $params->{context};
152
0
0
    my $plugin_name = $params->{plugin_name};
153
0
0
    my $arg         = $params->{arg};
154
155
0
0
    my $text;
156
157
0
0
    my @plugins = $self->app->format_helper_plugins;
158
0
0
    PLUGIN: for my $plugin (@plugins) {
159
0
0
        my $helpers = $plugin->format_helpers;
160
0
0
        if (defined $helpers->{ $plugin_name }) {
161            $text = try {
162
0
0
                my $helper = $helpers->{ $plugin_name };
163                $plugin->$helper({
164                    context     => $ctx,
165                    file        => $params->{file},
166
0
0
                    helper_name => $plugin_name,
167                    arg         => $arg,
168                });
169            }
170
171            catch {
172
0
0
                warn "Plugin Error: $_";
173
0
0
            };
174
175
0
0
            last PLUGIN if defined $text;
176        }
177    }
178
179
0
0
    $text //= "{{$plugin_name:$arg}}";
180
0
0
    return $text;
181}
182
183 - 200
=head2 yukkitext

  my $html = $view->yukkitext({
      context    => $ctx,
      repository => $repository_name,
      page       => $page,
      file       => $file,
  });

Yukkitext is markdown plus some extra stuff. The extra stuff is:

  [[ main:/link/to/page.yukki | Link Title ]] - wiki link
  [[ /link/to/page.yukki | Link Title ]]      - wiki link
  [[ /link/to/page.yukki ]]                   - wiki link

  {{attachment:file.pdf}}                     - attachment URL

=cut
201
202sub yukkitext {
203
1
1
2
    my ($self, $params) = @_;
204
205
1
3
    my $file       = $params->{file};
206
1
4
    my $position   = 0 + ($params->{position} // -1);
207
1
17
    my $repository = $file->repository_name;
208
1
31
    my $yukkitext  = $file->fetch;
209
210
1
14196
    $yukkitext =~ s[(.{$position}.*?)$][$1<span id="yukkitext-caret"></span>]sm
211        if $position >= 0;
212
213    # Yukki Links
214
1
7
    $yukkitext =~ s{
215        (?<!\\)                 # \ will escape the link
216        \[\[ \s*                # [[ to start it
217
218            (?: ([\w]+) : )?    # repository: is optional
219            ([^|\]]+) \s*       # link/to/page is mandatory
220
221            (?: \|              # | to split link from label
222                ([^\]]+)        # a pretty label (needs trimming)
223            )?                  # is optional
224
225        \]\]                    # ]] to end
226    }{
227
0
0
        $self->yukkilink({
228            %$params,
229
230            repository => $1 // $repository,
231            link       => $2,
232            label      => $3,
233        });
234    }xeg;
235
236    # Handle escaped links, hide the escape
237
1
4
    $yukkitext =~ s{
238        \\                      # \ will escape the link
239        (\[\[ \s*               # [[ to start it
240
241            (?: [\w]+ : )?      # repository: is optional
242            [^|\]]+ \s*         # link/to/page is mandatory
243
244            (?: \|              # | to split link from label
245                [^\]]+          # a pretty label (needs trimming)
246            )?                  # is optional
247
248        \]\])                    # ]] to end
249    }{$1}gx;
250
251    # Yukki Plugins
252
1
3
    $yukkitext =~ s{
253        (?<!\\)                 # \ will escape the plugin
254        \{\{ \s*                # {{ to start it
255
256            ([^:]+) :           # plugin_name: is required
257
258            (.*?)               # plugin arguments
259
260        \}\}                    # }} to end
261    }{
262
0
0
        $self->yukkiplugin({
263            %$params,
264
265            plugin_name => $1,
266            arg         => $2,
267        });
268   }xegms;
269
270    # Handle the escaped plugin thing
271
1
4
    $yukkitext =~ s{
272        \\                      # \ will escape the plugin
273        (\{\{ \s*               # {{ to start it
274
275            [^:]+ :             # plugin_name: is required
276
277            .*?                 # plugin arguments
278
279        \}\})                   # }} to end
280    }{$1}xgms;
281
282
1
49
    my $formatted = '<div>' . $self->format_markdown($yukkitext) . '</div>';
283
284    # Just in case markdown mangled the caret marker:
285
1
3318
    $formatted =~ s[&lt;span id="yukkitext-caret"&gt;&lt;/span&gt;]
286                   [<span id="yukkitext-caret"></span>];
287
288
1
22
    return $formatted;
289}
290
2911;