summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuss Allbery <rra@cpan.org>2019-03-16 15:53:54 -0700
committerRuss Allbery <rra@cpan.org>2019-03-16 15:53:54 -0700
commit6030c61d9a418d85a789bcc8bdfd751e8afd2a93 (patch)
tree4d04e7001d4df549e1cc8c7c8c5ee8311eebc117
parent1c1515b5d74f26125f61ffb0dd70ac0c7d28b278 (diff)
Separate App::DocKnot::Command, add base App::DocKnot
Move the entry point for command-line commands from App::DocKnot to App::DocKnot::Command. The App::DocKnot module now only provides some helper methods to load application data, used by both App::DocKnot::Config and App::DocKnot::Generate. It's no longer necessary to explicitly load App::DocKnot before using one of the submodules.
-rw-r--r--Changes9
-rw-r--r--MANIFEST2
-rwxr-xr-xbin/docknot4
-rw-r--r--lib/App/DocKnot.pm279
-rw-r--r--lib/App/DocKnot/Command.pm312
-rw-r--r--lib/App/DocKnot/Config.pm51
-rw-r--r--lib/App/DocKnot/Generate.pm32
-rwxr-xr-xt/cli/errors.t8
-rwxr-xr-xt/cli/generate.t8
-rw-r--r--t/config/basic.t7
-rwxr-xr-xt/generate/basic.t9
-rwxr-xr-xt/generate/output.t9
-rwxr-xr-xt/generate/self.t9
13 files changed, 417 insertions, 322 deletions
diff --git a/Changes b/Changes
index d616637..78f0576 100644
--- a/Changes
+++ b/Changes
@@ -1,10 +1,17 @@
User-Visible DocKnot Changes
-DocKnot 2.01 (unreleased)
+DocKnot 3.00 (unreleased)
Separate configuration parsing into a new App::DocKnot::Config module,
used by App::DocKnot::Generate.
+ Move the entry point for command-line commands from App::DocKnot to
+ App::DocKnot::Command. The App::DocKnot module now only provides some
+ helper methods to load application data, used by both
+ App::DocKnot::Config and App::DocKnot::Generate. It's no longer
+ necessary to explicitly load App::DocKnot before using one of the
+ submodules.
+
Support orphaned warnings in the README and README.md output as well
as thread output.
diff --git a/MANIFEST b/MANIFEST
index f654155..e2bc930 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -10,6 +10,7 @@ docs/metadata/README
docs/metadata/requirements
docs/metadata/test/suffix
lib/App/DocKnot.pm
+lib/App/DocKnot/Command.pm
lib/App/DocKnot/Config.pm
lib/App/DocKnot/Generate.pm
LICENSE
@@ -26,6 +27,7 @@ share/templates/readme.tmpl
share/templates/thread.tmpl
t/cli/errors.t
t/cli/generate.t
+t/config/basic.t
t/data/ansicolor/metadata/blurb
t/data/ansicolor/metadata/description
t/data/ansicolor/metadata/metadata.json
diff --git a/bin/docknot b/bin/docknot
index 3f13163..36ba39a 100755
--- a/bin/docknot
+++ b/bin/docknot
@@ -15,7 +15,7 @@ use warnings;
use App::DocKnot;
# Dispatch everything to the module.
-my $docknot = App::DocKnot->new();
+my $docknot = App::DocKnot::Command->new();
$docknot->run();
__END__
@@ -154,7 +154,7 @@ Russ Allbery <rra@cpan.org>
=head1 COPYRIGHT AND LICENSE
-Copyright 2016, 2018 Russ Allbery <rra@cpan.org>
+Copyright 2016, 2018-2019 Russ Allbery <rra@cpan.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/lib/App/DocKnot.pm b/lib/App/DocKnot.pm
index f1dfb98..b4ed7f1 100644
--- a/lib/App/DocKnot.pm
+++ b/lib/App/DocKnot.pm
@@ -1,8 +1,9 @@
-# Dispatch code for the DocKnot application.
+# Parent module for DocKnot.
#
# DocKnot provides various commands for generating documentation, web pages,
-# and software releases. This module provides command-line parsing and
-# dispatch of commands to the various App::DocKnot modules.
+# and software releases. This parent module provides some internal helper
+# functions used to load configuration and metadata. The normal entry point
+# are the various submodules, or App::DocKnot::Command via docknot.
#
# SPDX-License-Identifier: MIT
@@ -16,220 +17,74 @@ use 5.024;
use autodie;
use warnings;
-use App::DocKnot::Generate;
-use Getopt::Long;
-
-# Defines the subcommands, their options, and the module and method that
-# implements them. The keys are the names of the commands. Each value is a
-# hash with one or more of the following keys:
-#
-# code
-# A reference to a function to call to implement this command. If set,
-# overrides method and module. The function will be passed a reference to
-# the hash resulting from option parsing as its first argument and any
-# other command-line arguments as its remaining arguments.
-#
-# maximum
-# The maximum number of positional arguments this command takes.
-#
-# method
-# The name of the method to run to implement this command. It is passed
-# as arguments any remaining command-line arguments after option parsing.
-#
-# minimum
-# The minimum number of positional arguments this command takes.
-#
-# module
-# The name of the module that implements this command. Its constructor
-# (which must be named new) will be passed as its sole argument a
-# reference to the hash containing the results of parsing any options.
-#
-# options
-# A reference to an array of Getopt::Long option specifications defining
-# the arguments that can be passed to this subcommand.
-#
-# required
-# A reference to an array of required option names (the part before any |
-# in the option specification for that option). If any of these options
-# are not set, an error will be thrown.
-our %COMMANDS = (
- generate => {
- method => 'generate_output',
- module => 'App::DocKnot::Generate',
- options => ['metadata|m=s', 'width|w=i'],
- maximum => 2,
- minimum => 1,
- },
- 'generate-all' => {
- method => 'generate_all',
- module => 'App::DocKnot::Generate',
- options => ['metadata|m=s', 'width|w=i'],
- maximum => 0,
- },
-);
+use File::BaseDir qw(config_files);
+use File::ShareDir qw(module_file);
+use File::Spec;
+use JSON;
+use Perl6::Slurp;
##############################################################################
-# Option parsing
+# Helper methods
##############################################################################
-# Parse command-line options and do any required error handling.
-#
-# $self - The App::DocKnot object
-# $command - The command being run or undef for top-level options
-# $options_ref - A reference to the options specification
-# @args - The arguments to the command
+# Helper routine to return the path of a file from the application data.
+# These data files are installed with App::DocKnot, but each file can be
+# overridden by the user via files in $HOME/.config/docknot or
+# /etc/xdg/docknot (or whatever $XDG_CONFIG_DIRS is set to).
#
-# Returns: A list composed of a reference to a hash of options and values,
-# followed by a reference to the remaining arguments after options
-# have been extracted
-# Throws: A text error message if the options are invalid
-sub _parse_options {
- my ($self, $command, $options_ref, @args) = @_;
-
- # Use the object-oriented syntax to isolate configuration options from the
- # rest of the program.
- my $parser = Getopt::Long::Parser->new;
- $parser->configure(qw(bundling no_ignore_case require_order));
-
- # Parse the options and capture any errors, turning them into exceptions.
- # The first letter of the Getopt::Long warning message will be capitalized
- # but we want it to be lowercase to follow our error message standard.
- my %opts;
- {
- my $error = 'option parsing failed';
- local $SIG{__WARN__} = sub { ($error) = @_ };
- if (!$parser->getoptionsfromarray(\@args, \%opts, $options_ref->@*)) {
- $error =~ s{ \n+ \z }{}xms;
- $error =~ s{ \A (\w) }{ lc($1) }xmse;
- if ($command) {
- die "$0 $command: $error\n";
- } else {
- die "$0: $error\n";
- }
- }
- }
-
- # Success. Return the options and the remaining arguments.
- return (\%opts, \@args);
-}
-
-# Parse command-line options for a given command.
+# We therefore try File::BaseDir first (which handles the XDG paths) and fall
+# back on using File::ShareDir to locate the data.
#
-# $self - The App::DocKnot object
-# $command - The command being run
-# @args - The arguments to the command
+# $self - The App::DocKnot object
+# @path - The relative path of the file as a list of components
#
-# Returns: A list composed of a reference to a hash of options and values,
-# followed by a reference to the remaining arguments after options
-# have been extracted
-# Throws: A text error message if the options are invalid
-sub _parse_command {
- my ($self, $command, @args) = @_;
- my $options_ref = $COMMANDS{$command}{options};
- return $self->_parse_options($command, $options_ref, @args);
-}
+# Returns: The absolute path to the application data
+# Throws: Text exception on failure to locate the desired file
+sub appdata_path {
+ my ($self, @path) = @_;
-##############################################################################
-# Public interface
-##############################################################################
+ # Try XDG paths first.
+ my $path = config_files('docknot', @path);
-# Create a new App::DocKnot object.
-#
-# $class - Class of object to create
-#
-# Returns: Newly created object
-sub new {
- my ($class) = @_;
- my $self = {};
- bless($self, $class);
- return $self;
+ # If that doesn't work, use the data that came with the module.
+ if (!defined($path)) {
+ $path = module_file('App::DocKnot', File::Spec->catfile(@path));
+ }
+ return $path;
}
-# Parse command-line options to determine which command to run, and then
-# dispatch that command.
+# Helper routine that locates an application data file, interprets it as JSON,
+# and returns the resulting decoded contents. This uses the relaxed parsing
+# mode, so comments and commas after data elements are supported.
#
# $self - The App::DocKnot object
-# @args - Command-line arguments (optional, default: @ARGV)
-#
-# Returns: undef
-# Throws: A text error message for invalid arguments
-sub run {
- my ($self, @args) = @_;
- if (!@args) {
- @args = @ARGV;
- }
-
- # Parse the initial options and extract the subcommand to run, preserving
- # any options after the subcommand.
- my $spec = ['help|h'];
- my ($opts_ref, $args_ref) = $self->_parse_options(undef, $spec, @args);
- if ($opts_ref->{help}) {
- pod2usage(0);
- }
- if (!$args_ref->@*) {
- die "$0: no subcommand given\n";
- }
- my $command = shift($args_ref->@*);
- if (!$COMMANDS{$command}) {
- die "$0: unknown command $command\n";
- }
-
- # Parse the arguments for the command and check for required arguments.
- ($opts_ref, $args_ref) = $self->_parse_command($command, $args_ref->@*);
- if (exists($COMMANDS{$command}{required})) {
- for my $required ($COMMANDS{$command}{required}->@*) {
- if (!exists($opts_ref->{$required})) {
- die "$0 $command: missing required option --$required\n";
- }
- }
- }
-
- # Check that we have the correct number of remaining arguments.
- if (exists($COMMANDS{$command}{maximum})) {
- if (scalar($args_ref->@*) > $COMMANDS{$command}{maximum}) {
- die "$0 $command: too many arguments\n";
- }
- }
- if (exists($COMMANDS{$command}{minimum})) {
- if (scalar($args_ref->@*) < $COMMANDS{$command}{minimum}) {
- die "$0 $command: too few arguments\n";
- }
- }
-
- # Dispatch the command and turn exceptions into error messages.
- eval {
- if ($COMMANDS{$command}{code}) {
- $COMMANDS{$command}{code}->($opts_ref, $args_ref->@*);
- } else {
- my $object = $COMMANDS{$command}{module}->new($opts_ref);
- my $method = $COMMANDS{$command}{method};
- $object->$method($args_ref->@*);
- }
- };
- if ($@) {
- my $error = $@;
- chomp($error);
- $error =~ s{ \s+ at \s+ \S+ \s+ line \s+ \d+ [.]? \z }{}xms;
- die "$0 $command: $error\n";
- }
- return;
+# @path - The path of the file to load, as a list of components
+#
+# Returns: Anonymous hash or array resulting from decoding the JSON object
+# Throws: slurp or JSON exception on failure to load or decode the object
+sub load_appdata_json {
+ my ($self, @path) = @_;
+ my $path = $self->appdata_path(@path);
+ my $json = JSON->new;
+ $json->relaxed;
+ return $json->decode(scalar(slurp($path)));
}
+##############################################################################
+# Module return value and documentation
+##############################################################################
+
1;
__END__
=for stopwords
-Allbery DocKnot docknot MERCHANTABILITY NONINFRINGEMENT sublicense
+Allbery DocKnot docknot MERCHANTABILITY NONINFRINGEMENT sublicense JSON
+submodules
=head1 NAME
App::DocKnot - Documentation and software release management
-=head1 SYNOPSIS
-
- my $docknot = App::DocKnot->new();
- $docknot->run();
-
=head1 REQUIREMENTS
Perl 5.24 or later and the modules File::BaseDir, File::ShareDir, JSON,
@@ -238,31 +93,35 @@ available from CPAN.
=head1 DESCRIPTION
-The App::DocKnot module implements the B<docknot> command-line interface to
-all of the functions of DocKnot. It is an implementation detail of the
-B<docknot> command-line tool and is normally only called by that program.
+DocKnot is a system for documentation and software release management. Its
+functionality is provided by various submodules, often invoked via the
+B<docknot> command-line program. For more information, see L<docknot(1)>.
-For full documentation, see L<docknot(1)>.
+This module only provides helper functions to load configuration and metadata
+that are used by its various submodules.
-=head1 CLASS METHODS
+=head1 INSTANCE METHODS
=over 4
-=item new()
+=item appdata_path(PATH[, ...])
-Create a new App::DocKnot object.
+Return the path of a file from the application data. The file is specified as
+one or more path components.
-=back
+These data files are installed with App::DocKnot, but each file can be
+overridden by the user via files in F<$HOME/.config/docknot> or
+F</etc/xdg/docknot> (or whatever $XDG_CONFIG_DIRS is set to). Raises a text
+exception if the desired file could not be located.
-=head1 INSTANCE METHODS
-
-=over 4
+=item load_appdata_json(PATH[, ...])
-=item run([ARGS])
+Locate an application data file using the same algorithm as appdata_path(),
+interpret it as JSON, and returns the resulting decoded contents. This uses
+the relaxed JSON parsing mode, so comments and commas after data elements are
+supported. The path is specified as one or more path components.
-Run the DocKnot action specified by ARGS, which are parsed as command-line
-arguments to B<docknot>. If ARGS is not given or is empty, C<@ARGV> will be
-parsed instead.
+Raises a slurp or JSON exception on failure to load or decode the data file.
=back
@@ -272,7 +131,7 @@ Russ Allbery <rra@cpan.org>
=head1 COPYRIGHT AND LICENSE
-Copyright 2018 Russ Allbery <rra@cpan.org>
+Copyright 2013-2019 Russ Allbery <rra@cpan.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -294,7 +153,7 @@ SOFTWARE.
=head1 SEE ALSO
-L<App::DocKnot::Generate>, L<docknot(1)>
+L<docknot(1)>
This module is part of the App-DocKnot distribution. The current version of
App::DocKnot is available from CPAN, or directly from its web site at
diff --git a/lib/App/DocKnot/Command.pm b/lib/App/DocKnot/Command.pm
new file mode 100644
index 0000000..f7fcf3e
--- /dev/null
+++ b/lib/App/DocKnot/Command.pm
@@ -0,0 +1,312 @@
+# Dispatch code for the DocKnot application.
+#
+# DocKnot provides various commands for generating documentation, web pages,
+# and software releases. This module provides command-line parsing and
+# dispatch of commands to the various App::DocKnot modules.
+#
+# SPDX-License-Identifier: MIT
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package App::DocKnot::Command 2.00;
+
+use 5.024;
+use autodie;
+use warnings;
+
+use App::DocKnot::Generate;
+use Getopt::Long;
+
+# Defines the subcommands, their options, and the module and method that
+# implements them. The keys are the names of the commands. Each value is a
+# hash with one or more of the following keys:
+#
+# code
+# A reference to a function to call to implement this command. If set,
+# overrides method and module. The function will be passed a reference to
+# the hash resulting from option parsing as its first argument and any
+# other command-line arguments as its remaining arguments.
+#
+# maximum
+# The maximum number of positional arguments this command takes.
+#
+# method
+# The name of the method to run to implement this command. It is passed
+# as arguments any remaining command-line arguments after option parsing.
+#
+# minimum
+# The minimum number of positional arguments this command takes.
+#
+# module
+# The name of the module that implements this command. Its constructor
+# (which must be named new) will be passed as its sole argument a
+# reference to the hash containing the results of parsing any options.
+#
+# options
+# A reference to an array of Getopt::Long option specifications defining
+# the arguments that can be passed to this subcommand.
+#
+# required
+# A reference to an array of required option names (the part before any |
+# in the option specification for that option). If any of these options
+# are not set, an error will be thrown.
+our %COMMANDS = (
+ generate => {
+ method => 'generate_output',
+ module => 'App::DocKnot::Generate',
+ options => ['metadata|m=s', 'width|w=i'],
+ maximum => 2,
+ minimum => 1,
+ },
+ 'generate-all' => {
+ method => 'generate_all',
+ module => 'App::DocKnot::Generate',
+ options => ['metadata|m=s', 'width|w=i'],
+ maximum => 0,
+ },
+);
+
+##############################################################################
+# Option parsing
+##############################################################################
+
+# Parse command-line options and do any required error handling.
+#
+# $self - The App::DocKnot::Command object
+# $command - The command being run or undef for top-level options
+# $options_ref - A reference to the options specification
+# @args - The arguments to the command
+#
+# Returns: A list composed of a reference to a hash of options and values,
+# followed by a reference to the remaining arguments after options
+# have been extracted
+# Throws: A text error message if the options are invalid
+sub _parse_options {
+ my ($self, $command, $options_ref, @args) = @_;
+
+ # Use the object-oriented syntax to isolate configuration options from the
+ # rest of the program.
+ my $parser = Getopt::Long::Parser->new;
+ $parser->configure(qw(bundling no_ignore_case require_order));
+
+ # Parse the options and capture any errors, turning them into exceptions.
+ # The first letter of the Getopt::Long warning message will be capitalized
+ # but we want it to be lowercase to follow our error message standard.
+ my %opts;
+ {
+ my $error = 'option parsing failed';
+ local $SIG{__WARN__} = sub { ($error) = @_ };
+ if (!$parser->getoptionsfromarray(\@args, \%opts, $options_ref->@*)) {
+ $error =~ s{ \n+ \z }{}xms;
+ $error =~ s{ \A (\w) }{ lc($1) }xmse;
+ if ($command) {
+ die "$0 $command: $error\n";
+ } else {
+ die "$0: $error\n";
+ }
+ }
+ }
+
+ # Success. Return the options and the remaining arguments.
+ return (\%opts, \@args);
+}
+
+# Parse command-line options for a given command.
+#
+# $self - The App::DocKnot::Command object
+# $command - The command being run
+# @args - The arguments to the command
+#
+# Returns: A list composed of a reference to a hash of options and values,
+# followed by a reference to the remaining arguments after options
+# have been extracted
+# Throws: A text error message if the options are invalid
+sub _parse_command {
+ my ($self, $command, @args) = @_;
+ my $options_ref = $COMMANDS{$command}{options};
+ return $self->_parse_options($command, $options_ref, @args);
+}
+
+##############################################################################
+# Public interface
+##############################################################################
+
+# Create a new App::DocKnot::Command object.
+#
+# $class - Class of object to create
+#
+# Returns: Newly created object
+sub new {
+ my ($class) = @_;
+ my $self = {};
+ bless($self, $class);
+ return $self;
+}
+
+# Parse command-line options to determine which command to run, and then
+# dispatch that command.
+#
+# $self - The App::DocKnot::Command object
+# @args - Command-line arguments (optional, default: @ARGV)
+#
+# Returns: undef
+# Throws: A text error message for invalid arguments
+sub run {
+ my ($self, @args) = @_;
+ if (!@args) {
+ @args = @ARGV;
+ }
+
+ # Parse the initial options and extract the subcommand to run, preserving
+ # any options after the subcommand.
+ my $spec = ['help|h'];
+ my ($opts_ref, $args_ref) = $self->_parse_options(undef, $spec, @args);
+ if ($opts_ref->{help}) {
+ pod2usage(0);
+ }
+ if (!$args_ref->@*) {
+ die "$0: no subcommand given\n";
+ }
+ my $command = shift($args_ref->@*);
+ if (!$COMMANDS{$command}) {
+ die "$0: unknown command $command\n";
+ }
+
+ # Parse the arguments for the command and check for required arguments.
+ ($opts_ref, $args_ref) = $self->_parse_command($command, $args_ref->@*);
+ if (exists($COMMANDS{$command}{required})) {
+ for my $required ($COMMANDS{$command}{required}->@*) {
+ if (!exists($opts_ref->{$required})) {
+ die "$0 $command: missing required option --$required\n";
+ }
+ }
+ }
+
+ # Check that we have the correct number of remaining arguments.
+ if (exists($COMMANDS{$command}{maximum})) {
+ if (scalar($args_ref->@*) > $COMMANDS{$command}{maximum}) {
+ die "$0 $command: too many arguments\n";
+ }
+ }
+ if (exists($COMMANDS{$command}{minimum})) {
+ if (scalar($args_ref->@*) < $COMMANDS{$command}{minimum}) {
+ die "$0 $command: too few arguments\n";
+ }
+ }
+
+ # Dispatch the command and turn exceptions into error messages.
+ eval {
+ if ($COMMANDS{$command}{code}) {
+ $COMMANDS{$command}{code}->($opts_ref, $args_ref->@*);
+ } else {
+ my $object = $COMMANDS{$command}{module}->new($opts_ref);
+ my $method = $COMMANDS{$command}{method};
+ $object->$method($args_ref->@*);
+ }
+ };
+ if ($@) {
+ my $error = $@;
+ chomp($error);
+ $error =~ s{ \s+ at \s+ \S+ \s+ line \s+ \d+ [.]? \z }{}xms;
+ die "$0 $command: $error\n";
+ }
+ return;
+}
+
+##############################################################################
+# Module return value and documentation
+##############################################################################
+
+1;
+__END__
+
+=for stopwords
+Allbery DocKnot docknot MERCHANTABILITY NONINFRINGEMENT sublicense
+
+=head1 NAME
+
+App::DocKnot::Command - Run DocKnot commands
+
+=head1 SYNOPSIS
+
+ my $docknot = App::DocKnot::Command->new();
+ $docknot->run();
+
+=head1 REQUIREMENTS
+
+Perl 5.24 or later and the modules File::BaseDir, File::ShareDir, JSON,
+Perl6::Slurp, and Template (part of Template Toolkit), all of which are
+available from CPAN.
+
+=head1 DESCRIPTION
+
+The App::DocKnot::Command module implements the B<docknot> command-line
+interface to all of the functions of DocKnot. It is an implementation detail
+of the B<docknot> command-line tool and is normally only called by that
+program.
+
+For full documentation, see L<docknot(1)>.
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item new()
+
+Create a new App::DocKnot::Command object.
+
+=back
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item run([ARGS])
+
+Run the DocKnot action specified by ARGS, which are parsed as command-line
+arguments to B<docknot>. If ARGS is not given or is empty, C<@ARGV> will be
+parsed instead.
+
+=back
+
+=head1 AUTHOR
+
+Russ Allbery <rra@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2018-2019 Russ Allbery <rra@cpan.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+=head1 SEE ALSO
+
+L<App::DocKnot::Generate>, L<docknot(1)>
+
+This module is part of the App-DocKnot distribution. The current version of
+App::DocKnot is available from CPAN, or directly from its web site at
+L<https://www.eyrie.org/~eagle/software/docknot/>.
+
+=cut
+
+# Local Variables:
+# copyright-at-end-flag: t
+# End:
diff --git a/lib/App/DocKnot/Config.pm b/lib/App/DocKnot/Config.pm
index de8d8b9..9273719 100644
--- a/lib/App/DocKnot/Config.pm
+++ b/lib/App/DocKnot/Config.pm
@@ -13,11 +13,10 @@ package App::DocKnot::Config 2.00;
use 5.024;
use autodie;
+use parent qw(App::DocKnot);
use warnings;
use Carp qw(croak);
-use File::BaseDir qw(config_files);
-use File::ShareDir qw(module_file);
use File::Spec;
use JSON;
use Perl6::Slurp;
@@ -41,49 +40,6 @@ our @METADATA_FILES = qw(
# Helper methods
##############################################################################
-# Internal helper routine to return the path of a file from the application
-# data. These data files are installed with App::DocKnot, but each file can
-# be overridden by the user via files in $HOME/.config/docknot or
-# /etc/xdg/docknot (or whatever $XDG_CONFIG_DIRS is set to).
-#
-# We therefore try File::BaseDir first (which handles the XDG paths) and fall
-# back on using File::ShareDir to locate the data.
-#
-# $self - The App::DocKnot::Generate object
-# @path - The relative path of the file as a list of components
-#
-# Returns: The absolute path to the application data
-# Throws: Text exception on failure to locate the desired file
-sub _appdata_path {
- my ($self, @path) = @_;
-
- # Try XDG paths first.
- my $path = config_files('docknot', @path);
-
- # If that doesn't work, use the data that came with the module.
- if (!defined($path)) {
- $path = module_file('App::DocKnot', File::Spec->catfile(@path));
- }
- return $path;
-}
-
-# Internal helper routine that locates an application data file, interprets it
-# as JSON, and returns the resulting decoded contents. This uses the relaxed
-# parsing mode, so comments and commas after data elements are supported.
-#
-# $self - The App::DocKnot::Generate object
-# @path - The path of the file to load, as a list of components
-#
-# Returns: Anonymous hash or array resulting from decoding the JSON object
-# Throws: slurp or JSON exception on failure to load or decode the object
-sub _load_appdata_json {
- my ($self, @path) = @_;
- my $path = $self->_appdata_path(@path);
- my $json = JSON->new;
- $json->relaxed;
- return $json->decode(scalar(slurp($path)));
-}
-
# Internal helper routine to return the path of a file or directory from the
# package metadata directory. The resulting file or directory path is not
# checked for existence.
@@ -209,11 +165,11 @@ sub config {
# Expand the package license into license text.
my $license = $data_ref->{license};
- my $licenses_ref = $self->_load_appdata_json('licenses.json');
+ my $licenses_ref = $self->load_appdata_json('licenses.json');
if (!exists($licenses_ref->{$license})) {
die "Unknown license $license\n";
}
- my $license_text = slurp($self->_appdata_path('licenses', $license));
+ my $license_text = slurp($self->appdata_path('licenses', $license));
$data_ref->{license} = { $licenses_ref->{$license}->%* };
$data_ref->{license}{full} = $license_text;
@@ -258,7 +214,6 @@ App::DocKnot::Config - Read and return DocKnot package configuration
=head1 SYNOPSIS
- use App::DocKnot;
use App::DocKnot::Config;
my $reader = App::DocKnot::Config->new({ metadata => 'docs/metadata' });
my $config = $reader->config();
diff --git a/lib/App/DocKnot/Generate.pm b/lib/App/DocKnot/Generate.pm
index 0eca5ef..de8426b 100644
--- a/lib/App/DocKnot/Generate.pm
+++ b/lib/App/DocKnot/Generate.pm
@@ -14,12 +14,11 @@ package App::DocKnot::Generate 2.00;
use 5.024;
use autodie;
+use parent qw(App::DocKnot);
use warnings;
use App::DocKnot::Config;
use Carp qw(croak);
-use File::BaseDir qw(config_files);
-use File::ShareDir qw(module_file);
use Template;
use Text::Wrap qw(wrap);
@@ -304,32 +303,6 @@ sub _code_for_to_thread {
# Helper methods
##############################################################################
-# Internal helper routine to return the path of a file from the application
-# data. These data files are installed with App::DocKnot, but each file can
-# be overridden by the user via files in $HOME/.config/docknot or
-# /etc/xdg/docknot (or whatever $XDG_CONFIG_DIRS is set to).
-#
-# We therefore try File::BaseDir first (which handles the XDG paths) and fall
-# back on using File::ShareDir to locate the data.
-#
-# $self - The App::DocKnot::Generate object
-# @path - The relative path of the file as a list of components
-#
-# Returns: The absolute path to the application data
-# Throws: Text exception on failure to locate the desired file
-sub _appdata_path {
- my ($self, @path) = @_;
-
- # Try XDG paths first.
- my $path = config_files('docknot', @path);
-
- # If that doesn't work, use the data that came with the module.
- if (!defined($path)) {
- $path = module_file('App::DocKnot', File::Spec->catfile(@path));
- }
- return $path;
-}
-
# 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.
#
@@ -496,7 +469,7 @@ sub generate {
$vars{to_thread} = $self->_code_for_to_thread;
# Ensure we were given a valid template.
- $template = $self->_appdata_path('templates', "${template}.tmpl");
+ $template = $self->appdata_path('templates', "${template}.tmpl");
# Run Template Toolkit processing.
my $tt = Template->new({ ABSOLUTE => 1 }) or croak(Template->error());
@@ -568,7 +541,6 @@ App::DocKnot::Generate - Generate documentation from package metadata
=head1 SYNOPSIS
- use App::DocKnot;
use App::DocKnot::Generate;
my $docknot = App::DocKnot::Generate->new({ metadata => 'docs/metadata' });
my $readme = $docknot->generate('readme');
diff --git a/t/cli/errors.t b/t/cli/errors.t
index bd27bfd..fc0561e 100755
--- a/t/cli/errors.t
+++ b/t/cli/errors.t
@@ -2,7 +2,7 @@
#
# Tests for the App::DocKnot command dispatch error handling.
#
-# Copyright 2018 Russ Allbery <rra@cpan.org>
+# Copyright 2018-2019 Russ Allbery <rra@cpan.org>
#
# SPDX-License-Identifier: MIT
@@ -13,7 +13,7 @@ use warnings;
use Test::More tests => 10;
# Load the module.
-BEGIN { use_ok('App::DocKnot') }
+BEGIN { use_ok('App::DocKnot::Command') }
# Check an error against the expected message, removing the trailing newline
# and stripping off the leading $0 that's prepended and the colon and space
@@ -33,8 +33,8 @@ sub is_error {
}
# Create the command-line parser.
-my $docknot = App::DocKnot->new();
-isa_ok($docknot, 'App::DocKnot');
+my $docknot = App::DocKnot::Command->new();
+isa_ok($docknot, 'App::DocKnot::Command');
# Test various errors.
eval { $docknot->run('foo') };
diff --git a/t/cli/generate.t b/t/cli/generate.t
index 21ab2f0..05e096d 100755
--- a/t/cli/generate.t
+++ b/t/cli/generate.t
@@ -2,7 +2,7 @@
#
# Tests for the App::DocKnot command dispatch for generate.
#
-# Copyright 2018 Russ Allbery <rra@cpan.org>
+# Copyright 2018-2019 Russ Allbery <rra@cpan.org>
#
# SPDX-License-Identifier: MIT
@@ -21,11 +21,11 @@ use Test::RRA qw(is_file_contents);
use Test::More tests => 7;
# Load the module.
-BEGIN { use_ok('App::DocKnot') }
+BEGIN { use_ok('App::DocKnot::Command') }
# Create the command-line parser.
-my $docknot = App::DocKnot->new();
-isa_ok($docknot, 'App::DocKnot');
+my $docknot = App::DocKnot::Command->new();
+isa_ok($docknot, 'App::DocKnot::Command');
# Generate the package README file to a temporary file, read it into memory,
# and compare it to the actual README file. This duplicates part of the
diff --git a/t/config/basic.t b/t/config/basic.t
index 1dd9409..6b719bb 100644
--- a/t/config/basic.t
+++ b/t/config/basic.t
@@ -15,13 +15,10 @@ use File::Spec;
use JSON;
use Perl6::Slurp;
-use Test::More tests => 8;
+use Test::More tests => 7;
# Load the modules.
-BEGIN {
- use_ok('App::DocKnot');
- use_ok('App::DocKnot::Config');
-}
+BEGIN { use_ok('App::DocKnot::Config') }
# Load a test configuration and check a few inobvious pieces of it.
my $metadata_path = File::Spec->catfile('t', 'data', 'ansicolor', 'metadata');
diff --git a/t/generate/basic.t b/t/generate/basic.t
index c5ead59..52f5c83 100755
--- a/t/generate/basic.t
+++ b/t/generate/basic.t
@@ -2,7 +2,7 @@
#
# Tests for the App::DocKnot::Generate module API.
#
-# Copyright 2013, 2016-2018 Russ Allbery <rra@cpan.org>
+# Copyright 2013, 2016-2019 Russ Allbery <rra@cpan.org>
#
# SPDX-License-Identifier: MIT
@@ -18,10 +18,7 @@ use Test::RRA qw(is_file_contents);
use Test::More;
# Load the modules.
-BEGIN {
- use_ok('App::DocKnot');
- use_ok('App::DocKnot::Generate');
-}
+BEGIN { use_ok('App::DocKnot::Generate') }
# We have a set of test cases in the data directory. Each of them contains
# metadata and output directories.
@@ -48,4 +45,4 @@ for my $test (@tests) {
}
# Check that we ran the correct number of tests.
-done_testing(2 + scalar(@tests) * 4);
+done_testing(1 + scalar(@tests) * 4);
diff --git a/t/generate/output.t b/t/generate/output.t
index 8478cf1..11e8e3d 100755
--- a/t/generate/output.t
+++ b/t/generate/output.t
@@ -3,7 +3,7 @@
# Test the generate_output method. This doubles as a test for whether the
# package metadata is consistent with the files currently in the distribution.
#
-# Copyright 2016, 2018 Russ Allbery <rra@cpan.org>
+# Copyright 2016, 2018-2019 Russ Allbery <rra@cpan.org>
#
# SPDX-License-Identifier: MIT
@@ -19,13 +19,10 @@ use File::Temp;
use Perl6::Slurp;
use Test::RRA qw(is_file_contents);
-use Test::More tests => 8;
+use Test::More tests => 7;
# Load the module.
-BEGIN {
- use_ok('App::DocKnot');
- use_ok('App::DocKnot::Generate');
-}
+BEGIN { use_ok('App::DocKnot::Generate') }
# Initialize the App::DocKnot object using the default metadata path.
my $metadata_path = File::Spec->catfile(getcwd(), 'docs', 'metadata');
diff --git a/t/generate/self.t b/t/generate/self.t
index b4ac0ea..352d0c2 100755
--- a/t/generate/self.t
+++ b/t/generate/self.t
@@ -2,7 +2,7 @@
#
# Test generated files against the files included in the package.
#
-# Copyright 2016, 2018 Russ Allbery <rra@cpan.org>
+# Copyright 2016, 2018-2019 Russ Allbery <rra@cpan.org>
#
# SPDX-License-Identifier: MIT
@@ -15,13 +15,10 @@ use lib 't/lib';
use File::Spec;
use Test::RRA qw(is_file_contents);
-use Test::More tests => 6;
+use Test::More tests => 5;
# Load the module.
-BEGIN {
- use_ok('App::DocKnot');
- use_ok('App::DocKnot::Generate');
-}
+BEGIN { use_ok('App::DocKnot::Generate') }
# Initialize the App::DocKnot object using the default metadata path.
my $docknot = App::DocKnot::Generate->new();