A utility to mark and operate on files in the shell.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 lines
4.6 KiB

5 years ago
  1. =pod
  2. =head1 NAME
  3. App::MarkFiles - some utility functions for marking and operating on files
  4. =head1 SYNOPSIS
  5. # This module:
  6. use App::MarkFiles qw(get_dbh each_path add remove);
  7. my $dbh = get_dbh(); # db handle for marks.db
  8. add('/foo/bar', '/foo/baz');
  9. remove('/foo/baz');
  10. each_path(sub {
  11. my ($path) = @_;
  12. print "$path\n";
  13. });
  14. # marks commands:
  15. $ marks add foo.txt
  16. $ cd ~/somedir
  17. $ marks mv
  18. =head1 INSTALLING
  19. $ perl Build.PL
  20. $ ./Build
  21. $ ./Build install
  22. =head1 DESCRIPTION
  23. The mark utilities store a list of marked file paths in marks.db in the user's
  24. home directory. Once marked, files can be copied, moved, listed, or passed as
  25. parameters to arbitrary shell commands.
  26. This originated as a simple tool for collecting files from one or more
  27. directories and moving or copying them to another. A basic usage pattern
  28. looks something like this:
  29. $ cd ~/screenshots
  30. $ marks add foo.png
  31. $ cd ~/blog/files/screenshots
  32. $ marks mv
  33. Moved: /home/brennen/screenshots/foo.png
  34. This is more steps than a simple invocation of mv(1), but its utility becomes
  35. more apparent when it's combined with aliases for quickly navigating
  36. directories or invoked from other programs like editors and file managers.
  37. See C<bin/marks> in this distribution (or, when installed, the marks(1) man
  38. page) for details on the commands.
  39. =cut
  40. package App::MarkFiles;
  41. use strict;
  42. use warnings;
  43. use base qw(Exporter);
  44. our @EXPORT_OK = qw(get_dbh each_path add remove check_collisions);
  45. our ($VERSION) = '0.0.3';
  46. use DBI;
  47. use File::Basename;
  48. use File::HomeDir;
  49. use File::Spec;
  50. =head1 SUBROUTINES
  51. =over
  52. =item get_dbh()
  53. Get database handle for default marks database, stored in F<~/marks.db>.
  54. Creates a new marks.db with the correct schema if one doesn't already exist.
  55. =cut
  56. sub get_dbh {
  57. my $dbfile = File::Spec->catfile(File::HomeDir->my_home, 'marks.db');
  58. my $init_new = 0;
  59. $init_new = 1 unless -f $dbfile;
  60. my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", "", "");
  61. create_mark_db($dbh) if $init_new;
  62. return $dbh;
  63. }
  64. =item create_mark_db($dbh)
  65. Create a new marks table.
  66. =cut
  67. sub create_mark_db {
  68. my ($dbh) = @_;
  69. $dbh->do(<<'SQL'
  70. CREATE TABLE marks (
  71. id integer primary key,
  72. path text,
  73. datetime text
  74. );
  75. CREATE TABLE things (
  76. id integer primary key,
  77. address text
  78. );
  79. CREATE TABLE links (
  80. id integer primary key,
  81. from_thing integer,
  82. to_thing integer,
  83. created text,
  84. updated text
  85. );
  86. SQL
  87. );
  88. }
  89. =item add(@paths)
  90. Add a mark to one or more paths.
  91. =cut
  92. sub add {
  93. my (@paths) = @_;
  94. # Filter out any paths that have already been marked:
  95. my %pathmap = map { $_ => 1 } @paths;
  96. each_path(sub {
  97. my ($existing_path) = @_;
  98. if ($pathmap{ $existing_path }) {
  99. delete $pathmap{ $existing_path };
  100. }
  101. });
  102. my $sth = get_dbh()->prepare(q{
  103. INSERT INTO marks (path, datetime) VALUES (?, datetime('now'))
  104. });
  105. foreach my $path (keys %pathmap) {
  106. $sth->execute($path);
  107. }
  108. }
  109. =item remove(@paths)
  110. Remove all given paths from the mark list.
  111. =cut
  112. sub remove {
  113. my (@paths) = @_;
  114. my $sth = get_dbh()->prepare(q{
  115. DELETE FROM marks WHERE PATH = ?;
  116. });
  117. foreach my $path (@paths) {
  118. $sth->execute($path);
  119. }
  120. }
  121. =item each_path($func)
  122. Run an anonymous function against each item in the mark list.
  123. Expects a sub which takes a path string.
  124. =cut
  125. sub each_path {
  126. my ($func) = @_;
  127. my $sth = get_dbh()->prepare(q{
  128. SELECT DISTINCT(path) as path FROM marks ORDER BY datetime;
  129. });
  130. $sth->execute();
  131. while (my $data = $sth->fetchrow_hashref()) {
  132. $func->($data->{path});
  133. }
  134. }
  135. =item check_collisions()
  136. Return a list of basenames which would collide from the current list.
  137. =cut
  138. sub check_collisions {
  139. # Accumulate a list of basenames:
  140. my %basenames;
  141. each_path(sub {
  142. my ($path) = @_;
  143. my ($source_basename, $source_path) = fileparse($path);
  144. $basenames{ $source_basename }++;
  145. });
  146. my @collisions;
  147. while(my($basename, $count) = each %basenames) {
  148. push @collisions, $basename if 1 < $count;
  149. }
  150. return @collisions;
  151. }
  152. =back
  153. =head1 AUTHOR
  154. Copyright 2018 Brennen Bearnes
  155. mark is free software; you can redistribute it and/or modify
  156. it under the terms of the GNU General Public License as published by
  157. the Free Software Foundation, version 2 of the License.
  158. This program is distributed in the hope that it will be useful,
  159. but WITHOUT ANY WARRANTY; without even the implied warranty of
  160. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  161. GNU General Public License for more details.
  162. =cut
  163. 1;