gitstats-ruby 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 +3 -0
- data/LICENSE +674 -0
- data/README.markdown +53 -0
- data/bin/gitstats +176 -0
- data/gitstats-ruby.gemspec +22 -0
- data/lib/gitstats.rb +18 -0
- data/lib/gitstats/author.rb +41 -0
- data/lib/gitstats/git.rb +158 -0
- data/lib/gitstats/renderer.rb +30 -0
- data/lib/gitstats/renderer/gnuplot.rb +150 -0
- data/lib/gitstats/renderer/haml.rb +70 -0
- data/lib/gitstats/renderer/sass.rb +33 -0
- data/lib/gitstats/statgen.rb +116 -0
- data/lib/gitstats/stats.rb +12 -0
- data/lib/gitstats/stats/commit.rb +48 -0
- data/lib/gitstats/stats/commit/author.rb +17 -0
- data/lib/gitstats/stats/commit/time.rb +92 -0
- data/lib/gitstats/stats/file.rb +15 -0
- data/lib/gitstats/stats/file/filetype.rb +14 -0
- data/lib/gitstats/yearmonth.rb +39 -0
- data/template/activity.haml +3 -0
- data/template/asc.gif +0 -0
- data/template/authors.haml +2 -0
- data/template/bg.gif +0 -0
- data/template/commits_per_hour.plot +14 -0
- data/template/commits_per_month.plot +15 -0
- data/template/commits_per_wday.plot +15 -0
- data/template/commits_per_year.plot +11 -0
- data/template/commits_per_yearmonth.plot +11 -0
- data/template/desc.gif +0 -0
- data/template/filechanges_by_yearmonth.plot +17 -0
- data/template/files.haml +2 -0
- data/template/files_by_yearmonth.plot +11 -0
- data/template/helpers/block.rb +26 -0
- data/template/helpers/config.rb +12 -0
- data/template/helpers/names.rb +76 -0
- data/template/helpers/utils.rb +16 -0
- data/template/index.haml +2 -0
- data/template/jquery.js +4 -0
- data/template/jquery.tablesorter.js +4 -0
- data/template/lastweeks.plot +14 -0
- data/template/layouts/default.haml +43 -0
- data/template/linechanges_by_yearmonth.plot +17 -0
- data/template/lines.haml +2 -0
- data/template/lines_by_yearmonth.plot +11 -0
- data/template/partials/authors.haml +30 -0
- data/template/partials/blockheader.haml +2 -0
- data/template/partials/blocktoc.haml +7 -0
- data/template/partials/commits_per_month.haml +27 -0
- data/template/partials/commits_per_year.haml +27 -0
- data/template/partials/commits_per_yearmonth.haml +27 -0
- data/template/partials/day_of_week.haml +28 -0
- data/template/partials/filechanges_by_yearmonth.haml +3 -0
- data/template/partials/files_by_yearmonth.haml +3 -0
- data/template/partials/filetypes.haml +23 -0
- data/template/partials/general.haml +34 -0
- data/template/partials/hour_of_day.haml +28 -0
- data/template/partials/hour_of_week.haml +24 -0
- data/template/partials/lastweeks.haml +24 -0
- data/template/partials/linechanges_by_yearmonth.haml +3 -0
- data/template/partials/lines_by_yearmonth.haml +3 -0
- data/template/partials/repos.haml +13 -0
- data/template/partials/top_authors_of_year.haml +30 -0
- data/template/partials/top_authors_of_yearmonth.haml +30 -0
- data/template/style.scss +132 -0
- metadata +187 -0
data/README.markdown
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# gitstats-ruby
|
2
|
+
|
3
|
+
gitstats-ruby is a clone of http://gitstats.sourceforce.net written in ruby. It's written to support templates and should be easily extendable.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Right now you have to clone this repository until I upload a gem to rubygems.
|
8
|
+
|
9
|
+
git clone https://github.com/chrisistuff/gitstats-ruby.git
|
10
|
+
|
11
|
+
## Getting started
|
12
|
+
|
13
|
+
The basic usage is quite simple. Just run gitstats with the git directory as parameter. If you want to generate stats of more than one repository just list them one after another. Note that this will generate only one statistic but consider the commits from all repositories.
|
14
|
+
Additionally you can also specify a name and a ref for each repository. To do this please use the following format: `<name>:<path to repository>:<ref>`.
|
15
|
+
For example:
|
16
|
+
|
17
|
+
gitstats gitstats-ruby:.:master
|
18
|
+
|
19
|
+
For more options please read sections about caching below or run `gitstats -h`.
|
20
|
+
|
21
|
+
## Caching
|
22
|
+
|
23
|
+
gitstats-ruby implements two types of caches but one of them is just useful when developing new statistic classes. The one useful for the end-user is the stats-cache and the other one (for the devs) is the commit-cache.
|
24
|
+
|
25
|
+
### stats-cache
|
26
|
+
|
27
|
+
The stats-cache caches the statistic objects used internally. This drastically improves the speed of incremental updates because only the new commits have to be taken into account. To activate this cache pass the `-c` command line flag. By default this creates the stats-cache file in the output directory. If you want to use another file you can specify it by using `--statcache <filename>`.
|
28
|
+
|
29
|
+
Please note that this cache can only be used when working on the same repositories as used in the previous run. Identification is done using the repository name or, if not specified, using the given (not relative!) path.
|
30
|
+
|
31
|
+
### commit-cache
|
32
|
+
|
33
|
+
As already mentioned this cache is only useful if you want to develope new statistic classes. It works by caching the internal commit objects to a per repository file that can be reread when running again. This is especially useful if you experiment with big repositories (i.e. the linux kernel with ~275000 commits) where this cache is about twice as fast as the `git log` command used internally.
|
34
|
+
|
35
|
+
This cache can be activated with the `-C` command line flag. By default the commitcache is written into the output directory (./stats by default). If you want to use another directory you can specify it by using `--commitcache <directory name>`.
|
36
|
+
|
37
|
+
## Examples
|
38
|
+
|
39
|
+
* [gitstats-ruby](http://chrisistuff.github.com/gitstats-ruby/gitstats-ruby/)
|
40
|
+
* [Linux](http://chrisistuff.github.com/gitstats-ruby/linux/)
|
41
|
+
|
42
|
+
## Dependencies
|
43
|
+
|
44
|
+
* [HAML](http://haml-lang.com)
|
45
|
+
* [SASS](http://sass-lang.com)
|
46
|
+
* [Compass](http://compass-style.org)
|
47
|
+
* [Gnuplot (GEM)](http://rubygems.org/gems/gnuplot)
|
48
|
+
* [Gnuplot binary](http://www.gnuplot.info/)
|
49
|
+
|
50
|
+
## License
|
51
|
+
|
52
|
+
See LICENSE
|
53
|
+
|
data/bin/gitstats
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'ftools'
|
5
|
+
require 'rubygems'
|
6
|
+
|
7
|
+
require 'haml'
|
8
|
+
require 'sass'
|
9
|
+
require 'compass'
|
10
|
+
require 'gnuplot'
|
11
|
+
|
12
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
|
13
|
+
|
14
|
+
require 'gitstats'
|
15
|
+
|
16
|
+
$options = {
|
17
|
+
:out => 'stats',
|
18
|
+
:template => nil,
|
19
|
+
:verbose => false,
|
20
|
+
:debug => false,
|
21
|
+
:quiet => false,
|
22
|
+
:cache => false,
|
23
|
+
:statcache => nil,
|
24
|
+
:commitcache => nil,
|
25
|
+
:commitcache_dir => nil,
|
26
|
+
:future => true,
|
27
|
+
:maxage => 0,
|
28
|
+
:withmail => false,
|
29
|
+
}
|
30
|
+
|
31
|
+
parser = OptionParser.new do |opts|
|
32
|
+
opts.banner = 'Usage: gitstats.rb [options] <[name1:]gitdir1[:ref1]> [<[name2:]gitdir2[:ref2]> ...]'
|
33
|
+
|
34
|
+
opts.on('-o', '--out=arg', 'output directory') do |arg|
|
35
|
+
$options[:out] = arg
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on('-t', '--template=arg', 'template directory') do |arg|
|
39
|
+
$options[:template] = arg
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on('-c', '--[no-]cache', 'use the statcache file') do |arg|
|
43
|
+
$options[:cache] = arg
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on('-C', '--[no-]commitcache', 'use the commit cache') do |arg|
|
47
|
+
$options[:commitcache] = arg
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on('-s', '--statcache=arg', 'statcache file to use') do |arg|
|
51
|
+
$options[:statcache] = arg
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on('--commitcache=arg', 'commit cache directory to use') do |arg|
|
55
|
+
$options[:commitcache_dir] = arg
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on('--[no-]future', 'count future commits') do |arg|
|
59
|
+
$options[:future] = arg
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.on('-m', '--max-age=arg', Integer, 'set max age of commit in days') do |arg|
|
63
|
+
$options[:maxage] = arg
|
64
|
+
end
|
65
|
+
|
66
|
+
opts.on('--[no-]mail', 'include mail in author names') do |arg|
|
67
|
+
$options[:withmail] = arg
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on('-v', '--[no-]verbose', 'verbose mode') do |arg|
|
71
|
+
$options[:verbose] = arg
|
72
|
+
end
|
73
|
+
|
74
|
+
opts.on('-q', '--[no-]quiet', 'quiet mode') do |arg|
|
75
|
+
$options[:quiet] = arg
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on('-d', '--[no-]debug', 'print debug messages') do |arg|
|
79
|
+
$options[:debug] = arg
|
80
|
+
end
|
81
|
+
|
82
|
+
opts.on_tail('-h', '--help', 'this help') do
|
83
|
+
puts opts
|
84
|
+
exit 0
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
parser.parse!
|
89
|
+
|
90
|
+
if $options[:quiet] && $options[:verbose]
|
91
|
+
STDERR.puts 'cannot specify --quiet and --verbose at the same time!'
|
92
|
+
exit 1
|
93
|
+
end
|
94
|
+
|
95
|
+
$options[:statcache] = File.join($options[:out], '.statcache') if $options[:statcache].nil?
|
96
|
+
$options[:commitcache_dir] = $options[:out] if $options[:commitcache_dir].nil?
|
97
|
+
$options[:template] = File.expand_path(File.join(File.dirname(__FILE__), '..', 'template')) if $options[:template].nil?
|
98
|
+
|
99
|
+
FileUtils.mkdir_p($options[:out])
|
100
|
+
|
101
|
+
stat = nil
|
102
|
+
cache_loaded = false
|
103
|
+
if $options[:cache]
|
104
|
+
begin
|
105
|
+
puts 'trying to load cache ...' unless $options[:quiet]
|
106
|
+
stat = Marshal::load(IO::readlines($options[:statcache]).join(''))
|
107
|
+
stat.clear_repos
|
108
|
+
cache_loaded = true
|
109
|
+
rescue
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
if stat.nil?
|
114
|
+
if ARGV.empty?
|
115
|
+
puts parser
|
116
|
+
exit 1
|
117
|
+
end
|
118
|
+
|
119
|
+
stat = StatGen.new
|
120
|
+
end
|
121
|
+
|
122
|
+
if cache_loaded
|
123
|
+
if stat.include_mail != $options[:withmail]
|
124
|
+
puts 'cannot change --[no-]mail option when using statcache'
|
125
|
+
exit 1
|
126
|
+
end
|
127
|
+
|
128
|
+
if stat.future != $options[:future]
|
129
|
+
puts 'cannot change --[no-]future option when using statcache'
|
130
|
+
exit 1
|
131
|
+
end
|
132
|
+
|
133
|
+
if stat.maxage != $options[:maxage] * 24 * 60 * 60
|
134
|
+
puts 'cannot change --max-age option when using statcache'
|
135
|
+
exit 1
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
Author::include_mail = $options[:withmail]
|
140
|
+
stat.include_mail = $options[:withmail]
|
141
|
+
stat.verbose = $options[:verbose]
|
142
|
+
stat.debug = $options[:debug]
|
143
|
+
stat.quiet = $options[:quiet]
|
144
|
+
stat.future = $options[:future]
|
145
|
+
stat.maxage = $options[:maxage] * 24 * 60 * 60
|
146
|
+
stat.commitcache = $options[:commitcache] ? $options[:commitcache_dir] : nil
|
147
|
+
|
148
|
+
ARGV.each do |path|
|
149
|
+
name, path, ref = path.split(':')
|
150
|
+
path ||= name
|
151
|
+
ref ||= 'HEAD'
|
152
|
+
stat << [name, path, ref]
|
153
|
+
end
|
154
|
+
|
155
|
+
if cache_loaded
|
156
|
+
unless stat.check_repostate
|
157
|
+
puts 'cannot use cache when working on different repositories!'
|
158
|
+
exit 1
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
puts 'fetching statistics ...' unless $options[:quiet]
|
163
|
+
begin
|
164
|
+
stat.calc
|
165
|
+
ensure
|
166
|
+
if $options[:cache]
|
167
|
+
puts 'writing cache ...' unless $options[:quiet]
|
168
|
+
cache = Marshal::dump(stat)
|
169
|
+
File.new($options[:statcache], 'w').write(cache)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
puts 'rendering ...' unless $options[:quiet]
|
174
|
+
renderer = Renderer.new($options[:template], $options[:out], $options[:verbose])
|
175
|
+
renderer.render(stat)
|
176
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'gitstats-ruby'
|
5
|
+
s.version = '1.0.0'
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.authors = ['Christoph Plank']
|
8
|
+
s.email = ['chrisistuff@gmail.com']
|
9
|
+
s.homepage = 'http://rubygems.org/gems/gitstats-ruby'
|
10
|
+
s.summary = %q{Generates statistics of git repositories}
|
11
|
+
s.description = %q{Generates statistics of git repositories like http://gitstats.sourceforge.net but with a more extendable and flexible backend system}
|
12
|
+
s.has_rdoc = false
|
13
|
+
|
14
|
+
s.add_dependency 'haml'
|
15
|
+
s.add_dependency 'sass'
|
16
|
+
s.add_dependency 'compass'
|
17
|
+
s.add_dependency 'gnuplot'
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ['lib']
|
22
|
+
end
|
data/lib/gitstats.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'gitstats/author'
|
2
|
+
require 'gitstats/yearmonth'
|
3
|
+
require 'gitstats/git'
|
4
|
+
|
5
|
+
require 'gitstats/stats'
|
6
|
+
require 'gitstats/stats/commit'
|
7
|
+
require 'gitstats/stats/commit/author'
|
8
|
+
require 'gitstats/stats/commit/time'
|
9
|
+
require 'gitstats/stats/file'
|
10
|
+
require 'gitstats/stats/file/filetype'
|
11
|
+
|
12
|
+
require 'gitstats/statgen'
|
13
|
+
|
14
|
+
require 'gitstats/renderer'
|
15
|
+
require 'gitstats/renderer/haml'
|
16
|
+
require 'gitstats/renderer/sass'
|
17
|
+
require 'gitstats/renderer/gnuplot'
|
18
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Author
|
2
|
+
include Comparable
|
3
|
+
|
4
|
+
def self.include_mail=(include_mail)
|
5
|
+
@@include_mail = include_mail
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.include_mail
|
9
|
+
@@include_mail ||= false
|
10
|
+
@@include_mail
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :name
|
14
|
+
attr_reader :email
|
15
|
+
|
16
|
+
def initialize(name, email)
|
17
|
+
@name = name
|
18
|
+
@email = email
|
19
|
+
end
|
20
|
+
|
21
|
+
def <=>(b)
|
22
|
+
to_i <=> b.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
if self.class.include_mail
|
27
|
+
"#{name} <#{email}>"
|
28
|
+
else
|
29
|
+
name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def eql?(b)
|
34
|
+
to_s.hash == b.to_s.hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def hash
|
38
|
+
to_s.hash
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
data/lib/gitstats/git.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
class Git
|
2
|
+
attr_reader :name
|
3
|
+
attr_reader :base
|
4
|
+
attr_reader :ref
|
5
|
+
|
6
|
+
def initialize(name, base, ref = 'HEAD', debug = false, cachefile = nil)
|
7
|
+
@name = name
|
8
|
+
@base = base
|
9
|
+
@ref = ref
|
10
|
+
@debug = debug
|
11
|
+
@cachefile = cachefile
|
12
|
+
end
|
13
|
+
|
14
|
+
def open_cache
|
15
|
+
@cache = File.new(@cachefile, 'a')
|
16
|
+
end
|
17
|
+
|
18
|
+
def close_cache
|
19
|
+
unless @cache.nil?
|
20
|
+
@cache.close
|
21
|
+
@cache = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def write_cache(commit)
|
26
|
+
obj = Marshal.dump(commit)
|
27
|
+
raise "Object too large" if obj.size > 65535
|
28
|
+
|
29
|
+
str = ((obj.size >> 8) & 0xff).chr
|
30
|
+
str += (obj.size & 0xff).chr
|
31
|
+
str += obj
|
32
|
+
|
33
|
+
@cache.write(str)
|
34
|
+
@cache.flush
|
35
|
+
end
|
36
|
+
|
37
|
+
def read_cache
|
38
|
+
f = File.new(@cachefile)
|
39
|
+
while(!f.eof?)
|
40
|
+
tmp = f.read(2)
|
41
|
+
len = (tmp[0] << 8) + tmp[1]
|
42
|
+
obj = f.read(len)
|
43
|
+
raise "Read short object" if obj.size != len
|
44
|
+
yield Marshal.load(obj)
|
45
|
+
end
|
46
|
+
f.close
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_commits(last = nil, &block)
|
50
|
+
if last.nil?
|
51
|
+
range = @ref
|
52
|
+
unless @cachefile.nil?
|
53
|
+
begin
|
54
|
+
read_cache do |commit|
|
55
|
+
block.call(commit)
|
56
|
+
last = commit
|
57
|
+
end
|
58
|
+
range = "#{last[:hash]}..#{@ref}"
|
59
|
+
rescue
|
60
|
+
end
|
61
|
+
end
|
62
|
+
else
|
63
|
+
range = "#{last}..#{@ref}"
|
64
|
+
end
|
65
|
+
|
66
|
+
open_cache unless @cachefile.nil?
|
67
|
+
|
68
|
+
commit = nil
|
69
|
+
sh("git log --reverse --summary --numstat --pretty=format:\"HEADER: %at %ai %H %T %aN <%aE>\" #{range}") do |line|
|
70
|
+
if line =~ /^HEADER:/
|
71
|
+
unless commit.nil?
|
72
|
+
write_cache(commit) unless @cachefile.nil?
|
73
|
+
block.call(commit)
|
74
|
+
end
|
75
|
+
|
76
|
+
parts = line.split(' ', 8)
|
77
|
+
parts.shift
|
78
|
+
|
79
|
+
commit = Hash.new
|
80
|
+
commit[:time] = Time.at(parts[0].to_i)
|
81
|
+
commit[:timezone] = parts[3]
|
82
|
+
commit[:hash] = parts[4]
|
83
|
+
commit[:tree] = parts[5]
|
84
|
+
name = nil
|
85
|
+
email = ''
|
86
|
+
match = /^(.+) <(.+)>$/.match(parts[6])
|
87
|
+
if match.nil?
|
88
|
+
name = parts[6]
|
89
|
+
else
|
90
|
+
name, email = match.captures
|
91
|
+
end
|
92
|
+
commit[:author] = Author.new(name, email)
|
93
|
+
commit[:files_added] = 0
|
94
|
+
commit[:files_deleted] = 0
|
95
|
+
commit[:lines_added] = 0
|
96
|
+
commit[:lines_deleted] = 0
|
97
|
+
elsif line == ''
|
98
|
+
write_cache(commit) unless @cachefile.nil?
|
99
|
+
block.call(commit)
|
100
|
+
commit = nil
|
101
|
+
elsif line =~ /^ /
|
102
|
+
if line =~ /^ create/
|
103
|
+
commit[:files_added] += 1
|
104
|
+
elsif line =~ /^ delete/
|
105
|
+
commit[:files_deleted] += 1
|
106
|
+
end
|
107
|
+
else
|
108
|
+
match = /^(\d+)\s+(\d+)/.match(line)
|
109
|
+
unless match.nil?
|
110
|
+
added, deleted = match.captures
|
111
|
+
commit[:lines_added] += added.to_i
|
112
|
+
commit[:lines_deleted] += deleted.to_i
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
unless commit.nil?
|
118
|
+
write_cache(commit) unless @cachefile.nil?
|
119
|
+
block.call(commit)
|
120
|
+
end
|
121
|
+
|
122
|
+
ensure
|
123
|
+
close_cache unless @cachefile.nil?
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_files(ref = nil, &block)
|
127
|
+
ref ||= @ref
|
128
|
+
|
129
|
+
sh("git ls-tree -r -l #{ref}").split(/\n/).each do |line|
|
130
|
+
parts = line.split(/\s+/, 5)
|
131
|
+
next if parts[1] != 'blob'
|
132
|
+
|
133
|
+
file = Hash.new
|
134
|
+
file[:hash] = parts[2]
|
135
|
+
file[:size] = parts[3].to_i
|
136
|
+
file[:name] = parts[4]
|
137
|
+
|
138
|
+
block.call(file)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
def sh(cmd, &block)
|
144
|
+
puts cmd if @debug
|
145
|
+
Dir.chdir(@base) do
|
146
|
+
if block.nil?
|
147
|
+
`#{cmd}`
|
148
|
+
else
|
149
|
+
IO.popen(cmd) do |io|
|
150
|
+
io.each_line do |line|
|
151
|
+
block.call(line.chomp)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|