App-Sqitch

 view release on metacpan or  search on metacpan

lib/App/Sqitch/Role/TargetConfigCommand.pm  view on Meta::CPAN

requires 'options';
requires 'configure';
requires 'sqitch';
requires 'extra_target_keys';
requires 'default_target';

has properties => (
    is  => 'ro',
    isa => HashRef,
    default => sub { {} },
);

around options => sub {
    my ($orig, $class) = @_;
    return ($class->$orig), (map { "$_=s" } $class->extra_target_keys), qw(
        plan-file|f=s
        registry=s
        client=s
        extension=s
        top-dir=s
        dir|d=s%
        set|s=s%
    );
};

around configure => sub {
    my ( $orig, $class, $config, $opt ) = @_;

    # Grab the options we're responsible for.
    my $props = {};
    for my $key (
        $class->extra_target_keys,
        qw(plan_file registry client extension top_dir dir)
    ) {
        $props->{$key} = delete $opt->{$key} if exists $opt->{$key};
    }

    # Let the command take care of its options.
    my $params = $class->$orig($config, $opt);

    # Convert file option to Class::Path::File object.
    if ( my $file = $props->{plan_file} ) {
        $props->{plan_file} = file($file)->cleanup;
    }

    # Convert directory option to Class::Path::Dir object.
    if ( my $file = $props->{top_dir} ) {
        $props->{top_dir} = dir($file)->cleanup;
    }

    # Convert URI.
    if ( my $uri = $props->{uri} ) {
        require URI;
        $props->{uri} = URI->new($uri);
    }

    # Convert directory properties to Class::Path::Dir objects.
    if (my $dirs = delete $props->{dir}) {
        my %ok_keys = map {; $_ => undef } (
            qw(reworked),
            map { ($_, "reworked_$_") } qw(deploy revert verify)
        );

        my @unknown;
        for my $key (keys %{ $dirs }) {
            unless (exists $ok_keys{$key}) {
                push @unknown => $key;
                next;
            }
            $props->{"$key\_dir"} = dir(delete $dirs->{$key})->cleanup
        }

        if (@unknown) {
            hurl $class->command => __nx(
                'Unknown directory name: {dirs}',
                'Unknown directory names: {dirs}',
                @unknown,
                dirs => join(__ ', ', sort @unknown),
            );
        }
    }

    # Copy variables.
    if ( my $vars = $opt->{set} ) {
        $props->{variables} = $vars;
    }

    # All done.
    $params->{properties} = $props;
    return $params;
};

sub BUILD {
    my $self = shift;
    my $props = $self->properties;

    if (my $engine = $props->{engine}) {
        # Validate engine.
        hurl $self->command => __x(
            'Unknown engine "{engine}"', engine => $engine
        ) unless first { $engine eq $_ } App::Sqitch::Command::ENGINES;
    }

    if (my $uri = $props->{uri}) {
        # Validate URI.
        hurl $self->command => __x(
            'URI "{uri}" is not a database URI',
            uri => $uri,
        ) unless eval { $uri->isa('URI::db') };

        my $engine = $uri->canonical_engine or hurl $self->command => __x(
            'No database engine in URI "{uri}"',
            uri => $uri,
        );
        hurl $self->command => __x(
            'Unknown engine "{engine}" in URI "{uri}"',
            engine => $engine,
            uri    => $uri,
        ) unless first { $engine eq $_ } App::Sqitch::Command::ENGINES;

    }
}

sub config_target {
    my ($self, %p) = @_;
    my $sqitch = $self->sqitch;
    my $props  = $self->properties;
    my @params = (sqitch => $sqitch);

    if (my $name = $p{name} || $props->{target}) {
        push @params => (name => $name);
        if (my $uri = $p{uri}) {
            push @params => (uri => $uri);
        } else {
            my $config = $sqitch->config;
            if ($name !~ /:/ && !$config->get(key => "target.$name.uri")) {
                # No URI. Give it one.
                my $engine = $p{engine} || $props->{engine}
                    || $config->get(key => 'core.engine')
                    || hurl $self->command => __(
                        'No engine specified; specify via target or core.engine'
                    );
                push @params => (uri => URI::db->new("db:$engine:"));
            }
        }
    } elsif (my $engine = $p{engine} || $props->{engine}) {
        my $config = $sqitch->config;
        push @params => (
            name => $config->get(key => "engine.$engine.target")
                 || $config->get(key => 'core.target')
                 || "db:$engine:"
        );
    } else {
        # Get the name and URI from the default target.
        my $default = $self->default_target;
        push @params => (
            name => $default->name,
            uri  => $default->uri,
        );
    }

    # Return the target with all relevant attributes overridden.
    require App::Sqitch::Target;
    return App::Sqitch::Target->new(
        @params,
        map { $_ => $props->{$_} } grep { $props->{$_} } qw(
            top_dir
            plan_file
            registry
            client
            deploy_dir
            revert_dir
            verify_dir
            reworked_dir
            reworked_deploy_dir
            reworked_revert_dir
            reworked_verify_dir
            extension
        )
    );
}

