#   ---------------------------------------------------------------------- copyright and license ---
#
#   file: lib/Version/Dotted.pm
#
#   Copyright © 2016 Van de Bugger.
#
#   This file is part of perl-Version-Dotted.
#
#   perl-Version-Dotted is free software: you can redistribute it and/or modify it under the terms
#   of the GNU General Public License as published by the Free Software Foundation, either version
#   3 of the License, or (at your option) any later version.
#
#   perl-Version-Dotted is distributed in the hope that it will be useful, but WITHOUT ANY
#   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
#   PURPOSE. See the GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License along with
#   perl-Version-Dotted. If not, see <http://www.gnu.org/licenses/>.
#
#   ---------------------------------------------------------------------- copyright and license ---

#pod =for test_synopsis my ( $v, $i, $p );
#pod
#pod =head1 SYNOPSIS
#pod
#pod     use Version::Dotted;            # import nothing
#pod     use Version::Dotted qw{ qv };   # import qv
#pod
#pod     # Construct:
#pod     $v = Version::Dotted->new( v1.2.3 );    # the same as qv( v1.2.3 )
#pod     $v = qv( v1.2.3 );      # v1.2.3
#pod     $v = qv( '1.2.0' );     # v1.2 (trailing zero parts ignored)
#pod     $v = qv( 'v1' );        # v1
#pod
#pod     # Access individual parts:
#pod     $p = $v->part( $i );    # Get i-th part.
#pod
#pod     # Stringify:
#pod     $v->stringify;          # "v1.2.3" (always with 'v' prefix)
#pod     "$v";                   # ditto
#pod
#pod     # Bump the version:
#pod     $v->bump( $i );         # Bump i-th part
#pod                             # and drop all parts behind i-th.
#pod
#pod     # Compare:
#pod     $v > v1.2.3;
#pod     $v == '1.2.3';
#pod
#pod =head1 DESCRIPTION
#pod
#pod C<Version::Dotted> is a subclass of C<version>.
#pod
#pod The class narrows C<version> — C<Version::Dotted> creates only I<dotted> (aka dotted-decimal)
#pod version objects, support for creating decimal versions is not provided. Support for "alpha"
#pod versions is dropped too (subclasses provides support for "trial" versions instead).
#pod
#pod The class extends C<version> — C<Version::Dotted> objects are I<modifiable>.
#pod
#pod =head2 Dotted Version
#pod
#pod Dotted (aka dotted-decimal) version is a series of parts joined with dots, each part is a cardinal
#pod (non-negative) integer. Every part (except the first) should be in range [0..999], the first part
#pod can be bigger than 999.
#pod
#pod See also L<version::Internals/"Dotted-Decimal Versions">.
#pod
#pod =head2 Warnings
#pod
#pod Methods report error conditions by C<warnings::warnif>. It gives the caller flexibility: warning
#pod may be either suppressed
#pod
#pod     no warnings 'Version::Dotted';
#pod
#pod or made fatal:
#pod
#pod     use warnings FATAL => 'Version::Dotted';
#pod
#pod =head2 Release Status
#pod
#pod Unfortunately, Perl terminology in this area in not well-defined and not consistently used:
#pod
#pod =over
#pod
#pod =item *
#pod
#pod The C<version> module names a version containing underscore "alpha version" and refers to CPAN.
#pod
#pod =item *
#pod
#pod C<CPAN::Meta::Spec> defines status as one of: C<stable>, C<testing>, and C<unstable>. Word "alpha"
#pod is used in the description of C<unstable> release, while C<testing> release is described as "beta".
#pod There is also requirement that C<stable> release version should not contain underscore.
#pod
#pod =item *
#pod
#pod pause.perl.org site has section named "Developer Releases" which is about releasing "code for
#pod testing". Such releases should either have underscore in version or "-TRIAL" suffix.
#pod
#pod =item *
#pod
#pod meta::cpan site in the list of module releases shows "DEV" after versions containing underscore.
#pod
#pod =item *
#pod
#pod C<dzil> tool has C<--trial> command line option to build a "release that PAUSE will not index".
#pod
#pod =back
#pod
#pod I think using word "alpha" by C<version> module is a confusing, because C<version> does not provide
#pod any support for "beta" and "rc" ("release candidate"). Thus, "alpha" term is dropped in favor of
#pod more generic term "trial": trial could be either "alpha", "beta", or "rc".
#pod
#pod However, C<Version::Dotted> does not define C<is_trial> method but leaves this for subclasses.
#pod
#pod =cut

