cache_box 0.0.1.pre.preview4 → 0.0.1.pre.preview9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CacheBox
4
+ module Storage
5
+ # A storage backed by files.
6
+ class File
7
+ LOCK = Mutex.new
8
+
9
+ # Input:
10
+ #
11
+ # namespace = Symbol | String # Default: 'namespace'
12
+ # path = String # Path; Default: nil
13
+ #
14
+ # Output: N/A
15
+ def initialize(namespace: nil, path: nil)
16
+ validate_symbol_or_string!(namespace, 'namespace')
17
+ validate_string!(path, 'path')
18
+
19
+ namespace = namespace.to_s || 'namespace'
20
+
21
+ root = path || ::File.join(Dir.pwd, '.cache')
22
+ @path = ::File.join(root, namespace)
23
+ end
24
+
25
+ # Output: self
26
+ def reset!
27
+ LOCK.synchronize do
28
+ FileUtils.remove_entry_secure(@path, true)
29
+ end
30
+
31
+ self
32
+ end
33
+
34
+ # Reads the content.
35
+ #
36
+ # Input:
37
+ #
38
+ # name = String
39
+ #
40
+ # Output: Object # Anything
41
+ def read!(name)
42
+ validate_string!(name, 'name')
43
+
44
+ LOCK.synchronize do
45
+ file = ::File.join(@path, name)
46
+ return unless ::File.exist?(file)
47
+
48
+ Marshal.load(::File.read(file))
49
+ end
50
+ end
51
+
52
+ # Input:
53
+ #
54
+ # name = String
55
+ # data = Object # Anything
56
+ #
57
+ # Output: self
58
+ def write!(name, data)
59
+ validate_string!(name, 'name')
60
+
61
+ content = Marshal.dump(data)
62
+ file = ::File.join(@path, name)
63
+
64
+ LOCK.synchronize do
65
+ FileUtils.mkdir_p(@path)
66
+
67
+ ::File.write(file, content)
68
+ end
69
+
70
+ self
71
+ end
72
+
73
+ # Input:
74
+ #
75
+ # name = String
76
+ #
77
+ # Output: self
78
+ def delete!(name)
79
+ validate_string!(name, 'name')
80
+
81
+ LOCK.synchronize do
82
+ file = ::File.join(@path, name)
83
+ FileUtils.remove_entry_secure(file, true)
84
+ end
85
+
86
+ self
87
+ end
88
+
89
+ # Input:
90
+ #
91
+ # name = String
92
+ #
93
+ # Output: true | false
94
+ def has?(name)
95
+ validate_string!(name, 'name')
96
+
97
+ LOCK.synchronize do
98
+ file = ::File.join(@path, name)
99
+ ::File.file?(file)
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def validate_symbol_or_string!(arg, name)
106
+ return if arg.nil? || arg.is_a?(Symbol) || arg.is_a?(String)
107
+
108
+ klass = arg.class
109
+ value = arg.inspect
110
+
111
+ raise(
112
+ ArgumentError,
113
+ "#{name} must be a Symbol or String, got #{klass}: #{value}"
114
+ )
115
+ end
116
+
117
+ def validate_string!(arg, name)
118
+ return if arg.nil? || arg.is_a?(String)
119
+
120
+ klass = arg.class
121
+ value = arg.inspect
122
+
123
+ raise(
124
+ ArgumentError,
125
+ "#{name} must be a String, got #{klass}: #{value}"
126
+ )
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CacheBox
4
+ module Storage
5
+ # A storage backed by an in memory Hash.
6
+ class Memory
7
+ LOCK = Mutex.new
8
+
9
+ # Accepts a Hash or any Hash-like object as argument. Will use a plain Hash
10
+ # if none provided.
11
+ #
12
+ # Input:
13
+ #
14
+ # state = Hash{...Object => Object} # Default: nil
15
+ #
16
+ # Output: N/A
17
+ def initialize(state = nil)
18
+ validate!(state)
19
+
20
+ LOCK.synchronize do
21
+ @state = state || {}
22
+ end
23
+ end
24
+
25
+ # Accepts a Hash or any Hash-like object as argument. Will use a plain Hash
26
+ # if none provided.
27
+ #
28
+ # Input:
29
+ #
30
+ # state = Hash{...Object => Object}
31
+ #
32
+ # Output: self
33
+ def reset!(state = nil)
34
+ initialize(state)
35
+
36
+ self
37
+ end
38
+
39
+ # Input:
40
+ #
41
+ # name = String
42
+ #
43
+ # Output: Object # Anything
44
+ def read!(name)
45
+ validate_string!(name, 'name')
46
+
47
+ LOCK.synchronize do
48
+ @state[name]
49
+ end
50
+ end
51
+
52
+ # Input:
53
+ #
54
+ # name = String
55
+ # data = Object # Anything
56
+ #
57
+ # Output: self
58
+ def write!(name, data)
59
+ validate_string!(name, 'name')
60
+
61
+ LOCK.synchronize do
62
+ @state[name] = data
63
+ end
64
+
65
+ self
66
+ end
67
+
68
+ # Input:
69
+ #
70
+ # name = String
71
+ #
72
+ # Output: self
73
+ def delete!(name)
74
+ validate_string!(name, 'name')
75
+
76
+ LOCK.synchronize do
77
+ @state.delete(name)
78
+ end
79
+
80
+ self
81
+ end
82
+
83
+ # Input:
84
+ #
85
+ # name = String
86
+ #
87
+ # Output: true | false
88
+ def has?(name)
89
+ validate_string!(name, 'name')
90
+
91
+ LOCK.synchronize do
92
+ @state.key?(name)
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ # Input:
99
+ #
100
+ # state = Object # Anything
101
+ #
102
+ # Output: N/A
103
+ def validate!(state)
104
+ return if state.nil? || state.is_a?(Hash)
105
+
106
+ validate_get!(state)
107
+ validate_set!(state)
108
+ validate_delete!(state)
109
+ validate_key!(state)
110
+ end
111
+
112
+ # Input:
113
+ #
114
+ # state = Object # Anything
115
+ #
116
+ # Output: N/A
117
+ def validate_get!(state)
118
+ unless state.respond_to?(:[])
119
+ raise(ArgumentError, 'Given state object does not respond to `:[]`')
120
+ end
121
+
122
+ arity = state.method(:[]).arity
123
+ unless arity == 1
124
+ raise(
125
+ ArgumentError,
126
+ "Given state object's `:[]` method arity must be 1, got: #{arity}"
127
+ )
128
+ end
129
+ end
130
+
131
+ # Input:
132
+ #
133
+ # state = Object # Anything
134
+ #
135
+ # Output: N/A
136
+ def validate_set!(state)
137
+ unless state.respond_to?(:[]=)
138
+ raise(ArgumentError, 'Given state object does not respond to `:[]=`')
139
+ end
140
+
141
+ arity = state.method(:[]=).arity
142
+ unless arity == 2
143
+ raise(
144
+ ArgumentError,
145
+ "Given state object's `:[]=` method arity must be 2, got: #{arity}"
146
+ )
147
+ end
148
+ end
149
+
150
+ # Input:
151
+ #
152
+ # state = Object # Anything
153
+ #
154
+ # Output: N/A
155
+ def validate_delete!(state)
156
+ unless state.respond_to?(:delete)
157
+ raise(ArgumentError, 'Given state object does not respond to `delete`')
158
+ end
159
+
160
+ arity = state.method(:delete).arity
161
+ unless arity == 1
162
+ raise(
163
+ ArgumentError,
164
+ "Given state object's `:delete` method arity must be 1, got: #{arity}"
165
+ )
166
+ end
167
+ end
168
+
169
+ # Input:
170
+ #
171
+ # state = Object # Anything
172
+ #
173
+ # Output: N/A
174
+ def validate_key!(state)
175
+ unless state.respond_to?(:key?)
176
+ raise(ArgumentError, 'Given state object does not respond to `:key?`')
177
+ end
178
+
179
+ arity = state.method(:key?).arity
180
+ unless arity == 1
181
+ raise(
182
+ ArgumentError,
183
+ "Given state object's `:key?` method arity must be 1, got: #{arity}"
184
+ )
185
+ end
186
+ end
187
+
188
+ # Input:
189
+ #
190
+ # arg = String
191
+ # name = String
192
+ #
193
+ # Output: N/A
194
+ def validate_string!(arg, name)
195
+ return if arg.nil? || arg.is_a?(String)
196
+
197
+ klass = arg.class
198
+ value = arg.inspect
199
+
200
+ raise(
201
+ ArgumentError,
202
+ "#{name} must be a String, got #{klass}: #{value}"
203
+ )
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CacheBox
4
+ class Unit
5
+ include CacheBox::Helper::Validate
6
+
7
+ LOCK = Mutex.new
8
+
9
+ # Input:
10
+ #
11
+ # namespace = String | Symbol # Default: :namespace
12
+ #
13
+ # Output: N/A
14
+ def initialize(namespace = nil, logger: nil, storage: nil)
15
+ namespace ||= :namespace
16
+ validate_string_or_symbol!(namespace, 'namespace')
17
+
18
+ @namespace_o = namespace
19
+ @namespace_s = namespace.to_s
20
+ @logger = logger || Logger.new(STDOUT, level: Logger::INFO)
21
+ @storage = storage || ::CacheBox::Storage::File.new(namespace: @namespace_o)
22
+
23
+ LOCK.synchronize do
24
+ @state = { result: ::CacheBox::Stash.new, stash: ::CacheBox::Stash.new }
25
+ end
26
+ end
27
+
28
+ # Input:
29
+ #
30
+ # name = String | Symbol # Default: 'name'
31
+ # args = Object
32
+ # input = Object
33
+ # &block = Proc(CacheBox::Box)
34
+ #
35
+ # Output: Object # Anything the &block returns.
36
+ def with(name = nil, args = nil, input = nil, &block)
37
+ name ||= 'name'
38
+ validate_string_or_symbol!(name, 'name')
39
+ validate_block_presence!(block, '#with')
40
+
41
+ name_o = name
42
+ name_s = name.to_s
43
+ box = nil
44
+
45
+ LOCK.synchronize do
46
+ return @state[:result][name_s] if @state[:result].key?(name_s)
47
+
48
+ data = @storage.read!(name_s)
49
+ if data&.key?(:result)
50
+ @state[:result][name_s] = data[:result]
51
+
52
+ return data[:result]
53
+ elsif data&.key?(:stash)
54
+ @state[:stash][name_s] = data[:stash]
55
+ end
56
+
57
+ box = ::CacheBox::Box.new(
58
+ namespace: @namespace_o.dup,
59
+ name: name_o.dup,
60
+ args: args,
61
+ input: input,
62
+ stash: @state[:stash][name] || ::CacheBox::Stash.new,
63
+ logger: @logger
64
+ )
65
+ end
66
+
67
+ begin
68
+ result = block.call(box)
69
+
70
+ LOCK.synchronize do
71
+ @state[:result][name_s] = result
72
+ end
73
+ ensure
74
+ LOCK.synchronize do
75
+ if @state[:result].key?(name_s)
76
+ @state[:stash].delete(name_s)
77
+ @storage.write!(name_s, { result: @state[:result][name_s] })
78
+ else
79
+ stash = box.stash
80
+ stash.with do |state|
81
+ dump = { stash: state }
82
+
83
+ @state[:stash][name_s] = stash
84
+ @storage.write!(name_s, dump)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ # Input:
92
+ #
93
+ # name = String | Symbol # Default: 'name'
94
+ #
95
+ # Output: true | false
96
+ def has?(name = nil)
97
+ name ||= 'name'
98
+ validate_string_or_symbol!(name, 'name')
99
+ name = name.to_s
100
+
101
+ LOCK.synchronize do
102
+ return true if @state[:result].key?(name)
103
+
104
+ data = @storage.read!(name)
105
+
106
+ if data&.key?(:result)
107
+ @state[:result][name] = data[:result]
108
+ elsif data&.key?(:stash)
109
+ @state[:stash][name] = data[:stash]
110
+ end
111
+
112
+ @state[:result].key?(name)
113
+ end
114
+ end
115
+
116
+ # Input:
117
+ #
118
+ # name = String | Symbol # Default: 'name'
119
+ #
120
+ # Output: true | false
121
+ def result(name = nil)
122
+ name ||= 'name'
123
+ validate_string_or_symbol!(name, 'name')
124
+ name = name.to_s
125
+
126
+ LOCK.synchronize do
127
+ return @state[:result][name] if @state[:result].key?(name)
128
+
129
+ data = @storage.read!(name)
130
+
131
+ if data&.key?(:result)
132
+ @state[:result][name] = data[:result]
133
+ elsif data&.key?(:stash)
134
+ @state[:stash][name] = data[:stash]
135
+ end
136
+
137
+ @state[:result][name]
138
+ end
139
+ end
140
+
141
+ # Input:
142
+ #
143
+ # name = Array[String | Symbol] # Default: []
144
+ #
145
+ # Output: self
146
+ def expire!(*names)
147
+ validate_array_of_string_or_symbol!(names, 'names')
148
+
149
+ if names.empty?
150
+ LOCK.synchronize do
151
+ @state = { result: ::CacheBox::Stash.new, stash: ::CacheBox::Stash.new }
152
+
153
+ @storage.reset!
154
+ end
155
+ else
156
+ LOCK.synchronize do
157
+ names.each do |name_o|
158
+ name_s = name_o.to_s
159
+
160
+ @state[:result].delete(name_s)
161
+ @state[:stash].delete(name_s)
162
+
163
+ @storage.delete!(name_s)
164
+ end
165
+ end
166
+ end
167
+
168
+ self
169
+ end
170
+
171
+ # Input:
172
+ #
173
+ # name = Array[String | Symbol] # Default: []
174
+ #
175
+ # Output: self
176
+ def clear(*names)
177
+ validate_array_of_string_or_symbol!(names, 'names')
178
+
179
+ if names.empty?
180
+ LOCK.synchronize do
181
+ @state = { result: ::CacheBox::Stash.new, stash: ::CacheBox::Stash.new }
182
+ end
183
+ else
184
+ LOCK.synchronize do
185
+ names.each do |name_o|
186
+ name_s = name_o.to_s
187
+
188
+ @state[:result].delete(name_s)
189
+ @state[:stash].delete(name_s)
190
+ end
191
+ end
192
+ end
193
+
194
+ self
195
+ end
196
+ end
197
+ end