App-Notes
view release on metacpan or search on metacpan
#!/usr/bin/env perl
use v5.10;
use strict;
use warnings;
use App::Rad;
use Capture::Tiny qw( capture );
use Git::Repository;
use List::MoreUtils qw( firstidx );
use Path::Class qw( file dir );
# ABSTRACT: Simple. Git-based. Notes.
sub setup {
my ( $c ) = @_;
$c->register_commands({
add => 'add a new note, and edit it',
append => 'append to a note',
delete => 'delete the note',
edit => 'edit a note',
init => 'Initiliazie notes (optionally from remote repo)',
list => 'lists id and subject of all notes',
rename => 'rename a note',
replace => 'replace the contents of the note ( from STDIN )',
show => 'show the contents of the note',
sync => 'Sync notes with remote (pull + push)',
});
$c->register_command(mv => \&rename, 'alias for rename');
}
sub pre_process {
my ( $c ) = @_;
my $cmd = $c->cmd;
# If we aren't initializing, check to make sure our notes directory exists
if( $cmd ne 'init' ) {
if( -d notes_repo() ) {
$c->stash->{git} = Git::Repository->new( git_dir => notes_repo() );
} else {
# We are not initialized
die "Notes Directory has not been initialized!\n" .
"Run init [remote git repo] to initialize.\n";
}
}
return if not $c->is_command($cmd) or $cmd ~~ [qw( help init sync )];
sync( $c, pull_only => 1 ) if auto_sync();
}
sub post_process {
my ( $c ) = @_;
my $cmd = $c->cmd;
if($c->is_command($cmd) and not $cmd ~~ [qw( help init list show sync )]) {
sync( $c, push_only => 1 ) if auto_sync();
}
say $c->output if $c->output;
}
sub invalid {
my ( $c ) = @_;
my $cmd = $c->cmd;
($cmd) = grep { /^$cmd/ } $c->commands;
$cmd ? $c->execute( $cmd ) : $c->execute( 'help' );
return; # This makes the command name not be printed from the last line
}
App::Rad->run;
# helpers ---------------------------------------------------------------------
sub editor { $ENV{EDITOR} || 'vim' }
sub notes_dir { dir( $ENV{APP_NOTES_DIR} ) || dir( $ENV{HOME}, '.notes' ) }
sub notes_repo { file( notes_dir, '.git' ) }
sub auto_sync { $ENV{APP_NOTES_AUTOSYNC} // 1 }
sub has_origin {
grep { /^origin$/ } split ' ', $_[0]->stash->{git}->run( 'remote' )
}
sub find_notes {
my ( $c, %args ) = @_;
my $search = $args{search};
my @notes = map file($_), sort {-M $a <=> -M $b} glob file(notes_dir, '*');
# Filter results if requested
if (defined $search) {
@notes = grep { $_->basename =~ /$search/i } @notes;
# If there is an exact match, make sure it is first
my $idx = firstidx { $search eq $_->basename } @notes;
unshift @notes, splice @notes, $idx, 1 if $idx > 0;
}
return \@notes;
}
sub read_stdin { local $/; <STDIN> }
sub check_stdin { ( -t STDIN ) ? 0 : read_stdin() }
sub get_title { join ' ', @ARGV; }
sub get_filename {
return undef unless $_[0];
( my $r = $_[0] ) =~ s/ /-/g;
return $r;
}
sub is_yes { shift ~~ /^y(es)?$/i }
sub edit_file {
my ( $c, $file, %args ) = @_;
$args{check_stdin} //= 1; # Default to check stdin
my $verb = ( -e $file->stringify ) ? "Updated " : "Created ";
my $stdin = $args{check_stdin} ? check_stdin() : 0;
my $file = file( notes_dir(), get_filename( $title ) );
die "File already exists!" if -e $file;
edit_file( $c, $file );
}
sub append {
my ( $c ) = @_;
my $title = get_title();
my $notes = find_notes( $c, search => get_filename( $title ) );
die "No matching notes found" unless @$notes > 0;
my $file = $notes->[0];
edit_file( $c, $file, append => 1 );
}
sub delete {
my ( $c ) = @_;
my $title = get_title();
my $notes = find_notes( $c, search => get_filename( $title ) );
die "No matching note found!" unless @$notes > 0;
my $to_rm = $notes->[0];
print qq(Delete "@{[$to_rm->basename]}" ? );
my $res = <STDIN>;
if( is_yes($res) ) {
my $output = capture {
my $msg = qq(Removed "$to_rm");
$c->stash->{git}->run( rm => "$to_rm" );
$c->stash->{git}->run( commit => -m => $msg );
};
} else {
return "Not Removed.";
}
}
sub edit {
my ( $c ) = @_;
my $title = get_title();
my $notes = find_notes( $c, search => get_filename( $title ) );
die "No matching notes found!" unless @$notes > 0;
my $to_edit = $notes->[0];
edit_file( $c, $to_edit, check_stdin => 0 );
}
sub init {
my ( $c ) = @_;
die "Notes dir already exists!" if -d notes_dir();
my $dir = notes_dir();
my $repo = $ARGV[0];
my $output = capture {
if( $repo ) {
say "Initializing notes from $repo...";
Git::Repository->run( clone => $repo, $dir->stringify );
} else {
say "Initializing notes ($dir)...";
print Git::Repository->run( init => $dir->stringify );
}
}
}
sub list {
my ( $c ) = @_;
my $search = @ARGV > 0 ? join ' ', @ARGV : undef;
my $notes = find_notes( $c, search => get_filename( $search ) );
say $_->basename for @$notes;
return;
}
sub rename {
my ( $c ) = @_;
my $cmd = $c->cmd;
die "usage: $0 $cmd <orig_name> <new_name>\n" unless @ARGV == 2;
my ($orig_name, $new_name) = @ARGV;
my $notes = find_notes( $c, search => get_filename( $orig_name ) );
die "No such note [$orig_name]" unless @$notes;
my ($orig_file, $new_file) = ($notes->[0], get_filename($new_name));
print qq(Rename "@{[$orig_file->basename]}" to "$new_name" ? );
my $res = <STDIN>;
if( is_yes($res) ){
$c->stash->{git}->run(mv => "$orig_file" => "$new_file");
$c->stash->{git}->run(commit => '-m',
"Rename " . $orig_file->basename . " => $new_file");
}
return;
}
sub replace {
my ( $c ) = @_;
my $title = get_title();
my $notes = find_notes( $c, search => get_filename( $title ) );
die "No matching notes found" unless @$notes > 0;
my $file = $notes->[0];
edit_file( $c, $file );
}
sub show {
my ( $c ) = @_;
my $title = get_title();
my $notes = find_notes( $c, search => get_filename( $title ) );
die "No matching notes found" unless @$notes > 0;
system "cat $notes->[0]";
}
sub sync {
my ( $c, %args ) = @_;
return unless has_origin( $c );
my $output = capture {
$c->stash->{git}->run( 'pull' ) unless $args{push_only};
$c->stash->{git}->run( 'push' ) unless $args{pull_only};
};
return;
}
# PODNAME: notes
__END__
=pod
( run in 0.842 second using v1.01-cache-2.11-cpan-9169edd2b0e )