pstore 0.1.0 → 0.1.2
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 +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