]> git.donarmstrong.com Git - bin.git/blob - bibtex_to_paper
ignore git files
[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
24 =head1 NAME
25
26 bibtex_to_paper - opens the paper corresponding to a bibtex key
27
28 =head1 SYNOPSIS
29
30 bibtex_to_paper [options] bibtexkey
31
32  Options:
33   --bibtex, -b bibtex file to look up key in
34   --bibtex-cache, -c bibtex cache file
35   --build-cache, -B build cache using bibtex files
36   --pdf-dir pdf directory
37   --pdfviewer, -p pdf viewer to use
38   --debug, -d debugging level (Default 0)
39   --help, -h display this help
40   --man, -m display manual
41
42 =head1 OPTIONS
43
44 =over
45
46 =item B<--bibtex, -b>
47
48 Bibtex file to look key up in
49
50 =item B<--bibtex-cache, -c>
51
52 Bibtex cache file; rebuilt if bibtex file changes
53
54 =item B<--pdfviewer, -p>
55
56 PDF viewer to use; defaults to evince unless a .xoj exists, in which
57 case xournal is used.
58
59 =item B<--debug, -d>
60
61 Debug verbosity. (Default 0)
62
63 =item B<--help, -h>
64
65 Display brief usage information.
66
67 =item B<--man, -m>
68
69 Display this manual.
70
71 =back
72
73 =head1 EXAMPLES
74
75 bibtex_to_paper
76
77 =cut
78
79
80 use vars qw($DEBUG);
81
82 my %options = (debug           => 0,
83                help            => 0,
84                man             => 0,
85                'bibtex_cache'  => File::Spec->catfile(User->Home,'.bibtex_to_paper_cache'),
86               );
87
88 GetOptions(\%options,
89            'build_cache|build-cache!',
90            'bibtex|b=s@',
91            'bibtex_cache|bibtex-cache|c=s',
92            'pdfviewer|p=s',
93            'clear_cache|clear-cache!',
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     if (exists $options{clear_cache}) {
128         clear_cache($dbh,$sth);
129     }
130     my %entries;
131     if (exists $options{build_cache}) {
132         $options{bibtex} //= [];
133         $options{bibtex} =
134             [@ARGV,
135              @{ref $options{bibtex}?$options{bibtex}:[$options{bibtex}]},
136             ];
137         @ARGV = ();
138     }
139     if (exists $options{bibtex}) {
140         for my $bibtex_file (@{ref $options{bibtex}?$options{bibtex}:[$options{bibtex}]}) {
141             parse_bibtex_file($bibtex_file,\%entries);
142         }
143     }
144
145     if (exists $options{papers_directory} and
146         defined $dbh
147        ) {
148         load_papers_into_database($dbh,$sth,$options{papers_directory});
149     }
150
151     p %entries if $DEBUG;
152     if (keys %entries and
153         defined $dbh) {
154         load_bibtex_entries_into_database($dbh,$sth,\%entries);
155     }
156
157     p @ARGV if $DEBUG;
158     for my $bibtex_key (@ARGV) {
159         open_bibtex_key(\%options,$dbh,$sth,\%entries,$bibtex_key);
160     }
161
162 }
163
164 sub clear_cache {
165     my ($dbh,$sth) = @_;
166     $sth->{clear_papers_cache}->execute();
167     $sth->{clear_bibtex_cache}->execute();
168 }
169
170 sub load_papers_into_database {
171     my ($dbh,$sth,$dir) = @_;
172
173     my @dirs = ref($dir)?@{$dir}:$dir;
174
175     my $actually_load_it = sub {
176         if (/\.git/) {
177             $File::Find::prune = 1;
178             return;
179         }
180         return unless /\.pdf$/;
181         my $xoj = 0;
182         if (-e "${_}.xoj") {
183             $xoj = 1;
184         }
185         insert_or_replace_papers($dbh,$sth,basename($File::Find::name),File::Spec->rel2abs($_),$xoj);
186     };
187
188     my @pdfs;
189     find($actually_load_it,@dirs);
190 }
191
192 sub insert_or_replace_papers {
193     my ($dbh,$sth,$file_name,$file_loc,$has_xoj) = @_;
194     $sth->{insert_papers}->execute($file_name,$file_loc,$has_xoj);
195     $sth->{insert_papers}->finish();
196 }
197
198 sub load_bibtex_entries_into_database {
199     my ($dbh,$sth,$entries) = @_;
200     for my $entry (keys %{$entries}) {
201         next unless defined $entries->{$entry};
202         $sth->{insert_bibtex}->execute($entry,@{$entries->{$entry}}{qw(file_name doi html)});
203         $sth->{insert_bibtex}->finish();
204         print STDERR "inserted $entry {".join(',',map {defined $_?"'$_'":"'undef'"} %{$entries->{$entry}})."}\n" if $DEBUG;
205     }
206 }
207
208 sub open_bibtex_key {
209     my ($options,$dbh,$sth,$entries,$bibtex_key) = @_;
210     if (not defined $dbh) {
211         open_entry($dbh,$sth,$entries->{$bibtex_key},$options);
212     } else {
213         my $entry = select_entry_from_bibtex_key($dbh,$sth,$bibtex_key);
214         p $entry if $DEBUG;
215         open_entry($dbh,$sth,$entry,$options);
216     }
217 }
218
219 sub fork_exec {
220     my (@cmd) = @_;
221     my $child = fork();
222     if (not defined $child) {
223         die "Unable to fork for some reason: $!";
224     }
225     if ($child == 0) {
226         exec(@cmd);
227     } else {
228         return $child;
229     }
230
231 }
232
233 sub open_pdf {
234     my ($file_name,$options,$has_xoj) = @_;
235     print STDERR "opening $file_name\n" if $DEBUG;
236     if ($has_xoj) {
237         fork_exec('xournal',$file_name);
238     } else {
239         fork_exec('evince',$file_name)
240     }
241 }
242
243 sub open_browser{
244     my ($file) = @_;
245     fork_exec('sensible-browser',$file);
246 }
247
248 sub open_entry{
249     my ($dbh,$sth,$entry,$options) = @_;
250
251     return unless defined $entry and ref $entry and keys %{$entry};
252     if (defined $entry->{file_name} and length $entry->{file_name}) {
253         my $paper = select_one($dbh,$sth->{select_papers_by_name},$entry->{file_name});
254         if (not defined $paper) {
255             my ($pmid) = $entry->{file_name} =~ /pmid_(\d+)/;
256             if (defined $pmid and length $pmid) {
257                 $paper = select_one($dbh,$sth->{select_papers_by_pmid},'%pmid_'.$pmid.'.%');
258             }
259         }
260         p $paper if $DEBUG;
261         print STDERR $entry->{file_name} if $DEBUG;
262         if (defined $paper) {
263             open_pdf($paper->{path},$options,$paper->{has_xoj});
264             return;
265         }
266     }
267     if (defined $entry->{doi}) {
268         my $url = $entry->{doi};
269         $url =~ s{^doi://}{http://dx.doi.org/};
270         open_browser($url,$options);
271         return;
272     }
273     if (defined $entry->{html}) {
274         open_browser($entry->{html},$options);
275         return;
276     }
277 }
278
279 sub select_entry_from_bibtex_key{
280     my ($dbh,$sth,$bibtex_key) = @_;
281
282     my $entry = select_one($dbh,$sth->{select_bibtex_by_key},$bibtex_key);
283     if (not defined $entry) {
284         $bibtex_key =~ s/:.*$//;
285         $entry = select_one($dbh,$sth->{select_bibtex_by_approximate_key},$bibtex_key.'%');
286     }
287     return $entry;
288 }
289
290 sub select_one{
291     my ($dbh,$sth,@bind_vals) = @_;
292     $sth->execute(@bind_vals) or
293         die "Unable to select one: ".$dbh->errstr();
294     my $results = $sth->fetchall_arrayref({});
295     $sth->finish();
296     return ref($results)?$results->[0]:undef;
297 }
298
299 sub parse_bibtex_file {
300     my ($file,$entries) = @_;
301
302     my $bibfile = Text::BibTeX::File->new($file)
303         or die "Unable to open $file for reading: $!";
304     my @entry_comments;
305     my $entry;
306     while ($entry = Text::BibTeX::Entry->new($bibfile)) {
307         print STDERR "In Entry ".$entry->metatype() if $DEBUG;
308         if ($entry->metatype() == BTE_COMMENT) {
309             push @entry_comments,$entry->value();
310         } elsif ($entry->metatype() == BTE_REGULAR) {
311             my $entry_key = $entry->key();
312             if (not defined $entry_key) {
313                 @entry_comments = ();
314                 next;
315             }
316             my %entry_data;
317             # if there is a file comment, use it as the file name
318             for my $comment (@entry_comments) {
319                 next unless $comment =~ /^\s*file(?:name)?:?\s*(.+?)\s*$/i;
320                 next unless length $1;
321                 $entry_data{file_name} = $1.'.pdf';
322                 last;
323             }
324             my %field_prefix = (doi => 'doi://',
325                                 html => 'http://',
326                                 file => '',
327                                );
328             my %field_name = (doi => 'doi',
329                               html => 'html',
330                               file => 'file_name',);
331             for my $field (qw(file doi html)) {
332                 my $field_value = $entry->get($field);
333                 if (defined $field_value and $field_value =~ /\S+/) {
334                     $entry_data{$field_name{$field}} =
335                         $field_prefix{$field}.$field_value if
336                         not defined $entry_data{$field_name{$field}};
337                 }
338             }
339             $entries->{$entry_key} = {} if not defined $entries->{$entry_key};
340             for my $field (keys %entry_data) {
341                 $entries->{$entry_key}{$field} = $entry_data{$field} if
342                     defined $entry_data{$field};
343             }
344             # reset the entry comments
345             @entry_comments = ();
346         } else {
347             # do nothing
348         }
349         print STDERR "\n" if $DEBUG;
350     }
351     return $entries;
352 }
353
354
355 sub initialize_database {
356     my ($cache) = @_;
357     return open_cache($cache,1);
358 }
359
360 sub open_cache {
361     my ($cache,$initialize) = @_;
362     my $dbh = DBI->connect("dbi:SQLite:dbname=$cache","","") or
363         die "Unable to open/create database $cache";
364     if ($initialize) {
365         $dbh->do("DROP TABLE IF EXISTS bibtex;");
366         $dbh->do("DROP TABLE IF EXISTS papers;");
367         $dbh->do(<<EOF);
368 CREATE TABLE bibtex (
369 bibtex_key TEXT PRIMARY KEY,
370 file_name TEXT,
371 doi TEXT,
372 html TEXT
373 );
374 EOF
375         $dbh->do(<<EOF);
376 CREATE UNIQUE INDEX bibtex_file_name ON bibtex(file_name);
377 EOF
378         $dbh->do(<<EOF);
379 CREATE UNIQUE INDEX bibtex_bibtex_key ON bibtex(bibtex_key);
380 EOF
381         $dbh->do(<<EOF);
382 CREATE TABLE papers (
383 file_name TEXT PRIMARY KEY,
384 path TEXT,
385 has_xoj BOOLEAN
386 );
387 EOF
388         $dbh->do(<<EOF);
389 CREATE UNIQUE INDEX papers_path ON papers(path);
390 EOF
391         $dbh->do(<<EOF);
392 CREATE UNIQUE INDEX papers_file_name ON papers(file_name);
393 EOF
394     }
395     my %s =
396         (insert_papers => <<'EOF',
397 INSERT OR REPLACE INTO papers(file_name,path,has_xoj) VALUES (?,?,?);
398 EOF
399          insert_bibtex => <<'EOF',
400 INSERT OR REPLACE INTO bibtex (bibtex_key,file_name,doi,html) VALUES (?,?,?,?);
401 EOF
402          select_papers_by_name => <<'EOF',
403 SELECT * FROM papers WHERE file_name = ?;
404 EOF
405          select_papers_by_pmid => <<'EOF',
406 SELECT * FROM papers WHERE file_name LIKE ?;
407 EOF
408          select_papers_by_path => <<'EOF',
409 SELECT * FROM papers WHERE path = ?;
410 EOF
411          select_bibtex_by_key => <<'EOF',
412 SELECT * FROM bibtex WHERE bibtex_key = ?;
413 EOF
414          select_bibtex_by_approximate_key => <<'EOF',
415 SELECT * FROM bibtex WHERE bibtex_key LIKE ?;
416 EOF
417          select_bibtex_by_file_name => <<'EOF',
418 SELECT * FROM bibtex WHERE file_name = ?;
419 EOF
420          clear_papers_cache => <<'EOF',
421 DELETE FROM papers;
422 EOF
423          clear_bibtex_cache => <<'EOF',
424 DELETE FROM bibtex;
425 EOF
426         );
427     my $st;
428     for my $key (keys %s) {
429         $st->{$key}=$dbh->prepare($s{$key}) //
430             die "Unable to prepare sql statement: ".$dbh->errstr;
431     }
432     return ($dbh,$st);
433 }
434
435
436 __END__