#!/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<p1k3|https://p1k3.com/> (my blog), Pinboard bookmarks, command
|
|
line history using
|
|
L<commandlog|https://code.p1k3.com/gitea/brennen/commandlog>, and commits in
|
|
git repos registered with myrepos.
|
|
|
|
This code is hot garbage.
|
|
|
|
=head1 AUTHOR
|
|
|
|
L<Brennen Bearnes|https://p1k3.com/>
|
|
|
|
=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;
|
|
}
|