DATA.save 0.0.1
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/.gitignore +1 -0
- data/DATA.save.gemspec +20 -0
- data/Readme.md +67 -0
- data/lib/DATA.save.rb +47 -0
- data/spec/DATA.save_spec.rb +298 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 82028c8bd9bc63cab5bdb572528e5ac79a5a452f
|
4
|
+
data.tar.gz: d258af74d79e3a80b807346b0b02f57b8ca9e619
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: eec3648ff909e617dd11f301463e1e5480d376a3924d99cd9c342f755bf5f0af88f92e643968a7dd9962f54873828c328d75078530ee63550ed35388aa7a99e9
|
7
|
+
data.tar.gz: 8552ca116db426f91f90167012cd18cdf05fe5fc76268d83478abcda766d0e40a989161a099cd8e13b7dcc95d2d3ef4abc69deebe6af797014aa13c56c6c8ea4
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
**.gem
|
data/DATA.save.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative "lib/DATA.save"
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "DATA.save"
|
5
|
+
s.version = DataSave::VERSION
|
6
|
+
s.authors = ["Josh Cheek"]
|
7
|
+
s.email = ["josh.cheek@gmail.com"]
|
8
|
+
s.homepage = "https://github.com/JoshCheek/DATA.save"
|
9
|
+
s.summary = %q{Store shit in your script's DATA segment}
|
10
|
+
s.description = %q{Store shit in your script's DATA segment}
|
11
|
+
s.license = "WTFPL"
|
12
|
+
|
13
|
+
s.rubyforge_project = "seeing_is_believing"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_development_dependency "rspec", "~> 3.2"
|
20
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
`DATA.save`
|
2
|
+
===========
|
3
|
+
|
4
|
+
Stored shit in your script's data segment.
|
5
|
+
|
6
|
+
|
7
|
+
Install
|
8
|
+
-------
|
9
|
+
|
10
|
+
```sh
|
11
|
+
$ gem install DATA.save
|
12
|
+
```
|
13
|
+
|
14
|
+
|
15
|
+
Example
|
16
|
+
-------
|
17
|
+
|
18
|
+
A program that records how many times it's been run:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
require 'DATA.save'
|
22
|
+
|
23
|
+
run_count = DATA.load.to_i
|
24
|
+
run_count += 1
|
25
|
+
DATA.save run_count
|
26
|
+
|
27
|
+
puts "Run count: #{run_count}"
|
28
|
+
|
29
|
+
__END__
|
30
|
+
0
|
31
|
+
```
|
32
|
+
|
33
|
+
When we invoke it, it increments the counter,
|
34
|
+
which it stores in its `DATA` segment.
|
35
|
+
|
36
|
+
```sh
|
37
|
+
# run it 3 times
|
38
|
+
$ ruby count_runs.rb
|
39
|
+
Run count: 1
|
40
|
+
|
41
|
+
$ ruby count_runs.rb
|
42
|
+
Run count: 2
|
43
|
+
|
44
|
+
$ ruby count_runs.rb
|
45
|
+
Run count: 3
|
46
|
+
|
47
|
+
# now check out the file
|
48
|
+
$ cat count_runs.rb
|
49
|
+
require 'DATA.save'
|
50
|
+
|
51
|
+
run_count = DATA.load.to_i
|
52
|
+
run_count += 1
|
53
|
+
DATA.save run_count
|
54
|
+
|
55
|
+
puts "Run count: #{run_count}"
|
56
|
+
|
57
|
+
__END__
|
58
|
+
3
|
59
|
+
```
|
60
|
+
|
61
|
+
|
62
|
+
License
|
63
|
+
-------
|
64
|
+
|
65
|
+
Just do what the fuck you want to.
|
66
|
+
|
67
|
+
[http://www.wtfpl.net/about/](http://www.wtfpl.net/about/)
|
data/lib/DATA.save.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
class DataSave
|
2
|
+
VERSION = '0.0.1'
|
3
|
+
|
4
|
+
def self.for(data_segment)
|
5
|
+
new data_segment.path, data_segment.pos
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.on(data_segment)
|
9
|
+
store = DataSave.for data_segment
|
10
|
+
data_segment.define_singleton_method(:load) { store.load }
|
11
|
+
data_segment.define_singleton_method(:save) { |data| store.save data }
|
12
|
+
data_segment.define_singleton_method(:update) { |&block| store.update(&block) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(path, offset)
|
16
|
+
self.path = path
|
17
|
+
self.offset = offset
|
18
|
+
end
|
19
|
+
|
20
|
+
def load
|
21
|
+
open_segment { |ds| ds.read }
|
22
|
+
end
|
23
|
+
|
24
|
+
def save(data)
|
25
|
+
open_segment do |ds|
|
26
|
+
ds.puts data
|
27
|
+
ds.truncate ds.pos
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def update
|
32
|
+
save yield load
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
attr_accessor :path, :offset
|
38
|
+
|
39
|
+
def open_segment
|
40
|
+
File.open path, 'r+' do |file|
|
41
|
+
file.seek offset
|
42
|
+
yield file
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
DataSave.on DATA if defined? DATA
|
@@ -0,0 +1,298 @@
|
|
1
|
+
require 'DATA.save'
|
2
|
+
|
3
|
+
module SpecHelpers
|
4
|
+
RunResult = Struct.new :status, :stdout, :stderr
|
5
|
+
|
6
|
+
class RunResults
|
7
|
+
attr_accessor :body
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
self.body = ''
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_run(status:, stdout:, stderr:)
|
14
|
+
result = RunResult.new status, stdout, stderr
|
15
|
+
results << result
|
16
|
+
end
|
17
|
+
|
18
|
+
def results
|
19
|
+
@results ||= []
|
20
|
+
end
|
21
|
+
|
22
|
+
def stdouts
|
23
|
+
results.map(&:stdout)
|
24
|
+
end
|
25
|
+
|
26
|
+
def stderrs
|
27
|
+
results.map(&:stderr)
|
28
|
+
end
|
29
|
+
|
30
|
+
def statuses
|
31
|
+
results.map(&:status)
|
32
|
+
end
|
33
|
+
|
34
|
+
def data_segment
|
35
|
+
body.split("\n__END__\n").last
|
36
|
+
end
|
37
|
+
|
38
|
+
def assert_no_err!
|
39
|
+
stderrs.each do |err|
|
40
|
+
next if err.empty?
|
41
|
+
raise RSpec::Expectations::ExpectationNotMetError,
|
42
|
+
"Expectected no stderr, got:\n\n#{err}"
|
43
|
+
end
|
44
|
+
|
45
|
+
statuses.each do |status|
|
46
|
+
next if status.success?
|
47
|
+
raise RSpec::Expectations::ExpectationNotMetError,
|
48
|
+
"Expected all success, got:\n\n#{status.inspect}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def lib_dir
|
55
|
+
File.expand_path '../lib', __dir__
|
56
|
+
end
|
57
|
+
|
58
|
+
def capture3(*command)
|
59
|
+
read_stdout, write_stdout = IO.pipe
|
60
|
+
read_stderr, write_stderr = IO.pipe
|
61
|
+
|
62
|
+
pid = spawn *command,
|
63
|
+
out: write_stdout,
|
64
|
+
err: write_stderr
|
65
|
+
|
66
|
+
write_stdout.close
|
67
|
+
write_stderr.close
|
68
|
+
|
69
|
+
Process.wait pid
|
70
|
+
|
71
|
+
{ status: $?,
|
72
|
+
stdout: read_stdout.read,
|
73
|
+
stderr: read_stderr.read,
|
74
|
+
}
|
75
|
+
ensure
|
76
|
+
read_stdout.close unless read_stdout.closed?
|
77
|
+
read_stderr.close unless read_stderr.closed?
|
78
|
+
end
|
79
|
+
|
80
|
+
require 'tempfile'
|
81
|
+
def with_file(name, body, closed: false)
|
82
|
+
Tempfile.open name do |file|
|
83
|
+
file.write body
|
84
|
+
file.rewind
|
85
|
+
file.close if closed
|
86
|
+
yield file
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def run_file(name, body, num_times:1)
|
91
|
+
indentation = body[/\A */]
|
92
|
+
body = body.gsub /^#{indentation}/, ''
|
93
|
+
result = RunResults.new
|
94
|
+
with_file name, body, closed: true do |file|
|
95
|
+
num_times.times do
|
96
|
+
run = capture3 'ruby', '-w', '-I', lib_dir, file.path
|
97
|
+
result.add_run run
|
98
|
+
end
|
99
|
+
result.body = File.read file.path
|
100
|
+
end
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
def segment_for(obj)
|
105
|
+
body_after(body: '') { |storage| storage.save obj }
|
106
|
+
end
|
107
|
+
|
108
|
+
def body_after(body:, pos:0)
|
109
|
+
with_file 'body_after.rb', body do |file|
|
110
|
+
file.seek pos
|
111
|
+
yield DataSave.for(file), file
|
112
|
+
File.read file.path
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
RSpec.configure do |config|
|
118
|
+
config.include SpecHelpers
|
119
|
+
config.disable_monkey_patching!
|
120
|
+
config.fail_fast = true
|
121
|
+
end
|
122
|
+
|
123
|
+
RSpec.describe 'DATA.save' do
|
124
|
+
it 'loads data from the data segment' do
|
125
|
+
result = run_file 'file.rb', <<-FILE
|
126
|
+
require 'DATA.save'
|
127
|
+
p DATA.load
|
128
|
+
__END__
|
129
|
+
the data
|
130
|
+
FILE
|
131
|
+
result.assert_no_err!
|
132
|
+
expect(result.stdouts).to eq [%'"the data\\n"\n']
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'saves data to the data segment' do
|
136
|
+
result = run_file 'file.rb', <<-FILE
|
137
|
+
require 'DATA.save'
|
138
|
+
DATA.save("new data")
|
139
|
+
__END__
|
140
|
+
old data
|
141
|
+
FILE
|
142
|
+
result.assert_no_err!
|
143
|
+
expect(result.data_segment).to eq "new data\n"
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'works with the example from the readme' do
|
147
|
+
result = run_file 'count_runs.rb', <<-FILE, num_times: 3
|
148
|
+
require 'DATA.save'
|
149
|
+
|
150
|
+
run_count = DATA.load.to_i
|
151
|
+
run_count += 1
|
152
|
+
DATA.save run_count
|
153
|
+
|
154
|
+
puts "Run count: \#{run_count}"
|
155
|
+
|
156
|
+
__END__
|
157
|
+
0
|
158
|
+
FILE
|
159
|
+
|
160
|
+
result.assert_no_err!
|
161
|
+
expect(result.stdouts).to eq [
|
162
|
+
"Run count: 1\n",
|
163
|
+
"Run count: 2\n",
|
164
|
+
"Run count: 3\n",
|
165
|
+
]
|
166
|
+
|
167
|
+
expect(result.body).to eq <<-FILE.gsub(/^ */, '')
|
168
|
+
require 'DATA.save'
|
169
|
+
|
170
|
+
run_count = DATA.load.to_i
|
171
|
+
run_count += 1
|
172
|
+
DATA.save run_count
|
173
|
+
|
174
|
+
puts "Run count: \#{run_count}"
|
175
|
+
|
176
|
+
__END__
|
177
|
+
3
|
178
|
+
FILE
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
describe '.for' do
|
183
|
+
it 'creates a new instance every time, when a data segment is given' do
|
184
|
+
with_file 'f.rb', 'zomg', closed: true do |file|
|
185
|
+
segment1 = File.open file
|
186
|
+
segment2 = File.open file
|
187
|
+
segment2.getc
|
188
|
+
|
189
|
+
storage1 = DataSave.for segment1
|
190
|
+
storage2 = DataSave.for segment2
|
191
|
+
|
192
|
+
expect(storage1.load).to eq 'zomg'
|
193
|
+
expect(storage2.load).to eq 'omg'
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
describe '.on' do
|
200
|
+
it 'is places the methods on the object' do
|
201
|
+
with_file 'f.rb', '-----' do |file|
|
202
|
+
file.seek 1
|
203
|
+
DataSave.on file
|
204
|
+
file.save 'a'
|
205
|
+
expect(File.read file).to eq "-a\n"
|
206
|
+
expect(file.load).to eq "a\n"
|
207
|
+
file.update { |loaded| expect(loaded).to eq "a\n"; "C" }
|
208
|
+
expect(File.read file).to eq "-C\n"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
it 'calls #to_s when writing' do
|
215
|
+
o = Object.new
|
216
|
+
def o.inspect() "inspected\n" end
|
217
|
+
def o.to_s() "to_s'd\n" end
|
218
|
+
expect(segment_for o).to eq "to_s'd\n"
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
it 'appends a newline if the data doesn\'t have one (b/c this is a file)' do
|
223
|
+
expect(segment_for "a\n").to eq "a\n"
|
224
|
+
expect(segment_for "a" ).to eq "a\n"
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
it 'works when the written segment is shorter than the existing data segment' do
|
229
|
+
body = body_after(body: 'abcdefg', pos: 1) { |storage| storage.save "X\n" }
|
230
|
+
expect(body).to eq "aX\n"
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
it 'works when the written segment is longer than the existing data segment' do
|
235
|
+
body = body_after(body: 'ab', pos: 1) { |storage| storage.save "X\n" }
|
236
|
+
expect(body).to eq "aX\n"
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
it 'works when there are UTF8 characters in the body' do
|
241
|
+
body = body_after body: 'Ω1', pos: 'Ω'.bytesize do |storage|
|
242
|
+
storage.save "X\n"
|
243
|
+
end
|
244
|
+
expect(body).to eq "ΩX\n"
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
it 'works when there are UTF8 characters in the old DATA segment' do
|
249
|
+
body = body_after body: '1Ω', pos: 1 do |storage|
|
250
|
+
storage.save "X\n"
|
251
|
+
end
|
252
|
+
expect(body).to eq "1X\n"
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
it 'works when there are UTF8 characters in the new DATA segment' do
|
257
|
+
body = body_after body: '12', pos: 1 do |storage|
|
258
|
+
storage.save "Ω\n"
|
259
|
+
end
|
260
|
+
expect(body).to eq "1Ω\n"
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
it 'can be reinvoked multiple times' do
|
265
|
+
body = body_after body: "-0\n", pos: 1 do |storage, file|
|
266
|
+
expect(storage.load).to eq "0\n"
|
267
|
+
|
268
|
+
storage.save 1
|
269
|
+
expect(File.read file).to eq "-1\n"
|
270
|
+
expect(storage.load).to eq "1\n"
|
271
|
+
|
272
|
+
storage.save 2
|
273
|
+
expect(File.read file).to eq "-2\n"
|
274
|
+
expect(storage.load).to eq "2\n"
|
275
|
+
end
|
276
|
+
expect(body).to eq "-2\n"
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
it 'reloads the data every time' do
|
281
|
+
body = body_after body: "-0\n", pos: 1 do |storage, file|
|
282
|
+
expect(storage.load).to eq "0\n"
|
283
|
+
File.write file, 'zomg'
|
284
|
+
expect(storage.load).to eq 'omg'
|
285
|
+
end
|
286
|
+
expect(body).to eq "zomg"
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'allows for upddating, which gives the current value to a block, and saves whatever comes back' do
|
290
|
+
body = body_after body: "-0\n", pos: 1 do |storage|
|
291
|
+
storage.update do |current|
|
292
|
+
expect(current).to eq "0\n"
|
293
|
+
100
|
294
|
+
end
|
295
|
+
end
|
296
|
+
expect(body).to eq "-100\n"
|
297
|
+
end
|
298
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: DATA.save
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Josh Cheek
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.2'
|
27
|
+
description: Store shit in your script's DATA segment
|
28
|
+
email:
|
29
|
+
- josh.cheek@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gitignore"
|
35
|
+
- DATA.save.gemspec
|
36
|
+
- Readme.md
|
37
|
+
- lib/DATA.save.rb
|
38
|
+
- spec/DATA.save_spec.rb
|
39
|
+
homepage: https://github.com/JoshCheek/DATA.save
|
40
|
+
licenses:
|
41
|
+
- WTFPL
|
42
|
+
metadata: {}
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project: seeing_is_believing
|
59
|
+
rubygems_version: 2.4.1
|
60
|
+
signing_key:
|
61
|
+
specification_version: 4
|
62
|
+
summary: Store shit in your script's DATA segment
|
63
|
+
test_files:
|
64
|
+
- spec/DATA.save_spec.rb
|
65
|
+
has_rdoc:
|