cache_box 0.0.1.pre.preview7 → 0.0.1.pre.preview8

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