package Version::Dotted;

use strict;
use warnings;
use warnings::register;
use version 0.77 qw{};

# ABSTRACT: TODO
our $VERSION = 'v0.0.0_04'; # TRIAL VERSION

use parent 'version';
use overload (
    'cmp' => \&_cmp,
    '<=>' => \&_cmp,
);

sub _min_len { 1 };     ## no critic ( RequireFinalReturn )
sub _max_len { 1000 };  ## no critic ( RequireFinalReturn )
# TODO: INTMAX?

sub _warn {
    my ( $self, $message ) = @_;
    warnings::warnif( 'Version::Dotted', $message );
    return;
};

# --------------------------------------------------------------------------------------------------

#pod =Method C<new>
#pod
#pod Constructs new version object.
#pod
#pod     $version = Version::Dotted->new( $arg );
#pod
#pod The constructor accepts one argument and creates dotted version object. An argument can be either
#pod integer number (C<1>),  floating point number (C<1.2>), v-string (C<v1.2>), or string (with or
#pod without leading v: C<'1.2'>, C<'v1.2'>), or version object. Trailing zero parts are stripped,
#pod leading zeros in parts are insignificant:
#pod
#pod     Version::Dotted->new( 1.2.0 ) == Version::Dotted->new( v1.2 )
#pod     Version::Dotted->new( 1.002 ) == Version::Dotted->new( v1.2 )
#pod
#pod TODO: Issue a warning if argument is not v-string or string?
#pod
#pod Actually, C<Version::Dotted> has a notion of "minimum number of parts": version object is
#pod maintained to have at least minimum number of parts. In C<Version::Dotted> minimum number of parts
#pod is C<1>, but subclasses may raise the bar.
#pod
#pod =caveat Leading Zeros
#pod
#pod Leading zeros in parts are insignificant:
#pod
#pod     Version::Dotted->new( v01.02.03 ) == Version::Dotted->new( v1.2.3 );
#pod     Version::Dotted->new( 1.002 ) == Version::Dotted->new( v1.2 );
#pod
#pod However, Perl interprets numbers with leading zero as octal, so aware of:
#pod
#pod     Version::Dotted->new( 010 ) == v8;
#pod     Version::Dotted->new( 010.011 ) == v89;         # oops
#pod     Version::Dotted->new( 010.011.012 ) == v8910;   # ooops
#pod
#pod To avoid surprises stick to using v-strings or strings:
#pod
#pod     Version::Dotted->new( v010 ) == Version::Dotted->new( v10 );
#pod     Version::Dotted->new( v010.011 ) == Version::Dotted->new( v10.10 );
#pod     Version::Dotted->new( '010.011' ) == Version::Dotted->new( v10.10 );
#pod
#pod =cut

