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
6 # Copyright 2014 by Don Armstrong <don@donarmstrong.com>.
16 use File::Basename qw(basename);
17 use File::Spec qw(rel2abs);
27 bibtex_to_paper - opens the paper corresponding to a bibtex key
31 bibtex_to_paper [options] bibtexkey
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
51 Bibtex file to look key up in
53 =item B<--bibtex-cache, -c>
55 Bibtex cache file; rebuilt if bibtex file changes
57 =item B<--pdfviewer, -p>
59 PDF viewer to use; defaults to evince unless a .xoj exists, in which
64 Only print the PDF file name, don't open it.
68 Debug verbosity. (Default 0)
72 Display brief usage information.
89 my %options = (debug => 0,
96 'bibtex_cache' => File::Spec->catfile(User->Home,'.bibtex_to_paper_cache'),
100 'build_cache|build-cache!',
102 'bibtex_cache|bibtex-cache|c=s',
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');
112 pod2usage() if $options{help};
113 pod2usage({verbose=>2}) if $options{man};
115 $DEBUG = $options{debug};
118 if (not exists $options{bibtex} and
119 not exists $options{bibtex_cache}) {
121 "You must give at least one of --bibtex".
125 pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
133 if (exists $options{bibtex_cache}) {
135 if (-e $options{bibtex_cache}) {
136 ($dbh,$sth) = open_cache($options{bibtex_cache});
138 ($dbh,$sth) = initialize_database($options{bibtex_cache});
142 if (exists $options{clear_cache}) {
143 clear_cache($dbh,$sth);
146 if (exists $options{build_cache}) {
147 $options{bibtex} //= [];
150 @{ref $options{bibtex}?$options{bibtex}:[$options{bibtex}]},
154 if (exists $options{bibtex}) {
155 for my $bibtex_file (@{ref $options{bibtex}?$options{bibtex}:[$options{bibtex}]}) {
156 parse_bibtex_file($bibtex_file,\%entries);
160 if (exists $options{papers_directory} and
164 load_papers_into_database($dbh,$sth,$options{papers_directory});
168 p %entries if $DEBUG;
169 if (keys %entries and
172 load_bibtex_entries_into_database($dbh,$sth,\%entries);
177 for my $bibtex_key (@ARGV) {
178 open_bibtex_key(\%options,$dbh,$sth,\%entries,$bibtex_key);
185 $sth->{clear_papers_cache}->execute();
186 $sth->{clear_bibtex_cache}->execute();
189 sub load_papers_into_database {
190 my ($dbh,$sth,$dir) = @_;
192 my @dirs = ref($dir)?@{$dir}:$dir;
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");
201 my $actually_load_it = sub {
203 $File::Find::prune = 1;
206 return unless /\.pdf$/;
211 insert_or_replace_papers($dbh,$sth,basename($File::Find::name),File::Spec->rel2abs($_),$xoj);
213 find($actually_load_it,@dirs);
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();
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;
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);
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);
244 $entry = select_entry_from_bibtex_key($dbh,$sth,$bibtex_key);
247 open_entry($dbh,$sth,$entry,$options);
254 if (not defined $child) {
255 die "Unable to fork for some reason: $!";
258 foreach (0 .. (POSIX::sysconf (&POSIX::_SC_OPEN_MAX) || 1024))
260 open (STDIN, "</dev/null");
261 open (STDOUT, ">/dev/null");
262 open (STDERR, ">&STDOUT");
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};
277 fork_exec($pdf_viewer,$file_name);
282 fork_exec('sensible-browser',$file);
286 my ($dbh,$sth,$entry,$options) = @_;
288 return unless defined $entry and ref $entry and keys %{$entry};
290 print STDERR "Entry: \n";
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.'.%');
302 print STDERR $entry->{file_name} if $DEBUG;
303 if (defined $paper) {
304 if ($options->{only_print}) {
305 print $paper->{path};
308 open_pdf($paper->{path},$options,$paper->{has_xoj});
311 print STDERR "Unable to find paper\n" if $DEBUG;
314 if (defined $entry->{doi}) {
315 if ($options->{only_print}) {
319 my $url = $entry->{doi};
320 $url =~ s{^doi://}{http://dx.doi.org/};
321 open_browser($url,$options);
324 if (defined $entry->{html}) {
325 if ($options->{only_print}) {
326 print $entry->{html};
329 open_browser($entry->{html},$options);
334 sub select_entry_from_pmid{
335 my ($dbh,$sth,$pmid) = @_;
337 return select_one($dbh,$sth->{select_bibtex_by_pmid},$pmid);
340 sub select_entry_from_file{
341 my ($dbh,$sth,$filename) = @_;
343 return select_one($dbh,$sth->{select_bibtex_by_file_name_like},'%'.$filename.'%');
347 sub select_entry_from_bibtex_key{
348 my ($dbh,$sth,$bibtex_key) = @_;
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.'%');
356 print STDERR "Found entry\n" if $DEBUG and defined $entry;
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({});
366 return ref($results)?$results->[0]:undef;
369 sub parse_bibtex_file {
370 my ($file,$entries) = @_;
372 my $bibfile = Text::BibTeX::File->new($file)
373 or die "Unable to open $file for reading: $!";
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 = ();
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';
394 my %field_prefix = (doi => 'doi://',
399 my %field_name = (doi => 'doi',
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}};
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};
416 # reset the entry comments
417 @entry_comments = ();
421 print STDERR "\n" if $DEBUG;
427 sub initialize_database {
429 return open_cache($cache,1);
433 my ($cache,$initialize) = @_;
434 my $dbh = DBI->connect("dbi:SQLite:dbname=$cache","","") or
435 die "Unable to open/create database $cache";
437 $dbh->do("DROP TABLE IF EXISTS bibtex;");
438 $dbh->do("DROP TABLE IF EXISTS papers;");
440 CREATE TABLE bibtex (
441 bibtex_key TEXT PRIMARY KEY,
449 CREATE UNIQUE INDEX bibtex_file_name ON bibtex(file_name);
452 CREATE UNIQUE INDEX bibtex_bibtex_key ON bibtex(bibtex_key);
455 CREATE INDEX bibtex_pmid ON bibtex(pmid);
458 CREATE TABLE papers (
459 file_name TEXT PRIMARY KEY,
465 CREATE UNIQUE INDEX papers_path ON papers(path);
468 CREATE UNIQUE INDEX papers_file_name ON papers(file_name);
472 (insert_papers => <<'EOF',
473 INSERT OR REPLACE INTO papers(file_name,path,has_xoj) VALUES (?,?,?);
475 insert_bibtex => <<'EOF',
476 INSERT OR REPLACE INTO bibtex (bibtex_key,file_name,pmid,doi,html) VALUES (?,?,?,?,?);
478 select_papers_by_name => <<'EOF',
479 SELECT * FROM papers WHERE file_name = ?;
481 select_papers_by_pmid => <<'EOF',
482 SELECT * FROM papers JOIN bibtex ON papers.file_name = bibtex.file_name WHERE bibtex.pmid = ?;
484 select_papers_by_name_like => <<'EOF',
485 SELECT * FROM papers WHERE file_name LIKE ?;
487 select_papers_by_path => <<'EOF',
488 SELECT * FROM papers WHERE path = ?;
490 select_bibtex_by_key => <<'EOF',
491 SELECT * FROM bibtex WHERE bibtex_key = ?;
493 select_bibtex_by_approximate_key => <<'EOF',
494 SELECT * FROM bibtex WHERE bibtex_key LIKE ?;
496 select_bibtex_by_file_name => <<'EOF',
497 SELECT * FROM bibtex WHERE file_name = ?;
499 select_bibtex_by_file_name_like => <<'EOF',
500 SELECT * FROM bibtex WHERE file_name LIKE ?;
502 select_bibtex_by_pmid => <<'EOF',
503 SELECT * FROM bibtex WHERE pmid = ?;
505 clear_papers_cache => <<'EOF',
508 clear_bibtex_cache => <<'EOF',
513 for my $key (keys %s) {
514 $st->{$key}=$dbh->prepare($s{$key}) //
515 die "Unable to prepare sql statement: ".$dbh->errstr;