2017-01-17 07:47:38 -05:00
|
|
|
# frozen_string_literal: true
|
2005-10-27 04:18:38 -04:00
|
|
|
# = PStore -- Transactional File Storage for Ruby Objects
|
1999-01-19 23:59:39 -05:00
|
|
|
#
|
2005-10-27 04:18:38 -04:00
|
|
|
# pstore.rb -
|
2005-11-01 08:04:35 -05:00
|
|
|
# originally by matz
|
2005-10-27 04:18:38 -04:00
|
|
|
# documentation by Kev Jackson and James Edward Gray II
|
2008-04-10 03:12:41 -04:00
|
|
|
# improved by Hongli Lai
|
1998-01-16 07:19:09 -05:00
|
|
|
#
|
2005-10-27 04:18:38 -04:00
|
|
|
# See PStore for documentation.
|
1998-01-16 07:19:09 -05:00
|
|
|
|
2016-09-28 10:14:21 -04:00
|
|
|
require "digest"
|
1998-01-16 07:19:09 -05:00
|
|
|
|
2022-06-28 17:25:38 -04:00
|
|
|
# \PStore implements a file based persistence mechanism based on a Hash.
|
|
|
|
# User code can store hierarchies of Ruby objects (values)
|
|
|
|
# into the data store by name (keys).
|
|
|
|
# An object hierarchy may be just a single object.
|
|
|
|
# User code may later read values back from the data store
|
|
|
|
# or even update data, as needed.
|
|
|
|
#
|
|
|
|
# The transactional behavior ensures that any changes succeed or fail together.
|
|
|
|
# This can be used to ensure that the data store is not left in a transitory state,
|
|
|
|
# where some values were updated but others were not.
|
|
|
|
#
|
|
|
|
# Behind the scenes, Ruby objects are stored to the data store file with Marshal.
|
|
|
|
# That carries the usual limitations. Proc objects cannot be marshalled,
|
|
|
|
# for example.
|
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# There are three important concepts here (details at the links):
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# - {Store}[rdoc-ref:PStore@The+Store]: a store is an instance of \PStore.
|
2022-07-06 18:04:47 -04:00
|
|
|
# - {Entries}[rdoc-ref:PStore@Entries]: the store is hash-like;
|
|
|
|
# each entry is the key for a stored object.
|
2022-06-28 17:25:38 -04:00
|
|
|
# - {Transactions}[rdoc-ref:PStore@Transactions]: each transaction is a collection
|
2022-06-27 14:16:58 -04:00
|
|
|
# of prospective changes to the store;
|
|
|
|
# a transaction is defined in the block given with a call
|
|
|
|
# to PStore#transaction.
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# == About the Examples
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# Examples on this page need a store that has known properties.
|
2022-06-30 15:00:06 -04:00
|
|
|
# They can get a new (and populated) store by calling thus:
|
|
|
|
#
|
|
|
|
# example_store do |store|
|
|
|
|
# # Example code using store goes here.
|
|
|
|
# end
|
|
|
|
#
|
2022-06-30 16:50:43 -04:00
|
|
|
# All we really need to know about +example_store+
|
2022-07-06 18:04:47 -04:00
|
|
|
# is that it yields a fresh store with a known population of entries;
|
2022-06-30 16:50:43 -04:00
|
|
|
# its implementation:
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# require 'pstore'
|
2022-06-30 12:49:32 -04:00
|
|
|
# require 'tempfile'
|
|
|
|
# # Yield a pristine store for use in examples.
|
|
|
|
# def example_store
|
2022-06-30 16:50:43 -04:00
|
|
|
# # Create the store in a temporary file.
|
|
|
|
# Tempfile.create do |file|
|
|
|
|
# store = PStore.new(file)
|
|
|
|
# # Populate the store.
|
|
|
|
# store.transaction do
|
|
|
|
# store[:foo] = 0
|
|
|
|
# store[:bar] = 1
|
|
|
|
# store[:baz] = 2
|
|
|
|
# end
|
|
|
|
# yield store
|
2022-06-30 12:49:32 -04:00
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# == The Store
|
|
|
|
#
|
|
|
|
# The contents of the store are maintained in a file whose path is specified
|
2022-06-29 11:08:18 -04:00
|
|
|
# when the store is created (see PStore.new).
|
|
|
|
# The objects are stored and retrieved using
|
2022-06-27 14:16:58 -04:00
|
|
|
# module Marshal, which means that certain objects cannot be added to the store;
|
|
|
|
# see {Marshal::dump}[https://docs.ruby-lang.org/en/master/Marshal.html#method-c-dump].
|
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# == Entries
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# A store may have any number of entries.
|
|
|
|
# Each entry has a key and a value, just as in a hash:
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
|
|
|
# - Key: as in a hash, the key can be (almost) any object;
|
2022-06-28 17:25:38 -04:00
|
|
|
# see {Hash Keys}[https://docs.ruby-lang.org/en/master/Hash.html#class-Hash-label-Hash+Keys].
|
2022-06-27 14:16:58 -04:00
|
|
|
# You may find it convenient to keep it simple by using only
|
|
|
|
# symbols or strings as keys.
|
2022-06-29 11:08:18 -04:00
|
|
|
# - Value: the value may be any object that can be marshalled by \Marshal
|
2022-06-28 17:25:38 -04:00
|
|
|
# (see {Marshal::dump}[https://docs.ruby-lang.org/en/master/Marshal.html#method-c-dump])
|
|
|
|
# and in fact may be a collection
|
2022-06-27 14:16:58 -04:00
|
|
|
# (e.g., an array, a hash, a set, a range, etc).
|
2022-06-28 17:25:38 -04:00
|
|
|
# That collection may in turn contain nested objects,
|
|
|
|
# including collections, to any depth;
|
2022-06-29 11:08:18 -04:00
|
|
|
# those objects must also be \Marshal-able.
|
2022-07-06 18:04:47 -04:00
|
|
|
# See {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
|
|
|
# == Transactions
|
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# === The Transaction Block
|
|
|
|
#
|
|
|
|
# The block given with a call to method #transaction#
|
|
|
|
# contains a _transaction_,
|
|
|
|
# which consists of calls to \PStore methods that
|
|
|
|
# read from or write to the store
|
|
|
|
# (that is, all \PStore methods except #transaction itself,
|
|
|
|
# #path, and Pstore.new):
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
2022-07-06 18:04:47 -04:00
|
|
|
# store.keys # => [:foo, :bar, :baz]
|
2022-06-30 12:49:32 -04:00
|
|
|
# store[:bat] = 3
|
2022-07-06 18:04:47 -04:00
|
|
|
# store.keys # => [:foo, :bar, :baz, :bat]
|
2022-06-30 12:49:32 -04:00
|
|
|
# end
|
2022-06-29 11:08:18 -04:00
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# Execution of the transaction is deferred until the block exits,
|
|
|
|
# and is executed _atomically_ (all-or-nothing):
|
|
|
|
# either all transaction calls are executed, or none are.
|
2022-06-29 11:08:18 -04:00
|
|
|
# This maintains the integrity of the store.
|
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# Other code in the block (including even calls to #path and PStore.new)
|
2022-06-30 17:16:25 -04:00
|
|
|
# is executed immediately, not deferred.
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# The transaction block:
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# - May not contain a nested call to #transaction.
|
|
|
|
# - Is the only context where methods that read from or write to
|
|
|
|
# the store are allowed.
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-29 11:08:18 -04:00
|
|
|
# As seen above, changes in a transaction are made automatically
|
|
|
|
# when the block exits.
|
2022-06-27 14:16:58 -04:00
|
|
|
# The block may be exited early by calling method #commit or #abort.
|
|
|
|
#
|
|
|
|
# - Method #commit triggers the update to the store and exits the block:
|
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
2022-07-06 18:04:47 -04:00
|
|
|
# store.keys # => [:foo, :bar, :baz]
|
2022-06-30 12:49:32 -04:00
|
|
|
# store[:bat] = 3
|
|
|
|
# store.commit
|
|
|
|
# fail 'Cannot get here'
|
|
|
|
# end
|
|
|
|
# store.transaction do
|
|
|
|
# # Update was completed.
|
2022-07-06 18:04:47 -04:00
|
|
|
# store.keys # => [:foo, :bar, :baz, :bat]
|
2022-06-30 12:49:32 -04:00
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# - Method #abort discards the update to the store and exits the block:
|
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
2022-07-06 18:04:47 -04:00
|
|
|
# store.keys # => [:foo, :bar, :baz]
|
2022-06-30 12:49:32 -04:00
|
|
|
# store[:bat] = 3
|
|
|
|
# store.abort
|
|
|
|
# fail 'Cannot get here'
|
|
|
|
# end
|
|
|
|
# store.transaction do
|
|
|
|
# # Update was not completed.
|
2022-07-06 18:04:47 -04:00
|
|
|
# store.keys # => [:foo, :bar, :baz]
|
2022-06-30 12:49:32 -04:00
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# === Read-Only Transactions
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# By default, a transaction allows both reading from and writing to
|
|
|
|
# the store:
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# store.transaction do
|
|
|
|
# # Read-write transaction.
|
|
|
|
# # Any code except a call to #transaction is allowed here.
|
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# If argument +read_only+ is passed as +true+,
|
|
|
|
# only reading is allowed:
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# store.transaction(true) do
|
|
|
|
# # Read-only transaction:
|
|
|
|
# # Calls to #transaction, #[]=, and #delete are not allowed here.
|
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# == Hierarchical Values
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# The value for an entry may be a simple object (as seen above).
|
2022-06-27 14:16:58 -04:00
|
|
|
# It may also be a hierarchy of objects nested to any depth:
|
|
|
|
#
|
|
|
|
# deep_store = PStore.new('deep.store')
|
|
|
|
# deep_store.transaction do
|
|
|
|
# array_of_hashes = [{}, {}, {}]
|
|
|
|
# deep_store[:array_of_hashes] = array_of_hashes
|
|
|
|
# deep_store[:array_of_hashes] # => [{}, {}, {}]
|
|
|
|
# hash_of_arrays = {foo: [], bar: [], baz: []}
|
|
|
|
# deep_store[:hash_of_arrays] = hash_of_arrays
|
|
|
|
# deep_store[:hash_of_arrays] # => {:foo=>[], :bar=>[], :baz=>[]}
|
|
|
|
# deep_store[:hash_of_arrays][:foo].push(:bat)
|
|
|
|
# deep_store[:hash_of_arrays] # => {:foo=>[:bat], :bar=>[], :baz=>[]}
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# And recall that you can use
|
|
|
|
# {dig methods}[https://docs.ruby-lang.org/en/master/dig_methods_rdoc.html]
|
|
|
|
# in a returned hierarchy of objects.
|
|
|
|
#
|
|
|
|
# == Working with the Store
|
|
|
|
#
|
|
|
|
# === Creating a Store
|
|
|
|
#
|
|
|
|
# Use method PStore.new to create a store.
|
|
|
|
# The new store creates or opens its containing file:
|
|
|
|
#
|
|
|
|
# store = PStore.new('t.store')
|
|
|
|
#
|
|
|
|
# === Modifying the Store
|
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# Use method #[]= to update or create an entry:
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
|
|
|
# store[:foo] = 1 # Update.
|
|
|
|
# store[:bam] = 1 # Create.
|
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# Use method #delete to remove an entry:
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
|
|
|
# store.delete(:foo)
|
|
|
|
# store[:foo] # => nil
|
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# === Retrieving Values
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
|
|
|
# Use method #fetch (allows default) or #[] (defaults to +nil+)
|
2022-07-06 18:04:47 -04:00
|
|
|
# to retrieve an entry:
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
|
|
|
# store[:foo] # => 0
|
|
|
|
# store[:nope] # => nil
|
|
|
|
# store.fetch(:baz) # => 2
|
|
|
|
# store.fetch(:nope, nil) # => nil
|
|
|
|
# store.fetch(:nope) # Raises exception.
|
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# === Querying the Store
|
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# Use method #key? to determine whether a given key exists:
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
2022-07-06 18:04:47 -04:00
|
|
|
# store.key?(:foo) # => true
|
2022-06-30 12:49:32 -04:00
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# Use method #keys to retrieve keys:
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
2022-07-06 18:04:47 -04:00
|
|
|
# store.keys # => [:foo, :bar, :baz]
|
2022-06-30 12:49:32 -04:00
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
|
|
|
#
|
2022-06-29 11:08:18 -04:00
|
|
|
# Use method #path to retrieve the path to the store's underlying file;
|
|
|
|
# this method may be called from outside a transaction block:
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# store = PStore.new('t.store')
|
|
|
|
# store.path # => "t.store"
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
|
|
|
# == Transaction Safety
|
|
|
|
#
|
|
|
|
# For transaction safety, see:
|
|
|
|
#
|
|
|
|
# - Optional argument +thread_safe+ at method PStore.new.
|
|
|
|
# - Attribute #ultra_safe.
|
|
|
|
#
|
|
|
|
# Needless to say, if you're storing valuable data with \PStore, then you should
|
|
|
|
# backup the \PStore file from time to time.
|
|
|
|
#
|
|
|
|
# == An Example Store
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2005-10-27 04:18:38 -04:00
|
|
|
# require "pstore"
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# # A mock wiki object.
|
2005-10-27 04:18:38 -04:00
|
|
|
# class WikiPage
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
|
|
|
# attr_reader :page_name
|
|
|
|
#
|
|
|
|
# def initialize(page_name, author, contents)
|
2005-10-27 04:18:38 -04:00
|
|
|
# @page_name = page_name
|
|
|
|
# @revisions = Array.new
|
|
|
|
# add_revision(author, contents)
|
|
|
|
# end
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# def add_revision(author, contents)
|
|
|
|
# @revisions << {created: Time.now,
|
|
|
|
# author: author,
|
|
|
|
# contents: contents}
|
2005-10-27 04:18:38 -04:00
|
|
|
# end
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2005-10-27 04:18:38 -04:00
|
|
|
# def wiki_page_references
|
|
|
|
# [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/)
|
|
|
|
# end
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2005-10-27 04:18:38 -04:00
|
|
|
# end
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# # Create a new wiki page.
|
|
|
|
# home_page = WikiPage.new("HomePage", "James Edward Gray II",
|
|
|
|
# "A page about the JoysOfDocumentation..." )
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2005-10-27 04:18:38 -04:00
|
|
|
# wiki = PStore.new("wiki_pages.pstore")
|
2022-06-27 14:16:58 -04:00
|
|
|
# # Update page data and the index together, or not at all.
|
|
|
|
# wiki.transaction do
|
|
|
|
# # Store page.
|
2005-10-27 04:18:38 -04:00
|
|
|
# wiki[home_page.page_name] = home_page
|
2022-06-27 14:16:58 -04:00
|
|
|
# # Create page index.
|
2005-10-27 04:18:38 -04:00
|
|
|
# wiki[:wiki_index] ||= Array.new
|
2022-06-27 14:16:58 -04:00
|
|
|
# # Update wiki index.
|
2005-10-27 04:18:38 -04:00
|
|
|
# wiki[:wiki_index].push(*home_page.wiki_page_references)
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# # Read wiki data, setting argument read_only to true.
|
|
|
|
# wiki.transaction(true) do
|
2022-07-06 18:04:47 -04:00
|
|
|
# wiki.keys.each do |key|
|
|
|
|
# puts key
|
|
|
|
# puts wiki[key]
|
2005-10-27 04:18:38 -04:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
1998-01-16 07:19:09 -05:00
|
|
|
class PStore
|
2020-12-22 07:43:30 -05:00
|
|
|
VERSION = "0.1.1"
|
2020-07-28 11:31:52 -04:00
|
|
|
|
2011-09-13 02:02:59 -04:00
|
|
|
RDWR_ACCESS = {mode: IO::RDWR | IO::CREAT | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
|
|
|
|
RD_ACCESS = {mode: IO::RDONLY | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
|
|
|
|
WR_ACCESS = {mode: IO::WRONLY | IO::CREAT | IO::TRUNC | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
|
2006-09-08 15:56:37 -04:00
|
|
|
|
2005-10-27 04:18:38 -04:00
|
|
|
# The error type thrown by all PStore methods.
|
1999-01-19 23:59:39 -05:00
|
|
|
class Error < StandardError
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2022-06-27 14:16:58 -04:00
|
|
|
# Whether \PStore should do its best to prevent file corruptions,
|
|
|
|
# even when an unlikely error (such as memory-error or filesystem error) occurs:
|
|
|
|
#
|
|
|
|
# - +true+: changes are posted by creating a temporary file,
|
|
|
|
# writing the updated data to it, then renaming the file to the given #path.
|
|
|
|
# File integrity is maintained.
|
|
|
|
# Note: has effect only if the filesystem has atomic file rename
|
|
|
|
# (as do POSIX platforms Linux, MacOS, FreeBSD and others).
|
|
|
|
#
|
|
|
|
# - +false+ (the default): changes are posted by rewinding the open file
|
|
|
|
# and writing the updated data.
|
|
|
|
# File integrity is maintained if the filesystem raises
|
|
|
|
# no unexpected I/O error;
|
|
|
|
# if such an error occurs during a write to the store,
|
|
|
|
# the file may become corrupted.
|
2008-04-10 03:12:41 -04:00
|
|
|
#
|
|
|
|
attr_accessor :ultra_safe
|
1998-01-16 07:19:09 -05:00
|
|
|
|
2022-06-27 14:16:58 -04:00
|
|
|
# Returns a new \PStore object.
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# Argument +file+ is the path to the file in which objects are to be stored;
|
2022-06-29 11:08:18 -04:00
|
|
|
# if the file exists, it should be one that was written by \PStore.
|
2008-04-10 03:12:41 -04:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# path = 't.store'
|
|
|
|
# store = PStore.new(path)
|
|
|
|
#
|
|
|
|
# A \PStore object is
|
2022-06-30 12:49:32 -04:00
|
|
|
# {reentrant}[https://en.wikipedia.org/wiki/Reentrancy_(computing)].
|
|
|
|
# If argument +thread_safe+ is given as +true+,
|
2022-06-27 14:16:58 -04:00
|
|
|
# the object is also thread-safe (at the cost of a small performance penalty):
|
|
|
|
#
|
|
|
|
# store = PStore.new(path, true)
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2008-04-10 03:12:41 -04:00
|
|
|
def initialize(file, thread_safe = false)
|
1998-01-16 07:19:09 -05:00
|
|
|
dir = File::dirname(file)
|
|
|
|
unless File::directory? dir
|
|
|
|
raise PStore::Error, format("directory %s does not exist", dir)
|
|
|
|
end
|
|
|
|
if File::exist? file and not File::readable? file
|
|
|
|
raise PStore::Error, format("file %s not readable", file)
|
|
|
|
end
|
|
|
|
@filename = file
|
|
|
|
@abort = false
|
2008-04-10 03:12:41 -04:00
|
|
|
@ultra_safe = false
|
2010-02-15 09:09:19 -05:00
|
|
|
@thread_safe = thread_safe
|
2016-08-30 02:22:30 -04:00
|
|
|
@lock = Thread::Mutex.new
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
|
|
|
|
2005-10-27 04:18:38 -04:00
|
|
|
# Raises PStore::Error if the calling code is not in a PStore#transaction.
|
1998-01-16 07:19:09 -05:00
|
|
|
def in_transaction
|
2011-03-07 09:03:41 -05:00
|
|
|
raise PStore::Error, "not in transaction" unless @lock.locked?
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2005-10-27 04:18:38 -04:00
|
|
|
# Raises PStore::Error if the calling code is not in a PStore#transaction or
|
|
|
|
# if the code is in a read-only PStore#transaction.
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2011-12-20 09:57:29 -05:00
|
|
|
def in_transaction_wr
|
|
|
|
in_transaction
|
2004-02-17 10:26:03 -05:00
|
|
|
raise PStore::Error, "in read-only transaction" if @rdonly
|
|
|
|
end
|
|
|
|
private :in_transaction, :in_transaction_wr
|
1998-01-16 07:19:09 -05:00
|
|
|
|
2022-07-06 18:04:47 -04:00
|
|
|
# Returns the value for the given +key+ if the key exists.
|
2022-06-27 14:16:58 -04:00
|
|
|
# +nil+ otherwise;
|
|
|
|
# if not +nil+, the returned value is an object or a hierarchy of objects:
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
|
|
|
# store[:foo] # => 0
|
|
|
|
# store[:nope] # => nil
|
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# Returns +nil+ if there is no such key.
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
|
2022-06-27 14:16:58 -04:00
|
|
|
#
|
|
|
|
# Raises an exception if called outside a transaction block.
|
2022-06-29 12:07:50 -04:00
|
|
|
def [](key)
|
1998-01-16 07:19:09 -05:00
|
|
|
in_transaction
|
2022-06-29 12:07:50 -04:00
|
|
|
@table[key]
|
2002-07-11 04:22:18 -04:00
|
|
|
end
|
2022-06-27 14:16:58 -04:00
|
|
|
|
|
|
|
# Like #[], except that it accepts a default value for the store.
|
2022-07-06 18:04:47 -04:00
|
|
|
# If the +key+ does not exist:
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# - Raises an exception if +default+ is +PStore::Error+.
|
|
|
|
# - Returns the value of +default+ otherwise:
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
|
|
|
# store.fetch(:nope, nil) # => nil
|
|
|
|
# store.fetch(:nope) # Raises an exception.
|
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# Raises an exception if called outside a transaction block.
|
2022-06-29 12:07:50 -04:00
|
|
|
def fetch(key, default=PStore::Error)
|
2006-02-05 00:28:11 -05:00
|
|
|
in_transaction
|
2022-06-29 12:07:50 -04:00
|
|
|
unless @table.key? key
|
2008-04-10 03:12:41 -04:00
|
|
|
if default == PStore::Error
|
2022-07-06 18:04:47 -04:00
|
|
|
raise PStore::Error, format("undefined key `%s'", key)
|
2002-07-11 04:22:18 -04:00
|
|
|
else
|
2008-04-10 03:12:41 -04:00
|
|
|
return default
|
2002-07-11 04:22:18 -04:00
|
|
|
end
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
2022-06-29 12:07:50 -04:00
|
|
|
@table[key]
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
2022-06-27 14:16:58 -04:00
|
|
|
|
2022-07-06 18:04:47 -04:00
|
|
|
# Creates or replaces the value for the given +key+:
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# temp.transaction do
|
|
|
|
# temp[:bat] = 3
|
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# Raises an exception if called outside a transaction block.
|
2022-06-29 12:07:50 -04:00
|
|
|
def []=(key, value)
|
2011-12-20 09:57:29 -05:00
|
|
|
in_transaction_wr
|
2022-06-29 12:07:50 -04:00
|
|
|
@table[key] = value
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
2022-06-27 14:16:58 -04:00
|
|
|
|
2022-06-28 17:25:38 -04:00
|
|
|
# Removes and returns the value at +key+ if it exists:
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
|
|
|
# store[:bat] = 3
|
|
|
|
# store.delete(:bat)
|
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-07-06 18:04:47 -04:00
|
|
|
# Returns +nil+ if there is no such key.
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# Raises an exception if called outside a transaction block.
|
2022-06-29 12:07:50 -04:00
|
|
|
def delete(key)
|
2011-12-20 09:57:29 -05:00
|
|
|
in_transaction_wr
|
2022-06-29 12:07:50 -04:00
|
|
|
@table.delete key
|
2000-03-05 23:15:42 -05:00
|
|
|
end
|
1998-01-16 07:19:09 -05:00
|
|
|
|
2022-07-06 18:04:47 -04:00
|
|
|
# Returns an array of the existing keys:
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
2022-07-06 18:04:47 -04:00
|
|
|
# store.keys # => [:foo, :bar, :baz]
|
2022-06-30 12:49:32 -04:00
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# Raises an exception if called outside a transaction block.
|
2022-07-06 18:04:47 -04:00
|
|
|
#
|
|
|
|
# PStore#roots is an alias for PStore#keys.
|
|
|
|
def keys
|
1998-01-16 07:19:09 -05:00
|
|
|
in_transaction
|
|
|
|
@table.keys
|
|
|
|
end
|
2022-07-06 18:04:47 -04:00
|
|
|
alias roots keys
|
2022-06-27 14:16:58 -04:00
|
|
|
|
2022-07-06 18:04:47 -04:00
|
|
|
# Returns +true+ if +key+ exists, +false+ otherwise:
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-06-30 12:49:32 -04:00
|
|
|
# example_store do |store|
|
|
|
|
# store.transaction do
|
2022-07-06 18:04:47 -04:00
|
|
|
# store.key?(:foo) # => true
|
2022-06-30 12:49:32 -04:00
|
|
|
# end
|
2022-06-27 14:16:58 -04:00
|
|
|
# end
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# Raises an exception if called outside a transaction block.
|
2022-07-06 18:04:47 -04:00
|
|
|
#
|
|
|
|
# PStore#root? is an alias for PStore#key?.
|
|
|
|
def key?(key)
|
1998-01-16 07:19:09 -05:00
|
|
|
in_transaction
|
2022-06-29 12:07:50 -04:00
|
|
|
@table.key? key
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
2022-07-06 18:04:47 -04:00
|
|
|
alias root? key?
|
2022-06-27 14:16:58 -04:00
|
|
|
|
|
|
|
# Returns the string file path used to create the store:
|
|
|
|
#
|
|
|
|
# store.path # => "flat.store"
|
|
|
|
#
|
1998-01-16 07:19:09 -05:00
|
|
|
def path
|
|
|
|
@filename
|
|
|
|
end
|
|
|
|
|
2022-06-28 17:25:38 -04:00
|
|
|
# Exits the current transaction block, committing any changes
|
|
|
|
# specified in the transaction block.
|
2022-06-27 14:16:58 -04:00
|
|
|
# See {Committing or Aborting}[rdoc-ref:PStore@Committing+or+Aborting].
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# Raises an exception if called outside a transaction block.
|
1998-01-16 07:19:09 -05:00
|
|
|
def commit
|
2001-05-06 11:06:00 -04:00
|
|
|
in_transaction
|
1998-01-16 07:19:09 -05:00
|
|
|
@abort = false
|
|
|
|
throw :pstore_abort_transaction
|
|
|
|
end
|
2022-06-27 14:16:58 -04:00
|
|
|
|
2022-06-28 17:25:38 -04:00
|
|
|
# Exits the current transaction block, discarding any changes
|
|
|
|
# specified in the transaction block.
|
2022-06-27 14:16:58 -04:00
|
|
|
# See {Committing or Aborting}[rdoc-ref:PStore@Committing+or+Aborting].
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# Raises an exception if called outside a transaction block.
|
1998-01-16 07:19:09 -05:00
|
|
|
def abort
|
2001-05-06 11:06:00 -04:00
|
|
|
in_transaction
|
1998-01-16 07:19:09 -05:00
|
|
|
@abort = true
|
|
|
|
throw :pstore_abort_transaction
|
|
|
|
end
|
|
|
|
|
2022-06-28 17:25:38 -04:00
|
|
|
# Opens a transaction block for the store.
|
2022-06-27 14:16:58 -04:00
|
|
|
# See {Transactions}[rdoc-ref:PStore@Transactions].
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-28 17:25:38 -04:00
|
|
|
# With argument +read_only+ as +false+, the block may both read from
|
|
|
|
# and write to the store.
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# With argument +read_only+ as +true+, the block may not include calls
|
|
|
|
# to #transaction, #[]=, or #delete.
|
2005-10-27 04:18:38 -04:00
|
|
|
#
|
2022-06-27 14:16:58 -04:00
|
|
|
# Raises an exception if called within a transaction block.
|
* ext/pathname/lib/pathname.rb, ext/tk/lib/multi-tk.rb,
ext/tk/sample/demos-en/widget, lib/benchmark.rb, lib/irb/cmd/fork.rb,
lib/mkmf.rb, lib/net/ftp.rb, lib/net/smtp.rb, lib/open3.rb,
lib/pstore.rb, lib/rexml/element.rb, lib/rexml/light/node.rb,
lib/rinda/tuplespace.rb, lib/rss/maker/base.rb,
lib/rss/maker/entry.rb, lib/scanf.rb, lib/set.rb, lib/shell.rb,
lib/shell/command-processor.rb, lib/shell/process-controller.rb,
lib/shell/system-command.rb, lib/uri/common.rb: remove unused block
arguments to avoid creating Proc objects.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@33638 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2011-11-05 03:37:47 -04:00
|
|
|
def transaction(read_only = false) # :yields: pstore
|
2008-04-10 03:12:41 -04:00
|
|
|
value = nil
|
2012-11-10 23:23:04 -05:00
|
|
|
if !@thread_safe
|
|
|
|
raise PStore::Error, "nested transaction" unless @lock.try_lock
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
@lock.lock
|
|
|
|
rescue ThreadError
|
|
|
|
raise PStore::Error, "nested transaction"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
begin
|
2004-02-17 10:26:03 -05:00
|
|
|
@rdonly = read_only
|
2008-04-10 03:12:41 -04:00
|
|
|
@abort = false
|
|
|
|
file = open_and_lock_file(@filename, read_only)
|
|
|
|
if file
|
|
|
|
begin
|
|
|
|
@table, checksum, original_data_size = load_data(file, read_only)
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2008-04-10 03:12:41 -04:00
|
|
|
catch(:pstore_abort_transaction) do
|
|
|
|
value = yield(self)
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2008-04-10 03:12:41 -04:00
|
|
|
if !@abort && !read_only
|
|
|
|
save_data(checksum, original_data_size, file)
|
|
|
|
end
|
|
|
|
ensure
|
2016-11-21 18:05:41 -05:00
|
|
|
file.close
|
2008-04-10 03:12:41 -04:00
|
|
|
end
|
2004-02-17 10:26:03 -05:00
|
|
|
else
|
2008-04-10 03:12:41 -04:00
|
|
|
# This can only occur if read_only == true.
|
|
|
|
@table = {}
|
|
|
|
catch(:pstore_abort_transaction) do
|
|
|
|
value = yield(self)
|
|
|
|
end
|
|
|
|
end
|
2012-11-10 23:23:04 -05:00
|
|
|
ensure
|
|
|
|
@lock.unlock
|
2008-04-10 03:12:41 -04:00
|
|
|
end
|
2008-07-10 09:06:03 -04:00
|
|
|
value
|
2008-04-10 03:12:41 -04:00
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2008-04-10 03:12:41 -04:00
|
|
|
private
|
|
|
|
# Constant for relieving Ruby's garbage collector.
|
2016-09-28 10:14:21 -04:00
|
|
|
CHECKSUM_ALGO = %w[SHA512 SHA384 SHA256 SHA1 RMD160 MD5].each do |algo|
|
|
|
|
begin
|
|
|
|
break Digest(algo)
|
|
|
|
rescue LoadError
|
|
|
|
end
|
|
|
|
end
|
2008-04-10 03:12:41 -04:00
|
|
|
EMPTY_STRING = ""
|
|
|
|
EMPTY_MARSHAL_DATA = Marshal.dump({})
|
2016-06-28 09:55:02 -04:00
|
|
|
EMPTY_MARSHAL_CHECKSUM = CHECKSUM_ALGO.digest(EMPTY_MARSHAL_DATA)
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2008-04-10 03:12:41 -04:00
|
|
|
#
|
|
|
|
# Open the specified filename (either in read-only mode or in
|
|
|
|
# read-write mode) and lock it for reading or writing.
|
|
|
|
#
|
|
|
|
# The opened File object will be returned. If _read_only_ is true,
|
|
|
|
# and the file does not exist, then nil will be returned.
|
|
|
|
#
|
|
|
|
# All exceptions are propagated.
|
|
|
|
#
|
|
|
|
def open_and_lock_file(filename, read_only)
|
|
|
|
if read_only
|
|
|
|
begin
|
Make rb_scan_args handle keywords more similar to Ruby methods (#2460)
Cfuncs that use rb_scan_args with the : entry suffer similar keyword
argument separation issues that Ruby methods suffer if the cfuncs
accept optional or variable arguments.
This makes the following changes to : handling.
* Treats as **kw, prompting keyword argument separation warnings
if called with a positional hash.
* Do not look for an option hash if empty keywords are provided.
For backwards compatibility, treat an empty keyword splat as a empty
mandatory positional hash argument, but emit a a warning, as this
behavior will be removed in Ruby 3. The argument number check
needs to be moved lower so it can correctly handle an empty
positional argument being added.
* If the last argument is nil and it is necessary to treat it as an option
hash in order to make sure all arguments are processed, continue to
treat the last argument as the option hash. Emit a warning in this case,
as this behavior will be removed in Ruby 3.
* If splitting the keyword hash into two hashes, issue a warning, as we
will not be splitting hashes in Ruby 3.
* If the keyword argument is required to fill a mandatory positional
argument, continue to do so, but emit a warning as this behavior will
be going away in Ruby 3.
* If keyword arguments are provided and the last argument is not a hash,
that indicates something wrong. This can happen if a cfunc is calling
rb_scan_args multiple times, and providing arguments that were not
passed to it from Ruby. Callers need to switch to the new
rb_scan_args_kw function, which allows passing of whether keywords
were provided.
This commit fixes all warnings caused by the changes above.
It switches some function calls to *_kw versions with appropriate
kw_splat flags. If delegating arguments, RB_PASS_CALLED_KEYWORDS
is used. If creating new arguments, RB_PASS_KEYWORDS is used if
the last argument is a hash to be treated as keywords.
In open_key_args in io.c, use rb_scan_args_kw.
In this case, the arguments provided come from another C
function, not Ruby. The last argument may or may not be a hash,
so we can't set keyword argument mode. However, if it is a
hash, we don't want to warn when treating it as keywords.
In Ruby files, make sure to appropriately use keyword splats
or literal keywords when calling Cfuncs that now issue keyword
argument separation warnings through rb_scan_args. Also, make
sure not to pass nil in place of an option hash.
Work around Kernel#warn warnings due to problems in the Rubygems
override of the method. There is an open pull request to fix
these issues in Rubygems, but part of the Rubygems tests for
their override fail on ruby-head due to rb_scan_args not
recognizing empty keyword splats, which this commit fixes.
Implementation wise, adding rb_scan_args_kw is kind of a pain,
because rb_scan_args takes a variable number of arguments.
In order to not duplicate all the code, the function internals need
to be split into two functions taking a va_list, and to avoid passing
in a ton of arguments, a single struct argument is used to handle
the variables previously local to the function.
2019-09-25 14:18:49 -04:00
|
|
|
file = File.new(filename, **RD_ACCESS)
|
* array.c: replace rb_protect_inspect() and rb_inspecting_p() by
rb_exec_recursive() in eval.c.
* eval.c (rb_exec_recursive): new function.
* array.c (rb_ary_join): use rb_exec_recursive().
* array.c (rb_ary_inspect, rb_ary_hash): ditto.
* file.c (rb_file_join): ditto.
* hash.c (rb_hash_inspect, rb_hash_to_s, rb_hash_hash): ditto.
* io.c (rb_io_puts): ditto.
* object.c (rb_obj_inspect): ditto
* struct.c (rb_struct_inspect): ditto.
* lib/set.rb (SortedSet::setup): a hack to shut up warning.
[ruby-talk:132866]
* lib/time.rb (Time::strptime): add new function. inspired by
[ruby-talk:132815].
* lib/parsedate.rb (ParseDate::strptime): ditto.
* regparse.c: move st_*_strend() functions from st.c. fixed some
potential memory leaks.
* exception error messages updated. [ruby-core:04497]
* ext/socket/socket.c (Init_socket): add bunch of Socket
constants. Patch from Sam Roberts <sroberts@uniserve.com>.
[ruby-core:04409]
* array.c (rb_ary_s_create): no need for negative argc check.
[ruby-core:04463]
* array.c (rb_ary_unshift_m): ditto.
* lib/xmlrpc/parser.rb (XMLRPC::FaultException): make it subclass
of StandardError class, not Exception class. [ruby-core:04429]
* parse.y (fcall_gen): lvar(arg) will be evaluated as
lvar.call(arg) when lvar is a defined local variable. [new]
* object.c (rb_class_initialize): call inherited method before
calling initializing block.
* eval.c (rb_thread_start_1): initialize newly pushed frame.
* lib/open3.rb (Open3::popen3): $? should not be EXIT_FAILURE.
fixed: [ruby-core:04444]
* eval.c (is_defined): NODE_IASGN is an assignment.
* ext/readline/readline.c (Readline.readline): use rl_outstream
and rl_instream. [ruby-dev:25699]
* ext/etc/etc.c (Init_etc): sGroup needs HAVE_ST_GR_PASSWD check
[ruby-dev:25675]
* misc/ruby-mode.el: [ruby-core:04415]
* lib/rdoc/generators/html_generator.rb: [ruby-core:04412]
* lib/rdoc/generators/ri_generator.rb: ditto.
* struct.c (make_struct): fixed: [ruby-core:04402]
* ext/curses/curses.c (window_color_set): [ruby-core:04393]
* ext/socket/socket.c (Init_socket): SO_REUSEPORT added.
[ruby-talk:130092]
* object.c: [ruby-doc:818]
* parse.y (open_args): fix too verbose warnings for the space
before argument parentheses. [ruby-dev:25492]
* parse.y (parser_yylex): ditto.
* parse.y (parser_yylex): the first expression in the parentheses
should not be a command. [ruby-dev:25492]
* lib/irb/context.rb (IRB::Context::initialize): [ruby-core:04330]
* object.c (Init_Object): remove Object#type. [ruby-core:04335]
* st.c (st_foreach): report success/failure by return value.
[ruby-Bugs-1396]
* parse.y: forgot to initialize parser struct. [ruby-dev:25492]
* parse.y (parser_yylex): no tLABEL on EXPR_BEG.
[ruby-talk:127711]
* document updates - [ruby-core:04296], [ruby-core:04301],
[ruby-core:04302], [ruby-core:04307]
* dir.c (rb_push_glob): should work for NUL delimited patterns.
* dir.c (rb_glob2): should aware of offset in the pattern.
* string.c (rb_str_new4): should propagate taintedness.
* env.h: rename member names in struct FRAME; last_func -> callee,
orig_func -> this_func, last_class -> this_class.
* struct.c (rb_struct_set): use original method name, not callee
name, to retrieve member slot. [ruby-core:04268]
* time.c (time_strftime): protect from format modification from GC
finalizers.
* object.c (Init_Object): remove rb_obj_id_obsolete()
* eval.c (rb_mod_define_method): incomplete subclass check.
[ruby-dev:25464]
* gc.c (rb_data_object_alloc): klass may be NULL.
[ruby-list:40498]
* bignum.c (rb_big_rand): should return positive random number.
[ruby-dev:25401]
* bignum.c (rb_big_rand): do not use rb_big_modulo to generate
random bignums. [ruby-dev:25396]
* variable.c (rb_autoload): [ruby-dev:25373]
* eval.c (svalue_to_avalue): [ruby-dev:25366]
* string.c (rb_str_justify): [ruby-dev:25367]
* io.c (rb_f_select): [ruby-dev:25312]
* ext/socket/socket.c (sock_s_getservbyport): [ruby-talk:124072]
* struct.c (make_struct): [ruby-dev:25249]
* dir.c (dir_open_dir): new function. [ruby-dev:25242]
* io.c (rb_f_open): add type check for return value from to_open.
* lib/pstore.rb (PStore#transaction): Use the empty content when a
file is not found. [ruby-dev:24561]
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@8068 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2005-03-04 01:47:45 -05:00
|
|
|
begin
|
|
|
|
file.flock(File::LOCK_SH)
|
2008-04-10 03:12:41 -04:00
|
|
|
return file
|
|
|
|
rescue
|
|
|
|
file.close
|
|
|
|
raise
|
* array.c: replace rb_protect_inspect() and rb_inspecting_p() by
rb_exec_recursive() in eval.c.
* eval.c (rb_exec_recursive): new function.
* array.c (rb_ary_join): use rb_exec_recursive().
* array.c (rb_ary_inspect, rb_ary_hash): ditto.
* file.c (rb_file_join): ditto.
* hash.c (rb_hash_inspect, rb_hash_to_s, rb_hash_hash): ditto.
* io.c (rb_io_puts): ditto.
* object.c (rb_obj_inspect): ditto
* struct.c (rb_struct_inspect): ditto.
* lib/set.rb (SortedSet::setup): a hack to shut up warning.
[ruby-talk:132866]
* lib/time.rb (Time::strptime): add new function. inspired by
[ruby-talk:132815].
* lib/parsedate.rb (ParseDate::strptime): ditto.
* regparse.c: move st_*_strend() functions from st.c. fixed some
potential memory leaks.
* exception error messages updated. [ruby-core:04497]
* ext/socket/socket.c (Init_socket): add bunch of Socket
constants. Patch from Sam Roberts <sroberts@uniserve.com>.
[ruby-core:04409]
* array.c (rb_ary_s_create): no need for negative argc check.
[ruby-core:04463]
* array.c (rb_ary_unshift_m): ditto.
* lib/xmlrpc/parser.rb (XMLRPC::FaultException): make it subclass
of StandardError class, not Exception class. [ruby-core:04429]
* parse.y (fcall_gen): lvar(arg) will be evaluated as
lvar.call(arg) when lvar is a defined local variable. [new]
* object.c (rb_class_initialize): call inherited method before
calling initializing block.
* eval.c (rb_thread_start_1): initialize newly pushed frame.
* lib/open3.rb (Open3::popen3): $? should not be EXIT_FAILURE.
fixed: [ruby-core:04444]
* eval.c (is_defined): NODE_IASGN is an assignment.
* ext/readline/readline.c (Readline.readline): use rl_outstream
and rl_instream. [ruby-dev:25699]
* ext/etc/etc.c (Init_etc): sGroup needs HAVE_ST_GR_PASSWD check
[ruby-dev:25675]
* misc/ruby-mode.el: [ruby-core:04415]
* lib/rdoc/generators/html_generator.rb: [ruby-core:04412]
* lib/rdoc/generators/ri_generator.rb: ditto.
* struct.c (make_struct): fixed: [ruby-core:04402]
* ext/curses/curses.c (window_color_set): [ruby-core:04393]
* ext/socket/socket.c (Init_socket): SO_REUSEPORT added.
[ruby-talk:130092]
* object.c: [ruby-doc:818]
* parse.y (open_args): fix too verbose warnings for the space
before argument parentheses. [ruby-dev:25492]
* parse.y (parser_yylex): ditto.
* parse.y (parser_yylex): the first expression in the parentheses
should not be a command. [ruby-dev:25492]
* lib/irb/context.rb (IRB::Context::initialize): [ruby-core:04330]
* object.c (Init_Object): remove Object#type. [ruby-core:04335]
* st.c (st_foreach): report success/failure by return value.
[ruby-Bugs-1396]
* parse.y: forgot to initialize parser struct. [ruby-dev:25492]
* parse.y (parser_yylex): no tLABEL on EXPR_BEG.
[ruby-talk:127711]
* document updates - [ruby-core:04296], [ruby-core:04301],
[ruby-core:04302], [ruby-core:04307]
* dir.c (rb_push_glob): should work for NUL delimited patterns.
* dir.c (rb_glob2): should aware of offset in the pattern.
* string.c (rb_str_new4): should propagate taintedness.
* env.h: rename member names in struct FRAME; last_func -> callee,
orig_func -> this_func, last_class -> this_class.
* struct.c (rb_struct_set): use original method name, not callee
name, to retrieve member slot. [ruby-core:04268]
* time.c (time_strftime): protect from format modification from GC
finalizers.
* object.c (Init_Object): remove rb_obj_id_obsolete()
* eval.c (rb_mod_define_method): incomplete subclass check.
[ruby-dev:25464]
* gc.c (rb_data_object_alloc): klass may be NULL.
[ruby-list:40498]
* bignum.c (rb_big_rand): should return positive random number.
[ruby-dev:25401]
* bignum.c (rb_big_rand): do not use rb_big_modulo to generate
random bignums. [ruby-dev:25396]
* variable.c (rb_autoload): [ruby-dev:25373]
* eval.c (svalue_to_avalue): [ruby-dev:25366]
* string.c (rb_str_justify): [ruby-dev:25367]
* io.c (rb_f_select): [ruby-dev:25312]
* ext/socket/socket.c (sock_s_getservbyport): [ruby-talk:124072]
* struct.c (make_struct): [ruby-dev:25249]
* dir.c (dir_open_dir): new function. [ruby-dev:25242]
* io.c (rb_f_open): add type check for return value from to_open.
* lib/pstore.rb (PStore#transaction): Use the empty content when a
file is not found. [ruby-dev:24561]
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@8068 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2005-03-04 01:47:45 -05:00
|
|
|
end
|
2008-04-10 03:12:41 -04:00
|
|
|
rescue Errno::ENOENT
|
|
|
|
return nil
|
1999-11-17 02:30:37 -05:00
|
|
|
end
|
2008-04-10 03:12:41 -04:00
|
|
|
else
|
Make rb_scan_args handle keywords more similar to Ruby methods (#2460)
Cfuncs that use rb_scan_args with the : entry suffer similar keyword
argument separation issues that Ruby methods suffer if the cfuncs
accept optional or variable arguments.
This makes the following changes to : handling.
* Treats as **kw, prompting keyword argument separation warnings
if called with a positional hash.
* Do not look for an option hash if empty keywords are provided.
For backwards compatibility, treat an empty keyword splat as a empty
mandatory positional hash argument, but emit a a warning, as this
behavior will be removed in Ruby 3. The argument number check
needs to be moved lower so it can correctly handle an empty
positional argument being added.
* If the last argument is nil and it is necessary to treat it as an option
hash in order to make sure all arguments are processed, continue to
treat the last argument as the option hash. Emit a warning in this case,
as this behavior will be removed in Ruby 3.
* If splitting the keyword hash into two hashes, issue a warning, as we
will not be splitting hashes in Ruby 3.
* If the keyword argument is required to fill a mandatory positional
argument, continue to do so, but emit a warning as this behavior will
be going away in Ruby 3.
* If keyword arguments are provided and the last argument is not a hash,
that indicates something wrong. This can happen if a cfunc is calling
rb_scan_args multiple times, and providing arguments that were not
passed to it from Ruby. Callers need to switch to the new
rb_scan_args_kw function, which allows passing of whether keywords
were provided.
This commit fixes all warnings caused by the changes above.
It switches some function calls to *_kw versions with appropriate
kw_splat flags. If delegating arguments, RB_PASS_CALLED_KEYWORDS
is used. If creating new arguments, RB_PASS_KEYWORDS is used if
the last argument is a hash to be treated as keywords.
In open_key_args in io.c, use rb_scan_args_kw.
In this case, the arguments provided come from another C
function, not Ruby. The last argument may or may not be a hash,
so we can't set keyword argument mode. However, if it is a
hash, we don't want to warn when treating it as keywords.
In Ruby files, make sure to appropriately use keyword splats
or literal keywords when calling Cfuncs that now issue keyword
argument separation warnings through rb_scan_args. Also, make
sure not to pass nil in place of an option hash.
Work around Kernel#warn warnings due to problems in the Rubygems
override of the method. There is an open pull request to fix
these issues in Rubygems, but part of the Rubygems tests for
their override fail on ruby-head due to rb_scan_args not
recognizing empty keyword splats, which this commit fixes.
Implementation wise, adding rb_scan_args_kw is kind of a pain,
because rb_scan_args takes a variable number of arguments.
In order to not duplicate all the code, the function internals need
to be split into two functions taking a va_list, and to avoid passing
in a ton of arguments, a single struct argument is used to handle
the variables previously local to the function.
2019-09-25 14:18:49 -04:00
|
|
|
file = File.new(filename, **RDWR_ACCESS)
|
2008-04-10 03:12:41 -04:00
|
|
|
file.flock(File::LOCK_EX)
|
|
|
|
return file
|
|
|
|
end
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2008-04-10 03:12:41 -04:00
|
|
|
# Load the given PStore file.
|
|
|
|
# If +read_only+ is true, the unmarshalled Hash will be returned.
|
|
|
|
# If +read_only+ is false, a 3-tuple will be returned: the unmarshalled
|
2016-09-29 08:08:30 -04:00
|
|
|
# Hash, a checksum of the data, and the size of the data.
|
2008-04-10 03:12:41 -04:00
|
|
|
def load_data(file, read_only)
|
|
|
|
if read_only
|
|
|
|
begin
|
2008-04-10 10:10:19 -04:00
|
|
|
table = load(file)
|
2011-12-20 09:57:29 -05:00
|
|
|
raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
|
2008-04-10 03:12:41 -04:00
|
|
|
rescue EOFError
|
|
|
|
# This seems to be a newly-created file.
|
|
|
|
table = {}
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
2008-04-10 03:12:41 -04:00
|
|
|
table
|
|
|
|
else
|
|
|
|
data = file.read
|
|
|
|
if data.empty?
|
|
|
|
# This seems to be a newly-created file.
|
|
|
|
table = {}
|
2008-04-11 03:43:31 -04:00
|
|
|
checksum = empty_marshal_checksum
|
2011-12-20 09:56:07 -05:00
|
|
|
size = empty_marshal_data.bytesize
|
2008-04-10 03:12:41 -04:00
|
|
|
else
|
2008-04-10 10:10:19 -04:00
|
|
|
table = load(data)
|
2016-06-28 09:55:02 -04:00
|
|
|
checksum = CHECKSUM_ALGO.digest(data)
|
2011-12-20 09:56:07 -05:00
|
|
|
size = data.bytesize
|
2011-12-20 09:57:29 -05:00
|
|
|
raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
2008-04-10 03:12:41 -04:00
|
|
|
data.replace(EMPTY_STRING)
|
|
|
|
[table, checksum, size]
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2008-04-10 03:12:41 -04:00
|
|
|
def on_windows?
|
2011-12-20 09:57:29 -05:00
|
|
|
is_windows = RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince/
|
2008-04-10 03:12:41 -04:00
|
|
|
self.class.__send__(:define_method, :on_windows?) do
|
|
|
|
is_windows
|
|
|
|
end
|
|
|
|
is_windows
|
2004-05-27 03:43:38 -04:00
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2008-04-10 03:12:41 -04:00
|
|
|
def save_data(original_checksum, original_file_size, file)
|
2011-12-20 09:56:07 -05:00
|
|
|
new_data = dump(@table)
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2016-06-28 09:55:02 -04:00
|
|
|
if new_data.bytesize != original_file_size || CHECKSUM_ALGO.digest(new_data) != original_checksum
|
2008-04-10 03:12:41 -04:00
|
|
|
if @ultra_safe && !on_windows?
|
|
|
|
# Windows doesn't support atomic file renames.
|
|
|
|
save_data_with_atomic_file_rename_strategy(new_data, file)
|
|
|
|
else
|
|
|
|
save_data_with_fast_strategy(new_data, file)
|
|
|
|
end
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2008-04-10 03:12:41 -04:00
|
|
|
new_data.replace(EMPTY_STRING)
|
2004-05-27 03:43:38 -04:00
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2008-04-10 03:12:41 -04:00
|
|
|
def save_data_with_atomic_file_rename_strategy(data, file)
|
|
|
|
temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}"
|
Make rb_scan_args handle keywords more similar to Ruby methods (#2460)
Cfuncs that use rb_scan_args with the : entry suffer similar keyword
argument separation issues that Ruby methods suffer if the cfuncs
accept optional or variable arguments.
This makes the following changes to : handling.
* Treats as **kw, prompting keyword argument separation warnings
if called with a positional hash.
* Do not look for an option hash if empty keywords are provided.
For backwards compatibility, treat an empty keyword splat as a empty
mandatory positional hash argument, but emit a a warning, as this
behavior will be removed in Ruby 3. The argument number check
needs to be moved lower so it can correctly handle an empty
positional argument being added.
* If the last argument is nil and it is necessary to treat it as an option
hash in order to make sure all arguments are processed, continue to
treat the last argument as the option hash. Emit a warning in this case,
as this behavior will be removed in Ruby 3.
* If splitting the keyword hash into two hashes, issue a warning, as we
will not be splitting hashes in Ruby 3.
* If the keyword argument is required to fill a mandatory positional
argument, continue to do so, but emit a warning as this behavior will
be going away in Ruby 3.
* If keyword arguments are provided and the last argument is not a hash,
that indicates something wrong. This can happen if a cfunc is calling
rb_scan_args multiple times, and providing arguments that were not
passed to it from Ruby. Callers need to switch to the new
rb_scan_args_kw function, which allows passing of whether keywords
were provided.
This commit fixes all warnings caused by the changes above.
It switches some function calls to *_kw versions with appropriate
kw_splat flags. If delegating arguments, RB_PASS_CALLED_KEYWORDS
is used. If creating new arguments, RB_PASS_KEYWORDS is used if
the last argument is a hash to be treated as keywords.
In open_key_args in io.c, use rb_scan_args_kw.
In this case, the arguments provided come from another C
function, not Ruby. The last argument may or may not be a hash,
so we can't set keyword argument mode. However, if it is a
hash, we don't want to warn when treating it as keywords.
In Ruby files, make sure to appropriately use keyword splats
or literal keywords when calling Cfuncs that now issue keyword
argument separation warnings through rb_scan_args. Also, make
sure not to pass nil in place of an option hash.
Work around Kernel#warn warnings due to problems in the Rubygems
override of the method. There is an open pull request to fix
these issues in Rubygems, but part of the Rubygems tests for
their override fail on ruby-head due to rb_scan_args not
recognizing empty keyword splats, which this commit fixes.
Implementation wise, adding rb_scan_args_kw is kind of a pain,
because rb_scan_args takes a variable number of arguments.
In order to not duplicate all the code, the function internals need
to be split into two functions taking a va_list, and to avoid passing
in a ton of arguments, a single struct argument is used to handle
the variables previously local to the function.
2019-09-25 14:18:49 -04:00
|
|
|
temp_file = File.new(temp_filename, **WR_ACCESS)
|
2008-04-10 03:12:41 -04:00
|
|
|
begin
|
|
|
|
temp_file.flock(File::LOCK_EX)
|
|
|
|
temp_file.write(data)
|
|
|
|
temp_file.flush
|
|
|
|
File.rename(temp_filename, @filename)
|
|
|
|
rescue
|
|
|
|
File.unlink(temp_file) rescue nil
|
|
|
|
raise
|
|
|
|
ensure
|
|
|
|
temp_file.close
|
2004-02-17 10:26:03 -05:00
|
|
|
end
|
2008-04-10 03:12:41 -04:00
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2008-04-10 03:12:41 -04:00
|
|
|
def save_data_with_fast_strategy(data, file)
|
|
|
|
file.rewind
|
|
|
|
file.write(data)
|
2011-12-20 09:56:07 -05:00
|
|
|
file.truncate(data.bytesize)
|
2004-02-17 10:26:03 -05:00
|
|
|
end
|
2008-04-10 10:10:19 -04:00
|
|
|
|
|
|
|
|
|
|
|
# This method is just a wrapped around Marshal.dump
|
|
|
|
# to allow subclass overriding used in YAML::Store.
|
|
|
|
def dump(table) # :nodoc:
|
|
|
|
Marshal::dump(table)
|
|
|
|
end
|
|
|
|
|
|
|
|
# This method is just a wrapped around Marshal.load.
|
|
|
|
# to allow subclass overriding used in YAML::Store.
|
|
|
|
def load(content) # :nodoc:
|
|
|
|
Marshal::load(content)
|
|
|
|
end
|
2008-04-11 03:43:31 -04:00
|
|
|
|
|
|
|
def empty_marshal_data
|
|
|
|
EMPTY_MARSHAL_DATA
|
|
|
|
end
|
|
|
|
def empty_marshal_checksum
|
|
|
|
EMPTY_MARSHAL_CHECKSUM
|
|
|
|
end
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|