pstore 0.1.0 → 0.1.2
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 +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/test.yml +22 -0
- data/Rakefile +7 -0
- data/lib/pstore.rb +411 -167
- data/pstore.gemspec +14 -9
- metadata +11 -10
- data/.travis.yml +0 -7
- data/lib/pstore/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ca86b8693a69b67fa1864f055fb547d881d62979a047a7fa24e43aa0688cc01
|
4
|
+
data.tar.gz: b4af87cbe5e36edcf852c5465e686e477cb8ae301cd00c4dd8a38f56aa23f6e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f87e2b27466bcb3dfb2b910b1fb2fafcec06beef6d4aa49c9dad2acdf170c9d5db8631b9c08097b97334ec3797043c89b96b138c363805205407d0cc0b08590
|
7
|
+
data.tar.gz: 0efcfca9e94dae981be03cfd191265d10c348241b243c2fd7f7ae0fa07b63781ddcf70853dea07889294fd85794fbbc34f7b00b2de67de2b16950b882fe302f2
|
@@ -0,0 +1,22 @@
|
|
1
|
+
name: test
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
name: build (${{ matrix.ruby }} / ${{ matrix.os }})
|
8
|
+
strategy:
|
9
|
+
matrix:
|
10
|
+
ruby: [ 2.7, 2.6, 2.5, 2.4, head ]
|
11
|
+
os: [ ubuntu-latest, macos-latest ]
|
12
|
+
runs-on: ${{ matrix.os }}
|
13
|
+
steps:
|
14
|
+
- uses: actions/checkout@v3
|
15
|
+
- name: Set up Ruby
|
16
|
+
uses: ruby/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
ruby-version: ${{ matrix.ruby }}
|
19
|
+
- name: Install dependencies
|
20
|
+
run: bundle install
|
21
|
+
- name: Run test
|
22
|
+
run: rake test
|
data/Rakefile
CHANGED
@@ -7,4 +7,11 @@ Rake::TestTask.new(:test) do |t|
|
|
7
7
|
t.test_files = FileList["test/**/test_*.rb"]
|
8
8
|
end
|
9
9
|
|
10
|
+
task :sync_tool do
|
11
|
+
require 'fileutils'
|
12
|
+
FileUtils.cp "../ruby/tool/lib/core_assertions.rb", "./test/lib"
|
13
|
+
FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib"
|
14
|
+
FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib"
|
15
|
+
end
|
16
|
+
|
10
17
|
task :default => :test
|
data/lib/pstore.rb
CHANGED
@@ -10,88 +10,324 @@
|
|
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:
|
22
247
|
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
248
|
+
# example_store do |store|
|
249
|
+
# store.transaction do
|
250
|
+
# store.key?(:foo) # => true
|
251
|
+
# end
|
252
|
+
# end
|
26
253
|
#
|
27
|
-
#
|
254
|
+
# Use method #keys to retrieve keys:
|
255
|
+
#
|
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
|
329
|
+
VERSION = "0.1.2"
|
330
|
+
|
95
331
|
RDWR_ACCESS = {mode: IO::RDWR | IO::CREAT | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
|
96
332
|
RD_ACCESS = {mode: IO::RDONLY | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
|
97
333
|
WR_ACCESS = {mode: IO::WRONLY | IO::CREAT | IO::TRUNC | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
|
@@ -100,21 +336,38 @@ class PStore
|
|
100
336
|
class Error < StandardError
|
101
337
|
end
|
102
338
|
|
103
|
-
# Whether PStore should do its best to prevent file corruptions,
|
104
|
-
# unlikely
|
105
|
-
#
|
106
|
-
#
|
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.
|
107
354
|
#
|
108
|
-
# This flag only has effect on platforms on which file renames are atomic (e.g.
|
109
|
-
# all POSIX platforms: Linux, MacOS X, FreeBSD, etc). The default value is false.
|
110
355
|
attr_accessor :ultra_safe
|
111
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.
|
361
|
+
#
|
362
|
+
# path = 't.store'
|
363
|
+
# store = PStore.new(path)
|
112
364
|
#
|
113
|
-
#
|
114
|
-
#
|
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):
|
115
369
|
#
|
116
|
-
# PStore
|
117
|
-
# then it will become thread-safe at the cost of a minor performance hit.
|
370
|
+
# store = PStore.new(path, true)
|
118
371
|
#
|
119
372
|
def initialize(file, thread_safe = false)
|
120
373
|
dir = File::dirname(file)
|
@@ -145,169 +398,160 @@ class PStore
|
|
145
398
|
end
|
146
399
|
private :in_transaction, :in_transaction_wr
|
147
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:
|
148
404
|
#
|
149
|
-
#
|
150
|
-
#
|
405
|
+
# example_store do |store|
|
406
|
+
# store.transaction do
|
407
|
+
# store[:foo] # => 0
|
408
|
+
# store[:nope] # => nil
|
409
|
+
# end
|
410
|
+
# end
|
151
411
|
#
|
152
|
-
#
|
153
|
-
# raise PStore::Error if called at any other time.
|
412
|
+
# Returns +nil+ if there is no such key.
|
154
413
|
#
|
155
|
-
|
414
|
+
# See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
|
415
|
+
#
|
416
|
+
# Raises an exception if called outside a transaction block.
|
417
|
+
def [](key)
|
156
418
|
in_transaction
|
157
|
-
@table[
|
419
|
+
@table[key]
|
158
420
|
end
|
421
|
+
|
422
|
+
# Like #[], except that it accepts a default value for the store.
|
423
|
+
# If the +key+ does not exist:
|
159
424
|
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
# found in the data store, your _default_ will be returned instead. If you do
|
163
|
-
# not specify a default, PStore::Error will be raised if the object is not
|
164
|
-
# found.
|
425
|
+
# - Raises an exception if +default+ is +PStore::Error+.
|
426
|
+
# - Returns the value of +default+ otherwise:
|
165
427
|
#
|
166
|
-
#
|
167
|
-
#
|
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
|
168
434
|
#
|
169
|
-
|
435
|
+
# Raises an exception if called outside a transaction block.
|
436
|
+
def fetch(key, default=PStore::Error)
|
170
437
|
in_transaction
|
171
|
-
unless @table.key?
|
438
|
+
unless @table.key? key
|
172
439
|
if default == PStore::Error
|
173
|
-
raise PStore::Error, format("undefined
|
440
|
+
raise PStore::Error, format("undefined key `%s'", key)
|
174
441
|
else
|
175
442
|
return default
|
176
443
|
end
|
177
444
|
end
|
178
|
-
@table[
|
445
|
+
@table[key]
|
179
446
|
end
|
447
|
+
|
448
|
+
# Creates or replaces the value for the given +key+:
|
180
449
|
#
|
181
|
-
#
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
# require "pstore"
|
188
|
-
#
|
189
|
-
# store = PStore.new("data_file.pstore")
|
190
|
-
# store.transaction do # begin transaction
|
191
|
-
# # load some data into the store...
|
192
|
-
# store[:single_object] = "My data..."
|
193
|
-
# store[:obj_hierarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
|
194
|
-
# "James Gray" => ["erb.rb", "pstore.rb"] }
|
195
|
-
# end # commit changes to data store file
|
450
|
+
# example_store do |store|
|
451
|
+
# temp.transaction do
|
452
|
+
# temp[:bat] = 3
|
453
|
+
# end
|
454
|
+
# end
|
196
455
|
#
|
197
|
-
#
|
198
|
-
# be read-only. It will raise PStore::Error if called at any other time.
|
456
|
+
# See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
|
199
457
|
#
|
200
|
-
|
458
|
+
# Raises an exception if called outside a transaction block.
|
459
|
+
def []=(key, value)
|
201
460
|
in_transaction_wr
|
202
|
-
@table[
|
461
|
+
@table[key] = value
|
203
462
|
end
|
463
|
+
|
464
|
+
# Removes and returns the value at +key+ if it exists:
|
204
465
|
#
|
205
|
-
#
|
466
|
+
# example_store do |store|
|
467
|
+
# store.transaction do
|
468
|
+
# store[:bat] = 3
|
469
|
+
# store.delete(:bat)
|
470
|
+
# end
|
471
|
+
# end
|
206
472
|
#
|
207
|
-
#
|
208
|
-
# be read-only. It will raise PStore::Error if called at any other time.
|
473
|
+
# Returns +nil+ if there is no such key.
|
209
474
|
#
|
210
|
-
|
475
|
+
# Raises an exception if called outside a transaction block.
|
476
|
+
def delete(key)
|
211
477
|
in_transaction_wr
|
212
|
-
@table.delete
|
478
|
+
@table.delete key
|
213
479
|
end
|
214
480
|
|
481
|
+
# Returns an array of the existing keys:
|
215
482
|
#
|
216
|
-
#
|
483
|
+
# example_store do |store|
|
484
|
+
# store.transaction do
|
485
|
+
# store.keys # => [:foo, :bar, :baz]
|
486
|
+
# end
|
487
|
+
# end
|
217
488
|
#
|
218
|
-
#
|
219
|
-
# raise PStore::Error if called at any other time.
|
489
|
+
# Raises an exception if called outside a transaction block.
|
220
490
|
#
|
221
|
-
|
491
|
+
# PStore#roots is an alias for PStore#keys.
|
492
|
+
def keys
|
222
493
|
in_transaction
|
223
494
|
@table.keys
|
224
495
|
end
|
496
|
+
alias roots keys
|
497
|
+
|
498
|
+
# Returns +true+ if +key+ exists, +false+ otherwise:
|
225
499
|
#
|
226
|
-
#
|
500
|
+
# example_store do |store|
|
501
|
+
# store.transaction do
|
502
|
+
# store.key?(:foo) # => true
|
503
|
+
# end
|
504
|
+
# end
|
227
505
|
#
|
228
|
-
#
|
229
|
-
# raise PStore::Error if called at any other time.
|
506
|
+
# Raises an exception if called outside a transaction block.
|
230
507
|
#
|
231
|
-
|
508
|
+
# PStore#root? is an alias for PStore#key?.
|
509
|
+
def key?(key)
|
232
510
|
in_transaction
|
233
|
-
@table.key?
|
511
|
+
@table.key? key
|
234
512
|
end
|
235
|
-
|
513
|
+
alias root? key?
|
514
|
+
|
515
|
+
# Returns the string file path used to create the store:
|
516
|
+
#
|
517
|
+
# store.path # => "flat.store"
|
518
|
+
#
|
236
519
|
def path
|
237
520
|
@filename
|
238
521
|
end
|
239
522
|
|
523
|
+
# Exits the current transaction block, committing any changes
|
524
|
+
# specified in the transaction block.
|
525
|
+
# See {Committing or Aborting}[rdoc-ref:PStore@Committing+or+Aborting].
|
240
526
|
#
|
241
|
-
#
|
242
|
-
# store immediately.
|
243
|
-
#
|
244
|
-
# == Example:
|
245
|
-
#
|
246
|
-
# require "pstore"
|
247
|
-
#
|
248
|
-
# store = PStore.new("data_file.pstore")
|
249
|
-
# store.transaction do # begin transaction
|
250
|
-
# # load some data into the store...
|
251
|
-
# store[:one] = 1
|
252
|
-
# store[:two] = 2
|
253
|
-
#
|
254
|
-
# store.commit # end transaction here, committing changes
|
255
|
-
#
|
256
|
-
# store[:three] = 3 # this change is never reached
|
257
|
-
# end
|
258
|
-
#
|
259
|
-
# *WARNING*: This method is only valid in a PStore#transaction. It will
|
260
|
-
# raise PStore::Error if called at any other time.
|
261
|
-
#
|
527
|
+
# Raises an exception if called outside a transaction block.
|
262
528
|
def commit
|
263
529
|
in_transaction
|
264
530
|
@abort = false
|
265
531
|
throw :pstore_abort_transaction
|
266
532
|
end
|
533
|
+
|
534
|
+
# Exits the current transaction block, discarding any changes
|
535
|
+
# specified in the transaction block.
|
536
|
+
# See {Committing or Aborting}[rdoc-ref:PStore@Committing+or+Aborting].
|
267
537
|
#
|
268
|
-
#
|
269
|
-
# store.
|
270
|
-
#
|
271
|
-
# == Example:
|
272
|
-
#
|
273
|
-
# require "pstore"
|
274
|
-
#
|
275
|
-
# store = PStore.new("data_file.pstore")
|
276
|
-
# store.transaction do # begin transaction
|
277
|
-
# store[:one] = 1 # this change is not applied, see below...
|
278
|
-
# store[:two] = 2 # this change is not applied, see below...
|
279
|
-
#
|
280
|
-
# store.abort # end transaction here, discard all changes
|
281
|
-
#
|
282
|
-
# store[:three] = 3 # this change is never reached
|
283
|
-
# end
|
284
|
-
#
|
285
|
-
# *WARNING*: This method is only valid in a PStore#transaction. It will
|
286
|
-
# raise PStore::Error if called at any other time.
|
287
|
-
#
|
538
|
+
# Raises an exception if called outside a transaction block.
|
288
539
|
def abort
|
289
540
|
in_transaction
|
290
541
|
@abort = true
|
291
542
|
throw :pstore_abort_transaction
|
292
543
|
end
|
293
544
|
|
545
|
+
# Opens a transaction block for the store.
|
546
|
+
# See {Transactions}[rdoc-ref:PStore@Transactions].
|
294
547
|
#
|
295
|
-
#
|
296
|
-
#
|
297
|
-
# file.
|
298
|
-
#
|
299
|
-
# At the end of the block, changes are committed to the data store
|
300
|
-
# automatically. You may exit the transaction early with a call to either
|
301
|
-
# PStore#commit or PStore#abort. See those methods for details about how
|
302
|
-
# changes are handled. Raising an uncaught Exception in the block is
|
303
|
-
# equivalent to calling PStore#abort.
|
304
|
-
#
|
305
|
-
# If _read_only_ is set to +true+, you will only be allowed to read from the
|
306
|
-
# data store during the transaction and any attempts to change the data will
|
307
|
-
# raise a PStore::Error.
|
548
|
+
# With argument +read_only+ as +false+, the block may both read from
|
549
|
+
# and write to the store.
|
308
550
|
#
|
309
|
-
#
|
551
|
+
# With argument +read_only+ as +true+, the block may not include calls
|
552
|
+
# to #transaction, #[]=, or #delete.
|
310
553
|
#
|
554
|
+
# Raises an exception if called within a transaction block.
|
311
555
|
def transaction(read_only = false) # :yields: pstore
|
312
556
|
value = nil
|
313
557
|
if !@thread_safe
|
data/pstore.gemspec
CHANGED
@@ -1,17 +1,22 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
name = File.basename(__FILE__, ".gemspec")
|
4
|
+
version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir|
|
5
|
+
break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
|
6
|
+
/^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
|
7
|
+
end rescue nil
|
8
|
+
end
|
4
9
|
|
5
10
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name =
|
7
|
-
spec.version =
|
8
|
-
spec.authors = ["
|
9
|
-
spec.email = ["
|
11
|
+
spec.name = name
|
12
|
+
spec.version = version
|
13
|
+
spec.authors = ["Yukihiro Matsumoto"]
|
14
|
+
spec.email = ["matz@ruby-lang.org"]
|
10
15
|
|
11
16
|
spec.summary = %q{Transactional File Storage for Ruby Objects}
|
12
17
|
spec.description = spec.summary
|
13
18
|
spec.homepage = "https://github.com/ruby/pstore"
|
14
|
-
spec.
|
19
|
+
spec.licenses = ["Ruby", "BSD-2-Clause"]
|
15
20
|
|
16
21
|
spec.metadata["homepage_uri"] = spec.homepage
|
17
22
|
spec.metadata["source_code_uri"] = "https://github.com/ruby/pstore"
|
@@ -19,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
19
24
|
# Specify which files should be added to the gem when it is released.
|
20
25
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
26
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
22
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
27
|
+
`git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
28
|
end
|
24
29
|
spec.bindir = "exe"
|
25
30
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
metadata
CHANGED
@@ -1,24 +1,25 @@
|
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
autorequire:
|
7
|
+
- Yukihiro Matsumoto
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Transactional File Storage for Ruby Objects
|
14
14
|
email:
|
15
|
-
-
|
15
|
+
- matz@ruby-lang.org
|
16
16
|
executables: []
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
+
- ".github/dependabot.yml"
|
21
|
+
- ".github/workflows/test.yml"
|
20
22
|
- ".gitignore"
|
21
|
-
- ".travis.yml"
|
22
23
|
- Gemfile
|
23
24
|
- LICENSE.txt
|
24
25
|
- README.md
|
@@ -26,15 +27,15 @@ files:
|
|
26
27
|
- bin/console
|
27
28
|
- bin/setup
|
28
29
|
- lib/pstore.rb
|
29
|
-
- lib/pstore/version.rb
|
30
30
|
- pstore.gemspec
|
31
31
|
homepage: https://github.com/ruby/pstore
|
32
32
|
licenses:
|
33
|
+
- Ruby
|
33
34
|
- BSD-2-Clause
|
34
35
|
metadata:
|
35
36
|
homepage_uri: https://github.com/ruby/pstore
|
36
37
|
source_code_uri: https://github.com/ruby/pstore
|
37
|
-
post_install_message:
|
38
|
+
post_install_message:
|
38
39
|
rdoc_options: []
|
39
40
|
require_paths:
|
40
41
|
- lib
|
@@ -49,8 +50,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
50
|
- !ruby/object:Gem::Version
|
50
51
|
version: '0'
|
51
52
|
requirements: []
|
52
|
-
rubygems_version: 3.0.
|
53
|
-
signing_key:
|
53
|
+
rubygems_version: 3.4.0.dev
|
54
|
+
signing_key:
|
54
55
|
specification_version: 4
|
55
56
|
summary: Transactional File Storage for Ruby Objects
|
56
57
|
test_files: []
|
data/.travis.yml
DELETED
data/lib/pstore/version.rb
DELETED