paperback 0.0.3 → 0.0.5
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 +5 -5
- data/.github/workflows/tests.yml +46 -0
- data/.rubocop-disables.yml +26 -12
- data/CHANGELOG.md +21 -0
- data/README.md +27 -6
- data/bin/paperback +4 -0
- data/lib/paperback/cli.rb +38 -2
- data/lib/paperback/document.rb +134 -17
- data/lib/paperback/preparer.rb +107 -29
- data/lib/paperback/version.rb +2 -1
- data/lib/paperback.rb +12 -0
- data/paperback.gemspec +10 -7
- data/sorbet/config +3 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
- data/sorbet/rbi/gems/chunky_png@1.4.0.rbi +4498 -0
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1083 -0
- data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
- data/sorbet/rbi/gems/parallel@1.22.1.rbi +277 -0
- data/sorbet/rbi/gems/parser@3.2.0.0.rbi +6963 -0
- data/sorbet/rbi/gems/pdf-core@0.4.0.rbi +1682 -0
- data/sorbet/rbi/gems/prawn@1.3.0.rbi +5567 -0
- data/sorbet/rbi/gems/pry@0.14.1.rbi +9990 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +408 -0
- data/sorbet/rbi/gems/rake@13.0.6.rbi +3023 -0
- data/sorbet/rbi/gems/rbi@0.0.16.rbi +3008 -0
- data/sorbet/rbi/gems/regexp_parser@2.6.1.rbi +3481 -0
- data/sorbet/rbi/gems/rexml@3.2.5.rbi +4717 -0
- data/sorbet/rbi/gems/rqrcode@0.10.1.rbi +617 -0
- data/sorbet/rbi/gems/rspec-core@3.12.0.rbi +10791 -0
- data/sorbet/rbi/gems/rspec-expectations@3.12.1.rbi +8106 -0
- data/sorbet/rbi/gems/rspec-mocks@3.12.1.rbi +5305 -0
- data/sorbet/rbi/gems/rspec-support@3.12.0.rbi +1617 -0
- data/sorbet/rbi/gems/rspec@3.12.0.rbi +88 -0
- data/sorbet/rbi/gems/rubocop-ast@1.24.1.rbi +6617 -0
- data/sorbet/rbi/gems/rubocop@0.93.1.rbi +40848 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +1234 -0
- data/sorbet/rbi/gems/sixword@0.4.0.rbi +536 -0
- data/sorbet/rbi/gems/spoom@1.1.15.rbi +2383 -0
- data/sorbet/rbi/gems/subprocess@1.5.6.rbi +391 -0
- data/sorbet/rbi/gems/tapioca@0.10.5.rbi +3207 -0
- data/sorbet/rbi/gems/thor@1.2.1.rbi +3956 -0
- data/sorbet/rbi/gems/ttfunk@1.4.0.rbi +1951 -0
- data/sorbet/rbi/gems/unicode-display_width@1.8.0.rbi +40 -0
- data/sorbet/rbi/gems/unparser@0.6.7.rbi +4524 -0
- data/sorbet/rbi/gems/webrick@1.7.0.rbi +2555 -0
- data/sorbet/rbi/gems/yard-sorbet@0.8.0.rbi +441 -0
- data/sorbet/rbi/gems/yard@0.9.28.rbi +17816 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +4 -0
- data/spec/functional/paperback/cli_spec.rb +195 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/unit/paperback_spec.rb +1 -0
- metadata +91 -7
@@ -0,0 +1,195 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'digest/sha2'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'tmpdir'
|
7
|
+
|
8
|
+
require 'subprocess'
|
9
|
+
|
10
|
+
RSpec.describe Paperback::CLI do
|
11
|
+
Executable = File.dirname(__FILE__) + '/../../bin/paperback'
|
12
|
+
|
13
|
+
SupportedPopplerVersions = [
|
14
|
+
/\A0\.86\..*\z/,
|
15
|
+
/\A22\.02\..*\z/,
|
16
|
+
]
|
17
|
+
|
18
|
+
def get_pdftotext_version
|
19
|
+
output = Subprocess.check_output(
|
20
|
+
['pdftotext', '-v'], stderr: Subprocess::STDOUT
|
21
|
+
)
|
22
|
+
first_line = output.lines(chomp: true)[0]
|
23
|
+
if first_line.nil?
|
24
|
+
raise Exception('Unexpected version line from pdftotext: ' + output.inspect)
|
25
|
+
end
|
26
|
+
|
27
|
+
parts = first_line.split(' ')
|
28
|
+
if parts[0] != 'pdftotext' || parts[1] != 'version'
|
29
|
+
raise Exception('Bad version line from pdftotext: ' + first_line.inspect)
|
30
|
+
end
|
31
|
+
|
32
|
+
return parts[2..].join(' ')
|
33
|
+
end
|
34
|
+
|
35
|
+
# pdftotext from poppler is a test dependency
|
36
|
+
it 'finds system pdftotext installed (test dependency)' do
|
37
|
+
expect(Subprocess.check_output(%w[which pdftotext])
|
38
|
+
).to match(/pdftotext/)
|
39
|
+
|
40
|
+
version = get_pdftotext_version
|
41
|
+
|
42
|
+
expect(version).to match(/\A\d+\..*/)
|
43
|
+
|
44
|
+
unless SupportedPopplerVersions.any? {|regex| regex.match(version) }
|
45
|
+
Paperback.log.warn("#{File.basename(__FILE__)}: Unsupported poppler-utils pdftotext version #{version}, tests may fail!")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'end-to-end test:' do
|
50
|
+
before(:each) do
|
51
|
+
@tmpdir = Dir.mktmpdir('paperback-rspec.')
|
52
|
+
@orig_cwd = Dir.pwd
|
53
|
+
Dir.chdir(@tmpdir)
|
54
|
+
end
|
55
|
+
|
56
|
+
after(:each) do
|
57
|
+
Dir.chdir(@orig_cwd) if @orig_cwd
|
58
|
+
FileUtils.rm_r(@tmpdir) if @tmpdir
|
59
|
+
@tmpdir = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'backs up unencrypted content correctly' do
|
63
|
+
secret = "The quick brown fox jumps over the lazy dog.\n"
|
64
|
+
sha = 'b47cc0f104b62d4c7c30bcd68fd8e67613e287dc4ad8c310ef10cbadea9c4380'
|
65
|
+
|
66
|
+
File.write('fox.txt', secret)
|
67
|
+
|
68
|
+
expect(Digest::SHA256.hexdigest(File.read('fox.txt'))).to eq(sha)
|
69
|
+
|
70
|
+
timestamp = Time.now.strftime('%F %T %z')
|
71
|
+
|
72
|
+
expected = <<-EOM
|
73
|
+
This is a paper backup produced by `paperback`. https://github.com/ab/paperback
|
74
|
+
|
75
|
+
Filename: fox.txt
|
76
|
+
Backed up: #{timestamp}
|
77
|
+
Mtime: #{timestamp}
|
78
|
+
Bytes: 45
|
79
|
+
Comment: Some comment
|
80
|
+
SHA256: b47cc0f104b62d4c7c30bcd68fd8e67613e287dc4ad8c310ef10cbadea9c4380
|
81
|
+
Encrypted: no
|
82
|
+
|
83
|
+
|
84
|
+
1 of 2
|
85
|
+
\fThis sixword text encodes 45 bytes in 6 six-word sentences. Decode with `sixword -d`.
|
86
|
+
|
87
|
+
1: beak us ache sour bern lola
|
88
|
+
2: core arc hulk slid drew due
|
89
|
+
3: chub ends bog russ bess mast
|
90
|
+
4: dime arm skew quit kim san
|
91
|
+
5: coat mod hit need knew grim
|
92
|
+
6: cave ella raft fir a act3
|
93
|
+
|
94
|
+
|
95
|
+
This base64 text encodes 45 bytes in 61 characters. Decode with `base64 --decode`.
|
96
|
+
|
97
|
+
VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4K
|
98
|
+
|
99
|
+
|
100
|
+
2 of 2
|
101
|
+
\f
|
102
|
+
EOM
|
103
|
+
|
104
|
+
# strip trailing newline and extra runs of spaces or newlines
|
105
|
+
expected = expected[0...-1].gsub(/ +/, ' ').gsub(/\n\n+/, "\n")
|
106
|
+
|
107
|
+
# create the backup file!
|
108
|
+
Paperback::CLI.create_backup(
|
109
|
+
input: 'fox.txt', output: 'fox.pdf', encrypt: false,
|
110
|
+
comment: 'Some comment'
|
111
|
+
)
|
112
|
+
|
113
|
+
# make sure we got a PDF
|
114
|
+
expect(File.size('fox.pdf')).to be > 2000
|
115
|
+
expect(File.read('fox.pdf', 20)).to start_with("%PDF-1.3\n")
|
116
|
+
|
117
|
+
# convert to text to see what's in it
|
118
|
+
Subprocess.check_call(%w[pdftotext -layout fox.pdf fox.pdftotext.txt])
|
119
|
+
|
120
|
+
expect(File.size('fox.pdftotext.txt')).to be > 100
|
121
|
+
|
122
|
+
# This test is likely to break given even minor changes to paperback or
|
123
|
+
# pdftotext, so it may not be particularly useful. However, it's nice to
|
124
|
+
# at least have some kind of end-to-end test.
|
125
|
+
converted_text = File.read('fox.pdftotext.txt')
|
126
|
+
converted_text_stripped = converted_text.gsub(/ +/, ' ').gsub(/\n\n+/, "\n")
|
127
|
+
expect(converted_text_stripped).to eq(expected)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'backs up encrypted content correctly' do
|
131
|
+
secret = 'Ey9V03Iuq/gKLH0aVf2aPTAULWydpWt6G/L+O7LJXHeK'
|
132
|
+
sha = '8675e6be1ae84a781b2a81c65b3300ee0611ccb3ad1e0b8e8dad5a94b6d4eef2'
|
133
|
+
|
134
|
+
File.write('2.txt', secret)
|
135
|
+
expect(Digest::SHA256.hexdigest(File.read('2.txt'))).to eq(sha)
|
136
|
+
|
137
|
+
# create the backup file!
|
138
|
+
Paperback::CLI.create_backup(
|
139
|
+
input: '2.txt', output: '2.pdf', encrypt: true,
|
140
|
+
passphrase_file: '2.passphrase.txt'
|
141
|
+
)
|
142
|
+
|
143
|
+
# make sure we got a PDF
|
144
|
+
expect(File.size('2.pdf')).to be > 2000
|
145
|
+
expect(File.read('2.pdf', 20)).to start_with("%PDF-1.3\n")
|
146
|
+
|
147
|
+
# make sure we got a passphrase
|
148
|
+
pass = File.read('2.passphrase.txt')
|
149
|
+
expect(pass.length).to eq 43
|
150
|
+
pass_sha = Digest::SHA256.hexdigest(pass)
|
151
|
+
|
152
|
+
# convert pdf to text
|
153
|
+
Subprocess.check_call(%w[pdftotext 2.pdf 2.pdftotext.txt])
|
154
|
+
|
155
|
+
expect(File.size('2.pdftotext.txt')).to be > 100
|
156
|
+
|
157
|
+
content = File.read('2.pdftotext.txt')
|
158
|
+
|
159
|
+
expect(content).to include('2.txt')
|
160
|
+
expect(content).to include(sha)
|
161
|
+
expect(content).to include(pass_sha[0...16])
|
162
|
+
|
163
|
+
# locate sixword content
|
164
|
+
expect(content).to include('This sixword text')
|
165
|
+
pages = content.split("\f")
|
166
|
+
pg2 = pages.fetch(1)
|
167
|
+
lines = pg2.split("\n")
|
168
|
+
expect(lines.fetch(0)).to start_with('This sixword text')
|
169
|
+
|
170
|
+
# pull sixword lines until blank line
|
171
|
+
sixword_lines = []
|
172
|
+
lines[1..-1].each do |line|
|
173
|
+
break if line.strip.empty?
|
174
|
+
expect(line).to match(/\A\d+: [a-z ]+[0-9]?\z/)
|
175
|
+
sixword_lines << line.split(':').fetch(1)
|
176
|
+
end
|
177
|
+
|
178
|
+
# decode sixword content
|
179
|
+
decoded = Sixword.decode(sixword_lines.join("\n"), padding_ok: true)
|
180
|
+
|
181
|
+
# decrypt data
|
182
|
+
File.write('2.sixword.gpg', decoded)
|
183
|
+
p = Subprocess::Process.new(
|
184
|
+
%w[gpg --batch --passphrase-fd 0 --decrypt 2.sixword.gpg],
|
185
|
+
stdin: Subprocess::PIPE, stdout: Subprocess::PIPE
|
186
|
+
)
|
187
|
+
decrypted, _ = p.communicate(pass)
|
188
|
+
expect(p.wait.exitstatus).to eq 0
|
189
|
+
expect(p.wait.success?).to eq true
|
190
|
+
|
191
|
+
expect(decrypted).to eq(secret)
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/unit/paperback_spec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paperback
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Brody
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: pry
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0.50'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sorbet
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.5'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.5'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: tapioca
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.10'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.10'
|
83
111
|
- !ruby/object:Gem::Dependency
|
84
112
|
name: yard
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,6 +164,20 @@ dependencies:
|
|
136
164
|
- - "~>"
|
137
165
|
- !ruby/object:Gem::Version
|
138
166
|
version: '0.3'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: sorbet-runtime
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0.5'
|
174
|
+
type: :runtime
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0.5'
|
139
181
|
- !ruby/object:Gem::Dependency
|
140
182
|
name: subprocess
|
141
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -160,6 +202,7 @@ executables:
|
|
160
202
|
extensions: []
|
161
203
|
extra_rdoc_files: []
|
162
204
|
files:
|
205
|
+
- ".github/workflows/tests.yml"
|
163
206
|
- ".gitignore"
|
164
207
|
- ".rspec"
|
165
208
|
- ".rubocop-disables.yml"
|
@@ -177,6 +220,47 @@ files:
|
|
177
220
|
- lib/paperback/preparer.rb
|
178
221
|
- lib/paperback/version.rb
|
179
222
|
- paperback.gemspec
|
223
|
+
- sorbet/config
|
224
|
+
- sorbet/rbi/annotations/rainbow.rbi
|
225
|
+
- sorbet/rbi/gems/ast@2.4.2.rbi
|
226
|
+
- sorbet/rbi/gems/chunky_png@1.4.0.rbi
|
227
|
+
- sorbet/rbi/gems/coderay@1.1.3.rbi
|
228
|
+
- sorbet/rbi/gems/diff-lcs@1.5.0.rbi
|
229
|
+
- sorbet/rbi/gems/method_source@1.0.0.rbi
|
230
|
+
- sorbet/rbi/gems/netrc@0.11.0.rbi
|
231
|
+
- sorbet/rbi/gems/parallel@1.22.1.rbi
|
232
|
+
- sorbet/rbi/gems/parser@3.2.0.0.rbi
|
233
|
+
- sorbet/rbi/gems/pdf-core@0.4.0.rbi
|
234
|
+
- sorbet/rbi/gems/prawn@1.3.0.rbi
|
235
|
+
- sorbet/rbi/gems/pry@0.14.1.rbi
|
236
|
+
- sorbet/rbi/gems/rainbow@3.1.1.rbi
|
237
|
+
- sorbet/rbi/gems/rake@13.0.6.rbi
|
238
|
+
- sorbet/rbi/gems/rbi@0.0.16.rbi
|
239
|
+
- sorbet/rbi/gems/regexp_parser@2.6.1.rbi
|
240
|
+
- sorbet/rbi/gems/rexml@3.2.5.rbi
|
241
|
+
- sorbet/rbi/gems/rqrcode@0.10.1.rbi
|
242
|
+
- sorbet/rbi/gems/rspec-core@3.12.0.rbi
|
243
|
+
- sorbet/rbi/gems/rspec-expectations@3.12.1.rbi
|
244
|
+
- sorbet/rbi/gems/rspec-mocks@3.12.1.rbi
|
245
|
+
- sorbet/rbi/gems/rspec-support@3.12.0.rbi
|
246
|
+
- sorbet/rbi/gems/rspec@3.12.0.rbi
|
247
|
+
- sorbet/rbi/gems/rubocop-ast@1.24.1.rbi
|
248
|
+
- sorbet/rbi/gems/rubocop@0.93.1.rbi
|
249
|
+
- sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi
|
250
|
+
- sorbet/rbi/gems/sixword@0.4.0.rbi
|
251
|
+
- sorbet/rbi/gems/spoom@1.1.15.rbi
|
252
|
+
- sorbet/rbi/gems/subprocess@1.5.6.rbi
|
253
|
+
- sorbet/rbi/gems/tapioca@0.10.5.rbi
|
254
|
+
- sorbet/rbi/gems/thor@1.2.1.rbi
|
255
|
+
- sorbet/rbi/gems/ttfunk@1.4.0.rbi
|
256
|
+
- sorbet/rbi/gems/unicode-display_width@1.8.0.rbi
|
257
|
+
- sorbet/rbi/gems/unparser@0.6.7.rbi
|
258
|
+
- sorbet/rbi/gems/webrick@1.7.0.rbi
|
259
|
+
- sorbet/rbi/gems/yard-sorbet@0.8.0.rbi
|
260
|
+
- sorbet/rbi/gems/yard@0.9.28.rbi
|
261
|
+
- sorbet/tapioca/config.yml
|
262
|
+
- sorbet/tapioca/require.rb
|
263
|
+
- spec/functional/paperback/cli_spec.rb
|
180
264
|
- spec/spec_helper.rb
|
181
265
|
- spec/unit/paperback_spec.rb
|
182
266
|
homepage: https://github.com/ab/paperback
|
@@ -191,18 +275,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
191
275
|
requirements:
|
192
276
|
- - ">="
|
193
277
|
- !ruby/object:Gem::Version
|
194
|
-
version: '2.
|
278
|
+
version: '2.7'
|
195
279
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
196
280
|
requirements:
|
197
281
|
- - ">="
|
198
282
|
- !ruby/object:Gem::Version
|
199
283
|
version: '0'
|
200
284
|
requirements: []
|
201
|
-
|
202
|
-
rubygems_version: 2.5.2.3
|
285
|
+
rubygems_version: 3.3.26
|
203
286
|
signing_key:
|
204
287
|
specification_version: 4
|
205
288
|
summary: Create paper backups of sensitive data
|
206
289
|
test_files:
|
290
|
+
- spec/functional/paperback/cli_spec.rb
|
207
291
|
- spec/spec_helper.rb
|
208
292
|
- spec/unit/paperback_spec.rb
|