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 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: