The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# =============================================================================
# $Id: Versions.pm 522 2006-09-19 11:26:13Z HVRTWall $
# Copyright (c) 2005-2006 Thomas Walloschke (thw@cpan.org). All rights reserved.
# Find versions of loaded modules, format results flexibel
# ==============================================================================

package Module::Versions;

# -- Force Perl version
use 5.008006;

# -- Pragmas
use strict;
use warnings;

# -- Global modules
use IO::Handle;
use Data::Dumper;

our ( $VERSION, $v, $_VERSION );

# -- CPAN VERSION (='major.minor{2}')
#    With respect to historical versions of Perl itself prior to 5.6.0 and
#    the Camel rules ('...Leading zeros *are* significant...').
#    Should be CPAN and version.pm compatible... Ref: BUGS
$VERSION = do { my @r = ( ( $v = q<Version value="0.20.1"> ) =~ /\d+/g ); sprintf "%d.%02d", $r[0], int( $r[1] / 10 ) };

# -- Mumified VERSION (='major.minor{3}release{3}revision{3}')
$_VERSION = do {
    my @r = ( $v =~ /\d+/g, q$Revision: 522 $ =~ /\d+/g );
    sprintf "%d." . "%03d" x $#r, @r;
};

# *** Constructor **************************************************************

sub new {

    my $class;
    if ( $_[0] and UNIVERSAL::isa( $_[0], __PACKAGE__ ) ) {
        $class = shift;
        $class = __PACKAGE__ unless $class;
    }
    else { $class = __PACKAGE__ }

    my ( $names, $select ) = @_;

    # -- Make object, load defaults
    my $self = bless {

        names => ( ref $names eq 'ARRAY' and @{$names} )    # list of names
        ? $names                                            #
        : ( defined $names and !ref $names )                # single name
        ? [$names]                                          #
        : [ sort keys %INC ],                               #
        select => ( ref $select eq 'ARRAY' and @{$select} ) # select list
        ? $select                                           #
        : ( defined $select and !ref $select )              # single select
        ? [$select]                                         #
        : [],                                               #

        _oldver  => 0,              # default = 0
                                    # 0: try to load 'version.pm' to get
                                    #    Perl 5.10.0 compatibility
                                    # 1: try to be conservative/compatible -
                                    #    ignored if version module was loaded
                                    #    from another source part with 'use'
        _notme   => 0,              # default = 0
                                    # 0: show my own package version
                                    # 1: suppress my package version
        _all     => 0,              # default 0
                                    # 0: suppress unknown modules
                                    # 1: show unknown modules
        _version => 0,              # default 0
                                    # 0: suppress 'version' module
                                    # 1: show 'version' module
        _package => __PACKAGE__,    #

    }, $class;

    return $self;
}

# *** Methods ******************************************************************

# -- Retrieve data
sub get {

    my $self = shift->_isa_obj;     # ensure versions object
    my ($criteria) = @_;            # get params

    $self->{_criteria}
        = ( ref $criteria eq 'ARRAY' and @{$criteria} )    # list of criteria
        ? $criteria                                        #
        : ( $criteria and !ref $criteria )                 # single criteria
        ? [$criteria]                                      #
        : () if defined $criteria;                         # redefine criteria

    $self->_get;                                           # get versions

    return $self;
}

# -- Print list of modules
sub list {

    my $self = shift->_isa_obj;    # ensure versions object
    my ( $fd, $mask ) = @_;        # get params

    $self->get unless $self->_get_versions;    # get versions if missing

    $self->{fd}   = $fd   if defined $fd;      # redefine output file
    $self->{mask} = $mask if defined $mask;    # redefine output format
    $self->{fd}   ||= *STDOUT;                 # default print fd
    $self->{mask} ||= '%5d %s[ %s %s %s ]';    # default print mask
                                               # e.g. '%5d %s Module %s: %s %s'
                                               # or '%s|%s|%s', ...
    $self->_format;

    return $self;

}

# -- Return data structure with modules
sub data {

    my $self = shift->_isa_obj;                # ensure versions object
    my ($cb) = @_;                             # get params

    $self->get unless $self->_get_versions;    # get versions if missing

    $self->{cb} = $cb if defined $cb and ref $cb eq 'CODE';
    $self->{cb} ||= sub { $self->ARRAY };

    return $self->{cb}->( $self->_get_versions, $self->_get_names );
}

# -- Return ARRAY with modules - pre-formed ARRAY structure
sub ARRAY {

    my $self = shift->_isa_obj;                # ensure versions object

    $self->get unless $self->_get_versions;    # get versions if missing

    return $self->_get_versions;               # TODO: sort
}

# -- Return HASH with modules - pre-formed HASH structure
sub HASH {

    my $self = shift->_isa_obj;                # ensure versions object
    my $hash;

    $self->get unless $self->_get_versions;    # get versions if missing

    map { $hash->{ $_->[0] }{ $_->[1] } = $_->[2] }
        map { [ @{$_} ] } @{ $self->_get_versions };

    return $hash;
}

# -- Return SCALAR with modules - pre-formed SCALAR structure
sub SCALAR {

    my $self = shift->_isa_obj;                # ensure versions object

    $self->get unless $self->_get_versions;    # get versions if missing

    return
        join( qq{\n}, map { join q{,}, @{$_} } @{ $self->_get_versions } )
        ;                                      # TODO: sort
}

# -- Return CSV structure with modules - pre-formed CSV structure
sub CSV {

    my $self = shift->_isa_obj;                # ensure versions object

    my $header = q(Module,Name,Value);

    return join qq{\n}, $header, $self->SCALAR;
}

# -- Return XML structure with modules - pre-formed XML structure
sub XML {

    my $self = shift->_isa_obj;                # ensure versions object

    my $dtd = $self->{dtd}
        if defined $self->{dtd};    # Experimental: will be set by $self->DTD
    my $xsi = $self->{xsi}
        if defined $self->{xsi};    # Experimental: will be set by $self->XSD

    $self->get unless $self->_get_versions;    # get versions if missing

    my $header = q{<?xml version="1.0" encoding="UTF-8"?>};
    my ( $root, $elem, $att0, $att1 ) = (qw{versions version module name});

    my ( $open_root, $close_root ) = (
        qq{<$root}
            . ( defined $xsi ? $xsi : q{} )    # Experimental: assign W3C schema
            . qq{>},
        qq{</$root>}
    );

    return join qq{\n}, grep {$_} $header,
        ( defined $dtd ? $dtd : q{} ),         # Experimental: assign DTD
        $open_root,
        ( map {qq{\t<$elem $att0="$_->[0]" $att1="$_->[1]">$_->[2]</$elem>}}
            @{ $self->_get_versions } ), $close_root;
}

# -- Return XSD schema - set xsi in object
sub XSD {

    my $self = shift->_isa_obj;                # ensure versions object

    my $noNSL = "xsd/versions.xsd";   # Experimental: define W3C schema location
    $self->{xsi} = qq{ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"}
        . qq{ xsi:noNamespaceSchemaLocation="$noNSL"};

    return $self->_xsd;
}

# -- Return DTD - set dtd in object
sub DTD {

    my $self = shift->_isa_obj;       # ensure versions object

    my $SYSTEM = "dtd/versions.dtd";  # Experimental: define DTD location
    $self->{dtd} = qq{<!DOCTYPE versions SYSTEM "$SYSTEM">};

    return $self->_dtd;
}

# *** Internal methods *******************************************************

# -- Check if object is valid - 'is_an_object?'
sub _isa_obj {

    my $self;

    # If this is not a method call with a valid object, create a new object
    if ( $_[0] and UNIVERSAL::isa( $_[0], __PACKAGE__ ) ) {

        $self = shift;
        $self = __PACKAGE__->new unless ref $self;    # *)
    }
    else {

        $self = __PACKAGE__->new;    # *)
    }

    # *) Attention: a new object contains implicitly the $VERSION from loaded
    #               modules only

    return $self;
}

# -- Retrieve module versions
sub _get {

    my $self = shift;

    my $names    = $self->_get_names;
    my $select   = $self->_get_select;
    my $criteria = $self->_get_criteria;
    my $package  = $self->_get_package;

    # -- Map
    my $_map = {
        'oldver' => sub { $self->{_oldver} = 1 },  # Be conservative/compatible
        'notme'  => sub { $self->{_notme}  = 1 },  # Suppress my package version
        'all'    => sub { $self->{_all}    = 1 },  # Show unknown modules also
        'version' => sub { push @{$names}, 'version'; $self->{_version} = 1 }
        ,                                          # Show version module also
    };

    # -- Criteria mapping
    foreach my $criteria ( @{$criteria} ) {
        $_map->{ lc $criteria }->()
            if ref $_map->{ lc $criteria } eq 'CODE';
    }

    # -- Clear result
    $self->{versions} = [];

    # -- Find modules
    my $_seen = {};
    foreach my $name ( @{$names} ) {

        next
            if $name =~ /\.(al|ix|bs)$/;    # ignore this  (DynaLoader, etc.)

        ( my $module = $name ) =~ s|/|::|g; # convert name to module
        $module =~ s|\.(pm)$||;             # clean module notation

        next if $_seen->{$module}++;                         # ignore duplicates
        next if $module =~ /^$package$/ and $self->{_notme}; # be demure
        next if $name =~ /^version$/ and !$self->{_version}; # be demure

        # -- Find version infos
        my $seen = { q{::} => { 'VERSION' => 1 } };    # avoid multiple scans
                                                       # of VERSION (object and
                                                       # veriable)
        push @{ $self->{versions} },

            grep { !$seen->{ $_->[0] }{ $_->[1] }++ }    # ignore duplicates
            grep { @{$_} }                               # ignore empty results

            ( eval "require $module" )                   # try to load module
            ? (

            # -- Mandantory scan of VERSION - as an object!
            [   (   eval "$module->VERSION"              # try to find VERSION
                    ? ( $module, 'VERSION',
                        eval "$module->VERSION"
                        ? eval "$module->VERSION"
                        : 'undefined'                    # ERROR! Ref: Camel ...
                        )
                    : ( $module, qw{VERSION unknown} )   # FATAL! Ref: Camel ...
                )
            ],

            # -- Optional scan of selected variables - no objects!
            map {
                [   (   eval "\$${module}::$_"           # try to find selection
                        ? ( $module, $_,
                            eval "\$${module}::$_"
                            ? eval "\$${module}::$_"
                            : 'undefined'                # !defined <variable>
                            )
                        : ()                             # can't find <variable>
                    )
                ]
                }
                grep { !$seen->{q{::}}{$_}++ } @{$select},
            )
            : [ ( $module, qw{Module unknown} ) ]        # can't load module
            if $self->{_all}              # check 'all modules wanted'
            or eval "require $module";    # check 'loadable modules only'
    }

    return $self;                         # object with versions
}

# -- Output formatting
sub _format {

    my $self     = shift;
    my $fd       = $self->_get_fd;
    my $mask     = $self->_get_mask;
    my $versions = $self->_get_versions;
    my $select   = $self->_get_select;

    # -- Map for pre-formed data format creation
    my $_map = {
        ARRAY => sub { Data::Dumper->Dump( [ shift->ARRAY ], [q{$versions}] ) },
        HASH  => sub { Data::Dumper->Dump( [ shift->HASH ],  [q{$versions}] ) },
        SCALAR => sub { shift->SCALAR },
        CSV    => sub { shift->CSV },
        XML    => sub { shift->XML },
        XSD    => sub { shift->XSD },
        DTD    => sub { shift->DTD },
    };

    # -- Valid fd only wanted...
    if ( fileno($fd) ) {

        # -- Try to use 'version' for overloading VERSION formatting
        unless ( $self->{_oldver} ) {
            eval "require version";    # try to load version.pm
            $self->{has_version} = version->can('new');    # version loaded?
        }

        # -- Message format creation
        if ( my @conv = ( $mask =~ /\%[^%]/g ) )
        {    # count universally-known conversions

            my $cnt = 0;
            foreach my $version ( @{$versions} ) {

                next unless @{$version};
                my ( $_module, $_name, $_value ) = @{$version};

                # -- Formatting
                printf $fd "$mask\n",
                    ( @conv == 5 )    # formatting with 5 conversions (%)
                    ? (
                    ++$cnt,           # 1. %
                    ( ( $_name and $_value !~ /^un/ ) ? q{ } : q{*} ),    # 2. %
                    )
                    : (),
                    (
                           @conv == 3
                        or @conv == 5
                    )    # formatting with 3 or 5 conversions (%)
                    ? (
                    ($_module),    # 3./1. %
                    ( $_name ? $_name : q{-} ),    # 4./2. %
                    (   ( defined $_value and $_value !~ /^un/ )    # 5./3. %
                        ? ( $self->{has_version}
                            ? version->new($_value)->normal    # v0.1.0
                            : $_value                          # 0.01 or 0.010
                            )
                        : ( defined $_value
                            ? $_value
                            : q{-}
                        )
                    )
                    )
                    : ();

            }
        }

        # -- Data format autoserialized preform mapping
        elsif ( $mask =~ m{^(ARRAY|HASH|SCALAR|CSV|XML|XSD|DTD)$} ) {

            local $Data::Dumper::Indent   = 0;
            local $Data::Dumper::Sortkeys = 1;

            print $fd $_map->{ uc $mask }->($self)
                if ref $_map->{ uc $mask } eq 'CODE';
        }

        # -- Data format serialized callback mapping
        elsif ( defined $mask and ref $mask eq 'CODE' ) {

            print $fd Data::Dumper->Dump(
                [ $mask->( $self->_get_versions, $self->_get_names ) ],
                [q{$versions}] );
        }
    }
}

# -- Internal getter
sub _get_versions { shift->{versions} }
sub _get_names    { shift->{names} }
sub _get_select   { shift->{select} }
sub _get_mask     { shift->{mask} }
sub _get_fd       { shift->{fd} }
sub _get_criteria { shift->{_criteria} }
sub _get_package  { shift->{_package} }

# *** Experimental *************************************************************

# -- W3C XML schema of generated XML file
sub _xsd {

    my $self = shift;

    my $localtime = scalar localtime;
    my $package   = $self->_get_package;

    return <<XSD;
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- \$Id XSD schema created by $package $VERSION at $localtime \$ -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
    <xs:element name="version">
	<xs:complexType mixed="true">
	    <xs:attribute name="module" use="required">
		<xs:simpleType>
		    <xs:restriction base="xs:string">
			<xs:pattern value="(\\w|_)+(::(\\w|_)+)*"/>
		    </xs:restriction>
		</xs:simpleType>
	    </xs:attribute>
	    <xs:attribute name="name" use="required">
		<xs:simpleType>
		    <xs:restriction base="xs:string">
			<xs:pattern value="VERSION"/>
			<xs:pattern value="Module"/>
			<xs:pattern value="(\\w|_)+"/>
		    </xs:restriction>
		</xs:simpleType>
	    </xs:attribute>
	</xs:complexType>
    </xs:element>
    <xs:element name="versions">
	<xs:complexType>
	    <xs:sequence>
		<xs:element ref="version" maxOccurs="unbounded"/>
	    </xs:sequence>
	</xs:complexType>
    </xs:element>
</xs:schema>
XSD
}

# -- DTD of generated XML file
sub _dtd {

    my $self = shift;

    my $localtime = scalar localtime;
    my $package   = $self->_get_package;

    return <<DTD;
<?xml version="1.0" encoding="UTF-8"?>
<!-- \$Id DTD created by $package $VERSION at $localtime \$ -->
<!ELEMENT version (#PCDATA)>
<!ATTLIST version
	module CDATA #REQUIRED
	name CDATA #REQUIRED
>
<!ELEMENT versions (version+)>    
DTD

}

1;

__END__

=head1 NAME

Module::Versions - Handle versions of loaded modules with flexible result interface

=head1 VERSION

This documentation refers to Module::Versions Version 0.01
$Revision: 522 $

Precautions: Alpha Release. 

=head1 SYNOPSIS

    use Module::Versions;
    
    # Simple Interface
    list Module::Versions;               # prints formatted results to STDOUT
    Module::Versions->list;              # prints formatted results to STDOUT
                  
    # Shortcuts      
    $vers  = get Module::Versions;       # retrieves loaded modules

    $vers  = Module::Versions->get;      # retrieves loaded modules
    
    $array = Module::Versions->ARRAY;    # returns array with version infos
    $hash  = Module::Versions->HASH;     # returns hash with version infos
    
    $list  = Module::Versions->SCALAR;   # returns text list with version infos
    $csv   = Module::Versions->CSV;      # returns csv list with version infos
    $xml   = Module::Versions->XML;      # returns xml struct with version infos
    $xsd   = Module::Versions->XSD;      # returns xml schema of version infos
    $dtd   = Module::Versions->DTD;      # returns DTD of version infos
    
    # Individual Parameters
    $vers = Module::Versions             # retrieves mods and vars as defined
            ->new($mods,$vars)
            ->get($criteria);
            
    $vers->list($fd,$mask);              # prints formatted results to file  
    $vers->list($fd,$preform);           # prints preformatted results to file
    $vers->list($fd,\&cb);               # prints serialied results as handled 
                                         # in callback routine
    $vers->data(\&cb);                   # returns transformed results as
                                         # defined in callback routine
    # Individual formatted output
    list Module::Versions(*LOG, '%5d %1s %-20s %10s %-16s');
                                         # prints individually formatted
                                         # results to LOG
    list Module::Versions(*DBIMPORT, '%s|%s|%s');
                                         # prints individually formatted
                                         # results to Database Import file
    
    list Module::Versions(*FD, 'SCALAR');# prints text list results to file
    list Module::Versions(*FD, 'CSV');   # prints csv list results to file
    list Module::Versions(*FD, 'XML');   # prints xml struct results to file
    list Module::Versions(*FD, 'XSD');   # prints xml schema to file
    list Module::Versions(*FD, 'DTD');   # prints DTD to file
    
    list Module::Versions(*FD, 'ARRAY'); # prints serialized results to file
    list Module::Versions(*FD, 'HASH');  # prints serialized results to file

    Module::Versions->list(*LOG);        # prints formatted results to LOG
    
    # Pretty Compact
    Module::Versions->list               # prints formatted results on STDOUT
    ->list(*XML,'XML');                  # prints xml struct results to XML file
    
    Module::Versions->list               # prints formatted results on STDOUT
    ->list(*XSD,'XSD')                   # prints xml schema to XSD file
    ->list(*XML,'XML');                  # prints xml struct results to XML file
    
    Module::Versions->list               # prints formatted results on STDOUT
    ->list(*DTD,'DTD')                   # prints DTD to DTD file
    ->list(*XML,'XML');                  # prints xml struct results to XML file
    

=head1 DESCRIPTION

Module::Versions handles versions of loaded modules with a flexible result
interface. The main goal is to get as much version informations as possible
about a module or module list with a simple call interface and an absolutely
flexible result interface. Module::Versions handles *loaded* and *loadable*
modules.

The motivation for writing this module was the need for better support
facilities to get  informations about the used modules and versions in the
productivity environment. Module::Versions allows shipping applications
basically with something like a '-version' option (See L<Getopt::Long>) but
with expanded functions.

Module::Versions tries to read the loaded/loadable module's $VERSION. For
extended purposes any private project 'version variables' can be fetched
($_VERSION, $version, $REV, etc.).

Module::Versions has a flexible result interface to satisfy different needs:
results can be lists and data structures with different formats - pre-formed
ARRAY, HASH, SCALAR, CSV, XML/XSD/DTD and a full flexible user callback
interface.

I<It is for example very simple to print a good formatted version list to the
console and save a version.xml file (in conjunction with an xsd-schema) at
the same time with an absolutely minimum of coding (L<SYNOPSIS>, Pretty
Compact) >.

Module::Versions tries to load 'version.pm' to support Perl 5.10.0's $VERSION
formatting.

=head1 METHODS

=over 2

=item Calling

Module::Versions Methods can be called as

    Class methods:              e.g.    Module::Versions->new;      
    Instance methods:           e.g.    $versions->new;
    Indirect objects:           e.q.    new Module::Versions;

=item Shortcuts

The standard chaining can be written with shortcuts:

    1.) Module::Versions->new->get;
    2.) Module::Versions->new->get->list;
    3.) Module::Versions->new->get->data;
    4.) Module::Versions->new->get->XML;
    5.) $versions = Module::Versions->new->get-list;
        $versions->list(*XSD, 'XSD');
        $versions->list(*XML, 'XML');
    
    can be written as
    
    1.) Module::Versions->get;      # result is an object
    2.) Module::Versions->list;     # result is an object and a printed list
    3.) Module::Versions->data;     # result is an ARRAY
    4.) Module::Versions->XML;      # result is a XML scalar 
    5.) Module::Versions->list      # result is an object and a printed list
        ->list(*XSD,'XSD')          # result is a XSD schema in file *XSD
        ->list(*XML,'XML');         # result is a XML scalar in file *XML

