rubysl-pstore 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 49e8c464bdca0f27119a2c5e04523932784880e7
4
+ data.tar.gz: 4e10e4cf5ec7e64f77589b6918b4bb66aadba2d8
5
+ SHA512:
6
+ metadata.gz: c0194a5e9c670355a299c6cf3c78c74711ede3aef8f2446c2231a3216371256bb3287be3458766ce9e396124cc91841666ecd2fcaaefc4b7e90ac5339efedd52
7
+ data.tar.gz: d71099fd0bd5172c5ad39ab78f771d4904f6270b8447ead45958e533d1648fcc19c4cf3b284a45f0ade1d1f944db3c2c264dfc7e960dae8acab084523c5c6261
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ before_install:
3
+ - gem update --system
4
+ - gem --version
5
+ - gem install rubysl-bundler
6
+ script: bundle exec mspec spec
7
+ rvm:
8
+ - rbx-nightly-18mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rubysl-pstore.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2013, Brian Shirai
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ 3. Neither the name of the library nor the names of its contributors may be
13
+ used to endorse or promote products derived from this software without
14
+ specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT,
20
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
25
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,29 @@
1
+ # Rubysl::Pstore
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'rubysl-pstore'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install rubysl-pstore
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ require "rubysl/pstore"
@@ -0,0 +1,2 @@
1
+ require "rubysl/pstore/pstore"
2
+ require "rubysl/pstore/version"
@@ -0,0 +1,395 @@
1
+ # = PStore -- Transactional File Storage for Ruby Objects
2
+ #
3
+ # pstore.rb -
4
+ # originally by matz
5
+ # documentation by Kev Jackson and James Edward Gray II
6
+ #
7
+ # See PStore for documentation.
8
+
9
+
10
+ require "fileutils"
11
+ require "digest/md5"
12
+
13
+ #
14
+ # PStore implements a file based persistence mechanism based on a Hash. User
15
+ # code can store hierarchies of Ruby objects (values) into the data store file
16
+ # by name (keys). An object hierarchy may be just a single object. User code
17
+ # may later read values back from the data store or even update data, as needed.
18
+ #
19
+ # The transactional behavior ensures that any changes succeed or fail together.
20
+ # This can be used to ensure that the data store is not left in a transitory
21
+ # state, where some values were updated but others were not.
22
+ #
23
+ # Behind the scenes, Ruby objects are stored to the data store file with
24
+ # Marshal. That carries the usual limitations. Proc objects cannot be
25
+ # marshalled, for example.
26
+ #
27
+ # == Usage example:
28
+ #
29
+ # require "pstore"
30
+ #
31
+ # # a mock wiki object...
32
+ # class WikiPage
33
+ # def initialize( page_name, author, contents )
34
+ # @page_name = page_name
35
+ # @revisions = Array.new
36
+ #
37
+ # add_revision(author, contents)
38
+ # end
39
+ #
40
+ # attr_reader :page_name
41
+ #
42
+ # def add_revision( author, contents )
43
+ # @revisions << { :created => Time.now,
44
+ # :author => author,
45
+ # :contents => contents }
46
+ # end
47
+ #
48
+ # def wiki_page_references
49
+ # [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/)
50
+ # end
51
+ #
52
+ # # ...
53
+ # end
54
+ #
55
+ # # create a new page...
56
+ # home_page = WikiPage.new( "HomePage", "James Edward Gray II",
57
+ # "A page about the JoysOfDocumentation..." )
58
+ #
59
+ # # then we want to update page data and the index together, or not at all...
60
+ # wiki = PStore.new("wiki_pages.pstore")
61
+ # wiki.transaction do # begin transaction; do all of this or none of it
62
+ # # store page...
63
+ # wiki[home_page.page_name] = home_page
64
+ # # ensure that an index has been created...
65
+ # wiki[:wiki_index] ||= Array.new
66
+ # # update wiki index...
67
+ # wiki[:wiki_index].push(*home_page.wiki_page_references)
68
+ # end # commit changes to wiki data store file
69
+ #
70
+ # ### Some time later... ###
71
+ #
72
+ # # read wiki data...
73
+ # wiki.transaction(true) do # begin read-only transaction, no changes allowed
74
+ # wiki.roots.each do |data_root_name|
75
+ # p data_root_name
76
+ # p wiki[data_root_name]
77
+ # end
78
+ # end
79
+ #
80
+ class PStore
81
+ binmode = defined?(File::BINARY) ? File::BINARY : 0
82
+ RDWR_ACCESS = File::RDWR | File::CREAT | binmode
83
+ RD_ACCESS = File::RDONLY | binmode
84
+ WR_ACCESS = File::WRONLY | File::CREAT | File::TRUNC | binmode
85
+
86
+ # The error type thrown by all PStore methods.
87
+ class Error < StandardError
88
+ end
89
+
90
+ #
91
+ # To construct a PStore object, pass in the _file_ path where you would like
92
+ # the data to be stored.
93
+ #
94
+ def initialize(file)
95
+ dir = File::dirname(file)
96
+ unless File::directory? dir
97
+ raise PStore::Error, format("directory %s does not exist", dir)
98
+ end
99
+ if File::exist? file and not File::readable? file
100
+ raise PStore::Error, format("file %s not readable", file)
101
+ end
102
+ @transaction = false
103
+ @filename = file
104
+ @abort = false
105
+ end
106
+
107
+ # Raises PStore::Error if the calling code is not in a PStore#transaction.
108
+ def in_transaction
109
+ raise PStore::Error, "not in transaction" unless @transaction
110
+ end
111
+ #
112
+ # Raises PStore::Error if the calling code is not in a PStore#transaction or
113
+ # if the code is in a read-only PStore#transaction.
114
+ #
115
+ def in_transaction_wr()
116
+ in_transaction()
117
+ raise PStore::Error, "in read-only transaction" if @rdonly
118
+ end
119
+ private :in_transaction, :in_transaction_wr
120
+
121
+ #
122
+ # Retrieves a value from the PStore file data, by _name_. The hierarchy of
123
+ # Ruby objects stored under that root _name_ will be returned.
124
+ #
125
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
126
+ # raise PStore::Error if called at any other time.
127
+ #
128
+ def [](name)
129
+ in_transaction
130
+ @table[name]
131
+ end
132
+ #
133
+ # This method is just like PStore#[], save that you may also provide a
134
+ # _default_ value for the object. In the event the specified _name_ is not
135
+ # found in the data store, your _default_ will be returned instead. If you do
136
+ # not specify a default, PStore::Error will be raised if the object is not
137
+ # found.
138
+ #
139
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
140
+ # raise PStore::Error if called at any other time.
141
+ #
142
+ def fetch(name, default=PStore::Error)
143
+ in_transaction
144
+ unless @table.key? name
145
+ if default==PStore::Error
146
+ raise PStore::Error, format("undefined root name `%s'", name)
147
+ else
148
+ return default
149
+ end
150
+ end
151
+ @table[name]
152
+ end
153
+ #
154
+ # Stores an individual Ruby object or a hierarchy of Ruby objects in the data
155
+ # store file under the root _name_. Assigning to a _name_ already in the data
156
+ # store clobbers the old data.
157
+ #
158
+ # == Example:
159
+ #
160
+ # require "pstore"
161
+ #
162
+ # store = PStore.new("data_file.pstore")
163
+ # store.transaction do # begin transaction
164
+ # # load some data into the store...
165
+ # store[:single_object] = "My data..."
166
+ # store[:obj_heirarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
167
+ # "James Gray" => ["erb.rb", "pstore.rb"] }
168
+ # end # commit changes to data store file
169
+ #
170
+ # *WARNING*: This method is only valid in a PStore#transaction and it cannot
171
+ # be read-only. It will raise PStore::Error if called at any other time.
172
+ #
173
+ def []=(name, value)
174
+ in_transaction_wr()
175
+ @table[name] = value
176
+ end
177
+ #
178
+ # Removes an object hierarchy from the data store, by _name_.
179
+ #
180
+ # *WARNING*: This method is only valid in a PStore#transaction and it cannot
181
+ # be read-only. It will raise PStore::Error if called at any other time.
182
+ #
183
+ def delete(name)
184
+ in_transaction_wr()
185
+ @table.delete name
186
+ end
187
+
188
+ #
189
+ # Returns the names of all object hierarchies currently in the store.
190
+ #
191
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
192
+ # raise PStore::Error if called at any other time.
193
+ #
194
+ def roots
195
+ in_transaction
196
+ @table.keys
197
+ end
198
+ #
199
+ # Returns true if the supplied _name_ is currently in the data store.
200
+ #
201
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
202
+ # raise PStore::Error if called at any other time.
203
+ #
204
+ def root?(name)
205
+ in_transaction
206
+ @table.key? name
207
+ end
208
+ # Returns the path to the data store file.
209
+ def path
210
+ @filename
211
+ end
212
+
213
+ #
214
+ # Ends the current PStore#transaction, committing any changes to the data
215
+ # store immediately.
216
+ #
217
+ # == Example:
218
+ #
219
+ # require "pstore"
220
+ #
221
+ # store = PStore.new("data_file.pstore")
222
+ # store.transaction do # begin transaction
223
+ # # load some data into the store...
224
+ # store[:one] = 1
225
+ # store[:two] = 2
226
+ #
227
+ # store.commit # end transaction here, committing changes
228
+ #
229
+ # store[:three] = 3 # this change is never reached
230
+ # end
231
+ #
232
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
233
+ # raise PStore::Error if called at any other time.
234
+ #
235
+ def commit
236
+ in_transaction
237
+ @abort = false
238
+ throw :pstore_abort_transaction
239
+ end
240
+ #
241
+ # Ends the current PStore#transaction, discarding any changes to the data
242
+ # store.
243
+ #
244
+ # == Example:
245
+ #
246
+ # require "pstore"
247
+ #
248
+ # store = PStore.new("data_file.pstore")
249
+ # store.transaction do # begin transaction
250
+ # store[:one] = 1 # this change is not applied, see below...
251
+ # store[:two] = 2 # this change is not applied, see below...
252
+ #
253
+ # store.abort # end transaction here, discard all changes
254
+ #
255
+ # store[:three] = 3 # this change is never reached
256
+ # end
257
+ #
258
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
259
+ # raise PStore::Error if called at any other time.
260
+ #
261
+ def abort
262
+ in_transaction
263
+ @abort = true
264
+ throw :pstore_abort_transaction
265
+ end
266
+
267
+ #
268
+ # Opens a new transaction for the data store. Code executed inside a block
269
+ # passed to this method may read and write data to and from the data store
270
+ # file.
271
+ #
272
+ # At the end of the block, changes are committed to the data store
273
+ # automatically. You may exit the transaction early with a call to either
274
+ # PStore#commit or PStore#abort. See those methods for details about how
275
+ # changes are handled. Raising an uncaught Exception in the block is
276
+ # equivalent to calling PStore#abort.
277
+ #
278
+ # If _read_only_ is set to +true+, you will only be allowed to read from the
279
+ # data store during the transaction and any attempts to change the data will
280
+ # raise a PStore::Error.
281
+ #
282
+ # Note that PStore does not support nested transactions.
283
+ #
284
+ def transaction(read_only=false) # :yields: pstore
285
+ raise PStore::Error, "nested transaction" if @transaction
286
+ begin
287
+ @rdonly = read_only
288
+ @abort = false
289
+ @transaction = true
290
+ value = nil
291
+ new_file = @filename + ".new"
292
+
293
+ content = nil
294
+ unless read_only
295
+ file = File.open(@filename, RDWR_ACCESS)
296
+ file.flock(File::LOCK_EX)
297
+ commit_new(file) if FileTest.exist?(new_file)
298
+ content = file.read()
299
+ else
300
+ begin
301
+ file = File.open(@filename, RD_ACCESS)
302
+ file.flock(File::LOCK_SH)
303
+ content = (File.open(new_file, RD_ACCESS) {|n| n.read} rescue file.read())
304
+ rescue Errno::ENOENT
305
+ content = ""
306
+ end
307
+ end
308
+
309
+ if content != ""
310
+ @table = load(content)
311
+ if !read_only
312
+ size = content.size
313
+ md5 = Digest::MD5.digest(content)
314
+ end
315
+ else
316
+ @table = {}
317
+ end
318
+ content = nil # unreference huge data
319
+
320
+ begin
321
+ catch(:pstore_abort_transaction) do
322
+ value = yield(self)
323
+ end
324
+ rescue Exception
325
+ @abort = true
326
+ raise
327
+ ensure
328
+ if !read_only and !@abort
329
+ tmp_file = @filename + ".tmp"
330
+ content = dump(@table)
331
+ if !md5 || size != content.size || md5 != Digest::MD5.digest(content)
332
+ File.open(tmp_file, WR_ACCESS) {|t| t.write(content)}
333
+ File.rename(tmp_file, new_file)
334
+ commit_new(file)
335
+ end
336
+ content = nil # unreference huge data
337
+ end
338
+ end
339
+ ensure
340
+ @table = nil
341
+ @transaction = false
342
+ file.close if file
343
+ end
344
+ value
345
+ end
346
+
347
+ # This method is just a wrapped around Marshal.dump.
348
+ def dump(table) # :nodoc:
349
+ Marshal::dump(table)
350
+ end
351
+
352
+ # This method is just a wrapped around Marshal.load.
353
+ def load(content) # :nodoc:
354
+ Marshal::load(content)
355
+ end
356
+
357
+ # This method is just a wrapped around Marshal.load.
358
+ def load_file(file) # :nodoc:
359
+ Marshal::load(file)
360
+ end
361
+
362
+ private
363
+ # Commits changes to the data store file.
364
+ def commit_new(f)
365
+ f.truncate(0)
366
+ f.rewind
367
+ new_file = @filename + ".new"
368
+ File.open(new_file, RD_ACCESS) do |nf|
369
+ FileUtils.copy_stream(nf, f)
370
+ end
371
+ File.unlink(new_file)
372
+ end
373
+ end
374
+
375
+ # :enddoc:
376
+
377
+ if __FILE__ == $0
378
+ db = PStore.new("/tmp/foo")
379
+ db.transaction do
380
+ p db.roots
381
+ ary = db["root"] = [1,2,3,4]
382
+ ary[1] = [1,1.5]
383
+ end
384
+
385
+ 1000.times do
386
+ db.transaction do
387
+ db["root"][0] += 1
388
+ p db["root"][0]
389
+ end
390
+ end
391
+
392
+ db.transaction(true) do
393
+ p db["root"]
394
+ end
395
+ end
@@ -0,0 +1,5 @@
1
+ module RubySL
2
+ module PStore
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ require './lib/rubysl/pstore/version'
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "rubysl-pstore"
6
+ spec.version = RubySL::PStore::VERSION
7
+ spec.authors = ["Brian Shirai"]
8
+ spec.email = ["brixen@gmail.com"]
9
+ spec.description = %q{Ruby standard library pstore.}
10
+ spec.summary = %q{Ruby standard library pstore.}
11
+ spec.homepage = "https://github.com/rubysl/rubysl-pstore"
12
+ spec.license = "BSD"
13
+
14
+ spec.files = `git ls-files`.split($/)
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_development_dependency "bundler", "~> 1.3"
20
+ spec.add_development_dependency "rake", "~> 10.0"
21
+ spec.add_development_dependency "mspec", "~> 1.5"
22
+ spec.add_development_dependency "rubysl-prettyprint", "~> 1.0"
23
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubysl-pstore
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Shirai
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubysl-prettyprint
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ description: Ruby standard library pstore.
70
+ email:
71
+ - brixen@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .travis.yml
78
+ - Gemfile
79
+ - LICENSE
80
+ - README.md
81
+ - Rakefile
82
+ - lib/pstore.rb
83
+ - lib/rubysl/pstore.rb
84
+ - lib/rubysl/pstore/pstore.rb
85
+ - lib/rubysl/pstore/version.rb
86
+ - rubysl-pstore.gemspec
87
+ homepage: https://github.com/rubysl/rubysl-pstore
88
+ licenses:
89
+ - BSD
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.0.7
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Ruby standard library pstore.
111
+ test_files: []