http://qs321.pair.com?node_id=11127801

GrandFather has asked for the wisdom of the Perl Monks concerning the following question:

I have a module file that includes packages for helper classes. In outline the file looks something like:

use strict; use warnings; package Segment; sub new { ...; } sub Describe { ...; } package ELF::File; sub new { ...; } sub Describe { ...; } =pod =head1 NAME ELF::File =head1 VERSION ... =head1 USAGE =item C<new(%params)> C<new(...)> creates a new ELF::File object and parses the Program Head +er table and the Section Header table. =item C<Describe()> C<Describe()> returns a multi-line string describing the ELF file head +er contents. ... =cut

Pod::Coverage is happy enough with the ELF::File package, but doesn't see the Segment package. Is there a way to convince Pod::Coverage to notice extra packages in a module and process them? I could move the helpers out into other module files, but that gets pretty silly for some of the helper classes. ELF::File returns helper class objects for some purposes so it is important that their public methods are documented.

Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

Replies are listed 'Best First'.
Re: Pod::Coverage for helper classes
by kcott (Archbishop) on Feb 02, 2021 at 15:14 UTC

    G'day GrandFather,

    I ran quite a few tests. I had problems reproducing your results. I initially assumed, based on the order of package statements, that your filename was based on the first package name (e.g. .../Segment.pm); if I used the second package name (e.g. .../ELF/File.pm), I got the results you indicated.

    You didn't say how you were using Pod::Coverage. For my tests, I used

    $ perl -I. -MPod::Coverage -e 'my $pc = Pod::Coverage::->new(package = +> "XYZ"); print $pc->coverage ? "Y" : "N"'

    where XYZ was one of ten different package names spread across five *.pm modules. Each module had two packages. I'm also using the latest (0.23) version of Pod::Coverage.

    Here's a quick rundown of what I found:

    • =head1 NAME ... had no bearing on the results. It could be present, absent, or even use a totally bogus name.
    • The order of package statements had no bearing on the results.
    • The use of package statements with or without blocks had no bearing on the results.
    • POD was only considered to be related to the module filename (cf. first paragraph above).
    • POD did not need to be part of a package block to be considered as covering code in that package.

    I considered all tests and related code to be too much to post here; however, this last set covers most of the points I mentioned.

    $ cat X5.pm package Y5 { sub fred { 2 } =head1 NAME Y5 =head2 fred fred() returns 2 =cut }; 1; package X5 { sub fred { 1 } }; 1; $ perl -I. -MPod::Coverage -e 'my $pc = Pod::Coverage::->new(package = +> "X5"); print $pc->coverage ? "Y" : "N"' Y $ perl -I. -MPod::Coverage -e 'my $pc = Pod::Coverage::->new(package = +> "Y5"); print $pc->coverage ? "Y" : "N"' N

    If your "helper classes" are acting in a similar way to _helper() private functions, I would consider POD to be an inappropriate vehicle for documenting them (in much the same was as you wouldn't have POD with =item C<< _helper() >>). If these classes are intended to be public, I would suggest putting them in separate modules with their own specific POD.

    "I could move the helpers out into other module files, but that gets pretty silly for some of the helper classes."

    It may be that some classes get their own modules with POD but others use embedded package statements but have no POD.

    "ELF::File returns helper class objects for some purposes so it is important that their public methods are documented."

    So that would be an example of the former category: separate module with its own POD.

    — Ken

Re: Pod::Coverage for helper classes
by Discipulus (Canon) on Feb 02, 2021 at 07:48 UTC
    Hello GrandFather,

    not a solution but maybe something helpfull. You know I'm not an expert, just writing my first serious modules, but why your other classes are not in their own files? The assumption My::Module lives in @INC/My/Module.pm is a convention so widely used that at the end is a rule and a limitation. It seems Pod::Coverage too assumes this design. ( PS if your modules works fine why to care about Pod::Coverage ? ;)

    That said I looked at source and pod_from comes from Pod::Find that describe itself with: NOTE: This module is considered legacy;

    You can use sub Pod::Coverage::TRACE_ALL] () { 1 } to show where Pod::Coverage looks and, yes, I hacked it to force looking in the current file with pod_from => 'podcovtest.pm' but it fails requiring the package: requiring 'Segment' require failed with Can't locate Segment.pm in @INC

    A semplified example (you forgot to return true ;) inside the file podcovtest.pm

    use strict; use warnings; package Segment; sub new { 1 } sub Describe { 1 } package podcovtest; sub new { 1 } sub Describe { 1 } sub Pod::Coverage::TRACE_ALL () { 1 } use Pod::Coverage; foreach my $pkg ( qw( Segment podcovtest ) ){ my $pc = Pod::Coverage->new(package => $pkg, pod_from => 'podc +ovtest.pm'); print qq(OK $pkg\n) if $pc->coverage == 1; } 1; =pod =head1 NAME Segment =head2 Describe =head1 NAME podcovtest =head2 new =head2 Describe =head1 VERSION =head1 USAGE =item C<new(%params)> C<new(...)> creates a new ELF::File object and parses the Program Head +er table and the Section Header table. =item C<Describe()> C<Describe()> returns a multi-line string describing the ELF file head +er contents. =cut

    and its output:

    >perl -I . -Mpodcovtest -e 1 getting pod location for 'Segment' parsing 'podcovtest.pm' requiring 'Segment' require failed with Can't locate Segment.pm in @INC (you may need to i +nstall the Segment module) (@INC contains: . C:/EX_D/ulisseDUE/perl5. +26.64bit/perl/site/lib/MSWin32-x64-multi-thread C:/EX_D/ulisseDUE/per +l5.26.64bit/perl/site/lib C:/EX_D/ulisseDUE/perl5.26.64bit/perl/vendo +r/lib C:/EX_D/ulisseDUE/perl5.26.64bit/perl/lib) at (eval 8) line 1. Use of uninitialized value in numeric eq (==) at podcovtest.pm line 19 +. getting pod location for 'podcovtest' parsing 'podcovtest.pm' requiring 'podcovtest' walking symbols tying shoelaces $VAR1 = bless( { 'package' => 'podcovtest', 'nonwhitespace' => undef, 'private' => [ qr/^_/, qr/^(un)?import$/, qr/^DESTROY$/, qr/^AUTOLOAD$/, qr/^bootstrap$/, qr/^\(/, qr/^(TIE( SCALAR | ARRAY | HASH | HAND +LE ) | FETCH | STORE | UNTIE | FETCHSIZE | STORESIZE | POP | PUSH | SHIFT | UNSHIFT | SPLICE | DELETE | EXISTS | EXTEND | CLEAR | FIRSTKEY | NEXTKEY | PRINT | PR +INTF | WRITE | READLINE | GETC | READ | CLOSE | BINMODE | OPEN | EOF | FILENO | SEEK | TELL | SCALAR )$/x, qr/^( MODIFY | FETCH )_( REF | SCALAR +| ARRAY | HASH | CODE | GLOB | FORMAT | IO)_ATTRIBUTES $/x, qr/^CLONE(_SKIP)?$/ ], 'trustme' => [], 'pod_from' => 'podcovtest.pm', 'symbols' => { 'new' => 1, 'Describe' => 1 } }, 'Pod::Coverage' ); OK podcovtest

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
      "why your other classes are not in their own files?"

      From my OP: "I could move the helpers out into other module files, but that gets pretty silly for some of the helper classes.". Some of the helper classes are very small and it is something of a pain to spin up a new module just to provide three methods in a helper class where each method is only a few lines long. It is also a pain during development to flip back and forth between different files as I evolve the design. None of these are show stopper reasons, but they add up to "I'd rather have the helpers in the main module file.".

      "PS if your modules works fine why to care about Pod::Coverage"

      My module doesn't yet "work fine" - I'm still writing it and I like to check code and documentation coverage as I write the code and even write tests for code I haven't written yet in some cases. Even if this were a complete working module, I'd still be concerned that tests, code coverage and documentation coverage were all up to snuff so that I can maintain the module with confidence.

      "you forgot to return true ;"

      From OP: "In outline the file looks something like"

      You may be interested to note that '...' is the yada yada statement. It is useful as a place holder for code yet to be written. It compiles correctly, but generates an error if executed. Kinda useful in code that isn't fleshed out yet. You can write a place holder sub and write tests against it which will fail until the ... is replaced by real code.

      Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: Pod::Coverage for helper classes
