App-karr
view release on metacpan or search on metacpan
lib/App/karr/Task.pm view on Meta::CPAN
# ABSTRACT: Task object representing a single kanban card
package App::karr::Task;
our $VERSION = '0.302';
use Moo;
use YAML::XS qw( Load Dump );
use Path::Tiny;
use Time::Piece;
use JSON::MaybeXS qw( encode_json );
has id => ( is => 'ro', required => 1 );
has title => ( is => 'rw', required => 1 );
has status => ( is => 'rw', default => sub { 'backlog' } );
has priority => ( is => 'rw', default => sub { 'medium' } );
has assignee => ( is => 'rw', predicate => 1, clearer => 1 );
has tags => ( is => 'rw', default => sub { [] } );
has due => ( is => 'rw', predicate => 1, clearer => 1 );
has estimate => ( is => 'rw', predicate => 1, clearer => 1 );
has class => ( is => 'rw', default => sub { 'standard' } );
has parent => ( is => 'rw', predicate => 1, clearer => 1 );
has depends_on => ( is => 'rw', default => sub { [] } );
has body => ( is => 'rw', default => sub { '' } );
has created => ( is => 'ro', default => sub { gmtime->datetime . 'Z' } );
has updated => ( is => 'rw', default => sub { gmtime->datetime . 'Z' } );
has claimed_by => ( is => 'rw', predicate => 1, clearer => 1 );
has claimed_at => ( is => 'rw', predicate => 1, clearer => 1 );
has blocked => ( is => 'rw', predicate => 1, clearer => 1 );
has started => ( is => 'rw', predicate => 1, clearer => 1 );
has completed => ( is => 'rw', predicate => 1, clearer => 1 );
has file_path => ( is => 'rw', predicate => 1 );
# Optional fields are addressed through their predicate everywhere (pick,
# board, list, show, handoff all treat has_X as "is this set"). Clearing one
# by assigning undef would leave the predicate true, so callers must use the
# generated clear_X. This guards the load path: a file that carries an
# explicit null (our own older writes, or an external kanban-md edit) is
# normalized back to "unset" instead of lingering as has_X-true-but-undef.
sub BUILD {
my ($self) = @_;
for my $attr (qw( assignee due estimate parent claimed_by claimed_at blocked started completed )) {
my $clearer = "clear_$attr";
my $has = "has_$attr";
$self->$clearer if $self->$has && !defined $self->$attr;
}
}
sub slug {
my ($self) = @_;
my $slug = lc($self->title);
$slug =~ s/[^a-z0-9]+/-/g;
$slug =~ s/^-|-$//g;
$slug = substr($slug, 0, 50);
return $slug;
}
sub filename {
my ($self) = @_;
return sprintf('%03d-%s.md', $self->id, $self->slug);
}
sub to_frontmatter {
my ($self) = @_;
my %fm = (
id => $self->id,
title => $self->title,
status => $self->status,
priority => $self->priority,
created => $self->created,
updated => $self->updated,
class => $self->class,
);
$fm{assignee} = $self->assignee if $self->has_assignee;
$fm{tags} = $self->tags if @{$self->tags};
$fm{due} = $self->due if $self->has_due;
$fm{estimate} = $self->estimate if $self->has_estimate;
$fm{parent} = $self->parent if $self->has_parent;
$fm{depends_on} = $self->depends_on if @{$self->depends_on};
$fm{claimed_by} = $self->claimed_by if $self->has_claimed_by;
$fm{claimed_at} = $self->claimed_at if $self->has_claimed_at;
$fm{blocked} = $self->blocked if $self->has_blocked;
$fm{started} = $self->started if $self->has_started;
$fm{completed} = $self->completed if $self->has_completed;
return \%fm;
}
sub to_markdown {
my ($self) = @_;
my $yaml = Dump($self->to_frontmatter);
$yaml =~ s/\A---\n//;
my $md = "---\n${yaml}---\n";
$md .= "\n" . $self->body . "\n" if $self->body;
return $md;
}
sub _parse_content {
my ($class, $content) = @_;
my ($yaml, $body) = $content =~ m{\A---\n(.+?)---(?:\n(.*))?\z}s
or die "Invalid task format\n";
$body //= '';
$body =~ s/^\n//;
$body =~ s/\n$//;
return (Load($yaml), $body);
}
sub from_string {
my ($class, $content) = @_;
my ($fm, $body) = $class->_parse_content($content);
return $class->new(%$fm, body => $body);
}
sub from_file {
my ($class, $file) = @_;
$file = path($file);
my ($fm, $body) = $class->_parse_content($file->slurp_utf8);
return $class->new(%$fm, body => $body, file_path => $file);
}
sub save {
my ($self, $dir) = @_;
$self->updated(gmtime->datetime . 'Z');
my $file = $dir ? path($dir)->child($self->filename) : path($self->file_path);
$file->spew_utf8($self->to_markdown);
$self->file_path($file);
return $file;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
App::karr::Task - Task object representing a single kanban card
=head1 VERSION
( run in 2.229 seconds using v1.01-cache-2.11-cpan-2398b32b56e )