Dotfiles, utilities, and other apparatus.
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.

217 lines
4.5 KiB

  1. #!/usr/bin/env perl
  2. =pod
  3. =head1 NAME
  4. notes-links - work with links within my notes
  5. =head1 SYNOPSIS
  6. Currently this is vimwiki-dependent.
  7. # List pages linking to foo:
  8. notes links --target foo
  9. # Pages linking to foo.wiki:
  10. notes links --file ./foo.wiki
  11. # Pages with a date (i.e. logs and diaries) linking to foo:
  12. notes links --with-date --target foo
  13. # Links in foo (experimental, probably broken):
  14. notes links --source foo
  15. # Format (defaults to location):
  16. notes links --format target
  17. =head1 OUTPUT FORMATS
  18. B<--format> options include C<vimwiki>, C<tsv>, C<location>, C<target>,
  19. C<full>, and C<name>.
  20. Formats may be glitchy.
  21. =head1 AUTHOR
  22. Brennen Bearnes
  23. =cut
  24. use warnings;
  25. use strict;
  26. use 5.10.0;
  27. use Cwd;
  28. use Data::Dumper;
  29. use DBI;
  30. use File::Basename;
  31. use File::Spec;
  32. use Getopt::Long;
  33. use Pod::Usage;
  34. use SQL::Abstract;
  35. use Sys::Hostname;
  36. # Display formats:
  37. my %FORMATS = (
  38. vimwiki => sub {
  39. my ($data) = @_;
  40. my $timestamp = '';
  41. if (defined $data->{timestamp}) {
  42. $timestamp = $data->{timestamp} . " ";
  43. }
  44. return ' - '
  45. . $timestamp
  46. . '[[/' . $data->{page} . '|'
  47. . $data->{title}
  48. . "]]\n";
  49. },
  50. tsv => sub {
  51. my ($data) = @_;
  52. my $timestamp = '(null)';
  53. if (defined $data->{timestamp}) {
  54. $timestamp = $data->{timestamp};
  55. }
  56. return (join "\t", ($timestamp, $data->{page}, $data->{title})) . "\n";
  57. },
  58. # vim location list
  59. #
  60. # TODO: Note that the "1" here is, theoretically, a line number.
  61. # Unfortunately, there's no obvious way to extract that from Pandoc's parsing
  62. # of the original file, and thus no non-hacky way for notes-collect-metadata to
  63. # store an approximate value in the database. We _could_ read the file
  64. # line-by-line, or run grep, looking for the file's basename, but this is
  65. # tricky to get right.
  66. #
  67. # If I keep using this long-term, I should solve this problem.
  68. location => sub {
  69. my ($data) = @_;
  70. my $result = "$ENV{HOME}/notes/vimwiki/$data->{page}.wiki";
  71. if ($data->{title}) {
  72. $result .= ":1:$data->{title}";
  73. } else {
  74. $result .= print ":1:$data->{page}"
  75. }
  76. return $result . "\n";
  77. },
  78. # List of bare link _target_ names
  79. target => sub {
  80. my ($data) = @_;
  81. return $data->{target} . "\n";
  82. },
  83. # Fulltext of vimwiki pages:
  84. full => sub {
  85. my ($data) = @_;
  86. my $result = '';
  87. my $pagepath = "$ENV{HOME}/notes/vimwiki/$data->{page}.wiki";
  88. $result .= "%% $pagepath {{{\n\n";
  89. $result .= file_get_contents($pagepath);
  90. $result .= "\n%% }}}\n\n";
  91. return $result;
  92. },
  93. # Bare names of wiki pages (no extension):
  94. name => sub {
  95. my ($data) = @_;
  96. return $data->{page} . "\n";
  97. },
  98. );
  99. # Handle options, including help generated from the POD above.
  100. my $debug = 0;
  101. my $with_date = 0;
  102. my $target;
  103. my $source;
  104. my $file;
  105. my $format = 'location';
  106. GetOptions(
  107. 'debug' => \$debug,
  108. 'target=s' => \$target,
  109. 'file=s' => \$file,
  110. 'format=s' => \$format,
  111. 'with-date' => \$with_date,
  112. 'help' => sub { pod2usage(0) },
  113. ) or pod2usage(2);
  114. if (defined $file)
  115. {
  116. my ($name, $path, $suffix) = fileparse($file, '.wiki');
  117. # XXX: This is a dirty fucking hack - get rid of the leading path stuff, but
  118. # keep subdirs within the vimwiki directory:
  119. $path =~ s{^.*?notes/vimwiki/}{};
  120. $target = $path . $name;
  121. }
  122. my %where = ();
  123. # Require that the link point to a given page:
  124. if (defined $target) {
  125. $where{target} = $target;
  126. }
  127. # Get only links from pages that have a datetime:
  128. if ($with_date) {
  129. # NOT NULL:
  130. $where{'pages.datetime'} = { '!=', undef };
  131. }
  132. my $dbfile = $ENV{HOME} . "/notes/metadata.db";
  133. my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", "", "");
  134. my $sql = SQL::Abstract->new;
  135. my ($query, @bind) = $sql->select(
  136. 'links JOIN pages ON links.page = pages.page',
  137. # timestamp will be null if datetime is
  138. "*, DATETIME(datetime, 'localtime') AS timestamp",
  139. \%where,
  140. {-desc => [ 'datetime', 'page' ] }
  141. );
  142. if ($debug) {
  143. say STDERR $query;
  144. say STDERR Dumper(%where);
  145. }
  146. my $sth = $dbh->prepare($query);
  147. $sth->execute(@bind);
  148. while (my $data = $sth->fetchrow_hashref())
  149. {
  150. if ($debug) {
  151. print STDERR Dumper($data);
  152. }
  153. print $FORMATS{$format}->($data);
  154. }
  155. $sth->execute();
  156. # PHP-style file-content grabbing:
  157. sub file_get_contents {
  158. my ($file) = @_;
  159. open my $fh, '<', $file
  160. or die "Couldn't open $file: $!\n";
  161. my $contents;
  162. {
  163. # line separator:
  164. local $/ = undef;
  165. $contents = <$fh>;
  166. }
  167. close $fh or die "Couldn't close $file: $!";
  168. return $contents;
  169. }
  170. 1;