reflekt 0.7.0 → 0.9.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9282a0295c04e95cec339bfa6563a517ac12d964f6310320056669ad52b86e1
4
- data.tar.gz: 8ad30ad01a3c021d60eb645e56ddf7aea528860ca30588bb9269dac0a5cc74d8
3
+ metadata.gz: 21ac72b1a4f8bf9bd182f1d1ae5464d11aa87ab0f38c6fa418c9a3e73ff33a5d
4
+ data.tar.gz: bfd3a381c930d910e3b293e835877712734eecc2a51c7949e7f4747b67377058
5
5
  SHA512:
6
- metadata.gz: e7be8cc94f17f83c4c5131862c0ffa424c7976f4baae38d535e2a45f77318d47543493b0259dc0a7a62ad7481a380615c09030060d29ef0462c4ab6bfa6d5cb2
7
- data.tar.gz: 7fcee25ce29344e2181a8db3f3020c11ab4668560651869e81cfa5e059aa31ae35ce45c06892a19946b8509f2d592e0dc7d171ede27b1d6b5f45fa1dbff3744c
6
+ metadata.gz: e9b62612aa60d91bf591eae59c9797c97e9b54fdafd58d2785ed4fdfa8c7bc256cd288e13dd9e83d4fd8d11fbdbe043ce0660f6ba82878f8fd5eabb224d691b0
7
+ data.tar.gz: 9a38959ce057bdf3f4e7e2091f0aa6666be940e356b5865952a7e9e7749958d77f8beb68d2fd8f9a252f047bb45bccc756b3f9af793c85e3031a4f0cab541c74
@@ -0,0 +1,33 @@
1
+ require 'Reflection'
2
+
3
+ class Control < Reflection
4
+
5
+ ##
6
+ # Reflect on a method.
7
+ #
8
+ # Creates a shadow execution stack.
9
+ #
10
+ # @param method - The name of the method.
11
+ # @param *args - The method arguments.
12
+ #
13
+ # @return - A reflection hash.
14
+ ##
15
+ def reflect(*args)
16
+
17
+ @input = *args
18
+
19
+ # Action method with new arguments.
20
+ begin
21
+ @output = @clone.send(@method, *@input)
22
+ # When fail.
23
+ rescue StandardError => message
24
+ @status = FAIL
25
+ @message = message
26
+ # When pass.
27
+ else
28
+ @status = PASS
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,47 @@
1
+ class Execution
2
+
3
+ attr_accessor :object
4
+ attr_accessor :caller_id
5
+ attr_accessor :caller_class
6
+ attr_accessor :parent
7
+ attr_accessor :child
8
+ attr_accessor :control
9
+ attr_accessor :reflections
10
+ attr_accessor :is_reflecting
11
+
12
+ def initialize(object, reflection_count)
13
+
14
+ @object = object
15
+ @caller_id = object.object_id
16
+ @caller_class = object.class
17
+ @parent = nil
18
+ @child = nil
19
+
20
+ @control = []
21
+ @reflections = Array.new(reflection_count)
22
+ @is_reflecting = false
23
+
24
+ end
25
+
26
+ def has_empty_reflections?
27
+ @reflections.include? nil
28
+ end
29
+
30
+ ##
31
+ # Is the Execution currently reflecting methods?
32
+ ##
33
+ def is_reflecting?
34
+ @is_reflecting
35
+ end
36
+
37
+ def has_finished_reflecting?
38
+ if is_reflecting?
39
+ return false
40
+ end
41
+ if has_empty_reflections?
42
+ return false
43
+ end
44
+ return true
45
+ end
46
+
47
+ end
@@ -0,0 +1,158 @@
1
+ class Reflection
2
+
3
+ # Keys.
4
+ TIME = "t"
5
+ INPUT = "i"
6
+ OUTPUT = "o"
7
+ TYPE = "T"
8
+ COUNT = "C"
9
+ VALUE = "V"
10
+ STATUS = "s"
11
+ MESSAGE = "m"
12
+ # Values.
13
+ PASS = "p"
14
+ FAIL = "f"
15
+
16
+ attr_accessor :clone
17
+
18
+ def initialize(execution, method, ruler)
19
+
20
+ @execution = execution
21
+ @method = method
22
+ @ruler = ruler
23
+
24
+ # Clone the execution's object.
25
+ @clone = execution.object.clone
26
+ @clone_id = nil
27
+
28
+ # Result.
29
+ @status = nil
30
+ @time = Time.now.to_i
31
+ @inputs = []
32
+ @output = nil
33
+
34
+ end
35
+
36
+ ##
37
+ # Reflect on a method.
38
+ #
39
+ # Creates a shadow execution stack.
40
+ #
41
+ # @param method - The name of the method.
42
+ # @param *args - The method arguments.
43
+ #
44
+ # @return - A reflection hash.
45
+ ##
46
+ def reflect(*args)
47
+
48
+ # Create deviated arguments.
49
+ args.each do |arg|
50
+ case arg
51
+ when Integer
52
+ @inputs << rand(999)
53
+ else
54
+ @inputs << arg
55
+ end
56
+ end
57
+
58
+ # Action method with new arguments.
59
+ begin
60
+ # Validate input with controls.
61
+ unless @ruler.nil?
62
+ if @ruler.validate_inputs(@execution.caller_class, @method, @inputs)
63
+ @status = PASS
64
+ else
65
+ @status = FAIL
66
+ end
67
+ end
68
+ # Run reflection.
69
+ @output = @clone.send(@method, *@inputs)
70
+ # When fail.
71
+ rescue StandardError => message
72
+ @status = FAIL
73
+ @message = message
74
+ # When pass.
75
+ else
76
+ # Validate output with controls.
77
+ unless @ruler.nil?
78
+ if @ruler.validate_output(@execution.caller_class, @method, @output)
79
+ @status = PASS
80
+ else
81
+ @status = FAIL
82
+ end
83
+ return
84
+ end
85
+ @status = PASS
86
+ end
87
+
88
+ end
89
+
90
+ def result()
91
+ # Build reflection.
92
+ reflection = {
93
+ TIME => @time,
94
+ STATUS => @status,
95
+ INPUT => normalize_input(@inputs),
96
+ OUTPUT => normalize_output(@output),
97
+ MESSAGE => @message
98
+ }
99
+ end
100
+
101
+ ##
102
+ # Normalize inputs.
103
+ #
104
+ # @param args - The actual inputs.
105
+ # @return - A generic inputs representation.
106
+ ##
107
+ def normalize_input(args)
108
+ inputs = []
109
+ args.each do |arg|
110
+ input = {
111
+ TYPE => arg.class.to_s,
112
+ VALUE => normalize_value(arg)
113
+ }
114
+ if (arg.class == Array)
115
+ input[COUNT] = arg.count
116
+ end
117
+ inputs << input
118
+ end
119
+ inputs
120
+ end
121
+
122
+ ##
123
+ # Normalize output.
124
+ #
125
+ # @param input - The actual output.
126
+ # @return - A generic output representation.
127
+ ##
128
+ def normalize_output(input)
129
+
130
+ output = {
131
+ TYPE => input.class.to_s,
132
+ VALUE => normalize_value(input)
133
+ }
134
+
135
+ if (input.class == Array || input.class == Hash)
136
+ output[COUNT] = input.count
137
+ elsif (input.class == TrueClass || input.class == FalseClass)
138
+ output[TYPE] = :Boolean
139
+ end
140
+
141
+ return output
142
+
143
+ end
144
+
145
+ def normalize_value(value)
146
+
147
+ unless value.nil?
148
+ value = value.to_s.gsub(/\r?\n/, " ").to_s
149
+ if value.length >= 30
150
+ value = value[0, value.rindex(/\s/,30)].rstrip() + '...'
151
+ end
152
+ end
153
+
154
+ return value
155
+
156
+ end
157
+
158
+ end
@@ -0,0 +1,38 @@
1
+ require 'set'
2
+
3
+ class Rule
4
+
5
+ attr_accessor :type
6
+ attr_accessor :min
7
+ attr_accessor :max
8
+ attr_accessor :length
9
+ attr_accessor :types
10
+ attr_accessor :values
11
+
12
+ def initialize()
13
+
14
+ @types = Set.new
15
+ @values = Set.new
16
+ @min = nil
17
+ @max = nil
18
+ @length = nil
19
+
20
+ end
21
+
22
+ ##
23
+ # A parameter can accept multiple input types.
24
+ # Duplicates will not be added to the set.
25
+ ##
26
+ def add_type(type)
27
+ @types.add(type)
28
+ end
29
+
30
+ def add_value(value)
31
+ @values.add(value)
32
+ end
33
+
34
+ def is_number?
35
+ @types.include? Integer
36
+ end
37
+
38
+ end
@@ -0,0 +1,112 @@
1
+ require 'Rule'
2
+
3
+ class Ruler
4
+
5
+ INPUT = "i"
6
+ OUTPUT = "o"
7
+ TYPE = "T"
8
+ VALUE = "V"
9
+
10
+ def initialize()
11
+
12
+ @controls = nil
13
+
14
+ # Rules.
15
+ @inputs = []
16
+ @output = nil
17
+
18
+ end
19
+
20
+ def load(controls)
21
+
22
+ @controls = controls
23
+ @controls.each_with_index do |control, index|
24
+
25
+ # Multiple inputs.
26
+ control[INPUT].each_with_index do |input, index|
27
+ unless input.nil?
28
+
29
+ # Create rule.
30
+ if @inputs[index].nil?
31
+ rule = Rule.new()
32
+ @inputs[index] = rule
33
+ else
34
+ rule = @inputs[index]
35
+ end
36
+
37
+ # Add rules to rule.
38
+ unless input[TYPE].nil? || input[TYPE].empty?
39
+ rule.add_type(input[TYPE].class)
40
+ end
41
+ unless input[VALUE].nil? || input[VALUE].empty?
42
+ rule.add_value(input[VALUE])
43
+ end
44
+
45
+ index = index + 1
46
+ end
47
+ end
48
+
49
+ # Singular output.
50
+ output = control[OUTPUT]
51
+ unless control[OUTPUT].nil?
52
+
53
+ # Create rule.
54
+ if @output.nil?
55
+ rule = Rule.new()
56
+ @output = rule
57
+ else
58
+ rule = @output
59
+ end
60
+
61
+ ## Add rules to rule.
62
+ unless output[TYPE].nil? || output[TYPE].empty?
63
+ rule.add_type(output[TYPE])
64
+ end
65
+ unless output[VALUE].nil? || output[VALUE].empty?
66
+ rule.add_value(output[VALUE])
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+
75
+ def train()
76
+
77
+ @inputs.each do |rule|
78
+ # Get min/max.
79
+ if rule.is_number?
80
+ numbers = rule.values.select {|num| num.class == Integer }
81
+ numbers.sort!
82
+ rule.min = numbers.first
83
+ rule.max = numbers.last
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+
90
+ def validate_inputs(klass, method, inputs)
91
+ result = true
92
+ inputs.each_with_index do |value, index|
93
+ rule = @inputs[index]
94
+ if rule.is_number? && value.class == Integer
95
+ result = false if value < rule.min
96
+ result = false if value > rule.max
97
+ end
98
+ end
99
+ return result
100
+ end
101
+
102
+ def validate_output(klass, method, outputs)
103
+ result = true
104
+ rule = @output
105
+ if rule.is_number? && value.class == Integer
106
+ result = false if value < rule.min
107
+ result = false if value > rule.max
108
+ end
109
+ return result
110
+ end
111
+
112
+ end
@@ -0,0 +1,56 @@
1
+ ################################################################################
2
+ # SHADOW STACK
3
+ #
4
+ # Track the executions in a call stack.
5
+ ################################################################################
6
+
7
+ class ShadowStack
8
+
9
+ def initialize()
10
+ @bottom = nil
11
+ @top = nil
12
+ end
13
+
14
+ def peek()
15
+ @top
16
+ end
17
+
18
+ def base()
19
+ @bottom
20
+ end
21
+
22
+ ##
23
+ # Push Execution.
24
+ #
25
+ # @param object - The object being executed.
26
+ # @param args - The arguments being executed.
27
+ #
28
+ # @return Execution - The new execution.
29
+ ##
30
+ def push(execution)
31
+
32
+ # Reference previous execution.
33
+ if @bottom.nil?
34
+ @bottom = execution
35
+ else
36
+ execution.child = @top
37
+ @top.parent = execution
38
+ end
39
+
40
+ # Place new execution at the top of the stack.
41
+ @top = execution
42
+
43
+ end
44
+
45
+ def display
46
+ display_execution_tree(@bottom)
47
+ end
48
+
49
+ def display_execution_tree(execution)
50
+ p execution
51
+ unless execution.parent == nil
52
+ display_execution_tree(execution.parent)
53
+ end
54
+ end
55
+
56
+ end
@@ -1,11 +1,21 @@
1
1
  require 'set'
