reflekt 0.9.7 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/Accessor.rb +4 -5
- data/lib/{Ruler.rb → Aggregator.rb} +70 -69
- data/lib/Control.rb +16 -6
- data/lib/Meta.rb +27 -0
- data/lib/MetaBuilder.rb +56 -0
- data/lib/Reflection.rb +71 -86
- data/lib/Reflekt.rb +26 -36
- data/lib/Renderer.rb +5 -0
- data/lib/Rule.rb +23 -19
- data/lib/RuleSet.rb +54 -28
- data/lib/ShadowStack.rb +8 -11
- data/lib/meta/IntegerMeta.rb +27 -0
- data/lib/meta/StringMeta.rb +27 -0
- data/lib/rules/IntegerRule.rb +30 -10
- data/lib/rules/StringRule.rb +36 -8
- data/lib/web/bundle.js +5 -5
- data/lib/web/gitignore.txt +7 -0
- data/lib/web/index.html +3 -0
- metadata +9 -5
- data/lib/rules/FloatRule.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5b9e88d60fea03ebe06c430b489cb1295b8d56ec76f30982721a709a406f0af
|
4
|
+
data.tar.gz: 948f53a8b72f5befda3e175029edfdb017d98b3b8fd41998ab95b5f6b0a6b95e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e70f287d10e70e3ac8ab3e01881ed6d8bb79f881c9af5530f39c7dd29997021ddf246d1129467503cfb3c6efb0589604648876c64bda917e8b970cb278a79fb3
|
7
|
+
data.tar.gz: ef005f855d7c3d1002eb059a73ecf742645841d4d3d0bd416a84b549e2e7d5b63a8bc216e8fe1404bf20c383a8e0a10c54740458e40382fa9591f9c67f3a075c
|
data/lib/Accessor.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
################################################################################
|
2
|
-
# ACCESSOR
|
3
|
-
#
|
4
2
|
# Access variables via one object to avoid polluting the caller class scope.
|
5
3
|
#
|
6
|
-
#
|
4
|
+
# @pattern Singleton
|
5
|
+
# @note Some variables are not accessed via Accessor:
|
7
6
|
# - @reflekt_counts on the instance
|
8
7
|
# - @reflekt_enabled on the instance
|
9
8
|
# - @@reflekt_skipped_methods on the instance's singleton class
|
@@ -14,7 +13,7 @@ class Accessor
|
|
14
13
|
attr_accessor :setup
|
15
14
|
attr_accessor :db
|
16
15
|
attr_accessor :stack
|
17
|
-
attr_accessor :
|
16
|
+
attr_accessor :aggregator
|
18
17
|
attr_accessor :renderer
|
19
18
|
attr_accessor :path
|
20
19
|
attr_accessor :output_path
|
@@ -26,7 +25,7 @@ class Accessor
|
|
26
25
|
@setup = nil
|
27
26
|
@db = nil
|
28
27
|
@stack = nil
|
29
|
-
@
|
28
|
+
@aggregator = nil
|
30
29
|
@renderer = nil
|
31
30
|
@path = nil
|
32
31
|
@output_path = nil
|
@@ -1,60 +1,64 @@
|
|
1
1
|
################################################################################
|
2
|
-
#
|
2
|
+
# Aggregate reflection metadata into rule sets.
|
3
|
+
# Validate reflection arguments against aggregates.
|
3
4
|
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
5
|
+
# @pattern Singleton
|
6
|
+
#
|
7
|
+
# @hierachy
|
8
|
+
# 1. Aggregator
|
9
|
+
# 2. RuleSet
|
10
|
+
# 3. Rule
|
7
11
|
################################################################################
|
8
12
|
|
9
13
|
require 'RuleSet'
|
10
14
|
|
11
|
-
class
|
15
|
+
class Aggregator
|
12
16
|
|
13
17
|
def initialize()
|
18
|
+
|
19
|
+
# Key rule sets by class and method.
|
14
20
|
@rule_sets = {}
|
21
|
+
|
15
22
|
end
|
16
23
|
|
17
24
|
##
|
18
|
-
# Get
|
25
|
+
# Get aggregated RuleSets for all inputs.
|
19
26
|
#
|
20
|
-
# @param Symbol
|
21
|
-
# @param Symbol
|
22
|
-
#
|
23
|
-
# @return Array
|
27
|
+
# @param klass [Symbol]
|
28
|
+
# @param method [Symbol]
|
29
|
+
# @return [Array]
|
24
30
|
##
|
25
31
|
def get_input_rule_sets(klass, method)
|
26
32
|
return @rule_sets.dig(klass, method, :inputs)
|
27
33
|
end
|
28
34
|
|
29
35
|
##
|
30
|
-
# Get
|
31
|
-
#
|
32
|
-
# @param Symbol klass
|
33
|
-
# @param Symbol method
|
36
|
+
# Get an aggregated RuleSet for an input.
|
34
37
|
#
|
35
|
-
# @
|
38
|
+
# @param klass [Symbol]
|
39
|
+
# @param method [Symbol]
|
40
|
+
# @return [RuleSet]
|
36
41
|
##
|
37
42
|
def get_input_rule_set(klass, method, arg_num)
|
38
43
|
@rule_sets.dig(klass, method, :inputs, arg_num)
|
39
44
|
end
|
40
45
|
|
41
46
|
##
|
42
|
-
# Get
|
43
|
-
#
|
44
|
-
# @param Symbol klass
|
45
|
-
# @param Symbol method
|
47
|
+
# Get an aggregated RuleSet for an output.
|
46
48
|
#
|
47
|
-
# @
|
49
|
+
# @param klass [Symbol]
|
50
|
+
# @param method [Symbol]
|
51
|
+
# @return [RuleSet]
|
48
52
|
##
|
49
53
|
def get_output_rule_set(klass, method)
|
50
54
|
@rule_sets.dig(klass, method, :output)
|
51
55
|
end
|
52
56
|
|
53
57
|
##
|
54
|
-
# Set
|
58
|
+
# Set an aggregated RuleSet for an input.
|
55
59
|
#
|
56
|
-
# @param Symbol
|
57
|
-
# @param Symbol
|
60
|
+
# @param klass [Symbol]
|
61
|
+
# @param method [Symbol]
|
58
62
|
##
|
59
63
|
def set_input_rule_set(klass, method, arg_num, rule_set)
|
60
64
|
# Set defaults.
|
@@ -66,11 +70,11 @@ class Ruler
|
|
66
70
|
end
|
67
71
|
|
68
72
|
##
|
69
|
-
# Set
|
73
|
+
# Set an aggregated RuleSet for an output.
|
70
74
|
#
|
71
|
-
# @param Symbol
|
72
|
-
# @param Symbol
|
73
|
-
# @param RuleSet
|
75
|
+
# @param klass [Symbol]
|
76
|
+
# @param method [Symbol]
|
77
|
+
# @param rule_set [RuleSet]
|
74
78
|
##
|
75
79
|
def set_output_rule_set(klass, method, rule_set)
|
76
80
|
# Set defaults.
|
@@ -81,57 +85,54 @@ class Ruler
|
|
81
85
|
end
|
82
86
|
|
83
87
|
##
|
84
|
-
#
|
88
|
+
# Create aggregated rule sets from reflection metadata.
|
85
89
|
#
|
86
|
-
# @param
|
87
|
-
# @param Symbol method
|
88
|
-
# @param Array controls
|
90
|
+
# @param reflections [Array] Controls with metadata.
|
89
91
|
##
|
90
|
-
def
|
92
|
+
def train(reflections)
|
93
|
+
|
94
|
+
# On first use there are no previous reflections.
|
95
|
+
return if reflections.nil?
|
96
|
+
|
97
|
+
reflections.each do |reflection|
|
98
|
+
|
99
|
+
klass = reflection[:class]
|
100
|
+
method = reflection[:method]
|
101
|
+
|
102
|
+
##
|
103
|
+
# INPUT
|
104
|
+
##
|
105
|
+
|
106
|
+
unless reflection[:inputs].nil?
|
107
|
+
reflection[:inputs].each_with_index do |meta, arg_num|
|
108
|
+
|
109
|
+
# Get rule set.
|
110
|
+
rule_set = get_input_rule_set(klass, method, arg_num)
|
111
|
+
if rule_set.nil?
|
112
|
+
rule_set = RuleSet.new()
|
113
|
+
set_input_rule_set(klass, method, arg_num, rule_set)
|
114
|
+
end
|
91
115
|
|
92
|
-
|
93
|
-
|
116
|
+
# Train on metadata.
|
117
|
+
rule_set.train(meta)
|
94
118
|
|
95
|
-
# Process inputs.
|
96
|
-
control[:inputs].each_with_index do |input, arg_num|
|
97
|
-
rule_set = get_input_rule_set(klass, method, arg_num)
|
98
|
-
if rule_set.nil?
|
99
|
-
rule_set = RuleSet.new()
|
100
|
-
set_input_rule_set(klass, method, arg_num, rule_set)
|
101
119
|
end
|
102
|
-
rule_set.load(input[:type], input[:value])
|
103
120
|
end
|
104
121
|
|
105
|
-
|
122
|
+
##
|
123
|
+
# OUTPUT
|
124
|
+
##
|
125
|
+
|
126
|
+
# Get rule set.
|
106
127
|
output_rule_set = get_output_rule_set(klass, method)
|
107
128
|
if output_rule_set.nil?
|
108
129
|
output_rule_set = RuleSet.new()
|
109
130
|
set_output_rule_set(klass, method, output_rule_set)
|
110
131
|
end
|
111
|
-
output_rule_set.load(control[:output][:type], control[:output][:value])
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
end
|
116
|
-
|
117
|
-
##
|
118
|
-
# Train RuleSets from controls.
|
119
|
-
#
|
120
|
-
# @param Symbol klass
|
121
|
-
# @param Symbol method
|
122
|
-
##
|
123
|
-
def train(klass, method)
|
124
132
|
|
125
|
-
|
126
|
-
|
127
|
-
input_rule_sets.each do |input_rule_set|
|
128
|
-
input_rule_set.train()
|
129
|
-
end
|
130
|
-
end
|
133
|
+
# Train on metadata.
|
134
|
+
output_rule_set.train(reflection[:output])
|
131
135
|
|
132
|
-
output_rule_set = get_output_rule_set(klass, method)
|
133
|
-
unless output_rule_set.nil?
|
134
|
-
output_rule_set.train()
|
135
136
|
end
|
136
137
|
|
137
138
|
end
|
@@ -139,15 +140,15 @@ class Ruler
|
|
139
140
|
##
|
140
141
|
# Validate inputs.
|
141
142
|
#
|
142
|
-
# @param
|
143
|
-
# @param
|
143
|
+
# @param inputs [Array] The method's arguments.
|
144
|
+
# @param input_rule_sets [Array] The RuleSets to validate each input with.
|
144
145
|
##
|
145
146
|
def validate_inputs(inputs, input_rule_sets)
|
146
147
|
|
147
148
|
# Default to a PASS result.
|
148
149
|
result = true
|
149
150
|
|
150
|
-
# Validate each argument against each
|
151
|
+
# Validate each argument against each RuleSet for that argument.
|
151
152
|
inputs.each_with_index do |input, arg_num|
|
152
153
|
|
153
154
|
unless input_rule_sets[arg_num].nil?
|
@@ -168,8 +169,8 @@ class Ruler
|
|
168
169
|
##
|
169
170
|
# Validate output.
|
170
171
|
#
|
171
|
-
# @param
|
172
|
-
# @param
|
172
|
+
# @param output [Dynamic] The method's return value.
|
173
|
+
# @param output_rule_set [RuleSet] The RuleSet to validate the output with.
|
173
174
|
##
|
174
175
|
def validate_output(output, output_rule_set)
|
175
176
|
|
data/lib/Control.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
|
+
################################################################################
|
2
|
+
# A shapshot of real data.
|
3
|
+
#
|
4
|
+
# @hierachy
|
5
|
+
# 1. Execution
|
6
|
+
# 2. Control
|
7
|
+
# 3. RuleSet
|
8
|
+
################################################################################
|
9
|
+
|
1
10
|
require 'Reflection'
|
11
|
+
require 'MetaBuilder'
|
2
12
|
|
3
13
|
class Control < Reflection
|
4
14
|
|
@@ -7,18 +17,18 @@ class Control < Reflection
|
|
7
17
|
#
|
8
18
|
# Creates a shadow execution stack.
|
9
19
|
#
|
10
|
-
# @param method
|
11
|
-
# @param *args
|
12
|
-
#
|
13
|
-
# @return - A reflection hash.
|
20
|
+
# @param method [Symbol] The name of the method.
|
21
|
+
# @param *args [Args] The method arguments.
|
22
|
+
# @return [Hash] A reflection hash.
|
14
23
|
##
|
15
24
|
def reflect(*args)
|
16
25
|
|
17
|
-
|
26
|
+
# Create metadata for each argument.
|
27
|
+
@inputs = MetaBuilder.create_many(args)
|
18
28
|
|
19
29
|
# Action method with new arguments.
|
20
30
|
begin
|
21
|
-
|
31
|
+
output = @clone.send(@method, *args)
|
22
32
|
# When fail.
|
23
33
|
rescue StandardError => message
|
24
34
|
@status = :fail
|
data/lib/Meta.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
################################################################################
|
2
|
+
# Meta for input and output. All meta behave the same.
|
3
|
+
#
|
4
|
+
# @pattern Abstract class.
|
5
|
+
# @see lib/meta for each meta.
|
6
|
+
################################################################################
|
7
|
+
|
8
|
+
class Meta
|
9
|
+
|
10
|
+
##
|
11
|
+
# Each meta loads values.
|
12
|
+
#
|
13
|
+
# @param value [Dynamic]
|
14
|
+
##
|
15
|
+
def load(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Each meta provides metadata.
|
20
|
+
#
|
21
|
+
# @return [Hash]
|
22
|
+
##
|
23
|
+
def result()
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/MetaBuilder.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
################################################################################
|
2
|
+
# Create Meta.
|
3
|
+
#
|
4
|
+
# @pattern Builder.
|
5
|
+
# @see lib/meta for each meta.
|
6
|
+
################################################################################
|
7
|
+
|
8
|
+
require 'Meta'
|
9
|
+
require_relative './meta/IntegerMeta'
|
10
|
+
require_relative './meta/StringMeta'
|
11
|
+
|
12
|
+
class MetaBuilder
|
13
|
+
|
14
|
+
##
|
15
|
+
# Create meta.
|
16
|
+
#
|
17
|
+
# @param value
|
18
|
+
##
|
19
|
+
def self.create(value)
|
20
|
+
|
21
|
+
meta = nil
|
22
|
+
|
23
|
+
# Creates values for matching data type.
|
24
|
+
case value.class.to_s
|
25
|
+
when "Integer"
|
26
|
+
meta = IntegerMeta.new()
|
27
|
+
when "String"
|
28
|
+
meta = StringMeta.new()
|
29
|
+
end
|
30
|
+
|
31
|
+
unless meta.nil?
|
32
|
+
meta.load(value)
|
33
|
+
end
|
34
|
+
|
35
|
+
return meta
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Create meta for multiple values.
|
41
|
+
#
|
42
|
+
# @param values
|
43
|
+
##
|
44
|
+
def self.create_many(values)
|
45
|
+
|
46
|
+
meta = []
|
47
|
+
|
48
|
+
values.each do |value|
|
49
|
+
meta << self.create(value)
|
50
|
+
end
|
51
|
+
|
52
|
+
return meta
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/lib/Reflection.rb
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
################################################################################
|
2
|
+
# A snapshot of simulated data.
|
3
|
+
#
|
4
|
+
# @nomenclature
|
5
|
+
# args/inputs/values are the same thing but at a different stage of lifecycle.
|
6
|
+
#
|
7
|
+
# @hierachy
|
8
|
+
# 1. Execution
|
9
|
+
# 2. Reflection
|
10
|
+
# 3. RuleSet
|
11
|
+
################################################################################
|
12
|
+
|
13
|
+
require 'MetaBuilder'
|
14
|
+
|
1
15
|
class Reflection
|
2
16
|
|
3
17
|
attr_accessor :clone
|
@@ -5,24 +19,24 @@ class Reflection
|
|
5
19
|
##
|
6
20
|
# Create a Reflection.
|
7
21
|
#
|
8
|
-
# @param
|
9
|
-
# @param
|
10
|
-
# @param
|
22
|
+
# @param execution [Execution] The Execution that created this Reflection.
|
23
|
+
# @param number [Integer] Multiple Reflections can be created per Execution.
|
24
|
+
# @param aggregator [Aggregator] The aggregated RuleSet for this class/method.
|
11
25
|
##
|
12
|
-
def initialize(execution, number,
|
26
|
+
def initialize(execution, number, aggregator)
|
13
27
|
|
14
28
|
@execution = execution
|
15
29
|
@unique_id = execution.unique_id + number
|
16
30
|
@number = number
|
17
31
|
|
18
32
|
# Dependency.
|
19
|
-
@
|
33
|
+
@aggregator = aggregator
|
20
34
|
|
21
35
|
# Caller.
|
22
36
|
@klass = execution.klass
|
23
37
|
@method = execution.method
|
24
38
|
|
25
|
-
#
|
39
|
+
# Metadata.
|
26
40
|
@inputs = []
|
27
41
|
@output = nil
|
28
42
|
|
@@ -40,43 +54,37 @@ class Reflection
|
|
40
54
|
# Reflect on a method.
|
41
55
|
#
|
42
56
|
# Creates a shadow execution stack.
|
43
|
-
#
|
44
|
-
# @param *args - The method's arguments.
|
45
|
-
#
|
46
|
-
# @return - A reflection hash.
|
57
|
+
# @param *args [Dynamic] The method's arguments.
|
47
58
|
##
|
48
59
|
def reflect(*args)
|
49
60
|
|
50
|
-
# Get RuleSets.
|
51
|
-
|
52
|
-
|
61
|
+
# Get aggregated RuleSets.
|
62
|
+
agg_input_rule_sets = @aggregator.get_input_rule_sets(@klass, @method)
|
63
|
+
agg_output_rule_set = @aggregator.get_output_rule_set(@klass, @method)
|
53
64
|
|
54
|
-
# Create
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
else
|
60
|
-
@inputs << arg
|
61
|
-
end
|
62
|
-
end
|
65
|
+
# Create random arguments.
|
66
|
+
new_args = randomize(args)
|
67
|
+
|
68
|
+
# Create metadata for each argument.
|
69
|
+
@inputs = MetaBuilder.create_many(new_args)
|
63
70
|
|
64
71
|
# Action method with new arguments.
|
65
72
|
begin
|
66
73
|
|
67
|
-
# Validate input with
|
68
|
-
unless
|
69
|
-
unless @
|
74
|
+
# Validate input with aggregated control RuleSets.
|
75
|
+
unless agg_input_rule_sets.nil?
|
76
|
+
unless @aggregator.validate_inputs(new_args, agg_input_rule_sets)
|
70
77
|
@status = :fail
|
71
78
|
end
|
72
79
|
end
|
73
80
|
|
74
81
|
# Run reflection.
|
75
|
-
|
82
|
+
output = @clone.send(@method, *new_args)
|
83
|
+
@output = MetaBuilder.create(output)
|
76
84
|
|
77
|
-
# Validate output with
|
78
|
-
unless
|
79
|
-
unless @
|
85
|
+
# Validate output with aggregated control RuleSets.
|
86
|
+
unless agg_output_rule_set.nil?
|
87
|
+
unless @aggregator.validate_output(output, agg_output_rule_set)
|
80
88
|
@status = :fail
|
81
89
|
end
|
82
90
|
end
|
@@ -89,6 +97,34 @@ class Reflection
|
|
89
97
|
|
90
98
|
end
|
91
99
|
|
100
|
+
##
|
101
|
+
# Create random values for each argument.
|
102
|
+
#
|
103
|
+
# @param args [Dynamic] The arguments to create random values for.
|
104
|
+
# @return [Dynamic] Random arguments.
|
105
|
+
##
|
106
|
+
def randomize(args)
|
107
|
+
|
108
|
+
random_args = []
|
109
|
+
|
110
|
+
args.each do |arg|
|
111
|
+
case arg
|
112
|
+
when Integer
|
113
|
+
random_args << rand(999)
|
114
|
+
else
|
115
|
+
random_args << arg
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
return random_args
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Get the results of the reflection.
|
125
|
+
#
|
126
|
+
# @return [Hash] Reflection metadata.
|
127
|
+
##
|
92
128
|
def result()
|
93
129
|
|
94
130
|
# The ID of the first execution in the ShadowStack.
|
@@ -107,66 +143,15 @@ class Reflection
|
|
107
143
|
:class => @klass,
|
108
144
|
:method => @method,
|
109
145
|
:status => @status,
|
110
|
-
:
|
111
|
-
:
|
112
|
-
:
|
146
|
+
:message => @message,
|
147
|
+
:inputs => [],
|
148
|
+
:output => @output,
|
113
149
|
}
|
114
|
-
|
115
|
-
|
116
|
-
end
|
117
|
-
|
118
|
-
##
|
119
|
-
# Normalize inputs.
|
120
|
-
#
|
121
|
-
# @param args - The actual inputs.
|
122
|
-
# @return - A generic inputs representation.
|
123
|
-
##
|
124
|
-
def normalize_input(args)
|
125
|
-
inputs = []
|
126
|
-
args.each do |arg|
|
127
|
-
input = {
|
128
|
-
:type => arg.class.to_s
|
129
|
-
}
|
130
|
-
if (arg.class == Array)
|
131
|
-
input[:count] = arg.count
|
132
|
-
end
|
133
|
-
inputs << input
|
150
|
+
@inputs.each do |meta|
|
151
|
+
reflection[:inputs] << meta.result()
|
134
152
|
end
|
135
|
-
inputs
|
136
|
-
end
|
137
153
|
|
138
|
-
|
139
|
-
# Normalize output.
|
140
|
-
#
|
141
|
-
# @param input - The actual output.
|
142
|
-
# @return - A generic output representation.
|
143
|
-
##
|
144
|
-
def normalize_output(arg)
|
145
|
-
|
146
|
-
input = {
|
147
|
-
:type => arg.class.to_s
|
148
|
-
}
|
149
|
-
|
150
|
-
if (arg.class == Array || arg.class == Hash)
|
151
|
-
input[:count] = arg.count
|
152
|
-
elsif (arg.class == TrueClass || arg.class == FalseClass)
|
153
|
-
input[:type] = :Boolean
|
154
|
-
end
|
155
|
-
|
156
|
-
return input
|
157
|
-
|
158
|
-
end
|
159
|
-
|
160
|
-
def normalize_value(value)
|
161
|
-
|
162
|
-
unless value.nil?
|
163
|
-
value = value.to_s.gsub(/\r?\n/, " ").to_s
|
164
|
-
if value.length >= 30
|
165
|
-
value = value[0, value.rindex(/\s/,30)].rstrip() + '...'
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
return value
|
154
|
+
return reflection
|
170
155
|
|
171
156
|
end
|
172
157
|
|