App::Info(3pm)

πŸ“¦
version 0.57 Β· 2 versions
  • 0.57 Β· debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking (latest)
  • 0.57 Β· ubuntu jammy
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

✨ App::Info(3pm) ✨ β€” User Contributed Perl Documentation

okay bestie, pull up a chair πŸͺ‘ because this is the page for <pink>App::Info</pink>, and it's a whole vibe. it's filed under User Contributed Perl Documentation, it lives at App::Info(3pm), and it's about to tell you everything you never knew you needed to know about sniffing out software packages on a system. let's go πŸ’…


🌸 NAME 🌸

App::Info β€” information about software packages on a system. that's it, that's the whole name section, she's concise πŸ‘‘


πŸ¦‹ SYNOPSIS πŸ¦‹

here's the whole flex in code form, no edits, just pure:

use App::Info::Category::FooApp;

my $app = App::Info::Category::FooApp->new;

if ($app->installed) {
    print "App name: ", $app->name, "\n";
    print "Version:  ", $app->version, "\n";
    print "Bin dir:  ", $app->bin_dir, "\n";
} else {
    print "App not installed on your system. :-(\n";
}

look at that little :-(\n at the end. the drama. the pathos. iconic 😭


🌈 DESCRIPTION 🌈

so <pink>App::Info</pink> is an abstract base class β€” meaning she's not out here doing the work herself, she's the blueprint, the blueprint bestie β€” designed to give you a generalized interface for subclasses that hand over meta data about software packages that are installed on a system. the whole point? these classes get used inside Perl application installers so the installer can figure out whether the software dependencies are actually fulfilled, and so it can grab all the meta data it needs about those packages. very "am i good? are my friends good? let's check" energy 🫢

she also provides an event model for handling events that App::Info subclasses trigger. those events come in four flavors: "info", "error", "unknown", and "confirm". and you can stack up multiple handlers to handle any or all of those event types, which means clients of App::Info get to handle events however they feel like it. flexible. unbothered. slay. and writing new event handlers? straight-forward, bestie. and triggering those events from your subclass? kept easy-to-use on purpose πŸ’–

a few sample subclasses ship with the distribution, but everybody else is invited to write their own and contribute them to the CPAN. contributors are welcome to extend their subclasses to serve up even more info relevant to whatever application they're providing data for β€” App::Info::HTTPD::Apache is the example given for that, go look at her β€” but at a minimum you're encouraged to implement the abstract methods defined here plus the ones in the category abstract base classes (like App::Info::HTTPD and App::Info::Lib). want the full tea? see the Subclassing section. 🌟


πŸ’œ INTERFACE πŸ’œ

this section is the public interface of App::Info, documented in full. no gatekeeping here ✨

πŸŽ€ Constructor πŸŽ€

new

my $app = App::Info::Category::FooApp->new(@params);

constructs an App::Info object and hands it right back to you. the @params arguments define attributes that help the App::Info object go rummaging around the file system for application info, and they also decide how the object will respond to certain events. the event parameters line up with their like-named methods β€” peek at the "Event Handler Object Methods" section for the whole story on events and handling them.

the search parameters you can pass to new() are:

  • search_exe_names β€” an array reference of possible names for binary executables. subclasses might use these to hunt down application programs they can run to pull application info out of, like version numbers. the subclasses generally provide reasonable defaults for most cases, so don't stress πŸ“
  • search_bin_dirs β€” an array reference of local directories to search for executables in. these may be used to search for the value of the "bin_dir" attribute, in addition to and in preference to the defaults each subclass uses.
  • search_lib_names β€” an array reference of possible names for library files. subclasses may use these to search for library files for the application. reasonable defaults for most cases, generally provided πŸ’–
  • search_so_lib_names β€” an array reference of possible names for shared object library files. subclasses may use these to search for shared object library files for the application. yes, also reasonable defaults for most cases.
  • search_lib_dirs β€” an array reference of local directories to search for libraries in. these may be used to search for the value of the "lib_dir" and "so_lib_dir" attributes, in addition to and in preference to the defaults each subclass uses.
  • search_inc_names β€” an array reference of possible names for include files. subclasses may use these to search for include files for the application. defaults: reasonable, most cases, you know the drill 🌸
  • search_inc_dirs β€” an array reference of local directories to search for include files in. these may be used to search for the value of the "inc_dir" attribute, in addition to and in preference to the defaults each subclass uses.

and the parameters to new() for the different types of App::Info events are:

  • on_info
  • on_error
  • on_unknown
  • on_confirm

when you're passing event handlers to new(), the list of handlers for each type needs to be an anonymous array, like this:

my $app = App::Info::Category::FooApp->new( on_info => \@handlers );

🧚 Meta Data Object Methods 🧚

these are abstract methods in App::Info, and they must be provided by its subclasses. they're the ones serving up the essential meta data of whatever software package the subclass supports. main character methods, honestly πŸ‘‘

  • key_name
my $key_name = $app->key_name;

returns a string that uniquely identifies the software the App::Info subclass provides data for. this value should be unique across all App::Info classes β€” typically it's just the name of the software. one of one, no duplicates πŸ’…

  • installed
if ($app->installed) {
    print "App is installed.\n"
} else {
    print "App is not installed.\n"
}

returns a true value if the application is installed, and a false value if it is not. the vibe check of methods πŸ¦‹

  • name
my $name = $app->name;

returns the name of the application.

  • version
my $version = $app->version;

returns the full version number of the application.

  • major_version
my $major_version = $app->major_version;

returns the major version number of the application. for example, if "version()" returns "7.1.2", then this method returns "7".

  • minor_version
my $minor_version = $app->minor_version;

returns the minor version number of the application. for example, if "version()" returns "7.1.2", then this method returns "1".

  • patch_version
my $patch_version = $app->patch_version;

returns the patch version number of the application. for example, if "version()" returns "7.1.2", then this method returns "2".

  • bin_dir
my $bin_dir = $app->bin_dir;

returns the full path the application's bin directory, if it exists.

  • executable
my $executable = $app->executable;

returns the full path the application's bin directory, if it exists.

  • inc_dir
my $inc_dir = $app->inc_dir;

returns the full path the application's include directory, if it exists.

  • lib_dir
my $lib_dir = $app->lib_dir;

returns the full path the application's lib directory, if it exists.

  • so_lib_dir
my $so_lib_dir = $app->so_lib_dir;

returns the full path the application's shared library directory, if it exists.

  • home_url
my $home_url = $app->home_url;

the URL for the software's home page. 🏠

  • download_url
my $download_url = $app->download_url;

the URL for the software's download page. go get it bestie ⬇️


πŸ“ Search Attributes πŸ“

these methods return lists of things to look for on the local file system when you're searching for application programs, library files, and include files. they're empty by default, since each subclass generally relies on its own settings β€” but you can add your own as preferred search parameters by passing them to the "new()" constructor. your search, your rules ✨

  • exe_names
my @search_exe_names = $app->search_exe_names;

returns a list of possible names for an executable. typically used by the "new()" constructor to search for an executable to execute and collect application info.

  • search_bin_dirs
my @search_bin_dirs = $app->search_bin_dirs;

returns a list of possible directories in which to search an executable. typically used by the "new()" constructor to find an executable to execute and collect application info. the found directory will also generally then be returned by the "bin_dir" method. cute little pipeline, love that for us πŸ’–

  • lib_names
my @search_lib_names = $app->search_lib_names;

returns a list of possible names for library files. typically used by the "lib_dir()" method to find library files.

  • so_lib_names
my @search_so_lib_names = $app->search_so_lib_names;

returns a list of possible names for library files. typically used by the "so_lib_dir()" method to find shared object library files.

  • search_lib_dirs
my @search_lib_dirs = $app->search_lib_dirs;

returns a list of possible directories in which to search for libraries. typically used by the "lib_dir()" and "so_lib_dir()" methods to find library files.

  • inc_names
my @search_inc_names = $app->search_inc_names;

returns a list of possible names for include files. typically used by the "inc_dir()" method to find include files.

  • search_inc_dirs
my @search_inc_dirs = $app->search_inc_dirs;

returns a list of possible directories in which to search for includes. typically used by the "inc_dir()" method to find include files.


πŸŽ€ Event Handler Object Methods πŸŽ€

these methods give you control over App::Info event handling. events can be handled by one or more objects from subclasses of App::Info::Handler. and here's the spicy rule: the first to return a true value will be the last to execute. that's what lets handlers get stacked, and it's what makes creating brand new handlers relatively easy. <purple>App::Info::Handler</purple> for the details on writing event handlers. (yes, the page really just says that, no "see", we're keeping it exactly as written, i don't make the rules 🫢)

each of the event handler methods takes a list of event handlers as its arguments. pass none? you get back the existing list of handlers for that event type. pass in new ones? those get returned. either way you're getting a list, so enjoy 🎁

the event handlers can be specified as one or more objects of the App::Info::Handler class or its subclasses, as one or more strings that tell App::Info to construct those handlers itself, or a combo of the two. but plot twist: strings only work if the relevant App::Info::Handler subclasses have registered strings with App::Info. for example, the App::Info::Handler::Print class included in the App::Info distribution registers the strings "stderr" and "stdout" when it starts up. so you can use those strings to tell App::Info to construct App::Info::Handler::Print objects that print to STDERR or to STDOUT, respectively. want to know which strings a handler registers? check the App::Info::Handler subclasses, that's where the receipts live 🧾

  • on_info
my @handlers = $app->on_info;
$app->on_info(@handlers);

info events get triggered when the App::Info subclass wants to send an informational status message. by default these events are ignored β€” but the super common need is for those messages to just print to STDOUT. so use the App::Info::Handler::Print class included with the App::Info distribution to have info messages print to STDOUT:

use App::Info::Handler::Print;
$app->on_info('stdout');
# Or:
my $stdout_handler = App::Info::Handler::Print->new('stdout');
$app->on_info($stdout_handler);

on_error

my @handlers = $app->on_error;
$app->on_error(@handlers);

Error events go off when your App::Info subclass bumps into something unexpected but not fatal. πŸ’… (Heads up bestie: if it's actually fatal, it'll probably just throw an exception instead, so this isn't your safety net for the big stuff.) By default these events are just ignored, like, completely. The classic glow-up move is to print them to STDERR, and yeah, you guessed it, that's the App::Info::Handler::Print class that ships with the App::Info distribution:

use App::Info::Handler::Print;
my $app->on_error('stderr');
# Or:
my $stderr_handler = App::Info::Handler::Print->new('stderr');
$app->on_error($stderr_handler);

Another vibe entirely: turn those events into fatal exceptions. For that, use the included App::Info::Handler::Carp class:

use App::Info::Handler::Carp;
my $app->on_error('croak');
# Or:
my $croaker = App::Info::Handler::Carp->new('croak');
$app->on_error($croaker);

on_unknown

my @handlers = $app->on_unknown;
$app->on_uknown(@handlers);

Unknown events fire when your App::Info subclass cannot find the value that a method call is supposed to return. Default behavior? Ignored, ignored, ignored πŸ™ˆ. A super common way to deal with them is to have the app just prompt the user for the data it needs. And surprise, the App::Info::Handler::Prompt class included with the App::Info distribution does exactly that:

use App::Info::Handler::Prompt;
my $app->on_unknown('prompt');
# Or:
my $prompter = App::Info::Handler::Prompt;
$app->on_unknown($prompter);

Peep App::Info::Handler::Prompt for the deets on how it actually works. πŸ‘‘


on_confirm

my @handlers = $app->on_confirm;
$app->on_confirm(@handlers);

Confirm events happen when your App::Info subclass has found an important piece of information β€” like the location of the executable it's gonna use to collect info for all its other methods β€” and wants to confirm that the info is correct. These usually pop off during the App::Info subclass object construction. And here too, App::Info::Handler::Prompt from the App::Info distribution is here to help a bestie out:

use App::Info::Handler::Prompt;
my $app->on_confirm('prompt');
# Or:
my $prompter = App::Info::Handler::Prompt;
$app->on_confirm($prompter);

✨ SUBCLASSING ✨

Okay so, real talk: as an abstract base class, App::Info is not meant to be used directly. Like, not even a little. 🚫 Instead you use concrete subclasses that implement the interface it defines. Each of those subclasses brings the meta data needed for one specific software package, delivered through the interface we just went over (plus any extra methods the class author thought made sense for their app).

This section is all about the facilities App::Info hands you for subclassing. The whole goal of the App::Info design was to make subclassing straight-forward β€” so devs can focus on gathering the data their app needs and minimize the work it takes to handle unknown values or confirm values. So basically there are three concepts you gotta understand when you subclass App::Info: organization, utility methods, and events. πŸ’…


πŸ—‚ Organization

The organizational idea here is to name subclasses by broad software categories. That way the categories themselves can function as abstract base classes that extend App::Info, so they get to specify more methods that all their base classes have to implement. Example: App::Info::HTTPD has specified the httpd_root() abstract method that its subclasses must implement. So when you're getting ready to write your own subclass, think about what category of software you're gathering info about. New categories can be added whenever they're needed. 🌈


🧰 Utility Methods

Once you've picked the right category, you can start building your App::Info concrete subclass. And while you do, please take advantage of App::Info::Util, where the author tried to encapsulate the common stuff so subclassing is easier. Turns out most of the repetitive work was looking for files and directories and searching through files. So App::Info::Util subclasses File::Spec to give you easy access to the commonly-used methods from that class, e.g., path(). On top of that it's got several methods of its own to help you find files and directories inside lists of files and directories, plus methods for searching through files and returning the values found in them. Peep App::Info::Util for more info, and check the App::Info subclasses in this distribution for usage examples. πŸ‘€

Also, big recommendation: use a package-scoped lexical App::Info::Util object. That way it's right there and handy whenever you need to do common tasks. And if you catch yourself doing something over and over that no App::Info::Util method covers yet, consider submitting a patch to App::Info::Util to add the functionality you need. Open source queen behavior fr fr. 🫢


πŸŽ‰ Events

Use the methods below to trigger events. Events exist to give App::Info subclass devs a simple way to send status messages and errors, confirm data values, and request a value when the class can't figure one out on its own. And optionally, module users can handle those events by assigning App::Info::Handler subclass objects to your App::Info subclass object using the event handling methods described in the "Event Handler Object Methods" section. πŸ’–


info

$self->info(@message);

Use this one to show status messages to the user. You might use it to tell them you're searching for a particular file, or trying to parse a file or some other resource for the data you need. A super common spot is the object constructor: generally, when an App::Info object gets created, some important initial piece of info is being hunted down, like an executable file. That file could be in one of many locations, so it just makes sense to let the user know you're out here looking for it:

$self->info("Searching for executable");

Now, because of how App::Info event handlers work, your informational message might be used or displayed any number of ways, or honestly not at all β€” that's the default behavior, bestie.

The @message will be joined into a single string and stored in the "message" attribute of the App::Info::Request object that gets passed to info event handlers. ✨


error

$self->error(@error);

Use this to let the user know something unexpected happened. Example: you invoke another program to parse its output, but its output isn't what you expected:

$self->error("Unable to parse version from `/bin/myapp -c`");

And like with all events, remember that error events might be handled in any number of ways β€” or not at all. πŸ’€

The @erorr will be joined into a single string and stored in the "message" attribute of the App::Info::Request object passed to error event handlers. If that's confusing, just think of it as an "error message" rather than an "error error." :-)


unknown

my $val = $self->unknown(@params);

Use this when a value is unknown. It gives the user the option β€” assuming the appropriate handler handles the event β€” to hand over the data you need. Whatever value gets entered will be returned by unknown(). The parameters go like this:

  • key β€” this one uniquely identifies the data point in your class, and App::Info uses it to make sure an unknown event is handled only once, no matter how many times the method gets called. The same value will be returned by subsequent calls to unknown() as was returned by the first call, and no handlers will be activated. Typical values are version and lib_dir.
  • prompt β€” the prompt to display if an event handler decides to prompt for the value. Something like "Path to your httpd executable?" is the vibe. If you don't provide this, App::Info builds one for you using your class' key_name() method and the key parameter β€” the result would be something like "Enter a valid FooApp version". The prompt value gets stored in the "message" attribute of the App::Info::Request object passed to event handlers.
  • callback β€” if a handler collected a value for your unknown data point, validating it might make sense. Like, if you prompt for a directory location and the user enters one, you probably wanna check that the directory actually exists. This parameter lets you do that. It's a code reference that takes the new value or values as its arguments, returning true if the value is valid and false if it isn't. For convenience, the first argument to the callback code reference is also stored in $_. That makes it easy to validate using functions or operators that, er, operate on $_ by default β€” while still letting you grab more info from @_ if you need it. For the directory example, a good callback would be sub { -d }. The callback code reference gets stored in the "callback" attribute of the App::Info::Request object passed to event handlers.
  • error β€” the error message to display if the callback code reference returns false. The event handler can then use it to let the user know what went wrong with the data she entered. For example, if the unknown value was a directory and the user entered something the callback flagged as invalid, a message like "Invalid directory path" would be the move. Note that if you don't provide error, App::Info supplies the generic message "Invalid value". This value gets stored in the "error" attribute of the App::Info::Request object passed to event handlers.

This might be the event method you use the most, because you should be calling it in every meta data method where you can't provide the data that method needs. It'll typically be the last part of the method. Here's an example showing off every single argument above:

my $dir = $self->unknown( key      => 'lib_dir',
                          prompt   => "Enter lib directory path",
                          callback => sub { -d },
                          error    => "Not a directory");

confirm

my $val = $self->confirm(@params);

...and this one is so unknown()'s cousin, but it's giving a whole different vibe. πŸ‘―β€β™€οΈ Use confirm() for those significant data points where you've actually found a value, but you wanna make absolutely sure it's the right one. A "significant data point" is basically a value your class needs to collect its meta data β€” like, say, you gotta locate an executable so you can then call it to grab everything else. πŸ”₯

Most of the time this only happens once per object β€” right during object construction β€” though there can be cases where you need it more than that. But hopefully, once you've confirmed in the constructor that you found what you needed, you can use that info to collect all the data your meta data methods want, and just assume it's all correct because that first, significant data point got confirmed. ✨

So other than where and how often you call confirm(), it works a lot like unknown(). Its parameters go like this: 🫢

  • key β€” Same as for unknown(): a string that uniquely IDs the data point in your class, and makes sure the event gets handled only once for a given key. Subsequent calls to confirm() for that same key will hand back the exact same value the first call returned. πŸ”‘
  • prompt β€” Same as for unknown() too. Even though confirm() is all about confirming a value, the prompt should typically still ask for the relevant value, just like with unknown(). The difference is that the handler should use the value parameter as the default if the user doesn't give you anything. This prompt gets stored in the message attribute of the App::Info::Request object that gets passed to event handlers. πŸ’¬
  • value β€” The value you're confirming. This is the one you found, and it gets offered to the user as the default option when they're prompted for a new value. It gets stored in the value attribute of the App::Info::Request object passed to event handlers. 🎁
  • callback β€” Same as for unknown(). Since the user can enter data to replace that default value you provided, you might wanna validate it β€” use this code reference for that. It gets stored in the callback attribute of the App::Info::Request object passed to event handlers. 🧠
  • error β€” Same as for unknown(): an error message to show in the event that a user-entered value doesn't pass the callback code reference's validation. It gets stored in the error attribute of the App::Info::Request object passed to event handlers. 🚨

Here's an example using all the arguments above, no cap:

my $exe = $self->confirm( key      => 'shell',
                          prompt   => 'Path to your shell?',
                          value    => '/bin/sh',
                          callback => sub { -x },
                          error    => 'Not an executable');

🌈 Event Examples 🌈

okay bestie, here's where I show you some examples of the event methods in action. These are meant to highlight the contexts where it's actually appropriate to use them. πŸ’…

Let's start with the simplest one first. Say that to find an app's version number, you need to search a file for the relevant data. Your App::Info concrete subclass might have a private method that does this work, and that method is the right place to use info() and, if you need it, error(). 🌸

sub _find_version {
    my $self = shift;

    # Try to find the revelant file. We cover this method below.
    # Just return if we cant' find it.
    my $file = $self->_find_file('version.conf') or return;

    # Send a status message.
    $self->info("Searching '$file' file for version");

    # Search the file. $util is an App::Info::Util object.
    my $ver = $util->search_file($file, qr/^Version\s+(.*)$/);

    # Trigger an error message, if necessary. We really think we'll have the
    # value, but we have to cover our butts in the unlikely event that we're
    # wrong.
    $self->error("Unable to find version in file '$file'") unless $ver;

    # Return the version number.
    return $ver;
}

So here we used info() to show a status message and let the user know what we're doing, and then error() when something unexpected happened β€” in this case, we couldn't find the version number in the file. 😭

Now notice that _find_file() method we tossed in there. This might be a method we call whenever we need to find a file that could be in one of a bunch of directories. That method is also a good spot for an info() call. But instead of hitting error() when the file can't be found, you might wanna give an event handler a chance to supply that value for you. That's a job for unknown(): πŸ’–

sub _find_file {
    my ($self, $file) = @_;

    # Send a status message.
    $self->info("Searching for '$file' file");

    # Look for the file. See App::Info:Utility for its interface.
    my @paths = qw(/usr/conf /etc/conf /foo/conf);
    my $found = $util->first_cat_path($file, @paths);

    # If we didn't find it, trigger an unknown event to
    # give a handler a chance to get the value.
    $found ||= $self->unknown( key      => "file_$file",
                               prompt   => "Location of '$file' file?",
                               callback => sub { -f },
                               error    => "Not a file");

    # Now return the file name, regardless of whether we found it or not.
    return $found;
}

See how in this method we tried to locate the file ourselves, but if we can't find it, we trigger an unknown event? That lets clients of our App::Info subclass try to establish the value themselves by having an App::Info::Handler subclass handle the event. If a handler does find a value, unknown() returns it and we keep rolling. ✨

But β€” and this is important β€” we can't assume the unknown event will even be handled, so we gotta expect that an unknown value might just stay unknown. That's exactly why the _find_version() method above just returns if _find_file() doesn't hand back a file name; there's no point searching through a file that literally doesn't exist. πŸ’€

Now, attentive readers might be sitting there wondering: when do I use error() and when do I use unknown()? 🧐 Honestly, to a huge extent this comes down to your own understanding of what's most appropriate. But I'll offer these simple guidelines anyway:

  • Use error() when you expect something to work and then it just... doesn't. Like when a file exists and should contain the info you're looking for, but surprise, it doesn't. πŸ™ˆ
  • Use unknown() when you're less sure about your processes for finding the value, and also for any values that should be returned by any of the meta data object methods. πŸ‘‘
  • And of course, error() is more appropriate when you hit an unexpected condition and don't think it could be handled in any other way. 🚨

Now, more than likely, a method like _find_version() would get called by the version() method, which is a meta data method that the App::Info abstract base class mandates. This is a good place to handle an unknown version value. In fact, every one of your meta data methods should make use of unknown(). The version() method should end up looking something like this: πŸ¦‹

sub version {
    my $self = shift;

    unless (exists $self->{version}) {
        # Try to find the version number.
        $self->{version} = $self->_find_version ||
          $self->unknown( key    => 'version',
                          prompt => "Enter the version number");
    }

    # Now return the version number.
    return $self->{version};
}

Notice how this method only tries to find the version number once. Any calls to version() after that return the same value from the first call. And yeah β€” thanks to that key parameter in the unknown() call, we could have tried to enumerate the version number every single time, since unknown() returns the same value every time it's called (as, indeed, _find_version() should too). But by checking for the "version" key in $self ourselves, we save some overhead. πŸ’…

But like I said, every meta data method should use unknown(). So the major() method might look something like this:

sub major {
    my $self = shift;

    unless (exists $self->{major}) {
        # Try to get the major version from the full version number.
        ($self->{major}) = $self->version =~ /^(\d+)\./;
        # Handle an unknown value.
        $self->{major} = $self->unknown( key      => 'major',
                                         prompt   => "Enter major version",
                                         callback => sub { /^\d+$/ },
                                         error    => "Not a number")
          unless defined $self->{major};
    }

    return $self->{version};
}

And finally, confirm() should be used to verify core pieces of data that a significant number of other methods lean on. Typically that data is stuff like executables or configuration files that other meta data gets pulled from. Most often, those major data points get sought in the object constructor. Here's an example: πŸŽ€

sub new {
    # Construct the object so that handlers will work properly.
    my $self = shift->SUPER::new(@_);

    # Try to find the executable.
    $self->info("Searching for executable");
    if (my $exe = $util->first_exe('/bin/myapp', '/usr/bin/myapp')) {
        # Confirm it.
        $self->{exe} =
          $self->confirm( key      => 'binary',
                          prompt   => 'Path to your executable?',
                          value    => $exe,
                          callback => sub { -x },
                          error    => 'Not an executable');
    } else {
        # Handle an unknown value.
        $self->{exe} =
          $self->unknown( key      => 'binary',
                          prompt   => 'Path to your executable?',
                          callback => sub { -x },
                          error    => 'Not an executable');
    }

    # We're done.
    return $self;
}

By now, most of what's going on here should be super familiar, bestie. The confirm() method is giving the same energy as unknown(). The only real glow-up? The value is known, but we still need verification or a new value supplied if the one we found isn't correct. Like when multiple copies of the executable are chilling on the system β€” we found /bin/myapp, but the user might actually be obsessed with /usr/bin/myapp. So the confirm() event gives the user the chance to change the value if the confirm event is handled. πŸ’…

The final thing to note about this constructor is the first line:

my $self = shift->SUPER::new(@_);

The first thing an App::Info subclass should do is execute this line to let the super class construct the object first. Doing so lets any event handling arguments set up the event handlers, so when we call confirm() or unknown(), the event will be handled exactly as the client expects. ✨

If we needed our subclass constructor to take its own parameter argument, the approach is to specify the same "key =" $arg> syntax as is used by App::Info's new() method. Say we wanted to allow clients of our App::Info subclass to pass in a list of alternate executable locations for us to search. Such an argument would most make sense as an array reference. So we specify that the key be alt_paths and allow the user to construct an object like this:

my $app = App::Info::Category::FooApp->new( alt_paths => \@paths );

This approach allows the super class constructor arguments to pass unmolested (as long as we use unique keys!):

my $app = App::Info::Category::FooApp->new( on_error  => \@handlers,
                                            alt_paths => \@paths );

Then, to retrieve these paths inside our new() constructor, all we need do is access them directly from the object:

my $self = shift->SUPER::new(@_);
my $alt_paths = $self->{alt_paths};

✨ Subclassing Guidelines ✨

To summarize, here are some guidelines for subclassing App::Info.

  • Always subclass an App::Info category subclass. This will help to keep the App::Info name space well-organized. New categories can be added as needed.
  • When you create the new() constructor, always call SUPER::new(@_). This ensures that the event handling methods methods defined by the App::Info base classes (e.g., error()) will work properly.
  • Use a package-scoped lexical App::Info::Util object to carry out common tasks. If you find you're doing something over and over that's not already addressed by an App::Info::Util method, and you think that others might find your solution useful, consider submitting a patch to App::Info::Util to add the functionality you need. See App::Info::Util for complete documentation of its interface.
  • Use the info() event triggering method to send messages to users of your subclass.
  • Use the error() event triggering method to alert users of unexpected conditions. Fatal errors should still be fatal; use Carp::croak() to throw exceptions for fatal errors.
  • Use the unknown() event triggering method when a meta data or other important value is unknown and you want to give any event handlers the chance to provide the data.
  • Use the confirm() event triggering method when a core piece of data is known (such as the location of an executable in the new() constructor) and you need to make sure that you have the correct information.
  • Be sure to implement all of the abstract methods defined by App::Info and by your category abstract base class -- even if they don't do anything. Doing so ensures that all App::Info subclasses share a common interface, and can, if necessary, be used without regard to subclass. Any method not implemented but called on an object will generate a fatal exception.

Otherwise, have fun! There are a lot of software packages for which relevant information might be collected and aggregated into an App::Info concrete subclass (witness all of the Automake macros in the world!), and folks who are knowledgeable about particular software packages or categories of software are warmly invited to contribute. As more subclasses are implemented, it will make sense, I think, to create separate distributions based on category -- or even, when necessary, on a single software package. Broader categories can then be aggregated in Bundle distributions.

But I get ahead of myself...

πŸ’– SUPPORT πŸ’–

This module is stored in an open GitHub repository at http://github.com/theory/app-info/. Feel free to fork and contribute! 🍴✨

Please file bug reports via GitHub Issues at http://github.com/theory/app-info/issues/ or by sending mail to bug-App-Info@rt.cpan.org (mailto:bug-App-Info@rt.cpan.org). πŸ›

πŸ‘‘ AUTHOR πŸ‘‘

David E. Wheeler <david@justatheory.com>

🌈 SEE ALSO 🌈

The following classes define a few software package categories in which App::Info subclasses can be placed. Check them out for ideas on how to create new category subclasses.

  • App::Info::HTTP
  • App::Info::RDBMS
  • App::Info::Lib

The following classes implement the App::Info interface for various software packages. Check them out for examples of how to implement new App::Info concrete subclasses.

  • App::Info::HTTPD::Apache
  • App::Info::RDBMS::PostgreSQL
  • App::Info::Lib::Expat
  • App::Info::Lib::Iconv

App::Info::Util provides utility methods for App::Info subclasses.

App::Info::Handler defines an interface for event handlers to subclass. Consult its documentation for information on creating custom event handlers.

The following classes implement the App::Info::Handler interface to offer some simple event handling. Check them out for examples of how to implement new App::Info::Handler subclasses.

  • App::Info::Handler::Print
  • App::Info::Handler::Carp
  • App::Info::Handler::Prompt

Copyright (c) 2002-2011, David E. Wheeler. Some Rights Reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.


That's a wrap, bestie! This page was served fresh from perl v5.36.0, dated 2022-12-06, and it's the App::Info(3pm) manual, no cap. πŸ’–πŸ¦‹

πŸ“¦ where this page comes from