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 +4 -4
- data/lib/Control.rb +33 -0
- data/lib/Execution.rb +47 -0
- data/lib/Reflection.rb +158 -0
- data/lib/Rule.rb +38 -0
- data/lib/Ruler.rb +112 -0
- data/lib/ShadowStack.rb +56 -0
- data/lib/reflekt.rb +142 -94
- data/lib/web/{alpine.js → script.js} +0 -0
- data/lib/web/style.css +99 -24
- data/lib/web/template.html.erb +164 -31
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21ac72b1a4f8bf9bd182f1d1ae5464d11aa87ab0f38c6fa418c9a3e73ff33a5d
|
4
|
+
data.tar.gz: bfd3a381c930d910e3b293e835877712734eecc2a51c7949e7f4747b67377058
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9b62612aa60d91bf591eae59c9797c97e9b54fdafd58d2785ed4fdfa8c7bc256cd288e13dd9e83d4fd8d11fbdbe043ce0660f6ba82878f8fd5eabb224d691b0
|
7
|
+
data.tar.gz: 9a38959ce057bdf3f4e7e2091f0aa6666be940e356b5865952a7e9e7749958d77f8beb68d2fd8f9a252f047bb45bccc756b3f9af793c85e3031a4f0cab541c74
|
data/lib/Control.rb
ADDED
@@ -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
|
data/lib/Execution.rb
ADDED
@@ -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
|
data/lib/Reflection.rb
ADDED
@@ -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
|
data/lib/Rule.rb
ADDED
@@ -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
|
data/lib/Ruler.rb
ADDED
@@ -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
|
data/lib/ShadowStack.rb
ADDED
@@ -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
|
data/lib/reflekt.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
26
|
+
# The amount of reflections to create per method call.
|
27
|
+
@@reflekt_reflect_amount = 2
|
17
28
|
|
18
|
-
|
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
|
-
|
21
|
-
@reflekt_clones = []
|
33
|
+
def initialize(*args)
|
22
34
|
|
23
|
-
|
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
|
-
#
|
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
|
-
#
|
33
|
-
|
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
|
-
|
36
|
-
|
55
|
+
# When stack empty or past execution done reflecting.
|
56
|
+
if execution.nil? || execution.has_finished_reflecting?
|
37
57
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
58
|
+
# Create execution.
|
59
|
+
execution = Execution.new(self, @@reflekt_reflect_amount)
|
60
|
+
@@reflekt_stack.push(execution)
|
42
61
|
|
43
|
-
|
44
|
-
@@reflekt_db.write()
|
62
|
+
end
|
45
63
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
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
|
118
|
+
# Continue execution / shadow execution.
|
73
119
|
super *args
|
120
|
+
|
74
121
|
end
|
75
122
|
|
76
123
|
end
|
77
124
|
|
78
|
-
# Continue
|
125
|
+
# Continue initialization.
|
79
126
|
super
|
80
127
|
|
81
|
-
# Create forks.
|
82
|
-
reflekt_fork()
|
83
|
-
|
84
128
|
end
|
85
129
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
|
-
@reflekt_forked = true
|
93
|
-
|
94
|
-
end
|
130
|
+
##
|
131
|
+
# Render results.
|
132
|
+
##
|
133
|
+
def reflekt_render()
|
95
134
|
|
96
|
-
|
135
|
+
# Get JSON.
|
136
|
+
@@reflekt_json = File.read("#{@@reflekt_output_path}/db.json")
|
97
137
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
#
|
102
|
-
|
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
|
-
#
|
113
|
-
|
114
|
-
|
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
|
-
#
|
130
|
-
|
131
|
-
|
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
|
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
|
-
#
|
178
|
+
# Build reflections directory.
|
154
179
|
if $ENV[:reflekt][:output_path]
|
155
180
|
@@reflekt_output_path = File.join($ENV[:reflekt][:output_path], 'reflections')
|
156
|
-
#
|
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 }})
|
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
|
-
@@
|
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
|
-
@@
|
230
|
+
@@reflekt_skipped_methods.add(method)
|
183
231
|
end
|
184
232
|
|
185
|
-
def
|
186
|
-
return true if @@
|
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
|
-
|
239
|
+
@@reflection_limit = amount
|
192
240
|
end
|
193
241
|
|
194
242
|
end
|
File without changes
|
data/lib/web/style.css
CHANGED
@@ -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:
|
10
|
+
max-width: 1000px;
|
10
11
|
padding: 2rem;
|
11
12
|
background: white;
|
12
13
|
}
|
13
14
|
|
14
|
-
|
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
|
-
|
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.
|
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
|
-
/*
|
36
|
-
.
|
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
|
-
.
|
44
|
-
|
45
|
-
margin-bottom: 1rem;
|
46
|
+
.status-row.pass {
|
47
|
+
background: #008C32;
|
46
48
|
}
|
47
|
-
.
|
48
|
-
|
49
|
+
.status-row.fail {
|
50
|
+
background: #D04700;
|
49
51
|
}
|
50
52
|
|
51
|
-
.
|
52
|
-
|
53
|
-
margin-left:
|
54
|
-
margin-
|
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
|
-
/*
|
71
|
-
.
|
72
|
-
|
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
|
-
.
|
75
|
-
|
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
|
}
|
data/lib/web/template.html.erb
CHANGED
@@ -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
|
-
|
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
|
21
|
-
console.log(reflections);
|
35
|
+
var data = JSON.parse(<%= @@reflekt_json %>);
|
22
36
|
var results = {};
|
23
37
|
|
24
|
-
|
25
|
-
|
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(
|
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
|
-
//
|
45
|
-
var pass_count = method.reduce(function(obj, v) {
|
46
|
-
obj[v
|
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[
|
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[
|
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[
|
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 {
|
100
|
+
return {
|
101
|
+
data: data,
|
102
|
+
results: results
|
103
|
+
};
|
84
104
|
}
|
85
105
|
|
86
106
|
</script>
|
87
107
|
|
88
|
-
<div class="container" x-data="
|
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"
|
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="
|
101
|
-
<template x-for="[class_id, klass] in Object.entries(
|
122
|
+
<ul class="classes">
|
123
|
+
<template x-for="[class_id, klass] in Object.entries(results)" :key="class_id">
|
102
124
|
|
103
|
-
<li class="
|
125
|
+
<li class="class-container">
|
104
126
|
|
105
|
-
<div class="
|
106
|
-
<h2 x-text="
|
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="
|
130
|
+
<div class="stat" x-text="`${klass.stats.pass_rate.toFixed(2)}%`"></div>
|
109
131
|
</div>
|
110
132
|
</div>
|
111
133
|
|
112
|
-
<ul class="
|
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="
|
115
|
-
|
116
|
-
<div class="
|
117
|
-
<
|
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="
|
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.
|
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/
|
40
|
+
- lib/web/script.js
|
35
41
|
- lib/web/style.css
|
36
42
|
- lib/web/template.html.erb
|
37
|
-
homepage: https://github.com/
|
43
|
+
homepage: https://github.com/refIekt/reflekt
|
38
44
|
licenses:
|
39
45
|
- MPL-2.0
|
40
46
|
metadata: {}
|