Chris,

> You can use the fact that the attribute's default is called as a method on
> the instance itself to look up the associated metaclass, then the attribute,
> then the type constraint...  At that point the type constraint, if any,
> should exist. :)

Wait ... what? the default sub is called as a method? no shit? <test
test test> No shit!  It really is ... I never knew that.  Very cool!

Okay, for the curious, here's basically what I ended up with.  I will
need to add tests and polish up the error checking, but it's probably
most of the way towards being releasable.  Unfortunately, I don't know
when I'm going to find the time to finish it up--testing user input
stuff is a big PITA; I'll probably need to gen up a dummy test chrome
and all that.  But, anyway, here's some code if someone didn't feel
like waiting for me to get organized and wanted to just run with it.
Comments are for my more junior teammates and not necessarily for you
guys.

Thanks to everyone for all the great help!


            -- Buddy


use MooseX::Declare;
use Method::Signatures::Modifiers;


role MooseX::PromptableAttributes::Trait::Attribute
{
    Moose::Util::meta_attribute_alias 'Promptable';

    has prompt => ( is => 'ro', isa => 'Str' );

    around _process_options ($class: $name, $options)
    {
        # You have to make a copy of $options->{prompt} because of the
way closures work.  See,
        # $options is a lexical variable, and that will be saved
inside the sub we return.  But it's
        # still just a pointer to the actual structure we were passed.
 By the time our sub is
        # actually _called_ (i.e. at object instantiation time), that
structure won't exist any
        # more.  So we need to save a copy of the prompt string in
another lexical so it can get
        # bundled up in the closure instead.  See?

        if (my $prompt = $options->{'prompt'})
        {
            # Ditto about $options->{default}.
            # We'll allow a default for an attribute with a prompt,
but only if it's a simple
            # scalar.  If provided, this default will become the
default for our prompt (i.e., what
            # the user gets if they just hit RETURN).

            my $default;
            if (exists $options->{'default'})
            {
                $default = $options->{'default'};
                die("default for a promptable attribute must be a
scalar") if ref $default;
            }

            # This is only needed for the error message below.  Sure,
it might not exist, but then
            # there'll be no type constrait and consequently no error,
so it would never be used in
            # that case.
            my $type = $options->{'isa'};

            $options->{'default'} = method
            {
                my $validator = sub
                {
                    # Yes, this is a closure inside a closure.  It's
not as bad as it seems, though.
                    # Just take it slow.

                    my ($val) = @_;

                    # remember: the $self below is for the default
method, *not* the validator sub
                    # (the validator sub doesn't have a $self: it's
not a method)
                    my $attr = $self->meta->get_attribute($name);

                    # just care whether
verify_against_type_constraint() blows up or not
                    if ( eval {
$attr->verify_against_type_constraint($val); 1; } )
                    {
                        # didn't blow up; value is good
                        return 1;
                    }
                    else
                    {
                        print "Sorry; value must be a(n) $type\n";
                        return 0;
                    }
                };

                return
$MooseX::PromptableAttributes::chrome->prompt($prompt, $validator,
$default);
            };
        }

        return $class->$orig($name, $options);
    }
}


# All packages succeed
1;

Reply via email to