=back

=head2 Overview

=over 2

=item Constuctor

C<new>

=item Standard Methods

C<get>,
C<list>,
C<data>

=item Preformed Methods

C<ARRAY>,
C<HASH>,
C<SCALAR>,
C<CSV>,
C<XML>,
C<XSD>,
C<DTD>

=back

=head2 Constructor

=over 2

=item * new

=item * new(E<lt>MODULESE<gt>, E<lt>SELECTIONE<gt>)

Creates a new Versions object.

The object contains a list of module names and a list of variables, which will
be scanned in addition to $VERSION. The module list contains explicitely defined
names or the internal %INC names.

=over 10

=item E<lt>MODULESE<gt>

String or ARRAY of strings; default is content of %INC.

=item E<lt>SELECTIONE<gt>

String or ARRAY of strings; default is 'VERSION'.

I<This may be a list of project specific version variables that can be
observed in addition to the Perl standard variable '$VERSION', e.g. '$_VERSION',
'$version', '$REV'. See '$_VERSION' in this source (='Mumified VERSION')>.

The selection of the Perl standard variable '$VERSION' is mandantory and cannot
be reset.

=back

=back

=head2 Methods

=over 2

=item * get

=item * get(E<lt>CRITERIAE<gt>)

Retrieve C<E<lt>MODULESE<gt>> as defined before and use the
C<E<lt>SELECTIONE<gt>> as defined in object by the constructor.

