]> git.donarmstrong.com Git - bin.git/blob - bibtex_to_paper
switch to sqlite for bibtex_to_paper; default cache location; actually kind of works now
[bin.git] / bibtex_to_paper
1 #!/usr/bin/perl
2 # bibtex_to_paper opens the paper corresponding to a bibtex key
3 # and is released under the terms of the GNU GPL version 3, or any
4 # later version, at your option. See the file README and COPYING for
5 # more information.
6 # Copyright 2014 by Don Armstrong <don@donarmstrong.com>.
7
8
9 use warnings;
10 use strict;
11
12 use Getopt::Long;
13 use Pod::Usage;
14
15 use File::Find;
16 use File::Basename qw(basename);
17 use File::Spec qw(rel2abs);
18 use Text::BibTeX;
19 use User;
20 use Data::Printer;
21
22 use DBI;
23 use Tie::DBI;
24
25 =head1 NAME
26
27 bibtex_to_paper - opens the paper corresponding to a bibtex key
28
29 =head1 SYNOPSIS
30
31 bibtex_to_paper [options] bibtexkey
32
33  Options:
34   --bibtex, -b bibtex file to look up key in
35   --bibtex-cache, -c bibtex cache file
36   --build-cache, -B build cache using bibtex files
37   --pdf-dir pdf directory
38   --pdfviewer, -p pdf viewer to use
39   --debug, -d debugging level (Default 0)
40   --help, -h display this help
41   --man, -m display manual
42
43 =head1 OPTIONS
44
45 =over
46
47 =item B<--bibtex, -b>
48
49 Bibtex file to look key up in
50
51 =item B<--bibtex-cache, -c>
52
53 Bibtex cache file; rebuilt if bibtex file changes
54
55 =item B<--pdfviewer, -p>
56
57 PDF viewer to use; defaults to evince unless a .xoj exists, in which
58 case xournal is used.
59
60 =item B<--debug, -d>
61
62 Debug verbosity. (Default 0)
63
64 =item B<--help, -h>
65
66 Display brief usage information.
67
68 =item B<--man, -m>
69
70 Display this manual.
71
72 =back
73
74 =head1 EXAMPLES
75
76 bibtex_to_paper
77
78 =cut
79
80
81 use vars qw($DEBUG);
82
83 my %options = (debug           => 0,
84                help            => 0,
85                man             => 0,
86                'bibtex_cache'  => File::Spec->catfile(User->Home,'.bibtex_to_paper_cache'),
87               );
88
89 GetOptions(\%options,
90            'build_cache|build-cache|B!',
91            'bibtex|b=s@',
92            'bibtex_cache|bibtex-cache|c=s',
93            'pdfviewer|p=s',
94            'papers_directory|papers-directory=s@',
95            'debug|d+','help|h|?','man|m');
96
97 pod2usage() if $options{help};
98 pod2usage({verbose=>2}) if $options{man};
99
100 $DEBUG = $options{debug};
101
102 my @USAGE_ERRORS;
103 if (not exists $options{bibtex} and
104     not exists $options{bibtex_cache}) {
105     push @USAGE_ERRORS,
106         "You must give at least one of --bibtex".
107         "or --bibtex-cache";
108 }
109
110 pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
111
112 main();
113
114 sub main{
115
116     my $dbh;
117     my $sth;
118     if (exists $options{bibtex_cache}) {
119         my $initialize = 0;
120         if (-e $options{bibtex_cache}) {
121             ($dbh,$sth) = open_cache($options{bibtex_cache});
122         } else {
123             ($dbh,$sth) = initialize_database($options{bibtex_cache});
124         }
125     }
126
127     my %entries;
128     if (exists $options{build_cache}) {
129         $options{bibtex} //= [];
130         $options{bibtex} =
131             [@ARGV,
132              @{ref $options{bibtex}?$options{bibtex}:[$options{bibtex}]},
133             ];
134     }
135     if (exists $options{bibtex}) {
136         for my $bibtex_file (@{ref $options{bibtex}?$options{bibtex}:[$options{bibtex}]}) {
137             parse_bibtex_file($bibtex_file,\%entries);
138         }
139     }
140
141     if (exists $options{papers_directory} and
142         defined $dbh
143        ) {
144         load_papers_into_database($dbh,$sth,$options{papers_directory});
145     }
146
147     p %entries if $DEBUG;
148     if (keys %entries and
149         defined $dbh) {
150         load_bibtex_entries_into_database($dbh,$sth,\%entries);
151     }
152
153     for my $bibtex_key (@ARGV) {
154         open_bibtex_key(\%options,$dbh,$sth,\%entries,$bibtex_key);
155     }
156
157 }
158
159 sub load_papers_into_database {
160     my ($dbh,$sth,$dir) = @_;
161
162     my @dirs = ref($dir)?@{$dir}:$dir;
163
164     my $actually_load_it = sub {
165         return unless /\.pdf$/;
166         my $xoj = 0;
167         if (-e "${_}.xoj") {
168             $xoj = 1;
169         }
170         insert_or_replace_papers($dbh,$sth,basename($File::Find::name),File::Spec->rel2abs($File::Find::name),$xoj);
171     };
172
173     my @pdfs;
174     find($actually_load_it,@dirs);
175 }
176
177 sub insert_or_replace_papers {
178     my ($dbh,$sth,$file_name,$file_loc,$has_xoj) = @_;
179     $sth->{insert_papers}->execute($file_name,$file_loc,$has_xoj);
180     $sth->{insert_papers}->finish();
181 }
182
183 sub load_bibtex_entries_into_database {
184     my ($dbh,$sth,$entries) = @_;
185     for my $entry (keys %{$entries}) {
186         next unless defined $entries->{$entry};
187         $sth->{insert_bibtex}->execute($entry,@{$entries->{$entry}}{qw(file_name doi html)});
188         $sth->{insert_bibtex}->finish();
189         print STDERR "inserted $entry $entries->{$entry}\n" if $DEBUG;
190     }
191 }
192
193 sub open_bibtex_key {
194     my ($options,$dbh,$sth,$entries,$bibtex_key) = @_;
195     if (not defined $dbh) {
196         open_entry($dbh,$sth,$entries->{$bibtex_key},$options);
197     } else {
198         my $entry = select_entry_from_bibtex_key($dbh,$sth,$bibtex_key);
199         p $entry if $DEBUG;
200         open_entry($dbh,$sth,$entry,$options);
201     }
202 }
203
204 sub fork_exec {
205     my (@cmd) = @_;
206     my $child = fork();
207     if (not defined $child) {
208         die "Unable to fork for some reason: $!";
209     }
210     if ($child == 0) {
211         exec(@cmd);
212     } else {
213         return $child;
214     }
215
216 }
217
218 sub open_pdf {
219     my ($file_name,$options,$has_xoj) = @_;
220     print STDERR "opening $file_name\n" if $DEBUG;
221     if ($has_xoj) {
222         fork_exec('xournal',$file_name);
223     } else {
224         fork_exec('evince',$file_name)
225     }
226 }
227
228 sub open_browser{
229     my ($file) = @_;
230     fork_exec('sensible-browser',$file);
231 }
232
233 sub open_entry{
234     my ($dbh,$sth,$entry,$options) = @_;
235
236     return unless defined $entry and ref $entry and keys %{$entry};
237     if (defined $entry->{file_name} and length $entry->{file_name}) {
238         my $paper = select_one($dbh,$sth->{select_papers_by_name},$entry->{file_name});
239         p $paper if $DEBUG;
240         print STDERR $entry->{file_name} if $DEBUG;
241         if (defined $paper) {
242             open_pdf($paper->{file_name},$options,$paper->{xoj});
243             return;
244         }
245     }
246     if (defined $entry->{doi}) {
247         my $url = $entry->{doi};
248         $url =~ s{^doi://}{http://dx.doi.org/};
249         open_browser($url,$options);
250         return;
251     }
252     if (defined $entry->{html}) {
253         open_browser($entry->{html},$options);
254         return;
255     }
256 }
257
258 sub select_entry_from_bibtex_key{
259     my ($dbh,$sth,$bibtex_key) = @_;
260
261     my $entry = select_one($dbh,$sth->{select_bibtex_by_key},$bibtex_key);
262     return $entry;
263 }
264
265 sub select_one{
266     my ($dbh,$sth,@bind_vals) = @_;
267     $sth->execute(@bind_vals) or
268         die "Unable to select one: ".$dbh->errstr();
269     my $results = $sth->fetchall_arrayref({});
270     $sth->finish();
271     return ref($results)?$results->[0]:undef;
272 }
273
274 sub parse_bibtex_file {
275     my ($file,$entries) = @_;
276
277     my $bibfile = Text::BibTeX::File->new($file)
278         or die "Unable to open $file for reading: $!";
279     my @entry_comments;
280     my $entry;
281     while ($entry = Text::BibTeX::Entry->new($bibfile)) {
282         print STDERR "In Entry ".$entry->metatype() if $DEBUG;
283         if ($entry->metatype() == BTE_COMMENT) {
284             push @entry_comments,$entry->value();
285         } elsif ($entry->metatype() == BTE_REGULAR) {
286             my $entry_key = $entry->key();
287             if (not defined $entry_key) {
288                 @entry_comments = ();
289                 next;
290             }
291             my %entry_data;
292             # if there is a file comment, use it as the file name
293             for my $comment (@entry_comments) {
294                 next unless $comment =~ /^\s*file(?:name)?:?\s*(.+?)\s*$/i;
295                 next unless length $1;
296                 $entry_data{file_name} = $1.'.pdf';
297                 last;
298             }
299             my %field_prefix = (doi => 'doi://',
300                                 html => 'http://',
301                                 file => '',
302                                );
303             my %field_name = (doi => 'doi',
304                               html => 'html',
305                               file => 'file_name',);
306             for my $field (qw(file doi html)) {
307                 my $field_value = $entry->get($field);
308                 if (defined $field_value and $field_value =~ /\S+/) {
309                     $entry_data{$field_name{$field}} =
310                         $field_prefix{$field}.$field_value if
311                         not defined $entry_data{$field_name{$field}};
312                 }
313             }
314             $entries->{$entry_key} = {} if not defined $entries->{$entry_key};
315             for my $field (keys %entry_data) {
316                 $entries->{$entry_key}{$field} = $entry_data{$field} if
317                     defined $entry_data{$field};
318             }
319             # reset the entry comments
320             @entry_comments = ();
321         } else {
322             # do nothing
323         }
324         print STDERR "\n" if $DEBUG;
325     }
326     return $entries;
327 }
328
329
330 sub initialize_database {
331     my ($cache) = @_;
332     return open_cache($cache,1);
333 }
334
335 sub open_cache {
336     my ($cache,$initialize) = @_;
337     my $dbh = DBI->connect("dbi:SQLite:dbname=$cache","","") or
338         die "Unable to open/create database $cache";
339     if ($initialize) {
340         $dbh->do("DROP TABLE IF EXISTS bibtex;");
341         $dbh->do("DROP TABLE IF EXISTS papers;");
342         $dbh->do(<<EOF);
343 CREATE TABLE bibtex (
344 bibtex_key TEXT PRIMARY KEY,
345 file_name TEXT,
346 doi TEXT,
347 html TEXT
348 );
349 EOF
350         $dbh->do(<<EOF);
351 CREATE UNIQUE INDEX bibtex_file_name ON bibtex(file_name);
352 EOF
353         $dbh->do(<<EOF);
354 CREATE UNIQUE INDEX bibtex_bibtex_key ON bibtex(bibtex_key);
355 EOF
356         $dbh->do(<<EOF);
357 CREATE TABLE papers (
358 file_name TEXT PRIMARY KEY,
359 path TEXT,
360 has_xoj BOOLEAN
361 );
362 EOF
363         $dbh->do(<<EOF);
364 CREATE UNIQUE INDEX papers_path ON papers(path);
365 EOF
366         $dbh->do(<<EOF);
367 CREATE UNIQUE INDEX papers_file_name ON papers(file_name);
368 EOF
369     }
370     my %s =
371         (insert_papers => <<'EOF',
372 INSERT OR REPLACE INTO papers(file_name,path,has_xoj) VALUES (?,?,?);
373 EOF
374          insert_bibtex => <<'EOF',
375 INSERT OR REPLACE INTO bibtex (bibtex_key,file_name,doi,html) VALUES (?,?,?,?);
376 EOF
377          select_papers_by_name => <<'EOF',
378 SELECT * FROM papers WHERE file_name = ?;
379 EOF
380          select_papers_by_path => <<'EOF',
381 SELECT * FROM papers WHERE path = ?;
382 EOF
383          select_bibtex_by_key => <<'EOF',
384 SELECT * FROM bibtex WHERE bibtex_key = ?;
385 EOF
386          select_bibtex_by_file_name => <<'EOF',
387 SELECT * FROM bibtex WHERE file_name = ?;
388 EOF
389         );
390     my $st;
391     for my $key (keys %s) {
392         $st->{$key}=$dbh->prepare($s{$key}) //
393             die "Unable to prepare sql statement: ".$dbh->errstr;
394     }
395     return ($dbh,$st);
396 }
397
398
399 __END__