App-Info
view release on metacpan or search on metacpan
lib/App/Info.pm view on Meta::CPAN
package App::Info;
=head1 NAME
App::Info - Information about software packages on a system
=head1 SYNOPSIS
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";
}
=head1 DESCRIPTION
App::Info is an abstract base class designed to provide a generalized
interface for subclasses that provide meta data about software packages
installed on a system. The idea is that these classes can be used in Perl
application installers in order to determine whether software dependencies
have been fulfilled, and to get necessary meta data about those software
packages.
App::Info provides an event model for handling events triggered by App::Info
subclasses. The events are classified as "info", "error", "unknown", and
"confirm" events, and multiple handlers may be specified to handle any or all
of these event types. This allows App::Info clients to flexibly handle events
in any way they deem necessary. Implementing new event handlers is
straight-forward, and use the triggering of events by App::Info subclasses is
likewise kept easy-to-use.
A few L<sample subclasses|"SEE ALSO"> are provided with the distribution, but
others are invited to write their own subclasses and contribute them to the
CPAN. Contributors are welcome to extend their subclasses to provide more
information relevant to the application for which data is to be provided (see
L<App::Info::HTTPD::Apache|App::Info::HTTPD::Apache> for an example), but are
encouraged to, at a minimum, implement the abstract methods defined here and
in the category abstract base classes (e.g.,
L<App::Info::HTTPD|App::Info::HTTPD> and L<App::Info::Lib|App::Info::Lib>).
See L<Subclassing|"SUBCLASSING"> for more information on implementing new
subclasses.
=cut
use strict;
use Carp ();
use App::Info::Handler;
use App::Info::Request;
use vars qw($VERSION);
$VERSION = '0.57';
##############################################################################
##############################################################################
# This code ref is used by the abstract methods to throw an exception when
# they're called directly.
my $croak = sub {
my ($caller, $meth) = @_;
$caller = ref $caller || $caller;
if ($caller eq __PACKAGE__) {
$meth = __PACKAGE__ . '::' . $meth;
Carp::croak(__PACKAGE__ . " is an abstract base class. Attempt to " .
" call non-existent method $meth");
} else {
Carp::croak("Class $caller inherited from the abstract base class " .
__PACKAGE__ . ", but failed to redefine the $meth() " .
"method. Attempt to call non-existent method " .
"${caller}::$meth");
}
};
##############################################################################
# This code reference is used by new() and the on_* error handler methods to
# set the error handlers.
my $set_handlers = sub {
my $on_key = shift;
# Default is to do nothing.
return unless $on_key;
my $ref = ref $on_key;
if ($ref) {
$on_key = [$on_key] unless $ref eq 'ARRAY';
# Make sure they're all handlers.
foreach my $h (@$on_key) {
if (my $r = ref $h) {
Carp::croak("$r object is not an App::Info::Handler")
unless UNIVERSAL::isa($h, 'App::Info::Handler');
} else {
# Look up the handler.
$h = App::Info::Handler->new( key => $h);
}
}
# Return 'em!
return @$on_key;
} else {
# Look up the handler.
return App::Info::Handler->new( key => $on_key);
}
};
##############################################################################
##############################################################################
=head1 INTERFACE
This section documents the public interface of App::Info.
=head2 Constructor
=head3 new
my $app = App::Info::Category::FooApp->new(@params);
Constructs an App::Info object and returns it. The @params arguments define
attributes that can be used to help the App::Info object search for
application information on the file system, as well as how the App::Info
lib/App/Info.pm view on Meta::CPAN
##############################################################################
##############################################################################
=head2 Event Handler Object Methods
These methods provide control over App::Info event handling. Events can be
handled by one or more objects of subclasses of App::Info::Handler. The first
to return a true value will be the last to execute. This approach allows
handlers to be stacked, and makes it relatively easy to create new handlers.
L<App::Info::Handler|App::Info::Handler> for information on writing event
handlers.
Each of the event handler methods takes a list of event handlers as its
arguments. If none are passed, the existing list of handlers for the relevant
event type will be returned. If new handlers are passed in, they will be
returned.
The event handlers may be specified as one or more objects of the
App::Info::Handler class or subclasses, as one or more strings that tell
App::Info construct such handlers itself, or a combination of the two. The
strings can only be used 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. These strings may then be used to tell
App::Info to construct App::Info::Handler::Print objects that print to STDERR
or to STDOUT, respectively. See the App::Info::Handler subclasses for what
strings they register with App::Info.
=head3 on_info
my @handlers = $app->on_info;
$app->on_info(@handlers);
Info events are triggered when the App::Info subclass wants to send an
informational status message. By default, these events are ignored, but a
common need is for such messages to simply print to STDOUT. Use the
L<App::Info::Handler::Print|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);
=cut
sub on_info {
my $self = shift;
@{ $self->{on_info} } = $set_handlers->(\@_) if @_;
return @{ $self->{on_info} };
}
=head3 on_error
my @handlers = $app->on_error;
$app->on_error(@handlers);
Error events are triggered when the App::Info subclass runs into an unexpected
but not fatal problem. (Note that fatal problems will likely throw an
exception.) By default, these events are ignored. A common way of handling
these events is to print them to STDERR, once again using the
L<App::Info::Handler::Print|App::Info::Handler::Print> class included 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 approach might be to turn such events into fatal exceptions. Use the
included L<App::Info::Handler::Carp|App::Info::Handler::Carp> class for this
purpose:
use App::Info::Handler::Carp;
my $app->on_error('croak');
# Or:
my $croaker = App::Info::Handler::Carp->new('croak');
$app->on_error($croaker);
=cut
sub on_error {
my $self = shift;
@{ $self->{on_error} } = $set_handlers->(\@_) if @_;
return @{ $self->{on_error} };
}
=head3 on_unknown
my @handlers = $app->on_unknown;
$app->on_uknown(@handlers);
Unknown events are triggered when the App::Info subclass cannot find the value
to be returned by a method call. By default, these events are ignored. A
common way of handling them is to have the application prompt the user for the
relevant data. The App::Info::Handler::Prompt class included with the
App::Info distribution can do just that:
use App::Info::Handler::Prompt;
my $app->on_unknown('prompt');
# Or:
my $prompter = App::Info::Handler::Prompt;
$app->on_unknown($prompter);
See L<App::Info::Handler::Prompt|App::Info::Handler::Prompt> for information
on how it works.
=cut
sub on_unknown {
my $self = shift;
@{ $self->{on_unknown} } = $set_handlers->(\@_) if @_;
return @{ $self->{on_unknown} };
}
=head3 on_confirm
my @handlers = $app->on_confirm;
lib/App/Info.pm view on Meta::CPAN
my $key = $params{key}
or Carp::croak("No key parameter passed to confirm()");
return $self->{__confirm__}{$key} if exists $self->{__confirm__}{$key};
# Create a prompt and error message, if necessary.
$params{message} = delete $params{prompt} ||
"Enter a valid " . $self->key_name . " $key";
$params{error} ||= 'Invalid value';
# Execute the handler sequence.
my $req = $handler->($self, "confirm", \%params);
# Mark that we've confirmed this value.
$self->{__confirm__}{$key} = $req->value;
return $self->{__confirm__}{$key}
}
1;
__END__
=head2 Event Examples
Below I provide some examples demonstrating the use of the event methods.
These are meant to emphasize the contexts in which it's appropriate to use
them.
Let's start with the simplest, first. Let's say that to find the version
number for an application, you need to search a file for the relevant data.
Your App::Info concrete subclass might have a private method that handles this
work, and this method is the appropriate place to use the C<info()> and, if
necessary, C<error()> methods.
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;
}
Here we've used the C<info()> method to display a status message to let the
user know what we're doing. Then we used the C<error()> method when something
unexpected happened, which in this case was that we weren't able to find the
version number in the file.
Note the C<_find_file()> method we've thrown in. This might be a method that
we call whenever we need to find a file that might be in one of a list of
directories. This method, too, will be an appropriate place for an C<info()>
method call. But rather than call the C<error()> method when the file can't be
found, you might want to give an event handler a chance to supply that value
for you. Use the C<unknown()> method for a case such as this:
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;
}
Note how in this method, we've tried to locate the file ourselves, but if we
can't find it, we trigger an unknown event. This allows clients of our
App::Info subclass to try to establish the value themselves by having an
App::Info::Handler subclass handle the event. If a value is found by an
App::Info::Handler subclass, it will be returned by C<unknown()> and we can
continue. But we can't assume that the unknown event will even be handled, and
thus must expect that an unknown value may remain unknown. This is why the
C<_find_version()> method above simply returns if C<_find_file()> doesn't
return a file name; there's no point in searching through a file that doesn't
exist.
Attentive readers may be left to wonder how to decide when to use C<error()>
and when to use C<unknown()>. To a large extent, this decision must be based
on one's own understanding of what's most appropriate. Nevertheless, I offer
the following simple guidelines: Use C<error()> when you expect something to
work and then it just doesn't (as when a file exists and should contain the
information you seek, but then doesn't). Use C<unknown()> when you're less
sure of your processes for finding the value, and also for any of the values
that should be returned by any of the L<meta data object methods|"Metadata
Object Methods">. And of course, C<error()> would be more appropriate when you
encounter an unexpected condition and don't think that it could be handled in
any other way.
Now, more than likely, a method such C<_find_version()> would be called by the
C<version()> method, which is a meta data method mandated by the App::Info
abstract base class. This is an appropriate place to handle an unknown version
value. Indeed, every one of your meta data methods should make use of the
C<unknown()> method. The C<version()> method then should look something like
this:
sub version {
my $self = shift;
lib/App/Info.pm view on Meta::CPAN
the super class to construct the object first. Doing so allows any event
handling arguments to set up the event handlers, so that when we call
C<confirm()> or C<unknown()> the event will be handled as the client expects.
If we needed our subclass constructor to take its own parameter argument, the
approach is to specify the same C<key => $arg> syntax as is used by
App::Info's C<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 C<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 C<new()> constructor, all we need do
is access them directly from the object:
my $self = shift->SUPER::new(@_);
my $alt_paths = $self->{alt_paths};
=head2 Subclassing Guidelines
To summarize, here are some guidelines for subclassing App::Info.
=over 4
=item *
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.
=item *
When you create the C<new()> constructor, always call C<SUPER::new(@_)>. This
ensures that the event handling methods methods defined by the App::Info base
classes (e.g., C<error()>) will work properly.
=item *
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 L<App::Info::Util|App::Info::Util> for complete
documentation of its interface.
=item *
Use the C<info()> event triggering method to send messages to users of your
subclass.
=item *
Use the C<error()> event triggering method to alert users of unexpected
conditions. Fatal errors should still be fatal; use C<Carp::croak()> to throw
exceptions for fatal errors.
=item *
Use the C<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.
=item *
Use the C<confirm()> event triggering method when a core piece of data is
known (such as the location of an executable in the C<new()> constructor) and
you need to make sure that you have the I<correct> information.
=item *
Be sure to implement B<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.
=back
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...
=head1 SUPPORT
This module is stored in an open L<GitHub
repository|http://github.com/theory/app-info/>. Feel free to fork and
contribute!
Please file bug reports via L<GitHub
Issues|http://github.com/theory/app-info/issues/> or by sending mail to
L<bug-App-Info@rt.cpan.org|mailto:bug-App-Info@rt.cpan.org>.
=head1 AUTHOR
David E. Wheeler <david@justatheory.com>
=head1 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.
=over 4
=item L<App::Info::HTTP|App::Info::HTTPD>
=item L<App::Info::RDBMS|App::Info::RDBMS>
( run in 1.429 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )