scholar-rename 0.4.1 → 0.4.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 +4 -4
- data/bin/scholar-rename +4 -3
- data/lib/renamer.rb +39 -31
- data/lib/scholar_lookup.rb +127 -0
- data/lib/selector.rb +81 -37
- data/lib/version.rb +3 -1
- data/readme.md +21 -5
- metadata +25 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8ff9c16c734ec953edfc836e232ebaf70fb62d2902b0240a91f0631cfe077bdc
|
|
4
|
+
data.tar.gz: 24455839f23d27ccada557d23e5e1a27b63ff2a35bb44407da07b9577e66e76f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '085102e70f448e2e72862acb6ba3204e6b652907587aca0e972346f606399cb29ab3ad7d8b9c03a6c6591d2975b48631e4bcde32077734808a60e5d6939bf732'
|
|
7
|
+
data.tar.gz: 10844926e2c3ff6e525811b8582d3b3c9d02c37a57c47225a561a061e71b37241c897f715338eef276160bd285d97702e9038c782c0d8c338ccec99526cb7e92
|
data/bin/scholar-rename
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
# by jeremy warner, started fall 2016
|
|
4
5
|
# scholar-rename: interactive pdf-namer
|
|
@@ -7,7 +8,7 @@
|
|
|
7
8
|
# renames a pdf file to author-title-year.pdf
|
|
8
9
|
# or other formats based on your selection
|
|
9
10
|
|
|
10
|
-
require_relative
|
|
11
|
-
require_relative
|
|
11
|
+
require_relative '../lib/selector'
|
|
12
|
+
require_relative '../lib/renamer'
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
Renamer.new ARGV
|
data/lib/renamer.rb
CHANGED
|
@@ -1,82 +1,90 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
require_relative 'version'
|
|
2
5
|
|
|
3
6
|
class Renamer
|
|
4
7
|
attr_reader :version, :formats, :selector, :format
|
|
5
8
|
|
|
9
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
10
|
+
# Legacy CLI arg-parser predating this cleanup pass; splitting it apart is a
|
|
11
|
+
# real refactor, not a lint fix, so it's exempted rather than restructured here.
|
|
6
12
|
def initialize(*args)
|
|
7
13
|
args = args.flatten
|
|
8
14
|
a0 = args.first
|
|
9
15
|
al = args.last
|
|
10
16
|
|
|
11
17
|
@real_file = false
|
|
12
|
-
@options = { :
|
|
13
|
-
@version = SR::
|
|
18
|
+
@options = { format: 0, auto: false, debug: false, test: false, no_lookup: false }
|
|
19
|
+
@version = SR::VERSION
|
|
14
20
|
@selector = Selector.new
|
|
15
|
-
@formats = @selector.gen_forms(
|
|
21
|
+
@formats = @selector.gen_forms('Year', 'Title', 'Author')
|
|
16
22
|
|
|
17
|
-
if args.include?
|
|
18
|
-
@options[:debug] = true
|
|
19
|
-
end
|
|
23
|
+
@options[:debug] = true if args.include? '--debug'
|
|
20
24
|
|
|
21
|
-
if args.include?
|
|
25
|
+
if args.include? '--test'
|
|
22
26
|
@options[:test] = true
|
|
23
|
-
def puts(*x) x
|
|
27
|
+
def puts(*x) = x
|
|
24
28
|
end
|
|
25
29
|
|
|
30
|
+
@options[:no_lookup] = true if args.include? '--no-lookup'
|
|
31
|
+
|
|
32
|
+
@options[:auto] = true if args.include? '--auto'
|
|
33
|
+
|
|
34
|
+
@options[:format] = args[args.index('--format') + 1].to_i if args.include? '--format'
|
|
35
|
+
|
|
26
36
|
# Filename comes last.
|
|
27
|
-
if !al.nil? && al[0] !=
|
|
37
|
+
if !al.nil? && al[0] != '-'
|
|
28
38
|
@file = File.join(Dir.pwd, args.last).to_s
|
|
29
39
|
@real_file = @file && File.file?(@file)
|
|
30
40
|
end
|
|
31
41
|
if @file && !@real_file
|
|
32
42
|
puts "Input #{@file} not found."
|
|
33
|
-
puts
|
|
43
|
+
puts 'Please specify a pdf file to use with scholar-rename.'
|
|
34
44
|
exit 1
|
|
35
45
|
end
|
|
36
46
|
|
|
37
47
|
# Main argument processing.
|
|
38
|
-
if args.
|
|
39
|
-
puts
|
|
48
|
+
if args.empty? || a0 == '--h' || a0 == '--help'
|
|
49
|
+
puts 'usage: scholar-rename (--format #) (--auto) (--no-lookup) [file.pdf]'
|
|
40
50
|
puts "\t--show-formats\tshow format options"
|
|
41
51
|
puts "\t--auto\tpick default formatter"
|
|
52
|
+
puts "\t--no-lookup\tdisable Semantic Scholar metadata lookup"
|
|
42
53
|
puts "\t-v, --version\tshow version number"
|
|
43
|
-
elsif
|
|
54
|
+
elsif ['-v', '--version'].include?(a0)
|
|
44
55
|
puts @version
|
|
45
56
|
exit 0 unless @options[:test]
|
|
46
|
-
elsif !
|
|
47
|
-
puts
|
|
48
|
-
puts
|
|
57
|
+
elsif !prereq?
|
|
58
|
+
puts 'please install pdftotext (via poppler) to use scholar-rename'
|
|
59
|
+
puts 'OSX: brew install pkg-config poppler'
|
|
49
60
|
exit 1
|
|
50
|
-
elsif a0 ==
|
|
61
|
+
elsif a0 == '--show-formats'
|
|
51
62
|
@formats.each_with_index { |x, i| puts "\t#{i}: #{x}" }
|
|
52
|
-
elsif a0 == "--format"
|
|
53
|
-
@options[:format] = args[1].to_i
|
|
54
|
-
elsif a0 == "--auto"
|
|
55
|
-
@options[:auto] = true
|
|
56
63
|
end
|
|
57
64
|
|
|
58
65
|
rename args if @real_file
|
|
59
66
|
end
|
|
67
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
60
68
|
|
|
61
|
-
def
|
|
62
|
-
system(
|
|
63
|
-
|
|
69
|
+
def prereq?
|
|
70
|
+
system('pdftotext -v 2> /dev/null')
|
|
71
|
+
$CHILD_STATUS.success?
|
|
64
72
|
end
|
|
65
73
|
|
|
66
74
|
def rename(args)
|
|
67
75
|
raw = `pdftotext -q '#{@file}' -` # may contain non-ascii characters
|
|
68
|
-
content = raw.encode(
|
|
69
|
-
|
|
76
|
+
content = raw.encode('UTF-8', invalid: :replace, undef: :replace)
|
|
77
|
+
|
|
70
78
|
# Choose pdf qualities
|
|
71
|
-
@selector.
|
|
79
|
+
@selector.content = content
|
|
72
80
|
@selector.options = @options
|
|
73
81
|
@selector.select_all
|
|
74
82
|
md = @selector.metadata
|
|
75
83
|
|
|
76
84
|
if @options[:debug]
|
|
77
|
-
puts md[:year] if args.include?
|
|
78
|
-
puts md[:title] if args.include?
|
|
79
|
-
puts md[:author] if args.include?
|
|
85
|
+
puts md[:year] if args.include? '--show-year'
|
|
86
|
+
puts md[:title] if args.include? '--show-title'
|
|
87
|
+
puts md[:author] if args.include? '--show-author'
|
|
80
88
|
else
|
|
81
89
|
File.rename(@file, @selector.title)
|
|
82
90
|
puts "Saved #{@selector.title}"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'openssl'
|
|
7
|
+
|
|
8
|
+
# Queries the Semantic Scholar Graph API to cross-reference a rough title
|
|
9
|
+
# guess against real paper metadata. Always returns a Hash or nil; never
|
|
10
|
+
# raises, so a network hiccup just means the caller falls back to its own
|
|
11
|
+
# heuristics.
|
|
12
|
+
class ScholarLookup
|
|
13
|
+
ENDPOINT = 'https://api.semanticscholar.org/graph/v1/paper/search'
|
|
14
|
+
FIELDS = 'title,authors,year,venue'
|
|
15
|
+
SOURCE_NAME = 'Semantic Scholar'
|
|
16
|
+
|
|
17
|
+
# Exceptions expected from a flaky network/API, silently treated as "no
|
|
18
|
+
# match". Anything else (e.g. a response-shape change breaking #parse) is
|
|
19
|
+
# still caught so a rename can never crash, but is reported via warn so the
|
|
20
|
+
# regression isn't invisible.
|
|
21
|
+
EXPECTED_ERRORS = [
|
|
22
|
+
SocketError,
|
|
23
|
+
Timeout::Error,
|
|
24
|
+
SystemCallError,
|
|
25
|
+
OpenSSL::SSL::SSLError,
|
|
26
|
+
Net::ProtocolError,
|
|
27
|
+
JSON::ParserError
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
# A match only counts if at least half of the query's words also appear in
|
|
31
|
+
# the returned title -- guards against a short/generic query (e.g. a running
|
|
32
|
+
# header) fuzzy-matching an unrelated paper.
|
|
33
|
+
MIN_WORD_OVERLAP = 0.5
|
|
34
|
+
|
|
35
|
+
def initialize(open_timeout: 2, read_timeout: 3)
|
|
36
|
+
@open_timeout = open_timeout
|
|
37
|
+
@read_timeout = read_timeout
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def source_name
|
|
41
|
+
SOURCE_NAME
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def lookup(query)
|
|
45
|
+
return nil if query.nil? || query.strip.length < 4
|
|
46
|
+
|
|
47
|
+
body = fetch(build_uri(query.strip))
|
|
48
|
+
return nil if body.nil?
|
|
49
|
+
|
|
50
|
+
match = parse(body)
|
|
51
|
+
return nil unless match && plausible_match?(query, match[:title])
|
|
52
|
+
|
|
53
|
+
match
|
|
54
|
+
rescue *EXPECTED_ERRORS
|
|
55
|
+
nil
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
warn "scholar-rename: unexpected lookup error (#{e.class}): #{e.message}"
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def plausible_match?(query, title)
|
|
64
|
+
query_words = normalize_words(query)
|
|
65
|
+
title_words = normalize_words(title)
|
|
66
|
+
return false if query_words.empty? || title_words.empty?
|
|
67
|
+
|
|
68
|
+
overlap = (query_words & title_words).length
|
|
69
|
+
overlap.to_f / query_words.length >= MIN_WORD_OVERLAP
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def normalize_words(str)
|
|
73
|
+
str.downcase.gsub(/[^a-z0-9\s]/, '').split
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def build_uri(query)
|
|
77
|
+
uri = URI(ENDPOINT)
|
|
78
|
+
uri.query = URI.encode_www_form('query' => query, 'fields' => FIELDS, 'limit' => '1')
|
|
79
|
+
uri
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def fetch(uri)
|
|
83
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
84
|
+
http.use_ssl = true
|
|
85
|
+
http.open_timeout = @open_timeout
|
|
86
|
+
http.read_timeout = @read_timeout
|
|
87
|
+
|
|
88
|
+
req = Net::HTTP::Get.new(uri)
|
|
89
|
+
req['User-Agent'] = 'scholar-rename'
|
|
90
|
+
|
|
91
|
+
res = http.request(req)
|
|
92
|
+
return nil unless res.is_a?(Net::HTTPSuccess)
|
|
93
|
+
|
|
94
|
+
res.body
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def parse(body)
|
|
98
|
+
papers = JSON.parse(body)['data']
|
|
99
|
+
return nil if papers.nil? || papers.empty?
|
|
100
|
+
|
|
101
|
+
paper = papers.first
|
|
102
|
+
title = paper['title']
|
|
103
|
+
author = format_authors(paper['authors'])
|
|
104
|
+
return nil if title.nil? || title.strip.empty? || author.nil?
|
|
105
|
+
|
|
106
|
+
{
|
|
107
|
+
title: title,
|
|
108
|
+
author: author,
|
|
109
|
+
year: paper['year']&.to_s,
|
|
110
|
+
venue: paper['venue']
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def format_authors(authors)
|
|
115
|
+
return nil if authors.nil? || authors.empty?
|
|
116
|
+
|
|
117
|
+
last_names = authors.map { |a| a['name'].to_s.split.last }.compact
|
|
118
|
+
return nil if last_names.empty?
|
|
119
|
+
|
|
120
|
+
case last_names.length
|
|
121
|
+
when 1, 2
|
|
122
|
+
last_names.join(' ')
|
|
123
|
+
else
|
|
124
|
+
"#{last_names.first} et al"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
data/lib/selector.rb
CHANGED
|
@@ -1,69 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'scholar_lookup'
|
|
4
|
+
|
|
1
5
|
# Class for choosing and displaying the information from the pdf.
|
|
2
|
-
# I wonder if this could be augmented with information from google scholar
|
|
3
|
-
# somehow, there is probably a ruby (or at least python) api.
|
|
4
6
|
|
|
5
7
|
class Selector
|
|
6
8
|
attr_reader :title, :metadata, :content
|
|
7
9
|
attr_accessor :options
|
|
8
10
|
|
|
9
|
-
def initialize(c = "Test\nPDF\nContent", opts = { :
|
|
10
|
-
|
|
11
|
+
def initialize(c = "Test\nPDF\nContent", opts = { format: 0, auto: true, no_lookup: false }, lookup = nil)
|
|
12
|
+
self.content = c
|
|
11
13
|
@options = opts
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
@lookup = lookup || ScholarLookup.new
|
|
15
|
+
return unless opts[:test]
|
|
16
|
+
|
|
17
|
+
def puts(*x) = x
|
|
15
18
|
end
|
|
16
19
|
|
|
17
|
-
def
|
|
20
|
+
def content=(str)
|
|
18
21
|
@full_text = str.split("\n")
|
|
19
22
|
@content = @full_text[0..14]
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
.reject { |x| x.length < 2 }
|
|
24
|
+
.map { |x| x[0..100] } # trim
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
def select_all
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
title, author, year = lookup_metadata unless @options[:no_lookup]
|
|
29
|
+
title, author, year = manual_metadata if title.nil?
|
|
30
|
+
|
|
31
|
+
forms = gen_forms(year, title, author)
|
|
32
|
+
@metadata = { year: year, title: title, author: author }
|
|
33
|
+
@title = forms[@options[:format]]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Query the lookup source with our best title guess, and let the user
|
|
37
|
+
# accept or reject the match (--auto always accepts, matching how #choose
|
|
38
|
+
# behaves elsewhere). Returns a [title, author, year] triple, or nil if
|
|
39
|
+
# there was no match or it was rejected.
|
|
40
|
+
def lookup_metadata
|
|
41
|
+
match = @lookup.lookup(@content.first)
|
|
42
|
+
return nil unless match
|
|
43
|
+
|
|
44
|
+
summary = "#{match[:title]} -- #{match[:author]}"
|
|
45
|
+
summary += " (#{match[:year]})" if match[:year]
|
|
46
|
+
summary += " [#{match[:venue]}]" if match[:venue]
|
|
47
|
+
puts "Found match via #{@lookup.source_name}: #{summary}"
|
|
48
|
+
|
|
49
|
+
if @options[:auto]
|
|
50
|
+
puts '(auto-accepted)'
|
|
51
|
+
else
|
|
52
|
+
printf 'Use this? [Y/n]: '
|
|
53
|
+
resp = $stdin.gets.to_s.strip.downcase
|
|
54
|
+
return nil unless resp.empty? || resp == 'y' || resp == 'yes'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
[match[:title], match[:author], match[:year] || gen_year]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Ask the user to pick the title/author lines by hand. Returns a
|
|
61
|
+
# [title, author, year] triple.
|
|
62
|
+
def manual_metadata
|
|
63
|
+
unless @options[:auto]
|
|
64
|
+
puts 'Options:'
|
|
27
65
|
@content.each_with_index { |l, i| puts "#{i}\t#{l}" }
|
|
28
66
|
end
|
|
29
|
-
printf
|
|
67
|
+
printf 'Select title line number:' unless @options[:auto]
|
|
30
68
|
title = choose(@content, print: false)
|
|
31
69
|
|
|
32
|
-
printf
|
|
70
|
+
printf 'Select author line number:' unless @options[:auto]
|
|
33
71
|
authors = choose(@content, print: false)
|
|
34
72
|
|
|
35
|
-
puts
|
|
73
|
+
puts 'Select author form:' unless @options[:auto]
|
|
36
74
|
author = gen_authors(authors)
|
|
37
75
|
|
|
38
|
-
|
|
39
|
-
year = gen_year
|
|
40
|
-
|
|
41
|
-
forms = gen_forms(year, title, author)
|
|
42
|
-
@metadata = {:year => year, :title => title, :author => author}
|
|
43
|
-
@title = forms[@options[:format]]
|
|
76
|
+
[title, author, gen_year]
|
|
44
77
|
end
|
|
45
78
|
|
|
46
79
|
# based on the collected information, generate different forms of the title.
|
|
47
80
|
def gen_forms(y, t, a)
|
|
81
|
+
y = sanitize_path_component(y)
|
|
82
|
+
t = sanitize_path_component(t)
|
|
83
|
+
a = sanitize_path_component(a)
|
|
48
84
|
ad = a.downcase
|
|
49
85
|
au = a.upcase
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
86
|
+
[
|
|
87
|
+
"#{y} - #{a} - #{t}.pdf",
|
|
88
|
+
"#{y} #{a} #{t}.pdf",
|
|
89
|
+
"#{a}_#{y}_#{t}.pdf".gsub(' ', '_'),
|
|
90
|
+
"#{au}_#{y}_#{t}.pdf".gsub(' ', '_'),
|
|
91
|
+
"#{ad}_#{y}_#{t}.pdf".gsub(' ', '_'),
|
|
92
|
+
"#{a} #{y} #{t}.pdf",
|
|
93
|
+
"#{au} #{y} #{t}.pdf",
|
|
94
|
+
"#{ad} #{y} #{t}.pdf"
|
|
95
|
+
]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Filenames are built by combining year/title/author into a single path
|
|
99
|
+
# segment -- strip path separators so a "/" in a title (manually picked or
|
|
100
|
+
# fetched from an external source) can't turn File.rename into a move into
|
|
101
|
+
# a different (likely non-existent) directory.
|
|
102
|
+
def sanitize_path_component(s)
|
|
103
|
+
s.to_s.gsub(%r{[/\\]}, '-')
|
|
60
104
|
end
|
|
61
105
|
|
|
62
106
|
# Pass in an array to list and be selected, and return the element that the
|
|
63
107
|
# user selects back to the calling method. this is a way to interpret
|
|
64
108
|
# the users input as a range (e.g., 0..2) or an integer (1).
|
|
65
109
|
def choose(list, print: true)
|
|
66
|
-
raise
|
|
110
|
+
raise 'List is empty.' if list.empty?
|
|
67
111
|
|
|
68
112
|
if @options[:auto]
|
|
69
113
|
line = 0
|
|
@@ -73,19 +117,19 @@ class Selector
|
|
|
73
117
|
list.each_with_index { |l, i| puts "#{i}\t#{l}" }
|
|
74
118
|
printf "[0 - #{options.length - 1}]: "
|
|
75
119
|
end
|
|
76
|
-
line =
|
|
120
|
+
line = $stdin.gets&.chomp || 0
|
|
77
121
|
end
|
|
78
122
|
|
|
79
123
|
meta = "list[#{line}]"
|
|
80
|
-
mout = eval meta
|
|
81
|
-
mout = mout.join
|
|
124
|
+
mout = eval meta # rubocop:disable Security/Eval -- `line` is local interactive/--auto input only, never external
|
|
125
|
+
mout = mout.join ' ' if mout.is_a?(Array)
|
|
82
126
|
mout
|
|
83
127
|
end
|
|
84
128
|
|
|
85
129
|
# Generate different forms for author selection, enumerating the different
|
|
86
130
|
# author that you want to save the file as. Split based on a comma.
|
|
87
131
|
def gen_authors(aline)
|
|
88
|
-
lines = aline.split(
|
|
132
|
+
lines = aline.split(', ').map { |a| a.sub(/\d$/, '') } # delete ref number.
|
|
89
133
|
if lines.is_a?(String)
|
|
90
134
|
aline
|
|
91
135
|
else # its an array, augment w/ lname and choose
|
|
@@ -99,7 +143,7 @@ class Selector
|
|
|
99
143
|
def gen_year
|
|
100
144
|
@full_text.each do |l| # find year
|
|
101
145
|
lm = l.match(/(19|20)\d\d/)
|
|
102
|
-
return lm[0]
|
|
146
|
+
return lm[0] unless lm.nil?
|
|
103
147
|
end
|
|
104
148
|
|
|
105
149
|
Time.now.year.to_s # not matched, just return current year
|
data/lib/version.rb
CHANGED
data/readme.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# scholar-rename
|
|
2
2
|
|
|
3
|
-
[](http://jeremywrnr.com/mit-license)
|
|
4
3
|
[](https://badge.fury.io/rb/scholar-rename)
|
|
4
|
+
[](https://github.com/jeremywrnr/scholar-rename/actions/workflows/ci.yml)
|
|
5
|
+
[](http://jeremywrnr.com/mit-license)
|
|
5
6
|
|
|
6
7
|
an interactive pdf-renamer tool.
|
|
7
8
|
|
|
@@ -15,11 +16,26 @@ for macOS, you can install w/ brew:
|
|
|
15
16
|
|
|
16
17
|
brew install pkg-config poppler
|
|
17
18
|
|
|
19
|
+
for Ubuntu:
|
|
20
|
+
|
|
21
|
+
sudo apt-get install poppler-utils
|
|
22
|
+
|
|
18
23
|
## about
|
|
19
24
|
|
|
20
25
|
renames a pdf file to author-title-year.pdf or other formats based on your
|
|
21
|
-
selection.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
selection. academic people may find it useful for renaming pdfs that come in
|
|
27
|
+
arbitrarily named file formats. it helps when searching for a specific pdf
|
|
28
|
+
and when labeling pdfs.
|
|
29
|
+
|
|
30
|
+
## metadata lookup
|
|
31
|
+
|
|
32
|
+
by default, scholar-rename queries the [Semantic Scholar Graph
|
|
33
|
+
API](https://api.semanticscholar.org/) using the extracted pdf text to
|
|
34
|
+
suggest an authoritative title/author/year. you'll be asked to confirm the
|
|
35
|
+
match (auto-accepted when using `--auto`). if there's no internet
|
|
36
|
+
connection, the api times out, or no match is found, scholar-rename falls
|
|
37
|
+
back to its original manual line-picking flow -- nothing ever crashes or
|
|
38
|
+
hangs waiting on the network. use `--no-lookup` to skip the network step
|
|
39
|
+
entirely (useful when offline, batch-renaming many files, or avoiding the
|
|
40
|
+
api's rate limits).
|
|
25
41
|
|
metadata
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: scholar-rename
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeremy Warner
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
13
|
+
name: rspec
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
@@ -25,7 +24,21 @@ dependencies:
|
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
25
|
version: '0'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
27
|
+
name: rubocop
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: webmock
|
|
29
42
|
requirement: !ruby/object:Gem::Requirement
|
|
30
43
|
requirements:
|
|
31
44
|
- - ">="
|
|
@@ -47,14 +60,16 @@ extra_rdoc_files: []
|
|
|
47
60
|
files:
|
|
48
61
|
- bin/scholar-rename
|
|
49
62
|
- lib/renamer.rb
|
|
63
|
+
- lib/scholar_lookup.rb
|
|
50
64
|
- lib/selector.rb
|
|
51
65
|
- lib/version.rb
|
|
52
66
|
- readme.md
|
|
53
|
-
homepage:
|
|
67
|
+
homepage: https://github.com/jeremywrnr/scholar-rename
|
|
54
68
|
licenses:
|
|
55
69
|
- MIT
|
|
56
|
-
metadata:
|
|
57
|
-
|
|
70
|
+
metadata:
|
|
71
|
+
source_code_uri: https://github.com/jeremywrnr/scholar-rename
|
|
72
|
+
bug_tracker_uri: https://github.com/jeremywrnr/scholar-rename/issues
|
|
58
73
|
rdoc_options: []
|
|
59
74
|
require_paths:
|
|
60
75
|
- lib
|
|
@@ -62,15 +77,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
62
77
|
requirements:
|
|
63
78
|
- - ">="
|
|
64
79
|
- !ruby/object:Gem::Version
|
|
65
|
-
version: '0'
|
|
80
|
+
version: '3.0'
|
|
66
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
82
|
requirements:
|
|
68
83
|
- - ">="
|
|
69
84
|
- !ruby/object:Gem::Version
|
|
70
85
|
version: '0'
|
|
71
86
|
requirements: []
|
|
72
|
-
rubygems_version:
|
|
73
|
-
signing_key:
|
|
87
|
+
rubygems_version: 4.0.10
|
|
74
88
|
specification_version: 4
|
|
75
89
|
summary: Rename pdfs based on author/title/year.
|
|
76
90
|
test_files: []
|