pstore 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/test.yml +11 -6
- data/Gemfile +1 -0
- data/lib/pstore.rb +408 -170
- data/pstore.gemspec +2 -2
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72f658b529641295888e56602677fe0f38bda8824529a2a8d35100e6d58e99e8
|
4
|
+
data.tar.gz: 1531cc04ead49cbc5dbf481f2f4a695bc1de94398f057ff0c65ff022322284bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79f06b0cb5666d9ba0a2497d99b42761c5c728b87137693581faa6c08f9b3f4f53dc34764db01b4190a08e33259e0b933a98aa17f5dd8f0717670fdb55a9b0df
|
7
|
+
data.tar.gz: 7cd25219d39946f558480e53373b47da30d8083cea9e67a3dfd44f7612050f23fd05577d932cb0f4a1af46b42119ff2a4bf3bb36300a5163f31d837f7126dbda
|
data/.github/workflows/test.yml
CHANGED
@@ -3,22 +3,27 @@ name: test
|
|
3
3
|
on: [push, pull_request]
|
4
4
|
|
5
5
|
jobs:
|
6
|
-
|
6
|
+
ruby-versions:
|
7
|
+
uses: ruby/actions/.github/workflows/ruby_versions.yml@master
|
8
|
+
with:
|
9
|
+
engine: cruby
|
10
|
+
min_version: 2.4
|
11
|
+
|
12
|
+
test:
|
13
|
+
needs: ruby-versions
|
7
14
|
name: build (${{ matrix.ruby }} / ${{ matrix.os }})
|
8
15
|
strategy:
|
9
16
|
matrix:
|
10
|
-
ruby:
|
17
|
+
ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
|
11
18
|
os: [ ubuntu-latest, macos-latest ]
|
12
19
|
runs-on: ${{ matrix.os }}
|
13
20
|
steps:
|
14
|
-
- uses: actions/checkout@
|
21
|
+
- uses: actions/checkout@v4
|
15
22
|
- name: Set up Ruby
|
16
23
|
uses: ruby/setup-ruby@v1
|
17
24
|
with:
|
18
25
|
ruby-version: ${{ matrix.ruby }}
|
19
26
|
- name: Install dependencies
|
20
|
-
run:
|
21
|
-
gem install bundler --no-document
|
22
|
-
bundle install
|
27
|
+
run: gem i test-unit test-unit-ruby-core
|
23
28
|
- name: Run test
|
24
29
|
run: rake test
|
data/Gemfile
CHANGED
data/lib/pstore.rb
CHANGED
@@ -10,89 +10,323 @@
|
|
10
10
|
|
11
11
|
require "digest"
|
12
12
|
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# may later read values back from the data store
|
13
|
+
# \PStore implements a file based persistence mechanism based on a Hash.
|
14
|
+
# User code can store hierarchies of Ruby objects (values)
|
15
|
+
# into the data store by name (keys).
|
16
|
+
# An object hierarchy may be just a single object.
|
17
|
+
# User code may later read values back from the data store
|
18
|
+
# or even update data, as needed.
|
18
19
|
#
|
19
20
|
# 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
|
-
#
|
21
|
+
# This can be used to ensure that the data store is not left in a transitory state,
|
22
|
+
# where some values were updated but others were not.
|
23
|
+
#
|
24
|
+
# Behind the scenes, Ruby objects are stored to the data store file with Marshal.
|
25
|
+
# That carries the usual limitations. Proc objects cannot be marshalled,
|
26
|
+
# for example.
|
27
|
+
#
|
28
|
+
# There are three important concepts here (details at the links):
|
29
|
+
#
|
30
|
+
# - {Store}[rdoc-ref:PStore@The+Store]: a store is an instance of \PStore.
|
31
|
+
# - {Entries}[rdoc-ref:PStore@Entries]: the store is hash-like;
|
32
|
+
# each entry is the key for a stored object.
|
33
|
+
# - {Transactions}[rdoc-ref:PStore@Transactions]: each transaction is a collection
|
34
|
+
# of prospective changes to the store;
|
35
|
+
# a transaction is defined in the block given with a call
|
36
|
+
# to PStore#transaction.
|
37
|
+
#
|
38
|
+
# == About the Examples
|
39
|
+
#
|
40
|
+
# Examples on this page need a store that has known properties.
|
41
|
+
# They can get a new (and populated) store by calling thus:
|
42
|
+
#
|
43
|
+
# example_store do |store|
|
44
|
+
# # Example code using store goes here.
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# All we really need to know about +example_store+
|
48
|
+
# is that it yields a fresh store with a known population of entries;
|
49
|
+
# its implementation:
|
50
|
+
#
|
51
|
+
# require 'pstore'
|
52
|
+
# require 'tempfile'
|
53
|
+
# # Yield a pristine store for use in examples.
|
54
|
+
# def example_store
|
55
|
+
# # Create the store in a temporary file.
|
56
|
+
# Tempfile.create do |file|
|
57
|
+
# store = PStore.new(file)
|
58
|
+
# # Populate the store.
|
59
|
+
# store.transaction do
|
60
|
+
# store[:foo] = 0
|
61
|
+
# store[:bar] = 1
|
62
|
+
# store[:baz] = 2
|
63
|
+
# end
|
64
|
+
# yield store
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# == The Store
|
69
|
+
#
|
70
|
+
# The contents of the store are maintained in a file whose path is specified
|
71
|
+
# when the store is created (see PStore.new).
|
72
|
+
# The objects are stored and retrieved using
|
73
|
+
# module Marshal, which means that certain objects cannot be added to the store;
|
74
|
+
# see {Marshal::dump}[https://docs.ruby-lang.org/en/master/Marshal.html#method-c-dump].
|
75
|
+
#
|
76
|
+
# == Entries
|
77
|
+
#
|
78
|
+
# A store may have any number of entries.
|
79
|
+
# Each entry has a key and a value, just as in a hash:
|
80
|
+
#
|
81
|
+
# - Key: as in a hash, the key can be (almost) any object;
|
82
|
+
# see {Hash Keys}[https://docs.ruby-lang.org/en/master/Hash.html#class-Hash-label-Hash+Keys].
|
83
|
+
# You may find it convenient to keep it simple by using only
|
84
|
+
# symbols or strings as keys.
|
85
|
+
# - Value: the value may be any object that can be marshalled by \Marshal
|
86
|
+
# (see {Marshal::dump}[https://docs.ruby-lang.org/en/master/Marshal.html#method-c-dump])
|
87
|
+
# and in fact may be a collection
|
88
|
+
# (e.g., an array, a hash, a set, a range, etc).
|
89
|
+
# That collection may in turn contain nested objects,
|
90
|
+
# including collections, to any depth;
|
91
|
+
# those objects must also be \Marshal-able.
|
92
|
+
# See {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
|
93
|
+
#
|
94
|
+
# == Transactions
|
95
|
+
#
|
96
|
+
# === The Transaction Block
|
97
|
+
#
|
98
|
+
# The block given with a call to method #transaction#
|
99
|
+
# contains a _transaction_,
|
100
|
+
# which consists of calls to \PStore methods that
|
101
|
+
# read from or write to the store
|
102
|
+
# (that is, all \PStore methods except #transaction itself,
|
103
|
+
# #path, and Pstore.new):
|
104
|
+
#
|
105
|
+
# example_store do |store|
|
106
|
+
# store.transaction do
|
107
|
+
# store.keys # => [:foo, :bar, :baz]
|
108
|
+
# store[:bat] = 3
|
109
|
+
# store.keys # => [:foo, :bar, :baz, :bat]
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# Execution of the transaction is deferred until the block exits,
|
114
|
+
# and is executed _atomically_ (all-or-nothing):
|
115
|
+
# either all transaction calls are executed, or none are.
|
116
|
+
# This maintains the integrity of the store.
|
117
|
+
#
|
118
|
+
# Other code in the block (including even calls to #path and PStore.new)
|
119
|
+
# is executed immediately, not deferred.
|
120
|
+
#
|
121
|
+
# The transaction block:
|
122
|
+
#
|
123
|
+
# - May not contain a nested call to #transaction.
|
124
|
+
# - Is the only context where methods that read from or write to
|
125
|
+
# the store are allowed.
|
126
|
+
#
|
127
|
+
# As seen above, changes in a transaction are made automatically
|
128
|
+
# when the block exits.
|
129
|
+
# The block may be exited early by calling method #commit or #abort.
|
130
|
+
#
|
131
|
+
# - Method #commit triggers the update to the store and exits the block:
|
132
|
+
#
|
133
|
+
# example_store do |store|
|
134
|
+
# store.transaction do
|
135
|
+
# store.keys # => [:foo, :bar, :baz]
|
136
|
+
# store[:bat] = 3
|
137
|
+
# store.commit
|
138
|
+
# fail 'Cannot get here'
|
139
|
+
# end
|
140
|
+
# store.transaction do
|
141
|
+
# # Update was completed.
|
142
|
+
# store.keys # => [:foo, :bar, :baz, :bat]
|
143
|
+
# end
|
144
|
+
# end
|
145
|
+
#
|
146
|
+
# - Method #abort discards the update to the store and exits the block:
|
147
|
+
#
|
148
|
+
# example_store do |store|
|
149
|
+
# store.transaction do
|
150
|
+
# store.keys # => [:foo, :bar, :baz]
|
151
|
+
# store[:bat] = 3
|
152
|
+
# store.abort
|
153
|
+
# fail 'Cannot get here'
|
154
|
+
# end
|
155
|
+
# store.transaction do
|
156
|
+
# # Update was not completed.
|
157
|
+
# store.keys # => [:foo, :bar, :baz]
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
#
|
161
|
+
# === Read-Only Transactions
|
162
|
+
#
|
163
|
+
# By default, a transaction allows both reading from and writing to
|
164
|
+
# the store:
|
165
|
+
#
|
166
|
+
# store.transaction do
|
167
|
+
# # Read-write transaction.
|
168
|
+
# # Any code except a call to #transaction is allowed here.
|
169
|
+
# end
|
170
|
+
#
|
171
|
+
# If argument +read_only+ is passed as +true+,
|
172
|
+
# only reading is allowed:
|
173
|
+
#
|
174
|
+
# store.transaction(true) do
|
175
|
+
# # Read-only transaction:
|
176
|
+
# # Calls to #transaction, #[]=, and #delete are not allowed here.
|
177
|
+
# end
|
178
|
+
#
|
179
|
+
# == Hierarchical Values
|
180
|
+
#
|
181
|
+
# The value for an entry may be a simple object (as seen above).
|
182
|
+
# It may also be a hierarchy of objects nested to any depth:
|
183
|
+
#
|
184
|
+
# deep_store = PStore.new('deep.store')
|
185
|
+
# deep_store.transaction do
|
186
|
+
# array_of_hashes = [{}, {}, {}]
|
187
|
+
# deep_store[:array_of_hashes] = array_of_hashes
|
188
|
+
# deep_store[:array_of_hashes] # => [{}, {}, {}]
|
189
|
+
# hash_of_arrays = {foo: [], bar: [], baz: []}
|
190
|
+
# deep_store[:hash_of_arrays] = hash_of_arrays
|
191
|
+
# deep_store[:hash_of_arrays] # => {:foo=>[], :bar=>[], :baz=>[]}
|
192
|
+
# deep_store[:hash_of_arrays][:foo].push(:bat)
|
193
|
+
# deep_store[:hash_of_arrays] # => {:foo=>[:bat], :bar=>[], :baz=>[]}
|
194
|
+
# end
|
195
|
+
#
|
196
|
+
# And recall that you can use
|
197
|
+
# {dig methods}[https://docs.ruby-lang.org/en/master/dig_methods_rdoc.html]
|
198
|
+
# in a returned hierarchy of objects.
|
199
|
+
#
|
200
|
+
# == Working with the Store
|
201
|
+
#
|
202
|
+
# === Creating a Store
|
203
|
+
#
|
204
|
+
# Use method PStore.new to create a store.
|
205
|
+
# The new store creates or opens its containing file:
|
206
|
+
#
|
207
|
+
# store = PStore.new('t.store')
|
208
|
+
#
|
209
|
+
# === Modifying the Store
|
210
|
+
#
|
211
|
+
# Use method #[]= to update or create an entry:
|
212
|
+
#
|
213
|
+
# example_store do |store|
|
214
|
+
# store.transaction do
|
215
|
+
# store[:foo] = 1 # Update.
|
216
|
+
# store[:bam] = 1 # Create.
|
217
|
+
# end
|
218
|
+
# end
|
219
|
+
#
|
220
|
+
# Use method #delete to remove an entry:
|
221
|
+
#
|
222
|
+
# example_store do |store|
|
223
|
+
# store.transaction do
|
224
|
+
# store.delete(:foo)
|
225
|
+
# store[:foo] # => nil
|
226
|
+
# end
|
227
|
+
# end
|
228
|
+
#
|
229
|
+
# === Retrieving Values
|
230
|
+
#
|
231
|
+
# Use method #fetch (allows default) or #[] (defaults to +nil+)
|
232
|
+
# to retrieve an entry:
|
233
|
+
#
|
234
|
+
# example_store do |store|
|
235
|
+
# store.transaction do
|
236
|
+
# store[:foo] # => 0
|
237
|
+
# store[:nope] # => nil
|
238
|
+
# store.fetch(:baz) # => 2
|
239
|
+
# store.fetch(:nope, nil) # => nil
|
240
|
+
# store.fetch(:nope) # Raises exception.
|
241
|
+
# end
|
242
|
+
# end
|
243
|
+
#
|
244
|
+
# === Querying the Store
|
245
|
+
#
|
246
|
+
# Use method #key? to determine whether a given key exists:
|
247
|
+
#
|
248
|
+
# example_store do |store|
|
249
|
+
# store.transaction do
|
250
|
+
# store.key?(:foo) # => true
|
251
|
+
# end
|
252
|
+
# end
|
22
253
|
#
|
23
|
-
#
|
24
|
-
# Marshal. That carries the usual limitations. Proc objects cannot be
|
25
|
-
# marshalled, for example.
|
254
|
+
# Use method #keys to retrieve keys:
|
26
255
|
#
|
27
|
-
#
|
256
|
+
# example_store do |store|
|
257
|
+
# store.transaction do
|
258
|
+
# store.keys # => [:foo, :bar, :baz]
|
259
|
+
# end
|
260
|
+
# end
|
261
|
+
#
|
262
|
+
# Use method #path to retrieve the path to the store's underlying file;
|
263
|
+
# this method may be called from outside a transaction block:
|
264
|
+
#
|
265
|
+
# store = PStore.new('t.store')
|
266
|
+
# store.path # => "t.store"
|
267
|
+
#
|
268
|
+
# == Transaction Safety
|
269
|
+
#
|
270
|
+
# For transaction safety, see:
|
271
|
+
#
|
272
|
+
# - Optional argument +thread_safe+ at method PStore.new.
|
273
|
+
# - Attribute #ultra_safe.
|
274
|
+
#
|
275
|
+
# Needless to say, if you're storing valuable data with \PStore, then you should
|
276
|
+
# backup the \PStore file from time to time.
|
277
|
+
#
|
278
|
+
# == An Example Store
|
28
279
|
#
|
29
280
|
# require "pstore"
|
30
281
|
#
|
31
|
-
# #
|
282
|
+
# # A mock wiki object.
|
32
283
|
# class WikiPage
|
33
|
-
#
|
284
|
+
#
|
285
|
+
# attr_reader :page_name
|
286
|
+
#
|
287
|
+
# def initialize(page_name, author, contents)
|
34
288
|
# @page_name = page_name
|
35
289
|
# @revisions = Array.new
|
36
|
-
#
|
37
290
|
# add_revision(author, contents)
|
38
291
|
# end
|
39
292
|
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# :author => author,
|
45
|
-
# :contents => contents }
|
293
|
+
# def add_revision(author, contents)
|
294
|
+
# @revisions << {created: Time.now,
|
295
|
+
# author: author,
|
296
|
+
# contents: contents}
|
46
297
|
# end
|
47
298
|
#
|
48
299
|
# def wiki_page_references
|
49
300
|
# [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/)
|
50
301
|
# end
|
51
302
|
#
|
52
|
-
# # ...
|
53
303
|
# end
|
54
304
|
#
|
55
|
-
# #
|
56
|
-
# home_page = WikiPage.new(
|
57
|
-
#
|
305
|
+
# # Create a new wiki page.
|
306
|
+
# home_page = WikiPage.new("HomePage", "James Edward Gray II",
|
307
|
+
# "A page about the JoysOfDocumentation..." )
|
58
308
|
#
|
59
|
-
# # then we want to update page data and the index together, or not at all...
|
60
309
|
# wiki = PStore.new("wiki_pages.pstore")
|
61
|
-
#
|
62
|
-
#
|
310
|
+
# # Update page data and the index together, or not at all.
|
311
|
+
# wiki.transaction do
|
312
|
+
# # Store page.
|
63
313
|
# wiki[home_page.page_name] = home_page
|
64
|
-
# #
|
314
|
+
# # Create page index.
|
65
315
|
# wiki[:wiki_index] ||= Array.new
|
66
|
-
# #
|
316
|
+
# # Update wiki index.
|
67
317
|
# wiki[:wiki_index].push(*home_page.wiki_page_references)
|
68
|
-
# end
|
69
|
-
#
|
70
|
-
# ### Some time later... ###
|
318
|
+
# end
|
71
319
|
#
|
72
|
-
# #
|
73
|
-
# wiki.transaction(true) do
|
74
|
-
# wiki.
|
75
|
-
#
|
76
|
-
#
|
320
|
+
# # Read wiki data, setting argument read_only to true.
|
321
|
+
# wiki.transaction(true) do
|
322
|
+
# wiki.keys.each do |key|
|
323
|
+
# puts key
|
324
|
+
# puts wiki[key]
|
77
325
|
# end
|
78
326
|
# end
|
79
327
|
#
|
80
|
-
# == Transaction modes
|
81
|
-
#
|
82
|
-
# By default, file integrity is only ensured as long as the operating system
|
83
|
-
# (and the underlying hardware) doesn't raise any unexpected I/O errors. If an
|
84
|
-
# I/O error occurs while PStore is writing to its file, then the file will
|
85
|
-
# become corrupted.
|
86
|
-
#
|
87
|
-
# You can prevent this by setting <em>pstore.ultra_safe = true</em>.
|
88
|
-
# However, this results in a minor performance loss, and only works on platforms
|
89
|
-
# that support atomic file renames. Please consult the documentation for
|
90
|
-
# +ultra_safe+ for details.
|
91
|
-
#
|
92
|
-
# Needless to say, if you're storing valuable data with PStore, then you should
|
93
|
-
# backup the PStore files from time to time.
|
94
328
|
class PStore
|
95
|
-
VERSION = "0.1.
|
329
|
+
VERSION = "0.1.3"
|
96
330
|
|
97
331
|
RDWR_ACCESS = {mode: IO::RDWR | IO::CREAT | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
|
98
332
|
RD_ACCESS = {mode: IO::RDONLY | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
|
@@ -102,21 +336,38 @@ class PStore
|
|
102
336
|
class Error < StandardError
|
103
337
|
end
|
104
338
|
|
105
|
-
# Whether PStore should do its best to prevent file corruptions,
|
106
|
-
# unlikely
|
107
|
-
#
|
108
|
-
#
|
339
|
+
# Whether \PStore should do its best to prevent file corruptions,
|
340
|
+
# even when an unlikely error (such as memory-error or filesystem error) occurs:
|
341
|
+
#
|
342
|
+
# - +true+: changes are posted by creating a temporary file,
|
343
|
+
# writing the updated data to it, then renaming the file to the given #path.
|
344
|
+
# File integrity is maintained.
|
345
|
+
# Note: has effect only if the filesystem has atomic file rename
|
346
|
+
# (as do POSIX platforms Linux, MacOS, FreeBSD and others).
|
347
|
+
#
|
348
|
+
# - +false+ (the default): changes are posted by rewinding the open file
|
349
|
+
# and writing the updated data.
|
350
|
+
# File integrity is maintained if the filesystem raises
|
351
|
+
# no unexpected I/O error;
|
352
|
+
# if such an error occurs during a write to the store,
|
353
|
+
# the file may become corrupted.
|
109
354
|
#
|
110
|
-
# This flag only has effect on platforms on which file renames are atomic (e.g.
|
111
|
-
# all POSIX platforms: Linux, MacOS X, FreeBSD, etc). The default value is false.
|
112
355
|
attr_accessor :ultra_safe
|
113
356
|
|
357
|
+
# Returns a new \PStore object.
|
358
|
+
#
|
359
|
+
# Argument +file+ is the path to the file in which objects are to be stored;
|
360
|
+
# if the file exists, it should be one that was written by \PStore.
|
114
361
|
#
|
115
|
-
#
|
116
|
-
#
|
362
|
+
# path = 't.store'
|
363
|
+
# store = PStore.new(path)
|
117
364
|
#
|
118
|
-
# PStore
|
119
|
-
#
|
365
|
+
# A \PStore object is
|
366
|
+
# {reentrant}[https://en.wikipedia.org/wiki/Reentrancy_(computing)].
|
367
|
+
# If argument +thread_safe+ is given as +true+,
|
368
|
+
# the object is also thread-safe (at the cost of a small performance penalty):
|
369
|
+
#
|
370
|
+
# store = PStore.new(path, true)
|
120
371
|
#
|
121
372
|
def initialize(file, thread_safe = false)
|
122
373
|
dir = File::dirname(file)
|
@@ -147,169 +398,156 @@ class PStore
|
|
147
398
|
end
|
148
399
|
private :in_transaction, :in_transaction_wr
|
149
400
|
|
401
|
+
# Returns the value for the given +key+ if the key exists.
|
402
|
+
# +nil+ otherwise;
|
403
|
+
# if not +nil+, the returned value is an object or a hierarchy of objects:
|
404
|
+
#
|
405
|
+
# example_store do |store|
|
406
|
+
# store.transaction do
|
407
|
+
# store[:foo] # => 0
|
408
|
+
# store[:nope] # => nil
|
409
|
+
# end
|
410
|
+
# end
|
150
411
|
#
|
151
|
-
#
|
152
|
-
# Ruby objects stored under that root _name_ will be returned.
|
412
|
+
# Returns +nil+ if there is no such key.
|
153
413
|
#
|
154
|
-
#
|
155
|
-
# raise PStore::Error if called at any other time.
|
414
|
+
# See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
|
156
415
|
#
|
157
|
-
|
416
|
+
# Raises an exception if called outside a transaction block.
|
417
|
+
def [](key)
|
158
418
|
in_transaction
|
159
|
-
@table[
|
419
|
+
@table[key]
|
160
420
|
end
|
421
|
+
|
422
|
+
# Like #[], except that it accepts a default value for the store.
|
423
|
+
# If the +key+ does not exist:
|
161
424
|
#
|
162
|
-
#
|
163
|
-
#
|
164
|
-
# found in the data store, your _default_ will be returned instead. If you do
|
165
|
-
# not specify a default, PStore::Error will be raised if the object is not
|
166
|
-
# found.
|
425
|
+
# - Raises an exception if +default+ is +PStore::Error+.
|
426
|
+
# - Returns the value of +default+ otherwise:
|
167
427
|
#
|
168
|
-
#
|
169
|
-
#
|
428
|
+
# example_store do |store|
|
429
|
+
# store.transaction do
|
430
|
+
# store.fetch(:nope, nil) # => nil
|
431
|
+
# store.fetch(:nope) # Raises an exception.
|
432
|
+
# end
|
433
|
+
# end
|
170
434
|
#
|
171
|
-
|
435
|
+
# Raises an exception if called outside a transaction block.
|
436
|
+
def fetch(key, default=PStore::Error)
|
172
437
|
in_transaction
|
173
|
-
unless @table.key?
|
438
|
+
unless @table.key? key
|
174
439
|
if default == PStore::Error
|
175
|
-
raise PStore::Error, format("undefined
|
440
|
+
raise PStore::Error, format("undefined key `%s'", key)
|
176
441
|
else
|
177
442
|
return default
|
178
443
|
end
|
179
444
|
end
|
180
|
-
@table[
|
445
|
+
@table[key]
|
181
446
|
end
|
447
|
+
|
448
|
+
# Creates or replaces the value for the given +key+:
|
182
449
|
#
|
183
|
-
#
|
184
|
-
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
#
|
188
|
-
#
|
189
|
-
# require "pstore"
|
190
|
-
#
|
191
|
-
# store = PStore.new("data_file.pstore")
|
192
|
-
# store.transaction do # begin transaction
|
193
|
-
# # load some data into the store...
|
194
|
-
# store[:single_object] = "My data..."
|
195
|
-
# store[:obj_hierarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
|
196
|
-
# "James Gray" => ["erb.rb", "pstore.rb"] }
|
197
|
-
# end # commit changes to data store file
|
450
|
+
# example_store do |store|
|
451
|
+
# temp.transaction do
|
452
|
+
# temp[:bat] = 3
|
453
|
+
# end
|
454
|
+
# end
|
198
455
|
#
|
199
|
-
#
|
200
|
-
# be read-only. It will raise PStore::Error if called at any other time.
|
456
|
+
# See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
|
201
457
|
#
|
202
|
-
|
458
|
+
# Raises an exception if called outside a transaction block.
|
459
|
+
def []=(key, value)
|
203
460
|
in_transaction_wr
|
204
|
-
@table[
|
461
|
+
@table[key] = value
|
205
462
|
end
|
463
|
+
|
464
|
+
# Removes and returns the value at +key+ if it exists:
|
206
465
|
#
|
207
|
-
#
|
466
|
+
# example_store do |store|
|
467
|
+
# store.transaction do
|
468
|
+
# store[:bat] = 3
|
469
|
+
# store.delete(:bat)
|
470
|
+
# end
|
471
|
+
# end
|
208
472
|
#
|
209
|
-
#
|
210
|
-
# be read-only. It will raise PStore::Error if called at any other time.
|
473
|
+
# Returns +nil+ if there is no such key.
|
211
474
|
#
|
212
|
-
|
475
|
+
# Raises an exception if called outside a transaction block.
|
476
|
+
def delete(key)
|
213
477
|
in_transaction_wr
|
214
|
-
@table.delete
|
478
|
+
@table.delete key
|
215
479
|
end
|
216
480
|
|
481
|
+
# Returns an array of the existing keys:
|
217
482
|
#
|
218
|
-
#
|
483
|
+
# example_store do |store|
|
484
|
+
# store.transaction do
|
485
|
+
# store.keys # => [:foo, :bar, :baz]
|
486
|
+
# end
|
487
|
+
# end
|
219
488
|
#
|
220
|
-
#
|
221
|
-
|
222
|
-
#
|
223
|
-
def roots
|
489
|
+
# Raises an exception if called outside a transaction block.
|
490
|
+
def keys
|
224
491
|
in_transaction
|
225
492
|
@table.keys
|
226
493
|
end
|
494
|
+
alias roots keys
|
495
|
+
|
496
|
+
# Returns +true+ if +key+ exists, +false+ otherwise:
|
227
497
|
#
|
228
|
-
#
|
229
|
-
#
|
230
|
-
#
|
231
|
-
#
|
498
|
+
# example_store do |store|
|
499
|
+
# store.transaction do
|
500
|
+
# store.key?(:foo) # => true
|
501
|
+
# end
|
502
|
+
# end
|
232
503
|
#
|
233
|
-
|
504
|
+
# Raises an exception if called outside a transaction block.
|
505
|
+
def key?(key)
|
234
506
|
in_transaction
|
235
|
-
@table.key?
|
507
|
+
@table.key? key
|
236
508
|
end
|
237
|
-
|
509
|
+
alias root? key?
|
510
|
+
|
511
|
+
# Returns the string file path used to create the store:
|
512
|
+
#
|
513
|
+
# store.path # => "flat.store"
|
514
|
+
#
|
238
515
|
def path
|
239
516
|
@filename
|
240
517
|
end
|
241
518
|
|
519
|
+
# Exits the current transaction block, committing any changes
|
520
|
+
# specified in the
|
521
|
+
# {transaction block}[rdoc-ref:PStore@The+Transaction+Block].
|
242
522
|
#
|
243
|
-
#
|
244
|
-
# store immediately.
|
245
|
-
#
|
246
|
-
# == Example:
|
247
|
-
#
|
248
|
-
# require "pstore"
|
249
|
-
#
|
250
|
-
# store = PStore.new("data_file.pstore")
|
251
|
-
# store.transaction do # begin transaction
|
252
|
-
# # load some data into the store...
|
253
|
-
# store[:one] = 1
|
254
|
-
# store[:two] = 2
|
255
|
-
#
|
256
|
-
# store.commit # end transaction here, committing changes
|
257
|
-
#
|
258
|
-
# store[:three] = 3 # this change is never reached
|
259
|
-
# end
|
260
|
-
#
|
261
|
-
# *WARNING*: This method is only valid in a PStore#transaction. It will
|
262
|
-
# raise PStore::Error if called at any other time.
|
263
|
-
#
|
523
|
+
# Raises an exception if called outside a transaction block.
|
264
524
|
def commit
|
265
525
|
in_transaction
|
266
526
|
@abort = false
|
267
527
|
throw :pstore_abort_transaction
|
268
528
|
end
|
529
|
+
|
530
|
+
# Exits the current transaction block, discarding any changes
|
531
|
+
# specified in the
|
532
|
+
# {transaction block}[rdoc-ref:PStore@The+Transaction+Block].
|
269
533
|
#
|
270
|
-
#
|
271
|
-
# store.
|
272
|
-
#
|
273
|
-
# == Example:
|
274
|
-
#
|
275
|
-
# require "pstore"
|
276
|
-
#
|
277
|
-
# store = PStore.new("data_file.pstore")
|
278
|
-
# store.transaction do # begin transaction
|
279
|
-
# store[:one] = 1 # this change is not applied, see below...
|
280
|
-
# store[:two] = 2 # this change is not applied, see below...
|
281
|
-
#
|
282
|
-
# store.abort # end transaction here, discard all changes
|
283
|
-
#
|
284
|
-
# store[:three] = 3 # this change is never reached
|
285
|
-
# end
|
286
|
-
#
|
287
|
-
# *WARNING*: This method is only valid in a PStore#transaction. It will
|
288
|
-
# raise PStore::Error if called at any other time.
|
289
|
-
#
|
534
|
+
# Raises an exception if called outside a transaction block.
|
290
535
|
def abort
|
291
536
|
in_transaction
|
292
537
|
@abort = true
|
293
538
|
throw :pstore_abort_transaction
|
294
539
|
end
|
295
540
|
|
541
|
+
# Opens a transaction block for the store.
|
542
|
+
# See {Transactions}[rdoc-ref:PStore@Transactions].
|
296
543
|
#
|
297
|
-
#
|
298
|
-
#
|
299
|
-
# file.
|
300
|
-
#
|
301
|
-
# At the end of the block, changes are committed to the data store
|
302
|
-
# automatically. You may exit the transaction early with a call to either
|
303
|
-
# PStore#commit or PStore#abort. See those methods for details about how
|
304
|
-
# changes are handled. Raising an uncaught Exception in the block is
|
305
|
-
# equivalent to calling PStore#abort.
|
306
|
-
#
|
307
|
-
# If _read_only_ is set to +true+, you will only be allowed to read from the
|
308
|
-
# data store during the transaction and any attempts to change the data will
|
309
|
-
# raise a PStore::Error.
|
544
|
+
# With argument +read_only+ as +false+, the block may both read from
|
545
|
+
# and write to the store.
|
310
546
|
#
|
311
|
-
#
|
547
|
+
# With argument +read_only+ as +true+, the block may not include calls
|
548
|
+
# to #transaction, #[]=, or #delete.
|
312
549
|
#
|
550
|
+
# Raises an exception if called within a transaction block.
|
313
551
|
def transaction(read_only = false) # :yields: pstore
|
314
552
|
value = nil
|
315
553
|
if !@thread_safe
|
data/pstore.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
name = File.basename(__FILE__, ".gemspec")
|
4
|
-
version = ["lib", Array.new(name.count("-")+1, "
|
4
|
+
version = ["lib", Array.new(name.count("-")+1, ".").join("/")].find do |dir|
|
5
5
|
break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
|
6
6
|
/^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
|
7
7
|
end rescue nil
|
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
# Specify which files should be added to the gem when it is released.
|
25
25
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
26
26
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
27
|
-
`git ls-files -z 2
|
27
|
+
`git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
28
28
|
end
|
29
29
|
spec.bindir = "exe"
|
30
30
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pstore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yukihiro Matsumoto
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-11-07 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Transactional File Storage for Ruby Objects
|
14
14
|
email:
|
@@ -17,6 +17,7 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
+
- ".github/dependabot.yml"
|
20
21
|
- ".github/workflows/test.yml"
|
21
22
|
- ".gitignore"
|
22
23
|
- Gemfile
|
@@ -49,7 +50,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
50
|
- !ruby/object:Gem::Version
|
50
51
|
version: '0'
|
51
52
|
requirements: []
|
52
|
-
rubygems_version: 3.
|
53
|
+
rubygems_version: 3.5.0.dev
|
53
54
|
signing_key:
|
54
55
|
specification_version: 4
|
55
56
|
summary: Transactional File Storage for Ruby Objects
|