package Pod::Readme; =head1 NAME Pod::Readme - Intelligently generate a README file from POD =for readme plugin version =head1 SYNOPSIS In a module's POD: =head1 NAME MyApp - my nifty app =for readme plugin version =head1 DESCRIPTION This is a nifty app. =begin :readme =for readme plugin requires =head1 INSTALLATION ... =end :readme =for readme stop =head1 METHODS ... Then from the command-line: pod2readme lib/MyModule.pm README =for readme stop From within Perl: use Pod::Readme; my $prf = Pod::Readme->new( input_file => 'lib/MyModule.pm', translate_to_file => $dest, translation_class => 'Pod::Simple::Text', ); $prf->run(); =for readme start =head1 DESCRIPTION This module filters POD to generate a F file, by using POD commands to specify which parts are included or excluded from the F file. =begin :readme See the L documentation for more details on the POD syntax that this module recognizes. See L for command-line usage. =head1 INSTALLATION See L. =for readme plugin requires heading-level=2 title="Required Modules" =for readme plugin changes =end :readme =for readme stop =head1 POD COMMANDS =head2 C<=for readme stop> Stop including the POD that follows in the F. =head2 C<=for readme start> =head2 C<=for readme continue> Start (or continue to) include the POD that follows in the F. Note that the C command was added as a synonym in version 1.0.0. =head2 C<=for readme include> =for readme include file="INSTALL" type="text" Include a text or POD file in the F. It accepts the following options: =over =item C Required. This is the file name to include. =item C Can be "text" or "pod" (default). =item C An optional regex of where to start including the file. =item C An optional regex of where to stop including the file. =back =head2 C<=for readme plugin> Loads a plugin, e.g. =for readme plugin version Note that specific plugins may add options, e.g. =for readme plugin changes title='CHANGES' See L for more information. Note that the C command was added in version 1.0.0. =head2 C<=begin :readme> =head2 C<=end :readme> Specify a block of POD to include only in the F. You can also specify a block in another format: =begin readme text ... =end readme text This will be translated into =begin text ... =end text and will only be included in F files of that format. Note: earlier versions of this module suggested using =begin readme ... =end readme While this version supports that syntax for backwards compatibility, it is not standard POD. =cut use v5.10.1; use Moo; extends 'Pod::Readme::Filter'; our $VERSION = 'v1.2.3'; use Carp; use IO qw/ File Handle /; use List::Util 1.33 qw/ any /; use Module::Load qw/ load /; use Path::Tiny qw/ path tempfile /; use Pod::Simple; use Types::Standard qw/ Bool Maybe Str /; use Pod::Readme::Types qw/ File WriteIO /; # RECOMMEND PREREQ: Pod::Man # RECOMMEND PREREQ: Pod::Markdown # RECOMMEND PREREQ: Pod::Markdown::Github # RECOMMEND PREREQ: Pod::Simple::HTML # RECOMMEND PREREQ: Pod::Simple::LaTeX # RECOMMEND PREREQ: Pod::Simple::RTF # RECOMMEND PREREQ: Pod::Simple::Text # RECOMMEND PREREQ: Pod::Simple::XHTML =head1 ATTRIBUTES This module extends L with the following attributes: =head2 C The class used to translate the filtered POD into another format, e.g. L. If it is C, then there is no translation. Only subclasses of L are supported. =cut has translation_class => ( is => 'ro', isa => Maybe [Str], default => undef, ); =head2 C The L to save the translated file to. =cut has translate_to_fh => ( is => 'ro', isa => WriteIO, lazy => 1, builder => '_build_translate_to_fh', coerce => sub { WriteIO->coerce(@_) }, ); sub _build_translate_to_fh { my ($self) = @_; if ( $self->translate_to_file ) { $self->translate_to_file->openw; } else { my $fh = IO::Handle->new; if ( $fh->fdopen( fileno(STDOUT), 'w' ) ) { return $fh; } else { croak "Cannot get a filehandle for STDOUT"; } } } =head2 C The L filename to save the translated file to. If omitted, then it will be saved to C. =cut has translate_to_file => ( is => 'ro', isa => File, coerce => sub { File->coerce(@_) }, lazy => 1, builder => 'default_readme_file', ); =head2 C The L C will default to a temporary file. =cut has '+output_file' => ( lazy => 1, default => sub { tempfile( SUFFIX => '.pod', UNLINK => 1 ); }, ); around '_build_output_fh' => sub { my ( $orig, $self ) = @_; if ( defined $self->translation_class ) { $self->$orig(); } else { $self->translate_to_fh; } }; =head2 C For a new F to be generated, even if the dependencies have not been updated. See L. =cut has 'force' => ( is => 'ro', isa => Bool, default => 0, ); =head2 C For use with L plugins. This allows plugins which normally depend on files in the distribution to use metadata from here instead. =cut =head1 METHODS This module extends L with the following methods: =head2 C The default name of the F file, which depends on the L. =cut sub default_readme_file { my ($self) = @_; my $name = uc( $self->target ); state $extensions = { 'Pod::Man' => '.1', 'Pod::Markdown' => '.md', 'Pod::Simple::HTML' => '.html', 'Pod::Simple::LaTeX' => '.tex', 'Pod::Simple::RTF' => '.rtf', 'Pod::Simple::Text' => '', 'Pod::Simple::XHTML' => '.xhtml', }; my $class = $self->translation_class; if ( defined $class ) { if ( my $ext = $extensions->{$class} ) { $name .= $ext; } } else { $name .= '.pod'; } path( $self->base_dir, $name ); } =head2 C This method runs translates the resulting POD from C. =cut sub translate_file { my ($self) = @_; if ( my $class = $self->translation_class ) { load $class; my $converter = $class->new() or croak "Cannot instantiate a ${class} object"; if ( $converter->isa('Pod::Simple') ) { my $tmp_file = $self->output_file->stringify; close $self->output_fh or croak "Unable to close file ${tmp_file}"; $converter->output_fh( $self->translate_to_fh ); $converter->parse_file($tmp_file); } else { croak "Don't know how to translate POD using ${class}"; } } } =head2 C Used to determine when the dependencies have been updated, and a translation can be run. Note that this only returns a meaningful value after the POD has been processed, since plugins may add to the dependencies. A side-effect of this is that when generating a POD formatted F is that it will always be updated, even when L is false. =cut sub dependencies_updated { my ($self) = @_; my $dest = $self->translate_to_file; if ( $dest and $self->input_file) { return 1 unless -e $dest; my $stat = $dest->stat; return 1 unless $stat; my $time = $stat->mtime; return any { $_->mtime > $time } ( map { $_->stat } $self->depends_on ); } else { return 1; } } =head2 C This method runs C and then L. =cut around 'run' => sub { my ( $orig, $self ) = @_; $self->$orig(); if ( $self->force or $self->dependencies_updated ) { $self->translate_file(); } }; =head2 C my $parser = Pod::Readme->new(); $parser->parse_from_file( 'README.pod', 'README' ); Pod::Readme->parse_from_file( 'README.pod', 'README' ); This is a class method that acts as a L compatibility shim for software that is designed for versions of L prior to v1.0. Its use is deprecated, and will be deleted in later versions. =cut sub parse_from_file { my ( $self, $source, $dest ) = @_; my $class = ref($self) || __PACKAGE__; my $prf = $class->new( input_file => $source, translate_to_file => $dest, translation_class => 'Pod::Simple::Text', force => 1, ); $prf->run(); } =head2 C Like L, this exists as a compatibility shim. Its use is deprecated, and will be deleted in later versions. =cut sub parse_from_filehandle { my ( $self, $source_fh, $dest_fh ) = @_; my $class = ref($self) || __PACKAGE__; my $src_io = IO::Handle->new_from_fd( ( defined $source_fh ) ? fileno($source_fh) : 0, 'r' ); my $dest_io = IO::Handle->new_from_fd( ( defined $dest_fh ) ? fileno($dest_fh) : 1, 'w' ); my $prf = $class->new( input_fh => $src_io, translate_to_fh => $dest_io, translation_class => 'Pod::Simple::Text', force => 1, ); $prf->run(); } use namespace::autoclean; 1; =for readme start =head1 CAVEATS This module is intended to be used by module authors for their own modules. It is not recommended for generating F files from arbitrary Perl modules from untrusted sources. =head1 SEE ALSO See L, L and L. =head1 AUTHORS The original version was by Robert Rothenberg until 2010, when maintenance was taken over by David Precious . In 2014, Robert Rothenberg rewrote the module to use filtering instead of subclassing a POD parser. =head2 Acknowledgements Thanks to people who gave feedback and suggestions to posts about the rewrite of this module on L. =head2 Suggestions, Bug Reporting and Contributing This module is developed on GitHub at L =head1 LICENSE Copyright (c) 2005-2014 Robert Rothenberg. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut