quality_meter 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +34 -0
- data/app/assets/javascripts/quality_meter/jquery-v1.11.2.js +4 -0
- data/app/assets/javascripts/quality_meter/quality_meter.js +312 -0
- data/app/assets/stylesheets/quality_meter/quality_meter.css +229 -0
- data/app/controllers/quality_meter/report_controller.rb +35 -0
- data/app/views/quality_meter/report/index.html.erb +276 -0
- data/config/routes.rb +4 -0
- data/lib/quality_meter.rb +170 -0
- data/lib/quality_meter/engine.rb +5 -0
- data/lib/quality_meter/string.rb +7 -0
- data/lib/quality_meter/templates/summary_report.html.erb +248 -0
- data/lib/quality_meter/version.rb +3 -0
- data/lib/tasks/quality_meter_tasks.rake +97 -0
- metadata +128 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module QaulityMeter
|
4
|
+
class ReportController < ::ApplicationController
|
5
|
+
|
6
|
+
# GET::report#index
|
7
|
+
# localhost:3000/qmeter
|
8
|
+
def index
|
9
|
+
thresholds = {}
|
10
|
+
thresholds['security_warnings_min'] = 1
|
11
|
+
thresholds['security_warnings_max'] = 100
|
12
|
+
|
13
|
+
thresholds['rails_best_practices_min'] = 30
|
14
|
+
thresholds['rails_best_practices_max'] = 100
|
15
|
+
|
16
|
+
thresholds['flog_complexity_min'] = 3
|
17
|
+
thresholds['flog_complexity_max'] = 25
|
18
|
+
|
19
|
+
thresholds['stats_ratio_min'] = 0.0
|
20
|
+
thresholds['stats_ratio_max'] = 5.0
|
21
|
+
|
22
|
+
extend QaulityMeter
|
23
|
+
# Call methods from lib/qmeter.rb
|
24
|
+
self.initialize_thresholds(thresholds)
|
25
|
+
self.generate_final_report
|
26
|
+
self.choose_color
|
27
|
+
|
28
|
+
# move report.html from root to the /public folder
|
29
|
+
FileUtils.cp('report.html', 'public/') if File.file?("#{Rails.root}/report.html")
|
30
|
+
|
31
|
+
render layout: false
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,276 @@
|
|
1
|
+
<!DOCTYPE HTML SYSTEM>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
5
|
+
<title>Quality Report</title>
|
6
|
+
<%= stylesheet_link_tag 'quality_meter/quality_meter' %>
|
7
|
+
<%= javascript_include_tag 'quality_meter/jquery-v1.11.2' %>
|
8
|
+
<%= javascript_include_tag 'quality_meter/quality_meter' %>
|
9
|
+
<script type="text/javascript">
|
10
|
+
// Custom JS
|
11
|
+
// Get previous report data
|
12
|
+
var data = <%= raw @previous_reports.to_json %>;
|
13
|
+
|
14
|
+
// Transpose array
|
15
|
+
var newArray = data[0].map(function(col, i) {
|
16
|
+
return data.map(function(row) {
|
17
|
+
return row[i]
|
18
|
+
})
|
19
|
+
});
|
20
|
+
|
21
|
+
|
22
|
+
// Get time intervals
|
23
|
+
time = newArray[newArray.length-1]
|
24
|
+
|
25
|
+
// Get data of each modules
|
26
|
+
arr_flog = newArray[newArray.length-5]
|
27
|
+
arr_stats = newArray[newArray.length-4]
|
28
|
+
arr_best_practise = newArray[newArray.length-3]
|
29
|
+
arr_security_warnings = newArray[newArray.length-2]
|
30
|
+
|
31
|
+
// Parse flog & stats data into float
|
32
|
+
arr_flog_hash = [];
|
33
|
+
for( var i = 0, len = arr_flog.length; i < len; i++ ) {
|
34
|
+
arr_flog_hash[i] = {X: time[i] , Y: parseFloat( arr_flog[i] ) };
|
35
|
+
}
|
36
|
+
arr_stats_hash = [];
|
37
|
+
for( var i = 0, len = arr_stats.length; i < len; i++ ) {
|
38
|
+
arr_stats_hash[i] = {X: time[i], Y: parseFloat( arr_stats[i] )};
|
39
|
+
}
|
40
|
+
arr_best_practise_hash = [];
|
41
|
+
for( var i = 0, len = arr_best_practise.length; i < len; i++ ) {
|
42
|
+
arr_best_practise_hash[i] ={X: time[i], Y: parseInt( arr_best_practise[i] )};
|
43
|
+
}
|
44
|
+
arr_security_warnings_hash = [];
|
45
|
+
for( var i = 0, len = arr_security_warnings.length; i < len; i++ ) {
|
46
|
+
arr_security_warnings_hash[i] ={X: time[i], Y: parseInt( arr_security_warnings[i] )};
|
47
|
+
}
|
48
|
+
|
49
|
+
var graphdata1 = {
|
50
|
+
linecolor: "Random",
|
51
|
+
values: arr_flog_hash
|
52
|
+
};
|
53
|
+
|
54
|
+
var graphdata2 = {
|
55
|
+
linecolor: "Random",
|
56
|
+
values: arr_stats_hash
|
57
|
+
};
|
58
|
+
|
59
|
+
var graphdata3 = {
|
60
|
+
linecolor: "Random",
|
61
|
+
values: arr_security_warnings_hash
|
62
|
+
};
|
63
|
+
|
64
|
+
var graphdata4 = {
|
65
|
+
linecolor: "Random",
|
66
|
+
title: 'rails practise',
|
67
|
+
values: arr_best_practise_hash
|
68
|
+
};
|
69
|
+
|
70
|
+
$(function () {
|
71
|
+
$("#Linegraph").SimpleChart({
|
72
|
+
toolwidth: "150",
|
73
|
+
toolheight: "25",
|
74
|
+
axiscolor: "#E6E6E6",
|
75
|
+
textcolor: "#6E6E6E",
|
76
|
+
title: 'flog',
|
77
|
+
data: [graphdata1],
|
78
|
+
});
|
79
|
+
|
80
|
+
$("#Linegraph1").SimpleChart({
|
81
|
+
toolwidth: "150",
|
82
|
+
toolheight: "25",
|
83
|
+
axiscolor: "#E6E6E6",
|
84
|
+
textcolor: "#6E6E6E",
|
85
|
+
title: 'stats',
|
86
|
+
data: [graphdata2],
|
87
|
+
});
|
88
|
+
|
89
|
+
$("#Linegraph2").SimpleChart({
|
90
|
+
toolwidth: "150",
|
91
|
+
toolheight: "25",
|
92
|
+
axiscolor: "#E6E6E6",
|
93
|
+
textcolor: "#6E6E6E",
|
94
|
+
title: 'security warnings',
|
95
|
+
data: [graphdata3],
|
96
|
+
});
|
97
|
+
|
98
|
+
$("#Linegraph3").SimpleChart({
|
99
|
+
toolwidth: "150",
|
100
|
+
toolheight: "25",
|
101
|
+
axiscolor: "#E6E6E6",
|
102
|
+
textcolor: "#6E6E6E",
|
103
|
+
title: 'rails practise',
|
104
|
+
data: [graphdata4],
|
105
|
+
});
|
106
|
+
});
|
107
|
+
</script>
|
108
|
+
</head>
|
109
|
+
<body class="quality_meter">
|
110
|
+
<h2 class="tophead"> Q-meter Dashboard</h2>
|
111
|
+
<%if @app_data.present?%>
|
112
|
+
<fieldset>
|
113
|
+
<legend>App Info</legend>
|
114
|
+
<table>
|
115
|
+
|
116
|
+
<%#@app_data.each do |k,v|%>
|
117
|
+
<tr>
|
118
|
+
<td><%=@app_data.keys[0].to_s.humanize%> : </td>
|
119
|
+
<td colspan="1"> <%=@app_data.values[0]%></td>
|
120
|
+
<td><%=@app_data.keys[1].to_s.humanize%> : </td>
|
121
|
+
<td colspan="1"> <%=@app_data.values[1]%></td>
|
122
|
+
</tr>
|
123
|
+
<tr>
|
124
|
+
<td><%=@app_data.keys[2].to_s.humanize%> : </td>
|
125
|
+
<td colspan="1"> <%=@app_data.values[2]%></td>
|
126
|
+
<td><%=@app_data.keys[3].to_s.humanize%> : </td>
|
127
|
+
<td colspan="1"> <%=@app_data.values[3]%></td>
|
128
|
+
</tr>
|
129
|
+
<tr>
|
130
|
+
<td><%=@app_data.keys[4].to_s.humanize%> : </td>
|
131
|
+
<td colspan="1"> <%=@app_data.values[4]%></td>
|
132
|
+
<td><%=@app_data.keys[5].to_s.humanize%> : </td>
|
133
|
+
<td colspan="1"> <%=@app_data.values[5]%></td>
|
134
|
+
</tr>
|
135
|
+
<%#end%>
|
136
|
+
</table>
|
137
|
+
</fieldset>
|
138
|
+
<%end%>
|
139
|
+
<fieldset>
|
140
|
+
<legend>Quick View</legend>
|
141
|
+
<table class="headings" align="left">
|
142
|
+
<tr>
|
143
|
+
<th>Stat</th>
|
144
|
+
<th >Rails Best Practise</th>
|
145
|
+
<th>Code Complexity</th>
|
146
|
+
<th>Security Warnings</th>
|
147
|
+
</tr>
|
148
|
+
|
149
|
+
<tr >
|
150
|
+
<th>
|
151
|
+
<a href="/metric_fu/stats.html" target="_blank">
|
152
|
+
<div class="pie" style="<%= @stats_rgy %>">
|
153
|
+
<div class="title"></div>
|
154
|
+
<div class="outer-right mask" id= "10deg">
|
155
|
+
<div class="inner-right"></div>
|
156
|
+
</div>
|
157
|
+
|
158
|
+
<div class="outer-left mask">
|
159
|
+
<div class="inner-left"></div>
|
160
|
+
</div>
|
161
|
+
<div class="content">
|
162
|
+
<span><%= @stats_code_to_test_ratio %></span>
|
163
|
+
</div>
|
164
|
+
</div>
|
165
|
+
</a>
|
166
|
+
<p></p>
|
167
|
+
<a href="/metric_fu/stats.html" target="_blank">Details</a>
|
168
|
+
</th>
|
169
|
+
<th>
|
170
|
+
<a href="/metric_fu/rails_best_practices.html" target="_blank">
|
171
|
+
<div class="pie" style="<%= @rails_best_practices_rgy %>">
|
172
|
+
<div class="title"></div>
|
173
|
+
<div class="outer-right mask" id= "10deg">
|
174
|
+
</div>
|
175
|
+
|
176
|
+
<div class="outer-left mask">
|
177
|
+
<div class="inner-left"></div>
|
178
|
+
</div>
|
179
|
+
<div class="content">
|
180
|
+
<span><%= @rails_best_practices_total %></span>
|
181
|
+
</div>
|
182
|
+
</div>
|
183
|
+
</a>
|
184
|
+
<p></p>
|
185
|
+
<a href="/metric_fu/rails_best_practices.html" target="_blank">Details</a>
|
186
|
+
</th>
|
187
|
+
<th>
|
188
|
+
<a href="/metric_fu/flog.html" target="_blank">
|
189
|
+
<div class="pie" style="<%= @flog_rgy %>">
|
190
|
+
<div class="title"></div>
|
191
|
+
<div class="outer-right mask" id= "10deg">
|
192
|
+
<div class="inner-right"></div>
|
193
|
+
</div>
|
194
|
+
|
195
|
+
<div class="outer-left mask">
|
196
|
+
<div class="inner-left"></div>
|
197
|
+
</div>
|
198
|
+
<div class="content">
|
199
|
+
<span><%= @flog_average_complexity %></span>
|
200
|
+
</div>
|
201
|
+
</div>
|
202
|
+
</a>
|
203
|
+
<p></p>
|
204
|
+
<a href="/metric_fu/flog.html" target="_blank">Details</a>
|
205
|
+
</th>
|
206
|
+
<th>
|
207
|
+
<a href="report.html" target="_blank">
|
208
|
+
<div class="pie" style="<%= @brakeman_warnings_rgy %>">
|
209
|
+
<div class="title"></div>
|
210
|
+
<div class="outer-right mask" id= "10deg"></div>
|
211
|
+
<div class="inner-right"></div>
|
212
|
+
|
213
|
+
<div class="outer-left mask">
|
214
|
+
<div class="inner-left"></div>
|
215
|
+
</div>
|
216
|
+
<div class="content">
|
217
|
+
<span><%= @warnings_count %></span>
|
218
|
+
</div>
|
219
|
+
</div>
|
220
|
+
</a>
|
221
|
+
<p></p>
|
222
|
+
<a href="report.html" target="_blank">Details</a>
|
223
|
+
</th>
|
224
|
+
</tr>
|
225
|
+
<tr><td colspan="4"></td></tr>
|
226
|
+
<tr>
|
227
|
+
<td colspan="4">
|
228
|
+
<div class="small-box green-box"></div><span class="small_span">Nice</span>
|
229
|
+
<div class="small-box yellow-box"></div><span class="small_span">Good</span>
|
230
|
+
<div class="small-box orange-box"></div><span class="small_span">Medium</span>
|
231
|
+
<div class="small-box red-box"></div><span class="small_span">Needs Improvement (Low)</span>
|
232
|
+
</td>
|
233
|
+
</tr>
|
234
|
+
</table>
|
235
|
+
|
236
|
+
</fieldset>
|
237
|
+
<fieldset>
|
238
|
+
<legend> Security Warning in Detail </legend>
|
239
|
+
<table align="right">
|
240
|
+
<% unless @brakeman_warnings.blank? %>
|
241
|
+
<% @brakeman_warnings.each do |warning| %>
|
242
|
+
<tr>
|
243
|
+
<td><%= warning[0] %></td>
|
244
|
+
<td align="right" style="font-weight:700"><%= warning[1] %></td>
|
245
|
+
</tr>
|
246
|
+
<% end %>
|
247
|
+
<% else %>
|
248
|
+
<tr><td>No Security Warnings</td></tr>
|
249
|
+
<% end %>
|
250
|
+
</table>
|
251
|
+
</fieldset>
|
252
|
+
<fieldset>
|
253
|
+
<legend> Commit Wise Reports (History) </legend>
|
254
|
+
<table align="center">
|
255
|
+
<tr>
|
256
|
+
<% unless @previous_reports.blank? %>
|
257
|
+
<tr>
|
258
|
+
<th><div id="Linegraph2" style=" height: 200px"></div></th>
|
259
|
+
<tr><th> <div id="Linegraph3" style=" height: 200px"></div></th>
|
260
|
+
</tr>
|
261
|
+
<tr>
|
262
|
+
<th><div id="Linegraph" style="height: 200px"></div></th>
|
263
|
+
</tr>
|
264
|
+
<tr><th><div id="Linegraph1" style="height: 200px"></div></th></tr>
|
265
|
+
<!-- tr bgcolor="#A5FF88">
|
266
|
+
<td colspan="2" align="center" style="font-size: 9pt;"><%#= "Generated on #{Time.now}" %></td>
|
267
|
+
</tr> -->
|
268
|
+
<% else %>
|
269
|
+
<div class='git-commit'>No commits with quality_meter yet</div>
|
270
|
+
<% end %>
|
271
|
+
</table> <!-- -->
|
272
|
+
</fieldset>
|
273
|
+
|
274
|
+
<body>
|
275
|
+
</html>
|
276
|
+
|
data/config/routes.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
require "qmeter/version"
|
2
|
+
require 'qmeter/railtie' if defined?(Rails)
|
3
|
+
require "csv"
|
4
|
+
require "qmeter/engine"
|
5
|
+
|
6
|
+
module QualityMeter
|
7
|
+
def initialize_thresholds(thresholds)
|
8
|
+
# Initialize threshold values
|
9
|
+
@security_warnings_min = thresholds['security_warnings_min']
|
10
|
+
@security_warnings_max = thresholds['security_warnings_max']
|
11
|
+
|
12
|
+
@rails_best_practices_min = thresholds['rails_best_practices_min']
|
13
|
+
@rails_best_practices_max = thresholds['rails_best_practices_max']
|
14
|
+
|
15
|
+
@flog_complexity_min = thresholds['flog_complexity_min']
|
16
|
+
@flog_complexity_max = thresholds['flog_complexity_max']
|
17
|
+
|
18
|
+
@stats_ratio_min = thresholds['stats_ratio_min']
|
19
|
+
@stats_ratio_max = thresholds['stats_ratio_max']
|
20
|
+
|
21
|
+
@app_data = []
|
22
|
+
end
|
23
|
+
|
24
|
+
def collect_brakeman_details
|
25
|
+
# Breakman source file
|
26
|
+
file = check_and_assign_file_path('report.json')
|
27
|
+
if file
|
28
|
+
data_hash = JSON.parse(file)
|
29
|
+
### change array to hash and check it contain warnings or not
|
30
|
+
if data_hash.present? && data_hash.class == Hash ? data_hash.has_key?('warnings') : data_hash[0].has_key?('warnings')
|
31
|
+
warning_type = data_hash['warnings'].map {|a| a = a['warning_type'] }
|
32
|
+
assign_warnings(warning_type, data_hash['warnings'].count)
|
33
|
+
elsif data_hash[0].has_key?('warning_type')
|
34
|
+
assign_warnings([data_hash[0]['warning_type']])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def collect_app_data
|
40
|
+
@app_data = {app_path: "/home/arpit/applications/G8way/g8way",rails_version: "3.2.11",security_warnings: 84}
|
41
|
+
# Breakman source file
|
42
|
+
file = check_and_assign_file_path('report.json')
|
43
|
+
if file
|
44
|
+
data_hash = JSON.parse(file)
|
45
|
+
# change array to hash and check it contain warnings or not
|
46
|
+
if data_hash.present? && data_hash.has_key?('scan_info')
|
47
|
+
@app_data = {application: data_hash['scan_info']['app_path'].split("/").last, ruby_version: data_hash['scan_info']['ruby_version'], rails_version: data_hash['scan_info']['rails_version'], number_of_models: data_hash['scan_info']['number_of_models'], number_of_controllers: data_hash['scan_info']['number_of_controllers'], number_of_templates: data_hash['scan_info']['number_of_templates']}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
### Assign warnings to @breakeman_warnings ###
|
53
|
+
def assign_warnings(warning_type, warnings_count=1)
|
54
|
+
@brakeman_warnings = Hash.new(0)
|
55
|
+
# warning_type = data_hash[0]['warning_type']
|
56
|
+
@warnings_count = warnings_count
|
57
|
+
warning_type.each do |v|
|
58
|
+
@brakeman_warnings[v] += 1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def collect_metric_fu_details
|
63
|
+
# parsing metric_fu report from .yml file
|
64
|
+
file = check_and_assign_file_path('tmp/metric_fu/report.yml')
|
65
|
+
if file
|
66
|
+
@surveys = YAML.load(ERB.new(file).result)
|
67
|
+
@surveys.each do |survey|
|
68
|
+
assign_status(survey) if survey.present?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
### assing ration ,complexity and bestpractice of code ###
|
74
|
+
def assign_status(survey)
|
75
|
+
case survey[0]
|
76
|
+
when :flog
|
77
|
+
@flog_average_complexity = survey[1][:average].round(1)
|
78
|
+
when :stats
|
79
|
+
@stats_code_to_test_ratio = survey[1][:code_to_test_ratio]
|
80
|
+
when :rails_best_practices
|
81
|
+
@rails_best_practices_total = survey[1][:total].first.gsub(/[^\d]/, '').to_i
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def generate_final_report
|
86
|
+
collect_app_data
|
87
|
+
collect_metric_fu_details
|
88
|
+
collect_brakeman_details
|
89
|
+
@app_root = Rails.root
|
90
|
+
get_previour_result
|
91
|
+
end
|
92
|
+
|
93
|
+
def save_report
|
94
|
+
# Save report data into the CSV
|
95
|
+
### Hide this because we are not using this currently
|
96
|
+
#flag = false
|
97
|
+
#flag = File.file?("#{Rails.root}/qmeter.csv")
|
98
|
+
CSV.open("#{Rails.root}/qmeter.csv", "a") do |csv|
|
99
|
+
#csv << ['flog','stats','rails_best_practices','warnings', 'timestamp'] if flag == false
|
100
|
+
sha = `git rev-parse HEAD`
|
101
|
+
csv << [@flog_average_complexity, @stats_code_to_test_ratio, @rails_best_practices_total, @warnings_count, sha]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def get_previour_result
|
106
|
+
# Get previous report data
|
107
|
+
@previous_reports = CSV.read("#{Rails.root}/qmeter.csv").last(4) if File.file?("#{Rails.root}/qmeter.csv")
|
108
|
+
end
|
109
|
+
|
110
|
+
def choose_color
|
111
|
+
# Check threashhold
|
112
|
+
### set color to the variables ###
|
113
|
+
@brakeman_warnings_rgy = set_color(@warnings_count, @security_warnings_max, @security_warnings_min)
|
114
|
+
@rails_best_practices_rgy = set_color(@rails_best_practices_total, @rails_best_practices_max, @rails_best_practices_min)
|
115
|
+
@flog_rgy = set_color(@flog_average_complexity, @flog_complexity_max, @flog_complexity_min)
|
116
|
+
@stats_rgy = set_stat_color(@stats_code_to_test_ratio, @stats_ratio_max, @stats_ratio_min )
|
117
|
+
end
|
118
|
+
|
119
|
+
### method to check file is exist or not ###
|
120
|
+
def check_and_assign_file_path(path)
|
121
|
+
file = "#{Rails.root}/#{path}"
|
122
|
+
File.exist?(file) ? File.read(path) : nil
|
123
|
+
end
|
124
|
+
|
125
|
+
### send proper color according to data ###
|
126
|
+
def set_color(count, max, min)
|
127
|
+
if count.present? && count > max
|
128
|
+
'background-color:#D00000;'
|
129
|
+
elsif count.present? && count > min && count < max
|
130
|
+
avg = max.to_f / 2.to_f
|
131
|
+
low_avg = avg / 2.to_f
|
132
|
+
high_avg = avg + low_avg
|
133
|
+
if count >= high_avg
|
134
|
+
'background-color:#D00000;'
|
135
|
+
elsif count >= avg && count < high_avg
|
136
|
+
'background-color:orange;'
|
137
|
+
elsif count <= low_avg
|
138
|
+
'background-color:green;'
|
139
|
+
elsif count >= low_avg
|
140
|
+
'background-color:yellow;'
|
141
|
+
else
|
142
|
+
'background-color:#D00000;'
|
143
|
+
end
|
144
|
+
else
|
145
|
+
'background-color:#006633;'
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
### @arpit: send proper color according to data ###
|
150
|
+
def set_stat_color(count, max, min)
|
151
|
+
if count.present? && count > max
|
152
|
+
'background-color:#D00000;'
|
153
|
+
elsif count.present? && count > min && count < max
|
154
|
+
avg = max.to_f / 2.to_f
|
155
|
+
low_avg = avg / 2.to_f
|
156
|
+
if count > avg
|
157
|
+
'background-color:green;'
|
158
|
+
elsif count < avg && count < low_avg
|
159
|
+
'background-color:#D00000;'
|
160
|
+
elsif count < avg && count > low_avg
|
161
|
+
'background-color:orange;'
|
162
|
+
else
|
163
|
+
'background-color:#D00000;'
|
164
|
+
end
|
165
|
+
else
|
166
|
+
'background-color:#006633;'
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|