#!/usr/bin/env perl =pod =head1 NAME timeslice - aggregate CLI history, notes, blog entries, etc. for a given time =head1 SYNOPSIS # Find items for January 1st, 2019: timeslice 2019-01-01 # Find items corresponding to a file's time - special cases some files # with an embedded date, like a blog entry or a vimwiki diary page, # otherwise falls back to mtime: timeslice --file ~/notes/vimwiki/diary/2019-01-02.wiki timeslice --file ~/screenshots/foo.png =head1 DESCRIPTION timeslice is a utility, for now extremely specific to my personal setup, for aggregating info from various systems by date. It currently looks up entries in vimwiki, L (my blog), Pinboard bookmarks, command line history using L, and commits in git repos registered with myrepos. This code is hot garbage. =head1 AUTHOR L =cut use warnings; use strict; use 5.10.0; # This seems to be necessary to print UTF-8 without warnings: use open ':std', ':encoding(UTF-8)'; use Carp; use Data::Dumper; use Encode qw(decode encode); use File::Basename; use File::Find; use File::HomeDir; use File::Spec; use Getopt::Long; use JSON; use LWP::Simple; use Pod::Usage; use Text::Wrap; use Time::Piece; use Time::Seconds; $Text::Wrap::columns = 72; my $ISO_8601_FMT = '%Y-%m-%d'; # Handle options, including help generated from the POD above: my $date_from_file = 0; GetOptions( 'file' => \$date_from_file, 'help' => sub { pod2usage(0) }, ) or pod2usage(2); if (! @ARGV) { pod2usage(0); } # If --file was invoked, get time from targetted file instead of taking # the first param as a date: my %dates; if ($date_from_file) { my $file = File::Spec->rel2abs($ARGV[0]); say "Date from file: $file"; if ($file =~ m{^.*?/diary/(\d{4}-\d{2}-\d{2})[.]wiki$}) { # Handle vimwiki diary entries: $dates{$1} .= '(vimwiki)'; } elsif ($file =~ m{^.*?/p1k3/archives/(\d{4})/(\d+)/(\d+)(/index)?$}) { # Handle p1k3 entries: $dates{ sprintf('%d-%02d-%02d', $1, $2, $3) } .= "(p1k3)"; } # Just use the file's modification time: $dates{ get_mtime_day($ARGV[0]) } .= "(mtime)"; my $git_date = git_commit_date($file); if ($git_date) { # Handle git commit dates: $dates{ $git_date } .= "(git commit)"; } } else { $dates{ $ARGV[0] } .= "(arg)"; } foreach my $date (keys %dates) { unless ($date =~ m/\d+-\d+-\d+/) { say "invalid date: $date"; next; } my $day = Time::Piece->strptime($date, $ISO_8601_FMT); print_day_slice($day, $dates{$date}); } sub print_day_slice { my ($day, $label) = @_; my $next_day = $day + ONE_DAY; say "${day} ${label} {{{"; print_section('p1k3', p1k3($day)); print_section('vimwiki', vimwiki($day)); print_section('shell', commandlog($day, $next_day)); print_section('pinboard', pinboard($day)); print_section('myrepos', myrepos($day, $next_day)); say "}}}"; } sub print_section { my ($title, @content) = @_; return unless defined $content[0]; say "${title} {{{"; foreach my $thing (@content) { say $thing; } say "}}}"; } sub commandlog { my ($day, $next_day) = @_; my $after = $day->strftime('%Y-%m-%d 00:00:00'); my $before = $next_day->strftime('%Y-%m-%d 00:00:00'); my (@cl_lines) = `commandlog log --after="${after}" --before="${before}"`; if (@cl_lines > 1) { return join "", @cl_lines; } return; } sub myrepos { my ($day, $next_day) = @_; my $after = $day->strftime('%Y-%m-%d 00:00:00'); my $before = $next_day->strftime('%Y-%m-%d 00:00:00'); my (@mr_lines) = `cd ~ && mr -m log --stat --since="${after}" --before="${before}" 2>&1`; if (@mr_lines > 1) { return join "", @mr_lines; } return; } sub p1k3 { my ($day) = @_; my $daypath = $day->strftime( "%Y/%-m/%-d" ); if (-e File::HomeDir->my_home . '/p1k3/archives/' . $daypath) { return join "", `cd ~/p1k3 && wrt display ${daypath} | lynx -stdin -dump` } return; } sub pinboard { my ($day) = @_; # See the bit about auth here: # https://pinboard.in/api/ my $home = File::HomeDir->my_home; my $token = file_get_contents("${home}/.pinboardrc"); chomp($token); my $url = $day->strftime( 'https://api.pinboard.in/v1/posts/get?dt=%Y-%m-%d&meta=yes&format=json&auth_token=' . $token ); my $pinboard_json = `curl -s '${url}'`; my $JSON = JSON->new->utf8->pretty; my $pinboard_hashref = $JSON->decode($pinboard_json); my @posts = @{ $pinboard_hashref->{posts} }; my @output; # my @output = ( # "source: curl -s -K ${home}/.pinboardrc '${url}'" # ); foreach my $post (@posts) { my $display = wrap("", "", $post->{description}) . "\n" if length($post->{description}); $display .= wrap("\t", "\t", $post->{extended}) . "\n" if length($post->{extended}); $display .= "\t" . $post->{href} . "\n"; $display .= wrap("\t", "\t", $post->{tags}) . "\n" if length($post->{tags}); push @output, $display; } if (@output > 0) { return join "\n", @output; } return; } sub vimwiki { my ($day) = @_; my $daypath = $day->strftime( File::HomeDir->my_home . "/notes/vimwiki/diary/%Y-%m-%d.wiki" ); if (-e $daypath) { return ("source: $daypath", file_get_contents($daypath)); } } # PHP-style file-content grabbing: sub file_get_contents { my ($file) = @_; open my $fh, '<', $file or croak "Couldn't open $file: $!\n"; my $contents; { # line separator: local $/ = undef; $contents = <$fh>; } close $fh or croak "Couldn't close $file: $!"; # TODO: _May_ want to assume here that any file is UTF-8 text. # http://perldoc.perl.org/perlunitut.html # return decode('UTF-8', $contents); return $contents; } # Horked from WRT::Date. sub get_mtime_day { use POSIX qw(strftime); my ($file) = @_; # my( $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, # $atime, $mtime, $ctime, $blksize, $blocks ) # = stat( $filename ); my $mtime = (stat $file)[9]; # return strftime("%Y-%m-%dT%H:%M:%SZ", localtime($mtime)); return strftime("%Y-%m-%d", localtime($mtime)); } # Get a commit date for a given path: sub git_commit_date { my ($path) = @_; my $dirname = dirname($path); say $path; if (in_git_worktree($path)) { my ($git_output) = `cd ${dirname} && git log -1 --format=%ci "${path}"`; my ($date) = $git_output =~ m/^(\d{4}-\d{2}-\d{2}).*$/; if ($date) { return $date; } } return; } # Is a given path in a git working tree? sub in_git_worktree { my ($path) = @_; my $dirname = dirname($path); return 0 unless -d "$dirname/.git"; my ($git_check) = `cd "${dirname}" && git rev-parse --is-inside-work-tree`; if (defined $git_check) { chomp($git_check); return 1 if $git_check eq 'true'; } return 0; }