2
2
  require 'erb'
3
3
  require 'rowdb'
4
+ require 'Control'
5
+ require 'Execution'
6
+ require 'Reflection'
7
+ require 'Ruler'
8
+ require 'ShadowStack'
4
9
 
5
10
  ################################################################################
6
11
  # REFLEKT
7
12
  #
8
- # Usage. Prepend to the class like so:
13
+ # An Execution is created each time a method is called.
14
+ # Multiple Refections are created per Execution.
15
+ # These Reflections execute on a ShadowStack on cloned objects.
16
+ # Then flow is returned to the original method and normal execution continues.
17
+ #
18
+ # Usage:
9
19
  #
10
20
  # class ExampleClass
11
21
  # prepend Reflekt
@@ -13,122 +23,136 @@ require 'rowdb'
13
23
 
14
24
  module Reflekt
15
25
 
16
- @@reflekt_clone_count = 5
26
+ # The amount of reflections to create per method call.
27
+ @@reflekt_reflect_amount = 2
17
28
 
18
- def initialize(*args)
29
+ # Limit the amount of reflections that can be created per instance method.
30
+ # A method called thousands of times doesn't need that many reflections.
31
+ @@reflection_limit = 10
19
32
 
20
- @reflekt_forked = false
21
- @reflekt_clones = []
33
+ def initialize(*args)
22
34
 
23
- # Limit the amount of clones that can be created per instance.
24
- # A method called 30,000 times doesn't need that many reflections.
25
- @reflekt_limit = 5
26
- @reflekt_count = 0
35
+ @reflection_counts = {}
27
36
 
28
- # Override methods.
37
+ # Get instance methods.
38
+ # TODO: Include parent methods like "Array.include?".
29
39
  self.class.instance_methods(false).each do |method|
40
+
41
+ # Don't process skipped methods.
42
+ next if self.class.reflekt_skipped?(method)
43
+
44
+ @reflection_counts[method] = 0
45
+
46
+ # When method called in flow.
30
47
  self.define_singleton_method(method) do |*args|
31
48
 
32
- # When method called in flow.
33
- if @reflekt_forked
49
+ # Don't reflect when limit reached.
50
+ unless @reflection_counts[method] >= @@reflection_limit
51
+
52
+ # Get current execution.
53
+ execution = @@reflekt_stack.peek()
34
54
 
35
- if @reflekt_count < @reflekt_limit
36
- unless self.class.deflekted?(method)
55
+ # When stack empty or past execution done reflecting.
56
+ if execution.nil? || execution.has_finished_reflecting?
37
57
 
38
- # Reflekt on method.
39
- @reflekt_clones.each do |clone|
40
- reflekt_action(clone, method, *args)
41
- end
58
+ # Create execution.
59
+ execution = Execution.new(self, @@reflekt_reflect_amount)
60
+ @@reflekt_stack.push(execution)
42
61
 
