=pod
|
|
|
|
=head1 NAME
|
|
|
|
App::MarkFiles - some utility functions for marking and operating on files
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
# This module:
|
|
use App::MarkFiles qw(get_dbh each_path add remove);
|
|
|
|
my $dbh = get_dbh(); # db handle for marks.db
|
|
|
|
add('/foo/bar', '/foo/baz');
|
|
|
|
remove('/foo/baz');
|
|
|
|
each_path(sub {
|
|
my ($path) = @_;
|
|
print "$path\n";
|
|
});
|
|
|
|
# marks commands:
|
|
$ marks add foo.txt
|
|
$ cd ~/somedir
|
|
$ marks mv
|
|
|
|
=head1 INSTALLING
|
|
|
|
$ perl Build.PL
|
|
$ ./Build
|
|
$ ./Build install
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
The mark utilities store a list of marked file paths in marks.db in the user's
|
|
home directory. Once marked, files can be copied, moved, listed, or passed as
|
|
parameters to arbitrary shell commands.
|
|
|
|
This originated as a simple tool for collecting files from one or more
|
|
directories and moving or copying them to another. A basic usage pattern
|
|
looks something like this:
|
|
|
|
$ cd ~/screenshots
|
|
$ marks add foo.png
|
|
$ cd ~/blog/files/screenshots
|
|
$ marks mv
|
|
Moved: /home/brennen/screenshots/foo.png
|
|
|
|
This is more steps than a simple invocation of mv(1), but its utility becomes
|
|
more apparent when it's combined with aliases for quickly navigating
|
|
directories or invoked from other programs like editors and file managers.
|
|
|
|
See C<bin/marks> in this distribution (or, when installed, the marks(1) man
|
|
page) for details on the commands.
|
|
|
|
=cut
|
|
|
|
package App::MarkFiles;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use base qw(Exporter);
|
|
our @EXPORT_OK = qw(get_dbh each_path add remove check_collisions);
|
|
|
|
our ($VERSION) = '0.0.3';
|
|
|
|
use DBI;
|
|
use File::Basename;
|
|
use File::HomeDir;
|
|
use File::Spec;
|
|
|
|
=head1 SUBROUTINES
|
|
|
|
=over
|
|
|
|
=item get_dbh()
|
|
|
|
Get database handle for default marks database, stored in F<~/marks.db>.
|
|
|
|
Creates a new marks.db with the correct schema if one doesn't already exist.
|
|
|
|
=cut
|
|
|
|
sub get_dbh {
|
|
my $dbfile = File::Spec->catfile(File::HomeDir->my_home, 'marks.db');
|
|
|
|
my $init_new = 0;
|
|
$init_new = 1 unless -f $dbfile;
|
|
|
|
my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", "", "");
|
|
|
|
create_mark_db($dbh) if $init_new;
|
|
|
|
return $dbh;
|
|
}
|
|
|
|
=item create_mark_db($dbh)
|
|
|
|
Create a new marks table.
|
|
|
|
=cut
|
|
|
|
sub create_mark_db {
|
|
my ($dbh) = @_;
|
|
|
|
$dbh->do(<<'SQL'
|
|
CREATE TABLE marks (
|
|
id integer primary key,
|
|
path text,
|
|
datetime text
|
|
);
|
|
|
|
CREATE TABLE things (
|
|
id integer primary key,
|
|
address text
|
|
);
|
|
|
|
CREATE TABLE links (
|
|
id integer primary key,
|
|
from_thing integer,
|
|
to_thing integer,
|
|
created text,
|
|
updated text
|
|
);
|
|
SQL
|
|
);
|
|
}
|
|
|
|
=item add(@paths)
|
|
|
|
Add a mark to one or more paths.
|
|
|
|
=cut
|
|
|
|
sub add {
|
|
my (@paths) = @_;
|
|
|
|
# Filter out any paths that have already been marked:
|
|
my %pathmap = map { $_ => 1 } @paths;
|
|
each_path(sub {
|
|
my ($existing_path) = @_;
|
|
if ($pathmap{ $existing_path }) {
|
|
delete $pathmap{ $existing_path };
|
|
}
|
|
});
|
|
|
|
my $sth = get_dbh()->prepare(q{
|
|
INSERT INTO marks (path, datetime) VALUES (?, datetime('now'))
|
|
});
|
|
|
|
foreach my $path (keys %pathmap) {
|
|
$sth->execute($path);
|
|
}
|
|
}
|
|
|
|
=item remove(@paths)
|
|
|
|
Remove all given paths from the mark list.
|
|
|
|
=cut
|
|
|
|
sub remove {
|
|
my (@paths) = @_;
|
|
|
|
my $sth = get_dbh()->prepare(q{
|
|
DELETE FROM marks WHERE PATH = ?;
|
|
});
|
|
|
|
foreach my $path (@paths) {
|
|
$sth->execute($path);
|
|
}
|
|
}
|
|
|
|
=item each_path($func)
|
|
|
|
Run an anonymous function against each item in the mark list.
|
|
|
|
Expects a sub which takes a path string.
|
|
|
|
=cut
|
|
|
|
sub each_path {
|
|
my ($func) = @_;
|
|
|
|
my $sth = get_dbh()->prepare(q{
|
|
SELECT DISTINCT(path) as path FROM marks ORDER BY datetime;
|
|
});
|
|
|
|
$sth->execute();
|
|
|
|
while (my $data = $sth->fetchrow_hashref()) {
|
|
$func->($data->{path});
|
|
}
|
|
}
|
|
|
|
=item check_collisions()
|
|
|
|
Return a list of basenames which would collide from the current list.
|
|
|
|
=cut
|
|
|
|
sub check_collisions {
|
|
# Accumulate a list of basenames:
|
|
my %basenames;
|
|
each_path(sub {
|
|
my ($path) = @_;
|
|
my ($source_basename, $source_path) = fileparse($path);
|
|
$basenames{ $source_basename }++;
|
|
});
|
|
|
|
my @collisions;
|
|
while(my($basename, $count) = each %basenames) {
|
|
push @collisions, $basename if 1 < $count;
|
|
}
|
|
|
|
return @collisions;
|
|
}
|
|
|
|
=back
|
|
|
|
=head1 AUTHOR
|
|
|
|
Copyright 2018 Brennen Bearnes
|
|
|
|
mark is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
=cut
|
|
|
|
1;
|