gigawatt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ gem "trollop", "~> 2.0"
3
+ gem "highline", "~> 1.6.19"
4
+ gem "oauth2", "~> 0.9.2i"
5
+ gem "launchy", "~> 2.3.0"
6
+ gem "terminal-table", "~> 1.4.5"
7
+
8
+ group :development do
9
+ gem "shoulda", ">= 0"
10
+ gem "rdoc", "~> 3.12"
11
+ gem "bundler", "~> 1.0"
12
+ gem "jeweler", "~> 1.8.7"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,81 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (4.0.0)
5
+ i18n (~> 0.6, >= 0.6.4)
6
+ minitest (~> 4.2)
7
+ multi_json (~> 1.3)
8
+ thread_safe (~> 0.1)
9
+ tzinfo (~> 0.3.37)
10
+ addressable (2.3.5)
11
+ atomic (1.1.14)
12
+ builder (3.2.2)
13
+ faraday (0.8.8)
14
+ multipart-post (~> 1.2.0)
15
+ git (1.2.6)
16
+ github_api (0.10.1)
17
+ addressable
18
+ faraday (~> 0.8.1)
19
+ hashie (>= 1.2)
20
+ multi_json (~> 1.4)
21
+ nokogiri (~> 1.5.2)
22
+ oauth2
23
+ hashie (2.0.5)
24
+ highline (1.6.19)
25
+ httpauth (0.2.0)
26
+ i18n (0.6.5)
27
+ jeweler (1.8.7)
28
+ builder
29
+ bundler (~> 1.0)
30
+ git (>= 1.2.5)
31
+ github_api (= 0.10.1)
32
+ highline (>= 1.6.15)
33
+ nokogiri (= 1.5.10)
34
+ rake
35
+ rdoc
36
+ json (1.8.0)
37
+ jwt (0.1.8)
38
+ multi_json (>= 1.5)
39
+ launchy (2.3.0)
40
+ addressable (~> 2.3)
41
+ minitest (4.7.5)
42
+ multi_json (1.8.1)
43
+ multi_xml (0.5.5)
44
+ multipart-post (1.2.0)
45
+ nokogiri (1.5.10)
46
+ oauth2 (0.9.2)
47
+ faraday (~> 0.8)
48
+ httpauth (~> 0.2)
49
+ jwt (~> 0.1.4)
50
+ multi_json (~> 1.0)
51
+ multi_xml (~> 0.5)
52
+ rack (~> 1.2)
53
+ rack (1.5.2)
54
+ rake (10.1.0)
55
+ rdoc (3.12.2)
56
+ json (~> 1.4)
57
+ shoulda (3.5.0)
58
+ shoulda-context (~> 1.0, >= 1.0.1)
59
+ shoulda-matchers (>= 1.4.1, < 3.0)
60
+ shoulda-context (1.1.5)
61
+ shoulda-matchers (2.4.0)
62
+ activesupport (>= 3.0.0)
63
+ terminal-table (1.4.5)
64
+ thread_safe (0.1.3)
65
+ atomic
66
+ trollop (2.0)
67
+ tzinfo (0.3.37)
68
+
69
+ PLATFORMS
70
+ ruby
71
+
72
+ DEPENDENCIES
73
+ bundler (~> 1.0)
74
+ highline (~> 1.6.19)
75
+ jeweler (~> 1.8.7)
76
+ launchy (~> 2.3.0)
77
+ oauth2 (~> 0.9.2i)
78
+ rdoc (~> 3.12)
79
+ shoulda
80
+ terminal-table (~> 1.4.5)
81
+ trollop (~> 2.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 MadPilot Productions
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # 88 Miles Command Line Application
2
+
3
+ 88 Miles is simple time tracking for freelance developers, designers and copywriters. This gem allows you to access you account from your command line.
4
+
5
+ To use this gem, you will need an 88 Miles account. You can [register here](http://88miles.net)
6
+
7
+ ## Installation
8
+
9
+ gem install 88miles
10
+
11
+ ## Get started
12
+
13
+ First, you will need to give the gem access to 88 Miles. This is done via OAuth, so you don't need to ever store your login or password.
14
+
15
+ To setup the link:
16
+
17
+ 88miles setup
18
+
19
+ If you on a computer has a default browser, it will open a browser window asking you to login, and approve access to the application.
20
+
21
+ Once authentication has happened, you will be redirected to a confirmation window. Cut and paste the URL from this window in to the console that you started the process.
22
+
23
+ ## Linking a Project to a directory
24
+
25
+ The command line application links directories with projects in 88 Miles. To setup the link, run
26
+
27
+ 88miles init [path/to/directory]
28
+
29
+ You will be presented with a list of projects to select from. Select a project, and the link is complete.
30
+
31
+ ## Punching in
32
+
33
+ Once you have linked a project, you can punch in by:
34
+
35
+ 88miles start
36
+
37
+ If you want to select an activity:
38
+
39
+ 88miles start -a
40
+
41
+ You will be presented with a list of activities from which you can select from
42
+
43
+ ## Punching out
44
+
45
+ To punch out of the linked project
46
+
47
+ 88miles stop -n "Any notes to associate with shift"
48
+
49
+ ## View the current status of the project
50
+
51
+ You can see how much time you have clocked against a project by:
52
+
53
+ 88miles status
54
+
55
+ If you want to have it automatically update, you can leave it in the foreground
56
+
57
+ 88miles status -f
58
+
59
+ Hit Ctrl-C to exit foreground mode.
60
+
61
+ ## List all shifts
62
+
63
+ If you want to view a list of all the shifts clocked against the linked project:
64
+
65
+ 88miles log
66
+
67
+ To format it in a nice table view:
68
+
69
+ 88miles log -t
70
+
71
+ ## Update the local cache
72
+
73
+ To speed things up, your company, project and staff list is cached locally. If you modify any of these things on the website, you'll need to re-sync the cache by:
74
+
75
+ 88miles sync
76
+
77
+ ## Copyright
78
+
79
+ Copyright (c) 2013 [MadPilot Productions](http://www.madpilot.com.au/). See LICENSE.txt for further details.
data/Rakefile ADDED
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "gigawatt"
18
+ gem.homepage = "http://github.com/madpilot/gigawatt"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Command line interface to 88 Miles}
21
+ gem.description = %Q{Command line interface to 88 Miles}
22
+ gem.email = "myles@madpilot.com.au"
23
+ gem.authors = ["Myles Eftos"]
24
+ gem.executables = [ '88miles' ]
25
+ # dependencies defined in Gemfile
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ task :default => :test
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/bin/88miles ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'gigawatt'
3
+ exit Gigawatt::Application.run!
data/gigawatt.gemspec ADDED
@@ -0,0 +1,89 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "gigawatt"
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Myles Eftos"]
12
+ s.date = "2013-10-06"
13
+ s.description = "Command line interface to 88 Miles"
14
+ s.email = "myles@madpilot.com.au"
15
+ s.executables = ["88miles"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE.txt",
18
+ "README.md"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/88miles",
29
+ "gigawatt.gemspec",
30
+ "lib/gigawatt.rb",
31
+ "lib/gigawatt/application.rb",
32
+ "lib/gigawatt/cache.rb",
33
+ "lib/gigawatt/commands/init.rb",
34
+ "lib/gigawatt/commands/log.rb",
35
+ "lib/gigawatt/commands/setup.rb",
36
+ "lib/gigawatt/commands/start.rb",
37
+ "lib/gigawatt/commands/status.rb",
38
+ "lib/gigawatt/commands/stop.rb",
39
+ "lib/gigawatt/commands/sync.rb",
40
+ "lib/gigawatt/oauth.rb",
41
+ "lib/gigawatt/options.rb",
42
+ "lib/gigawatt/project_file.rb",
43
+ "lib/gigawatt/settings.rb",
44
+ "test/helper.rb",
45
+ "test/test_gigawatt.rb"
46
+ ]
47
+ s.homepage = "http://github.com/madpilot/gigawatt"
48
+ s.licenses = ["MIT"]
49
+ s.require_paths = ["lib"]
50
+ s.rubygems_version = "1.8.15"
51
+ s.summary = "Command line interface to 88 Miles"
52
+
53
+ if s.respond_to? :specification_version then
54
+ s.specification_version = 3
55
+
56
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
+ s.add_runtime_dependency(%q<trollop>, ["~> 2.0"])
58
+ s.add_runtime_dependency(%q<highline>, ["~> 1.6.19"])
59
+ s.add_runtime_dependency(%q<oauth2>, ["~> 0.9.2i"])
60
+ s.add_runtime_dependency(%q<launchy>, ["~> 2.3.0"])
61
+ s.add_runtime_dependency(%q<terminal-table>, ["~> 1.4.5"])
62
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
63
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
64
+ s.add_development_dependency(%q<bundler>, ["~> 1.0"])
65
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.7"])
66
+ else
67
+ s.add_dependency(%q<trollop>, ["~> 2.0"])
68
+ s.add_dependency(%q<highline>, ["~> 1.6.19"])
69
+ s.add_dependency(%q<oauth2>, ["~> 0.9.2i"])
70
+ s.add_dependency(%q<launchy>, ["~> 2.3.0"])
71
+ s.add_dependency(%q<terminal-table>, ["~> 1.4.5"])
72
+ s.add_dependency(%q<shoulda>, [">= 0"])
73
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
74
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
75
+ s.add_dependency(%q<jeweler>, ["~> 1.8.7"])
76
+ end
77
+ else
78
+ s.add_dependency(%q<trollop>, ["~> 2.0"])
79
+ s.add_dependency(%q<highline>, ["~> 1.6.19"])
80
+ s.add_dependency(%q<oauth2>, ["~> 0.9.2i"])
81
+ s.add_dependency(%q<launchy>, ["~> 2.3.0"])
82
+ s.add_dependency(%q<terminal-table>, ["~> 1.4.5"])
83
+ s.add_dependency(%q<shoulda>, [">= 0"])
84
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
85
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
86
+ s.add_dependency(%q<jeweler>, ["~> 1.8.7"])
87
+ end
88
+ end
89
+
@@ -0,0 +1,8 @@
1
+ module Gigawatt
2
+ class Application
3
+ def self.run!
4
+ trap("SIGINT") { puts " "; exit! }
5
+ Options.parse!
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,71 @@
1
+ module Gigawatt
2
+ class Cache
3
+ def initialize(settings, access_key)
4
+ @access_key = access_key
5
+ @settings = settings
6
+ end
7
+
8
+ def fetch_companies
9
+ companies = JSON.parse(@access_key.get('/api/1/companies.json').body)
10
+ companies["response"]
11
+ end
12
+
13
+ def fetch_projects
14
+ projects = JSON.parse(@access_key.get("/api/1/projects.json?where=#{URI::encode('active="true"')}").body)
15
+ projects["response"]
16
+ end
17
+
18
+ def fetch_staff
19
+ staff = JSON.parse(@access_key.get("/api/1/staff.json").body)
20
+ staff["response"]
21
+ end
22
+
23
+ def refresh!
24
+ @settings.companies = nil
25
+ @settings.projects = nil
26
+ @settings.staff = nil
27
+ companies
28
+ projects
29
+ staff
30
+ end
31
+
32
+ def companies(indexed = false)
33
+ if @settings.companies.nil?
34
+ @settings.companies = fetch_companies
35
+ @settings.write(:companies)
36
+ end
37
+ return @settings.companies unless indexed
38
+ companies = {}
39
+ @settings.companies.each do |c|
40
+ companies[c["uuid"]] = c
41
+ end
42
+ companies
43
+ end
44
+
45
+ def projects(indexed = false)
46
+ if @settings.projects.nil?
47
+ @settings.projects = fetch_projects
48
+ @settings.write(:projects)
49
+ end
50
+ return @settings.projects unless indexed
51
+ projects = {}
52
+ @settings.projects.each do |p|
53
+ projects[p["uuid"]] = p
54
+ end
55
+ projects
56
+ end
57
+
58
+ def staff(indexed = false)
59
+ if @settings.staff.nil?
60
+ @settings.staff = fetch_staff
61
+ @settings.write(:staff)
62
+ end
63
+ return @settings.staff unless indexed
64
+ staff = {}
65
+ @settings.staff.each do |s|
66
+ staff[s["uuid"]] = s
67
+ end
68
+ staff
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,51 @@
1
+ module Gigawatt
2
+ module Commands
3
+ class Init
4
+ attr_accessor :options
5
+
6
+ def self.run!(settings)
7
+ directory = nil
8
+ p = Trollop::Parser.new
9
+ options = p.parse
10
+ directory = p.leftovers.first
11
+
12
+ Trollop::die "Please supply a directory" unless directory
13
+ Trollop::die "Directory does not exist" unless File.exists?(directory)
14
+ Trollop::die "#{directory} is not a directory" unless File.directory?(directory)
15
+
16
+ instance = self.new(settings, options, directory)
17
+ begin
18
+ instance.list_projects
19
+ rescue OAuth2::Error => e
20
+ say "Access to your 88 Miles may have been revoked. Please run <%= color('88miles setup', BOLD) %> again."
21
+ return INVALID_OAUTH_TOKEN_EXIT_CODE
22
+ end
23
+
24
+ return OK_EXIT_CODE
25
+ end
26
+
27
+ def initialize(settings, options, directory)
28
+ @settings = settings
29
+ @options = options
30
+ @directory = directory
31
+ @access_key = OAuth.token(settings.access_key)
32
+ @cache = Cache.new(settings, @access_key)
33
+ end
34
+
35
+ def list_projects
36
+ companies = @cache.companies(true)
37
+
38
+ selected = nil
39
+ choose do |menu|
40
+ menu.prompt = "Pick a project"
41
+ @cache.projects.each do |project|
42
+ menu.choice("#{companies[project["company_uuid"]]["name"]}: #{project["name"]}") { selected = project }
43
+ end
44
+ end
45
+
46
+ ProjectFile.write(selected)
47
+ say("<%= color('#{companies[selected["company_uuid"]]["name"]}: #{selected["name"]}', GREEN) %> selected. Run <%= color('88miles start', BOLD) %> to punch in")
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,162 @@
1
+ module Gigawatt
2
+ module Commands
3
+ class Log
4
+ attr_accessor :options
5
+
6
+ def self.run!(settings)
7
+ options = Trollop::options do
8
+ banner <<-EOS
9
+ 88 Miles Command line application - http://88miles.net
10
+
11
+ Print out the shifts for the linked project
12
+
13
+ Usage:
14
+ 88miles log [options]
15
+
16
+ options
17
+ EOS
18
+ opt :page, "Page the output", :type => :flag, :default => true
19
+ opt :table, "Outputs a formatted table", :type => :flag, :default => false
20
+ end
21
+
22
+ instance = self.new(settings, options)
23
+ begin
24
+ return instance.log
25
+ rescue OAuth2::Error => e
26
+ say "Access to your 88 Miles may have been revoked. Please run <%= color('88miles setup', BOLD) %> again."
27
+ return INVALID_OAUTH_TOKEN_EXIT_CODE
28
+ end
29
+ end
30
+
31
+ def initialize(settings, options)
32
+ @settings = settings
33
+ @options = options
34
+
35
+ @access_key = OAuth.token(@settings.access_key)
36
+ @cache = Cache.new(settings, @access_key)
37
+ @project = Gigawatt::ProjectFile.new.project
38
+ end
39
+
40
+ def log
41
+ unless @project
42
+ say("No project found.")
43
+ return NO_PROJECT_EXIT_CODE
44
+ end
45
+
46
+ $terminal.page_at = :auto if @options[:page]
47
+
48
+ if @options[:table]
49
+ log_table
50
+ else
51
+ log_blob
52
+ end
53
+ return OK_EXIT_CODE
54
+ end
55
+
56
+ def log_blob
57
+ buffer = ''
58
+ shifts = JSON.parse(@access_key.get("/api/1/projects/#{@project["uuid"]}/shifts.json").body)
59
+
60
+ company = @cache.companies(true)[@project["company_uuid"]]
61
+ staff = @cache.staff(true)
62
+ buffer += "#{company["name"]}: #{@project["name"]} shifts:\n\n"
63
+
64
+ total = 0
65
+ rows = []
66
+ str = ""
67
+ shifts["response"].each do |shift|
68
+ staff_member = staff[shift["user_uuid"]]
69
+ start = Time.parse(shift["start"])
70
+ stop = Time.parse(shift["stop"] || Time.now.to_s)
71
+ total += (stop - start)
72
+
73
+ str += "<%= color('uuid #{shift["uuid"]}', YELLOW) %>\n"
74
+
75
+ if staff_member
76
+ str += "Staff: #{staff_member["first_name"]} #{staff_member["last_name"]} <#{staff_member["email_address"]}>\n"
77
+ else
78
+ str += "Staff: [Deleted user]\n"
79
+ end
80
+
81
+ str += "Start: #{start.getlocal.strftime('%c %:z')}\n"
82
+ str += "Stop: #{stop.getlocal.strftime('%c %:z')}\n"
83
+ str += "Total: #{to_clock_s(stop - start)}\n"
84
+ str += "\n\t"
85
+ if shift["notes"].to_s == ""
86
+ str += "No notes\n"
87
+ else
88
+ str += "#{shift["notes"]}\n"
89
+ end
90
+ str += "\n"
91
+ end
92
+
93
+ overdue = @project["grand_total"] > @project["time_limit"] if @project["time_limit"]
94
+ if overdue
95
+ str += "Total: #{HighLine::String.new(to_clock_s(total)).red}"
96
+ else
97
+ str += "Total: #{to_clock_s(total)}"
98
+ end
99
+
100
+ say(str)
101
+ end
102
+
103
+ def log_table
104
+ buffer = ''
105
+ shifts = JSON.parse(@access_key.get("/api/1/projects/#{@project["uuid"]}/shifts.json").body)
106
+
107
+ company = @cache.companies(true)[@project["company_uuid"]]
108
+ staff = @cache.staff(true)
109
+ buffer += "#{company["name"]}: #{@project["name"]} shifts:\n\n"
110
+
111
+ total = 0
112
+ rows = []
113
+ shifts["response"].each do |shift|
114
+ row = []
115
+
116
+ staff_member = staff[shift["user_uuid"]]
117
+ start = Time.parse(shift["start"])
118
+ stop = Time.parse(shift["stop"] || Time.now.to_s)
119
+ total += (stop - start)
120
+
121
+ if staff_member
122
+ row << "#{staff_member["first_name"]} #{staff_member["last_name"]}"
123
+ else
124
+ row << "[Deleted user]"
125
+ end
126
+ row << "#{start.getlocal.strftime("%d/%M/%Y %H:%M:%S")} - #{stop.getlocal.strftime("%H:%M:%S")}"
127
+ row << "#{to_clock_s(stop - start)}"
128
+
129
+ if shift["notes"].to_s == ""
130
+ row << " No notes"
131
+ else
132
+ row << " #{shift["notes"]}"
133
+ end
134
+
135
+ if shift["stop"].nil?
136
+ row = row.map{ |col| HighLine::String.new(col).green }
137
+ end
138
+ rows << row
139
+ end
140
+
141
+ rows << :separator
142
+ overdue = @project["grand_total"] > @project["time_limit"] if @project["time_limit"]
143
+ if overdue
144
+ rows << [ 'Total', '', HighLine::String.new(to_clock_s(total)).red, '' ]
145
+ else
146
+ rows << [ 'Total', '', to_clock_s(total), '' ]
147
+ end
148
+
149
+ say(Terminal::Table.new(:rows => rows).to_s) if @options[:table]
150
+ end
151
+
152
+ def to_clock_s(time, show_seconds = false)
153
+ hour = (time.abs / 3600).floor
154
+ minute = (time.abs / 60 % 60).floor
155
+ seconds = (time.abs % 60).floor if show_seconds
156
+
157
+ return (time != 0 && (time / time.abs) == -1 ? "-" : "") + hour.to_s.rjust(2, '0') + ":" + minute.to_s.rjust(2, '0') + (show_seconds ? ":" + seconds.to_s.rjust(2, '0') : '')
158
+ end
159
+
160
+ end
161
+ end
162
+ end