chaos_detector 0.4.9
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 +7 -0
- data/bin/detect_chaos +31 -0
- data/lib/chaos_detector.rb +22 -0
- data/lib/chaos_detector/chaos_graphs/chaos_edge.rb +32 -0
- data/lib/chaos_detector/chaos_graphs/chaos_graph.rb +389 -0
- data/lib/chaos_detector/chaos_graphs/domain_metrics.rb +19 -0
- data/lib/chaos_detector/chaos_graphs/domain_node.rb +57 -0
- data/lib/chaos_detector/chaos_graphs/function_node.rb +112 -0
- data/lib/chaos_detector/chaos_graphs/module_node.rb +86 -0
- data/lib/chaos_detector/chaos_utils.rb +57 -0
- data/lib/chaos_detector/graph_theory/appraiser.rb +162 -0
- data/lib/chaos_detector/graph_theory/edge.rb +76 -0
- data/lib/chaos_detector/graph_theory/graph.rb +144 -0
- data/lib/chaos_detector/graph_theory/loop_detector.rb +32 -0
- data/lib/chaos_detector/graph_theory/node.rb +70 -0
- data/lib/chaos_detector/graph_theory/node_metrics.rb +68 -0
- data/lib/chaos_detector/graph_theory/reduction.rb +40 -0
- data/lib/chaos_detector/graphing/directed_graphs.rb +396 -0
- data/lib/chaos_detector/graphing/graphs.rb +129 -0
- data/lib/chaos_detector/graphing/matrix_graphs.rb +101 -0
- data/lib/chaos_detector/navigator.rb +237 -0
- data/lib/chaos_detector/options.rb +51 -0
- data/lib/chaos_detector/stacker/comp_info.rb +42 -0
- data/lib/chaos_detector/stacker/fn_info.rb +44 -0
- data/lib/chaos_detector/stacker/frame.rb +34 -0
- data/lib/chaos_detector/stacker/frame_stack.rb +63 -0
- data/lib/chaos_detector/stacker/mod_info.rb +24 -0
- data/lib/chaos_detector/tracker.rb +276 -0
- data/lib/chaos_detector/utils/core_util.rb +117 -0
- data/lib/chaos_detector/utils/fs_util.rb +49 -0
- data/lib/chaos_detector/utils/lerp_util.rb +20 -0
- data/lib/chaos_detector/utils/log_util.rb +45 -0
- data/lib/chaos_detector/utils/str_util.rb +90 -0
- data/lib/chaos_detector/utils/tensor_util.rb +21 -0
- data/lib/chaos_detector/walkman.rb +214 -0
- metadata +147 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'chaos_detector/graph_theory/graph_theory'
|
2
|
+
|
3
|
+
# Dependency Count total/normalized
|
4
|
+
#
|
5
|
+
module ChaosDetector
|
6
|
+
module ChaosGraphs
|
7
|
+
class DomainMetrics
|
8
|
+
attr_reader :dep_count
|
9
|
+
attr_reader :dep_count_norm
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@dep_count = 0
|
13
|
+
@dep_count_norm = 0.0
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s; end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'chaos_detector/graph_theory/node'
|
2
|
+
# Domain node
|
3
|
+
module ChaosDetector
|
4
|
+
module ChaosGraphs
|
5
|
+
class DomainNode < ChaosDetector::GraphTheory::Node
|
6
|
+
alias domain_name name
|
7
|
+
|
8
|
+
def initialize(domain_name: nil, node_origin: nil, is_root: false, reduction: nil)
|
9
|
+
super(name: domain_name, root: is_root, node_origin: node_origin, reduction: reduction)
|
10
|
+
end
|
11
|
+
|
12
|
+
def hash
|
13
|
+
domain_name.hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def eql?(other)
|
17
|
+
self == other
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
domain_name&.to_s == other&.domain_name&.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def title
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def subtitle
|
29
|
+
root? ? 'Root Node' : ''
|
30
|
+
end
|
31
|
+
|
32
|
+
def graph_props
|
33
|
+
props = super
|
34
|
+
if reduction
|
35
|
+
props.merge!(
|
36
|
+
cardinality_modules: reduction.reduction_count,
|
37
|
+
cardinality_functions: reduction.reduction_sum
|
38
|
+
)
|
39
|
+
end
|
40
|
+
super.merge(props)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Must be name/domain_name for comparisons:
|
44
|
+
def to_s
|
45
|
+
domain_name
|
46
|
+
end
|
47
|
+
|
48
|
+
class << self
|
49
|
+
attr_reader :root_node
|
50
|
+
def root_node(force_new: false)
|
51
|
+
@root_node = new(is_root: true) if force_new || @root_node.nil?
|
52
|
+
@root_node
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'chaos_detector/stacker/fn_info'
|
2
|
+
require 'chaos_detector/stacker/mod_info'
|
3
|
+
require 'forwardable'
|
4
|
+
require 'chaos_detector/graph_theory/edge'
|
5
|
+
require 'chaos_detector/graph_theory/node'
|
6
|
+
|
7
|
+
require 'chaos_detector/chaos_utils'
|
8
|
+
|
9
|
+
module ChaosDetector
|
10
|
+
module ChaosGraphs
|
11
|
+
class FunctionNode < ChaosDetector::GraphTheory::Node
|
12
|
+
extend Forwardable
|
13
|
+
alias fn_name name
|
14
|
+
attr_accessor :domain_name
|
15
|
+
attr_accessor :fn_path
|
16
|
+
attr_accessor :fn_line
|
17
|
+
attr_accessor :fn_line_end
|
18
|
+
|
19
|
+
# Modules to which this Function Node is associated:
|
20
|
+
attr_reader :mod_infos
|
21
|
+
def_delegator :@mod_infos, :first, :mod_info_prime
|
22
|
+
|
23
|
+
def initialize(
|
24
|
+
fn_name: nil,
|
25
|
+
fn_path: nil,
|
26
|
+
fn_line: nil,
|
27
|
+
domain_name: nil,
|
28
|
+
is_root: false,
|
29
|
+
mod_info: nil,
|
30
|
+
reduction: nil
|
31
|
+
)
|
32
|
+
super(name: fn_name, root: is_root, reduction: reduction)
|
33
|
+
|
34
|
+
@domain_name = domain_name&.to_s
|
35
|
+
@fn_path = fn_path
|
36
|
+
@fn_line = fn_line
|
37
|
+
@mod_infos = []
|
38
|
+
|
39
|
+
# Add module info, if supplied:
|
40
|
+
add_module(mod_info)
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_module(mod_info)
|
44
|
+
@mod_infos << mod_info if mod_info
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_module_attrs(mod_name:, mod_path:, mod_type:)
|
48
|
+
add_module(ChaosDetector::Stacker::ModInfo.new(mod_name: mod_name, mod_path: mod_path, mod_type: mod_type))
|
49
|
+
end
|
50
|
+
|
51
|
+
def hash
|
52
|
+
[fn_name, fn_path].hash
|
53
|
+
end
|
54
|
+
|
55
|
+
def eql?(other)
|
56
|
+
self == other
|
57
|
+
end
|
58
|
+
|
59
|
+
def ==(other)
|
60
|
+
ChaosDetector::Stacker::FnInfo.match?(self, other)
|
61
|
+
end
|
62
|
+
|
63
|
+
def domain_name
|
64
|
+
@domain_name
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
ChaosUtils.decorate_tuple([domain_name, fn_name, super, short_path], clamp: :bracket)
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_info
|
72
|
+
FnInfo.new(fn_name: fn_name, fn_line: fn_line, fn_path: fn_path)
|
73
|
+
end
|
74
|
+
|
75
|
+
def title
|
76
|
+
fn_name
|
77
|
+
end
|
78
|
+
|
79
|
+
def subtitle
|
80
|
+
'(%s)%s' % [domain_name, short_path]
|
81
|
+
end
|
82
|
+
|
83
|
+
def short_path
|
84
|
+
ChaosDetector::Utils::StrUtil.humanize_module(@fn_path, sep_token: '/')
|
85
|
+
end
|
86
|
+
|
87
|
+
class << self
|
88
|
+
attr_reader :root_node
|
89
|
+
def root_node(force_new: false)
|
90
|
+
@root_node = new(is_root: true) if force_new || @root_node.nil?
|
91
|
+
@root_node
|
92
|
+
end
|
93
|
+
|
94
|
+
def match?(obj1, obj2)
|
95
|
+
raise 'Domains differ, but fn_info is the same. Weird.' if \
|
96
|
+
obj1.fn_name == obj2.fn_name \
|
97
|
+
&& obj1.fn_path == obj2.fn_path \
|
98
|
+
&& obj1.domain_name != other.domain_name
|
99
|
+
|
100
|
+
fn_path == other.fn_path &&
|
101
|
+
(fn_name == other.fn_name || line_match?(other.fn_line, fn_line))
|
102
|
+
end
|
103
|
+
|
104
|
+
def line_match?(l1, l2)
|
105
|
+
return false if l1.nil? || l2.nil?
|
106
|
+
|
107
|
+
(l2 - l1).between?(0, 1)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'chaos_detector/stacker/mod_info'
|
2
|
+
require 'chaos_detector/graph_theory/node'
|
3
|
+
require 'chaos_detector/utils/str_util'
|
4
|
+
require 'chaos_detector/chaos_utils'
|
5
|
+
|
6
|
+
module ChaosDetector
|
7
|
+
module ChaosGraphs
|
8
|
+
# Consider putting action/event in this class and naming it accordingly
|
9
|
+
class ModuleNode < ChaosDetector::GraphTheory::Node
|
10
|
+
attr_reader :mod_type # :unknown, :module, :class
|
11
|
+
attr_reader :mod_path # :unknown, :module, :class
|
12
|
+
attr_reader :domain_name
|
13
|
+
|
14
|
+
alias mod_name name
|
15
|
+
|
16
|
+
def initialize(mod_name: nil, mod_path: nil, is_root: false, node_origin: nil, domain_name: nil, mod_type: nil, reduction: nil)
|
17
|
+
super(name: mod_name, root: is_root, node_origin: node_origin, reduction: reduction)
|
18
|
+
@domain_name = domain_name&.to_s
|
19
|
+
@mod_path = mod_path
|
20
|
+
@mod_type = mod_type
|
21
|
+
end
|
22
|
+
|
23
|
+
def hash
|
24
|
+
[mod_name, mod_type, mod_path].hash
|
25
|
+
# [mod_name, mod_type, domain_name].hash
|
26
|
+
end
|
27
|
+
|
28
|
+
def eql?(other)
|
29
|
+
self == other
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(other)
|
33
|
+
# TODO? Checking domain name vs path name due to mixins/metacoding:
|
34
|
+
mod_name == other.mod_name &&
|
35
|
+
mod_type == other.mod_type &&
|
36
|
+
mod_path == other.mod_path
|
37
|
+
end
|
38
|
+
|
39
|
+
def title
|
40
|
+
mod_name
|
41
|
+
end
|
42
|
+
|
43
|
+
def subtitle
|
44
|
+
'%s[%s]' % [short_mod_type, domain_name]
|
45
|
+
end
|
46
|
+
|
47
|
+
def graph_props
|
48
|
+
props = super
|
49
|
+
if reduction
|
50
|
+
props.merge!(
|
51
|
+
cardinality_functions: reduction.reduction_count
|
52
|
+
)
|
53
|
+
end
|
54
|
+
super.merge(props)
|
55
|
+
end
|
56
|
+
|
57
|
+
def short_mod_type
|
58
|
+
mod_type && "(#{mod_type[0]})"
|
59
|
+
end
|
60
|
+
|
61
|
+
def short_path
|
62
|
+
ChaosDetector::Utils::StrUtil.humanize_module(@mod_path, sep_token: '/')
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_info
|
66
|
+
ChaosDetector::Stacker::ModInfo.new(mod_name: mod_name, mod_path: mod_path, mod_type: mod_type)
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_k
|
70
|
+
ChaosDetector::Utils::StrUtil.snakeize([domain_name, mod_name, @mod_type, @mod_path].compact.map(&:to_s))
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_s
|
74
|
+
[super, domain_name, @mod_type, short_path].join(', ')
|
75
|
+
end
|
76
|
+
|
77
|
+
class << self
|
78
|
+
attr_reader :root_node
|
79
|
+
def root_node(force_new: false)
|
80
|
+
@root_node = new(is_root: true) if force_new || @root_node.nil?
|
81
|
+
@root_node
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'chaos_detector/utils/core_util'
|
2
|
+
require 'chaos_detector/utils/fs_util'
|
3
|
+
require 'chaos_detector/utils/str_util'
|
4
|
+
require 'chaos_detector/utils/lerp_util'
|
5
|
+
require 'chaos_detector/utils/log_util'
|
6
|
+
|
7
|
+
module ChaosUtils
|
8
|
+
class << self
|
9
|
+
def delerp(val, min:, max:)
|
10
|
+
ChaosDetector::Utils::LerpUtil.delerp(val, min: min, max: max)
|
11
|
+
end
|
12
|
+
|
13
|
+
def lerp(pct, min:, max:)
|
14
|
+
ChaosDetector::Utils::LerpUtil.lerp(val, min: min, max: max)
|
15
|
+
end
|
16
|
+
|
17
|
+
def log_msg(msg, **args)
|
18
|
+
ChaosDetector::Utils::LogUtil.log(msg, **args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def decorate(text, **args)
|
22
|
+
ChaosDetector::Utils::StrUtil.decorate(text, **args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def decorate_pair(src, dest, **args)
|
26
|
+
ChaosDetector::Utils::StrUtil.decorate_pair(src, dest, **args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def decorate_tuple(tuple, **args)
|
30
|
+
ChaosDetector::Utils::StrUtil.decorate_tuple(tuple, **args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def assert(expected_result=true, msg=nil, &block)
|
34
|
+
ChaosDetector::Utils::CoreUtil.assert(expected_result, msg, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def aught?(obj)
|
38
|
+
ChaosDetector::Utils::CoreUtil.aught?(obj)
|
39
|
+
end
|
40
|
+
|
41
|
+
def naught?(obj)
|
42
|
+
ChaosDetector::Utils::CoreUtil.naught?(obj)
|
43
|
+
end
|
44
|
+
|
45
|
+
def rel_path(dir_path, from_path:)
|
46
|
+
ChaosDetector::Utils::FSUtil.rel_path(dir_path, from_path: from_path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def squish(str)
|
50
|
+
ChaosDetector::Utils::StrUtil.squish(str)
|
51
|
+
end
|
52
|
+
|
53
|
+
def with(obj)
|
54
|
+
ChaosDetector::Utils::CoreUtil.with(obj) {yield obj}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
require 'chaos_detector/chaos_utils'
|
3
|
+
require_relative 'node_metrics'
|
4
|
+
|
5
|
+
module ChaosDetector
|
6
|
+
module GraphTheory
|
7
|
+
class Appraiser
|
8
|
+
attr_reader :cyclomatic_complexity
|
9
|
+
attr_reader :adjacency_matrix
|
10
|
+
|
11
|
+
def initialize(graph)
|
12
|
+
@graph = graph
|
13
|
+
@adjacency_matrix = nil
|
14
|
+
@cyclomatic_complexity = nil
|
15
|
+
@nodes_appraised = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def appraise(update_nodes:true)
|
19
|
+
log('Appraising nodes.')
|
20
|
+
@nodes_appraised = appraise_nodes!(update_nodes: update_nodes)
|
21
|
+
|
22
|
+
# TODO: Store adjacency (to each other node) as a node metric?
|
23
|
+
@adjacency_matrix = build_adjacency_matrix(@graph.nodes)
|
24
|
+
|
25
|
+
# log('Measuring cyclomatic complexity.')
|
26
|
+
# measure_cyclomatic_complexity
|
27
|
+
|
28
|
+
log("Performed appraisal: %s" % to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
def metrics_for(node:)
|
32
|
+
raise ArgumentError, 'Node is required' if node.nil?
|
33
|
+
raise ArgumentError, ('Node [%s] has no metrics' % node) if !@nodes_appraised&.include?(node)
|
34
|
+
log('has no metrics', object: node) if !@nodes_appraised&.include?(node)
|
35
|
+
@nodes_appraised[node]
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
format('N: %d, E: %d', @graph.nodes.length, @graph.edges.length)
|
40
|
+
end
|
41
|
+
|
42
|
+
def report
|
43
|
+
buffy = [to_s]
|
44
|
+
|
45
|
+
# buffy << "Circular References #{@circular_paths.length} / #{@circular_paths.uniq.length}"
|
46
|
+
# buffy.concat(@circular_paths.map do |p|
|
47
|
+
# ' ' + p.map(&:title).join(' -> ')
|
48
|
+
# end)
|
49
|
+
|
50
|
+
# Gather nodes:
|
51
|
+
buffy << 'Nodes:'
|
52
|
+
buffy.concat(@nodes_appraised.map { |n, m| " (#{n.title})[#{n.subtitle}]: #{m}" })
|
53
|
+
|
54
|
+
buffy.join("\n")
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns Hash<Node, NodeMetrics>
|
58
|
+
# update_nodes: Updates all nodes' :graph_props to appraisal metrics hash:
|
59
|
+
def appraise_nodes!(update_nodes: true)
|
60
|
+
node_metrics = @graph.nodes.map do |node|
|
61
|
+
metrics = appraise_node(node)
|
62
|
+
[node, metrics]
|
63
|
+
end.to_h
|
64
|
+
|
65
|
+
if update_nodes
|
66
|
+
node_metrics.each do |node, metrics|
|
67
|
+
node.graph_props.merge!(metrics.to_h)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
node_metrics
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_adjacency_matrix(nodes)
|
75
|
+
matrix_dim = nodes.size
|
76
|
+
# nodes = @graph.nodes
|
77
|
+
Matrix.build(matrix_dim) do |row, col|
|
78
|
+
node_src = nodes[row]
|
79
|
+
node_dep = nodes[col]
|
80
|
+
# puts "Adjacency found for #{node_src}, #{node_dep}: #{edge&.reduction} / #{edge&.reduction&.reduction_sum.to_i}"
|
81
|
+
# puts "Adjacency found for #{row}:#{col} -> #{node_src}, #{node_dep}: #{adjacency?(node_src, node_dep)}"
|
82
|
+
adjacency?(node_src, node_dep)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# For each node, measure fan-in(Ca) and fan-out(Ce)
|
89
|
+
def appraise_node(node)
|
90
|
+
circular_routes, terminal_routes = appraise_node_routes(node)
|
91
|
+
ChaosDetector::GraphTheory::NodeMetrics.new(
|
92
|
+
node,
|
93
|
+
afference: @graph.edges.count { |e| e.dep_node == node },
|
94
|
+
efference: @graph.edges.count { |e| e.src_node == node },
|
95
|
+
circular_routes: circular_routes,
|
96
|
+
terminal_routes: terminal_routes
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
def log(msg, **opts)
|
101
|
+
ChaosUtils.log_msg(msg, subject: 'Appraiser', **opts)
|
102
|
+
end
|
103
|
+
|
104
|
+
def fan_out_edges(node)
|
105
|
+
@graph.edges.find_all { |e| e.src_node == node }
|
106
|
+
end
|
107
|
+
|
108
|
+
def fan_out_nodes(node)
|
109
|
+
@graph.edges.find_all { |e| e.src_node == node }.map(&:dep_node)
|
110
|
+
end
|
111
|
+
|
112
|
+
def fan_in_nodes(node)
|
113
|
+
@graph.edges.find_all { |e| e.dep_node == node }.map(&:src_node)
|
114
|
+
end
|
115
|
+
|
116
|
+
def appraise_node_routes(_node)
|
117
|
+
# Traverse starting at each node to see if
|
118
|
+
# and how many ways we come back to ourselves
|
119
|
+
terminal_routes = []
|
120
|
+
circular_routes = []
|
121
|
+
|
122
|
+
[terminal_routes, circular_routes]
|
123
|
+
end
|
124
|
+
|
125
|
+
def adjacency?(node_src, node_dest)
|
126
|
+
edge = @graph.edges.find{|e| e.src_node == node_src && e.dep_node == node_dest }
|
127
|
+
edge&.reduction&.reduction_sum.to_i
|
128
|
+
end
|
129
|
+
|
130
|
+
# Coupling: Each node couplet (Example for 100 nodes, we'd have 100 * 99 potential couplets)
|
131
|
+
# Capture how many other nodes depend upon both nodes in couplet [directly, indirectly]
|
132
|
+
# Capture how many other nodes from other domains depend upon both [directly, indirectly]
|
133
|
+
# TODO??
|
134
|
+
def node_matrix
|
135
|
+
node_matrix = Matrix.build(@graph.nodes.length) do |row, col|
|
136
|
+
end
|
137
|
+
node_matrix
|
138
|
+
end
|
139
|
+
|
140
|
+
def measure_cyclomatic_complexity
|
141
|
+
@circular_paths = []
|
142
|
+
@full_paths = []
|
143
|
+
traverse_nodes([@graph.root_node])
|
144
|
+
@path_count_uniq = @circular_paths.uniq.count + @full_paths.uniq.count
|
145
|
+
@cyclomatic_complexity = @graph.edges.count - @graph.nodes.count + (2 * @path_count_uniq)
|
146
|
+
end
|
147
|
+
|
148
|
+
# @return positive integer indicating distance in number of vertices
|
149
|
+
# from node_src to node_dep. If multiple routes, calculate shortest:
|
150
|
+
def node_distance(node_src, node_dep); end
|
151
|
+
|
152
|
+
def normalize(ary, property, norm_property)
|
153
|
+
vector = Vector.elements(ary.map { |obj| obj.send(property)})
|
154
|
+
vector = vector.normalize
|
155
|
+
ary.each_with_index do |obj, i|
|
156
|
+
obj.send("#{norm_property}=", vector[i])
|
157
|
+
end
|
158
|
+
ary
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|