reflekt 1.0.9 → 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
data/lib/reflekt.rb CHANGED
@@ -3,14 +3,6 @@
3
3
  #
4
4
  # @author Maedi Prichard
5
5
  #
6
- # @flow
7
- # 1. Reflekt is prepended to a class and setup.
8
- # 2. When a class insantiates so does Reflekt.
9
- # 3. An Action is created on method call.
10
- # 4. Many Refections are created per Action.
11
- # 5. Each Reflection executes on cloned data.
12
- # 6. Flow is returned to the original method.
13
- #
14
6
  # @usage
15
7
  # class ExampleClass
16
8
  # prepend Reflekt
@@ -19,6 +11,8 @@
19
11
  require 'set'
20
12
  require 'erb'
21
13
  require 'rowdb'
14
+ require 'lit_cli'
15
+
22
16
  require_relative 'accessor'
23
17
  require_relative 'action'
24
18
  require_relative 'action_stack'
@@ -27,193 +21,238 @@ require_relative 'control'
27
21
  require_relative 'experiment'
28
22
  require_relative 'renderer'
29
23
  require_relative 'rule_set_aggregator'
30
- # Require all rules.
24
+
25
+ # Require all rules in rules directory.
31
26
  Dir[File.join(__dir__, 'rules', '*.rb')].each { |file| require_relative file }
32
27
 
33
28
  module Reflekt
29
+ include LitCLI
34
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
+ ##
35
37
  def initialize(*args)
38
+ if @@reflekt.config.enabled
39
+ @reflekt_initialized = false
36
40
 
37
- # TODO: Store counts on @@reflekt and key by instance ID.
38
- @reflekt_counts = {}
41
+ 🔥"Initialize", :info, :setup, self.class
39
42
 
40
- # Get child and parent instance methods.
41
- parent_instance_methods = self.class.superclass.instance_methods(false)
42
- child_instance_methods = self.class.instance_methods(false)
43
- instance_methods = parent_instance_methods + child_instance_methods
44
-
45
- # TODO: Include core methods like "Array.include?".
46
- instance_methods.each do |method|
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
47
48
 
48
- @reflekt_counts[method] = 0
49
+ @reflekt_initialized = true
50
+ end
49
51
 
50
- # When method called in flow.
51
- self.define_singleton_method(method) do |*args|
52
+ # Continue initialization.
53
+ super
54
+ end
52
55
 
53
- # When Reflekt enabled and control reflection has executed without error.
54
- if @@reflekt.config.enabled && !@@reflekt.error
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)
55
65
 
56
- # Get current action.
57
- action = @@reflekt.stack.peek()
66
+ return child_instance_methods + parent_instance_methods
67
+ end
58
68
 
59
- # Don't reflect when reflect limit reached or method skipped.
60
- unless (@reflekt_counts[method] >= @@reflekt.config.reflect_limit) || self.class.reflekt_skipped?(method)
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|
61
77
 
62
- # When stack empty or past action done reflecting.
63
- if action.nil? || action.has_finished_reflecting?
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
64
97
 
65
- # Create action.
66
- action = Action.new(self, method, @@reflekt.config.reflect_amount, @@reflekt.stack)
98
+ action = @@reflekt.stack.peek()
67
99
 
68
- @@reflekt.stack.push(action)
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
69
106
 
70
- end
107
+ ##
108
+ # REFLECT
109
+ ##
71
110
 
72
- ##
73
- # Reflect the action.
74
- #
75
- # The first method call in the action creates a reflection.
76
- # Then method calls are shadow actions which return to the reflection.
77
- ##
78
- if action.has_empty_experiments? && !action.is_reflecting?
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
79
114
  action.is_reflecting = true
80
115
 
81
- # Create control.
82
- control = Control.new(action, 0, @@reflekt.aggregator)
83
- action.control = control
84
-
85
- # Execute control.
86
- control.reflect(*args)
87
-
88
- # Stop reflecting when control fails to execute.
89
- if control.status == :error
90
- @@reflekt.error = true
91
- # Continue reflecting when control executes succesfully.
92
- else
93
-
94
- # Save control as a reflection.
95
- @@reflekt.db.get("reflections").push(control.serialize())
96
-
97
- # Multiple experiments per action.
98
- action.experiments.each_with_index do |value, index|
99
-
100
- # Create experiment.
101
- experiment = Experiment.new(action, index + 1, @@reflekt.aggregator)
102
- action.experiments[index] = experiment
103
-
104
- # Execute experiment.
105
- experiment.reflect(*args)
106
- @reflekt_counts[method] = @reflekt_counts[method] + 1
107
-
108
- # Save experiment.
109
- @@reflekt.db.get("reflections").push(experiment.serialize())
110
-
111
- end
112
-
113
- # Save control.
114
- @@reflekt.db.get("controls").push(control.serialize())
115
-
116
- # Save results.
117
- @@reflekt.db.write()
118
-
119
- # Render results.
120
- @@reflekt.renderer.render()
121
-
116
+ action.reflect(*args)
117
+ if action.control.status == :error
118
+ @@reflekt.error = action.control.message
122
119
  end
123
120
 
121
+ # Render results.
122
+ @@reflekt.renderer.render()
123
+
124
124
  action.is_reflecting = false
125
125
  end
126
-
126
+ else
127
+ 🔥"> Skip reflection of #{method}()", :skip, :reflect, klass.class
127
128
  end
128
129
 
129
- # Don't execute skipped methods when reflecting.
130
- unless action.is_reflecting? && self.class.reflekt_skipped?(method)
130
+ ##
131
+ # EXECUTE
132
+ ##
131
133
 
