git_stats 1.0.0
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.
- data/.gitignore +21 -0
- data/.gitmodules +3 -0
- data/.rspec +1 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +44 -0
- data/Rakefile +1 -0
- data/bin/git_stats +11 -0
- data/config/locales/en.yml +56 -0
- data/config/locales/pl.yml +56 -0
- data/config/locales/pl_default.yml +224 -0
- data/git_stats.gemspec +27 -0
- data/lib/git_stats.rb +15 -0
- data/lib/git_stats/base.rb +30 -0
- data/lib/git_stats/by_field_finder.rb +7 -0
- data/lib/git_stats/cli.rb +17 -0
- data/lib/git_stats/core_extensions/hash.rb +9 -0
- data/lib/git_stats/core_extensions/string.rb +6 -0
- data/lib/git_stats/core_extensions/symbol.rb +6 -0
- data/lib/git_stats/generator.rb +32 -0
- data/lib/git_stats/git_data/activity.rb +78 -0
- data/lib/git_stats/git_data/author.rb +67 -0
- data/lib/git_stats/git_data/blob.rb +38 -0
- data/lib/git_stats/git_data/command_parser.rb +33 -0
- data/lib/git_stats/git_data/command_runner.rb +10 -0
- data/lib/git_stats/git_data/commit.rb +63 -0
- data/lib/git_stats/git_data/repo.rb +137 -0
- data/lib/git_stats/git_data/short_stat.rb +33 -0
- data/lib/git_stats/hash_initializable.rb +7 -0
- data/lib/git_stats/i18n.rb +2 -0
- data/lib/git_stats/stats_view/charts/activity_charts.rb +70 -0
- data/lib/git_stats/stats_view/charts/authors_charts.rb +35 -0
- data/lib/git_stats/stats_view/charts/chart.rb +119 -0
- data/lib/git_stats/stats_view/charts/charts.rb +35 -0
- data/lib/git_stats/stats_view/charts/repo_charts.rb +53 -0
- data/lib/git_stats/stats_view/template.rb +20 -0
- data/lib/git_stats/stats_view/view.rb +72 -0
- data/lib/git_stats/stats_view/view_data.rb +31 -0
- data/lib/git_stats/version.rb +4 -0
- data/spec/by_field_finder_spec.rb +25 -0
- data/spec/factories.rb +25 -0
- data/spec/git_data/activity_spec.rb +38 -0
- data/spec/git_data/author_spec.rb +25 -0
- data/spec/git_data/blob_spec.rb +25 -0
- data/spec/git_data/cli_spec.rb +20 -0
- data/spec/git_data/command_observer_spec.rb +35 -0
- data/spec/git_data/commit_range_spec.rb +47 -0
- data/spec/git_data/commit_spec.rb +48 -0
- data/spec/git_data/generator_spec.rb +46 -0
- data/spec/git_data/repo_spec.rb +46 -0
- data/spec/git_data/short_stat_spec.rb +28 -0
- data/spec/hash_extension_spec.rb +26 -0
- data/spec/integration/activity_spec.rb +33 -0
- data/spec/integration/author_spec.rb +67 -0
- data/spec/integration/file_spec.rb +31 -0
- data/spec/integration/repo_spec.rb +68 -0
- data/spec/integration/shared.rb +37 -0
- data/spec/spec_helper.rb +13 -0
- data/templates/activity/_activity.haml +101 -0
- data/templates/activity/by_date.haml +1 -0
- data/templates/activity/day_of_week.haml +1 -0
- data/templates/activity/hour_of_day.haml +1 -0
- data/templates/activity/hour_of_week.haml +1 -0
- data/templates/activity/month_of_year.haml +1 -0
- data/templates/activity/year.haml +1 -0
- data/templates/activity/year_month.haml +1 -0
- data/templates/assets/bootstrap/css/bootstrap-responsive.css +1058 -0
- data/templates/assets/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/templates/assets/bootstrap/css/bootstrap.css +5774 -0
- data/templates/assets/bootstrap/css/bootstrap.min.css +9 -0
- data/templates/assets/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/templates/assets/bootstrap/img/glyphicons-halflings.png +0 -0
- data/templates/assets/bootstrap/js/bootstrap.js +2027 -0
- data/templates/assets/bootstrap/js/bootstrap.min.js +6 -0
- data/templates/assets/highstock.js +305 -0
- data/templates/assets/jquery.min.js +2 -0
- data/templates/authors/_authors.haml +66 -0
- data/templates/authors/best_authors.haml +1 -0
- data/templates/authors/changed_lines_by_author_by_date.haml +1 -0
- data/templates/authors/commits_sum_by_author_by_date.haml +1 -0
- data/templates/authors/deletions_by_author_by_date.haml +1 -0
- data/templates/authors/insertions_by_author_by_date.haml +1 -0
- data/templates/files/_files.haml +15 -0
- data/templates/files/by_date.haml +1 -0
- data/templates/files/by_extension.haml +1 -0
- data/templates/general.haml +28 -0
- data/templates/layout.haml +29 -0
- data/templates/lines/_lines.haml +15 -0
- data/templates/lines/by_date.haml +1 -0
- data/templates/lines/by_extension.haml +1 -0
- data/templates/static/index.html +5 -0
- metadata +268 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
require 'git_stats/hash_initializable'
|
|
3
|
+
|
|
4
|
+
module GitStats
|
|
5
|
+
module GitData
|
|
6
|
+
class Repo
|
|
7
|
+
include HashInitializable
|
|
8
|
+
|
|
9
|
+
attr_reader :path
|
|
10
|
+
|
|
11
|
+
delegate :files, :files_by_extension, :files_by_extension_count, :lines_by_extension,
|
|
12
|
+
:files_count, :binary_files, :text_files, :lines_count, to: :last_commit
|
|
13
|
+
|
|
14
|
+
def initialize(params)
|
|
15
|
+
super(params)
|
|
16
|
+
@path = File.expand_path(@path)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def authors
|
|
20
|
+
@authors ||= run_and_parse("git shortlog -se #{commit_range}").map do |author|
|
|
21
|
+
Author.new(repo: self, name: author[:name], email: author[:email])
|
|
22
|
+
end.extend(ByFieldFinder)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def commits
|
|
26
|
+
@commits ||= run_and_parse("git rev-list --pretty=format:'%h|%at|%ai|%aE' #{commit_range} | grep -v commit").map do |commit_line|
|
|
27
|
+
Commit.new(
|
|
28
|
+
repo: self,
|
|
29
|
+
sha: commit_line[:sha],
|
|
30
|
+
stamp: commit_line[:stamp],
|
|
31
|
+
date: DateTime.parse(commit_line[:date]),
|
|
32
|
+
author: authors.by_email(commit_line[:author_email])
|
|
33
|
+
)
|
|
34
|
+
end.sort_by! { |e| e.date }.extend(ByFieldFinder)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def commits_period
|
|
38
|
+
commits.map(&:date).minmax
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def commits_count_by_author(limit = 4)
|
|
42
|
+
Hash[authors.map { |author| [author, author.commits.size] }.sort_by { |author, commits| -commits }[0..limit]]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
[:insertions, :deletions, :changed_lines].each do |method|
|
|
46
|
+
define_method "#{method}_by_author" do |limit = 4|
|
|
47
|
+
Hash[authors.map { |author| [author, author.send(method)] }.sort_by { |author, lines| -lines }[0..limit]]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def files_count_by_date
|
|
52
|
+
@files_count_each_day ||= Hash[commits.map { |commit|
|
|
53
|
+
[commit.date, commit.files_count]
|
|
54
|
+
}]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def lines_count_by_date
|
|
58
|
+
sum = 0
|
|
59
|
+
Hash[commits.map { |commit|
|
|
60
|
+
sum += commit.short_stat.insertions
|
|
61
|
+
sum -= commit.short_stat.deletions
|
|
62
|
+
[commit.date, sum]
|
|
63
|
+
}]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def last_commit
|
|
67
|
+
commits.last
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def commit_range
|
|
71
|
+
@first_commit_sha ? "#@first_commit_sha..#{last_commit_sha}" : last_commit_sha
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def last_commit_sha
|
|
75
|
+
@last_commit_sha ||= 'HEAD'
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def short_stats
|
|
79
|
+
@short_stats ||= commits.map(&:short_stat)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def activity
|
|
83
|
+
@activity ||= Activity.new(commits)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def project_version
|
|
87
|
+
@project_version ||= run("git rev-parse --short #{commit_range}").strip
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def project_name
|
|
91
|
+
@project_name ||= File.basename(path)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def run(command)
|
|
95
|
+
result = command_runner.run(path, command)
|
|
96
|
+
invoke_command_observers(command, result)
|
|
97
|
+
result
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def run_and_parse(command)
|
|
101
|
+
result = run(command)
|
|
102
|
+
command_parser.parse(command, result)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def command_runner
|
|
106
|
+
@command_runner ||= CommandRunner.new
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def command_parser
|
|
110
|
+
@command_parser ||= CommandParser.new
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def add_command_observer(proc=nil, &block)
|
|
114
|
+
command_observers << block if block_given?
|
|
115
|
+
command_observers << proc if proc
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def to_s
|
|
119
|
+
"#{self.class} #@path"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def ==(other)
|
|
123
|
+
self.path == other.path
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
def command_observers
|
|
128
|
+
@command_observers ||= []
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def invoke_command_observers(command, result)
|
|
132
|
+
command_observers.each { |o| o.call(command, result) }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
module GitStats
|
|
3
|
+
module GitData
|
|
4
|
+
class ShortStat
|
|
5
|
+
attr_reader :commit, :files_changed, :insertions, :deletions
|
|
6
|
+
|
|
7
|
+
def initialize(commit)
|
|
8
|
+
@commit = commit
|
|
9
|
+
calculate_stat
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def changed_lines
|
|
13
|
+
insertions + deletions
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
"#{self.class} #@commit"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
def calculate_stat
|
|
22
|
+
stat_line = commit.repo.run("git show --shortstat --oneline #{commit.sha}").lines.to_a[1]
|
|
23
|
+
if stat_line.blank?
|
|
24
|
+
@files_changed = @insertions = @deletions = 0
|
|
25
|
+
else
|
|
26
|
+
@files_changed = stat_line[/(\d+) files? changed/, 1].to_i
|
|
27
|
+
@insertions = stat_line[/(\d+) insertions?/, 1].to_i
|
|
28
|
+
@deletions = stat_line[/(\d+) deletions?/, 1].to_i
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
module GitStats
|
|
3
|
+
module StatsView
|
|
4
|
+
module Charts
|
|
5
|
+
class ActivityCharts
|
|
6
|
+
def initialize(repo)
|
|
7
|
+
@repo = repo
|
|
8
|
+
@activity = repo.activity
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def activity_by_date(author)
|
|
12
|
+
Chart.new do |f|
|
|
13
|
+
f.date_column_chart(
|
|
14
|
+
data: author.activity.by_date,
|
|
15
|
+
title: :commits_by_date.t,
|
|
16
|
+
y_text: :commits.t
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def activity_by_hour(author)
|
|
22
|
+
Chart.new do |f|
|
|
23
|
+
f.simple_column_chart(
|
|
24
|
+
title: :commits_by_hour.t,
|
|
25
|
+
y_text: :commits.t,
|
|
26
|
+
x_text: :hour.t,
|
|
27
|
+
data_x: (0..23),
|
|
28
|
+
data_y: author.activity.by_hour_array
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def activity_by_wday(author)
|
|
34
|
+
Chart.new do |f|
|
|
35
|
+
f.simple_column_chart(
|
|
36
|
+
title: :commits_by_wday.t,
|
|
37
|
+
y_text: :commits.t,
|
|
38
|
+
x_text: :day.t,
|
|
39
|
+
data_x: I18n.t('date.abbr_day_names'),
|
|
40
|
+
data_y: author.activity.by_wday_array
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def activity_by_month(author)
|
|
46
|
+
Chart.new do |f|
|
|
47
|
+
f.simple_column_chart(
|
|
48
|
+
title: :commits_by_month.t,
|
|
49
|
+
y_text: :commits.t,
|
|
50
|
+
x_text: :month.t,
|
|
51
|
+
data_x: I18n.t('date.abbr_month_names')[1..-1],
|
|
52
|
+
data_y: author.activity.by_month_array
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def activity_by_year(author)
|
|
58
|
+
Chart.new do |f|
|
|
59
|
+
f.column_hash_chart(
|
|
60
|
+
title: :commits_by_year.t,
|
|
61
|
+
y_text: :commits.t,
|
|
62
|
+
x_text: :year.t,
|
|
63
|
+
data: author.activity.by_year
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
module GitStats
|
|
3
|
+
module StatsView
|
|
4
|
+
module Charts
|
|
5
|
+
class AuthorsCharts
|
|
6
|
+
def initialize(authors)
|
|
7
|
+
@authors = authors
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def commits_sum_by_author_by_date(limit = 4)
|
|
11
|
+
Chart.new do |f|
|
|
12
|
+
f.multi_date_chart(
|
|
13
|
+
data: @authors.sort_by { |author| -author.commits.size }[0..limit].map { |author| {name: author.name, data: author.commits_sum_by_date} },
|
|
14
|
+
title: :lines_by_date.t,
|
|
15
|
+
y_text: :lines.t
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
[:insertions, :deletions, :changed_lines].each do |method|
|
|
21
|
+
define_method "#{method}_by_author_by_date" do |limit = 4|
|
|
22
|
+
Chart.new do |f|
|
|
23
|
+
f.multi_date_chart(
|
|
24
|
+
data: @authors.sort_by { |author| -author.send(method) }[0..limit].map { |author| {name: author.name, data: author.send("#{method}_by_date")} },
|
|
25
|
+
title: :lines_by_date.t,
|
|
26
|
+
y_text: :lines.t
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
module GitStats
|
|
3
|
+
module StatsView
|
|
4
|
+
module Charts
|
|
5
|
+
class Chart
|
|
6
|
+
|
|
7
|
+
def method_missing(name, *args, &block)
|
|
8
|
+
@chart.send(name, *args, &block)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@chart = LazyHighCharts::HighChart.new('graph')
|
|
13
|
+
yield self if block_given?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def simple_column_chart(params)
|
|
17
|
+
column_chart(params)
|
|
18
|
+
series(name: params[:title], data: params[:data_y])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def multiple_column_chart(params)
|
|
22
|
+
column_chart(params)
|
|
23
|
+
params[:data_y].each do |s|
|
|
24
|
+
series(name: s[:name], data: s[:data])
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def column_hash_chart(params)
|
|
29
|
+
simple_column_chart(params.merge(
|
|
30
|
+
data_x: params[:data].keys,
|
|
31
|
+
data_y: params[:data].values
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def date_chart(params)
|
|
37
|
+
common_options(params)
|
|
38
|
+
date_series(name: params[:title], data: params[:data])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def multi_date_chart(params)
|
|
42
|
+
common_options(params)
|
|
43
|
+
default_legend
|
|
44
|
+
params[:data].each do |s|
|
|
45
|
+
date_series(s)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def date_column_chart(params)
|
|
50
|
+
date_chart(params)
|
|
51
|
+
data[0][:type] = 'column'
|
|
52
|
+
data[0][:dataGrouping] = {units: [['day', [1]], ['week', [1]]], forced: true}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def default_legend
|
|
56
|
+
legend(
|
|
57
|
+
enabled: true,
|
|
58
|
+
layout: 'vertical',
|
|
59
|
+
backgroundColor: '#FFFFFF',
|
|
60
|
+
align: 'left',
|
|
61
|
+
verticalAlign: 'top',
|
|
62
|
+
x: 100,
|
|
63
|
+
y: 70,
|
|
64
|
+
floating: true,
|
|
65
|
+
shadow: true
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def no_legend
|
|
70
|
+
legend(
|
|
71
|
+
enabled: false
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def type(type)
|
|
76
|
+
@chart.chart!(type: type)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def x_categories(categories)
|
|
80
|
+
@chart.xAxis!(categories: categories)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def x_text(text)
|
|
84
|
+
@chart.xAxis!(title: {text: text})
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def y_text(text)
|
|
88
|
+
@chart.yAxis!(title: {text: text})
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def title(title)
|
|
92
|
+
@chart.title!(text: title)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
def common_options(params)
|
|
97
|
+
no_legend
|
|
98
|
+
title ""
|
|
99
|
+
y_text params[:y_text]
|
|
100
|
+
x_text params[:x_text]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def date_series(params)
|
|
104
|
+
series(
|
|
105
|
+
name: params[:name],
|
|
106
|
+
type: "spline",
|
|
107
|
+
data: params[:data].map { |date, value| [date.to_i * 1000, value] }
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def column_chart(params)
|
|
112
|
+
common_options(params)
|
|
113
|
+
type "column"
|
|
114
|
+
x_categories params[:data_x]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
|
2
|
+
module GitStats
|
|
3
|
+
module StatsView
|
|
4
|
+
module Charts
|
|
5
|
+
class All
|
|
6
|
+
delegate :files_by_extension, :lines_by_extension, :files_by_date, :lines_by_date, to: :repo_charts
|
|
7
|
+
|
|
8
|
+
delegate :commits_sum_by_author_by_date, :changed_lines_by_author_by_date,
|
|
9
|
+
:insertions_by_author_by_date, :deletions_by_author_by_date, to: :authors_charts
|
|
10
|
+
|
|
11
|
+
delegate :activity_by_date, :activity_by_hour, :activity_by_wday, :activity_by_month,
|
|
12
|
+
:activity_by_year, to: :activity_charts
|
|
13
|
+
|
|
14
|
+
attr_reader :repo
|
|
15
|
+
|
|
16
|
+
def initialize(repo)
|
|
17
|
+
@repo = repo
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def repo_charts
|
|
21
|
+
@repo_charts ||= Charts::RepoCharts.new(repo)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def authors_charts
|
|
25
|
+
@authors_charts ||= Charts::AuthorsCharts.new(repo.authors)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def activity_charts
|
|
29
|
+
@activity_charts ||= Charts::ActivityCharts.new(repo)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|