arxiv-dl 0.2.0 → 0.3.0
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/CHANGELOG.md +8 -0
- data/README.md +29 -16
- data/lib/arxiv/downloader/archive.rb +17 -10
- data/lib/arxiv/downloader/paper_folder.rb +85 -0
- data/lib/arxiv/downloader/version.rb +1 -1
- data/lib/arxiv/downloader.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c919d2e16ae72397375aa3aa5f71bd08fe4f3d0687d83358bf652081ee994596
|
|
4
|
+
data.tar.gz: 35a55cb7152642e19816a76b4a00fe52b06f74d5a52cb587d2e2b6897e23ac61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67f1ea3177544e5c4e5472d2b1e81d894a42346313ae4666f53f9de89571b36a55d50ee247dae0b8af2dce869cec03ea72890009817ee613b7cab37b93bddbfe
|
|
7
|
+
data.tar.gz: 5bcc8fc0ab51a75032ed0a3d8d2e20dad830f9b6bb92f75841be0fa23e05f60d66e76cb7e23c5a8b17fffba0634213d3b4299fde0c86314858f396135d2a9a84
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [0.3.0]
|
|
2
|
+
|
|
3
|
+
Less folder nesting for the common case: most papers only ever have one version.
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- Single-version papers are archived flat again: a paper whose only archived version is v1 keeps its files directly in the paper folder, without a `v1/` level. `v<N>/` folders are used only when a paper has more than one version. Archiving a second version of a flat paper moves the existing files into `v<N>/` first, rewriting `html/` links into `_shared/` for the new depth. A paper whose latest version is v2 or later starts out in `v<N>/` folders. Existing 0.2.0 archives keep their `v1/` folders.
|
|
8
|
+
|
|
1
9
|
## [0.2.0]
|
|
2
10
|
|
|
3
11
|
Versioned archives, author affiliations, and a round of robustness fixes. Two breaking changes to the output layout and metadata; see below.
|
data/README.md
CHANGED
|
@@ -108,27 +108,40 @@ $ARXIV_DOWNLOAD_PATH/ # default: $HOME/Downloads/ArXiv_Papers
|
|
|
108
108
|
arxiv.org/static/...
|
|
109
109
|
cdn.jsdelivr.net/...
|
|
110
110
|
YYYY/MM/DD/<primary_category>/<arxiv-id>-<slug>/
|
|
111
|
-
v<N
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
html
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
*.tex, *.bbl, ... # extracted from /src/<id>v<N>
|
|
111
|
+
<arxiv-id>v<N>.pdf
|
|
112
|
+
<arxiv-id>v<N>-abstract.html
|
|
113
|
+
metadata.md # YAML frontmatter + Markdown body
|
|
114
|
+
metadata.yaml
|
|
115
|
+
metadata.json
|
|
116
|
+
metadata.bib # upstream BibTeX, falls back to synthesized
|
|
117
|
+
html/ # absent when the paper has no HTML version
|
|
118
|
+
<arxiv-id>v<N>.html # path-rewritten to local assets
|
|
119
|
+
x1.png, x2.png, ... # paper-specific images
|
|
120
|
+
src/ # absent for PDF-only submissions
|
|
121
|
+
*.tex, *.bbl, ... # extracted from /src/<id>v<N>
|
|
123
122
|
```
|
|
124
123
|
|
|
125
|
-
An unversioned ID (`2508.16190`) archives the latest version. A versioned ID (`2508.16190v1`) archives that version.
|
|
124
|
+
An unversioned ID (`2508.16190`) archives the latest version. A versioned ID (`2508.16190v1`) archives that version.
|
|
126
125
|
|
|
127
|
-
|
|
126
|
+
A paper with a single archived version v1 is kept flat, as above. When a paper has more than one version, each version gets its own `v<N>/` folder with the same contents:
|
|
127
|
+
|
|
128
|
+
```txt
|
|
129
|
+
YYYY/MM/DD/<primary_category>/<arxiv-id>-<slug>/
|
|
130
|
+
v1/
|
|
131
|
+
<arxiv-id>v1.pdf
|
|
132
|
+
...
|
|
133
|
+
v2/
|
|
134
|
+
<arxiv-id>v2.pdf
|
|
135
|
+
...
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Archiving a second version of a flat paper first moves the existing files into `v<N>/` (rewriting `html/` links into `_shared/` for the new depth). A paper whose latest version is v2 or later starts out in `v<N>/` folders.
|
|
139
|
+
|
|
140
|
+
Each version downloads into a sibling `.partial` folder and is renamed into place only when every file succeeded. Re-running skips versions already archived and retries interrupted ones from scratch.
|
|
128
141
|
|
|
129
142
|
`YYYY/MM/DD` is the original submission date. `<primary_category>` is from the paper's metadata (`cs.CL`, `math.NT`, etc). `<slug>` is derived from the paper title (Unicode → ASCII, hyphenated, truncated to 80 chars at a word boundary).
|
|
130
143
|
|
|
131
|
-
For legacy IDs containing `/` (e.g. `cs/0002001`), the slash is replaced with `-` in the directory name and file names (`cs-0002001-.../
|
|
144
|
+
For legacy IDs containing `/` (e.g. `cs/0002001`), the slash is replaced with `-` in the directory name and file names (`cs-0002001-.../cs-0002001v1.pdf`).
|
|
132
145
|
|
|
133
146
|
## Library usage
|
|
134
147
|
|
|
@@ -138,7 +151,7 @@ require 'arxiv/downloader'
|
|
|
138
151
|
identifier = Arxiv::Downloader::Identifier.new '2508.16190'
|
|
139
152
|
client = Arxiv::Downloader::Client.new # 3-second rate limit by default
|
|
140
153
|
path = Arxiv::Downloader::Archive.new(identifier, root: '/tmp/papers', client: client).run
|
|
141
|
-
# => "/tmp/papers/2025/08/22/cs.CL/2508.16190-comicscene154-a-scene-dataset-for-comic-analysis
|
|
154
|
+
# => "/tmp/papers/2025/08/22/cs.CL/2508.16190-comicscene154-a-scene-dataset-for-comic-analysis"
|
|
142
155
|
```
|
|
143
156
|
|
|
144
157
|
## Development
|
|
@@ -9,12 +9,14 @@ module Arxiv
|
|
|
9
9
|
@client = client
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
# Downloads into
|
|
13
|
-
# renames it
|
|
14
|
-
# An
|
|
12
|
+
# Downloads into <destination>.partial/ and
|
|
13
|
+
# renames it into place only once everything succeeded.
|
|
14
|
+
# An archived version is always complete and is skipped.
|
|
15
|
+
# PaperFolder decides flat (single version) vs v<N>/ (several versions).
|
|
15
16
|
def run
|
|
16
|
-
return
|
|
17
|
+
return paper_folder.location_of(metadata.version) if paper_folder.archived? metadata.version
|
|
17
18
|
|
|
19
|
+
paper_folder.unflatten!
|
|
18
20
|
FileUtils.rm_rf staging_dir
|
|
19
21
|
FileUtils.mkdir_p staging_dir
|
|
20
22
|
|
|
@@ -24,8 +26,8 @@ module Arxiv
|
|
|
24
26
|
download_source_archive
|
|
25
27
|
write_sidecars
|
|
26
28
|
|
|
27
|
-
File.rename staging_dir,
|
|
28
|
-
|
|
29
|
+
File.rename staging_dir, destination
|
|
30
|
+
destination
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
private
|
|
@@ -44,13 +46,18 @@ module Arxiv
|
|
|
44
46
|
@archived ||= Identifier.new "#{metadata.arxiv_id}v#{metadata.version}"
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
def
|
|
48
|
-
@
|
|
49
|
+
def paper_folder
|
|
50
|
+
@paper_folder ||= PaperFolder.new File.join(@root, Path.new(metadata).to_s)
|
|
49
51
|
end
|
|
50
52
|
|
|
51
|
-
#
|
|
53
|
+
# evaluated after unflatten!, which can turn a flat folder into v<N>/ folders
|
|
54
|
+
def destination
|
|
55
|
+
@destination ||= paper_folder.destination_for metadata.version
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# a sibling of destination, so relative ../_shared/ refs survive the rename
|
|
52
59
|
def staging_dir
|
|
53
|
-
"#{
|
|
60
|
+
"#{destination}.partial"
|
|
54
61
|
end
|
|
55
62
|
|
|
56
63
|
def download_pdf
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'nokogiri'
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Arxiv
|
|
6
|
+
module Downloader
|
|
7
|
+
# One paper's folder. A paper with a single archived version is kept flat
|
|
8
|
+
# (files directly in the folder); once a second version arrives, each
|
|
9
|
+
# version gets its own v<N>/ folder.
|
|
10
|
+
class PaperFolder
|
|
11
|
+
VERSION_FOLDER = /\Av\d+(\.partial)?\z/
|
|
12
|
+
|
|
13
|
+
attr_reader :path
|
|
14
|
+
|
|
15
|
+
def initialize path
|
|
16
|
+
@path = path
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def archived? version
|
|
20
|
+
Dir.exist? location_of(version)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def location_of version
|
|
24
|
+
return path if flat_version == version
|
|
25
|
+
|
|
26
|
+
version_path version
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# where a new download of `version` goes; call unflatten! first if the folder is flat
|
|
30
|
+
def destination_for version
|
|
31
|
+
return path if version == 1 && !versioned?
|
|
32
|
+
|
|
33
|
+
version_path version
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# moves the flat version into v<N>/ so another version can sit beside it
|
|
37
|
+
def unflatten!
|
|
38
|
+
return if flat_version.nil?
|
|
39
|
+
|
|
40
|
+
destination = version_path flat_version
|
|
41
|
+
FileUtils.mkdir_p destination
|
|
42
|
+
|
|
43
|
+
# metadata.yaml marks the folder as flat, so it moves last
|
|
44
|
+
children = Dir.children(path) - [File.basename(destination), metadata_filename]
|
|
45
|
+
children.each { FileUtils.mv File.join(path, it), destination }
|
|
46
|
+
deepen_shared_links File.join(destination, 'html')
|
|
47
|
+
FileUtils.mv File.join(path, metadata_filename), destination
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def flat_version
|
|
53
|
+
metadata_path = File.join path, metadata_filename
|
|
54
|
+
return unless File.exist? metadata_path
|
|
55
|
+
|
|
56
|
+
::YAML.safe_load_file(metadata_path, permitted_classes: [Date]).fetch 'version'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def versioned?
|
|
60
|
+
Dir.exist?(path) && Dir.children(path).any? { VERSION_FOLDER.match? it }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def version_path version
|
|
64
|
+
File.join path, "v#{version}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def metadata_filename
|
|
68
|
+
Metadata::YAML::FILENAME
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# html/ moved one level deeper, so relative links up into _shared/ need one more ../
|
|
72
|
+
def deepen_shared_links html_dir
|
|
73
|
+
Dir.glob(File.join(html_dir, '*.html')).each do |html_path|
|
|
74
|
+
document = Nokogiri::HTML File.read(html_path)
|
|
75
|
+
HTMLArchive::ASSET_SELECTORS.each do |selector, attribute|
|
|
76
|
+
document.css(selector).each do |node|
|
|
77
|
+
node[attribute] = "../#{node[attribute]}" if node[attribute].start_with? '../'
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
File.write html_path, document.to_html
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/arxiv/downloader.rb
CHANGED
|
@@ -16,6 +16,7 @@ require_relative 'downloader/metadata/bibtex' # after metadata
|
|
|
16
16
|
require_relative 'downloader/metadata/json' # after metadata
|
|
17
17
|
require_relative 'downloader/metadata/markdown' # after metadata
|
|
18
18
|
require_relative 'downloader/metadata/yaml' # after metadata
|
|
19
|
+
require_relative 'downloader/paper_folder'
|
|
19
20
|
require_relative 'downloader/paper_not_found' # after error
|
|
20
21
|
require_relative 'downloader/path'
|
|
21
22
|
require_relative 'downloader/pdf'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: arxiv-dl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shane Becker
|
|
@@ -116,6 +116,7 @@ files:
|
|
|
116
116
|
- lib/arxiv/downloader/metadata/json.rb
|
|
117
117
|
- lib/arxiv/downloader/metadata/markdown.rb
|
|
118
118
|
- lib/arxiv/downloader/metadata/yaml.rb
|
|
119
|
+
- lib/arxiv/downloader/paper_folder.rb
|
|
119
120
|
- lib/arxiv/downloader/paper_not_found.rb
|
|
120
121
|
- lib/arxiv/downloader/path.rb
|
|
121
122
|
- lib/arxiv/downloader/pdf.rb
|