reflekt 1.0.5 → 1.0.10
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 +4 -4
- data/lib/accessor.rb +45 -0
- data/lib/action.rb +127 -0
- data/lib/action_stack.rb +44 -0
- data/lib/{Clone.rb → clone.rb} +11 -11
- data/lib/config.rb +48 -0
- data/lib/control.rb +81 -0
- data/lib/experiment.rb +99 -0
- data/lib/meta.rb +75 -0
- data/lib/meta/array_meta.rb +32 -0
- data/lib/meta/boolean_meta.rb +26 -0
- data/lib/meta/float_meta.rb +26 -0
- data/lib/meta/integer_meta.rb +26 -0
- data/lib/meta/{NullMeta.rb → null_meta.rb} +21 -19
- data/lib/meta/object_meta.rb +35 -0
- data/lib/meta/string_meta.rb +26 -0
- data/lib/meta_builder.rb +100 -0
- data/lib/reflection.rb +123 -0
- data/lib/reflekt.rb +277 -0
- data/lib/renderer.rb +38 -0
- data/lib/rule.rb +54 -0
- data/lib/rule_set.rb +110 -0
- data/lib/rule_set_aggregator.rb +260 -0
- data/lib/rules/array_rule.rb +94 -0
- data/lib/rules/boolean_rule.rb +43 -0
- data/lib/rules/float_rule.rb +55 -0
- data/lib/rules/integer_rule.rb +55 -0
- data/lib/rules/null_rule.rb +35 -0
- data/lib/rules/object_rule.rb +42 -0
- data/lib/rules/string_rule.rb +75 -0
- data/lib/web/index.html +3 -4
- metadata +46 -29
- data/lib/Accessor.rb +0 -37
- data/lib/Action.rb +0 -88
- data/lib/ActionStack.rb +0 -44
- data/lib/Aggregator.rb +0 -260
- data/lib/Config.rb +0 -42
- data/lib/Control.rb +0 -83
- data/lib/Meta.rb +0 -71
- data/lib/MetaBuilder.rb +0 -84
- data/lib/Reflection.rb +0 -195
- data/lib/Reflekt.rb +0 -243
- data/lib/Renderer.rb +0 -39
- data/lib/Rule.rb +0 -52
- data/lib/RuleSet.rb +0 -109
- data/lib/meta/ArrayMeta.rb +0 -34
- data/lib/meta/BooleanMeta.rb +0 -26
- data/lib/meta/FloatMeta.rb +0 -26
- data/lib/meta/IntegerMeta.rb +0 -26
- data/lib/meta/StringMeta.rb +0 -26
- data/lib/rules/ArrayRule.rb +0 -88
- data/lib/rules/BooleanRule.rb +0 -47
- data/lib/rules/FloatRule.rb +0 -57
- data/lib/rules/IntegerRule.rb +0 -57
- data/lib/rules/NullRule.rb +0 -33
- data/lib/rules/StringRule.rb +0 -81
data/lib/reflekt.rb
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
################################################################################
|
2
|
+
# Reflective testing.
|
3
|
+
#
|
4
|
+
# @author Maedi Prichard
|
5
|
+
#
|
6
|
+
# @usage
|
7
|
+
# class ExampleClass
|
8
|
+
# prepend Reflekt
|
9
|
+
################################################################################
|
10
|
+
|
11
|
+
require 'set'
|
12
|
+
require 'erb'
|
13
|
+
require 'rowdb'
|
14
|
+
require 'lit_cli'
|
15
|
+
|
16
|
+
require_relative 'accessor'
|
17
|
+
require_relative 'action'
|
18
|
+
require_relative 'action_stack'
|
19
|
+
require_relative 'config'
|
20
|
+
require_relative 'control'
|
21
|
+
require_relative 'experiment'
|
22
|
+
require_relative 'renderer'
|
23
|
+
require_relative 'rule_set_aggregator'
|
24
|
+
|
25
|
+
# Require all rules in rules directory.
|
26
|
+
Dir[File.join(__dir__, 'rules', '*.rb')].each { |file| require_relative file }
|
27
|
+
|
28
|
+
module Reflekt
|
29
|
+
include LitCLI
|
30
|
+
|
31
|
+
##
|
32
|
+
# Setup Reflekt per class.
|
33
|
+
# Override methods on class instantiation.
|
34
|
+
#
|
35
|
+
# @scope self [Object] Refers to the class that Reflekt is prepended to.
|
36
|
+
##
|
37
|
+
def initialize(*args)
|
38
|
+
if @@reflekt.config.enabled
|
39
|
+
@reflekt_initialized = false
|
40
|
+
|
41
|
+
🔥"Initialize", :info, :setup, self.class
|
42
|
+
|
43
|
+
# Override methods.
|
44
|
+
Reflekt.get_methods(self).each do |method|
|
45
|
+
Reflekt.setup_count(self, method)
|
46
|
+
Reflekt.override_method(self, method)
|
47
|
+
end
|
48
|
+
|
49
|
+
@reflekt_initialized = true
|
50
|
+
end
|
51
|
+
|
52
|
+
# Continue initialization.
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Get child and parent instance methods.
|
58
|
+
#
|
59
|
+
# TODO: Include methods from all ancestors.
|
60
|
+
# TODO: Include core methods like "Array.include?".
|
61
|
+
##
|
62
|
+
def self.get_methods(klass)
|
63
|
+
child_instance_methods = klass.class.instance_methods(false)
|
64
|
+
parent_instance_methods = klass.class.superclass.instance_methods(false)
|
65
|
+
|
66
|
+
return child_instance_methods + parent_instance_methods
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Override a method.
|
71
|
+
#
|
72
|
+
# @param klass [Dynamic] The class to override.
|
73
|
+
# @param method [Method] The method to override.
|
74
|
+
##
|
75
|
+
def self.override_method(klass, method)
|
76
|
+
klass.define_singleton_method(method) do |*args|
|
77
|
+
|
78
|
+
# When method called in flow.
|
79
|
+
if @reflekt_initialized
|
80
|
+
|
81
|
+
##
|
82
|
+
# Reflect-Execute loop.
|
83
|
+
#
|
84
|
+
# Reflect each method before finally executing it.
|
85
|
+
#
|
86
|
+
# @loop
|
87
|
+
# 1. The first method call creates an action
|
88
|
+
# 2. The action creates reflections and calls the method again
|
89
|
+
# 3. Subsequent method calls execute these reflections
|
90
|
+
# 4. Each reflection executes on cloned data
|
91
|
+
# 5. The original method call completes execution
|
92
|
+
#
|
93
|
+
# @see https://reflekt.dev/docs/reflect-execute-loop
|
94
|
+
##
|
95
|
+
|
96
|
+
unless @@reflekt.error
|
97
|
+
|
98
|
+
action = @@reflekt.stack.peek()
|
99
|
+
|
100
|
+
# New action when old action done reflecting.
|
101
|
+
if action.nil? || action.has_finished_loop?
|
102
|
+
🔥"^ Create action for #{method}()", :info, :action, klass.class
|
103
|
+
action = Action.new(klass, method, @@reflekt.config, @@reflekt.db, @@reflekt.stack, @@reflekt.aggregator)
|
104
|
+
@@reflekt.stack.push(action)
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# REFLECT
|
109
|
+
##
|
110
|
+
|
111
|
+
unless action.is_reflecting? && klass.class.reflekt_skipped?(method) || Reflekt.count(klass, method) >= @@reflekt.config.reflect_limit
|
112
|
+
unless action.is_actioned?
|
113
|
+
action.is_actioned = true
|
114
|
+
action.is_reflecting = true
|
115
|
+
|
116
|
+
action.reflect(*args)
|
117
|
+
if action.control.status == :error
|
118
|
+
@@reflekt.error = action.control.message
|
119
|
+
end
|
120
|
+
|
121
|
+
# Render results.
|
122
|
+
@@reflekt.renderer.render()
|
123
|
+
|
124
|
+
action.is_reflecting = false
|
125
|
+
end
|
126
|
+
else
|
127
|
+
🔥"> Skip reflection of #{method}()", :skip, :reflect, klass.class
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# EXECUTE
|
132
|
+
##
|
133
|
+
|
134
|
+
unless action.is_reflecting? && klass.class.reflekt_skipped?(method)
|
135
|
+
🔥"> Execute #{method}()", :info, :execute, klass.class
|
136
|
+
super *args
|
137
|
+
end
|
138
|
+
|
139
|
+
# Finish execution if control encounters unrecoverable error.
|
140
|
+
else
|
141
|
+
🔥"Reflection error, finishing original execution...", :error, :reflect, klass.class
|
142
|
+
super *args
|
143
|
+
end
|
144
|
+
|
145
|
+
# When method called in constructor.
|
146
|
+
else
|
147
|
+
p "Reflection unsupported in constructor for #{method}()", :info, :setup, klass.class
|
148
|
+
super *args
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.setup_count(klass, method)
|
154
|
+
caller_id = klass.object_id
|
155
|
+
@@reflekt.counts[caller_id] = {} unless @@reflekt.counts.has_key? caller_id
|
156
|
+
@@reflekt.counts[caller_id][method] = 0 unless @@reflekt.counts[caller_id].has_key? method
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.count(klass, method)
|
160
|
+
count = @@reflekt.counts.dig(klass.object_id, method) || 0
|
161
|
+
count
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.increase_count(klass, method)
|
165
|
+
caller_id = klass.object_id
|
166
|
+
@@reflekt.counts[caller_id][method] = @@reflekt.counts[caller_id][method] + 1
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# Configure Config singleton.
|
171
|
+
##
|
172
|
+
def self.configure
|
173
|
+
reflekt_setup_class()
|
174
|
+
|
175
|
+
yield(@@reflekt.config)
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def self.prepended(base)
|
181
|
+
# Prepend class methods to the instance's singleton class.
|
182
|
+
base.singleton_class.prepend(SingletonClassMethods)
|
183
|
+
|
184
|
+
reflekt_setup_class()
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# Setup class.
|
189
|
+
#
|
190
|
+
# @paths
|
191
|
+
# - package_path [String] Absolute path to the library itself.
|
192
|
+
# - project_path [String] Absolute path to the project root.
|
193
|
+
# - output_path [String] Absolute path to the reflections directory.
|
194
|
+
##
|
195
|
+
def self.reflekt_setup_class()
|
196
|
+
|
197
|
+
# Only setup once.
|
198
|
+
return if defined? @@reflekt
|
199
|
+
|
200
|
+
@@reflekt = Accessor.new()
|
201
|
+
@@reflekt.config = Config.new()
|
202
|
+
@@reflekt.stack = ActionStack.new()
|
203
|
+
|
204
|
+
# Setup paths.
|
205
|
+
@@reflekt.package_path = File.dirname(File.realpath(__FILE__))
|
206
|
+
@@reflekt.project_path = @@reflekt.config.project_path
|
207
|
+
@@reflekt.output_path = File.join(@@reflekt.project_path, @@reflekt.config.output_directory)
|
208
|
+
unless Dir.exist? @@reflekt.output_path
|
209
|
+
Dir.mkdir(@@reflekt.output_path)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Setup database.
|
213
|
+
@@reflekt.db = Rowdb.new(@@reflekt.output_path + '/db.js')
|
214
|
+
@@reflekt.db.defaults({ :reflekt => { :api_version => 1 }})
|
215
|
+
# TODO: Fix Rowdb.get(path) not returning values at path after Rowdb.push()
|
216
|
+
db = @@reflekt.db.value()
|
217
|
+
|
218
|
+
# Train aggregated rule sets.
|
219
|
+
@@reflekt.aggregator = RuleSetAggregator.new(@@reflekt.config.meta_map)
|
220
|
+
@@reflekt.aggregator.train(db[:controls])
|
221
|
+
|
222
|
+
# Setup renderer.
|
223
|
+
@@reflekt.renderer = Renderer.new(@@reflekt.package_path, @@reflekt.output_path)
|
224
|
+
|
225
|
+
LitCLI.configure do |config|
|
226
|
+
config.statuses = {
|
227
|
+
:info => { icon: "ℹ", color: :blue, styles: [:upcase] },
|
228
|
+
:pass => { icon: "✔", color: :green, styles: [:upcase] },
|
229
|
+
:save => { icon: "✔", color: :green, styles: [:upcase] },
|
230
|
+
:skip => { icon: "⨯", color: :yellow, styles: [:upcase] },
|
231
|
+
:warn => { icon: "⚠", color: :yellow, styles: [:upcase] },
|
232
|
+
:fail => { icon: "⨯", color: :red, styles: [:upcase] },
|
233
|
+
:error => { icon: "!", color: :red, styles: [:upcase] },
|
234
|
+
:debug => { icon: "?", color: :purple, styles: [:upcase] },
|
235
|
+
}
|
236
|
+
config.types = {
|
237
|
+
:setup => { styles: [:dim, :bold, :capitalize] },
|
238
|
+
:event => { color: :yellow, styles: [:bold, :capitalize] },
|
239
|
+
:reflect => { color: :yellow, styles: [:bold, :capitalize] },
|
240
|
+
:result => { color: :yellow, styles: [:bold, :capitalize] },
|
241
|
+
:action => { color: :red, styles: [:bold, :capitalize] },
|
242
|
+
:control => { color: :blue, styles: [:bold, :capitalize] },
|
243
|
+
:experiment => { color: :green, styles: [:bold, :capitalize] },
|
244
|
+
:execute => { color: :purple, styles: [:bold, :capitalize] },
|
245
|
+
:meta => { color: :blue, styles: [:bold, :capitalize] },
|
246
|
+
}
|
247
|
+
end
|
248
|
+
|
249
|
+
return true
|
250
|
+
end
|
251
|
+
|
252
|
+
##
|
253
|
+
# Publicly accessible class methods in the class that Reflekt is prepended to.
|
254
|
+
##
|
255
|
+
module SingletonClassMethods
|
256
|
+
@@reflekt_skipped_methods = Set.new()
|
257
|
+
|
258
|
+
##
|
259
|
+
# Skip a method.
|
260
|
+
#
|
261
|
+
# @note
|
262
|
+
# Class variables cascade to child classes.
|
263
|
+
# So a reflekt_skip on the parent class will persist to the child class.
|
264
|
+
#
|
265
|
+
# @param method [Symbol] The method name.
|
266
|
+
##
|
267
|
+
def reflekt_skip(method)
|
268
|
+
@@reflekt_skipped_methods.add(method)
|
269
|
+
end
|
270
|
+
|
271
|
+
def reflekt_skipped?(method)
|
272
|
+
return true if @@reflekt_skipped_methods.include?(method)
|
273
|
+
false
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
data/lib/renderer.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Reflekt
|
2
|
+
class Renderer
|
3
|
+
|
4
|
+
def initialize(package_path, output_path)
|
5
|
+
@package_path = package_path
|
6
|
+
@output_path = output_path
|
7
|
+
end
|
8
|
+
|
9
|
+
##
|
10
|
+
# Place files in output path.
|
11
|
+
##
|
12
|
+
def render()
|
13
|
+
|
14
|
+
filenames = [
|
15
|
+
"bundle.js",
|
16
|
+
"index.html",
|
17
|
+
"package-lock.json",
|
18
|
+
"package.json",
|
19
|
+
"README.md",
|
20
|
+
"server.js"
|
21
|
+
]
|
22
|
+
|
23
|
+
filenames.each do |filename|
|
24
|
+
file = File.read(File.join(@package_path, "web", filename))
|
25
|
+
File.open(File.join(@output_path, filename), 'w+') do |f|
|
26
|
+
f.write file
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
file = File.read(File.join(@package_path, "web", "gitignore.txt"))
|
31
|
+
File.open(File.join(@output_path, ".gitignore"), 'w+') do |f|
|
32
|
+
f.write file
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/rule.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
################################################################################
|
2
|
+
# A pattern that metadata follows.
|
3
|
+
#
|
4
|
+
# @pattern Abstract class
|
5
|
+
#
|
6
|
+
# @hierachy
|
7
|
+
# 1. RuleSetAggregator
|
8
|
+
# 2. RuleSet
|
9
|
+
# 3. Rule <- YOU ARE HERE
|
10
|
+
#
|
11
|
+
# @see lib/rules for rules.
|
12
|
+
################################################################################
|
13
|
+
|
14
|
+
module Reflekt
|
15
|
+
class Rule
|
16
|
+
|
17
|
+
attr_reader :type
|
18
|
+
|
19
|
+
##
|
20
|
+
# Each rule trains on metadata to determine its boundaries.
|
21
|
+
#
|
22
|
+
# @param meta [Meta]
|
23
|
+
##
|
24
|
+
def train(meta)
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Each rule validates a value with its boundaries.
|
29
|
+
#
|
30
|
+
# @param value [Dynamic]
|
31
|
+
# @return [Boolean] Whether the value passes or fails.
|
32
|
+
##
|
33
|
+
def test(value)
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Each rule provides results.
|
38
|
+
#
|
39
|
+
# @return [Hash]
|
40
|
+
##
|
41
|
+
def result()
|
42
|
+
{}
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Each rule provides a random example that matches the rule's boundaries.
|
47
|
+
#
|
48
|
+
# @return [Dynamic] A random value.
|
49
|
+
##
|
50
|
+
def random()
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/lib/rule_set.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
################################################################################
|
2
|
+
# A collection of rules that validate metadata.
|
3
|
+
#
|
4
|
+
# @patterns
|
5
|
+
# - Dependency Injection
|
6
|
+
# - Builder
|
7
|
+
#
|
8
|
+
# @hierachy
|
9
|
+
# 1. RuleSetAggregator
|
10
|
+
# 2. RuleSet <- YOU ARE HERE
|
11
|
+
# 3. Rule
|
12
|
+
################################################################################
|
13
|
+
|
14
|
+
require 'set'
|
15
|
+
require_relative 'meta_builder'
|
16
|
+
require_relative 'meta/null_meta.rb'
|
17
|
+
|
18
|
+
module Reflekt
|
19
|
+
class RuleSet
|
20
|
+
|
21
|
+
attr_accessor :rules
|
22
|
+
|
23
|
+
##
|
24
|
+
# @param meta_map [Hash] The rules to apply to each data type.
|
25
|
+
##
|
26
|
+
def initialize(meta_map)
|
27
|
+
|
28
|
+
# The rules that apply to meta types.
|
29
|
+
@meta_map = meta_map
|
30
|
+
|
31
|
+
# The types of meta this rule set applies to.
|
32
|
+
# Rules are only validated on their supported meta type.
|
33
|
+
@meta_types = Set.new()
|
34
|
+
|
35
|
+
@rules = {}
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Train rule set on metadata.
|
41
|
+
#
|
42
|
+
# @param meta [Hash] The metadata to train on.
|
43
|
+
##
|
44
|
+
def train(meta)
|
45
|
+
|
46
|
+
# Track supported meta types.
|
47
|
+
meta_type = meta[:type]
|
48
|
+
@meta_types << meta_type
|
49
|
+
|
50
|
+
# Get rule types for this meta type.
|
51
|
+
if @meta_map.key? meta_type
|
52
|
+
@meta_map[meta_type].each do |rule_type|
|
53
|
+
|
54
|
+
# Ensure rule exists.
|
55
|
+
if @rules[rule_type].nil?
|
56
|
+
@rules[rule_type] = rule_type.new()
|
57
|
+
end
|
58
|
+
|
59
|
+
# Train rule.
|
60
|
+
@rules[rule_type].train(meta)
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# @param value [Dynamic]
|
69
|
+
##
|
70
|
+
def test(value)
|
71
|
+
|
72
|
+
result = true
|
73
|
+
meta_type = MetaBuilder.data_type_to_meta_type(value)
|
74
|
+
|
75
|
+
# Fail if value's meta type not testable by rule set.
|
76
|
+
unless @meta_types.include? meta_type
|
77
|
+
return false
|
78
|
+
end
|
79
|
+
|
80
|
+
@rules.each do |klass, rule|
|
81
|
+
if (rule.type == meta_type)
|
82
|
+
unless rule.test(value)
|
83
|
+
result = false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
return result
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Get the results of the rules.
|
94
|
+
#
|
95
|
+
# @return [Array] The rules.
|
96
|
+
##
|
97
|
+
def result()
|
98
|
+
|
99
|
+
rules = {}
|
100
|
+
|
101
|
+
@rules.each do |key, rule|
|
102
|
+
rules[rule.class] = rule.result()
|
103
|
+
end
|
104
|
+
|
105
|
+
return rules
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|