sub new {
    my ( $class, $arg ) = @_;
    my $v;
    defined $arg or $class->_warn( "Use of undefined value to construct version" );
    if ( ref( $arg ) and $arg->isa( 'version' ) ) {
        $v = $class->declare( 0 );                      # Create a new version object.
        $v->{ version } = [ @{ $arg->{ version } } ];   # Copy version parts.
    } else {
        $v = $class->declare( $arg // 0 );
    };
    return $v->_norm();
};

# --------------------------------------------------------------------------------------------------

#pod =method C<part>
#pod
#pod Returns i-th part of the version.
#pod
#pod     $int = $v->part( $i );  # Get i-th part.
#pod
#pod If index is larger than actual number of version parts minus one, C<undef> is returned.
#pod
#pod Negative part index causes warning but works like index to regular Perl array: C<-1> is index
#pod of the last version part, C<-2> — second last, etc.
#pod
#pod =cut

sub part {
    my ( $self, $idx ) = @_;
    $idx >= 0 or $self->_warn( "Negative version part index '$idx'" );
    return $self->{ version }->[ $idx ];
};

# --------------------------------------------------------------------------------------------------

#pod =method C<bump>
#pod
#pod Bumps i-th version part and drops all the parts behind i-th.
#pod
#pod     $v->bump( $i );
#pod
#pod If index is larger than actual number of version parts (minus one), missed parts are autovivified
#pod with zero values. If result of bumping is bigger than allowed upper boundary for the part (C<999>
#pod for all the parts except the first), warning is printed.
#pod
#pod Negative part index causes warning but works.
#pod
#pod The method returns reference to version object.
#pod
#pod =cut

sub bump {
    my ( $self, $idx ) = @_;
    my $v = $self->{ version };
    if ( $idx < - abs( @$v ) ) {
        $self->_warn( "Invalid version part index '$idx'" );
        return;
    };
    $idx >= 0 or $self->_warn( "Negative version part index '$idx'" );
    ++ $v->[ $idx ];
    if ( $idx == -1 ) {
        # -1 denotes the last part, nothing to delete behind it.
    } else {
        # Ok, it is not the last part, let us delete everything behind it:
        splice( @$v, $idx + 1 );
    };
    return $self->_norm();
};

# --------------------------------------------------------------------------------------------------

#pod =operator C<E<lt>=E<gt>>
#pod
#pod Compares two versions.
#pod
#pod     $v <=> $other;
#pod
#pod The operator is inherited from parent's class (see L<version/"How to compare version objects">).
#pod However, there is a difference: if C<$other> is not a version object, it converted to a version
#pod object using C<new> (I<not> C<parse>).
#pod
#pod Other comparison operators (e. g. C<E<lt>>, C<E<gt>>, C<E<lt>=>, etc) are created by Perl.
#pod
#pod =operator C<cmp>
#pod
#pod The same as C<E<lt>=E<gt>>.
#pod
#pod =cut

sub _cmp {
    my ( $self, $other, $swap ) = @_;
    $other = $self->_obj( $other );
    no strict 'refs';                       ## no critic ( ProhibitNoStrict )
    return &{ 'version::(cmp' }( $self, $other, $swap );
};

# --------------------------------------------------------------------------------------------------

# If $arg is a version object, return it as-is. Otherwise create a version object and return it.
sub _obj {
    my ( $self, $arg ) = @_;
    if ( not defined $arg or not $arg->isa( 'version' ) ) {
        $arg = $self->new( $arg );
    };
    return $arg;
};

# --------------------------------------------------------------------------------------------------

# Normalize version representation.
sub _norm {
    my ( $self ) = @_;
    my $v = $self->{ version };
    my $m = $self->_min_len;
    # Make sure there are no undefined elements in the array (which can appear after `bump`):
    $_ // ( $_ = 0 ) for @$v;
    # Make sure we have at least $m parts:
    while ( @$v < $m ) {
        push( @$v, 0 );
    };
    # Drop zero parts from the end (but keep at lest $m parts):
    while ( @$v > $m and $v->[ -1 ] == 0 ) {
      -- $#$v;
    };
    # Update version string representation:
    my $s = 'v' . join( '.', @$v );
    $self->{ original } = $s;
    # Check number of parts:
    @$v <= $self->_max_len or $self->_warn( "Bad version '$s': too many parts" );
    # Verify all the parts after the first are in range [0..999]:
    for my $i ( 1 .. $#$v ) {
        $v->[ $i ] <= 999
            or $self->_warn( "Bad version '$s': too large part #$i '$v->[ $i ]'" );
    };
    return $self;
};

# --------------------------------------------------------------------------------------------------

#pod =Method C<parse>
#pod
#pod This method issues warning "Operation 'parse' is not supported" and always returns C<undef>.
#pod
#pod (The parent's method creates decimal version object. However, this class is intended to create only
#pod dotted-decimal version objects.)
#pod
#pod =cut

sub parse {
    my ( $class ) = @_;
    $class->_warn( "Operation 'parse' is not supported" );
    return;
};

# --------------------------------------------------------------------------------------------------

#pod =method C<is_alpha>
#pod
#pod The method prints a warning and always returns C<undef>.
#pod
#pod =cut

sub is_alpha {
    my ( $self ) = @_;
    $self->_warn( "Operation 'is_alpha' is not supported" );
    return;
};

# --------------------------------------------------------------------------------------------------

#pod =method C<numify>
#pod
#pod The method prints a warning and always returns C<undef>.
#pod
#pod =cut

sub numify {
    my ( $self ) = @_;
    $self->_warn( "operation 'numify' is not supported" );
    return;
};

# --------------------------------------------------------------------------------------------------

#pod =method C<stringify>
#pod
#pod     $str = $v->stringify;
#pod
#pod The method is inherited from the parent class.
#pod
#pod Since the C<Version::Dotted> class constructs only dotted version objects, result of
#pod stringification is always a dotted version string with leading C<'v'>, e. g.:
#pod
#pod     Version::Dotted->new( 1.2 )->stringify eq 'v1.2';
#pod
#pod =operator ""
#pod
#pod The same as C<stringify>.
#pod
#pod     $v->stringify eq "$v";
#pod
#pod =cut

# --------------------------------------------------------------------------------------------------

#pod =head1 EXPORT
#pod
#pod The module exports nothing by default. The module installs C<qv> function (I<not> a method) into
#pod caller namespace by explicit request:
#pod
#pod     use Version::Dotted qw{ qv };
#pod
#pod The module (unlike to C<version>) does not play any tricks with importer's C<VERSION> and/or
#pod C<UNIVERSAL::VERSION>.
#pod
#pod =func qv
#pod
#pod Shortcut for C<Version::Dotted->new>.
#pod
#pod     $v = Version::Dotted->new( $arg );
#pod     $v = qv( $arv );
#pod
#pod Note: There is I<no> function C<Version::Dotted::qv>, C<qv> function is installed into importer
#pod package by explicit request, see L</"EXPORT">.
#pod
#pod =cut

#   We have to redefine parents' import. Otherwise we will export `qv` into importer namespace by
#   default. Explicit import of `qv` could be a good idea, though.

sub import {
    my ( $class, @list ) = @_;
    my $pkg = caller();
    my %args = map( { $_ => 1 } @list );
    if ( delete( $args{ qv } ) ) {
        no strict 'refs';               ## no critic ( ProhibitNoStrict )
        *{ $pkg . '::qv' } = sub {
            return $class->new( @_ );
        };
    };
    if ( %args ) {
        $class->_warn( "Bad $class import: " . join( ', ', map( { "'$_'" } keys( %args ) ) ) );
    };
    return;
};

1;

# --------------------------------------------------------------------------------------------------

#pod =head1 SEE ALSO
#pod
#pod =begin :list
#pod
#pod = L<version>
#pod
#pod Parent class. It provides most of functionality, can work with decimal versions, but does not
#pod provide any modifiers. Release status depends on presence of underscore character in version.
#pod
#pod = L<Perl::Version>
#pod
#pod An alternative to C<version>. Works with both decimal and dotted versions, provides modification
#pod operations. Release status depends on presence of underscore character in version.
#pod
#pod = L<Version::Dotted::Semantic>
#pod
#pod Subclass implementing Perlish approach to Semantic Versioning.
#pod
#pod = L<Version::Dotted::Odd>
#pod
#pod Subclass implementing odd-even versioning scheme.
#pod
#pod =end :list
#pod
#pod =head1 COPYRIGHT AND LICENSE
#pod
#pod Copyright (C) 2016 Van de Bugger
#pod
#pod License GPLv3+: The GNU General Public License version 3 or later
#pod <http://www.gnu.org/licenses/gpl-3.0.txt>.
#pod
#pod This is free software: you are free to change and redistribute it. There is
#pod NO WARRANTY, to the extent permitted by law.
#pod
#pod
#pod =cut

#   ------------------------------------------------------------------------------------------------
#
#   file: doc/what.pod
#
#   This file is part of perl-Version-Dotted.
#
#   ------------------------------------------------------------------------------------------------

#pod =encoding UTF-8
#pod
#pod =head1 WHAT?
#pod
#pod TODO
#pod
#pod =cut

# end of file #
#   ------------------------------------------------------------------------------------------------
#
#   file: doc/why.pod
#
#   This file is part of perl-Version-Dotted.
#
#   ------------------------------------------------------------------------------------------------

#pod =encoding UTF-8
#pod
#pod =head1 WHY?
#pod
#pod I always used C<version> (a module recommended by C<Task::Kensho>) to compare versions:
#pod
#pod     if ( version->parse( $Module::VERSION ) < '0.10.1' ) {
#pod         plan skip_all => "Module $Module::VERSION too old";
#pod     };
#pod
#pod When I had a need to manipulate versions, I started to use C<Perl::Version> (another module
#pod recommended by C<Task::Kensho>) because C<version> does not provide any method to modify version
#pod object. I wanted to bump version of a distribution automatically after release, and
#pod C<Perl::Version> did the job for me:
#pod
#pod     my $v = Perl::Version->new( $self->zilla->version );
#pod     $v->inc_alpha();
#pod
#pod (The idea is: If I just released v0.10.1, the version of the next release would be automatically
#pod set to v0.10.1_01. If I released v0.10.1_01, the next version would be v0.10.1_02, and so on. If I
#pod decided it is time to release non-trial version, I would manually set version to v0.10.2 or
#pod whatever else, e. g. v0.11.0 or v1.0.0.)
#pod
#pod Everything was ok. However, I accidentally found that
#pod
#pod     version->parse( 'v0.10.1_01' ) > 'v0.10.2'  # is true
#pod
#pod Oops. That's was quite surprising, because I remembered some time ago this expression had opposite
#pod result:
#pod
#pod     version->parse( 'v0.10.1_01' ) < 'v0.10.2'  # was true
#pod
#pod Little investigation shown the breaking change is in C<version> 0.9913: earlier versions interpret
#pod underscore as version part delimiter ('v0.10.1_01' is the same as 'v0.10.1.1'+trial), but 0.9913
#pod and later versions do not ('v0.10.1_01' is the same as 'v.10.101'+trial).
#pod
#pod Ignoring underscore is probably a right thing to do, because it is the way how Perl itself
#pod interprets v-strings:
#pod
#pod     v0.10.1_01 eq v0.10.101     # is true
#pod
#pod but it is definitely a change which makes C<Perl::Version> useless (to me).
#pod
#pod =cut

# end of file #


# end of file #

__END__

=pod

=encoding UTF-8

=head1 NAME

Version::Dotted - TODO

=head1 VERSION

Version v0.0.0_04, released on 2016-12-27 20:03 UTC.
This is a B<trial release>.

=head1 WHAT?

TODO

=for test_synopsis my ( $v, $i, $p );

=head1 SYNOPSIS

    use Version::Dotted;            # import nothing
    use Version::Dotted qw{ qv };   # import qv

    # Construct:
    $v = Version::Dotted->new( v1.2.3 );    # the same as qv( v1.2.3 )
    $v = qv( v1.2.3 );      # v1.2.3
    $v = qv( '1.2.0' );     # v1.2 (trailing zero parts ignored)
    $v = qv( 'v1' );        # v1

    # Access individual parts:
    $p = $v->part( $i );    # Get i-th part.

    # Stringify:
    $v->stringify;          # "v1.2.3" (always with 'v' prefix)
    "$v";                   # ditto

    # Bump the version:
    $v->bump( $i );         # Bump i-th part
                            # and drop all parts behind i-th.

    # Compare:
    $v > v1.2.3;
    $v == '1.2.3';

=head1 DESCRIPTION

C<Version::Dotted> is a subclass of C<version>.

The class narrows C<version> — C<Version::Dotted> creates only I<dotted> (aka dotted-decimal)
version objects, support for creating decimal versions is not provided. Support for "alpha"
versions is dropped too (subclasses provides support for "trial" versions instead).

The class extends C<version> — C<Version::Dotted> objects are I<modifiable>.

=head2 Dotted Version

Dotted (aka dotted-decimal) version is a series of parts joined with dots, each part is a cardinal
(non-negative) integer. Every part (except the first) should be in range [0..999], the first part
can be bigger than 999.

See also L<version::Internals/"Dotted-Decimal Versions">.

=head2 Warnings

Methods report error conditions by C<warnings::warnif>. It gives the caller flexibility: warning
may be either suppressed

    no warnings 'Version::Dotted';

or made fatal:

    use warnings FATAL => 'Version::Dotted';

=head2 Release Status

Unfortunately, Perl terminology in this area in not well-defined and not consistently used:

=over

=item *

The C<version> module names a version containing underscore "alpha version" and refers to CPAN.

=item *

C<CPAN::Meta::Spec> defines status as one of: C<stable>, C<testing>, and C<unstable>. Word "alpha"
is used in the description of C<unstable> release, while C<testing> release is described as "beta".
There is also requirement that C<stable> release version should not contain underscore.

=item *

pause.perl.org site has section named "Developer Releases" which is about releasing "code for
testing". Such releases should either have underscore in version or "-TRIAL" suffix.

=item *

meta::cpan site in the list of module releases shows "DEV" after versions containing underscore.

=item *

C<dzil> tool has C<--trial> command line option to build a "release that PAUSE will not index".

=back

I think using word "alpha" by C<version> module is a confusing, because C<version> does not provide
any support for "beta" and "rc" ("release candidate"). Thus, "alpha" term is dropped in favor of
more generic term "trial": trial could be either "alpha", "beta", or "rc".

However, C<Version::Dotted> does not define C<is_trial> method but leaves this for subclasses.

=head1 EXPORT

The module exports nothing by default. The module installs C<qv> function (I<not> a method) into
caller namespace by explicit request:

    use Version::Dotted qw{ qv };

The module (unlike to C<version>) does not play any tricks with importer's C<VERSION> and/or
C<UNIVERSAL::VERSION>.

=head1 CLASS METHODS

=head2 C<new>

Constructs new version object.

    $version = Version::Dotted->new( $arg );

The constructor accepts one argument and creates dotted version object. An argument can be either
integer number (C<1>),  floating point number (C<1.2>), v-string (C<v1.2>), or string (with or
without leading v: C<'1.2'>, C<'v1.2'>), or version object. Trailing zero parts are stripped,
leading zeros in parts are insignificant:

    Version::Dotted->new( 1.2.0 ) == Version::Dotted->new( v1.2 )
    Version::Dotted->new( 1.002 ) == Version::Dotted->new( v1.2 )

TODO: Issue a warning if argument is not v-string or string?

Actually, C<Version::Dotted> has a notion of "minimum number of parts": version object is
maintained to have at least minimum number of parts. In C<Version::Dotted> minimum number of parts
is C<1>, but subclasses may raise the bar.

=head2 C<parse>

This method issues warning "Operation 'parse' is not supported" and always returns C<undef>.

(The parent's method creates decimal version object. However, this class is intended to create only
dotted-decimal version objects.)

=head1 OBJECT METHODS

=head2 C<part>

Returns i-th part of the version.

    $int = $v->part( $i );  # Get i-th part.

If index is larger than actual number of version parts minus one, C<undef> is returned.

Negative part index causes warning but works like index to regular Perl array: C<-1> is index
of the last version part, C<-2> — second last, etc.

=head2 C<bump>

Bumps i-th version part and drops all the parts behind i-th.

    $v->bump( $i );

If index is larger than actual number of version parts (minus one), missed parts are autovivified
with zero values. If result of bumping is bigger than allowed upper boundary for the part (C<999>
for all the parts except the first), warning is printed.

Negative part index causes warning but works.

The method returns reference to version object.

=head2 C<is_alpha>

The method prints a warning and always returns C<undef>.

=head2 C<numify>

The method prints a warning and always returns C<undef>.

=head2 C<stringify>

    $str = $v->stringify;

The method is inherited from the parent class.

Since the C<Version::Dotted> class constructs only dotted version objects, result of
stringification is always a dotted version string with leading C<'v'>, e. g.:

    Version::Dotted->new( 1.2 )->stringify eq 'v1.2';

=head1 FUNCTIONS

=head2 qv

Shortcut for C<Version::Dotted->new>.

    $v = Version::Dotted->new( $arg );
    $v = qv( $arv );

Note: There is I<no> function C<Version::Dotted::qv>, C<qv> function is installed into importer
package by explicit request, see L</"EXPORT">.

=head1 OPERATORS

=head2 C<E<lt>=E<gt>>

Compares two versions.

    $v <=> $other;

The operator is inherited from parent's class (see L<version/"How to compare version objects">).
However, there is a difference: if C<$other> is not a version object, it converted to a version
object using C<new> (I<not> C<parse>).

Other comparison operators (e. g. C<E<lt>>, C<E<gt>>, C<E<lt>=>, etc) are created by Perl.

=head2 C<cmp>

The same as C<E<lt>=E<gt>>.

=head2 ""

The same as C<stringify>.

    $v->stringify eq "$v";

=head1 CAVEATS

=head2 Leading Zeros

Leading zeros in parts are insignificant:

    Version::Dotted->new( v01.02.03 ) == Version::Dotted->new( v1.2.3 );
    Version::Dotted->new( 1.002 ) == Version::Dotted->new( v1.2 );

However, Perl interprets numbers with leading zero as octal, so aware of:

    Version::Dotted->new( 010 ) == v8;
    Version::Dotted->new( 010.011 ) == v89;         # oops
    Version::Dotted->new( 010.011.012 ) == v8910;   # ooops

To avoid surprises stick to using v-strings or strings:

    Version::Dotted->new( v010 ) == Version::Dotted->new( v10 );
    Version::Dotted->new( v010.011 ) == Version::Dotted->new( v10.10 );
    Version::Dotted->new( '010.011' ) == Version::Dotted->new( v10.10 );

=head1 WHY?

I always used C<version> (a module recommended by C<Task::Kensho>) to compare versions:

    if ( version->parse( $Module::VERSION ) < '0.10.1' ) {
        plan skip_all => "Module $Module::VERSION too old";
    };

When I had a need to manipulate versions, I started to use C<Perl::Version> (another module
recommended by C<Task::Kensho>) because C<version> does not provide any method to modify version
object. I wanted to bump version of a distribution automatically after release, and
C<Perl::Version> did the job for me:

    my $v = Perl::Version->new( $self->zilla->version );
    $v->inc_alpha();

(The idea is: If I just released v0.10.1, the version of the next release would be automatically
set to v0.10.1_01. If I released v0.10.1_01, the next version would be v0.10.1_02, and so on. If I
decided it is time to release non-trial version, I would manually set version to v0.10.2 or
whatever else, e. g. v0.11.0 or v1.0.0.)

Everything was ok. However, I accidentally found that

    version->parse( 'v0.10.1_01' ) > 'v0.10.2'  # is true

Oops. That's was quite surprising, because I remembered some time ago this expression had opposite
result:

    version->parse( 'v0.10.1_01' ) < 'v0.10.2'  # was true

Little investigation shown the breaking change is in C<version> 0.9913: earlier versions interpret
underscore as version part delimiter ('v0.10.1_01' is the same as 'v0.10.1.1'+trial), but 0.9913
and later versions do not ('v0.10.1_01' is the same as 'v.10.101'+trial).

Ignoring underscore is probably a right thing to do, because it is the way how Perl itself
interprets v-strings:

    v0.10.1_01 eq v0.10.101     # is true

but it is definitely a change which makes C<Perl::Version> useless (to me).

=head1 SEE ALSO

=over 4

=item L<version>

Parent class. It provides most of functionality, can work with decimal versions, but does not
provide any modifiers. Release status depends on presence of underscore character in version.

=item L<Perl::Version>

An alternative to C<version>. Works with both decimal and dotted versions, provides modification
operations. Release status depends on presence of underscore character in version.

=item L<Version::Dotted::Semantic>

Subclass implementing Perlish approach to Semantic Versioning.

=item L<Version::Dotted::Odd>

Subclass implementing odd-even versioning scheme.

=back

=head1 AUTHOR

Van de Bugger <van.de.bugger@gmail.com>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2016 Van de Bugger

License GPLv3+: The GNU General Public License version 3 or later
<http://www.gnu.org/licenses/gpl-3.0.txt>.

This is free software: you are free to change and redistribute it. There is
NO WARRANTY, to the extent permitted by law.

=cut
