App-Easer

 view release on metacpan or  search on metacpan

docs/docs/10-tutorial-base.md  view on Meta::CPAN

      },
      remove => {
         help        => 'delete a task',
         description => 'Get rid of a task (definitively)',
         supports    => [qw< remove rm delete del >],
         execute     => '#remove',
      },
   },
};
exit run($application, [@ARGV]);

package TuDu;
use Path::Tiny 'path';
use POSIX 'strftime';

sub ensure_basedir ($main, $spec, $args) {
   my $path = path($main->{configs}[-1]{basedir});
   $path->mkpath;
   $path->child($_)->mkpath for qw< ongoing waiting done >;
   return;
} ## end sub ensure_basedir

sub list_category ($config, $category) {
   my $dir = path($config->{basedir})->child($category);
   return reverse sort { $a cmp $b } $dir->children;
}

sub list ($main, $config, $args) {
   my @active = qw< ongoing waiting >;
   my @candidates = (@active, 'done');
   my %included;

   # Add stuff
   if ($config->{all}) {
      @included{@candidates} = (1) x @candidates;
   }
   for my $option (@candidates) {
      $included{$option} = 1 if $config->{$option};
   }
   if ($config->{active} || !scalar keys %included) {
      @included{@active} = (1) x @active;
   }

   # Remove stuff
   delete @included{@active}
     if exists $config->{active} && !$config->{active};
   for my $option (@candidates) {
      delete $included{$option}
        if exists $config->{$option} && !$config->{$option};
   }

   my $basedir = path($config->{basedir});
   my (%cf, %pf);
   my $limit = $config->{n};
   for my $source (@candidates) {
      next unless $included{$source};
      for my $file (list_category($config, $source)) {
         my $title = get_title($file);
         my $sid = $config->{id} ? '-' . $file->basename : ++$cf{$source};
         my $id = substr($source, 0, 1) . $sid;
         say "$id [$source] $title";
         last if $limit && ++$pf{$source} >= $limit;
      } ## end for my $file (list_category...)
   } ## end for my $source (@candidates)

   return 0;
} ## end sub list

sub resolve ($config, $oid) {
   fatal("no identifier provided") unless defined $oid;
   my $id = $oid;

   my %name_for = (o => 'ongoing', d => 'done', w => 'waiting');
   my $first = substr $id, 0, 1, '';
   my $type = $name_for{$first} // fatal("invalid identifier '$oid'");

   my $child;
   if ($id =~ s{\A -}{}mxs) {    # exact id
      $child = path($config->{basedir})->child($type, $id);
      fatal("unknown identifier '$oid'") unless -r $child;
   }
   else {
      fatal("invalid identifier '$oid'")
        unless $id =~ m{\A [1-9]\d* \z}mxs;
      my @children = list_category($config, $type);
      fatal(
"invalid identifier '$oid' (too high, max $first@{[scalar @children]})"
      ) if $id > @children;
      $child = $children[$id - 1];
   } ## end else [ if ($id =~ s{\A -}{}mxs)]

   return $child;
} ## end sub resolve

sub show ($main, $config, $args) {
   my $child = resolve($config, $args->[0]);
   my $contents = $child->slurp_utf8;
   $contents =~ s{\n\z}{}mxs;
   say "----\n$contents\n----";
   return 0;
} ## end sub show

sub cat ($main, $config, $args) {
   my $child = resolve($config, $args->[0]);
   print {*STDOUT} $child->slurp_utf8;
   return 0;
} ## end sub show

sub fatal ($message) { die join(' ', @_) . "\n" }
sub notice ($message) { warn join(' ', @_) . "\n" }

sub add_file ($config, $hint, $contents) {
   my $attempts = 0;
   my $file     = path($hint);
   while ('necessary') {
      eval {
         my $fh =
           $file->filehandle({exclusive => 1}, '>', ':encoding(UTF-8)');
         print {$fh} $contents;
         close $fh;
      } && return $file;
      ++$attempts;
      last if $config->{attempts} && $attempts >= $config->{attempts};
      $file = $hint->sibling($hint->basename . "-$attempts");
   } ## end while ('necessary')
   fatal("cannot save file '$hint' or variants");
} ## end sub add_file

sub move_task ($config, $src, $category) {
   $src = $src->[0] if 'ARRAY' eq ref $src;
   my $child = resolve($config, $src);
   my $parent = $child->parent;
   if ($parent->basename eq $category) {
      notice("task is already $category");
      return 0;
   }
   my $dest = $parent->sibling($category)->child($child->basename);
   add_file($config, $dest, $child->slurp_utf8);
   $child->remove;
   return 0;
} ## end sub move_task

sub done ($m, $config, $args) { move_task($config, $args, 'done') }
sub resume ($m, $config, $args) { move_task($config, $args, 'ongoing') }
sub waiting ($m, $config, $args) { move_task($config, $args, 'waiting') }

sub remove ($main, $config, $args) {
   resolve($config, $args->[0])->remove;
   return 0;
}

sub get_title ($path) {
   my ($title) = $path->lines({count => 1});
   ($title // '') =~ s{\A\s+|\s+\z}{}grmxs;
}

sub add ($main, $config, $args) {
   my $id = strftime('%Y%m%d-%H%M%S', localtime);
   my $category = $config->{waiting} ? 'waiting' : 'ongoing';



( run in 0.333 second using v1.01-cache-2.11-cpan-d7a12ab2c7f )