NAME

App::Actions::Access - list the private routines of a package for Sub::Private

SYNOPSIS

package App::Actions::Example;

use Sub::Private ();
use Sub::Protected ();
use App::Actions::Access;

sub run { my $self = shift; return $self->_step }
sub _step { ... }
sub _runner { ... }    # protected: subclasses may call it

# At the end of the file, once every routine above is compiled.
BEGIN {
	Sub::Protected->import('_runner');
	local $Sub::Private::config{mode} = 'enforce';
	Sub::Private->import(@{ App::Actions::Access->private_names(__PACKAGE__, '_runner') });
}

See which routines would be made private:

use App::Actions::Access;
use App::Actions::Runner;

my $names = App::Actions::Access->private_names('App::Actions::Runner');
print join("\n", @{$names}), "\n";

Leave some names out:

my $names = App::Actions::Access->private_names(__PACKAGE__, '_runner', '_docker');

DESCRIPTION

In Perl, a routine whose name starts with _ is private by convention only: any code can still call it. Sub::Private makes the rule real. In enforce mode, a call to a private routine from outside its own package dies.

There is a catch. Sub::Private's mode is one setting for the whole program. If one module set it to enforce for good, every module loaded after it would be switched too, even modules that did not ask for it.

So this distribution never changes the setting for good. Each module, at the end of its file, sets the mode with local for just one call to Sub::Private, and gives that call the list of its private routines. This module makes that list. Because the list is made from the code, a private routine added later is protected automatically; nobody has to remember to add it to a list.

METHODS

private_names

Lists the routines of a package whose names start with an underscore, which the package itself defined (not ones it imported), less the ones named as exceptions.

ARGUMENTS

  • $package - the package whose routines are listed.

  • @except - optional; names to leave out, such as the protected routines a subclass may call.

RETURNS

A sorted array reference of names.

SIDE EFFECTS

None.

EXAMPLE

my $names = App::Actions::Access->private_names('App::Actions::Runner');
print "@$names\n";    # _build_base_env _check_environment ...

API SPECIFICATION

INPUT

{
	package => { type => 'string', matches => qr/\A\w+(?:::\w+)*\z/ },
	except  => { type => 'arrayref', optional => 1 },
}

OUTPUT

{ type => 'arrayref' }

MESSAGES

+---------------------------+------------------------------+-----------------------------+
| Message                   | Meaning                      | Resolution                  |
+---------------------------+------------------------------+-----------------------------+
| A package name is required| No package, or not a name    | Pass __PACKAGE__            |
+---------------------------+------------------------------+-----------------------------+

LIMITATIONS

  • The listing has to run after the routines are compiled, which is why the BEGIN block goes at the end of the file. A routine compiled after it (by a string eval, say) is not protected.

  • Privacy is checked on every call, by walking caller, so a private routine costs more to call than a public one. None of this distribution's private routines is called in a tight loop.

  • The check is bypassed under a test harness (HARNESS_ACTIVE), which is how the white-box tests reach the private routines. Run a test file without the harness to see the checks in force.

  • A private routine handed to another package as a code reference is checked when that package calls it, and fails. Hand over a closure that calls it instead.

COMMON PITFALLS

  • Call it at the end of the file. It sees only the routines that are already compiled. Put the BEGIN block after the last sub.

  • Always use local for the mode. Write local $Sub::Private::config{mode} = 'enforce'; inside the BEGIN block. Without local, every module loaded later is enforced too.

  • Leave out protected routines. A routine that a subclass may call must be passed in @except (and given to Sub::Protected), or the subclass's calls will die.

  • Names starting with two underscores are not listed. __foo does not match; only _ followed by a letter or digit does.

  • Imported routines are not listed. A routine imported from another module belongs to that module.

  • Tests run with the checks off. Under prove, HARNESS_ACTIVE is set and the checks are bypassed. Run perl -Ilib t/file.t (without prove) to test with them on.

ENCODING

Package and routine names must be ASCII word characters (letters, digits and _, separated by ::). A package name with other characters is refused with A package name is required.

AUTHOR

Nigel Horne, njh@nigelhorne.com

FORMAL SPECIFICATION

This section describes each routine in the Z notation, a precise mathematical language for describing programs. You do not need to read it to use the module: everything here is also said in plain words in the sections above. It is for readers who want to check exactly what a routine promises.

How to read it: a box names a routine. The lines above the middle rule declare the values it works with. A name ending in ? is an input, and a name ending in ! is an output. ΞState means the routine changes nothing, and ΔState means it can change the state. The lines below the rule are the conditions that are always true when the routine returns.

private_names

┌─ PrivateNames ────────────────────────────────────
│ package? : PACKAGE
│ except?  : ℙ NAME
│ names!   : seq NAME
├───────────────────────────────────────────────────
│ ran names! = { n : dom stash(package?) |
│     n matches /\A_[A-Za-z0-9]\w*\z/ ∧ defined(code(package?, n)) ∧
│     owner(code(package?, n)) = package? } ∖ except?
│ names! is sorted
└───────────────────────────────────────────────────

STATE DIAGRAM

The module keeps no state. This is how one package's private routines become protected while the file is loaded:

 perl loads Package.pm
          |
          v
+--------------------+  each "sub _name { }" is compiled; at this
| COMPILING ROUTINES |  point any code may call _name
+--------------------+
          |
          | final BEGIN block runs
          v
+--------------------+  private_names(__PACKAGE__, @except):
| LISTING            |  own routines matching _[A-Za-z0-9]\w*,
+--------------------+  minus @except, sorted
          |
          | local mode = 'enforce'; Sub::Private->import(@names)
          v
+--------------------+  calls from outside the package die;
| ENFORCED           |  calls from inside work;
+--------------------+  protected names: Sub::Protected rules
          |
          | BEGIN block ends: the "local" mode is restored,
          v so later packages are not affected
+--------------------+
| OTHER PACKAGES     |  unchanged
| LOAD AS BEFORE     |
+--------------------+

Under a test harness (HARNESS_ACTIVE) the ENFORCED checks are bypassed.

LICENSE AND COPYRIGHT

Copyright 2026 Nigel Horne.

This program is released under the GNU General Public License, version 2. See the LICENSE file that comes with this distribution. If you use it, please let me know.