flexor 0.1.0
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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +394 -0
- data/.ruby-version +1 -0
- data/LICENSE.txt +21 -0
- data/README.md +218 -0
- data/Rakefile +8 -0
- data/benchmark/compare.rb +173 -0
- data/benchmark/results-with-caching.txt +175 -0
- data/docs/benchmark-results.md +64 -0
- data/docs/original_specification.yaml +426 -0
- data/docs/specification.yaml +453 -0
- data/lib/flexor/hash_delegation.rb +30 -0
- data/lib/flexor/serialization.rb +32 -0
- data/lib/flexor/version.rb +3 -0
- data/lib/flexor/vivification.rb +47 -0
- data/lib/flexor.rb +187 -0
- data/rakelib/benchmark.rake +4 -0
- data/rakelib/rdoc.rake +8 -0
- data/rakelib/version.rake +72 -0
- metadata +64 -0
data/lib/flexor.rb
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
require_relative "flexor/version"
|
|
2
|
+
require_relative "flexor/hash_delegation"
|
|
3
|
+
require_relative "flexor/serialization"
|
|
4
|
+
require_relative "flexor/vivification"
|
|
5
|
+
|
|
6
|
+
##
|
|
7
|
+
# A Hash-like data store with autovivifying nested access, nil-safe
|
|
8
|
+
# chaining, and seamless conversion between hashes and method-style
|
|
9
|
+
# access.
|
|
10
|
+
class Flexor
|
|
11
|
+
class Error < StandardError; end
|
|
12
|
+
|
|
13
|
+
include HashDelegation
|
|
14
|
+
include Serialization
|
|
15
|
+
include Vivification
|
|
16
|
+
|
|
17
|
+
def self.[](input = {})
|
|
18
|
+
case input
|
|
19
|
+
when String then from_json(input)
|
|
20
|
+
when Hash then new(input)
|
|
21
|
+
else raise ArgumentError, "expected a String or Hash, got #{input.class}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.from_json(json)
|
|
26
|
+
require "json"
|
|
27
|
+
JSON.parse(json, symbolize_names: true)
|
|
28
|
+
.then { new it }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.===(other)
|
|
32
|
+
other.is_a?(self)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize(hash = {}, root: true)
|
|
36
|
+
raise ArgumentError, "expected a Hash, got #{hash.class}" unless hash.is_a?(Hash)
|
|
37
|
+
|
|
38
|
+
@root = root
|
|
39
|
+
@store = vivify(hash)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def initialize_copy(original)
|
|
43
|
+
super
|
|
44
|
+
@store = @store.dup
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def [](key)
|
|
48
|
+
@store[key]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def []=(key, value)
|
|
52
|
+
@store[key] = vivify_value(value)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def set_raw(key, value)
|
|
56
|
+
@store[key] = value
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def delete(key)
|
|
60
|
+
@store.delete(key)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def clear
|
|
64
|
+
@store.clear
|
|
65
|
+
self
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_ary
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def freeze
|
|
73
|
+
@store.freeze
|
|
74
|
+
super
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def to_h
|
|
78
|
+
@store.each_with_object({}) do |(key, value), hash|
|
|
79
|
+
result = recurse_to_h(value)
|
|
80
|
+
hash[key] = result unless value.is_a?(Flexor) && result.nil?
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def to_json(...)
|
|
85
|
+
require "json"
|
|
86
|
+
to_h.to_json(...)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def to_s
|
|
90
|
+
return "" if nil?
|
|
91
|
+
|
|
92
|
+
@store.to_s
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def inspect
|
|
96
|
+
return @store.inspect if @root
|
|
97
|
+
return nil.inspect if @store.empty?
|
|
98
|
+
|
|
99
|
+
@store.inspect
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def deconstruct
|
|
103
|
+
@store.values
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def deconstruct_keys(keys)
|
|
107
|
+
return @store if keys.nil?
|
|
108
|
+
|
|
109
|
+
@store.slice(*keys)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def nil?
|
|
113
|
+
@store.empty?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def merge!(other)
|
|
117
|
+
other = other.to_h if other.is_a?(Flexor)
|
|
118
|
+
other.each do |key, value|
|
|
119
|
+
if value.is_a?(Hash) && self[key].is_a?(Flexor) && !self[key].nil?
|
|
120
|
+
self[key].merge!(value)
|
|
121
|
+
else
|
|
122
|
+
self[key] = value
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
self
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def merge(other)
|
|
129
|
+
dup.merge!(other)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def ==(other)
|
|
133
|
+
case other
|
|
134
|
+
in nil then nil?
|
|
135
|
+
in Flexor then to_h == other.to_h
|
|
136
|
+
in Hash then to_h == other
|
|
137
|
+
else super
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def ===(other)
|
|
142
|
+
other.nil? ? nil? : super
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def respond_to_missing?(_name, _include_private = false)
|
|
146
|
+
true
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def method_missing(name, *args, &block)
|
|
152
|
+
return super if block
|
|
153
|
+
|
|
154
|
+
case [name, args]
|
|
155
|
+
in /^[^=]+=$/, [arg] then write_via_method(name, arg)
|
|
156
|
+
in _, [] then read_via_method(name)
|
|
157
|
+
else super
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def write_via_method(name, arg)
|
|
162
|
+
key = name.to_s.chomp("=").to_sym
|
|
163
|
+
cache_setter(name, key)
|
|
164
|
+
self[key] = arg
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def read_via_method(name)
|
|
168
|
+
cache_getter(name) if !frozen? && @store.key?(name)
|
|
169
|
+
self[name]
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def cache_setter(name, key)
|
|
173
|
+
define_singleton_method(name) do |val = nil, &blk|
|
|
174
|
+
raise NoMethodError, "undefined method '#{name}' for #{inspect}" if blk
|
|
175
|
+
|
|
176
|
+
self[key] = val
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def cache_getter(name)
|
|
181
|
+
define_singleton_method(name) do |*a, &blk|
|
|
182
|
+
raise NoMethodError, "undefined method '#{name}' for #{inspect}" if blk || !a.empty?
|
|
183
|
+
|
|
184
|
+
self[name]
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
data/rakelib/rdoc.rake
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
VERSION_PATTERN = /VERSION\s*=\s*"(\d+\.\d+\.\d+)"/
|
|
2
|
+
|
|
3
|
+
# Encapsulates version file manipulation logic for rake tasks.
|
|
4
|
+
module VersionBumper
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def version_path
|
|
8
|
+
File.expand_path("../lib/flexor/version.rb", __dir__)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def print_current
|
|
12
|
+
require_relative "../lib/flexor/version"
|
|
13
|
+
puts "Current version: #{Flexor::VERSION}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def bump
|
|
17
|
+
File.open(version_path, File::RDWR, 0o644) do |f|
|
|
18
|
+
f.flock(File::LOCK_EX)
|
|
19
|
+
old_version, new_version, new_source = compute_bump(f.read)
|
|
20
|
+
f.rewind
|
|
21
|
+
f.write(new_source)
|
|
22
|
+
f.truncate(f.pos)
|
|
23
|
+
puts "Version bumped from #{old_version} to #{new_version}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def compute_bump(source)
|
|
28
|
+
match = source.match(VERSION_PATTERN)
|
|
29
|
+
abort "Could not find VERSION in #{version_path}" unless match
|
|
30
|
+
|
|
31
|
+
old_version = match[1]
|
|
32
|
+
parts = old_version.split(".").map(&:to_i)
|
|
33
|
+
parts[-1] += 1
|
|
34
|
+
new_version = parts.join(".")
|
|
35
|
+
new_source = source.sub(/VERSION\s*=\s*"#{Regexp.escape(old_version)}"/, "VERSION = \"#{new_version}\"")
|
|
36
|
+
[old_version, new_version, new_source]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def commit
|
|
40
|
+
require_relative "../lib/flexor/version"
|
|
41
|
+
system("git", "add", version_path) || abort("git add failed")
|
|
42
|
+
system("git", "commit", "-m", "Bump version to #{Flexor::VERSION}") || abort("git commit failed")
|
|
43
|
+
puts "Version change committed."
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def revert
|
|
47
|
+
last_message = `git log -1 --pretty=%B`.strip
|
|
48
|
+
abort "Last commit does not appear to be a version bump." unless last_message.start_with?("Bump version to ")
|
|
49
|
+
|
|
50
|
+
system("git", "revert", "HEAD", "--no-edit") || abort("git revert failed")
|
|
51
|
+
puts "Version bump reverted."
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
namespace :version do
|
|
56
|
+
desc "Display the current version"
|
|
57
|
+
task(:current) { VersionBumper.print_current }
|
|
58
|
+
|
|
59
|
+
desc "Bump the patch version"
|
|
60
|
+
task(:bump) { VersionBumper.bump }
|
|
61
|
+
|
|
62
|
+
desc "Commit the version change"
|
|
63
|
+
task(:commit) { VersionBumper.commit }
|
|
64
|
+
|
|
65
|
+
desc "Revert the last version bump commit"
|
|
66
|
+
task(:revert) { VersionBumper.revert }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
namespace :release do
|
|
70
|
+
desc "Bump version, commit, and release"
|
|
71
|
+
task full: ["version:bump", "version:commit", :release]
|
|
72
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: flexor
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- David Gillis
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Flexor provides autovivifying nested access, nil-safe chaining, and seamless
|
|
13
|
+
conversion between hashes and method-style access. Built for flexible data containers,
|
|
14
|
+
middleware state, and prototyping.
|
|
15
|
+
email:
|
|
16
|
+
- david@flipmine.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- ".rspec"
|
|
22
|
+
- ".rubocop.yml"
|
|
23
|
+
- ".ruby-version"
|
|
24
|
+
- LICENSE.txt
|
|
25
|
+
- README.md
|
|
26
|
+
- Rakefile
|
|
27
|
+
- benchmark/compare.rb
|
|
28
|
+
- benchmark/results-with-caching.txt
|
|
29
|
+
- docs/benchmark-results.md
|
|
30
|
+
- docs/original_specification.yaml
|
|
31
|
+
- docs/specification.yaml
|
|
32
|
+
- lib/flexor.rb
|
|
33
|
+
- lib/flexor/hash_delegation.rb
|
|
34
|
+
- lib/flexor/serialization.rb
|
|
35
|
+
- lib/flexor/version.rb
|
|
36
|
+
- lib/flexor/vivification.rb
|
|
37
|
+
- rakelib/benchmark.rake
|
|
38
|
+
- rakelib/rdoc.rake
|
|
39
|
+
- rakelib/version.rake
|
|
40
|
+
homepage: https://github.com/gillisd/flexor
|
|
41
|
+
licenses:
|
|
42
|
+
- MIT
|
|
43
|
+
metadata:
|
|
44
|
+
rubygems_mfa_required: 'true'
|
|
45
|
+
homepage_uri: https://github.com/gillisd/flexor
|
|
46
|
+
source_code_uri: https://github.com/gillisd/flexor
|
|
47
|
+
rdoc_options: []
|
|
48
|
+
require_paths:
|
|
49
|
+
- lib
|
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.4'
|
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0'
|
|
60
|
+
requirements: []
|
|
61
|
+
rubygems_version: 4.0.6
|
|
62
|
+
specification_version: 4
|
|
63
|
+
summary: A Hash-like data store that does what you tell it to do
|
|
64
|
+
test_files: []
|