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
BEGINblock goes at the end of the file. A routine compiled after it (by a stringeval, 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
BEGINblock after the lastsub.Always use
localfor the mode. Writelocal $Sub::Private::config{mode} = 'enforce';inside theBEGINblock. Withoutlocal, 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.
__foodoes 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_ACTIVEis set and the checks are bypassed. Runperl -Ilib t/file.t(withoutprove) 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.