The result can be accessed by C<list>, C<data> or the shortcuts C<ARRAY>,
C<HASH>, C<SCALAR>, C<CSV> and C<XML>.

=over 10

=item E<lt>CRITERIAE<gt>

String or ARRAY of strings; default formatting of the version info will be done
as 'normal' (e.g. B<v0.10.0>), if the module 'version.pm' (L<SEE ALSO>,
L<version>) is installed and can be 'required' - otherwise the original
presentation will be left untouched (e.q. B<0.01>). By default the result will
contain information about 'my own' module (Module::Versions) as well but will
ignore any information about 'unloadable' modules. A possibly loaded
'version.pm' module will not be shown.

Default is set to B<not C<oldver>>, B<not C<notme>>, B<not C<all>> and
B<not C<version>>.

=over 2

=item oldver

Tries to use the historical versions of Perl itself prior to 5.6.0, as
well as the Camel rules for the $VERSION (e.q. B<0.01>). This 'untouched'
presention can be ensured only if the module 'version.pm' had B<not> been
loaded before by the script or another module. If the 'version.pm'
was loaded, a 'version.pm' default will be used (e.q. B<0.010>). See
L<EXAMPLES>.

=item notme

Suppress 'my own' package version (Module::Versions).

=item all

Show 'unknown modules' also.