43
- # Save results.
44
- @@reflekt_db.write()
62
+ end
45
63
 
46
- # Render results.
47
- @@reflekt_json = File.read("#{@@reflekt_output_path}/db.json")
48
- template = File.read("#{@@reflekt_path}/web/template.html.erb")
49
- rendered = ERB.new(template).result(binding)
50
- File.open("#{@@reflekt_output_path}/index.html", 'w+') do |f|
51
- f.write rendered
52
- end
64
+ # Get ruler.
65
+ # The method's ruler will not exist the first time the db generated.
66
+ if @@reflekt_rules.key? execution.caller_class.to_s.to_sym
67
+ ruler = @@reflekt_rules[execution.caller_class.to_s.to_sym][method.to_s]
68
+ else
69
+ ruler = nil
70
+ end
53
71
 
54
- # Add JS.
55
- alpinejs = File.read("#{@@reflekt_path}/web/alpine.js")
56
- File.open("#{@@reflekt_output_path}/alpine.js", 'w+') do |f|
57
- f.write alpinejs
58
- end
72
+ # Reflect.
73
+ # The first method call in the Execution creates a Reflection.
74
+ # Subsequent method calls are shadow executions on cloned objects.
75
+ if execution.has_empty_reflections? && !execution.is_reflecting?
76
+ execution.is_reflecting = true
59
77
 
60
- # Add CSS.
61
- stylesheet = File.read("#{@@reflekt_path}/web/style.css")
62
- File.open("#{@@reflekt_output_path}/style.css", 'w+') do |f|
63
- f.write stylesheet
64
- end
78
+ class_name = execution.caller_class.to_s
79
+ method_name = method.to_s
80
+
81
+ # Create control.
82
+ control = Control.new(execution, method, ruler)
83
+ execution.control = control
84
+
85
+ # Execute control.
86
+ control.reflect(*args)
87
+
88
+ # Save control.
89
+ @@reflekt_db.get("#{class_name}.#{method_name}.controls").push(control.result())
90
+
91
+ # Multiple reflections per execution.
92
+ execution.reflections.each_with_index do |value, index|
93
+
94
+ # Create reflection.
95
+ reflection = Reflection.new(execution, method, ruler)
96
+ execution.reflections[index] = reflection
97
+
98
+ # Execute reflection.
99
+ reflection.reflect(*args)
100
+ @reflection_counts[method] = @reflection_counts[method] + 1
101
+
102
+ # Save reflection.
103
+ @@reflekt_db.get("#{class_name}.#{method_name}.reflections").push(reflection.result())
65
104
 
66
105
  end
67
- @reflekt_count = @reflekt_count + 1
106
+
107
+ # Save results.
108
+ @@reflekt_db.write()
109
+
110
+ # Render results.
111
+ reflekt_render()
112
+
113
+ execution.is_reflecting = false
68
114
  end
69
115
 
70
116
  end
71
117
 
72
- # Continue method flow.
118
+ # Continue execution / shadow execution.
73
119
  super *args
120
+
74
121
  end
75
122
 
76
123
  end
77
124
 
78
- # Continue contructor flow.
125
+ # Continue initialization.
79
126
  super
80
127
 
81
- # Create forks.
82
- reflekt_fork()
83
-
84
128
  end
85
129
 
86
- def reflekt_fork()
87
-
88
- @@reflekt_clone_count.times do |clone|
89
- @reflekt_clones << self.clone
90
- end
91
-
92
- @reflekt_forked = true
93
-
94
- end
130
+ ##
131
+ # Render results.
132
+ ##
133
+ def reflekt_render()
95
134
 
96
- def reflekt_action(clone, method, *args)
135
+ # Get JSON.
136
+ @@reflekt_json = File.read("#{@@reflekt_output_path}/db.json")
97
137
 
98
- class_name = clone.class.to_s
99
- method_name = method.to_s
100
-
101
- # Create new arguments that are deviations on inputted type.
102
- new_args = []
103
- args.each do |arg|
104
- case arg
105
- when Integer
106
- new_args << rand(9999)
107
- else
108
- new_args << arg
109
- end
138
+ # Save HTML.
139
+ template = File.read("#{@@reflekt_path}/web/template.html.erb")
140
+ rendered = ERB.new(template).result(binding)
141
+ File.open("#{@@reflekt_output_path}/index.html", 'w+') do |f|
142
+ f.write rendered
110
143
  end
111
144
 
112
- # Action method with new arguments.
113
- begin
114
- clone.send(method, *new_args)
115
-
116
- # Build reflection.
117
- reflection = {
118
- "time" => Time.now.to_i,
119
- }
120
- # When fail.
121
- rescue StandardError => error
122
- reflection["status"] = "error"
123
- reflection["error"] = error
124
- # When pass.
125
- else
126
- reflection["status"] = "pass"
145
+ # Add JS.
146
+ javascript = File.read("#{@@reflekt_path}/web/script.js")
147
+ File.open("#{@@reflekt_output_path}/script.js", 'w+') do |f|
148
+ f.write javascript
127
149
  end
128
150
 
