App-Info

 view release on metacpan or  search on metacpan

lib/App/Info.pm  view on Meta::CPAN

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;

      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};
  }

Note how this method only tries to find the version number once. Any
subsequent calls to C<version()> will return the same value that was returned
the first time it was called. Of course, thanks to the C<key> parameter in the
call to C<unknown()>, we could have have tried to enumerate the version number
every time, as C<unknown()> will return the same value every time it is called
(as, indeed, should C<_find_version()>. But by checking for the C<version> key
in C<$self> ourselves, we save some of the overhead.

But as I said before, every meta data method should make use of the
C<unknown()> method. Thus, the C<major()> method might looks 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};
  }

Finally, the C<confirm()> method should be used to verify core pieces of data
that significant numbers of other methods rely on. Typically such data are
executables or configuration files from which will be drawn other meta data.
Most often, such major data points will be 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 quite familiar. The use of the
C<confirm()> method is quite similar to that of C<unknown()>. Really the only
difference is that the value is known, but we need verification or a new value
supplied if the value we found isn't correct. Such may be the case when
multiple copies of the executable have been installed on the system, we found
F</bin/myapp>, but the user may really be interested in F</usr/bin/myapp>.
Thus the C<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 allow
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



( run in 1.131 second using v1.01-cache-2.11-cpan-5a3173703d6 )