=item version

Show 'version' module also.  Influences C<oldver> criterium.

=back

=back

=item * list

=item * list(E<lt>FDE<gt>, E<lt>MASKE<gt>)

=item * list(E<lt>FDE<gt>, E<lt>PREFORME<gt>)

=item * list(E<lt>FDE<gt>, E<lt>CALLBACKE<gt>)

Prints a formatted module list to a file.

If no parameters are defiend C<list> prints to STDOUT in an predefined format.
An opened filedescriptor C<E<lt>FDE<gt>> can define another result file.

A mask C<E<lt>MASKE<gt>> redefines the standard format. ALternatively a
preformed fileformat (C<E<lt>PREFORME<gt>>) can be selected to print in standard
formats.

For indivudual requirements a C<E<lt>CALLBACKE<gt>> interface can be used.

=over 10

=item E<lt>FDE<gt>

Filedescriptor, default *STDOUT.

=item E<lt>MASKE<gt>

String, default '%5d %s[ %s %s %s ]' in sprintf format.
See L<perldoc> -f sprintf.

Default result:

    1  [ AutoLoader VERSION v5.600.0 ]
    2  [ Carp VERSION v1.30.0 ]
    3 *[ Config VERSION unknown ]
    4  [ Cwd VERSION v3.10.0 ]

Mask needs 3 or 5 arguments.

=over 10

=item * 5 Arguments

                                        ---- Examples ----
    1.  cnt           [numeric]         1           2
    2.  error         [string]                      *
    3.  module        [string]          Carp        File
    4.  variable      [string]          VERSION     Module
    5.  value         [string]          1.030       unknown

=item * 3 Arguments

                                        ---- Examples ----
    1.  module        [string]          Carp        File
    2.  variable      [string]          VERSION     Module
    3.  value         [string]          1.030       unknown

=back

=item E<lt>PREFORME<gt>

String, no default.

The following strings are valid:

=over 10

=item 'ARRAY'

Print a serialized ARRAY. Ref: C<ARRAY>.

    $versions = [['AutoLoader','VERSION','5.600'],...];

=item 'HASH'

Print a serialized HASH. Ref: C<HASH>.

    $versions = {'AutoLoader' => {'VERSION' => '5.600'},...};

=item 'SCALAR'

Print a simple text list.

    Carp,VERSION,1.030                          # Module, Name, Value
    strict,VERSION,1.030
    File,Module,unknown                         # 'all': Module 'File' not found
    Data::Dumper,VERSION,2.121_020
    Win32::PerlExe::Env,VERSION,0.050           # Standard variable $VERSION
    Win32::PerlExe::Env,_VERSION,0.050001507    # Project variable $_VERSION

=item 'CSV'

Print a simple CSV list.

    Module,Name,Value                           # Header 
    Carp,VERSION,1.030                          # Data
    strict,VERSION,1.030                        #   :
    File,Module,unknown                         #   
    Data::Dumper,VERSION,2.121_020              #   
    Win32::PerlExe::Env,VERSION,0.050           #   
    Win32::PerlExe::Env,_VERSION,0.050001507    #   

=item 'XML'

Print a XML file sructure.

Default format, if B<no> C<'XSD'> or C<'DTD'> call was executed before.

    <?xml version="1.0" encoding="UTF-8"?>
    <versions>
        <version module="Carp" name="VERSION">1.030</version>
        <version module="strict" name="VERSION">1.030</version>
        <version module="File" name="Module">unknown</version>
        <version module="Data::Dumper" name="VERSION">2.121_020</version>
        <version module="Win32::PerlExe::Env" name="VERSION">0.050</version>
        <version module="Win32::PerlExe::Env" name="_VERSION">0.050001507</version>
    </versions>

