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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/lib/accessor.rb +45 -0
  3. data/lib/action.rb +127 -0
  4. data/lib/action_stack.rb +44 -0
  5. data/lib/{Clone.rb → clone.rb} +11 -11
  6. data/lib/config.rb +48 -0
  7. data/lib/control.rb +81 -0
  8. data/lib/experiment.rb +99 -0
  9. data/lib/meta.rb +75 -0
  10. data/lib/meta/array_meta.rb +32 -0
  11. data/lib/meta/boolean_meta.rb +26 -0
  12. data/lib/meta/float_meta.rb +26 -0
  13. data/lib/meta/integer_meta.rb +26 -0
  14. data/lib/meta/{NullMeta.rb → null_meta.rb} +21 -19
  15. data/lib/meta/object_meta.rb +35 -0
  16. data/lib/meta/string_meta.rb +26 -0
  17. data/lib/meta_builder.rb +100 -0
  18. data/lib/reflection.rb +123 -0
  19. data/lib/reflekt.rb +277 -0
  20. data/lib/renderer.rb +38 -0
  21. data/lib/rule.rb +54 -0
  22. data/lib/rule_set.rb +110 -0
  23. data/lib/rule_set_aggregator.rb +260 -0
  24. data/lib/rules/array_rule.rb +94 -0
  25. data/lib/rules/boolean_rule.rb +43 -0
  26. data/lib/rules/float_rule.rb +55 -0
  27. data/lib/rules/integer_rule.rb +55 -0
  28. data/lib/rules/null_rule.rb +35 -0
  29. data/lib/rules/object_rule.rb +42 -0
  30. data/lib/rules/string_rule.rb +75 -0
  31. data/lib/web/index.html +3 -4
  32. metadata +46 -29
  33. data/lib/Accessor.rb +0 -37
  34. data/lib/Action.rb +0 -88
  35. data/lib/ActionStack.rb +0 -44
  36. data/lib/Aggregator.rb +0 -260
  37. data/lib/Config.rb +0 -42
  38. data/lib/Control.rb +0 -83
  39. data/lib/Meta.rb +0 -71
  40. data/lib/MetaBuilder.rb +0 -84
  41. data/lib/Reflection.rb +0 -195
  42. data/lib/Reflekt.rb +0 -243
  43. data/lib/Renderer.rb +0 -39
  44. data/lib/Rule.rb +0 -52
  45. data/lib/RuleSet.rb +0 -109
  46. data/lib/meta/ArrayMeta.rb +0 -34
  47. data/lib/meta/BooleanMeta.rb +0 -26
  48. data/lib/meta/FloatMeta.rb +0 -26
  49. data/lib/meta/IntegerMeta.rb +0 -26
  50. data/lib/meta/StringMeta.rb +0 -26
  51. data/lib/rules/ArrayRule.rb +0 -88
  52. data/lib/rules/BooleanRule.rb +0 -47
  53. data/lib/rules/FloatRule.rb +0 -57
  54. data/lib/rules/IntegerRule.rb +0 -57
  55. data/lib/rules/NullRule.rb +0 -33
  56. 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