sub directories_for {
    my $self = shift;
    my $props = $self->properties;
    my (@dirs, %seen);

    for my $target (@_) {
        # Script directories.
        if (my $top_dir = $props->{top_dir}) {
            push @dirs => grep { !$seen{$_}++ } map {
                $props->{"$_\_$_"} || $top_dir->subdir($_);
            } qw(deploy revert verify);
        } else {
            push @dirs => grep { !$seen{$_}++ } map {
                my $name = "$_\_dir";
                $props->{$name} || $target->$name;
            } qw(deploy revert verify);
        }

        # Reworked script directories.
        if (my $reworked_dir = $props->{reworked_dir} || $props->{top_dir}) {
            push @dirs => grep { !$seen{$_}++ } map {
                $props->{"reworked_$_\_dir"} || $reworked_dir->subdir($_);
            } qw(deploy revert verify);
        } else {
            push @dirs => grep { !$seen{$_}++ } map {
                my $name = "reworked_$_\_dir";
                $props->{$name} || $target->$name;
            } qw(deploy revert verify);
        }
    }

    return @dirs;
}

sub make_directories_for {
    my $self  = shift;
    $self->mkdirs( $self->directories_for(@_) );
}

sub mkdirs {
    my $self = shift;

    for my $dir (@_) {
        next if -d $dir;
        my $sep = dir('')->stringify; # OS-specific directory separator.
        $self->info(__x(
            'Created {file}',
            file => "$dir$sep"
        )) if make_path $dir, { error => \my $err };
        if ( my $diag = shift @{ $err } ) {
            my ( $path, $msg ) = %{ $diag };
            hurl $self->command => __x(
                'Error creating {path}: {error}',
                path  => $path,
                error => $msg,
            ) if $path;
            hurl $self->command => $msg;
        }
    }

    return $self;
}

sub write_plan {
    my ( $self, %p ) = @_;
    my $project = $p{project};
    my $uri     = $p{uri};
    my $target  = $p{target} || $self->config_target;
    my $file    = $target->plan_file;

    unless ($project && $uri) {
        # Find a plan to copy the project name and URI from.
        my $conf_plan = $target->plan;
        my $def_plan  = $self->default_target->plan;
        if (try { $def_plan->project }) {
            $project ||= $def_plan->project;
            $uri     ||= $def_plan->uri;
        } elsif (try { $conf_plan->project }) {
            $project ||= $conf_plan->project;
            $uri     ||= $conf_plan->uri;
        } else {
            hurl $self->command => __x(
                'Missing %project pragma in {file}',
                file => $file,
            ) unless $project;
        }
    }

lib/App/Sqitch/Role/TargetConfigCommand.pm  view on Meta::CPAN

            push @vars => map {{
                key   => "$key.$prop.$_",
                value => $val->{$_},
            }} keys %{ $val };
        } else {
            push @vars => {
                key   => "$key.$prop",
                value => $val,
            };
        }
    }
    return \@vars;
}

1;

__END__

=head1 Name

App::Sqitch::Role::TargetConfigCommand - A command that handles target-related configuration

=head1 Synopsis

  package App::Sqitch::Command::init;
  extends 'App::Sqitch::Command';
  with 'App::Sqitch::Role::TargetConfigCommand';

=head1 Description

This role encapsulates the common attributes and methods required by commands
that deal with change script configuration, including script directories and
extensions.

=head1 Interface

=head2 Class Methods

=head3 C<options>

  my @opts = App::Sqitch::Command::checkout->options;

Adds options common to the commands that manage script configuration.

=head3 C<configure>

Configures the options common to commands manage script configuration.

=head2 Attributes

=head3 C<properties>

A hash reference of target configurations. The keys may be as follows:

=over

=item C<deploy>

=item C<revert>

=item C<verify>

=item C<reworked>

=item C<reworked_deploy>

=item C<reworked_revert>

=item C<reworked_verify>

=item C<extension>

=back

=head2 Instance Methods

=head3 C<config_target>

  my $target = $cmd->config_target;
  my $target = $cmd->config_target(%params);

Constructs a target based on the contents of C<properties>. The supported
parameters are:

=over

=item C<name>

A target name.

=item C<uri>

A target URI.

=item C<engine>

An engine name.

=back

The passed target and engine names take highest precedence, falling back on
the properties and the C<default_target>. All other properties are applied to
the target before returning it.

=head3 C<write_plan>

  $cmd->write_plan(%params);

Writes out the plan file. Supported parameters are:

=over

=item C<target>

The target for which the plan will be written. Defaults to the target returned
by C<config_target()>.

=item C<project>

The project name. If not passed, the project name will be read from the
default target's plan, if it exists. Otherwise an error will be thrown.

=item C<uri>

The project URI. Optional. If not passed, the URI will be read from the
default target's plan, if it exists. Optional.

=back



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