App-Jiffy

 view release on metacpan or  search on metacpan

lib/App/Jiffy.pm  view on Meta::CPAN


use 5.008_005;
our $VERSION = '0.09';

use App::Jiffy::TimeEntry;
use App::Jiffy::View::Timesheet;
use App::Jiffy::Util::Duration qw/round/;

use YAML::Any qw( LoadFile );
use JSON::MaybeXS 'JSON';

use Getopt::Long;
Getopt::Long::Configure("pass_through");

use Moo;

has cfg => (
  is      => 'ro',
  default => sub {
    LoadFile( $ENV{HOME} . '/.jiffy.yml' ) || {};
  },
);

has terminator_regex => (
  is  => 'ro',
  isa => sub {
    die 'terminator_regex must be a regex ref' unless ref $_[0] eq 'Regexp';
  },
  default => sub {
    qr/^end$|
    ^done$|
    ^eod$|
    ^finished$|
    ^\\\(^\s*\.^\s*\)\/$| # This is a smily face with hands raised
    ^✓$|
    ^x$/x;
  },
);

sub remove_terminators {
  my $self = shift;
  return (
    title => {
      '$not' => $self->terminator_regex,
    } );
}

sub add_entry {
  my $self    = shift;
  my $options = shift;
  my $title;
  if ( ref $options ne 'HASH' ) {
    $title = $options;
    undef $options;
  } else {
    $title = shift;
  }

  my $start_time;

  my $LocalTZ = DateTime::TimeZone->new( name => 'local' );    # For caching
  my $now = DateTime->now( time_zone => $LocalTZ );

  if ( $options->{time} ) {
    require DateTime::Format::Strptime;

    # @TODO Figure out something more flexible and powerful to get time

    # First try H:M:S
    my $strp = DateTime::Format::Strptime->new(
      pattern   => '%T',
      time_zone => $LocalTZ,
    );
    $start_time = $strp->parse_datetime( $options->{time} );

    # If no time found try just H:M
    if ( not $start_time ) {
      my $strp = DateTime::Format::Strptime->new(
        pattern   => '%R',
        time_zone => $LocalTZ,
      );
      $start_time = $strp->parse_datetime( $options->{time} );
    }

    # Make sure the date part of the datetime is not set to the
    # beginning of time.
    if ( $start_time and $start_time->year < $now->year ) {
      $start_time->set(
        day   => $now->day,
        month => $now->month,
        year  => $now->year,
      );
    }
  }

  # Create and save Entry
  App::Jiffy::TimeEntry->new(
    title      => $title,
    start_time => $start_time // $now,
    cfg        => $self->cfg,
  )->save;
}

sub current_time {
  my $self = shift;

  my $latest   = App::Jiffy::TimeEntry::last_entry( $self->cfg );
  my $duration = $latest->duration;

  print '"' . $latest->title . '" has been running for';

  my %deltas = $duration->deltas;
  foreach my $unit ( keys %deltas ) {
    next unless $deltas{$unit};
    print " " . $deltas{$unit} . " " . $unit;
  }
  print ".\n";
}

sub time_sheet {
  my $self    = shift;
  my $options = shift;
  my $from;
  if ( ref $options ne 'HASH' ) {
    $from = $options;
    undef $options;
  } else {
    $from = shift;
  }

  my $from_date = DateTime->today( time_zone => 'local' );

  if ( defined $from ) {
    $from_date->subtract( days => $from );
  }

  my @entries = App::Jiffy::TimeEntry::search(
    $self->cfg,
    query => {
      start_time => { '$gt' => $from_date, },
      $self->remove_terminators,
    },
    sort => {
      start_time => 1,
    },
  );

  if ( $options->{round} ) {
    @entries = map { $_->duration( round( $_->duration ) ); $_ } @entries;
  }

  if ( $options->{json} ) {
    my $json = JSON::MaybeXS->new( pretty => 1, convert_blessed => 1 );
    print $json->encode( \@entries );
  } else {
    $options->{from} = $from;
    App::Jiffy::View::Timesheet::render( \@entries, $options );
  }
}

sub search {
  my $self       = shift;
  my $query_text = shift;
  my $options    = shift;
  my $days;
  if ( ref $options ne 'HASH' ) {
    $days = $options;
    undef $options;
  } else {
    $days = shift;
  }

  my $from_date = DateTime->today( time_zone => 'local' );

  if ( defined $days ) {
    $from_date->subtract( days => $days );
  }

  my @entries = App::Jiffy::TimeEntry::search(
    $self->cfg,
    query => {
      start_time => { '$gt' => $from_date, },
      $self->remove_terminators,
      title => qr/$query_text/,
    },
    sort => {
      start_time => 1,
    },
  );

  if ( $options->{round} ) {
    @entries = map { $_->duration( round( $_->duration ) ); $_ } @entries;
  }

  if ( not @entries ) {
    print "No Entries Found\n";
    return;
  }

  if ( $options->{json} ) {
    my $json = JSON::MaybeXS->new( pretty => 1, convert_blessed => 1 );
    print $json->encode( \@entries );
  } else {
    $options->{from} = $days;
    App::Jiffy::View::Timesheet::render( \@entries, $options );
  }
}

sub run {
  my $self = shift;
  my @args = @_;

  if ( $args[0] ) {
    if ( $args[0] eq 'current' ) {
      shift @args;
      return $self->current_time(@args);
    } elsif ( $args[0] eq 'timesheet' ) {
      shift @args;

      my $p = Getopt::Long::Parser->new( config => ['pass_through'], );
      $p->getoptionsfromarray(
        \@args,
        'verbose' => \my $verbose,
        'round'   => \my $round,
        'json'    => \my $json
      );

      return $self->time_sheet( {
          verbose => $verbose,
          round   => $round,
          json    => $json,
        },
        @args



( run in 1.145 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )