App-DocKnot

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

 - Support version numbers prefixed with v in release tarball names, since
   this appears to be the convention for Perl modules that use semantic
   versions.

 - Change the docknot.yaml field orphaned to unmaintained, and change the
   templates to say the package is not maintained instead of orphaned.
   This terminology is more precise and less metaphorical.

 - Remove support information from the README.md and README templates for
   packages that are no longer maintained, and adjust some of the wording
   there and in the thread template.

 - Add missing </address> closing tag in pages rendered from Markdown.

 - Fix typo in README.md template for ExtUtils::MakeMaker packages.

 - Fix the URL in the footer added by docknot spin to point to DocKnot
   instead of my old web tools page.

7.01 - 2022-01-19

Changes  view on Meta::CPAN

   file with instructions for how to convert it to HTML.  Via this
   mechanism, support formatting Markdown files as HTML via docknot spin.
   This support requires the pandoc program be installed.  The path to
   pandoc may be specified in the DocKnot global configuration file.

 - Support *.spin pointers in addition to *.rpod pointers for external POD
   files.  The command-line flags used in *.rpod pointers are replaced by
   the title and options key of the *.spin file.  *.rpod files are
   deprecated and support will be removed in a future version of DocKnot.

 - Add spin_thread_output method to App::DocKnot::Spin::Thread, intended
   to convert thread to HTML as part of a conversion pipeline of a
   non-thread input file, while still using sitemap information and
   generating the page footer.  DocKnot now depends on Path::Tiny.

 - Support creating distributions from branches named main rather than
   master.  The first of main or master that's found in the repository
   will be used.

 - Move some utility functions into a new App::DocKnot::Util module.  This
   is primarily intended for internal use by other App::DocKnot modules.

 - Fix unintended localization of dates in RSS output, which are supposed
   to be RFC 2822 dates and therefore always use English month and day of
   week names.  Thanks to Slaven Rezić for testing.

 - Add load_yaml_file method to App::DocKnot, which loads a YAML file with
   schema checking.

 - Fix small whitespace problems in thread output.

5.00 - 2021-09-12

 - Merge spin and spin-rss into this package, making it a full, if highly
   idiosyncratic, static site generator built around a macro language
   called thread.  This adds new commands docknot spin, docknot spin-rss,
   and docknot spin-thread, as well as modules App::DocKnot::Spin and
   several submodules.  docknot spin should be equivalent to the old spin
   command except that \id support has been dropped.  DocKnot now depends
   on Date::Parse (from TimeDate), Git::Repository, Image::Size, and
   Pod::Thread 3.00 or later.

 - Support setting distribution.packaging.debian.package along with
   distribution.packaging.debian.personal to specify the package name.  Do
   not generate links to Debian in that case.

 - Ignore configure~ when checking distribution tarballs.

 - Stop generating a developer link to a to-do list by default in thread
   output.  Not all of my packages use a TODO file, so the ones that do
   should explicitly add this to the developer docs list.

4.01 - 2021-02-27

 - DocKnot now supports a global configuration file.  The default location
   is $HOME/.config/docknot/config.yaml, but it honors the XDG environment
   variables.  Currently, this configuration file can be used to set the
   distribution directory and signing PGP key for docknot dist.

Changes  view on Meta::CPAN

   submodules.

 - Add new docknot dist command and App::DocKnot::Dist module, which runs
   appropriate commands to create a distribution tarball.  This command
   will also run the test suite, with both Clang and GCC for packages
   using Autoconf, and will also build and test with C++ if the package
   indicates that it supports C++ via the build.cplusplus key in the
   package metadata.

 - Support orphaned warnings in the README and README.md output as well as
   thread output.

 - Add bug tracker links pointing to the CPAN RT installation to the
   developer documentation links section of the thread template if
   support.cpan is set.

 - Output more helpful information about test failures on Windows and
   other systems that don't have the diff command.

2.00 - 2019-01-12

 - Move previous docknot command functionality to a new docknot generate
   subcommand.  All docknot actions in the future will be added as
   subcommands.

Changes  view on Meta::CPAN

1.06 - 2018-08-31

 - When generating text output, put the footnotes containing URLs for
   links immediately following the containing paragraph rather than the
   end of the text block.  This is both more readable and avoids odd
   placement of the footnotes when a template adds further paragraphs to
   the end of a text block containing footnotes.

 - Do not wrap paragraphs in output that seem to be a bunch of short
   lines, and add support for broken quotes (multiple short lines, such as
   poetry) in quotes in the thread template.

 - Adjust the README and README.md template to say that make warnings
   requires either GCC or Clang, instead of only mentioning GCC.

 - Adjust the README.md wording for the list information URL when package
   releases are announced on a mailing list.

 - Add support for additional developer documentation links in the thread
   output template.

 - Add support for contributed program documentation links in the thread
   output template.

1.05 - 2018-05-05

 - Add the Travis-CI badge to README.md and a link to Travis-CI to the
   thread development links if the vcs.travis key is set in metadata.json.

 - Add a Shields.io badge for the CPAN version to README.md and a link to
   metacpan.org in the thread output if the distribution.cpan key is set
   in metadata.json.

 - Move the description of Lancaster Consensus environment variables into
   the testing section of README and README.md instead of the requirements
   section, since they're more about running the tests and less about
   package requirements in general.

 - Add support for a new packaging/extra metadata file (setting the
   packaging.extra key in templates) and use it in the thread template.

 - Correctly handle multi-paragraph debian.summary metadata in the thread
   template.

 - Fix formatting bug in the README template when additional bootstrap
   documentation is provided.

 - DocKnot now requires Perl 5.24 or later.

1.04 - 2018-03-24

 - Fix SPDX test failure on Windows.

Changes  view on Meta::CPAN

   SPDX license list.

 - Add SPDX-License-Identifier headers to all substantial source files,
   and add a test to check for them.

1.02 - 2017-12-31

 - Support quoted paragraphs (each line starting with ">") and turn them
   into indentation when turning markup into plain text.

 - Support numbered lists when converting to thread.

 - Force long, unbreakable lines to be left intact when wrapping.

 - When wrapping paragraphs, preserve two spaces after periods even when
   the period comes before a closing parenthesis or quote mark.

 - Support test/prefix metadata, which replaces the introduction to the
   test instructions.

 - Add new license text BSD-3-clause-or-GPL-1+ to support pam-krb5.
   Support markdown formatting in license texts when converting to thread
   to handle the numbered clauses.

 - Support more complex quote attributions in thread output, and
   automatically add \cite[] around work names if they don't contain
   double quotes.

 - Add security advisory support to the thread template.

1.01 - 2016-12-25

 - Add build and test instructions for Autoconf packages, including
   details about Kerberos configuration, to the README and README.md
   templates.  Allow a testing section in the metadata to override the
   added testing section.  Add a flag to indicate that the package is not
   intended to be installed, which suppresses some of the template.

 - Add support for license notices (the notices metadata file), which
   should be appended to the end of the stock license statement wherever
   it is generated.

 - Add support for stock building and installation sections for Perl
   module packages using either Module::Build or ExtUtils::MakeMaker.

 - Add thread template support for annotating quotes shorter than 80
   characters with the "short" class.

 - Add support for additional bootstrapping requirements in a separate
   bootstrap metadata file, appended to the Autotools bootstrapping
   section (at least for right now).

 - Allow markup in the license notices section.

1.00 - 2016-10-26

 - Initial public release.  Supports generating text and Markdown README
   files and a thread index page.  There is some automation of the
   requirements section, but only automation for the build and test
   section for Module::Build Perl modules.

MANIFEST  view on Meta::CPAN

share/schema/config.yaml
share/schema/docknot.yaml
share/schema/licenses.yaml
share/schema/pointer.yaml
share/templates/changes.tmpl
share/templates/html.tmpl
share/templates/index.tmpl
share/templates/readme-md.tmpl
share/templates/readme.tmpl
share/templates/rss.tmpl
share/templates/thread.tmpl
t/cli/errors.t
t/cli/generate.t
t/cli/spin.t
t/config/basic.t
t/data/dist/fake-gpg
t/data/dist/package/Build.PL
t/data/dist/package/docs/docknot.yaml
t/data/dist/package/lib/Empty.pm
t/data/dist/package/MANIFEST
t/data/dist/package/MANIFEST.SKIP
t/data/dist/package/t/api/empty.t.in
t/data/generate/ansicolor/docknot.yaml
t/data/generate/ansicolor/output/readme
t/data/generate/ansicolor/output/readme-md
t/data/generate/ansicolor/output/thread
t/data/generate/c-tap-harness/docknot.yaml
t/data/generate/c-tap-harness/output/readme
t/data/generate/c-tap-harness/output/readme-md
t/data/generate/c-tap-harness/output/thread
t/data/generate/control-archive/docknot.yaml
t/data/generate/control-archive/output/readme
t/data/generate/control-archive/output/readme-md
t/data/generate/control-archive/output/thread
t/data/generate/docknot/output/thread
t/data/generate/lbcd/docknot.yaml
t/data/generate/lbcd/output/readme
t/data/generate/lbcd/output/readme-md
t/data/generate/lbcd/output/thread
t/data/generate/pam-krb5/docknot.yaml
t/data/generate/pam-krb5/output/readme
t/data/generate/pam-krb5/output/readme-md
t/data/generate/pam-krb5/output/thread
t/data/generate/pgp-sign/docknot.yaml
t/data/generate/pgp-sign/output/readme
t/data/generate/pgp-sign/output/readme-md
t/data/generate/pgp-sign/output/thread
t/data/generate/pod-thread/docknot.yaml
t/data/generate/pod-thread/output/readme
t/data/generate/pod-thread/output/readme-md
t/data/generate/pod-thread/output/thread
t/data/generate/remctl/docknot.yaml
t/data/generate/remctl/output/readme
t/data/generate/remctl/output/readme-md
t/data/generate/remctl/output/thread
t/data/generate/rra-c-util/docknot.yaml
t/data/generate/rra-c-util/output/readme
t/data/generate/rra-c-util/output/readme-md
t/data/generate/rra-c-util/output/thread
t/data/generate/wallet/docknot.yaml
t/data/generate/wallet/output/readme
t/data/generate/wallet/output/readme-md
t/data/generate/wallet/output/thread
t/data/perl.conf
t/data/perlcriticrc
t/data/perltidyrc
t/data/regenerate-data
t/data/spin/errors/errors.th
t/data/spin/input/.rss
t/data/spin/input/.sitemap
t/data/spin/input/.versions
t/data/spin/input/index.th
t/data/spin/input/journal/.macros

MANIFEST  view on Meta::CPAN

t/lib/Test/RRA.pm
t/lib/Test/RRA/Config.pm
t/lib/Test/RRA/ModuleVersion.pm
t/metadata/licenses.t
t/release/basic.t
t/spin/errors.t
t/spin/file.t
t/spin/markdown.t
t/spin/sitemap.t
t/spin/text.t
t/spin/thread.t
t/spin/tree.t
t/spin/versions.t
t/style/coverage.t
t/style/critic.t
t/style/kwalitee.t
t/style/minimum-version.t
t/style/module-version.t
t/style/obsolete-strings.t
t/style/strict.t
t/update/basic.t

README  view on Meta::CPAN

              (Static web site and documentation generator)
                Maintained by Russ Allbery <rra@cpan.org>

  Copyright 1999-2024 Russ Allbery <rra@cpan.org>.  This software is
  distributed under a BSD-style license.  Please see the section LICENSE
  below for more information.

BLURB

  DocKnot is a static web site generator built around a macro language
  called thread, with special support for managing software releases.  In
  addition to building a web site, it can generate distribution tarballs
  and consistent human-readable software package documentation from a YAML
  metadata file and templates.  The goal is to generate both web pages and
  distributed documentation files (such as README) from the same source,
  using templates for consistency across multiple packages.

