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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cdb5cff14beb38b02d6e11aa44a05c8770dcea22aa520f6b61e77c25c9e8088b
4
- data.tar.gz: 36d7dc8907251cb05df51f66323ec5ba2c55046fbb03247c33ca1a962ef5ceec
3
+ metadata.gz: 8ff9c16c734ec953edfc836e232ebaf70fb62d2902b0240a91f0631cfe077bdc
4
+ data.tar.gz: 24455839f23d27ccada557d23e5e1a27b63ff2a35bb44407da07b9577e66e76f
5
5
  SHA512:
6
- metadata.gz: fa2b9fd4f226bfb6ca45c2847c9e16ec6bebe97a99e00b3cb4f220414bda6d55cf3b7f7a898c58fc843e5e08ed7ba66b86d5fee2733fe2eef6d89da01898c85d
7
- data.tar.gz: e24de26e632677c59ebddb7464eba87434a4ecabbe8579278399b2a4edb9cf7d9c63f38f82e60719b0df027fe90bde61eb44cc2375ecf8447b29284ce1af3e32
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 "../lib/selector"
11
- require_relative "../lib/renamer"
11
+ require_relative '../lib/selector'
12
+ require_relative '../lib/renamer'
12
13
 
13
- r = Renamer.new ARGV
14
+ Renamer.new ARGV
data/lib/renamer.rb CHANGED
@@ -1,82 +1,90 @@
1
- require_relative "./version.rb"
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 = { :format => 0, :auto => false, :debug => false, :test => false }
13
- @version = SR::Version
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("Year", "Title", "Author")
21
+ @formats = @selector.gen_forms('Year', 'Title', 'Author')
16
22
 
17
- if args.include? "--debug"
18
- @options[:debug] = true
19
- end
23
+ @options[:debug] = true if args.include? '--debug'
20
24
 
21
- if args.include? "--test"
25
+ if args.include? '--test'
22
26
  @options[:test] = true
23
- def puts(*x) x; end
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 "Please specify a pdf file to use with scholar-rename."
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.length == 0 || a0 == "--h" || a0 == "--help"
39
- puts "usage: scholar-rename (--format #) (--auto) [file.pdf]"
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 a0 == "-v" || a0 == "--version"
54
+ elsif ['-v', '--version'].include?(a0)
44
55
  puts @version
45
56
  exit 0 unless @options[:test]
46
- elsif !has_prereq?
47
- puts "please install pdftotext (via poppler) to use scholar-rename"
48
- puts "OSX: brew install pkg-config poppler"
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 == "--show-formats"
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 has_prereq?
62
- system("pdftotext -v 2> /dev/null")
63
- $?.success?
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("UTF-8", :invalid => :replace, :undef => :replace)
69
-
76
+ content = raw.encode('UTF-8', invalid: :replace, undef: :replace)
77
+
70
78
  # Choose pdf qualities
71
- @selector.set_content(content)
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? "--show-year"
78
- puts md[:title] if args.include? "--show-title"
79
- puts md[:author] if args.include? "--show-author"
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 = { :format => 0, :auto => true })
10
- set_content(c)
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
- if opts[:test]
13
- def puts(*x) x; end
14
- end
14
+ @lookup = lookup || ScholarLookup.new
15
+ return unless opts[:test]
16
+
17
+ def puts(*x) = x
15
18
  end
16
19
 
17
- def set_content(str)
20
+ def content=(str)
18
21
  @full_text = str.split("\n")
19
22
  @content = @full_text[0..14]
20
- .reject { |x| x.length < 2 }
21
- .map { |x| x[0..100] } # trim
23
+ .reject { |x| x.length < 2 }
24
+ .map { |x| x[0..100] } # trim
22
25
  end
23
26
 
24
27
  def select_all
25
- if !@options[:auto]
26
- puts "Options:"
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 "Select title line number " unless @options[:auto]
67
+ printf 'Select title line number:' unless @options[:auto]
30
68
  title = choose(@content, print: false)
31
69
 
32
- printf "Select author line number " unless @options[:auto]
70
+ printf 'Select author line number:' unless @options[:auto]
33
71
  authors = choose(@content, print: false)
34
72
 
35
- puts "Select author form:" unless @options[:auto]
73
+ puts 'Select author form:' unless @options[:auto]
36
74
  author = gen_authors(authors)
37
75
 
38
- # Automatically match.
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
- return [
51
- "#{y} - #{a} - #{t}.pdf",
52
- "#{y} #{a} #{t}.pdf",
53
- "#{a}_#{y}_#{t}.pdf".gsub(" ", "_"),
54
- "#{au}_#{y}_#{t}.pdf".gsub(" ", "_"),
55
- "#{ad}_#{y}_#{t}.pdf".gsub(" ", "_"),
56
- "#{a} #{y} #{t}.pdf",
57
- "#{au} #{y} #{t}.pdf",
58
- "#{ad} #{y} #{t}.pdf",
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 "List is empty." if list.empty?
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 = STDIN.gets.chomp || 0
120
+ line = $stdin.gets&.chomp || 0
77
121
  end
78
122
 
79
123
  meta = "list[#{line}]"
80
- mout = eval meta
81
- mout = mout.join " " if mout.is_a? (Array)
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(", ").map { |a| a.sub(/\d$/, "") } # delete ref number.
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] if !lm.nil?
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SR
2
- Version = "0.4.1"
4
+ VERSION = '0.4.2'
3
5
  end
data/readme.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # scholar-rename
2
2
 
3
- [![MIT](https://img.shields.io/npm/l/alt.svg?style=flat)](http://jeremywrnr.com/mit-license)
4
3
  [![Gem Version](https://badge.fury.io/rb/scholar-rename.svg)](https://badge.fury.io/rb/scholar-rename)
4
+ [![CI](https://github.com/jeremywrnr/scholar-rename/actions/workflows/ci.yml/badge.svg)](https://github.com/jeremywrnr/scholar-rename/actions/workflows/ci.yml)
5
+ [![MIT](https://img.shields.io/npm/l/alt.svg?style=flat)](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. there isn't actually anything scholarly about this tool, though
22
- academic people may find it useful for renaming pdfs that come in arbitrarily
23
- named file formats. services like mendeley kind of make this not super
24
- relevant. it helps when searching for a specific pdf and when labeling pdfs.
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.1
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: 2021-06-24 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: rake
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: rspec
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: http://github.com/jeremywrnr/scholar-rename
67
+ homepage: https://github.com/jeremywrnr/scholar-rename
54
68
  licenses:
55
69
  - MIT
56
- metadata: {}
57
- post_install_message:
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: 3.2.15
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: []