bibsync 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +10 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +88 -0
- data/Rakefile +16 -0
- data/bibsync.gemspec +4 -2
- data/lib/bibsync/actions/{check_versions.rb → check_arxiv_versions.rb} +6 -6
- data/lib/bibsync/actions/determine_arxiv_doi.rb +70 -0
- data/lib/bibsync/actions/fetch_from_arxiv.rb +11 -9
- data/lib/bibsync/actions/find_my_citations.rb +4 -4
- data/lib/bibsync/actions/jabref_format.rb +2 -2
- data/lib/bibsync/actions/synchronize_files.rb +5 -6
- data/lib/bibsync/actions/synchronize_metadata.rb +14 -57
- data/lib/bibsync/actions/validate.rb +16 -6
- data/lib/bibsync/actions.rb +1 -7
- data/lib/bibsync/bibliography.rb +60 -23
- data/lib/bibsync/command.rb +13 -8
- data/lib/bibsync/log.rb +22 -20
- data/lib/bibsync/transformer.rb +1 -1
- data/lib/bibsync/utils.rb +7 -9
- data/lib/bibsync/version.rb +1 -1
- data/test/actions/test_check_arxiv_versions.rb +4 -0
- data/test/actions/test_determine_arxiv_doi.rb +61 -0
- data/test/actions/test_fetch_from_arxiv.rb +4 -0
- data/test/actions/test_find_my_citations.rb +4 -0
- data/test/actions/test_jabref_format.rb +4 -0
- data/test/actions/test_synchronize_files.rb +4 -0
- data/test/actions/test_synchronize_metadata.rb +34 -0
- data/test/actions/test_validate.rb +4 -0
- data/test/fixture/FileWithEmbeddedArXiv.pdf +0 -0
- data/test/fixture/FileWithEmbeddedArXiv.tex +7 -0
- data/test/fixture/FileWithEmbeddedDOI.pdf +0 -0
- data/test/fixture/FileWithEmbeddedDOI.tex +7 -0
- data/test/fixture/entry.bib +8 -0
- data/test/fixture/test.bib +34 -0
- data/test/helper.rb +21 -0
- data/test/test_bibliography.rb +222 -0
- data/test/test_utils.rb +54 -0
- metadata +63 -16
data/lib/bibsync/bibliography.rb
CHANGED
@@ -3,16 +3,11 @@ module BibSync
|
|
3
3
|
include Enumerable
|
4
4
|
|
5
5
|
attr_reader :file
|
6
|
+
attr_accessor :save_hook
|
6
7
|
|
7
8
|
def initialize(file = nil)
|
8
|
-
@entries, @
|
9
|
-
|
10
|
-
@dirty = false
|
11
|
-
@save_hooks = []
|
12
|
-
end
|
13
|
-
|
14
|
-
def save_hook(hook)
|
15
|
-
@save_hooks << hook
|
9
|
+
@entries, @save_hook = {}, nil
|
10
|
+
load(file)
|
16
11
|
end
|
17
12
|
|
18
13
|
def dirty?
|
@@ -23,6 +18,14 @@ module BibSync
|
|
23
18
|
@dirty = true
|
24
19
|
end
|
25
20
|
|
21
|
+
def empty?
|
22
|
+
@entries.empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
def size
|
26
|
+
@entries.size
|
27
|
+
end
|
28
|
+
|
26
29
|
def [](key)
|
27
30
|
@entries[key]
|
28
31
|
end
|
@@ -35,10 +38,17 @@ module BibSync
|
|
35
38
|
end
|
36
39
|
end
|
37
40
|
|
41
|
+
def clear
|
42
|
+
unless @entries.empty?
|
43
|
+
@entries.clear
|
44
|
+
dirty!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
38
48
|
def relative_path(file)
|
39
49
|
raise 'No filename given' unless @file
|
40
|
-
bibpath =
|
41
|
-
Pathname.new(file).realpath.relative_path_from(bibpath).to_s
|
50
|
+
bibpath = File.absolute_path(File.dirname(@file))
|
51
|
+
Pathname.new(file).realpath.relative_path_from(Pathname.new(bibpath)).to_s
|
42
52
|
end
|
43
53
|
|
44
54
|
def each(&block)
|
@@ -49,12 +59,12 @@ module BibSync
|
|
49
59
|
if file
|
50
60
|
@file = file
|
51
61
|
@parent_path = nil
|
52
|
-
|
62
|
+
dirty!
|
53
63
|
end
|
54
64
|
|
55
65
|
raise 'No filename given' unless @file
|
56
66
|
if @dirty
|
57
|
-
@
|
67
|
+
@save_hook.call(self) if @save_hook
|
58
68
|
File.open("#{@file}.tmp", 'w') {|f| f.write(self) }
|
59
69
|
File.rename("#{@file}.tmp", @file)
|
60
70
|
@dirty = false
|
@@ -65,11 +75,25 @@ module BibSync
|
|
65
75
|
end
|
66
76
|
|
67
77
|
def <<(entry)
|
78
|
+
raise 'Entry has no key' if !entry.key || entry.key.empty?
|
79
|
+
raise 'Entry is already existing' if @entries.include?(entry.key)
|
68
80
|
entry.bibliography = self
|
69
81
|
@entries[entry.key] = entry
|
70
82
|
dirty!
|
71
83
|
end
|
72
84
|
|
85
|
+
def load(file)
|
86
|
+
parse(File.read(file)) if file && File.exists?(file)
|
87
|
+
@file = file
|
88
|
+
@dirty = false
|
89
|
+
end
|
90
|
+
|
91
|
+
def load!(file)
|
92
|
+
parse(File.read(file))
|
93
|
+
@file = file
|
94
|
+
@dirty = false
|
95
|
+
end
|
96
|
+
|
73
97
|
def parse(text)
|
74
98
|
until text.empty?
|
75
99
|
case text
|
@@ -94,21 +118,34 @@ module BibSync
|
|
94
118
|
class Entry
|
95
119
|
include Enumerable
|
96
120
|
|
97
|
-
attr_accessor :
|
121
|
+
attr_accessor :bibliography, :type
|
122
|
+
attr_reader :key
|
98
123
|
|
99
124
|
def self.parse(text)
|
100
|
-
|
101
|
-
|
102
|
-
|
125
|
+
Entry.new.tap {|e| e.parse(text) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def initialize(fields = {})
|
129
|
+
self.key = fields.delete(:key) if fields.include?(:key)
|
130
|
+
self.type = fields.delete(:type) if fields.include?(:type)
|
131
|
+
@fields = fields
|
103
132
|
end
|
104
133
|
|
105
|
-
def
|
106
|
-
|
134
|
+
def key=(key)
|
135
|
+
key = key.to_s
|
136
|
+
raise 'Key cannot be empty' if key.empty?
|
137
|
+
if bib = bibliography
|
138
|
+
bib.delete(self)
|
139
|
+
@key = key
|
140
|
+
bib << self
|
141
|
+
else
|
142
|
+
@key = key
|
143
|
+
end
|
107
144
|
end
|
108
145
|
|
109
146
|
def file=(file)
|
110
147
|
raise 'No bibliography set' unless bibliography
|
111
|
-
file =~ /\.(\w+)
|
148
|
+
file =~ /\.(\w+)\Z/
|
112
149
|
self[:file] = ":#{bibliography.relative_path(file)}:#{$1.upcase}" # JabRef file format "description:path:type"
|
113
150
|
file
|
114
151
|
end
|
@@ -116,9 +153,9 @@ module BibSync
|
|
116
153
|
def file
|
117
154
|
if self[:file]
|
118
155
|
raise 'No bibliography set' unless bibliography
|
119
|
-
|
120
|
-
path = (
|
121
|
-
{ :
|
156
|
+
_, file, type = self[:file].split(':', 3)
|
157
|
+
path = File.join(File.absolute_path(File.dirname(bibliography.file)), file)
|
158
|
+
{ name: File.basename(path), type: type.upcase.to_sym, path: path }
|
122
159
|
end
|
123
160
|
end
|
124
161
|
|
@@ -127,7 +164,7 @@ module BibSync
|
|
127
164
|
end
|
128
165
|
|
129
166
|
def []=(key, value)
|
130
|
-
if value
|
167
|
+
if value
|
131
168
|
key = convert_key(key)
|
132
169
|
value = RawValue === value ? RawValue.new(value.to_s.strip) : value.to_s.strip
|
133
170
|
if @fields[key] != value || @fields[key].class != value.class
|
data/lib/bibsync/command.rb
CHANGED
@@ -14,7 +14,7 @@ module BibSync
|
|
14
14
|
process
|
15
15
|
exit 0
|
16
16
|
rescue Exception => ex
|
17
|
-
raise ex if Log.trace
|
17
|
+
raise ex if Log.trace || SystemExit === ex
|
18
18
|
$stderr.print "#{ex.class}: " if ex.class != RuntimeError
|
19
19
|
$stderr.puts ex.message
|
20
20
|
$stderr.puts ' Use --trace for backtrace.'
|
@@ -63,11 +63,11 @@ module BibSync
|
|
63
63
|
end
|
64
64
|
|
65
65
|
opts.on('-V', '--verbose', 'Verbose output') do
|
66
|
-
Log.
|
66
|
+
Log.level = :debug
|
67
67
|
end
|
68
68
|
|
69
69
|
opts.on('--trace', 'Show a full traceback on error') do
|
70
|
-
Log.trace
|
70
|
+
Log.trace = true
|
71
71
|
end
|
72
72
|
|
73
73
|
opts.on('-h', '--help', 'Display this help') do
|
@@ -90,16 +90,21 @@ module BibSync
|
|
90
90
|
|
91
91
|
if @options[:bib]
|
92
92
|
@options[:bib] = Bibliography.new(@options[:bib])
|
93
|
-
@options[:bib].save_hook
|
93
|
+
@options[:bib].save_hook = Transformer.new
|
94
94
|
end
|
95
95
|
|
96
96
|
actions = []
|
97
97
|
actions << :FetchFromArXiv if @options[:fetch]
|
98
|
-
actions << :
|
99
|
-
actions << :SynchronizeFiles << :SynchronizeMetadata if @options[:sync] || @options[:resync]
|
98
|
+
actions << :CheckArXivVersions if @options[:check_versions] || @options[:update]
|
99
|
+
actions << :SynchronizeFiles << :DetermineArXivDOI << :SynchronizeMetadata if @options[:sync] || @options[:resync]
|
100
100
|
actions << :FindMyCitations if @options[:citedbyme]
|
101
|
-
actions << :Validate
|
102
|
-
actions << :
|
101
|
+
actions << :Validate if @options[:bib]
|
102
|
+
actions << :JabRefFormat if @options[:jabref]
|
103
|
+
|
104
|
+
if actions.empty?
|
105
|
+
puts "Please specify actions! See #{$0} --help"
|
106
|
+
exit
|
107
|
+
end
|
103
108
|
|
104
109
|
actions.map {|a| Actions.const_get(a).new(@options) }.each {|a| a.run }
|
105
110
|
end
|
data/lib/bibsync/log.rb
CHANGED
@@ -5,53 +5,55 @@ module BibSync
|
|
5
5
|
Yellow = "\e[33m"
|
6
6
|
Blue = "\e[36m"
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
Level = {
|
9
|
+
debug: nil,
|
10
|
+
info: nil,
|
11
|
+
notice: Blue,
|
12
|
+
warning: Yellow,
|
13
|
+
error: Red,
|
14
|
+
}
|
11
15
|
|
12
|
-
|
13
|
-
|
16
|
+
class << self
|
17
|
+
attr_accessor :level, :trace
|
14
18
|
end
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.trace!
|
21
|
-
@trace = true
|
22
|
-
end
|
20
|
+
self.trace = false
|
21
|
+
self.level = :info
|
23
22
|
|
24
23
|
def debug(message, opts = {})
|
25
|
-
|
24
|
+
log(:debug, message, opts)
|
26
25
|
end
|
27
26
|
|
28
27
|
def info(message, opts = {})
|
29
|
-
log(message, opts)
|
28
|
+
log(:info, message, opts)
|
30
29
|
end
|
31
30
|
|
32
31
|
def notice(message, opts = {})
|
33
|
-
log(message, opts
|
32
|
+
log(:notice, message, opts)
|
34
33
|
end
|
35
34
|
|
36
35
|
def warning(message, opts = {})
|
37
|
-
log(message, opts
|
36
|
+
log(:warning, message, opts)
|
38
37
|
end
|
39
38
|
|
40
39
|
def error(message, opts = {})
|
41
|
-
log(message, opts
|
40
|
+
log(:error, message, opts)
|
42
41
|
end
|
43
42
|
|
44
|
-
def log(message, opts = {})
|
43
|
+
def log(level, message, opts = {})
|
44
|
+
return if Level.keys.index(level) < Level.keys.index(Log.level)
|
45
45
|
if ex = opts[:ex]
|
46
46
|
message = "#{message} - #{ex.message}"
|
47
47
|
end
|
48
|
-
|
48
|
+
if color = Level[level]
|
49
|
+
message = "#{color}#{message}#{Reset}"
|
50
|
+
end
|
49
51
|
if key = opts[:key]
|
50
52
|
key = key.key if key.respond_to? :key
|
51
53
|
message = "#{key} : #{message}"
|
52
54
|
end
|
53
55
|
puts(message)
|
54
|
-
if Log.trace
|
56
|
+
if Log.trace && ex = opts[:ex]
|
55
57
|
puts(ex.backtrace.join("\n"))
|
56
58
|
end
|
57
59
|
end
|
data/lib/bibsync/transformer.rb
CHANGED
@@ -55,7 +55,7 @@ module BibSync
|
|
55
55
|
entry[:shortjournal] = 'RMP'
|
56
56
|
when /\ANew Journal of Physics\Z/i
|
57
57
|
entry[:shortjournal] = 'NJP'
|
58
|
-
when /\
|
58
|
+
when /\AArXiv e-prints\Z/i
|
59
59
|
entry[:shortjournal] = 'arXiv'
|
60
60
|
when /\AEurophysics Letters\Z/i
|
61
61
|
entry[:shortjournal] = 'EPL'
|
data/lib/bibsync/utils.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
module BibSync
|
2
2
|
module Utils
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
file =~ /^(.*?)\.(\w+)$/
|
7
|
-
return $1, $2.upcase
|
3
|
+
def name_without_ext(file)
|
4
|
+
file =~ /\A(.*?)\.\w+\Z/
|
5
|
+
$1
|
8
6
|
end
|
9
7
|
|
10
8
|
def fetch(url, headers = {})
|
@@ -17,7 +15,7 @@ module BibSync
|
|
17
15
|
|
18
16
|
def arxiv_download(dir, id)
|
19
17
|
url = "http://arxiv.org/pdf/#{id}"
|
20
|
-
file = File.join(dir, "#{arxiv_id(id, :
|
18
|
+
file = File.join(dir, "#{arxiv_id(id, version: true, prefix: false)}.pdf")
|
21
19
|
result = `curl --stderr - -S -s -L -o #{Shellwords.escape file} #{Shellwords.escape url}`
|
22
20
|
raise result.chomp if $? != 0
|
23
21
|
end
|
@@ -36,10 +34,10 @@ module BibSync
|
|
36
34
|
raise unless opts.include?(:prefix) && opts.include?(:version)
|
37
35
|
arxiv = arxiv[:arxiv] if Bibliography::Entry === arxiv
|
38
36
|
if arxiv
|
39
|
-
arxiv = arxiv.sub(
|
40
|
-
arxiv = arxiv.sub(/v\d
|
41
|
-
arxiv
|
37
|
+
arxiv = arxiv.sub(/\A.*\//, '') unless opts[:prefix]
|
38
|
+
arxiv = arxiv.sub(/v\d+\Z/, '') unless opts[:version]
|
42
39
|
end
|
40
|
+
arxiv
|
43
41
|
end
|
44
42
|
end
|
45
43
|
end
|
data/lib/bibsync/version.rb
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe BibSync::Actions::DetermineArXivDOI do
|
4
|
+
before do
|
5
|
+
@tmpfile = File.join(fixturedir, 'tmp.bib')
|
6
|
+
end
|
7
|
+
|
8
|
+
after do
|
9
|
+
File.unlink(@tmpfile) if File.exists?(@tmpfile)
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:bib) do
|
13
|
+
BibSync::Bibliography.new(@tmpfile)
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:fixturebib) do
|
17
|
+
BibSync::Bibliography.new(File.join(fixturedir, 'test.bib'))
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:action) do
|
21
|
+
BibSync::Actions::DetermineArXivDOI.new(bib: bib)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should find arXiv identifier in pdf file' do
|
25
|
+
entry = fixturebib['FileWithEmbeddedArXiv']
|
26
|
+
bib << entry
|
27
|
+
action.run
|
28
|
+
entry[:arxiv].must_equal '0911.2512v3'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should find DOI identifier in file and add missing arXiv identifier' do
|
32
|
+
entry = fixturebib['FileWithEmbeddedDOI']
|
33
|
+
bib << entry
|
34
|
+
action.run
|
35
|
+
entry[:arxiv].must_equal '0911.2512v3'
|
36
|
+
entry[:doi].must_equal '10.1103/PhysRevLett.104.106404'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should interpret file name as arXiv identifier' do
|
40
|
+
entry = fixturebib['0911.2512v3']
|
41
|
+
bib << entry
|
42
|
+
action.run
|
43
|
+
entry[:arxiv].must_equal '0911.2512v3'
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should interpret file name as DOI identifier and add missing arXiv identifier' do
|
47
|
+
entry = fixturebib['PhysRevLett.104.106404']
|
48
|
+
bib << entry
|
49
|
+
action.run
|
50
|
+
entry[:arxiv].must_equal '0911.2512v3'
|
51
|
+
entry[:doi].must_equal '10.1103/PhysRevLett.104.106404'
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should add missing arXiv identifier' do
|
55
|
+
entry = fixturebib['HasDOI']
|
56
|
+
bib << entry
|
57
|
+
entry[:doi].must_equal '10.1103/PhysRevLett.104.106404'
|
58
|
+
action.run
|
59
|
+
entry[:arxiv].must_equal '0911.2512v3'
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe BibSync::Actions::SynchronizeMetadata do
|
4
|
+
before do
|
5
|
+
@tmpfile = File.join(fixturedir, 'tmp.bib')
|
6
|
+
end
|
7
|
+
|
8
|
+
after do
|
9
|
+
File.unlink(@tmpfile) if File.exists?(@tmpfile)
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:bib) do
|
13
|
+
BibSync::Bibliography.new(@tmpfile)
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:fixturebib) do
|
17
|
+
BibSync::Bibliography.new(File.join(fixturedir, 'test.bib'))
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:action) do
|
21
|
+
BibSync::Actions::SynchronizeMetadata.new(bib: bib)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should download metadata' do
|
25
|
+
entry = fixturebib['HasArXiv']
|
26
|
+
bib << entry
|
27
|
+
action.run
|
28
|
+
entry[:title].must_equal 'Chirality Induced Tilted-Hill Giant Nernst Signal'
|
29
|
+
entry[:journal].must_equal 'Physical Review Letters'
|
30
|
+
entry[:doi].must_equal '10.1103/PhysRevLett.104.106404'
|
31
|
+
entry[:arxiv].must_equal '0911.2512v3'
|
32
|
+
entry[:url].must_match(/dx\.doi\.org/)
|
33
|
+
end
|
34
|
+
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,34 @@
|
|
1
|
+
@BOOK{TestBook,
|
2
|
+
title = {BookTitle},
|
3
|
+
publisher = {BookPublisher},
|
4
|
+
year = {2000},
|
5
|
+
month = jan,
|
6
|
+
author = {BookAuthor},
|
7
|
+
volume = {BookVolume}
|
8
|
+
}
|
9
|
+
|
10
|
+
@ARTICLE{FileWithEmbeddedArXiv,
|
11
|
+
file = {:FileWithEmbeddedArXiv.pdf:PDF}
|
12
|
+
}
|
13
|
+
|
14
|
+
@ARTICLE{FileWithEmbeddedDOI,
|
15
|
+
file = {:FileWithEmbeddedDOI.pdf:PDF}
|
16
|
+
}
|
17
|
+
|
18
|
+
@ARTICLE{HasArXiv,
|
19
|
+
arxiv = {0911.2512v3}
|
20
|
+
}
|
21
|
+
|
22
|
+
@ARTICLE{HasDOI,
|
23
|
+
doi = {10.1103/PhysRevLett.104.106404}
|
24
|
+
}
|
25
|
+
|
26
|
+
@ARTICLE{0911.2512v3,
|
27
|
+
file = {:0911.2512v3.txt:TXT}
|
28
|
+
}
|
29
|
+
|
30
|
+
@ARTICLE{PhysRevLett.104.106404,
|
31
|
+
file = {:PhysRevLett.104.106404.txt:TXT}
|
32
|
+
}
|
33
|
+
|
34
|
+
@COMMENT{TestComment}
|
data/test/helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'bibsync'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
BibSync::Log.level = :error
|
7
|
+
BibSync::Log.trace = true
|
8
|
+
|
9
|
+
module Helper
|
10
|
+
def fixturedir
|
11
|
+
File.join(File.dirname(__FILE__), 'fixture')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class MiniTest::Spec
|
16
|
+
include Helper
|
17
|
+
|
18
|
+
after do
|
19
|
+
FileUtils.rm_rf(File.join(File.dirname(__FILE__), 'tmp'))
|
20
|
+
end
|
21
|
+
end
|