App-DocKnot
view release on metacpan or search on metacpan
lib/App/DocKnot/Spin/Thread.pm view on Meta::CPAN
# Generate HTML from the macro language thread.
#
# Thread is a macro language designed for producing HTML pages. This module
# parses thread and generates the corresponding HTML.
#
# SPDX-License-Identifier: MIT
##############################################################################
# Modules and declarations
##############################################################################
package App::DocKnot::Spin::Thread v8.0.1;
use 5.024;
use autodie;
use warnings FATAL => 'utf8';
use App::DocKnot;
use App::DocKnot::Util qw(print_fh);
use Encode qw(decode);
use Git::Repository ();
use Image::Size qw(html_imgsize);
use Path::Tiny qw(path);
use Perl6::Slurp qw(slurp);
use POSIX qw(strftime);
use Text::Balanced qw(extract_bracketed);
# The URL to the software page for all of my web page generation software,
# used to embed a link to the software that generated the page.
my $URL = 'https://www.eyrie.org/~eagle/software/docknot/';
# The table of available commands. The columns are:
#
# 1. Number of arguments or -1 to consume as many arguments as it can find.
# 2. Name of the method to call with the arguments and (if wanted) format.
# 3. Whether to look for a format in parens before the arguments.
#<<<
my %COMMANDS = (
# name args method want_format
block => [1, '_cmd_block', 1],
bold => [1, '_cmd_bold', 1],
break => [0, '_cmd_break', 0],
bullet => [1, '_cmd_bullet', 1],
class => [1, '_cmd_class', 1],
cite => [1, '_cmd_cite', 1],
code => [1, '_cmd_code', 1],
desc => [2, '_cmd_desc', 1],
div => [1, '_cmd_div', 1],
emph => [1, '_cmd_emph', 1],
entity => [1, '_cmd_entity', 0],
h1 => [1, '_cmd_h1', 1],
h2 => [1, '_cmd_h2', 1],
h3 => [1, '_cmd_h3', 1],
h4 => [1, '_cmd_h4', 1],
h5 => [1, '_cmd_h5', 1],
h6 => [1, '_cmd_h6', 1],
heading => [2, '_cmd_heading', 0],
image => [2, '_cmd_image', 1],
include => [1, '_cmd_include', 0],
italic => [1, '_cmd_italic', 1],
link => [2, '_cmd_link', 1],
number => [1, '_cmd_number', 1],
pre => [1, '_cmd_pre', 1],
quote => [3, '_cmd_quote', 1],
release => [1, '_cmd_release', 0],
rss => [2, '_cmd_rss', 0],
rule => [0, '_cmd_rule', 0],
signature => [0, '_cmd_signature', 0],
sitemap => [0, '_cmd_sitemap', 0],
size => [1, '_cmd_size', 0],
strike => [1, '_cmd_strike', 1],
strong => [1, '_cmd_strong', 1],
sub => [1, '_cmd_sub', 1],
sup => [1, '_cmd_sup', 1],
table => [2, '_cmd_table', 1],
tablehead => [-1, '_cmd_tablehead', 1],
tablerow => [-1, '_cmd_tablerow', 1],
under => [1, '_cmd_under', 1],
version => [1, '_cmd_version', 0],
q{=} => [2, '_define_variable', 0],
q{==} => [3, '_define_macro', 0],
q{\\} => [0, '_literal', 0],
);
#>>>
##############################################################################
# Input and output
##############################################################################
# Determine the path to a file relative to the current file being processed.
# If the current file being processed is standard input, the path is relative
# to the current working directory.
#
# $path - File path as a string
#
# Returns: Path::Tiny object holding the absolute path
sub _file_path {
my ($self, $file) = @_;
my $input_path = $self->{input}[-1][1];
if (defined($input_path)) {
my $path = $input_path->sibling($file);
lib/App/DocKnot/Spin/Thread.pm view on Meta::CPAN
# These methods are all used, but are indirected through the above table, so
# perlcritic gets confused.
#
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
# Define a new macro. This is the command handler for \==.
#
# $name - Name of the macro
# $args - Number of arguments
# $definition - Definition of the macro
#
# Returns: Block context, empty output
sub _define_macro {
my ($self, $name, $args, $definition) = @_;
$args = $self->_parse($args);
# Verify the argument count and definition.
if ($args !~ m{ \A \d+ \z }xms) {
$self->_warning("invalid macro argument count for \\$name");
return (1, q{});
}
for my $arg ($definition =~ m{ \\(\d+) }xmsg) {
if ($arg > $args) {
my $msg = "invalid macro placeholder \\$arg (greater than $args)";
$self->_warning($msg);
}
}
# We don't parse the macro definition now but we need to update the line
# number for accurate error reporting.
$self->{input}[-1][2] += $definition =~ tr{\n}{};
# Define the macro.
$self->{macro}{$name} = [$args, $definition];
return (1, q{});
}
# Define a new variable. This is the command handler for \=.
#
# $name - Name of the variable
# $value - Value of the variable
#
# Returns: Block context, empty output
sub _define_variable {
my ($self, $name, $value) = @_;
$self->{variable}{$name} = $self->_parse($value);
return (1, q{});
}
# Literal backslash. This is the command handler for \\.
sub _literal { return (0, q{\\}) }
##############################################################################
# Regular commands
##############################################################################
# Basic inline commands.
#<<<
sub _cmd_break { return (0, '<br />') }
sub _cmd_bold { my ($self, @a) = @_; return $self->_inline('b', @a) }
sub _cmd_cite { my ($self, @a) = @_; return $self->_inline('cite', @a) }
sub _cmd_class { my ($self, @a) = @_; return $self->_inline('span', @a) }
sub _cmd_code { my ($self, @a) = @_; return $self->_inline('code', @a) }
sub _cmd_emph { my ($self, @a) = @_; return $self->_inline('em', @a) }
sub _cmd_italic { my ($self, @a) = @_; return $self->_inline('i', @a) }
sub _cmd_strike { my ($self, @a) = @_; return $self->_inline('strike', @a) }
sub _cmd_strong { my ($self, @a) = @_; return $self->_inline('strong', @a) }
sub _cmd_sub { my ($self, @a) = @_; return $self->_inline('sub', @a) }
sub _cmd_sup { my ($self, @a) = @_; return $self->_inline('sup', @a) }
sub _cmd_under { my ($self, @a) = @_; return $self->_inline('u', @a) }
#>>>
# The headings.
sub _cmd_h1 { my ($self, @a) = @_; return $self->_heading(1, @a); }
sub _cmd_h2 { my ($self, @a) = @_; return $self->_heading(2, @a); }
sub _cmd_h3 { my ($self, @a) = @_; return $self->_heading(3, @a); }
sub _cmd_h4 { my ($self, @a) = @_; return $self->_heading(4, @a); }
sub _cmd_h5 { my ($self, @a) = @_; return $self->_heading(5, @a); }
sub _cmd_h6 { my ($self, @a) = @_; return $self->_heading(6, @a); }
# A horizontal rule.
sub _cmd_rule {
my ($self) = @_;
return (1, $self->_border_end() . "<hr />\n");
}
# Simple block commands.
sub _cmd_div {
my ($self, $format, $text) = @_;
return $self->_block('div', q{}, $format, $text);
}
sub _cmd_block {
my ($self, $format, $text) = @_;
return $self->_block('blockquote', q{}, $format, $text);
}
sub _cmd_bullet {
my ($self, $format, $text) = @_;
my $border = $self->_border_start('bullet', "<ul>\n", "</ul>\n\n");
return $self->_block('li', $border, $format, $text);
}
sub _cmd_number {
my ($self, $format, $text) = @_;
my $border = $self->_border_start('number', "<ol>\n", "</ol>\n\n");
return $self->_block('li', $border, $format, $text);
}
# A description list entry.
#
# $format - Format string
# $heading - Initial heading
# $text - Body text
sub _cmd_desc {
my ($self, $format, $heading, $text) = @_;
$heading = $self->_parse($heading);
my $format_attr = $self->_format_attr($format);
my $border = $self->_border_start('desc', "<dl>\n", "</dl>\n\n");
lib/App/DocKnot/Spin/Thread.pm view on Meta::CPAN
Inserts a bullet list showing the structure of the whole site. A C<sitemap>
argument must be provided to the constructor to use this command. (If invoked
via App::DocKnot::Spin, this means a F<.sitemap> file must be present at the
root of the source directory.)
Be aware that B<spin> doesn't know whether a file contains a C<\sitemap>
command and hence won't know to regenerate a file when the F<.sitemap> file
has changed. You will need touch the source file to force it to be respun.
=item \table[OPTIONS][BODY]
Creates a table.
The OPTIONS text is added verbatim to the <table> tag in the generated HTML,
so it can be used to set various HTML attributes like C<cellpadding> that
aren't easily accessible in a portable fashion from style sheets.
BODY is the body of the table, which should generally consist exclusively of
C<\tablehead> and C<\tablerow> commands.
An example table:
\table[rules="cols" borders="1"][
\tablehead [Older Versions] [Webauth v3]
\tablerow [suauthSidentSrvtab] [WebAuthKeytab]
\tablerow [suauthFailAction] [WebAuthLoginURL]
\tablerow [suauthDebug] [WebAuthDebug]
\tablerow [suauthProxyHeader] [(use mod_headers)]
]
=item \tablehead[CELL][CELL] ...
A heading row in a table. C<\tablehead> takes any number of CELL arguments,
wraps them all in a C<< <tr> >> table row tag, and puts each cell inside C<<
<th> >>.
If a cell should have a class attribute, use a C<\class> command around the
CELL text. The class attribute will be "lifted" up to become an attribute of
the enclosing C<< <th> >> tag.
=item \tablerow[CELL][CELL] ...
A regular row in a table. C<\tablerow> takes any number of CELL arguments,
wraps them all in a C<< <tr> >> table row tag, and puts each cell inside C<<
<td> >>.
If a cell should have a class attribute, use a C<\class> command around the
CELL text. The class attribute will be "lifted" up to become an attribute of
the enclosing C<< <td> >> tag.
=back
=head2 Inline Commands
Inline commands can be used in the middle of a paragraph intermixed with other
text. Most of them are simple analogs to their HTML counterparts. All of the
following take a single argument (the enclosed text), an optional formatting
instruction, and map to simple HTML tags:
\bold <b></b> (usually use \strong)
\cite <cite></cite>
\code <code></code>
\emph <em></em>
\italic <i></i> (usually use \emph)
\strike <strike></strike> (should use styles)
\strong <strong></strong>
\sub <sub></sub>
\sup <sup></sup>
\under <u></u> (should use styles)
Here are the other inline commands:
=over 4
=item \break
A forced line break, C<< <br> >> in HTML.
=item \class[TEXT]
Does nothing except wrap TEXT in an HTML C<< <span> >> tag. The only purpose
of this command is to use it with a formatting instruction to generate an HTML
C<class> attribute on the C<< <span> >> tag. For example, you might write:
\class(red)[A style sheet can make this text red.]
and then use a style sheet that changes the text color for class C<red>.
=item \entity[CODE]
An HTML entity with code CODE. This normally becomes C<&CODE;> or C<&#CODE;>
in the generated HTML, depending on whether CODE is entirely numeric.
Use C<\entity[91]> and C<\entity[93]> for unbalanced C<[> and C<]> characters,
respectively.
Thread source is UTF-8, so this command is normally only necessary to escape
unbalanced square brackets.
=item \image[URL][TEXT]
Insert an inline image. TEXT is the alt text for the image (which will be
displayed on non-graphical browsers). Height and width tags are added
automatically if the URL is a relative path name and the corresponding file
exists and is supported by the Perl module Image::Size.
=item \link[URL][TEXT]
Create a link to URL with link text TEXT. Equivalent to C<< <a href> >>.
=item \release[PACKAGE]
If the C<versions> argument was provided, replaced with the latest release
date of PACKAGE. The date will be in the UTC time zone, not the local time
zone.
=item \size[FILE]
Replaced with the size of FILE in B, KB, MB, GB, or TB as is most appropriate,
without decimal places. The next largest unit is used if the value is larger
than 1024. 1024 is used as the scaling factor, not 1000.
=item \version[PACKAGE]
If the C<versions> argument was provided, replaced with the latest version of
PACKAGE.
=back
=head2 Defining Variables and Macros
One of the reasons to use thread instead of HTML is the ability to define new
macros on the fly. If there are constructs that are used more than once in
the page, you can define a macro at the top of that page and then use it
throughout the page.
A variable can be defined with the command:
\=[VARIABLE][VALUE]
where VARIABLE is the name that will be used (can only be alphanumerics plus
underscore) and VALUE is the value that string will expand into. Any later
occurrence of \=VARIABLE in the file will be replaced with <value>. For
example:
\=[FOO][some string]
will cause any later occurrences of C<\=FOO> in the file to be replaced with
the text C<some string>. Consider using this to collect external URLs for
links at the top of a page for easy updating.
A macro can be defined with the command:
\==[NAME][NARGS][DEFINITION]
where NAME is the name of the macro (again consisting only of alphanumerics or
underscore), NARGS is the number of arguments that it takes, and DEFINITION is
the definition of the macro.
When the macro is expanded, any occurrence of C<\1> in the definition is
replaced with the first argument, any occurrence of C<\2> with the second
argument, and so forth, and then the definition with those substitutions is
parsed as thread, as if it were written directly in the source page.
For example:
\==[bolddesc] [2] [\desc[\bold[\1]][\2]]
defines a macro C<\bolddesc> that takes the same arguments as the regular
C<\desc> command but always wraps the first argument, the heading, in C<<
<strong> >>.
=head1 AUTHOR
Russ Allbery <rra@cpan.org>
=head1 COPYRIGHT AND LICENSE
Copyright 1999-2011, 2013, 2021-2023 Russ Allbery <rra@cpan.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=head1 SEE ALSO
L<docknot(1)>, L<App::DocKnot::Spin>, L<App::DocKnot::Spin::Sitemap>,
L<App::DocKnot::Spin::Versions>
This module is part of the App-DocKnot distribution. The current version of
DocKnot is available from CPAN, or directly from its web site at
L<https://www.eyrie.org/~eagle/software/docknot/>.
=cut
# Local Variables:
# copyright-at-end-flag: t
# End:
( run in 0.540 second using v1.01-cache-2.11-cpan-39bf76dae61 )