The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# ============================================================================
package MooseX::App::Meta::Role::Attribute::Option;
# ============================================================================

use utf8;
use 5.010;

use namespace::autoclean;
use Moose::Role;

has 'cmd_type' => (
    is          => 'rw',
    isa         => 'MooseX::App::Types::CmdTypes',
    predicate   => 'has_cmd_type',
);

has 'cmd_tags' => (
    is          => 'rw',
    isa         => 'ArrayRef[Str]',
    predicate   => 'has_cmd_tags',
);

has 'cmd_flag' => (
    is          => 'rw',
    isa         => 'Str',
    predicate   => 'has_cmd_flag',
);

has 'cmd_aliases' => (
    is          => 'rw',
    isa         => 'MooseX::App::Types::List',
    predicate   => 'has_cmd_aliases',
    coerce      => 1,
);

has 'cmd_split' => (
    is          => 'rw',
    isa         => Moose::Util::TypeConstraints::union([qw(Str RegexpRef)]),
    predicate   => 'has_cmd_split',
);

has 'cmd_position' => (
    is => 'rw',
    isa => 'Int',
    default => 0,
);

my $GLOBAL_COUNTER = 1;

around 'new' => sub {
    my $orig = shift;
    my $class = shift;
    
    my $self = $class->$orig(@_);
    
    if ($self->has_cmd_type) {
        if ($self->cmd_position == 0) {
            $GLOBAL_COUNTER++;
            $self->cmd_position($GLOBAL_COUNTER);
        }
    }
    
    return $self;
};


sub cmd_is_bool {
    my ($self) = @_; 
   
    if ($self->has_type_constraint
        && $self->type_constraint->is_a_type_of('Bool')) {
        
        # Bool and defaults to true 
        if ($self->has_default 
            && ! $self->is_default_a_coderef
            && $self->default == 1) {
            return 0;
        ## Bool and is required
        #} elsif (! $self->has_default
        #    && $self->is_required) {
        #    return 0; 
        }
        
        # Ordinary bool
        return 1;
    }
    
    return undef
}

sub cmd_usage_description {
    my ($self) = @_;
    
    my $description = ($self->has_documentation) ? $self->documentation : '';
    my @tags = $self->cmd_tags_list();
    if (scalar @tags) {
        $description .= ' '
            if $description;
        $description .= '['.join('; ',@tags).']';
    }
    return $description
}   
    
sub cmd_usage_name {
    my ($self) = @_;
    
    if ($self->cmd_type eq 'parameter') {
        return $self->cmd_name_primary;
    } else {
        return join(' ', 
            map { (length($_) == 1) ? "-$_":"--$_" } 
            $self->cmd_name_possible);
    }
}

sub cmd_name_primary {
    my ($self) = @_;
    
    if ($self->has_cmd_flag) {
        return $self->cmd_flag;
    } else {
        return $self->name;
    }
}

sub cmd_name_possible {
    my ($self) = @_;
    
    my @names = ($self->cmd_name_primary);
    
    if ($self->has_cmd_aliases) {
        push(@names, @{$self->cmd_aliases});
    }
    
    return @names;
}

sub cmd_tags_list {
    my ($self) = @_;
    
    my @tags;
    
    if ($self->is_required
        && ! $self->is_lazy_build
        && ! $self->has_default) {
        push(@tags,'Required')
    }
    
    if ($self->has_default && ! $self->is_default_a_coderef) {
        if ($self->has_type_constraint
            && $self->type_constraint->is_a_type_of('Bool')) {
#            if ($attribute->default) {
#                push(@tags,'Default:Enabled');
#            } else {
#                push(@tags,'Default:Disabled');
#            }
        } else {
            push(@tags,'Default:"'.$self->default.'"');
        }
    }
    
    if ($self->has_type_constraint) {
        my $type_constraint = $self->type_constraint;
        if ($type_constraint->is_subtype_of('ArrayRef')) {
            if ($self->has_cmd_split) {
                my $split = $self->cmd_split;
                if (ref($split) eq 'Regexp') {
                    $split = "$split";
                    $split =~ s/^\(\?\^\w*:(.+)\)$/$1/x;
                }
                push(@tags,'Multiple','Split by "'.$split.'"');
            } else {
                push(@tags,'Multiple');
            }
        } elsif ($type_constraint->is_a_type_of('HashRef')) {
            push(@tags,'Key-Value');
        }
        unless ($self->should_coerce) {
            if ($type_constraint->is_a_type_of('Int')) {
                push(@tags,'Integer');
            } elsif ($type_constraint->is_a_type_of('Num')) {
                push(@tags ,'Number');
            } elsif ($type_constraint->is_a_type_of('Bool')) {
                push(@tags ,'Flag');
            }
        }
    }
    
    if ($self->can('cmd_tags')
        && $self->can('cmd_tags')
        && $self->has_cmd_tags) {
        push(@tags,@{$self->cmd_tags});
    }
    
    return @tags;
}

{
    package Moose::Meta::Attribute::Custom::Trait::AppOption;
    
    use strict;
    use warnings;
    
    sub register_implementation { return 'MooseX::App::Meta::Role::Attribute::Option' }
}

1;

=pod

=encoding utf8

=head1 NAME

MooseX::App::Meta::Role::Attribute::Option - Meta attribute role for options

=head1 DESCRIPTION

This meta attribute role will automatically be applied to all attributes
that should be used as options. 

=head1 ACCESSORS

In your app and command classes you can
use the following attributes in option or parameter definitions.

 option 'myoption' => (
     is                 => 'rw',
     isa                => 'ArrayRef[Str]',
     documentation      => 'My special option',
     cmd_flag           => 'myopt',
     cmd_aliases        => [qw(mopt localopt)],
     cmd_tags           => [qw(Important!)],
     cmd_position       => 1,
     cmd_split          => qr/,/,
 );

=head2 cmd_flag

Use this name instead of the attribute name as the option name

=head2 cmd_type

Option to mark if this attribute should be used as an option or parameter value.

Allowed values are:

=over

=item * option - Command line option

=item * proto - Command line option that should be processed prior to other
options (eg. a config-file option that sets other attribues) Usually only 
used for plugin developmemt

=item * parameter - Positional parameter command line value

=back

=head2 cmd_aliases

Arrayref of alternative option names

=head2 cmd_tags

Extra option tags displayed in the usage information (in brackets)

=head2 cmd_position

Override the order of the parameters in the usage message.

=head2 cmd_split

Splits multiple values at the given separator string or regular expression. 
Only works in conjunction with an 'ArrayRef[*]' type constraint (isa).

=head1 METHODS

These methods are only of interest to plugin authors.

=head2 cmd_name_possible

 my @names = $attribute->cmd_name_possible();

Returns a list of all possible option names.

=head2 cmd_name_primary

 my $name = $attribute->cmd_name_primary();

Returns the primary option name

=head2 cmd_usage_name

 my $name = $attribute->cmd_usage_name();

Returns the name as used by the usage text

=head2 cmd_usage_description

 my $name = $attribute->cmd_usage_description();

Returns the description as used by the usage text

=head2 cmd_tags_list

 my @tags = $attribute->cmd_tags_list();

Returns a list of tags

=head2 cmd_is_bool

 my $bool = $attribute->cmd_is_bool();

Returns true, false or undef depending on the type constraint and default
of the attribute:

=over

=item * undef: Does not have a boolean type constraint

=item * true: Has a boolean type constraint

=item * false: Has a boolean type constraint, and a true default value

=back

=cut