ACME-2026

 view release on metacpan or  search on metacpan

lib/ACME/2026.pm  view on Meta::CPAN

    _ensure_plan($plan);
    return _find_item($plan, $id);
}

sub add_note {
    my ($plan, $id, $note) = @_;
    _ensure_plan($plan);
    croak 'add_note requires a note' unless defined $note && length $note;

    my $item = _find_item($plan, $id);
    croak "No item with id $id" unless $item;

    _add_note($plan, $item, $note);
    _maybe_autosave($plan);

    return $item;
}

sub complete_item {
    my ($plan, $id, %opts) = @_;
    return _set_status($plan, $id, 'done', %opts);
}

sub skip_item {
    my ($plan, $id, %opts) = @_;
    return _set_status($plan, $id, 'skipped', %opts);
}

sub reopen_item {
    my ($plan, $id, %opts) = @_;
    return _set_status($plan, $id, 'todo', %opts);
}

sub items {
    my ($plan, %filters) = @_;
    _ensure_plan($plan);

    my @items = @{ $plan->{items} || [] };

    if (defined $filters{status}) {
        my $status = _normalize_status($filters{status});
        @items = grep { $_->{status} eq $status } @items;
    }

    if (defined $filters{list}) {
        @items = grep { defined $_->{list} && $_->{list} eq $filters{list} } @items;
    }

    my @tags;
    push @tags, $filters{tag} if defined $filters{tag};
    if (defined $filters{tags}) {
        if (ref $filters{tags} eq 'ARRAY') {
            push @tags, @{ $filters{tags} };
        } else {
            push @tags, $filters{tags};
        }
    }

    if (@tags) {
        @items = grep {
            my %item_tags = map { $_ => 1 } @{ $_->{tags} || [] };
            my $match = 0;
            for my $tag (@tags) {
                next unless defined $tag && length $tag;
                if ($item_tags{$tag}) {
                    $match = 1;
                    last;
                }
            }
            $match;
        } @items;
    }

    if (defined $filters{priority}) {
        @items = grep { defined $_->{priority} && $_->{priority} == $filters{priority} } @items;
    }

    if (defined $filters{min_priority}) {
        @items = grep { defined $_->{priority} && $_->{priority} >= $filters{min_priority} } @items;
    }

    if (defined $filters{max_priority}) {
        @items = grep { defined $_->{priority} && $_->{priority} <= $filters{max_priority} } @items;
    }

    if (defined $filters{due_before}) {
        @items = grep { defined $_->{due} && $_->{due} le $filters{due_before} } @items;
    }

    if (defined $filters{due_after}) {
        @items = grep { defined $_->{due} && $_->{due} ge $filters{due_after} } @items;
    }

    if (defined $filters{sort}) {
        @items = _sort_items(\@items, $filters{sort});
    }

    return @items;
}

sub stats {
    my ($plan, %filters) = @_;
    _ensure_plan($plan);

    my @items = items($plan, %filters);
    my %stats = (
        total => scalar @items,
        todo => 0,
        done => 0,
        skipped => 0,
    );

    for my $item (@items) {
        $stats{ $item->{status} }++ if exists $stats{ $item->{status} };
    }

    $stats{complete_pct} = $stats{total}
        ? int(($stats{done} / $stats{total}) * 100 + 0.5)
        : 0;

    return \%stats;

lib/ACME/2026.pm  view on Meta::CPAN

    my ($plan) = @_;
    $plan->{updated_at} = _now();
}

sub _maybe_autosave {
    my ($plan) = @_;
    return unless $plan->{autosave};
    plan_save($plan);
}

sub _sort_items {
    my ($items, $sort) = @_;
    return @$items unless defined $sort && length $sort;

    my $desc = ($sort =~ s/^-//);

    if ($sort eq 'due') {
        return sort {
            my $ad = defined $a->{due} ? $a->{due} : ($desc ? '0000-00-00' : '9999-12-31');
            my $bd = defined $b->{due} ? $b->{due} : ($desc ? '0000-00-00' : '9999-12-31');
            my $cmp = $ad cmp $bd;
            $desc ? -$cmp : $cmp;
        } @$items;
    }

    if ($sort eq 'priority') {
        return sort {
            my $ad = defined $a->{priority} ? $a->{priority} : 0;
            my $bd = defined $b->{priority} ? $b->{priority} : 0;
            my $cmp = $ad <=> $bd;
            $desc ? -$cmp : $cmp;
        } @$items;
    }

    if ($sort eq 'created') {
        return sort {
            my $cmp = ($a->{created_at} || '') cmp ($b->{created_at} || '');
            $desc ? -$cmp : $cmp;
        } @$items;
    }

    if ($sort eq 'updated') {
        return sort {
            my $cmp = ($a->{updated_at} || '') cmp ($b->{updated_at} || '');
            $desc ? -$cmp : $cmp;
        } @$items;
    }

    if ($sort eq 'title') {
        return sort {
            my $cmp = lc($a->{title} || '') cmp lc($b->{title} || '');
            $desc ? -$cmp : $cmp;
        } @$items;
    }

    return @$items;
}

sub _reject_unknown {
    my ($context, $attrs, @known) = @_;
    my %known = map { $_ => 1 } @known;
    my @unknown = grep { !$known{$_} } keys %$attrs;
    return unless @unknown;
    croak "$context does not accept: " . join(', ', sort @unknown);
}

sub _now {
    return strftime('%Y-%m-%dT%H:%M:%SZ', gmtime());
}

sub _read_file {
    my ($path) = @_;
    open my $fh, '<', $path or croak "Unable to read $path: $!";
    local $/;
    return <$fh>;
}

sub _write_file_atomic {
    my ($path, $content) = @_;
    my ($fh, $tmp) = tempfile('acme2026-XXXXXX', DIR => _temp_dir($path));
    print {$fh} $content or croak "Unable to write $tmp: $!";
    close $fh or croak "Unable to close $tmp: $!";
    rename $tmp, $path or croak "Unable to move $tmp to $path: $!";
}

sub _temp_dir {
    my ($path) = @_;
    return '.' unless defined $path && length $path;
    if ($path =~ /[\/\\]/) {
        $path =~ s/[\/\\][^\/\\]+$//;
        return length $path ? $path : '.';
    }
    return '.';
}

=head1 AUTHOR

Will Willis <wwillis@cpan.org>

=head1 BUGS

Please report any bugs or feature requests to C<bug-acme-2026 at rt.cpan.org>, or through
the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=ACME-2026>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

  perldoc ACME::2026

You can also look for information at:

=over 4

=item * RT: CPAN's request tracker (report bugs here)

L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=ACME-2026>

=item * Search CPAN



( run in 2.012 seconds using v1.01-cache-2.11-cpan-140bd7fdf52 )