DESCRIPTION

  In 1999, I wrote a program named spin that implemented an idiosyncratic
  macro language called thread.  It slowly expanded into a static web site
  generator and gained additional features to manage the journal entries,
  book reviews, RSS feeds, and software releases.  DocKnot is the latest
  incarnation.

  In addition to its static web site generator, DocKnot can use one
  metadata file as its source information and generate all the various
  bits of documentation for a software package.  This allows me to make
  any changes in one place and then regenerate the web page, included
  documentation, and other files to incorporate those changes.  It also
  lets me make changes to the templates to improve shared wording and push
  that out to every package I maintain without having to remember track
  those changes in each package.

  DocKnot is also slowly absorbing other tools that I use for software
  distribution and web site maintenance, such as generating distribution
  tarballs for software packages.

  DocKnot was designed and written for my personal needs, and I'm not sure
  it will be useful for anyone else.  At the least, the template files are
  rather specific to my preferences about how to write package
  documentation, and the thread macro language is highly specialized for
  my personal web site.  I'm not sure if I'll have the time to make it a
  more general tool.  But you're certainly welcome to use it if you find
  it useful, send pull requests to make it more general, or take ideas
  from it for your own purposes.

REQUIREMENTS

  Perl 5.24 or later and Module::Build are required to build this module.
  The following additional Perl modules are required to use it:

README.md  view on Meta::CPAN

[![Debian
package](https://img.shields.io/debian/v/docknot/unstable)](https://tracker.debian.org/pkg/docknot)

Copyright 1999-2024 Russ Allbery <rra@cpan.org>.  This software is
distributed under a BSD-style license.  Please see the section
[License](#license) below for more information.

## Blurb

DocKnot is a static web site generator built around a macro language
called thread, with special support for managing software releases.  In
addition to building a web site, it can generate distribution tarballs and
consistent human-readable software package documentation from a YAML
metadata file and templates.  The goal is to generate both web pages and
distributed documentation files (such as `README`) from the same source,
using templates for consistency across multiple packages.

## Description

In 1999, I wrote a program named `spin` that implemented an idiosyncratic
macro language called thread.  It slowly expanded into a static web site
generator and gained additional features to manage the journal entries,
book reviews, RSS feeds, and software releases.  DocKnot is the latest
incarnation.

In addition to its static web site generator, DocKnot can use one metadata
file as its source information and generate all the various bits of
documentation for a software package.  This allows me to make any changes
in one place and then regenerate the web page, included documentation, and
other files to incorporate those changes.  It also lets me make changes to
the templates to improve shared wording and push that out to every package
I maintain without having to remember track those changes in each package.

DocKnot is also slowly absorbing other tools that I use for software
distribution and web site maintenance, such as generating distribution
tarballs for software packages.

DocKnot was designed and written for my personal needs, and I'm not sure
it will be useful for anyone else.  At the least, the template files are
rather specific to my preferences about how to write package
documentation, and the thread macro language is highly specialized for my
personal web site.  I'm not sure if I'll have the time to make it a more
general tool.  But you're certainly welcome to use it if you find it
useful, send pull requests to make it more general, or take ideas from it
for your own purposes.

## Requirements

Perl 5.24 or later and Module::Build are required to build this module.
The following additional Perl modules are required to use it:

bin/docknot  view on Meta::CPAN

B<docknot> release [B<-a> I<archivedir>] [B<-d> I<distdir>] [B<-m> I<metadata>]

B<docknot> spin [B<-d>] [B<-e> I<pattern> ...] [B<-s> I<url>] I<source>
I<output>

B<docknot> spin-rss [B<-b> I<base>] I<file>

B<docknot> spin-text [B<-lu>] [B<-s> I<url>] [B<-t> I<title>] [I<source>
[I<output>]]

B<docknot> spin-thread [B<-f>] [B<-s> I<url>] [I<source> [I<output>]]

B<docknot> update [B<-m> I<metadata>] [B<-o> I<output>]

B<docknot> update-spin [I<path>]

=head1 DESCRIPTION

B<docknot> is a static web site generator with special support for managing
the documentation and releases of software packages.  Its actions are
organized into subcommands.  The supported subcommands are:

bin/docknot  view on Meta::CPAN

default output files are configured.  This is a quick short-cut to generating
all documentation that's shipped with the package.

=item release

Copy a distribution tarball into a release area, archiving old versions, and
optionally updating configuration for C<spin>.

=item spin

Spin a tree of files written in the macro language thread into an HTML web
site.  See L<App::DocKnot::Spin> for documentation on the input format and
details of the site generation.

=item spin-rss

Process a single F<.rss> file with L<App::DocKnot::Spin::RSS> (normally done
as part of using C<spin> to process a tree of files).  See that module's
documentation for more details.

=item spin-text

Convert a text file to HTML.

=item spin-thread

Like C<spin>, but convert a single file written in thread to HTML.

=item update

Update the DocKnot package configuration from an older format.

=item update-spin

Update an input tree for C<spin> to the latest expectations.  This will, for
example, convert old-style F<*.rpod> pointer files to new-style F<*.spin>
pointer files.

bin/docknot  view on Meta::CPAN

this argument is given, no style sheet will be referred to in the generated
web page.

=item B<-t> I<title>, B<--title>=I<title>

Use I<title> as the page title rather than whatever may be determined from
looking at the input file.

=back

=head2 spin-thread

=over 4

=item B<-s> I<url>, B<--style-url>=I<url>

The base URL for style sheets.  A style sheet specified in a C<\heading>
command will be considered to be relative to this URL and this URL will be
prepended to it.  If this option is not given, the name of the style sheet
will be used verbatim as its URL, except with C<.css> appended.

docs/docknot.yaml  view on Meta::CPAN

    - name: api/app-docknot-spin
      title: App::DocKnot::Spin
    - name: api/app-docknot-spin-pointer
      title: App::DocKnot::Spin::Pointer
    - name: api/app-docknot-spin-rss
      title: App::DocKnot::Spin::RSS
    - name: api/app-docknot-spin-sitemap
      title: App::DocKnot::Spin::Sitemap
    - name: api/app-docknot-spin-text
      title: App::DocKnot::Spin::Text
    - name: api/app-docknot-spin-thread
      title: App::DocKnot::Spin::Thread
    - name: api/app-docknot-spin-versions
      title: App::DocKnot::Spin::Versions
    - name: api/app-docknot-update
      title: App::DocKnot::Update
    - name: api/app-docknot-util
      title: App::DocKnot::Util
  developer:
    - name: todo
      title: To-do list
  user:
    - name: docknot
      title: docknot manual page

blurb: |
  DocKnot is a static web site generator built around a macro language called
  thread, with special support for managing software releases.  In addition to
  building a web site, it can generate distribution tarballs and consistent
  human-readable software package documentation from a YAML metadata file and
  templates.  The goal is to generate both web pages and distributed
  documentation files (such as `README`) from the same source, using templates
  for consistency across multiple packages.

description: |
  In 1999, I wrote a program named `spin` that implemented an idiosyncratic
  macro language called thread.  It slowly expanded into a static web site
  generator and gained additional features to manage the journal entries, book
  reviews, RSS feeds, and software releases.  DocKnot is the latest
  incarnation.

  In addition to its static web site generator, DocKnot can use one metadata
  file as its source information and generate all the various bits of
  documentation for a software package.  This allows me to make any changes in
  one place and then regenerate the web page, included documentation, and
  other files to incorporate those changes.  It also lets me make changes to
  the templates to improve shared wording and push that out to every package I
  maintain without having to remember track those changes in each package.

  DocKnot is also slowly absorbing other tools that I use for software
  distribution and web site maintenance, such as generating distribution
  tarballs for software packages.

  DocKnot was designed and written for my personal needs, and I'm not sure it
  will be useful for anyone else.  At the least, the template files are rather
  specific to my preferences about how to write package documentation, and the
  thread macro language is highly specialized for my personal web site.  I'm
  not sure if I'll have the time to make it a more general tool.  But you're
  certainly welcome to use it if you find it useful, send pull requests to
  make it more general, or take ideas from it for your own purposes.

requirements: |
  Perl 5.24 or later and Module::Build are required to build this module.
  The following additional Perl modules are required to use it:

  * Date::Language (part of TimeDate)
  * Date::Parse (part of TimeDate)

lib/App/DocKnot/Command.pm  view on Meta::CPAN

        maximum => 1,
    },
    'spin-text' => {
        method  => 'spin_text_file',
        module  => 'App::DocKnot::Spin::Text',
        options => [
            'modified|m', 'style|s=s', 'title|t=s', 'use-value|u',
        ],
        maximum => 2,
    },
    'spin-thread' => {
        method  => 'spin_thread_file',
        module  => 'App::DocKnot::Spin::Thread',
        options => ['style-url|s=s'],
        maximum => 2,
    },
    update => {
        method  => 'update',
        module  => 'App::DocKnot::Update',
        options => ['metadata|m=s', 'output|o=s'],
        maximum => 0,
    },

lib/App/DocKnot/Generate.pm  view on Meta::CPAN

            }
        }

        # Rejoin the paragraphs and return the result.
        return join("\n\n", @paragraphs);
    };
    return $to_text;
}