129
- # Save reflection.
130
- @@reflekt_db.get("#{class_name}.#{method_name}")
131
- .push(reflection)
151
+ # Add CSS.
152
+ stylesheet = File.read("#{@@reflekt_path}/web/style.css")
153
+ File.open("#{@@reflekt_output_path}/style.css", 'w+') do |f|
154
+ f.write stylesheet
155
+ end
132
156
 
133
157
  end
134
158
 
@@ -144,51 +168,75 @@ module Reflekt
144
168
  # Setup class.
145
169
  def self.reflekt_setup_class()
146
170
 
147
- # Receive configuration from host application.
171
+ # Receive configuration.
148
172
  $ENV ||= {}
149
173
  $ENV[:reflekt] ||= $ENV[:reflekt] = {}
150
174
 
175
+ # Set configuration.
151
176
  @@reflekt_path = File.dirname(File.realpath(__FILE__))
152
177
 
153
- # Create "reflections" directory in configured path.
178
+ # Build reflections directory.
154
179
  if $ENV[:reflekt][:output_path]
155
180
  @@reflekt_output_path = File.join($ENV[:reflekt][:output_path], 'reflections')
156
- # Create "reflections" directory in current execution path.
181
+ # Build reflections directory in current execution path.
157
182
  else
158
183
  @@reflekt_output_path = File.join(Dir.pwd, 'reflections')
159
184
  end
160
-
185
+ # Create reflections directory.
161
186
  unless Dir.exist? @@reflekt_output_path
162
187
  Dir.mkdir(@@reflekt_output_path)
163
188
  end
164
189
 
165
190
  # Create database.
166
191
  @@reflekt_db = Rowdb.new(@@reflekt_output_path + '/db.json')
167
- @@reflekt_db.defaults({ :reflekt => { :api_version => 1 }}).write()
192
+ @@reflekt_db.defaults({ :reflekt => { :api_version => 1 }})
193
+
194
+ # Create shadow execution stack.
195
+ @@reflekt_stack = ShadowStack.new()
196
+
197
+ # Define rules.
198
+ # TODO: Fix Rowdb.get(path) not returning data at path after Rowdb.push()?
199
+ @@reflekt_rules = {}
200
+ db = @@reflekt_db.value()
201
+ db.each do |class_name, class_values|
202
+ @@reflekt_rules[class_name] = {}
203
+ class_values.each do |method_name, method_values|
204
+ next if method_values.nil?
205
+ next unless method_values.class == Hash
206
+ if method_values.key? "controls"
207
+
208
+ ruler = Ruler.new()
209
+ ruler.load(method_values['controls'])
210
+ ruler.train()
211
+
212
+ @@reflekt_rules[class_name][method_name] = ruler
213
+ end
214
+ end
215
+ end
168
216
 
169
217
  return true
170
218
  end
171
219
 
172
220
  module SingletonClassMethods
173
221
 
174
- @@deflekted_methods = Set.new
222
+ @@reflekt_skipped_methods = Set.new
175
223
 
176
224
  ##
177
225
  # Skip a method.
178
226
  #
179
- # method - A symbol representing the method name.
227
+ # @param method - A symbol representing the method name.
180
228
  ##
181
229
  def reflekt_skip(method)
182
- @@deflekted_methods.add(method)
230
+ @@reflekt_skipped_methods.add(method)
183
231
  end
184
232
 
185
- def deflekted?(method)
186
- return true if @@deflekted_methods.include?(method)
233
+ def reflekt_skipped?(method)
234
+ return true if @@reflekt_skipped_methods.include?(method)
187
235
  false
188
236
  end
189
237
 
190
238
  def reflekt_limit(amount)
191
- @reflekt_limit = amount
239
+ @@reflection_limit = amount
192
240
  end
193
241
 
194
242
  end
File without changes
@@ -1,3 +1,4 @@
1
+ /* Layout. */
1
2
  body {
2
3
  padding: 0.5rem;
3
4
  background: #C9D2E6;
@@ -6,56 +7,60 @@ body {
6
7
 
7
8
  .container {
8
9
  margin: 0 auto;
9
- max-width: 800px;
10
+ max-width: 1000px;
10
11
  padding: 2rem;
11
12
  background: white;
12
13
  }
13
14
 
14
- #header {
15
-
16
- }
17
-
15
+ /* Header. */
18
16
  #logo {
19
17
  display: block;
20
18
  margin: 0 auto;
21
19
  max-width: 100px;
22
20
  }
23
21
 
24
- ul.panels,
22
+ /* Structure */
23
+ ul.classes,
24
+ ul.methods,
25
+ ul.controls,
25
26
  ul.reflections {
26
27
  padding-left: 0;
27
28
  }
28
29
 