by GrandFather (Saint) on Feb 03, 2021 at 09:47 UTC

    Good and helpful discussion everyone. I've gone with the flow and refactored the public facing helper classes out into their own modules. I'd rather the documentation for ELF::File were more of a one stop shop, but I guess good example code in the main module docs will help reduce the need to visit the docs for the helper classes. Or maybe I just roll the helper class docs into the main module docs?

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: Pod::Coverage for helper classes
by bliako (Monsignor) on Feb 02, 2021 at 10:44 UTC

    Could multiple packages in the same file be confusing to Pod::Coverage? use takes as parameter a Package name and then it infers the filename it should be living in and attempts to open it through the INC. Essentially, use needs the package name to be living in a similarly named file. So, in your case doing use Segment; will read Segment.pm and load all there is in it. Fine. But will you be able to access/refer-to the other packages?

    When said file contains two or more packages how can you access (e.g. File::ELF::Describe()) any of them except the one you named in the use? Of course you can resort to all sorts of hacks perhaps through require or dynamically injecting into the %%%%%%%INC. But Pod::Coverage may not be so hacky, especially if it does its enquiries not by parsing module files but by use'ing them (my guess).

    That said, multiple-packages-in-one-file works fine if you want to use them within the same file, as kind of private modules. But to keep my mind peaceful I am always enclosing each package in its own block.

    bw, bliako

Re: Pod::Coverage for helper classes
by jcb (Parson) on Feb 03, 2021 at 02:35 UTC
    ELF::File returns helper class objects for some purposes so it is important that their public methods are documented.

    If it needs to be documented, it needs to be in a separate file so the documentation tools can find it. The ELF::File POD should name the class of the helper object returned, and then that class should be in its own file. TkPod and perldoc will not be able to find that documentation if it is bundled into ELF/File.pm.

    The only case where I embed helper classes in the same file is where the helpers are tightly bound to the main class implementation. I did that with the tied array and tied hash helper classes in WARC::Fields, since they actually are dependent on their parent objects. The documentation for the WARC package has a page for it. In this case, the helper classes are not documented at all, but instead their public methods are documented along with the interface that uses them. (I need to get back on that project; I last hit a wall working on the archive-writing code... I wonder if it has simmered long enough on the proverbial back burner yet...)