]> git.donarmstrong.com Git - bin.git/blob - bibtex_to_paper
add reset usb bus command
[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 use POSIX;
22
23 use 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   --search-by-pmid Search term is a pmid instead of a bibtex key
38   --pdf-dir pdf directory
39   --pdfviewer, -p pdf viewer to use
40   --only-print Only print PDF file name
41   --debug, -d debugging level (Default 0)
42   --help, -h display this help
43   --man, -m display manual
44
45 =head1 OPTIONS
46
47 =over
48
49 =item B<--bibtex, -b>
50
51 Bibtex file to look key up in
52
53 =item B<--bibtex-cache, -c>
54
55 Bibtex cache file; rebuilt if bibtex file changes
56
57 =item B<--pdfviewer, -p>
58
59 PDF viewer to use; defaults to evince unless a .xoj exists, in which
60 case xournal is used.
61
62 =item B<--only-print>
63
64 Only print the PDF file name, don't open it.
65
66 =item B<--debug, -d>
67
68 Debug verbosity. (Default 0)
69
70 =item B<--help, -h>
71
72 Display brief usage information.
73
74 =item B<--man, -m>
75
76 Display this manual.
77
78 =back
79
80 =head1 EXAMPLES
81
82 bibtex_to_paper
83
84 =cut
85
86
87 use vars qw($DEBUG);
88
89 my %options = (debug           => 0,
90                help            => 0,
91                man             => 0,
92                only_print      => 0,
93                search_by_pmid  => 0,
94                search_by_file  => 0,
95                use_git         => 1,
96                'bibtex_cache'  => File::Spec->catfile(User->Home,'.bibtex_to_paper_cache'),
97               );
98
99 GetOptions(\%options,
100            'build_cache|build-cache!',
101            'bibtex|b=s@',
102            'bibtex_cache|bibtex-cache|c=s',
103            'pdfviewer|p=s',
104            'use_git|use-git!',
105            'only_print|only-print!',
106            'search_by_pmid|search-by-pmid!',
107            'search_by_file|search-by-file!',
108            'clear_cache|clear-cache!',
109            'papers_directory|papers-directory=s@',
110            'debug|d+','help|h|?','man|m');
111
112 pod2usage() if $options{help};
113 pod2usage({verbose=>2}) if $options{man};
114
115 $DEBUG = $options{debug};
116
117 my @USAGE_ERRORS;
118 if (not exists $options{bibtex} and
119     not exists $options{bibtex_cache}) {
120     push @USAGE_ERRORS,
121         "You must give at least one of --bibtex".
122         "or --bibtex-cache";
123 }
124
125 pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
126
127 main();
128
129 sub main{
130
131     my $dbh;
132     my $sth;
133     if (exists $options{bibtex_cache}) {
134         my $initialize = 0;
135         if (-e $options{bibtex_cache}) {
136             ($dbh,$sth) = open_cache($options{bibtex_cache});
137         } else {
138             ($dbh,$sth) = initialize_database($options{bibtex_cache});
139         }
140     }
141
142     if (exists $options{clear_cache}) {
143         clear_cache($dbh,$sth);
144     }
145     my %entries;
146     if (exists $options{build_cache}) {
147         $options{bibtex} //= [];
148         $options{bibtex} =
149             [@ARGV,
150              @{ref $options{bibtex}?$options{bibtex}:[$options{bibtex}]},
151             ];
152         @ARGV = ();
153     }
154     if (exists $options{bibtex}) {
155         for my $bibtex_file (@{ref $options{bibtex}?$options{bibtex}:[$options{bibtex}]}) {
156             parse_bibtex_file($bibtex_file,\%entries);
157         }
158     }
159
160     if (exists $options{papers_directory} and
161         defined $dbh
162        ) {
163         $dbh->begin_work;
164         load_papers_into_database($dbh,$sth,$options{papers_directory});
165         $dbh->commit;
166     }
167
168     p %entries if $DEBUG;
169     if (keys %entries and
170         defined $dbh) {
171         $dbh->begin_work;
172         load_bibtex_entries_into_database($dbh,$sth,\%entries);
173         $dbh->commit;
174     }
175
176     p @ARGV if $DEBUG;
177     for my $bibtex_key (@ARGV) {
178         open_bibtex_key(\%options,$dbh,$sth,\%entries,$bibtex_key);
179     }
180
181 }
182
183 sub clear_cache {
184     my ($dbh,$sth) = @_;
185     $sth->{clear_papers_cache}->execute();
186     $sth->{clear_bibtex_cache}->execute();
187 }
188
189 sub load_papers_into_database {
190     my ($dbh,$sth,$dir) = @_;
191
192     my @dirs = ref($dir)?@{$dir}:$dir;
193
194     if ($options{use_git}) {
195         my @files = grep /\.pdf\"?$/, split /\n/, qx(git ls-tree HEAD -r --full-name --name-only);
196         for my $file  (@files) {
197             $file =~ s/^\"(.+)\"$/"qq($1)"/gee;
198             insert_or_replace_papers($dbh,$sth,basename($file),File::Spec->rel2abs($file), -e "${file}.xoj");
199         }
200     } else {
201         my $actually_load_it = sub {
202             if (/\.git/) {
203                 $File::Find::prune = 1;
204                 return;
205             }
206             return unless /\.pdf$/;
207             my $xoj = 0;
208             if (-e "${_}.xoj") {
209                 $xoj = 1;
210             }
211             insert_or_replace_papers($dbh,$sth,basename($File::Find::name),File::Spec->rel2abs($_),$xoj);
212         };
213         find($actually_load_it,@dirs);
214     }
215 }
216
217 sub insert_or_replace_papers {
218     my ($dbh,$sth,$file_name,$file_loc,$has_xoj) = @_;
219     $sth->{insert_papers}->execute($file_name,$file_loc,$has_xoj);
220     $sth->{insert_papers}->finish();
221 }
222
223 sub load_bibtex_entries_into_database {
224     my ($dbh,$sth,$entries) = @_;
225     for my $entry (keys %{$entries}) {
226         next unless defined $entries->{$entry};
227         $sth->{insert_bibtex}->execute($entry,@{$entries->{$entry}}{qw(file_name pmid doi html)});
228         $sth->{insert_bibtex}->finish();
229         print STDERR "inserted $entry {".join(',',map {defined $_?"'$_'":"'undef'"} %{$entries->{$entry}})."}\n" if $DEBUG;
230     }
231 }
232
233 sub open_bibtex_key {
234     my ($options,$dbh,$sth,$entries,$bibtex_key) = @_;
235     if (not defined $dbh) {
236         open_entry($dbh,$sth,$entries->{$bibtex_key},$options);
237     } else {
238         my $entry;
239         if ($options->{search_by_pmid}) {
240             $entry = select_entry_from_pmid($dbh,$sth,$bibtex_key);
241         } elsif ($options->{search_by_file}) {
242             $entry = select_entry_from_file($dbh,$sth,$bibtex_key);
243         } else {
244             $entry = select_entry_from_bibtex_key($dbh,$sth,$bibtex_key);
245         }
246         p $entry if $DEBUG;
247         open_entry($dbh,$sth,$entry,$options);
248     }
249 }
250
251 sub fork_exec {
252     my (@cmd) = @_;
253     my $child = fork();
254     if (not defined $child) {
255         die "Unable to fork for some reason: $!";
256     }
257     if ($child == 0) {
258         foreach (0 .. (POSIX::sysconf (&POSIX::_SC_OPEN_MAX) || 1024))
259            { POSIX::close $_ }
260         open (STDIN, "</dev/null");
261         open (STDOUT, ">/dev/null");
262         open (STDERR, ">&STDOUT");
263         exec(@cmd);
264     } else {
265         return $child;
266     }
267
268 }
269
270 sub open_pdf {
271     my ($file_name,$options,$has_xoj) = @_;
272     print STDERR "opening $file_name\n" if $DEBUG;
273     my $pdf_viewer = 'xournal';
274     if (exists $options->{pdfviewer} and defined $options->{pdfviewer}) {
275         $pdf_viewer = $options->{pdfviewer};
276     }
277     fork_exec($pdf_viewer,$file_name);
278 }
279
280 sub open_browser{
281     my ($file) = @_;
282     fork_exec('sensible-browser',$file);
283 }
284
285 sub open_entry{
286     my ($dbh,$sth,$entry,$options) = @_;
287
288     return unless defined $entry and ref $entry and keys %{$entry};
289     if ($DEBUG) {
290         print STDERR "Entry: \n";
291         p $entry;
292     }
293     if (defined $entry->{file_name} and length $entry->{file_name}) {
294         my $paper = select_one($dbh,$sth->{select_papers_by_name},$entry->{file_name});
295         if (not defined $paper) {
296             my ($pmid) = $entry->{file_name} =~ /pmid_(\d+)/;
297             if (defined $pmid and length $pmid) {
298                 $paper = select_one($dbh,$sth->{select_papers_by_pmid},'%pmid_'.$pmid.'.%');
299             }
300         }
301         p $paper if $DEBUG;
302         print STDERR $entry->{file_name} if $DEBUG;
303         if (defined $paper) {
304             if ($options->{only_print}) {
305                 print $paper->{path};
306                 return;
307             }
308             open_pdf($paper->{path},$options,$paper->{has_xoj});
309             return;
310         } else {
311             print STDERR "Unable to find paper\n" if $DEBUG;
312         }
313     }
314     if (defined $entry->{doi}) {
315         if ($options->{only_print}) {
316             print $entry->{doi};
317             return;
318         }
319         my $url = $entry->{doi};
320         $url =~ s{^doi://}{http://dx.doi.org/};
321         open_browser($url,$options);
322         return;
323     }
324     if (defined $entry->{html}) {
325         if ($options->{only_print}) {
326             print $entry->{html};
327             return;
328         }
329         open_browser($entry->{html},$options);
330         return;
331     }
332 }
333
334 sub select_entry_from_pmid{
335     my ($dbh,$sth,$pmid) = @_;
336
337     return select_one($dbh,$sth->{select_bibtex_by_pmid},$pmid);
338 }
339
340 sub select_entry_from_file{
341     my ($dbh,$sth,$filename) = @_;
342
343     return select_one($dbh,$sth->{select_bibtex_by_file_name_like},'%'.$filename.'%');
344 }
345
346
347 sub select_entry_from_bibtex_key{
348     my ($dbh,$sth,$bibtex_key) = @_;
349
350     my $entry = select_one($dbh,$sth->{select_bibtex_by_key},$bibtex_key);
351     if (not defined $entry) {
352         print STDERR "Unable to find entry by exact search\n" if $DEBUG;
353         $bibtex_key =~ s/:.*$//;
354         $entry = select_one($dbh,$sth->{select_bibtex_by_approximate_key},$bibtex_key.'%');
355     }
356     print STDERR "Found entry\n" if $DEBUG and defined $entry;
357     return $entry;
358 }
359
360 sub select_one{
361     my ($dbh,$sth,@bind_vals) = @_;
362     $sth->execute(@bind_vals) or
363         die "Unable to select one: ".$dbh->errstr();
364     my $results = $sth->fetchall_arrayref({});
365     $sth->finish();
366     return ref($results)?$results->[0]:undef;
367 }
368
369 sub parse_bibtex_file {
370     my ($file,$entries) = @_;
371
372     my $bibfile = Text::BibTeX::File->new($file)
373         or die "Unable to open $file for reading: $!";
374     my @entry_comments;
375     my $entry;
376     while ($entry = Text::BibTeX::Entry->new($bibfile)) {
377         print STDERR "In Entry ".$entry->metatype() if $DEBUG;
378         if ($entry->metatype() == BTE_COMMENT) {
379             push @entry_comments,$entry->value();
380         } elsif ($entry->metatype() == BTE_REGULAR) {
381             my $entry_key = $entry->key();
382             if (not defined $entry_key) {
383                 @entry_comments = ();
384                 next;
385             }
386             my %entry_data;
387             # if there is a file comment, use it as the file name
388             for my $comment (@entry_comments) {
389                 next unless $comment =~ /^\s*file(?:name)?:?\s*(.+?)\s*$/i;
390                 next unless length $1;
391                 $entry_data{file_name} = $1.'.pdf';
392                 last;
393             }
394             my %field_prefix = (doi => 'doi://',
395                                 html => 'http://',
396                                 file => '',
397                                 pmid => '',
398                                );
399             my %field_name = (doi => 'doi',
400                               html => 'html',
401                               pmid => 'pmid',
402                               file => 'file_name',);
403             for my $field (qw(file doi html pmid)) {
404                 my $field_value = $entry->get($field);
405                 if (defined $field_value and $field_value =~ /\S+/) {
406                     $entry_data{$field_name{$field}} =
407                         $field_prefix{$field}.$field_value if
408                         not defined $entry_data{$field_name{$field}};
409                 }
410             }
411             $entries->{$entry_key} = {} if not defined $entries->{$entry_key};
412             for my $field (keys %entry_data) {
413                 $entries->{$entry_key}{$field} = $entry_data{$field} if
414                     defined $entry_data{$field};
415             }
416             # reset the entry comments
417             @entry_comments = ();
418         } else {
419             # do nothing
420         }
421         print STDERR "\n" if $DEBUG;
422     }
423     return $entries;
424 }
425
426
427 sub initialize_database {
428     my ($cache) = @_;
429     return open_cache($cache,1);
430 }
431
432 sub open_cache {
433     my ($cache,$initialize) = @_;
434     my $dbh = DBI->connect("dbi:SQLite:dbname=$cache","","") or
435         die "Unable to open/create database $cache";
436     if ($initialize) {
437         $dbh->do("DROP TABLE IF EXISTS bibtex;");
438         $dbh->do("DROP TABLE IF EXISTS papers;");
439         $dbh->do(<<EOF);
440 CREATE TABLE bibtex (
441 bibtex_key TEXT PRIMARY KEY,
442 file_name TEXT,
443 pmid TEXT,
444 doi TEXT,
445 html TEXT
446 );
447 EOF
448         $dbh->do(<<EOF);
449 CREATE UNIQUE INDEX bibtex_file_name ON bibtex(file_name);
450 EOF
451         $dbh->do(<<EOF);
452 CREATE UNIQUE INDEX bibtex_bibtex_key ON bibtex(bibtex_key);
453 EOF
454         $dbh->do(<<EOF);
455 CREATE INDEX bibtex_pmid ON bibtex(pmid);
456 EOF
457         $dbh->do(<<EOF);
458 CREATE TABLE papers (
459 file_name TEXT PRIMARY KEY,
460 path TEXT,
461 has_xoj BOOLEAN
462 );
463 EOF
464         $dbh->do(<<EOF);
465 CREATE UNIQUE INDEX papers_path ON papers(path);
466 EOF
467         $dbh->do(<<EOF);
468 CREATE UNIQUE INDEX papers_file_name ON papers(file_name);
469 EOF
470     }
471     my %s =
472         (insert_papers => <<'EOF',
473 INSERT OR REPLACE INTO papers(file_name,path,has_xoj) VALUES (?,?,?);
474 EOF
475          insert_bibtex => <<'EOF',
476 INSERT OR REPLACE INTO bibtex (bibtex_key,file_name,pmid,doi,html) VALUES (?,?,?,?,?);
477 EOF
478          select_papers_by_name => <<'EOF',
479 SELECT * FROM papers WHERE file_name = ?;
480 EOF
481          select_papers_by_pmid => <<'EOF',
482 SELECT * FROM papers JOIN bibtex ON papers.file_name = bibtex.file_name WHERE bibtex.pmid = ?;
483 EOF
484          select_papers_by_name_like => <<'EOF',
485 SELECT * FROM papers WHERE file_name LIKE ?;
486 EOF
487          select_papers_by_path => <<'EOF',
488 SELECT * FROM papers WHERE path = ?;
489 EOF
490          select_bibtex_by_key => <<'EOF',
491 SELECT * FROM bibtex WHERE bibtex_key = ?;
492 EOF
493          select_bibtex_by_approximate_key => <<'EOF',
494 SELECT * FROM bibtex WHERE bibtex_key LIKE ?;
495 EOF
496          select_bibtex_by_file_name => <<'EOF',
497 SELECT * FROM bibtex WHERE file_name = ?;
498 EOF
499          select_bibtex_by_file_name_like => <<'EOF',
500 SELECT * FROM bibtex WHERE file_name LIKE ?;
501 EOF
502          select_bibtex_by_pmid => <<'EOF',
503 SELECT * FROM bibtex WHERE pmid = ?;
504 EOF
505          clear_papers_cache => <<'EOF',
506 DELETE FROM papers;
507 EOF
508          clear_bibtex_cache => <<'EOF',
509 DELETE FROM bibtex;
510 EOF
511         );
512     my $st;
513     for my $key (keys %s) {
514         $st->{$key}=$dbh->prepare($s{$key}) //
515             die "Unable to prepare sql statement: ".$dbh->errstr;
516     }
517     return ($dbh,$st);
518 }
519
520
521 __END__