If C<'XSD'> was called before:

    <?xml version="1.0" encoding="UTF-8"?>
    <versions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:noNamespaceSchemaLocation="xsd/versions.xsd">
        <version module="Carp" name="VERSION">1.030</version>
        <version module="strict" name="VERSION">1.030</version>
            :
    </versions>

If C<'DTD'> was called before:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE versions SYSTEM "dtd/versions.dtd">
    <versions>
        <version module="Carp" name="VERSION">1.030</version>
        <version module="strict" name="VERSION">1.030</version>
            :
    </versions>

=item 'XSD'

Print a C<'XML'> file related XSD schema file.

=item 'DTD'

Print a C<'XML'> file related DTD file.

=back

=item E<lt>CALLBACKE<gt>

CODE reference, no default. Interface 

    sub cb {
        my ( $versions, $names ) = @_;
        # Do anything and build result...
        return $result;
    }

=back

=item * data

=item * data(E<lt>CALLBACKE<gt>)

Result from callback (C<E<lt>CALLBACKE<gt>>) routine, default callback is
C<ARRAY>;

=item * ARRAY

Result is an ARRAY:

        [
          [
            'AutoLoader',                   # Module
            'VERSION',                      # Variable
            '5.600'                         # Value
          ],
                :
                :
                :
          [
            'warnings::register',
            'VERSION',
            '1.000'
          ]
        ];

=item * HASH

Result is a HASH:

        {
          'Carp' => {                       # Module
                'VERSION' => '1.030'        # Variable and Value
                    },
          'Data::Dumper' => {
                'VERSION' => '2.121_020'
                            },
          'File' => {                       # Criterium was 'all'...
                'Module' => 'unknown'       # Module 'File' not found 
                    },
          'Win32::PerlExe::Env' => {
                'VERSION' => '0.050',       # Standard variable $VERSION
                '_VERSION' => '0.050001507  # Project variable $_VERSION
                                   },
        }

=item * SCALAR

Result is a simple text SCALAR. Ref: C<'SCALAR'>.

=item * CSV

Result is a simple CSV text scalar. Ref: C<'CSV'>.

=item * XML

Result is a XML text scalar. Ref: C<'XML'>.

=item * XSD

Result is a XSD text scalar. Ref: C<'XSD'>.

=item * DTD

Result is a DTD text scalar. Ref: C<'DTD'>.

=back

=head1 DIAGNOSTICS

The XML generation allows an experimental feature to build XML data which can
be validated. This will be done magically if one of the following sequences will
be used:

    1.) $v->list(*XSD,'XSD')->list(*XML,'XML');
    2.) $v->list(*DTD,'DTD')->list(*XML,'XML');

=head1 CONFIGURATION AND ENVIRONMENT

*** tbd ***

=head1 EXAMPLES

See F<examples> of this distributions.

=head1 DEPENDENCIES

L<IO::Handle> L<Data::Dumper>

=head1 BUGS

This is an Alpha Release.

The XSD/DTD methods are experimental.

Some parts of this documentation may be marked as *** tbd ***.

Send bug reports to my email address or use the CPAN RT system.

=head1 SEE ALSO

L<version>

L<Module::Find>,
L<Module::InstalledVersion>,
L<Module::Info>,
L<Module::List>,
L<Module::Locate>,
L<Module::Which>,
L<Module::Which::List>

=head1 AUTHOR

Thomas Walloschke E<lt>thw@cpan.orgE<gt>.

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2006 Thomas Walloschke (thw@cpan.org). All rights reserved.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.6 or,
at your option, any later version of Perl 5 you may have available. See
L<perlartistic>.

=head1 DATE

Last changed $Date: 2006-09-19 13:26:13 +0200 (Di, 19 Sep 2006) $.

=cut