29
- li.panel {
30
+ li.class-container,
31
+ li.method-container {
30
32
  list-style: none;
31
33
  padding: 1rem;
32
34
  border: 5px solid #dadcdc;
33
35
  }
34
36
 
35
- /* Reflection. */
36
- .reflection {
37
+ /* State. */
38
+ .status-row {
39
+ width: 100%;
37
40
  display: flex;
38
41
  align-items: center;
39
42
  flex-direction: row;
40
43
  color: white;
41
44
  background: #A9B6D2;
42
45
  }
43
- .reflection.class {
44
- padding: 2rem;
45
- margin-bottom: 1rem;
46
+ .status-row.pass {
47
+ background: #008C32;
46
48
  }
47
- .reflection.class h2 {
48
- margin: 0;
49
+ .status-row.fail {
50
+ background: #D04700;
49
51
  }
50
52
 
51
- .reflection.method {
52
- padding: 2rem;
53
- margin-left: 1rem;
54
- margin-bottom: 1rem;
53
+ /* Buttons. */
54
+ .method .buttons {
55
+ margin-left: auto;
56
+ margin-right: 1rem;
57
+ }
58
+ .buttons button {
59
+ padding: 1rem 2rem;
55
60
  }
56
61
 
57
62
  /* Stats. */
58
- .stats {
63
+ .class .stats {
59
64
  margin-left: auto;
60
65
  }
61
66
  .stat {
@@ -63,14 +68,84 @@ li.panel {
63
68
  font-size: 3.5rem;
64
69
  font-family: 'Merriweather', serif;
65
70
  }
71
+
72
+ /* Class. */
73
+ .class {
74
+ padding: 2rem;
75
+ margin-bottom: 1rem;
76
+ }
77
+ .class:hover {
78
+ cursor: pointer;
79
+ }
80
+ .class h2 {
81
+ margin: 0;
82
+ }
83
+
84
+ /* Method. */
85
+ .method {
86
+ padding: 2rem;
87
+ margin-left: 1rem;
88
+ margin-bottom: 1rem;
89
+ }
90
+ .method:hover {
91
+ cursor: pointer;
92
+ }
66
93
  .method .stat {
67
94
  font-size: 2.5rem;
68
95
  }
69
96
 
70
- /* State. */
71
- .reflection.pass {
72
- background: #008C32;
97
+ /* Reflection. */
98
+ .control,
99
+ .reflection {
100
+ list-style: none;
101
+ margin-left: 2rem;
102
+ display: flex;
103
+ flex-direction: row;
104
+ align-items: center;
105
+ background: #EFEFEF;
106
+ padding: 0.5rem 1.5rem;
107
+ margin-bottom: 0.3rem;
73
108
  }
74
- .reflection.fail {
75
- background: #D04700;
109
+ .control .time,
110
+ .reflection .time {
111
+ color: #777777;
112
+ margin-right: 2rem;
113
+ }
114
+
115
+ .info {
116
+ display: flex;
117
+ flex-direction: row;
118
+ align-items: center;
119
+ padding: 0.5rem 1rem;
120
+ border: 1px solid #aaa;
121
+ border-radius: 5px;
122
+ }
123
+ .info:not(:last-child) {
124
+ margin-right: 0.5rem;
125
+ }
126
+ .info h4 {
127
+ margin: 0;
128
+ color: #777777;
129
+ font-size: 1.2rem;
130
+ font-weight: normal;
131
+ }
132
+
133
+ .info-items {
134
+ display: flex;
135
+ flex-direction: row;
136
+ }
137
+ .info-item {
138
+ padding-left: 1rem;
139
+ padding-right: 1rem;
140
+ border-right: 1px solid #ccc;
141
+ }
142
+ .info-item:last-of-type {
143
+ padding-right: 0;
144
+ border-right: 0;
145
+ }
146
+ .info-item strong {
147
+ padding-bottom: 0.1rem;
148
+ }
149
+ .info-item pre {
150
+ margin: 0;
76
151
  }
@@ -1,32 +1,49 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
+
3
4
  <head>
4
5
  <meta charset="utf-8">
5
6
  <title>Reflekt</title>
6
- <meta name="description" content="">
7
- <meta name="author" content="">
7
+ <meta name="description" content="Reflective testing results.">
8
+ <meta name="author" content="Maedi Prichard">
8
9
  <meta name="viewport" content="width=device-width, initial-scale=1">
9
10
  <link rel="stylesheet" href="style.css">
10
11
  <link rel="shortcut icon" href="">
11
12
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
12
13
  <link href="https://fonts.googleapis.com/css2?family=Merriweather&display=swap" rel="stylesheet">
13
14
  </head>
15
+
14
16
  <body>
15
17
 
16
18
  <script>
17
19
 
18
- function getReflections() {
20
+ // Reflection keys.
21
+ const TIME = "t";
22
+ const INPUT = "i";
23
+ const OUTPUT = "o";
24
+ const TYPE = "T";
25
+ const COUNT = "C";
26
+ const VALUE = "V";
27
+ const STATUS = "s";
28
+ const MESSAGE = "m";
29
+ // Reflection values.
30
+ const PASS = "p";
31
+ const FAIL = "f";
32
+
33
+ function getData() {
19
34
 
20
- var reflections = JSON.parse(<%= @@reflekt_json %>);
21
- console.log(reflections);
35
+ var data = JSON.parse(<%= @@reflekt_json %>);
22
36
  var results = {};
23
37
 
24
- if ('reflekt' in reflections) {
25
- delete(reflections.reflekt);
38
+ console.log("DATA:");
39
+ console.log(data);
40
+
41
+ if ('reflekt' in data) {
42
+ delete(data.reflekt);
26
43
  }
27
44
 
28
45
  // Classes.
29
- for ([class_id, class_value] of Object.entries(reflections)) {
46
+ for ([class_id, class_value] of Object.entries(data)) {
30
47
 
31
48
  // Class pass rate.
32
49
  results[class_id] = {
@@ -41,18 +58,18 @@
41
58
  // Methods.
42
59
  for ([method_id, method] of Object.entries(class_value)) {
43
60
 
44
- // Method pass rate.
45
- var pass_count = method.reduce(function(obj, v) {
46
- obj[v.status] = (obj[v.status] || 0) + 1;
61
+ // Reflection pass rate.
62
+ var pass_count = method.reflections.reduce(function(obj, v) {
63
+ obj[v[STATUS]] = (obj[v[STATUS]] || 0) + 1;
47
64
  return obj;
48
65
  }, {});
49
66
 
50
- var pass_rate = (pass_count['pass'] / method.length) * 100;
67
+ var pass_rate = (pass_count[PASS] / method.reflections.length) * 100;
51
68
  results[class_id]['methods'][method_id] = {
52
69
  'stats': {
53
70
  'pass_rate': pass_rate,
54
- 'test_count': method.length,
55
- 'pass_count': pass_count['pass']
71
+ 'test_count': method.reflections.length,
72
+ 'pass_count': pass_count[PASS]
56
73
  }
57
74
  };
58
75
  if (pass_rate == 100) {
@@ -63,8 +80,8 @@
63
80
  }
64
81
 
65
82
  // Class pass rate.
66
- results[class_id]['stats']['test_count'] += method.length;
67
- results[class_id]['stats']['pass_count'] += pass_count['pass'];
83
+ results[class_id]['stats']['test_count'] += method.reflections.length;
84
+ results[class_id]['stats']['pass_count'] += pass_count[PASS];
68
85
 
69
86
  }
70
87
 
@@ -80,12 +97,15 @@
80
97
  }
81
98
  }
82
99
 
83
- return { refs: results };
100
+ return {
101
+ data: data,
102
+ results: results
103
+ };
84
104
  }
85
105
 
86
106
  </script>
87
107
 
88
- <div class="container" x-data="getReflections()">
108
+ <div class="container" x-data="getData()">
89
109
 
90
110
  <div id="header">
91
111
  <svg id="logo" enable-background="new 0 0 500 500" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
@@ -93,29 +113,141 @@
93
113
  <path d="m178 76.5-53.1-44-117.9 139 116 112z" fill="#d04800"/>
94
114
  <path d="m190.4 467.5h115l57.5-168h-229z" fill="#0047d0" opacity=".7"/>
95
115
  <path d="m177 467.5-81-85-92-197 115 113z" fill="#d04800" opacity=".7"/>
96
- <g fill="#008c33"><path d="m322 76.5 53.1-44 118 139-116 112z"/><path d="m320 467.5 84-85 92-197-117 113z" opacity=".7"/></g>
116
+ <g fill="#008c33"><path d="m322 76.5 53.1-44 118 139-116 112z"/>
117
+ <path d="m320 467.5 84-85 92-197-117 113z" opacity=".7"/>
118
+ </g>
97
119
  </svg>
98
120
  </div>
99
121
 
100
- <ul class="panels">
101
- <template x-for="[class_id, klass] in Object.entries(getReflections()['refs'])" :key="class_id">
122
+ <ul class="classes">
123
+ <template x-for="[class_id, klass] in Object.entries(results)" :key="class_id">
102
124
 
103
- <li class="panel">
125
+ <li class="class-container">
104
126
 
105
- <div class="reflection class" x-bind:class="` ${ klass.status } `">
106
- <h2 x-text="` ${class_id}() `"></h2>
127
+ <div class="status-row class" x-bind:class="`${klass.status}`" @click="klass['show'] = !klass['show']" :aria-expanded="klass['show'] ? 'true' : 'false'" :class="{ 'active': klass['show'] }">
128
+ <h2 x-text="`${class_id}()`"></h2>
107
129
  <div class="stats">
108
- <div class="stat" x-text="` ${klass.stats.pass_rate}% `"></div>
130
+ <div class="stat" x-text="`${klass.stats.pass_rate.toFixed(2)}%`"></div>
109
131
  </div>
110
132
  </div>
111
133
 
112
- <ul class="reflections methods">
134
+ <ul class="methods" x-show="klass['show']">
113
135
  <template x-for="[method_id, method] in Object.entries(klass['methods'])" :key="method_id">
114
- <li class="reflection method" x-bind:class="` ${ method.status } `">
115
- <h3 x-text="` ${method_id}() `"></h3>
116
- <div class="stats">
117
- <div class="stat" x-text="` ${method.stats.pass_rate}% `"></div>
136
+ <li class="method-container">
137
+
138
+ <div class="status-row method" x-bind:class="`${method.status}`" :class="{ 'active': method['show_reflections'] }" :class="{ 'active': method['show_controls'] }">
139
+ <h3 x-text="`${method_id}()`"></h3>
140
+ <div class="buttons">
141
+ <button @click="method['show_controls'] = !method['show_controls']">Controls</button>
142
+ <button @click="method['show_reflections'] = !method['show_reflections']">Reflections</button>
143
+ </div>
144
+ <div class="stats">
145
+ <div class="stat" x-text="`${method.stats.pass_rate.toFixed(2)}%`"></div>
146
+ </div>
118
147
  </div>
148
+
149
+ <ul class="reflections" x-show="method['show_reflections']">
150
+ <template x-for="[reflection_id, reflection] in Object.entries(data[class_id][method_id].reflections)">
151
+
152
+ <li class="reflection">
153
+
154
+ <div class="time" x-text="`${new Date(reflection.t * 1000).getFullYear()}/${new Date(reflection.t * 1000).getMonth() + 1}/${new Date(reflection.t * 1000).getDate()} ${new Date(reflection.t * 1000).getHours()}:${new Date(reflection.t * 1000).getMinutes()}:${new Date(reflection.t * 1000).getSeconds()}`"></div>
155
+
156
+ <template x-for="[input_id, input] in Object.entries(reflection.i)">
157
+
158
+ <div class="info">
159
+ <h4>Input</h4>
160
+ <div class="info-items">
161
+ <div class="info-item">
162
+ <strong>Type:</strong><div class="input" x-text="input.T"></div>
163
+ </div>
164
+ <template x-if="input.V != undefined">
165
+ <div class="info-item">
166
+ <strong>Value:</strong><pre><div class="output" x-text="input.V"></div></pre>
167
+ </div>
168
+ </template>
169
+ <template x-if="input.C != undefined">
170
+ <div class="info-item">
171
+ <strong>Count:</strong><div class="input" x-text="input.C"></div>
172
+ </div>
173
+ </template>
174
+ </div>
175
+ </div>
176
+
177
+ </template>
178
+
179
+ <div class="info">
180
+ <h4>Output</h4>
181
+ <div class="info-items">
182
+ <div class="info-item">
183
+ <strong>Type:</strong><div class="output" x-text="reflection.o.T"></div>
184
+ </div>
185
+ <template x-if="reflection.o.C != undefined">
186
+ <div class="info-item">
187
+ <strong>Count:</strong><div class="output" x-text="reflection.o.C"></div>
188
+ </div>
189
+ </template>
190
+ <div class="info-item">
191
+ <strong>Value:</strong><pre><div class="output" x-text="reflection.o.V"></div></pre>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </li>
196
+
197
+ </template>
198
+ </ul>
199
+
200
+ <ul class="controls" x-show="method['show_controls']">
201
+ <template x-for="[control_id, control] in Object.entries(data[class_id][method_id].controls)">
202
+
203
+ <li class="control">
204
+
205
+ <div class="time" x-text="`${new Date(control.t * 1000).getFullYear()}/${new Date(control.t * 1000).getMonth() + 1}/${new Date(control.t * 1000).getDate()} ${new Date(control.t * 1000).getHours()}:${new Date(control.t * 1000).getMinutes()}:${new Date(control.t * 1000).getSeconds()}`"></div>
206
+
207
+ <template x-for="[input_id, input] in Object.entries(control.i)">
208
+
209
+ <div class="info">
210
+ <h4>Input</h4>
211
+ <div class="info-items">
212
+ <div class="info-item">
213
+ <strong>Type:</strong><div class="input" x-text="input.T"></div>
214
+ </div>
215
+ <template x-if="input.V != undefined">
216
+ <div class="info-item">
217
+ <strong>Value:</strong><pre><div class="output" x-text="input.V"></div></pre>
218
+ </div>
219
+ </template>
220
+ <template x-if="input.C != undefined">
221
+ <div class="info-item">
222
+ <strong>Count:</strong><div class="input" x-text="input.C"></div>
223
+ </div>
224
+ </template>
225
+ </div>
226
+ </div>
227
+
228
+ </template>
229
+
230
+ <div class="info">
231
+ <h4>Output</h4>
232
+ <div class="info-items">
233
+ <div class="info-item">
234
+ <strong>Type:</strong><div class="output" x-text="control.o.T"></div>
235
+ </div>
236
+ <template x-if="control.o.C != undefined">
237
+ <div class="info-item">
238
+ <strong>Count:</strong><div class="output" x-text="control.o.C"></div>
239
+ </div>
240
+ </template>
241
+ <div class="info-item">
242
+ <strong>Value:</strong><pre><div class="output" x-text="control.o.V"></div></pre>
243
+ </div>
244
+ </div>
245
+ </div>
246
+ </li>
247
+
248
+ </template>
249
+ </ul>
250
+
119
251
  </li>
120
252
  </template>
121
253
  </ul>
@@ -127,7 +259,8 @@
127
259
 
128
260
  </div>
129
261
 
130
- <script src="alpine.js"></script>
262
+ <script src="script.js"></script>
131
263
 
132
264
  </body>
265
+
133
266
  </html>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reflekt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maedi Prichard
@@ -30,11 +30,17 @@ executables: []
30
30
  extensions: []
31
31
  extra_rdoc_files: []
32
32
  files:
33
+ - lib/Control.rb
34
+ - lib/Execution.rb
35
+ - lib/Reflection.rb
36
+ - lib/Rule.rb
37
+ - lib/Ruler.rb
38
+ - lib/ShadowStack.rb
33
39
  - lib/reflekt.rb
34
- - lib/web/alpine.js
40
+ - lib/web/script.js
35
41
  - lib/web/style.css
36
42
  - lib/web/template.html.erb
37
- homepage: https://github.com/maedi/reflekt
43
+ homepage: https://github.com/refIekt/reflekt
38
44
  licenses:
39
45
  - MPL-2.0
40
46
  metadata: {}