rubysl-pstore 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []