App-DocKnot

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

  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

README.md  view on Meta::CPAN

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

docs/docknot.yaml  view on Meta::CPAN

  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

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

        my ($indent, $lead) = @_;
        my $prefix = ($lead // q{}) . q{ } x $indent;
        my $notice;
        for my $copyright ($copyrights_ref->@*) {
            my $holder = $copyright->{holder};
            my $years = $copyright->{years};

            # Build the initial notice with the word copyright and the years.
            my $text = 'Copyright ' . $copyright->{years};
            local $Text::Wrap::columns = $self->{width} + 1;
            local $Text::Wrap::unexpand = 0;
            $text = wrap($prefix, $prefix . q{ } x 4, $text);

            # See if the holder fits on the last line.  If so, add it there;
            # otherwise, add another line.
            my $last_length;
            if (rindex($text, "\n") == -1) {
                $last_length = length($text);
            } else {
                $last_length = length($text) - rindex($text, "\n");
            }

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

    $para =~ s{ (?: \A | (?<=\n) ) \Q$indent\E }{}xmsg;

    # Remove any existing newlines, preserving two spaces after periods.
    $para =~ s{ [.] ([)\"]?) \n (\S) }{.$1  $2}xmsg;
    $para =~ s{ \n(\S) }{ $1}xmsg;

    # Force locally correct configuration of Text::Wrap.
    local $Text::Wrap::break = qr{\s+}xms;
    local $Text::Wrap::columns = $self->{width} + 1;
    local $Text::Wrap::huge = 'overflow';
    local $Text::Wrap::unexpand = 0;

    # Do the wrapping.  This modifies @paragraphs in place.
    $para = wrap($indent, $indent, $para);

    # Strip any trailing whitespace, since some gets left behind after periods
    # by Text::Wrap.
    $para =~ s{ [ ]+ \n }{\n}xmsg;

    # All done.
    return $para;

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

    # If there is no baseline, assume single lines of at most 34 characters
    # with no unexpected characters are headings.
    return $nobase && $paragraph =~ m{
        \A \s*
        [ \w\"\(\),:./&-]{0,33} [\w\"\)]
        \s* \n
        \z
    }xms;
}

# Whether a line is an RCS/CVS Id string that has been expanded.
#
# $line - Line to classify
#
# Returns: True if so, false otherise
sub _is_id {
    my ($line) = @_;
    return $line =~ m{ \A \s* [\$]Id: \N+ [\$] \s* \z }xms;
}

# Whether a paragraph should be a literal paragraph, decided based on whether

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

# Expand a macro invocation.
#
# $definition - Definition of the macro
# $block      - True if currently in block context
# @args       - The arguments to the macro
#
# Returns: List with the macro expansion and the block context flag
sub _macro {
    my ($self, $definition, $block, @args) = @_;

    # The function that expands a macro substitution marker.  If the number of
    # the marker is higher than the number of arguments of the macro, leave it
    # 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
#            in <p>
#
# Returns: List with the following elements:
#            $output - Output from expanding the command
#            $block  - Whether the output is block context
#            $text   - Remaining unparsed text
sub _expand {
    my ($self, $command, $text, $block) = @_;

    # Special handling for expanding variables.  These references look like
    # \=NAME and expand to the value of the variable "NAME".
    if ($command =~ m{ \A = \w }xms) {
        my $variable = substr($command, 1);
        if (exists($self->{variable}{$variable})) {
            return ($self->{variable}{$variable}, 0, $text);
        } else {
            $self->_warning("unknown variable \\=$variable");
            return (q{}, 0, $text);
        }
    }

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

        my ($format, $rest, @args) = $self->_extract($text, $args, 1);
        my ($blocktag, $output) = $self->$handler($format, @args);
        return ($output, $blocktag, $rest);
    } else {
        my ($rest, @args) = $self->_extract($text, $args);
        my ($blocktag, $output) = $self->$handler(@args);
        return ($output, $blocktag, $rest);
    }
}

# This is the heart of the input parser.  Take a string of raw input, expand
# the commands in it, and format the results as HTML.  This function is
# mutually recursive with _expand and _macro.
#
# This function is responsible for maintaining the line number in the file
# currently being processed, for error reporting.  The strategy used is to
# increment the line number whenever a newline is seen in processed text.
# This means that newlines are not seen until the text containing them is
# parsed, which in turn means that every argument that may contain a newline
# must be parsed or must update the line number.
#
# $text  - Input text to parse
# $block - True if the parse is done in a block context

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

    my $paragraph = q{};

    # Leading whitespace that should be added to a created paragraph.  This is
    # only non-empty if $paragraph is empty.
    my $space = q{};

    # Whether we saw a construct not suitable for block level.
    my $nonblock = 0;

    # We have at least one command.  Parse the text into sections of regular
    # text and commands, expand the commands, and glue the results together as
    # HTML.
    #
    # If we are at block level, we have to distinguish between plain text and
    # inline commands, which have to be wrapped in paragraph tags, and
    # block-level commands, which shouldn't be.
    while ($text ne q{}) {
        my ($string, $command);

        # Extract text before the next command, or a command name (but none of
        # its arguments).  I think it's impossible for this regex to fail to

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

                $output .= $string;
                $nonblock = 1;
            }
        }

        # Otherwise, we have a command.  Expand that command, setting block
        # context if we haven't seen any inline content so far.
        else {
            my ($result, $blocktag);
            ($result, $blocktag, $text)
              = $self->_expand($command, $text, $block && $paragraph eq q{});

            # If the result requires block context, output any pending
            # paragraph and then the result.  Otherwise, if we are already at
            # block context, start a new paragraph.  Otherwise, just append
            # the result to our output.
            if ($blocktag) {
                if ($block && $paragraph ne q{}) {
                    $output .= $border . $self->_paragraph($paragraph);
                    $border = q{};
                    $paragraph = q{};

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

be used verbatim as a URL except with C<.css> appended.

This command must come after any C<\id> or C<\rss> commands and may come after
commands that don't produce any output (such as macro definitions or
C<\include> of files that produce no output) but otherwise must be the first
command of the file.

=item \id[ID]

Sets the Subversion, CVS, or RCS revision number and time.  ID should be the
string C<< $Z<>Id$ >>, which will be expanded by Subversion, CVS, and RCS.
This string is embedded verbatim in an HTML comment near the beginning of the
generated output, and is used to determine last modified information for the
file (used by the C<\signature> command).

For this command to behave properly, it must be given before C<\heading>.

=item \include[FILE]

Include FILE after the current paragraph.  If multiple files are included in
the same paragraph, they're included in reverse order, but this behavior may

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

One of the reasons to use thread instead of HTML is the ability to define new
macros on the fly.  If there are constructs that are used more than once in
the page, you can define a macro at the top of that page and then use it
throughout the page.

A variable can be defined with the command:

    \=[VARIABLE][VALUE]

where VARIABLE is the name that will be used (can only be alphanumerics plus
underscore) and VALUE is the value that string will expand into.  Any later
occurrence of \=VARIABLE in the file will be replaced with <value>.  For
example:

    \=[FOO][some string]

will cause any later occurrences of C<\=FOO> in the file to be replaced with
the text C<some string>.  Consider using this to collect external URLs for
links at the top of a page for easy updating.

A macro can be defined with the command:

    \==[NAME][NARGS][DEFINITION]

where NAME is the name of the macro (again consisting only of alphanumerics or
underscore), NARGS is the number of arguments that it takes, and DEFINITION is
the definition of the macro.

When the macro is expanded, any occurrence of C<\1> in the definition is
replaced with the first argument, any occurrence of C<\2> with the second
argument, and so forth, and then the definition with those substitutions is
parsed as thread, as if it were written directly in the source page.

For example:

    \==[bolddesc] [2] [\desc[\bold[\1]][\2]]

defines a macro C<\bolddesc> that takes the same arguments as the regular
C<\desc> command but always wraps the first argument, the heading, in C<<

t/config/basic.t  view on Meta::CPAN

# Root of the test data.
my $dataroot = path('t', 'data', 'generate');

# Load a test configuration and check a few inobvious pieces of it.
my $metadata_path = $dataroot->child('ansicolor', 'docknot.yaml');
my $config = App::DocKnot::Config->new({ metadata => $metadata_path });
isa_ok($config, 'App::DocKnot::Config');
my $data_ref = $config->config();
ok($data_ref->{build}{install}, 'build/install defaults to true');

# Check that the license data is expanded correctly.
my $licenses_path = module_file('App::DocKnot', 'licenses.yaml');
my $licenses_ref = YAML::XS::LoadFile($licenses_path);
my $perl_license_ref = $licenses_ref->{Perl};
is($data_ref->{license}{summary}, $perl_license_ref->{summary}, 'summary');
is($data_ref->{license}{text}, $perl_license_ref->{text}, 'text');

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

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

t/data/spin/output/software/docknot/readme.html  view on Meta::CPAN

  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.
</p>

<h2>DESCRIPTION</h2>

<p>
  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.
</p>

<p>
  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

t/data/spin/text/input/docknot  view on Meta::CPAN

  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

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

# The expected output is a bit different since we won't add timestamp
# information or the filename to the comment, so we have to generate our
# expected output file.
my $tempfile = Path::Tiny->tempfile();
my $output = $expected->slurp_utf8();
$output =~ s{ from [ ] index[.]th [ ] }{}xms;
$output =~ s{ <address> .* </address> \n }{}xms;
$tempfile->spew_utf8($output);

# Spin the file using the spin_thread() API, using the right working directory
# to expand \image and the like.
my $spin
  = App::DocKnot::Spin::Thread->new({ 'style-url' => '/~eagle/styles/' });
my $thread = $input->slurp_utf8();
my $cwd = getcwd();
chdir($inputdir);
my $html = $spin->spin_thread($thread);
chdir($cwd);
my $outfile = Path::Tiny->tempfile();
$outfile->spew_utf8($html);
is_spin_output($outfile, $tempfile, 'spin_thread');



( run in 2.360 seconds using v1.01-cache-2.11-cpan-5623c5533a1 )