bibsync 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|