# Returns code that converts metadata text (which is assumed to be in
# Markdown) to thread.  This is not a complete Markdown formatter.  It only
# supports the bits of markup that I've had some reason to use.
#
# This is constructed as a method returning a closure so that its behavior can
# be influenced by App::DocKnot configuration in the future, but it currently
# doesn't use any configuration.
#
# Returns: Code reference to a closure that takes a block of text and returns
#          the converted thread
sub _code_for_to_thread {
    my ($self) = @_;
    my $to_thread = sub {
        my ($text) = @_;

        # Escape all backslashes.
        $text =~ s{ \\ }{\\\\}xmsg;

        # Rewrite triple backticks to \pre blocks and escape backticks inside
        # them so that they're not turned into \code blocks.
        $text =~ s{ ``` \w* (\s .*?) ``` }{
            my $text = $1;
            $text =~ s{ [\`] }{``}xmsg;
            '\pre[' . $1 . ']';
        }xmsge;

        # Rewrite backticks to \code blocks.
        $text =~ s{ ` ([^\`]+) ` }{\\code[$1]}xmsg;

        # Undo backtick escaping.
        $text =~ s{ `` }{\`}xmsg;

        # Rewrite all Markdown links into thread syntax.
        $text =~ s{ \[ ([^\]]+) \] [(] (\S+) [)] }{\\link[$2][$1]}xmsg;

        # Rewrite long bullets.  This is quite tricky since we have to grab
        # every line from the first bulleted one to the point where the
        # indentation stops.
        $text =~ s{
            (                   # capture whole contents
                ^ (\s*)         #   indent before bullet
                [*] (\s+)       #   bullet and following indent
                [^\n]+ \n       #   rest of line

lib/App/DocKnot/Generate.pm  view on Meta::CPAN

        # Rewrite compact bulleted lists.
        $text =~ s{ \n ( (?: \s* [*] \s+ [^\n]+ \s* \n ){2,} ) }{
            my $list = $1;
            $list =~ s{ \n [*] \s+ ([^\n]+) }{\n\\bullet(packed)[$1]}xmsg;
            "\n" . $list;
        }xmsge;

        # Done.  Return the results.
        return $text;
    };
    return $to_thread;
}

##############################################################################
# Helper methods
##############################################################################

# Word-wrap a paragraph of text.  This is a helper function for _wrap, mostly
# so that it can be invoked recursively to wrap bulleted paragraphs.
#
# If the paragraph looks like regular text, which means indented by two or

lib/App/DocKnot/Generate.pm  view on Meta::CPAN

    $options_ref //= {};
    my ($indent) = ($para =~ m{ \A ([ ]*) \S }xms);

    # If the indent is longer than five characters and the ignore indent
    # option is not set, leave it alone.  Allow an indent of five characters
    # since it may be a continuation of a numbered list entry.
    if (length($indent) > 5 && !$options_ref->{ignore_indent}) {
        return $para;
    }

    # If this looks like thread commands or URLs, leave it alone.
    if ($para =~ m{ \A \s* (?: \\ | \[\d+\] ) }xms) {
        return $para;
    }

    # If this starts with a bullet, strip the bullet off, wrap the paragraph,
    # and then add it back in.
    if ($para =~ s{ \A (\s*) [*] (\s+) }{$1 $2}xms) {
        my $offset = length($1);
        $para = $self->_wrap_paragraph($para, { ignore_indent => 1 });
        substr($para, $offset, 1, q{*});

lib/App/DocKnot/Generate.pm  view on Meta::CPAN


    # Create the variable information for the template.  Start with all
    # metadata as loaded above.
    my %vars = %{$data_ref};

    # Add code references for our defined helper functions.
    $vars{center} = $self->_code_for_center;
    $vars{copyright} = $self->_code_for_copyright($data_ref->{copyrights});
    $vars{indent} = $self->_code_for_indent;
    $vars{to_text} = $self->_code_for_to_text;
    $vars{to_thread} = $self->_code_for_to_thread;

    # Run Template Toolkit processing.
    $template = $self->appdata_path('templates', "${template}.tmpl");
    my $tt = Template->new({ ABSOLUTE => 1, ENCODING => 'utf8' })
      or croak(Template->error());
    my $result;
    $tt->process($template, \%vars, \$result) or croak($tt->error);

    # Word-wrap the results to our width and return them.
    return $self->_wrap($result);

lib/App/DocKnot/Generate.pm  view on Meta::CPAN

=head1 NAME

App::DocKnot::Generate - Generate documentation from package metadata

=head1 SYNOPSIS

    use App::DocKnot::Generate;
    my $docknot
      = App::DocKnot::Generate->new({ metadata => 'docs/docknot.yaml' });
    my $readme = $docknot->generate('readme');
    my $index = $docknot->generate('thread');
    $docknot->generate_output('readme');
    $docknot->generate_output('thread', 'www/index.th')

=head1 REQUIREMENTS

Perl 5.24 or later and the modules File::BaseDir, File::ShareDir, Kwalify,
Path::Tiny, Template (part of Template Toolkit), and YAML::XS, all of which
are available from CPAN.

=head1 DESCRIPTION

This component of DocKnot provides a system for generating consistent

lib/App/DocKnot/Spin.pm  view on Meta::CPAN

# Static site builder supporting thread macro language.
#
# This module translates a tree of files, possibly written in thread (a custom
# macro language) into an HTML static site.  It also handles formatting some
# other input types (text and POD, for example), copying other types of files
# to the output tree, and creating site navigation links.
#
# SPDX-License-Identifier: MIT

##############################################################################
# Modules and declarations
##############################################################################

lib/App/DocKnot/Spin.pm  view on Meta::CPAN

    if ($options) {
        if ($options =~ m{ -c ( \s | \z ) }xms) {
            $options{contents} = 1;
        }
        if ($options =~ m{ -t \s+ (?: '(.*)' | ( [^\'] \S+ ) ) }xms) {
            $options{title} = $1 || $2;
        }
    } else {
        $options{navbar} = 1;
    }
    my $podthread = Pod::Thread->new(%options);

    # Grab the thread output.
    my $data;
    $podthread->output_string(\$data);
    $podthread->parse_file("$source");
    $data = decode('utf-8', $data);

    # Spin that thread into HTML.
    my $page = $self->{thread}->spin_thread($data);

    # Push the result through _write_converter_output.
    my $footer = sub {
        my ($blurb) = @_;
        my $link = '<a href="%URL%">spun</a>';
        $self->_footer(
            $source, $output, undef,
            "Last modified and\n    $link %MOD%",
            "Last $link\n    %NOW% from POD modified %MOD%",
        );

lib/App/DocKnot/Spin.pm  view on Meta::CPAN

    my ($self, $input) = @_;

    # Conversion rules for pointers.  The key is the extension, the first
    # value is the name of the command for the purposes of output, and the
    # second is the name of the method to run.
    #<<<
    my %rules = (
        changelog => ['cl2xhtml',   '_cl2xhtml'],
        faq       => ['faq2html',   '_faq2html'],
        log       => ['cvs2xhtml',  '_cvs2xhtml'],
        rpod      => ['pod2thread', '_pod2html'],
    );
    #>>>

    # Figure out what to do with the input.
    if ($input->is_dir()) {
        my $output = $self->_output_for_file($input);
        $self->{generated}{"$output"} = 1;
        if ($output->exists() && !$output->is_dir()) {
            die "cannot replace $output with a directory\n";
        } elsif (!$output->is_dir()) {

lib/App/DocKnot/Spin.pm  view on Meta::CPAN

            my $relative = $input->relative($self->{source});
            my $time = $self->{versions}->latest_release($relative);
            return
              if is_newer($output, $input) && $output->stat()->[9] >= $time;
        } else {
            return if is_newer($output, $input);
        }

        # The output file is not newer.  Respin it.
        $self->_report_action('Spinning', $output);
        $self->{thread}->spin_thread_file($input, $output);
    } else {
        my ($extension) = ($input->basename =~ m{ [.] ([^.]+) \z }xms);
        if (defined($extension) && $rules{$extension}) {
            my ($name, $sub) = $rules{$extension}->@*;
            my $output = $self->_output_for_file($input, q{.} . $extension);
            $self->{generated}{"$output"} = 1;
            my ($source, $options, $style) = $self->_read_pointer($input);
            return if is_newer($output, $input, $source);
            $self->_report_action("Running $name for", $output);
            $self->$sub($source, $output, $options, $style);

lib/App/DocKnot/Spin.pm  view on Meta::CPAN

    # Process all .rss files in the input tree first.  This is done as a
    # separate pass because Path::Iterator::Rule appears to not always re-read
    # the directory when it's modified during the iteration.
    my $rss = App::DocKnot::Spin::RSS->new({ base => $input });
    my $rule = Path::Iterator::Rule->new()->name('.rss');
    my $iter = $rule->iter("$input", { follow_symlinks => 0 });
    while (defined(my $file = $iter->())) {
        $rss->generate(path($file), path($file)->parent);
    }

    # Create a new thread converter object.
    $self->{thread} = App::DocKnot::Spin::Thread->new(
        {
            output      => $output,
            sitemap     => $self->{sitemap},
            source      => $input,
            'style-url' => $self->{style_url},
            versions    => $self->{versions},
        },
    );

    # Create the processor for pointers.
    $self->{pointer} = App::DocKnot::Spin::Pointer->new(
        {
            output      => "$output",
            sitemap     => $self->{sitemap},
            'style-url' => $self->{style_url},
            thread      => $self->{thread},
        },
    );

    # Process the input tree.
    my %options = (follow_symlinks => 0, report_symlinks => 1);
    $rule = Path::Iterator::Rule->new();
    $rule = $rule->skip($rule->new()->name($self->{excludes}->@*));
    $iter = $rule->iter("$input", \%options);
    while (defined(my $file = $iter->())) {
        $self->_process_file(path($file));

lib/App/DocKnot/Spin.pm  view on Meta::CPAN


1;
__END__

=for stopwords
Allbery DocKnot MERCHANTABILITY NONINFRINGEMENT sublicense cvs2xhtml faq2html
cl2xhtml RSS

=head1 NAME

App::DocKnot::Spin - Static site builder supporting thread macro language

=head1 SYNOPSIS

    use App::DocKnot::Spin;

    my $spin = App::DocKnot::Spin->new({ delete => 1 });
    $spin->spin('/path/to/input', '/path/to/output');

=head1 REQUIREMENTS

lib/App/DocKnot/Spin.pm  view on Meta::CPAN

List::SomeUtils, Path::Iterator::Rule, Path::Tiny, Pod::Thread,
Sort::Versions, Template (part of Template Toolkit), and YAML::XS, all of
which are available from CPAN.  Also expects to find B<faq2html>,
B<cvs2xhtml>, and B<cl2xhtml> on the user's PATH to convert certain types of
files.

=head1 DESCRIPTION

App::DocKnot::Spin is a static site builder that takes an input tree of files
and generates an output HTML site.  It is built around the macro language
thread, which is designed for writing simple HTML pages using somewhat nicer
syntax, catering to my personal taste, and supporting variables and macros to
make writing pages less tedious.

Each file in the input tree is examined recursively and either copied verbatim
to the same relative path in the output tree (the default action), used as
instructions to an external program, or converted to HTML.  When converted to
HTML, the output file will be named the same as the input file except the
extension will be replaced with C<.html>.  Missing directories are created.

If the timestamp of the output file is the same as or newer than the timestamp
of the input file, it will be assumed to be up-to-date and will not be
regenerated.  This optimization makes updating an existing static site much
quicker.

Most files in the input tree will normally be thread files ending in C<.th>.
These are processed into HTML using L<App::DocKnot::Spin::Thread>.  See that
module's documentation for the details of the thread macro language.

Files that end in C<.spin> are pointers to external files that should be
converted to HTML and then written to the output tree.  These pointers are
interpreted by L<App::DocKnot::Spin::Pointer>.  See that module's
documentation for their format and effects.

Files that end in various other extensions are taken to be instructions to run
an external converter on a file.  The first line of such a pointer file should
be the path to the source file, the second line any arguments to the
converter, and the third line the style sheet to use if not the default.

lib/App/DocKnot/Spin.pm  view on Meta::CPAN

    .log        cvs log <file> | cvs2xhtml
    .rpod       Pod::Thread

All other files not beginning with a period are copied as-is, except that
files or directories named F<CVS>, F<Makefile>, or F<RCS> are ignored.  As an
exception, F<.htaccess> files are also copied.  This list of exclusions can
be added to with the C<exclude> constructor argument.

If there is a file named F<.sitemap> at the top of the input tree, it will be
parsed with L<App::DocKnot::Spin::Sitemap> and used for inter-page links and
the C<\sitemap> thread command.  See that module's documentation for the
format of this file.

If there is a file named F<.versions> at the top of the input tree, it will be
parsed with L<App::DocKnot::Spin::Versions> and used to determine when to
regenerate certain pages and for the C<\release> and C<\version> thread
commands.  See that module's documentation for the format of this file.

If there is a file named F<.rss> in any directory of the input tree, it will
be processed with L<App::DocKnot::Spin::RSS> to generate RSS and possibly
other files.  This is done before processing the files in that directory so
that the generated files will then be processed as normal.

If there is a directory named F<.git> at the top of the input tree,
App::DocKnot::Spin will assume that the input tree is a Git repository and
will try to use C<git log> to determine the last modification date of files.

lib/App/DocKnot/Spin/Pointer.pm  view on Meta::CPAN

    my %options = (
        contents => $data_ref->{options}{contents},
        style    => $data_ref->{style} // 'pod',
        title    => $data_ref->{title},
    );
    if (exists($data_ref->{options}{navbar})) {
        $options{navbar} = $data_ref->{options}{navbar};
    } else {
        $options{navbar} = 1;
    }
    my $podthread = Pod::Thread->new(%options);

    # Convert the POD to thread.
    my $data;
    $podthread->output_string(\$data);
    $podthread->parse_file("$source");
    $data = decode('utf-8', $data);

    # Spin that page into HTML.
    $self->{thread}->spin_thread_output($data, $source, 'POD', $output);
    return;
}

# Convert a text file to HTML.
#
# $data_ref - Data form the pointer file
#   options - Hash of conversion options
#     modified - Whether to add a last modified subheader
#   path    - Path to the text file to convert
#   style   - Style sheet to use

lib/App/DocKnot/Spin/Pointer.pm  view on Meta::CPAN

# Public interface
##############################################################################

# Create a new HTML converter for pointers.  This object can (and should) be
# reused for all pointer conversions done while spinning a tree of files.
#
# $args - Anonymous hash of arguments with the following keys:
#   output    - Root of the output tree
#   sitemap   - App::DocKnot::Spin::Sitemap object
#   style-url - Partial URL to style sheets
#   thread    - App::DocKnot::Spin::Thread object
#
# Returns: Newly created object
#  Throws: Text exception on failure to initialize Template Toolkit
sub new {
    my ($class, $args_ref) = @_;

    # Get the configured path to pandoc, if any.
    my $config_reader = App::DocKnot::Config->new();
    my $global_config_ref = $config_reader->global_config();
    my $pandoc = $global_config_ref->{pandoc} // 'pandoc';

lib/App/DocKnot/Spin/Pointer.pm  view on Meta::CPAN


    # Create and return the object.
    my $tt = Template->new({ ABSOLUTE => 1, ENCODING => 'utf8' })
      or croak(Template->error());
    my $self = {
        output      => $args_ref->{output},
        pandoc_path => $pandoc,
        sitemap     => $args_ref->{sitemap},
        style_url   => $style_url,
        template    => $tt,
        thread      => $args_ref->{thread},
    };
    bless($self, $class);
    $self->{template_path} = $self->appdata_path('templates', 'html.tmpl');
    return $self;
}

# Check if the result of a pointer file needs to be regenerated.
#
# $pointer - Path to pointer file
# $output  - Path to corresponding output file

lib/App/DocKnot/Spin/Pointer.pm  view on Meta::CPAN

An App::DocKnot::Spin::Sitemap object.  This will be used to create inter-page
links.  For inter-page links, the C<output> argument must also be provided.

=item style-url

The base URL for style sheets.  A style sheet specified in a pointer file will
be considered to be relative to this URL and this URL will be prepended to it.
If this option is not given, the name of the style sheet will be used verbatim
as its URL, except with C<.css> appended.

=item thread

An App::DocKnot::Spin::Thread object, used for converting POD into HTML.  It
should be configured with the same App::DocKnot::Spin::Sitemap object as the
C<sitemap> argument.

=back

=back

=head1 INSTANCE METHODS

lib/App/DocKnot/Spin/RSS.pm  view on Meta::CPAN

# Generate RSS and thread from a feed description file.
#
# This module generates RSS feeds and thread indexes of newly-published pages
# or change notes for a web site maintained with App::DocKnot::Spin.
#
# SPDX-License-Identifier: MIT

##############################################################################
# Modules and declarations
##############################################################################

package App::DocKnot::Spin::RSS v8.0.1;

lib/App/DocKnot/Spin/RSS.pm  view on Meta::CPAN

}

# Spin a file into HTML.
#
# $file - Path::Tiny path to the file
#
# Returns: Rendered HTML as a list with one element per line
sub _spin_file {
    my ($self, $file) = @_;
    my $source = $file->slurp_utf8();
    my $page = $self->{spin}->spin_thread($source, $file);
    return map { "$_\n" } split(m{ \n }xms, $page);
}

# Report an action to standard output.
#
# $action - String description of the action
# $output - Output file generated
# $base   - Base path for all output
sub _report_action {
    my ($self, $action, $output) = @_;

lib/App/DocKnot/Spin/RSS.pm  view on Meta::CPAN


    # Write the result to the output file.
    $file->spew_utf8($result);
    return;
}

##############################################################################
# Thread output
##############################################################################

# Print out the thread version of the recent changes list.
#
# $file         - Path::Tiny output path
# $metadata_ref - RSS feed metadata
# $entries_ref  - Entries
sub _thread_output {
    my ($self, $file, $metadata_ref, $entries_ref) = @_;

    # The entries are in a flat list, but we want a two-level list of entries
    # by month so that the template can add appropriate month headings.
    # Restructure the entry list accordingly.
    my (@entries_by_month, $last_month);
    for my $entry_ref ($entries_ref->@*) {
        my $month = strftime('%B %Y', localtime($entry_ref->{date}));
        my $date = strftime('%Y-%m-%d', localtime($entry_ref->{date}));

lib/App/DocKnot/Spin/RSS.pm  view on Meta::CPAN

            my $month_ref = { heading => $month, entries => [$formatted_ref] };
            push(@entries_by_month, $month_ref);
            $last_month = $month;
        } else {
            push($entries_by_month[-1]{entries}->@*, $formatted_ref);
        }
    }

    # Generate the RSS output using the template.
    my %vars = (
        prefix  => $metadata_ref->{'thread-prefix'},
        entries => \@entries_by_month,
    );
    my $result;
    $self->{template}->process($self->{templates}{changes}, \%vars, \$result)
      or croak($self->{template}->error());

    # Write the result to the output file.
    $file->spew_utf8($result);
    return;
}

##############################################################################
# Index output
##############################################################################

# Translate the thread of a journal entry for inclusion in an index page.
#
# $file - Path::Tiny to the journal entry
#
# Returns: Thread to include in the index page
sub _index_journal {
    my ($self, $file, $url) = @_;
    my $fh = $file->openr_utf8();

    # Skip to the first \h1 and exclude it.
    while (defined(my $line = <$fh>)) {

lib/App/DocKnot/Spin/RSS.pm  view on Meta::CPAN

    while (defined(my $line = <$fh>)) {
        last if $line =~ m{ \A \\date }xms;
        $text .= $line;
    }

    # All done.
    close($fh);
    return $text;
}

# Translate the thread of a book review for inclusion into an index page.
#
# $file - Path::Tiny to the book review
#
# Returns: Thread to include in the index page
sub _index_review {
    my ($self, $file) = @_;
    my $title;
    my $author;

    # Regex to match a single "character" in a macro argument.

lib/App/DocKnot/Spin/RSS.pm  view on Meta::CPAN

            $text = $self->_index_journal($path);
        } elsif ($entry_ref->{review}) {
            my $path = path($entry_ref->{review})->absolute($base);
            $text = $self->_index_review($path);
        } else {
            die "unknown entry type\n";
        }

        # Make all the URLs absolute and then convert images back to relative
        # based on the URL of the file we're creating.  This handles
        # correcting links from thread from elsewhere in the tree.
        $text =~ s{
            ( \\ (?: link | image ) \s* \[ ) ( [^\]]+ ) \]
        }{ $1 . _absolute_url($2, $entry_ref->{link}) . ']' }xmsge;
        $text =~ s{
            ( \\ image \s* \[ ) ( [^\]]+ ) \]
        }{$1 . _relative_url($2, $metadata_ref->{'index-base'}) . ']' }xmsge;

        # Add the entry to the list.
        my $formatted_ref = {
            date  => $date,

lib/App/DocKnot/Spin/RSS.pm  view on Meta::CPAN

        my @entries;
        if ($tags eq q{*}) {
            @entries = $changes_ref->@*;
        } else {
            my @tags = split(m{ , }xms, $tags);
            @entries
              = grep { _intersect($_->{tags}, \@tags) } $changes_ref->@*;
        }

        # Write the output.
        if ($format eq 'thread') {
            $self->_report_action('Generating thread file', $file);
            $self->_thread_output($file, $metadata_ref, \@entries);
        } elsif ($format eq 'rss') {
            if (scalar(@entries) > $metadata_ref->{recent}) {
                splice(@entries, $metadata_ref->{recent});
            }
            $self->_report_action('Generating RSS file', $file);
            $self->_rss_output($file, $base, $metadata_ref, \@entries);
        } elsif ($format eq 'index') {
            if (scalar(@entries) > $metadata_ref->{recent}) {
                splice(@entries, $metadata_ref->{recent});
            }

lib/App/DocKnot/Spin/RSS.pm  view on Meta::CPAN


1;
__END__

=for stopwords
Allbery DocKnot MERCHANTABILITY NONINFRINGEMENT RSS TimeDate YYYY-MM-DD
sublicense hoc rss

=head1 NAME

App::DocKnot::Spin::RSS - Generate RSS and thread from a feed description file

=head1 SYNOPSIS

    use App::DocKnot::Spin::RSS;

    my $rss = App::DocKnot::Spin::RSS->new({ base => 'path/to/tree' });
    $rss->generate('path/to/tree/.rss');

=head1 REQUIREMENTS

Perl 5.24 or later and the modules Date::Language, Date::Parse (both part of
the TimeDate distribution), List::SomeUtils, Path::Tiny, and Perl6::Slurp,
both of which are available from CPAN.

=head1 DESCRIPTION

App::DocKnot::Spin::RSS reads as input a feed description file consisting of
simple key/value pairs and writes out either thread (for input to
App::DocKnot::Spin::Thread) or RSS.  The feed description consists of a
leading block of metadata and then one block per entry in the feed.  Each
block can either include the content of the entry or can reference an external
thread file, in several formats, for the content.  The feed description file
defines one or more output files in the Output field of the metadata.

Output files are only regenerated if they are older than the input feed
description file.

App::DocKnot::Spin::RSS is designed for use with App::DocKnot::Spin.  It
relies on App::DocKnot::Spin::Thread to convert thread to HTML, both for
inclusion in RSS feeds and for post-processing of generated thread files.
App::DocKnot::Spin::RSS is invoked automatically by App::DocKnot::Spin when it
encounters an F<.rss> file in a directory it is processing.

See L<INPUT LANGUAGE> for the details of the language in which F<.rss> files
are written.

=head1 CLASS METHODS

=over 4

lib/App/DocKnot/Spin/RSS.pm  view on Meta::CPAN


=item Description

The description of the feed, used only in the RSS output.  This should always
be set if there are any RSS output files.

=item Index-Base

The base URL for output files of type C<index>.  This is used to canonicalize
relative URLs and should be the URL to the directory containing the HTML file
that will result from processing the thread output.  This should be set if
there are any output files of type C<index>; if it isn't set, relative links
may be rewritten incorrectly.

=item Index-Prefix

When generating output files of type C<index>, use the value as the initial
content of the generated thread.  This field should almost always be set if
any output files of type C<index> are defined.  It will contain such things as
the C<\heading> command, any prologue material, initial headings, and so
forth.

=item Index-Suffix

When generating output files of type C<index>, append the value to the end of
the generated thread.  The C<\signature> command is always appended and should
not be included here.  Set this field only if there is other thread that needs
to be appended (such as closing brackets for C<\div> commands).

=item Language

The language of the feed, used only in the RSS output.  This should always be
set if there are any RSS output files.  Use C<en-us> for US English.

=item Output

Specifies the output files for this input file in the form of a

lib/App/DocKnot/Spin/RSS.pm  view on Meta::CPAN

the output file (always a relative path), I<type> is the type of output, and
I<tags> indicates which entries to include in this file.  I<tags> is a
comma-separated list of tags or the special value C<*>, indicating all tags.

There are three types of output:

=over 4

=item index

Output thread containing all recent entries.  This output file honors the
Recent field similar to RSS output and is used to generate something akin to a
journal or blog front page: an HTML version of all recent entries.  It only
supports external entries (entries with C<Journal> or C<Review> fields).  The
C<Index-Base> and C<Index-Prefix> (and possibly C<Index-Suffix>) fields should
be set.

For output for entries with simple descriptions included in the input file,
see the C<thread> output type.

=item rss

Output an RSS file.  App::DocKnot::Spin::RSS only understands the RSS 2.0
output format.  The C<Description>, C<Language>, C<RSS-Base>, and C<Title>
fields should be set to provide additional metadata for the output file.

=item thread

Output thread containing all entries in this input file.  This should only be
used for input files where all entries have their description text inline in
the input file.  Every entry will be included.  The output will be divided
into sections by month, and each entry will be in a description list, with the
title prefixed by the date.  The C<Thread-Prefix> field should be set.

For output that can handle entries from external files, see the C<index>
output type.

=back

=item Recent

Sets the number of recent entries to include in output files of type C<rss> or
C<index> (but not C<thread>, which will include the full contents of the
file).  If this field is not present, the default is 15.

=item RSS-Base

The base URL for RSS files generated by this file.  Each generated RSS file
should have a link back to itself, and that link will be formed by taking the
output name and prepending this field.

=item Thread-Prefix

When generating thread output from this file, use the value as the initial
content of the generated thread.  This field should almost always be set if
any output files of type C<thread> are defined.  It will contain such things
as the C<\heading> command, any prologue material, initial headings, and so
forth.

=item Title

The title of the feed, used only in the RSS output.  This should always be set
if there are any RSS output files.

=back

lib/App/DocKnot/Spin/RSS.pm  view on Meta::CPAN


=item Date

The date of this entry in ISO date format (YYYY-MM-DD HH:MM).  This field is
required.

=item Description

The inline contents of this entry.  One and only one of this field,
C<Journal>, or C<Review> should be present.  C<Description> fields can only be
used with output types of C<rss> or C<thread>.

=item Journal

Specifies that the content of this entry should be read from an external
thread file given by the value of this field.  The contents of that file are
expected to be in the thread format used by my journal entries:  specifically,
everything is ignored up to the first C<\h1> and after the C<\date> macro, and
the C<\date> line is stripped off (the date information from the C<Date> field
is used instead).

One and only one of this field, C<Description>, or C<Review> should be
present.

=item Link

The link to the page referenced by this entry.  The link is relative to the
C<Base> field set in the input file metadata.  This field is required.

=item Review

Specifies that the content of this entry should be read from an external
thread file given by the value of this field.  The contents of that file are
expected to be in the thread format used by my book reviews.

Many transformations are applied to entries of this sort based on the format
used by my book reviews and the URL layout they use, none of which is
documented at present.  For the time being, see the source code for what
transformations are done.  This support will require modification for use by
anyone else.

One and only one of this field, C<Description>, or C<Review> should be
present.

lib/App/DocKnot/Spin/Sitemap.pm  view on Meta::CPAN

which are available from CPAN.

=head1 DESCRIPTION

App::DocKnot::Spin supports sitemap information stored in a C<.sitemap> file
at the top of the source directory.  If this is present, it is used to add
navigation information to every generated page.

App::DocKnot::Spin::Sitemap encapsulates parsing of that file and generating
the HTML for inter-page links.  It can also generate HTML for the entirety of
the sitemap to support the C<\sitemap> thread command.

The format of this file is one line per web page, with indentation showing the
tree structure.  Each line should be formatted as a partial URL (relative to
the top of the site) starting with C</>, a colon, and a page description.  The
partial URL should be for the generated pages, not the source files (so, for
example, should use an C<.html> extension).  The top of the generated site
should have the URL of C</> at the top of the sitemap.

If two pages at the same level aren't related and shouldn't have next and
previous links to each other, they should be separated by three dashes on a

lib/App/DocKnot/Spin/Sitemap.pm  view on Meta::CPAN

=item navbar(PAGE)

Generate the navigation bar for the provided PAGE, which should be a URL
relative to the top of the generated site and starting with C</>.  The return
value is a list of HTML lines suitable for injecting into the output page.

=item sitemap()

Return the sitemap as a list of lines of formatted HTML, suitable for
inclusion in a generated web page.  This is used to implement the C<\sitemap>
thread command.

=back

=head1 AUTHOR

Russ Allbery <rra@cpan.org>

=head1 COPYRIGHT AND LICENSE

Copyright 1999-2000, 2002-2004, 2008, 2021-2022 Russ Allbery <rra@cpan.org>

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;

lib/App/DocKnot/Spin/Thread.pm  view on Meta::CPAN

    # as-is.  (We will have already warned about this when defining the
    # macro.)
    my $expand = sub {
        my ($n) = @_;
        return ($n > scalar(@args)) ? "\\\\$n" : $args[$n - 1];
    };

    # Replace the substitution markers in the macro definition.
    $definition =~ s{ \\(\d+) }{ $expand->($1) }xmsge;

    # Now parse the result as if it were input thread and return the results.
    return $self->_parse_context($definition, $block);
}

# Expand a given command into its representation.  This function is mutually
# recursive with _parse_context and _macro.
#
# $command - Name of the command
# $text    - Input text following the command
# $block   - True if currently in block context (if so, and if the command
#            doesn't generate its own container, it will need to be wrapped

lib/App/DocKnot/Spin/Thread.pm  view on Meta::CPAN

# $text  - Input text to parse
# $block - True if the parse is done in a block context
#
# Returns: HTML output corresponding to $text
sub _parse {
    my ($self, $text, $block) = @_;
    my ($output) = $self->_parse_context($text, $block);
    return $output;
}

# The top-level function for parsing a thread document.  Be aware that the
# working directory from which this function is run matters a great deal,
# since thread may contain relative paths to files that the spinning process
# needs to access.
#
# $thread     - Thread to spin
# $in_path    - Input file path as a Path::Tiny object, or undef
# $out_fh     - Output file handle to which to write the HTML
# $out_path   - Output file path as a Path::Tiny object, or undef
# $input_type - Optional one-word description of input type
sub _parse_document {
    my ($self, $thread, $in_path, $out_fh, $out_path, $input_type) = @_;

    # Parse the thread into paragraphs and reverse them to form a stack.
    my @input = reverse($self->_split_paragraphs($thread));

    # Initialize object state for a new document.
    #<<<
    $self->{input}      = [[\@input, $in_path, 1]];
    $self->{input_type} = $input_type // 'thread';
    $self->{macro}      = {};
    $self->{out_fh}     = $out_fh;
    $self->{out_path}   = $out_path;
    $self->{rss}        = [];
    $self->{space}      = q{};
    $self->{state}      = ['BLOCK'];
    $self->{variable}   = {};
    #>>>

    # Parse the thread file a paragraph at a time.  _split_paragraphs takes
    # care of ensuring that each paragraph contains the complete value of a
    # command argument.
    #
    # The stack of parsed input is maintained in $self->{input} and the file
    # being parsed at any given point is $self->{input}[-1].  _cmd_include
    # will push new file information into this stack, and we pop off the top
    # element of the stack when we exhaust its paragraphs.
    while ($self->{input}->@*) {
        while (defined(my $para = pop($self->{input}[-1][0]->@*))) {
            my $result = $self->_parse(_escape($para), 1);

lib/App/DocKnot/Spin/Thread.pm  view on Meta::CPAN

    $output .= $self->_format_attr($format) . ' />';
    return (1, $output);
}

# Include a file.  Note that this includes a file after the current paragraph,
# not immediately, which may be a bit surprising.
sub _cmd_include {
    my ($self, $file) = @_;
    $file = $self->_file_path($self->_parse($file));

    # Read the thread, split it on paragraphs, and reverse it to make a stack.
    my $thread = $self->_read_file($file);
    my @paragraphs = reverse($self->_split_paragraphs($thread));

    # Add it to the file stack.
    push($self->{input}->@*, [\@paragraphs, $file, 1]);

    # Expand into empty output.
    return (1, q{});
}

# A link to a URL or partial URL.
#

lib/App/DocKnot/Spin/Thread.pm  view on Meta::CPAN

        $self->_warning(qq(no version known for "$package"));
        return (0, q{});
    }
    return (0, $version);
}

##############################################################################
# Public interface
##############################################################################

# Create a new thread to HTML converter.  This object can (and should) be
# reused for all thread conversions done while spinning a tree of files.
#
# $args - Anonymous hash of arguments with the following keys:
#   output    - Root of the output tree
#   sitemap   - App::DocKnot::Spin::Sitemap object
#   source    - Root of the source tree
#   style-url - Partial URL to style sheets
#   versions  - App::DocKnot::Spin::Versions object
#
# Returns: Newly created object
sub new {

lib/App/DocKnot/Spin/Thread.pm  view on Meta::CPAN

        repository => $repository,
        sitemap    => $args_ref->{sitemap},
        source     => $source,
        style_url  => $style_url,
        versions   => $args_ref->{versions},
    };
    bless($self, $class);
    return $self;
}

# Convert thread to HTML and return the output as a string.  The working
# directory still matters for file references in the thread.
#
# $thread - Thread to spin
# $input  - Optional input file path (for relative path and timestamps)
#
# Returns: Resulting HTML
sub spin_thread {
    my ($self, $thread, $input) = @_;
    my $result;
    open(my $out_fh, '>:raw:encoding(utf-8)', \$result);
    $self->_parse_document($thread, $input, $out_fh, undef);
    close($out_fh);
    return decode('utf-8', $result);
}

# Spin a single file of thread to HTML.
#
# $input  - Input file (if not given, assumes standard input)
# $output - Output file (if not given, assumes standard output)
#
# Raises: Text exception on processing error
sub spin_thread_file {
    my ($self, $input, $output) = @_;
    my $out_fh;
    my $thread;

    # Read the input file.
    if (defined($input)) {
        $input = path($input)->realpath();
        $thread = $input->slurp_utf8();
    } else {
        $thread = slurp(\*STDIN);
    }

    # Open the output file.
    if (defined($output)) {
        $output = path($output)->absolute();
        $out_fh = $output->openw_utf8();
    } else {
        open($out_fh, '>&:raw:encoding(utf-8)', 'STDOUT');
    }

    # Do the work.
    $self->_parse_document($thread, $input, $out_fh, $output);

    # Clean up.
    close($out_fh);
    return;
}

# Convert thread to HTML and write it to the given output file.  This is used
# when the thread isn't part of the input tree but instead is intermediate
# output from some other conversion process.
#
# $thread     - Thread to spin
# $input      - Original input file path (for relative path and timestamps)
# $input_type - One-word description of input type for the page footer
# $output     - Output file
#
# Returns: Resulting HTML
sub spin_thread_output {
    my ($self, $thread, $input, $input_type, $output) = @_;
    $input = path($input);

    # Open the output file.
    my $out_fh;
    if (defined($output)) {
        $output = path($output)->absolute();
        $out_fh = $output->openw_utf8();
    } else {
        open($out_fh, '>&:raw:encoding(utf-8)', 'STDOUT');
    }

    # Do the work.
    $self->_parse_document($thread, $input, $out_fh, $output, $input_type);

    # Clean up and restore the working directory.
    close($out_fh);
    return;
}

##############################################################################
# Module return value and documentation
##############################################################################

1;
__END__

=for stopwords
Allbery DocKnot MERCHANTABILITY NONINFRINGEMENT sublicense NARGS RCS RSS
preformatted respun

=head1 NAME

App::DocKnot::Spin::Thread - Generate HTML from the macro language thread

=head1 SYNOPSIS

    use App::DocKnot::Spin::Thread;

    my $input  = 'some thread';
    my $thread = App::DocKnot::Spin::Thread->new();
    my $output = $thread->spin_thread($input);

    use App::DocKnot::Spin::Sitemap;
    use App::DocKnot::Spin::Versions;

    my $sitemap  = App::DocKnot::Spin::Sitemap->new('/input/.sitemap');
    my $versions = App::DocKnot::Spin::Versions->new('/input/.versions');
    $thread = App::DocKnot::Spin::Thread->new({
        source   => '/input',
        output   => '/output',
        sitemap  => $sitemap,
        versions => $versions,
    });
    $thread->spin_thread_file('/input/file.th', '/output/file.html');
    $thread->spin_thread_output(
        $input, '/path/to/file.pod', 'POD', '/output/file.html'
    );

=head1 REQUIREMENTS

Perl 5.24 or later and the modules Git::Repository, Image::Size,
List::SomeUtils, and Path::Tiny, all of which are available from CPAN.

=head1 DESCRIPTION

This component of DocKnot implements the macro language thread, which is
designed for writing simple HTML pages using somewhat nicer syntax, catering
to my personal taste, and supporting variables and macros to make writing
pages less tedious.

For the details of the thread language, see L<THREAD LANGUAGE> below.

=head1 CLASS METHODS

=over 4

=item new(ARGS)

Create a new App::DocKnot::Spin::Thread object.  A single converter object
can be used repeatedly to convert a tree of files, or can convert a single
file.  ARGS should be a hash reference with one or more of the following

lib/App/DocKnot/Spin/Thread.pm  view on Meta::CPAN

data for the C<\release> and C<\version> commands.

=back

=back

=head1 INSTANCE METHODS

=over 4

=item spin_thread(THREAD[, INPUT])

Convert the given thread to HTML, returning the result.  When run via this
API, App::DocKnot::Spin::Thread will not be able to obtain sitemap information
even if a sitemap was provided and therefore will not add inter-page links.
INPUT, if given, is the full path to the original source file, used for
relative paths and modification time information.

=item spin_thread_file([INPUT[, OUTPUT]])

Convert a single thread file to HTML.  INPUT is the path of the thread file
and OUTPUT is the path of the output file.  OUTPUT or both INPUT and OUTPUT
may be omitted, in which case standard input or standard output, respectively,
will be used.

If OUTPUT is omitted, App::DocKnot::Spin::Thread will not be able to obtain
sitemap information even if a sitemap was provided and therefore will not add
inter-page links.

=item spin_thread_output(THREAD, INPUT, TYPE[, OUTPUT])

Convert the given thread to HTML, writing the result to OUTPUT.  If OUTPUT is
not given, write the results to standard output.  This is like spin_thread()
but does use sitemap information and adds inter-page links.  It should be used
when the thread input is the result of an intermediate conversion step of a
known input file.  INPUT should be the full path to the original source file,
used for relative paths and modification time information.  TYPE should be set
to a one-word description of the format of the input file and is used for the
page footer.

=back

=head1 THREAD LANGUAGE

=head2 Basic Syntax

A thread file is Unicode text with a blank line between paragraphs.

There is no need to explicitly mark paragraphs; paragraph boundaries will be
inferred from the blank line between them and the appropriate C<< <p> >> tags
will be added to the HTML output.

There is no need to escape any character except C<\> (which should be written
as C<\\>) and an unbalanced C<[> or C<]> (which should be written as
C<\entity[91]> or C<\entity[93]> respectively).  Escaping C<[> or C<]> is not
necessary if the brackets are balanced within the paragraph, and therefore is
only rarely needed.

lib/App/DocKnot/Spin/Thread.pm  view on Meta::CPAN

appending navigation links, closing any open blocks, and any other cleanup
that has to happen at the end of a generated HTML page.

You can include other files with the C<\include> command, although it has a
few restrictions.  The C<\include> command must appear either at the beginning
of the file or after a blank line, and should be followed by a blank line.  Be
careful not to include the same file recursively as there is no current
protection against infinite loops.

Thread files will not be automatically respun when included files change, so
you will need touch the thread file to force the corresponding output file to
be regenerated.

All further thread commands are divided into block commands and inline
commands.  These roughly correspond to HTML 5's "flow content" and "phrasing
content" respectively.

=head2 Block Commands

Block commands are commands that should occur in a paragraph by themselves,
not contained in a paragraph with other text.  They indicate high-level
structural elements of the page.  C<\heading> and C<\include> were already
discussed above, but here is a complete list.  Any argument of TEXT can be
multiple paragraphs and contain other embedded block commands (so you can nest

lib/App/DocKnot/Spin/Thread.pm  view on Meta::CPAN


=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

lib/App/DocKnot/Spin/Thread.pm  view on Meta::CPAN


    \==[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

lib/App/DocKnot/Spin/Versions.pm  view on Meta::CPAN

The date of the latest release of that package in YYYY-MM-DD format in the
local time zone.

=item <time>

The time of the latest release of that package in HH:MM:SS format in the local
time zone.

=item <files>

Any number of thread input files affected by this release, separated by
spaces.  The file names should be relative to the top of the source tree for
the web site.

=back

The <files> field can be continued on the following line by starting the line
with whitespace.  Each whitespace-separated word in a continuation line is
taken as an additional affected file for the previous line.

This information is used for the C<\version> and C<\release> thread commands
and to force regeneration of files affected by a release with a timestamp
newer than the timestamp of the corresponding output file.

=head1 CLASS METHODS

=over 4

=item new(PATH)

Create a new App::DocKnot::Spin::Versions object for the F<.versions> file

share/templates/thread.tmpl  view on Meta::CPAN

         [Debian package tracker] \break[% END %]
]
[% IF unmaintained %]
\h2[Warning]

\class(alert)[This package is not maintained.]
[% unmaintained %]
[% END %]
\h2[Blurb]

[% to_thread(blurb) %]

\h2[Description]

[% to_thread(description) %]

\h2[Requirements]

[% to_thread(requirements) %]
[% IF build.autotools %]
To bootstrap from a Git checkout, or if you change the Automake files and
need to regenerate Makefile.in, you will need Automake
[% build.automake %] or later.  For bootstrap or if you change
configure.ac or any of the m4 files it includes and need to regenerate
configure or config.h.in, you will need Autoconf [% build.autoconf %] or
later.[% IF build.manpages %]  Perl is also required to generate manual
pages from a fresh Git checkout.[% END %][% IF build.bootstrap %]
[% to_thread(build.bootstrap) %][% END %]
[% END %]
\h2[Download]

The distribution:

\table[][
    \program[[% name %]][[% distribution.version %]]
        [[% distribution.section %]/[% distribution.tarname %]-\version[[% distribution.version %]]]
]

An \link[https://archives.eyrie.org/software/ARCHIVE/[% distribution.tarname %]/]
[archive of older releases] is also available.[% IF advisories %]
\class(alert)[Versions older than [% advisories.0.threshold %] have known
security vulnerabilities and should not be used.][% END %]
[% IF distribution.packaging.debian.summary %]
[% IF distribution.packaging.debian.personal %][% to_thread(distribution.packaging.debian.summary) %]
[% ELSE %][% to_thread(distribution.packaging.debian.summary) | trim %][% IF distribution.packaging.debian.summary.match('\n\n') %]

[% ELSE %]  [% END %]See the \link[https://tracker.debian.org/pkg/[%
distribution.packaging.debian.package %]][Debian package tracker] for more
information.

[% END %][% ELSIF distribution.packaging.debian.personal %]
A Debian package [% IF distribution.packaging.debian.package %]([% distribution.packaging.debian.package %]) [% END %]is available from my \link[../debian.html][personal
repository].
[% END %][% IF distribution.cpan %]
[% name %] is available from CPAN as the
\link[https://metacpan.org/release/[% distribution.cpan %]]
[[% distribution.cpan %] distribution].
[% END %]
[% IF distribution.packaging.extra %]
[% to_thread(distribution.packaging.extra) %]
[% END %][% name %] [% IF unmaintained %]was[% ELSE %]is[% END %]
maintained using the [% vcs.type %] version control system.  To check out
the current development tree, [% IF vcs.github %]see
\link[https://github.com/[% vcs.github %]][GitHub] or [% END %]clone:

\pre[    [% vcs.url %]]

[% IF vcs.github && !unmaintained %]Pull requests on GitHub are welcome.
[% END %]You can also \link[[% vcs.browse %]][browse the current
development source].

share/templates/thread.tmpl  view on Meta::CPAN

[% END %]
The [% name %] package as a whole is covered by the following copyright
and license:

\block[
[% FOREACH copr IN copyrights %]
    Copyright [% copr.years %]
        [% copr.holder %]
[% END %]

[% indent(to_thread(license.text), 4) %]
[% IF license.notices %]
[% indent(to_thread(license.notices), 4) %]
[% END %]
]

Some individual source files are covered by other, compatible licenses.
For complete copyright and license information, see the file
\link[license.html][LICENSE] in the [% name %] source distribution.

\signature

t/cli/spin.t  view on Meta::CPAN

isa_ok($docknot, 'App::DocKnot::Command');

# Create a temporary directory for test output.
my $tempdir = Path::Tiny->tempdir();

# Spin a single file.
my $datadir = path('t', 'data', 'spin');
my $input = $datadir->child('input', 'index.th');
my $expected = $datadir->child('output', 'index.html');
my $output = $tempdir->child('index.html');
$docknot->run('spin-thread', '-s', '/~eagle/styles', "$input", "$output");
is_spin_output($output, $expected, 'spin-thread (output specified)');

# Spin a single file to standard output.
my $stdout = capture_stdout {
    $docknot->run('spin-thread', '-s', '/~eagle/styles', "$input");
};
$output->spew($stdout);
is_spin_output($output, $expected, 'spin-thread (standard output)');

# Copy the input tree to a new temporary directory since .rss files generate
# additional thread files.
my $indir = Path::Tiny->tempdir();
$input = $datadir->child('input');
dircopy($input, $indir)
  or die "Cannot copy $input to $indir: $!\n";
fix_pointers($indir, $input);

# Spin a tree of files.
$expected = $datadir->child('output');
capture_stdout {
    $docknot->run('spin', '-s', '/~eagle/styles', "$indir", "$tempdir");
};
my $count = is_spin_output_tree($tempdir, $expected, 'spin');

# Spin a file with warnings.  The specific warnings are checked in
# t/spin/errors.t; here, we just check the rewrite of the warning.
my $errors = $datadir->child('errors', 'errors.th')->realpath();
my $stderr;
($stdout, $stderr) = capture {
    $docknot->run('spin-thread', "$errors");
};
like(
    $stderr, qr{ \A \Q$0\E [ ] spin-thread : \Q$errors\E : 1 : }xms,
    'warnings are properly rewritten',
);

# Report the end of testing.
done_testing($count + 6);

t/data/generate/docknot/output/thread  view on Meta::CPAN

         [Git repository] \break
    \link[https://metacpan.org/release/App-DocKnot]
         [MetaCPAN] \break
    \link[https://tracker.debian.org/pkg/docknot]
         [Debian package tracker] \break
]

\h2[Blurb]

DocKnot is a static web site generator built around a macro language
called thread, with special support for managing software releases.  In
addition to building a web site, it can generate distribution tarballs and
consistent human-readable software package documentation from a YAML
metadata file and templates.  The goal is to generate both web pages and
distributed documentation files (such as \code[README]) from the same
source, using templates for consistency across multiple packages.

\h2[Description]

In 1999, I wrote a program named \code[spin] that implemented an
idiosyncratic macro language called thread.  It slowly expanded into a
static web site generator and gained additional features to manage the
journal entries, book reviews, RSS feeds, and software releases.  DocKnot
is the latest incarnation.

In addition to its static web site generator, DocKnot can use one metadata
file as its source information and generate all the various bits of
documentation for a software package.  This allows me to make any changes
in one place and then regenerate the web page, included documentation, and
other files to incorporate those changes.  It also lets me make changes to
the templates to improve shared wording and push that out to every package
I maintain without having to remember track those changes in each package.

DocKnot is also slowly absorbing other tools that I use for software
distribution and web site maintenance, such as generating distribution
tarballs for software packages.

DocKnot was designed and written for my personal needs, and I'm not sure
it will be useful for anyone else.  At the least, the template files are
rather specific to my preferences about how to write package
documentation, and the thread macro language is highly specialized for my
personal web site.  I'm not sure if I'll have the time to make it a more
general tool.  But you're certainly welcome to use it if you find it
useful, send pull requests to make it more general, or take ideas from it
for your own purposes.

\h2[Requirements]

Perl 5.24 or later and Module::Build are required to build this module.
The following additional Perl modules are required to use it:

t/data/generate/docknot/output/thread  view on Meta::CPAN

    \doc[api/app-docknot-command.html][App::DocKnot::Command]
    \doc[api/app-docknot-config.html][App::DocKnot::Config]
    \doc[api/app-docknot-dist.html][App::DocKnot::Dist]
    \doc[api/app-docknot-generate.html][App::DocKnot::Generate]
    \doc[api/app-docknot-release.html][App::DocKnot::Release]
    \doc[api/app-docknot-spin.html][App::DocKnot::Spin]
    \doc[api/app-docknot-spin-pointer.html][App::DocKnot::Spin::Pointer]
    \doc[api/app-docknot-spin-rss.html][App::DocKnot::Spin::RSS]
    \doc[api/app-docknot-spin-sitemap.html][App::DocKnot::Spin::Sitemap]
    \doc[api/app-docknot-spin-text.html][App::DocKnot::Spin::Text]
    \doc[api/app-docknot-spin-thread.html][App::DocKnot::Spin::Thread]
    \doc[api/app-docknot-spin-versions.html][App::DocKnot::Spin::Versions]
    \doc[api/app-docknot-update.html][App::DocKnot::Update]
    \doc[api/app-docknot-util.html][App::DocKnot::Util]
]

\h2(after)[License]

The DocKnot package as a whole is covered by the following copyright and
license:

t/data/generate/pod-thread/docknot.yaml  view on Meta::CPAN

format: v1

name: Pod::Thread
maintainer: Russ Allbery <rra@cpan.org>
version: '2.00'
synopsis: Format POD source into thread, an HTML macro language

license:
  name: Expat
copyrights:
  - holder: Russ Allbery <rra@cpan.org>
    years: 2002, 2008-2009, 2013, 2021

build:
  type: Module::Build
distribution:
  cpan: Pod-Thread
  packaging:
    debian:
      package: libpod-thread-perl
      personal: true
  section: web
  tarname: Pod-Thread
  version: pod-thread
support:
  email: rra@cpan.org
  github: rra/pod-thread
  web: https://www.eyrie.org/~eagle/software/pod-thread/
vcs:
  browse: https://git.eyrie.org/?p=web/pod-thread.git
  github: rra/pod-thread
  status:
    workflow: build
  type: Git
  url: https://git.eyrie.org/web/pod-thread.git

docs:
  api:
    - name: pod-thread
      title: Pod::Thread
  user:
    - name: pod2thread
      title: pod2thread manual page

blurb: |
  Pod::Thread translates POD source into thread, a macro language processed by
  spin.  It supports optionally adding a table of contents and a navigation
  bar to the genenerated file.  This package also includes the pod2thread
  driver script, invoked automatically by spin for POD files and pointers to
  POD files.

description: |
  This package contains a module to translate POD into thread, an HTML macro
  language.  As such, it's not very useful without
  [spin](https://www.eyrie.org/~eagle/software/web/), a separate program to
  convert thread into HTML.  I wrote this module for my personal needs and it
  may not be (and in fact probably isn't) suitable for more general use as
  yet.

  The eventual intention is to incorporate spin into
  [DocKnot](https://www.eyrie.org/~eagle/software/docknot/), at which point
  this module will provide the POD support for DocKnot as a static site
  generator.  I have no estimate for when that work will be done.

  The conversion done by this module is mostly straightforward.  The only
  notable parts are the optional generation of a table of contents or a

t/data/generate/pod-thread/output/readme  view on Meta::CPAN

                             Pod::Thread 2.00
         (Format POD source into thread, an HTML macro language)
                Maintained by Russ Allbery <rra@cpan.org>

  Copyright 2002, 2008-2009, 2013, 2021 Russ Allbery <rra@cpan.org>.  This
  software is distributed under a BSD-style license.  Please see the
  section LICENSE below for more information.

BLURB

  Pod::Thread translates POD source into thread, a macro language
  processed by spin.  It supports optionally adding a table of contents
  and a navigation bar to the genenerated file.  This package also
  includes the pod2thread driver script, invoked automatically by spin for
  POD files and pointers to POD files.

DESCRIPTION

  This package contains a module to translate POD into thread, an HTML
  macro language.  As such, it's not very useful without spin [1], a
  separate program to convert thread into HTML.  I wrote this module for
  my personal needs and it may not be (and in fact probably isn't)
  suitable for more general use as yet.

  [1] https://www.eyrie.org/~eagle/software/web/

  The eventual intention is to incorporate spin into DocKnot [2], at which
  point this module will provide the POD support for DocKnot as a static
  site generator.  I have no estimate for when that work will be done.

  [2] https://www.eyrie.org/~eagle/software/docknot/

t/data/generate/pod-thread/output/readme  view on Meta::CPAN

  sanity-check the release, set the environment variable RELEASE_TESTING
  to a true value.  To enable tests that may be sensitive to the local
  environment or that produce a lot of false positives without uncovering
  many problems, set the environment variable AUTHOR_TESTING to a true
  value.

SUPPORT

  The Pod::Thread web page at:

      https://www.eyrie.org/~eagle/software/pod-thread/

  will always have the current version of this package, the current
  documentation, and pointers to any additional resources.

  For bug tracking, use the issue tracker on GitHub:

      https://github.com/rra/pod-thread/issues

  Please be aware that I tend to be extremely busy and work projects often
  take priority.  I'll save your report and get to it as soon as I can,
  but it may take me a couple of months.

SOURCE REPOSITORY

  Pod::Thread is maintained using Git.  You can access the current source
  on GitHub at:

      https://github.com/rra/pod-thread

  or by cloning the repository at:

      https://git.eyrie.org/web/pod-thread.git

  or view the repository via the web at:

      https://git.eyrie.org/?p=web/pod-thread.git

  The eyrie.org repository is the canonical one, maintained by the author,
  but using GitHub is probably more convenient for most purposes.  Pull
  requests are gratefully reviewed and normally accepted.

LICENSE

  The Pod::Thread package as a whole is covered by the following copyright
  statement and license:

t/data/generate/pod-thread/output/readme-md  view on Meta::CPAN

# Pod::Thread

[![Build
status](https://github.com/rra/pod-thread/workflows/build/badge.svg)](https://github.com/rra/pod-thread/actions)
[![CPAN
version](https://img.shields.io/cpan/v/Pod-Thread)](https://metacpan.org/release/Pod-Thread)
[![License](https://img.shields.io/cpan/l/Pod-Thread)](https://github.com/rra/pod-thread/blob/master/LICENSE)

Copyright 2002, 2008-2009, 2013, 2021 Russ Allbery <rra@cpan.org>.  This
software is distributed under a BSD-style license.  Please see the section
[License](#license) below for more information.

## Blurb

Pod::Thread translates POD source into thread, a macro language processed
by spin.  It supports optionally adding a table of contents and a
navigation bar to the genenerated file.  This package also includes the
pod2thread driver script, invoked automatically by spin for POD files and
pointers to POD files.

## Description

This package contains a module to translate POD into thread, an HTML macro
language.  As such, it's not very useful without
[spin](https://www.eyrie.org/~eagle/software/web/), a separate program to
convert thread into HTML.  I wrote this module for my personal needs and
it may not be (and in fact probably isn't) suitable for more general use
as yet.

The eventual intention is to incorporate spin into
[DocKnot](https://www.eyrie.org/~eagle/software/docknot/), at which point
this module will provide the POD support for DocKnot as a static site
generator.  I have no estimate for when that work will be done.

The conversion done by this module is mostly straightforward.  The only
notable parts are the optional generation of a table of contents or a

t/data/generate/pod-thread/output/readme-md  view on Meta::CPAN

To enable tests that don't detect functionality problems but are used to
sanity-check the release, set the environment variable `RELEASE_TESTING`
to a true value.  To enable tests that may be sensitive to the local
environment or that produce a lot of false positives without uncovering
many problems, set the environment variable `AUTHOR_TESTING` to a true
value.

## Support

The [Pod::Thread web
page](https://www.eyrie.org/~eagle/software/pod-thread/) will always have
the current version of this package, the current documentation, and
pointers to any additional resources.

For bug tracking, use the [issue tracker on
GitHub](https://github.com/rra/pod-thread/issues).  Please be aware that I
tend to be extremely busy and work projects often take priority.  I'll
save your report and get to it as soon as I can, but it may take me a
couple of months.

## Source Repository

Pod::Thread is maintained using Git.  You can access the current source on
[GitHub](https://github.com/rra/pod-thread) or by cloning the repository
at:

https://git.eyrie.org/web/pod-thread.git

or [view the repository on the
web](https://git.eyrie.org/?p=web/pod-thread.git).

The eyrie.org repository is the canonical one, maintained by the author,
but using GitHub is probably more convenient for most purposes.  Pull
requests are gratefully reviewed and normally accepted.

## License

The Pod::Thread package as a whole is covered by the following copyright
statement and license:

t/data/generate/pod-thread/output/thread  view on Meta::CPAN

           [signature])\break
    Released \release[\2]]

\heading[Pod::Thread][software]

\h1[Pod::Thread]

\div(sidebar)[
    \h2[Download]

    \download[Pod::Thread][pod-thread]
        [web/Pod-Thread-\version[pod-thread]]

    \link[https://archives.eyrie.org/software/ARCHIVE/Pod-Thread/]
         [Archive]

    \h2[Documentation]

    \link[readme.html][General overview] \break
    \link[news.html][Change summary] \break
    \link[pod2thread.html][pod2thread manual page]

    \h2[Development]

    \link[https://github.com/rra/pod-thread]
         [GitHub] \break
    \link[https://github.com/rra/pod-thread/issues]
         [Bug tracker] \break
    \link[https://git.eyrie.org/?p=web/pod-thread.git]
         [Git repository] \break
    \link[https://metacpan.org/release/Pod-Thread]
         [MetaCPAN] \break
]

\h2[Blurb]

Pod::Thread translates POD source into thread, a macro language processed
by spin.  It supports optionally adding a table of contents and a
navigation bar to the genenerated file.  This package also includes the
pod2thread driver script, invoked automatically by spin for POD files and
pointers to POD files.

\h2[Description]

This package contains a module to translate POD into thread, an HTML macro
language.  As such, it's not very useful without
\link[https://www.eyrie.org/~eagle/software/web/][spin], a separate
program to convert thread into HTML.  I wrote this module for my personal
needs and it may not be (and in fact probably isn't) suitable for more
general use as yet.

The eventual intention is to incorporate spin into
\link[https://www.eyrie.org/~eagle/software/docknot/][DocKnot], at which
point this module will provide the POD support for DocKnot as a static
site generator.  I have no estimate for when that work will be done.

The conversion done by this module is mostly straightforward.  The only
notable parts are the optional generation of a table of contents or a

t/data/generate/pod-thread/output/thread  view on Meta::CPAN

\h2[Requirements]

Perl 5.24 or later and Pod::Parser 3.06 or later.  As mentioned above,
it's also not particularly useful without spin.

\h2[Download]

The distribution:

\table[][
    \program[Pod::Thread][pod-thread]
        [web/Pod-Thread-\version[pod-thread]]
]

An \link[https://archives.eyrie.org/software/ARCHIVE/Pod-Thread/] [archive
of older releases] is also available.

A Debian package (libpod-thread-perl) is available from my
\link[../debian.html][personal repository].

Pod::Thread is available from CPAN as the
\link[https://metacpan.org/release/Pod-Thread] [Pod-Thread distribution].

Pod::Thread is maintained using the Git version control system.  To check
out the current development tree, see
\link[https://github.com/rra/pod-thread][GitHub] or clone:

\pre[    https://git.eyrie.org/web/pod-thread.git]

Pull requests on GitHub are welcome.  You can also
\link[https://git.eyrie.org/?p=web/pod-thread.git][browse the current
development source].

\h2[Documentation]

\div(left)[
    \class(first)[User documentation:]

    \doc[readme.html][README]
    \doc[news.html][Change summary]
    \doc[pod2thread.html][pod2thread manual page]
    \doc[license.html][License and copyright]

    Developer documentation:

    \doc[https://github.com/rra/pod-thread]
        [GitHub]
    \doc[https://github.com/rra/pod-thread/issues]
        [Bug tracker]
]

\div(right)[
    \class(first)[API documentation:]

    \doc[pod-thread.html][Pod::Thread]
]

\h2(after)[License]

The Pod::Thread package as a whole is covered by the following copyright
and license:

\block[

    Copyright 2002, 2008-2009, 2013, 2021

t/data/regenerate-data  view on Meta::CPAN

}

# Regenerate the README and README.md for DocKnot itself.
my $docknot = App::DocKnot::Generate->new();
$docknot->generate_all();

# The test of spinning a tree of files uses a reference to App::DocKnot's own
# POD documentation.  Regenerate the expected output in case the POD has
# changed.
my $source = path('lib', 'App', 'DocKnot.pm');
my $podthread = Pod::Thread->new(navbar => 1);
my $spin = App::DocKnot::Spin::Thread->new();
my $thread;
$podthread->output_string(\$thread);
$podthread->parse_file("$source");
my $html = $spin->spin_thread($thread);

# Add the additional metadata that should be added by spin.
my $links = <<'EOD';
  <link rel="stylesheet" href="/~eagle/styles/pod.css" type="text/css" />
  <link rel="next" href="app-docknot-command.html"
        title="App::DocKnot::Command" />
  <link rel="up" href="../" title="DocKnot" />
  <link rel="top" href="../../../" />
EOD
my $comment = '<!-- Spun from DocKnot.pm by DocKnot %VERSION% on %DATE% -->';

t/data/regenerate-data  view on Meta::CPAN

    <a href="../../../">Russ Allbery</a>
    &gt; <a href="../../">Software</a>
    &gt; <a href="../">DocKnot</a>
  </td>
  <td class="navright"><a href="app-docknot-command.html">App::DocKnot::Command</a>&nbsp;&gt;</td>
</tr></table>
EOD
my $address = <<'EOD';
<address>
  Last <a href="https://www.eyrie.org/~eagle/software/docknot/">spun</a>
  %DATE% from thread modified %DATE%
</address>
EOD
$html =~ s{ (</head>) }{$links$1}xms;
$html =~ s{ <!-- [ ] Spun .*? [ ] --> }{$comment}xms;
$html =~ s{ (<body> \n) }{$1$navbar}xms;
$html =~ s{ (</body>) }{$navbar\n$address$1}xms;

# Replace the expected data file.
my $output = path(
    't', 'data', 'spin', 'output', 'software', 'docknot', 'api',

t/data/spin/input/.rss  view on Meta::CPAN

Base:        https://www.eyrie.org/~eagle/
RSS-Base:    https://www.eyrie.org/~eagle/
Title:       Changes to Russ Allbery's Web Pages
Description: Recent changes to Russ Allbery's web pages.
Language:    en-us
Output:      *:thread:changes.th *:rss:changes.rss
Thread-Prefix:
 \rss[changes.rss][Changes to Russ Allbery's Web Pages]
 .
 \heading[Recent Changes][indent]
 .
 \h1[Recent Changes]
 .
 \quote(broken)[
     Put up in a place
     where it's easy to see

t/data/spin/input/.rss  view on Meta::CPAN


Date: 2021-03-28 17:07
Title: Add sample INN init script and systemd unit file
Link: software/inn/
Description:
 In the documentation pages for INN CURRENT, 2.6, and 2.5, add the sample
 init script and systemd unit.

Date: 2021-03-28 09:21
Title: Pod::Thread 2.00
Link: software/pod-thread/
Description:
 Handle the navbar and table of contents internally in the module rather
 than via a pre-scanning pass.  Honor CVS Id strings found anywhere in the
 document.  Don't title-case words in section titles in the navbar that
 contain an underscore.

Date: 2021-03-21 19:58
Title: cvs2xhtml 1.15
Link: software/web/
Description:

t/data/spin/input/.rss  view on Meta::CPAN

 Support formatting of dense bullet lists with line continuations but no
 blank lines between bullets.  Update my email address.

Date: 2021-03-21 19:03
Title: Remove DocKnot from my personal Debian repository list
Link: software/debian.html
Description: DocKnot has now been uploaded to Debian proper.

Date: 2021-03-21 18:57
Title: Add separate web pages for Pod::Thread
Link: software/pod-thread/
Description:
 As the first step in cleaning up my static site generator pages, and in
 prepration for uploading it to CPAN in its own right, move Pod::Thread to
 its own separate web pages.

Date: 2021-03-20 13:35
Title: pam-krb5 4.10
Link: software/pam-krb5/
Description:
 Fix use-after-free if krb5_cc_get_principal fails on the newly-created

t/data/spin/input/software/docknot/index.th  view on Meta::CPAN

wording and push that out to every package I maintain during its next
release, without having to remember which changes I wanted to make.

DocKnot is also slowly absorbing other tools that I use for software
distribution and web site maintenance, such as generating distribution
tarballs for software packages.

DocKnot was designed and written for my personal needs, and I'm not sure
it will be useful for anyone else.  At the least, the template files are
rather specific to my preferences about how to write package
documentation, and the web page output is in my personal thread language
as opposed to HTML.  I'm not sure if I'll have the time to make it a more
general tool.  But you're certainly welcome to use it if you find it
useful, send pull requests to make it more general, or take ideas from it
for your own purposes.

Currently included in this package are just the App::DocKnot module and
its submodules, a small docknot driver program, and the templates I use
for my own software.  Over time, it may include more of my web publishing
framework, time permitting.

t/data/spin/input/software/index.th  view on Meta::CPAN

        [Netnews control message processing and archiving]
    \package[inn/][INN]
        [Full-featured, flexible and configurable news server]
    \package[gateway/][News::Gateway]
        [General toolkit for mail gatewaying and moderation]
    \package[postfaq/][postfaq]
        [Post FAQs and periodic postings with superseding]
]

\section[web][Web][
    \package[pod-thread/][Pod::Thread]
        [Format POD source into thread, an HTML macro language]
    \package[web/][Web tools]
        [Web page generation from a macro language and other sources]
]

\h2(#other)[Other Links]

\desclink[scripts/][Scripts][
    Some smaller scripts that don't warrant their own set of pages.  Some
    larger or more generally useful scripts get their own pages above;
    these are scripts with a narrower or specialized purpose, or scripts

t/data/spin/markdown/output/other.html  view on Meta::CPAN

<table class="navbar"><tr>
  <td class="navleft">&lt;&nbsp;<a href="foo.html">Some document</a></td>
  <td>
    <a href="./">Test Root</a>
  </td>
  <td class="navright"><a href="test.html">Test Markdown</a>&nbsp;&gt;</td>
</tr></table>

<address>
  Last <a href="https://www.eyrie.org/~eagle/software/docknot/">spun</a>
  %DATE% from thread modified %DATE%
</address>
</body>
</html>

t/data/spin/markdown/output/test.html  view on Meta::CPAN

<table class="navbar"><tr>
  <td class="navleft">&lt;&nbsp;<a href="other.html">Other test Markdown</a></td>
  <td>
    <a href="./">Test Root</a>
  </td>
  <td class="navright"></td>
</tr></table>

<address>
  Last <a href="https://www.eyrie.org/~eagle/software/docknot/">spun</a>
  %DATE% from thread modified %DATE%
</address>
</body>
</html>

t/data/spin/output/changes.html  view on Meta::CPAN

</p></dd>

<dt>2021-03-28 —
      <a href="https://www.eyrie.org/~eagle/software/inn/">Add sample INN init script and systemd unit file</a></dt>
<dd><p>
    In the documentation pages for INN CURRENT, 2.6, and 2.5, add the sample
    init script and systemd unit.
</p></dd>

<dt>2021-03-28 —
      <a href="https://www.eyrie.org/~eagle/software/pod-thread/">Pod::Thread 2.00</a></dt>
<dd><p>
    Handle the navbar and table of contents internally in the module rather
    than via a pre-scanning pass.  Honor CVS Id strings found anywhere in the
    document.  Don't title-case words in section titles in the navbar that
    contain an underscore.
</p></dd>

<dt>2021-03-21 —
      <a href="https://www.eyrie.org/~eagle/software/web/">cvs2xhtml 1.15</a></dt>
<dd><p>

t/data/spin/output/changes.html  view on Meta::CPAN

    blank lines between bullets.  Update my email address.
</p></dd>

<dt>2021-03-21 —
      <a href="https://www.eyrie.org/~eagle/software/debian.html">Remove DocKnot from my personal Debian repository list</a></dt>
<dd><p>
    DocKnot has now been uploaded to Debian proper.
</p></dd>

<dt>2021-03-21 —
      <a href="https://www.eyrie.org/~eagle/software/pod-thread/">Add separate web pages for Pod::Thread</a></dt>
<dd><p>
    As the first step in cleaning up my static site generator pages, and in
    prepration for uploading it to CPAN in its own right, move Pod::Thread to
    its own separate web pages.
</p></dd>

<dt>2021-03-20 —
      <a href="https://www.eyrie.org/~eagle/software/pam-krb5/">pam-krb5 4.10</a></dt>
<dd><p>
    Fix use-after-free if krb5_cc_get_principal fails on the newly-created

t/data/spin/output/changes.html  view on Meta::CPAN

<dt>2021-01-01 —
      <a href="https://www.eyrie.org/~eagle/reviews/year/2020.html">2020 reading in review</a></dt>
<dd><p>
    Add an overview of my 2020 reading, main book recommendations, and
    reading statistics.
</p></dd>
</dl>

<address>
  Last <a href="https://www.eyrie.org/~eagle/software/docknot/">spun</a>
  %DATE% from thread modified %DATE%
</address>
</body>
</html>

t/data/spin/output/index.html  view on Meta::CPAN

standards-compliant browser.  And somewhere herein is, I hope, something
that will capture your attention for more than five seconds.
</p>

<p>
Welcome.
</p>

<address>
  Last <a href="https://www.eyrie.org/~eagle/software/docknot/">spun</a>
  %DATE% from thread modified %DATE%
</address>
</body>
</html>

t/data/spin/output/journal/2011-08/006.html  view on Meta::CPAN

    I need to re-read this and write long reviews of them, since I have a
    lot to say about them.  But they need to be read in the context of the
    Christian faith to make any sense.</li>
<div class="date"><p>
Posted: %DATE% 00:09 &mdash; <span class="no-comment"><a href="/~eagle/faqs/comments.html">Why no comments?</a></span>
</p></div>
</ol>

<address>
  Last <a href="https://www.eyrie.org/~eagle/software/docknot/">spun</a>
  %DATE% from thread modified %DATE%
</address>
</body>
</html>



( run in 0.690 second using v1.01-cache-2.11-cpan-3cd7ad12f66 )