132
- # Continue action / shadow action.
134
+ unless action.is_reflecting? && klass.class.reflekt_skipped?(method)
135
+ 🔥"> Execute #{method}()", :info, :execute, klass.class
133
136
  super *args
134
-
135
137
  end
136
138
 
137
- # When Reflekt disabled or control reflection failed.
139
+ # Finish execution if control encounters unrecoverable error.
138
140
  else
139
-
140
- # Continue action.
141
+ 🔥"Reflection error, finishing original execution...", :error, :reflect, klass.class
141
142
  super *args
142
-
143
143
  end
144
144
 
145
+ # When method called in constructor.
146
+ else
147
+ p "Reflection unsupported in constructor for #{method}()", :info, :setup, klass.class
148
+ super *args
145
149
  end
146
-
147
150
  end
151
+ end
148
152
 
149
- # Continue initialization.
150
- super
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
151
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
152
167
  end
153
168
 
154
169
  ##
155
- # Provide Config instance to block.
170
+ # Configure Config singleton.
156
171
  ##
157
172
  def self.configure
173
+ reflekt_setup_class()
174
+
158
175
  yield(@@reflekt.config)
159
176
  end
160
177
 
161
178
  private
162
179
 
163
180
  def self.prepended(base)
164
-
165
181
  # Prepend class methods to the instance's singleton class.
166
182
  base.singleton_class.prepend(SingletonClassMethods)
167
183
 
168
- # Setup class.
169
- @@reflekt = Accessor.new()
170
- @@reflekt.setup ||= reflekt_setup_class
171
-
184
+ reflekt_setup_class()
172
185
  end
173
186
 
187
+ ##
174
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
+ ##
175
195
  def self.reflekt_setup_class()
176
196
 
177
- # Receive configuration.
178
- @@reflekt.config = Config.new()
179
-
180
- # Set configuration.
181
- @@reflekt.path = File.dirname(File.realpath(__FILE__))
197
+ # Only setup once.
198
+ return if defined? @@reflekt
182
199
 
183
- # Get reflections directory path from config or current action path.
184
- if @@reflekt.config.output_path
185
- @@reflekt.output_path = File.join(@@reflekt.config.output_path, @@reflekt.config.output_directory)
186
- else
187
- @@reflekt.output_path = File.join(Dir.pwd, @@reflekt.config.output_directory)
188
- end
200
+ @@reflekt = Accessor.new()
201
+ @@reflekt.config = Config.new()
202
+ @@reflekt.stack = ActionStack.new()
189
203
 
190
- # Create reflections directory.
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)
191
208
  unless Dir.exist? @@reflekt.output_path
192
209
  Dir.mkdir(@@reflekt.output_path)
193
210
  end
194
211
 
195
- # Create database.
212
+ # Setup database.
196
213
  @@reflekt.db = Rowdb.new(@@reflekt.output_path + '/db.js')
197
214
  @@reflekt.db.defaults({ :reflekt => { :api_version => 1 }})
198
- # @TODO Fix Rowdb.get(path) not returning values at path after Rowdb.push()
215
+ # TODO: Fix Rowdb.get(path) not returning values at path after Rowdb.push()
199
216
  db = @@reflekt.db.value()
200
217
 
201
- # Create shadow stack.
202
- @@reflekt.stack = ActionStack.new()
203
-
204
- # Create aggregated rule sets.
218
+ # Train aggregated rule sets.
205
219
  @@reflekt.aggregator = RuleSetAggregator.new(@@reflekt.config.meta_map)
206
220
  @@reflekt.aggregator.train(db[:controls])
207
221
 
208
- # Create renderer.
209
- @@reflekt.renderer = Renderer.new(@@reflekt.path, @@reflekt.output_path)
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
210
248
 
211
249
  return true
212
-
213
250
  end
214
251
 
252
+ ##
253
+ # Publicly accessible class methods in the class that Reflekt is prepended to.
254
+ ##
215
255
  module SingletonClassMethods
216
-
217
256
  @@reflekt_skipped_methods = Set.new()
218
257
 
219
258
  ##
@@ -233,11 +272,6 @@ module Reflekt
233
272
  return true if @@reflekt_skipped_methods.include?(method)
234
273
  false
235
274
  end
236
-
237
- #def reflekt_limit(amount)
238
- # @@reflekt.reflect_limit = amount
239
- #end
240
-
241
275
  end
242
276
 
243
277
  end
data/lib/renderer.rb CHANGED
@@ -1,41 +1,38 @@
1
1
  module Reflekt
2
- class Renderer
2
+ class Renderer
3
3
 
4
- def initialize(path, output_path)
5
-
6
- @path = path
7
- @output_path = output_path
8
-
9
- end
10
-
11
- ##
12
- # Place files in output path.
13
- ##
14
- def render()
4
+ def initialize(package_path, output_path)
5
+ @package_path = package_path
6
+ @output_path = output_path
7
+ end
15
8
 
16
- filenames = [
17
- "bundle.js",
18
- "index.html",
19
- "package-lock.json",
20
- "package.json",
21
- "README.md",
22
- "server.js"
23
- ]
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
24
29
 
25
- filenames.each do |filename|
26
- file = File.read(File.join(@path, "web", filename))
27
- File.open(File.join(@output_path, filename), 'w+') do |f|
30
+ file = File.read(File.join(@package_path, "web", "gitignore.txt"))
31
+ File.open(File.join(@output_path, ".gitignore"), 'w+') do |f|
28
32
  f.write file
29
33
  end
30
- end
31
34
 
32
- file = File.read(File.join(@path, "web", "gitignore.txt"))
33
- File.open(File.join(@output_path, ".gitignore"), 'w+') do |f|
34
- f.write file
35
35
  end
36
36
 
37
37
  end
38
-
39
-
40
- end
41
38
  end