App-karr

 view release on metacpan or  search on metacpan

lib/App/karr/Role/DependencyCheck.pm  view on Meta::CPAN

our $VERSION = '0.500';
use Moo::Role;

# What this role calls on its consumer, said out loud (ticket #128). It used to
# declare nothing, and got away with it only because every consumer happened to
# compose the roles that supply these: store from
# App::karr::Role::BoardDiscovery, find_task from App::karr::Role::BoardAccess,
# json from App::karr::Role::Output, quiet from
# App::karr::Role::SyncLifecycle. App::karr::Role::TaskMutation composes this
# role, so the next command to reach for the mutation path would have inherited
# methods whose collaborators nobody had checked for -- and found out at the
# moment a warning was due, as a "Can't locate object method", rather than at
# compile time.
#
# json is on the list since ticket #137 split the set-time helpers off into
# App::karr::Role::DependencyArgs. It could not be until then: this role also
# carried parse_dependency_ids and assert_dependencies_exist, and the command
# that composed it for those two alone -- create -- has no --json, so requiring
# json refused a consumer that never reaches the reporting half. Leaving it out
# was the narrower hole rather than none: `$self->json || $self->quiet`
# evaluates json first and unconditionally, so a consumer missing it broke on
# every warning, not only the ones without --json.
requires qw( store find_task json quiet );


# Keyed by task id rather than a flat list, because check_dependencies runs
# inside a compare-and-swap callback that re-runs when another agent gets in
# first (App::karr::Role::TaskMutation/update_task_guarded,
# App::karr::Cmd::Pick/_claim_under_lock). A list would grow one copy of every
# warning per attempt; a keyed slot is replaced by the attempt that wins.
has _dependency_warnings => (
    is      => 'ro',
    default => sub { {} },
);

sub check_dependencies {
    my ( $self, $task, $new_status ) = @_;

    my $id = $task->id;
    delete $self->_dependency_warnings->{$id};

    # A move into a terminal status is not taking work up, it is finishing it,
    # and what a finished card was once waiting for is no longer anybody's
    # decision to make. The board's own statuses answer this, not done/archived
    # (tickets #67, #98).
    return () if $self->store->is_terminal_status($new_status);

    my @deps = @{ $task->depends_on };
    return () unless @deps;

    my @warnings;
    for my $dep_id (@deps) {
        my $dep = $self->find_task($dep_id);

        # A deliberate divergence from the reference. kanban-md treats an id
        # that is not on the board as *satisfied*
        # (internal/board/filter.go:151-154): "Missing dependency IDs can occur
        # after legacy hard-deletes. Treat as satisfied so dependents are
        # recoverable via edit/cleanup." That reasoning is about not stranding a
        # card, and it is sound there, where an unsatisfied dependency makes the
        # card unpickable. Here nothing is blocked, so there is no card to
        # strand -- and a dependency pointing at an id that does not exist is
        # exactly the kind of thing whoever is about to start work wants told.
        if ( !$dep ) {
            push @warnings, sprintf
              'Warning: task %s depends on task %s, which does not exist on this board',
              $id, $dep_id;
            next;
        }

        next if $self->store->is_terminal_status( $dep->status );
        push @warnings, sprintf
          'Warning: task %s depends on task %s, which is still %s',
          $id, $dep_id, $dep->status;
    }

    $self->_dependency_warnings->{$id} = \@warnings if @warnings;
    return @warnings;
}


sub dependency_report {
    my ( $self, $id ) = @_;

    my $warnings = $self->_dependency_warnings->{$id};
    return () unless $warnings && @$warnings;

    print STDERR map { "$_\n" } @$warnings
      unless $self->json || $self->quiet;

    return ( dependency_warnings => $warnings );
}


1;

__END__

=pod

=encoding UTF-8

=head1 NAME

App::karr::Role::DependencyCheck - Warn when a card is taken up while its dependencies are unfinished

=head1 VERSION

version 0.500

=head1 DESCRIPTION

C<depends_on> was stored, round-tripped and written into the frontmatter by
L<App::karr::Task> long before anything read it. That is worse than a missing
feature: a card recording C<< depends_on: [5] >> looked as though karr would
hold it back until 5 was finished -- the field was accepted, kept and
materialized -- while C<move>, C<edit --status> and C<pick> handed it out with
no word said (ticket #123).

This role is what reads it. Taking a card up with unsatisfied dependencies
B<proceeds and exits 0>, but says so. "Taking up" is a status change into a



( run in 1.183 second using v1.01-cache-2.11-cpan-788537b7465 )