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.
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
@@ -0,0 +1,4 @@
1
+ desc "Run performance benchmarks (optionally install hashie gem for comparison)"
2
+ task :benchmark do
3
+ ruby "--yjit", "-rbundler/setup", "benchmark/compare.rb"
4
+ end
data/rakelib/rdoc.rake ADDED
@@ -0,0 +1,8 @@
1
+ require "rdoc/task"
2
+
3
+ RDoc::Task.new do |rdoc|
4
+ rdoc.rdoc_dir = "doc"
5
+ rdoc.title = "Flexor #{Flexor::VERSION}"
6
+ rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
7
+ rdoc.main = "README.md"
8
+ end
@@ -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: []