alfred-workflow 1.11.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -7
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Gemfile.lock +45 -28
- data/Guardfile +4 -4
- data/History.txt +142 -0
- data/Manifest.txt +7 -0
- data/README.md +19 -1
- data/Rakefile +6 -4
- data/alfred-workflow.gemspec +27 -9
- data/lib/alfred.rb +275 -47
- data/lib/alfred/feedback.rb +112 -24
- data/lib/alfred/feedback/file_item.rb +2 -6
- data/lib/alfred/feedback/item.rb +10 -0
- data/lib/alfred/feedback/webloc_item.rb +1 -1
- data/lib/alfred/handler.rb +133 -0
- data/lib/alfred/handler/autocomplete.rb +86 -0
- data/lib/alfred/handler/callback.rb +130 -0
- data/lib/alfred/handler/cofig.rb +33 -0
- data/lib/alfred/handler/help.rb +162 -0
- data/lib/alfred/osx.rb +63 -0
- data/lib/alfred/setting.rb +29 -73
- data/lib/alfred/ui.rb +1 -0
- data/lib/alfred/util.rb +53 -5
- data/lib/alfred/version.rb +1 -1
- data/spec/alfred/feedback_spec.rb +8 -8
- data/spec/alfred/setting_spec.rb +44 -50
- data/spec/alfred_spec.rb +2 -10
- data/test/workflow/setting.yaml +2 -1
- metadata +304 -135
- metadata.gz.sig +1 -3
@@ -8,8 +8,8 @@ module Alfred
|
|
8
8
|
def initialize(path, opts = {})
|
9
9
|
if opts[:title]
|
10
10
|
@title = opts[:title]
|
11
|
-
elsif ['.ennote', '.webbookmark'].include? File.extname(path)
|
12
|
-
@title = %x{mdls -name kMDItemDisplayName -raw '#{path}'}
|
11
|
+
elsif ['.ennote', '.webbookmark', '.vcf', '.abcdp', '.olk14Contact'].include? File.extname(path)
|
12
|
+
@title = %x{/usr/bin/mdls -name kMDItemDisplayName -raw '#{path}'}
|
13
13
|
else
|
14
14
|
@title = File.basename(path)
|
15
15
|
end
|
@@ -24,10 +24,6 @@ module Alfred
|
|
24
24
|
super @title, opts
|
25
25
|
end
|
26
26
|
|
27
|
-
def match?(query)
|
28
|
-
all_title_match?(query)
|
29
|
-
end
|
30
|
-
|
31
27
|
end
|
32
28
|
end
|
33
29
|
end
|
data/lib/alfred/feedback/item.rb
CHANGED
@@ -63,6 +63,13 @@ module Alfred
|
|
63
63
|
send(@matcher, query)
|
64
64
|
end
|
65
65
|
|
66
|
+
#
|
67
|
+
# Matchers
|
68
|
+
#
|
69
|
+
def always_match?(query)
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
66
73
|
def title_match?(query)
|
67
74
|
return true if query.empty?
|
68
75
|
if smartcase_query(query).match(@title)
|
@@ -125,6 +132,9 @@ module Alfred
|
|
125
132
|
|
126
133
|
protected
|
127
134
|
|
135
|
+
#
|
136
|
+
# Regex helpers
|
137
|
+
#
|
128
138
|
def build_regexp(query, option)
|
129
139
|
begin
|
130
140
|
Regexp.compile(".*#{query.gsub(/\s+/,'.*')}.*", option)
|
@@ -8,7 +8,7 @@ module Alfred
|
|
8
8
|
def initialize(title, opts = {})
|
9
9
|
unless File.exist? opts[:webloc]
|
10
10
|
opts[:webloc] = ::Alfred::Util.make_webloc(
|
11
|
-
opts[:title], opts[:url],
|
11
|
+
opts[:title], opts[:url], opts[:folder])
|
12
12
|
end
|
13
13
|
|
14
14
|
@subtitle = opts[:url]
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'alfred/util'
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require "rexml/document"
|
5
|
+
|
6
|
+
module Alfred
|
7
|
+
|
8
|
+
module Handler
|
9
|
+
class Base
|
10
|
+
Base_Invoke_Order = 100
|
11
|
+
|
12
|
+
attr_reader :status, :order
|
13
|
+
|
14
|
+
def initialize(alfred, opts = {})
|
15
|
+
@core = alfred
|
16
|
+
@order = Base_Invoke_Order
|
17
|
+
@status = :initialize
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def on_parser
|
22
|
+
;
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_help
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
|
29
|
+
def feedback?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
def on_feedback
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
35
|
+
|
36
|
+
def action?(arg)
|
37
|
+
arg.is_a?(Hash) && arg[:handler].eql?(@settings[:handler])
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_action(arg)
|
41
|
+
;
|
42
|
+
end
|
43
|
+
|
44
|
+
def on_close
|
45
|
+
;
|
46
|
+
end
|
47
|
+
|
48
|
+
def register
|
49
|
+
@core.handler_controller.register(self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def <=>(other)
|
53
|
+
order <=> other.order
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def status_message(text, exitstatus)
|
58
|
+
if exitstatus == 0
|
59
|
+
return "⭕ #{text}"
|
60
|
+
else
|
61
|
+
return "❌ #{text}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# from alfred core
|
67
|
+
def xml_builder(arg)
|
68
|
+
@core.xml_builder(arg)
|
69
|
+
end
|
70
|
+
|
71
|
+
def options
|
72
|
+
@core.options
|
73
|
+
end
|
74
|
+
|
75
|
+
def parser
|
76
|
+
@core.query_parser
|
77
|
+
end
|
78
|
+
|
79
|
+
def query
|
80
|
+
@core.query
|
81
|
+
end
|
82
|
+
|
83
|
+
def ui
|
84
|
+
@core.ui
|
85
|
+
end
|
86
|
+
|
87
|
+
def feedback
|
88
|
+
@core.feedback
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
class Controller
|
94
|
+
## handlers are called based on handler.order
|
95
|
+
# 1-10 : critical handler
|
96
|
+
# 100 : base order
|
97
|
+
|
98
|
+
include Enumerable
|
99
|
+
|
100
|
+
def initialize
|
101
|
+
@handlers = SortedSet.new
|
102
|
+
@status = {:break => [:break, :exclusive]}
|
103
|
+
end
|
104
|
+
|
105
|
+
def register(handler)
|
106
|
+
raise InvalidArgument unless handler.is_a? ::Alfred::Handler::Base
|
107
|
+
@handlers.add(handler)
|
108
|
+
end
|
109
|
+
|
110
|
+
def empty?
|
111
|
+
@handlers.empty?
|
112
|
+
end
|
113
|
+
|
114
|
+
def each
|
115
|
+
return enum_for(__method__) unless block_given?
|
116
|
+
|
117
|
+
@handlers.each do |h|
|
118
|
+
yield(h)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def each_handler
|
123
|
+
return enum_for(__method__) unless block_given?
|
124
|
+
|
125
|
+
@handlers.each do |h|
|
126
|
+
yield(h)
|
127
|
+
break if @status[:break].include?(h.status)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'alfred/handler'
|
2
|
+
require 'fuzzy_match'
|
3
|
+
require 'amatch'
|
4
|
+
|
5
|
+
module Alfred
|
6
|
+
module Handler
|
7
|
+
|
8
|
+
class Autocomplete < Base
|
9
|
+
def initialize(alfred, opts = {})
|
10
|
+
super
|
11
|
+
@settings = {
|
12
|
+
:handler => 'Autocomplete' ,
|
13
|
+
:items => {} ,
|
14
|
+
:fuzzy_score => 0.5 ,
|
15
|
+
}.update(opts)
|
16
|
+
|
17
|
+
if @settings[:items].empty?
|
18
|
+
@load_from_workflow_setting = true
|
19
|
+
else
|
20
|
+
@load_from_workflow_setting = false
|
21
|
+
end
|
22
|
+
FuzzyMatch.engine = :amatch
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
def on_feedback
|
28
|
+
if @load_from_workflow_setting
|
29
|
+
@settings[:items].merge! @core.workflow_setting[:autocomplete]
|
30
|
+
end
|
31
|
+
|
32
|
+
before, option, tail = @core.last_option
|
33
|
+
|
34
|
+
base_item ={
|
35
|
+
:match? => :always_match? ,
|
36
|
+
:subtitle => "↩ to autocomplete" ,
|
37
|
+
:valid => 'no' ,
|
38
|
+
:icon => ::Alfred::Feedback.CoreServicesIcon('ForwardArrowIcon') ,
|
39
|
+
}
|
40
|
+
|
41
|
+
if @settings[:items].has_key? tail
|
42
|
+
unify_items(@settings[:items][tail]).each do |item|
|
43
|
+
base_item[:autocomplete] = "#{(before + [tail, item[:complete]]).join(' ')} "
|
44
|
+
feedback.add_item(base_item.update(item))
|
45
|
+
end
|
46
|
+
else
|
47
|
+
add_fuzzy_match_feedback(unify_items(@settings[:items][option]),
|
48
|
+
before, tail, base_item, feedback)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def add_fuzzy_match_feedback(items, before, query, base_item, to_feedback)
|
54
|
+
matcher = FuzzyMatch.new(items, :read => :complete)
|
55
|
+
matcher.find_all_with_score(query).each do |item, dice_similar, leven_similar|
|
56
|
+
next if item[:complete].size < query.size
|
57
|
+
|
58
|
+
if (item[:complete].start_with?(query) or
|
59
|
+
dice_similar > @settings[:fuzzy_score] or
|
60
|
+
leven_similar > @settings[:fuzzy_score])
|
61
|
+
|
62
|
+
base_item[:autocomplete] = "#{(before + [item[:complete]]).join(' ')} "
|
63
|
+
to_feedback.add_item(base_item.update(item))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def unify_items(items)
|
69
|
+
return [] unless items
|
70
|
+
items.map do |item|
|
71
|
+
if item.is_a? String
|
72
|
+
{:title => item, :complete => item}
|
73
|
+
elsif item.is_a? Hash
|
74
|
+
unless item.has_key? :complete
|
75
|
+
item[:complete] = item[:title]
|
76
|
+
end
|
77
|
+
item
|
78
|
+
else
|
79
|
+
raise InvalidArgument, "autocomplete handler can only accept string or hash"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'moneta'
|
2
|
+
require 'alfred/util'
|
3
|
+
|
4
|
+
#
|
5
|
+
# = Alfred Callback Hander
|
6
|
+
#
|
7
|
+
# Each callback is stored using Moneta via YAML backend.!
|
8
|
+
#
|
9
|
+
# == Example:
|
10
|
+
# Suppose we have a callback with key "demo"
|
11
|
+
#
|
12
|
+
# - @backend[ENTRIES_KEY] => {
|
13
|
+
# 'demo' => {:key => 'demo', :title => 'title', :subtitle => ...}
|
14
|
+
# }
|
15
|
+
#
|
16
|
+
# - @backend['demo'] => the feedback items
|
17
|
+
#
|
18
|
+
module Alfred::Handler
|
19
|
+
|
20
|
+
class Callback < Base
|
21
|
+
ENTRIES_KEY = 'feedback_entries'
|
22
|
+
|
23
|
+
def initialize(alfred, opts = {})
|
24
|
+
super
|
25
|
+
@settings = {
|
26
|
+
:handler => 'Callback' ,
|
27
|
+
:exclusive? => true ,
|
28
|
+
:backend_dir => @core.volatile_storage_path ,
|
29
|
+
:backend_file => 'callback.yaml' ,
|
30
|
+
:handler_order => ( Base_Invoke_Order / 12 )
|
31
|
+
}.update(opts)
|
32
|
+
|
33
|
+
@order = @settings[:handler_order]
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def on_parser
|
38
|
+
parser.on("--callback [CALLBACK]", "Alfred callback feedback") do |v|
|
39
|
+
options.callback = v || ''
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def feedback?
|
44
|
+
options.callback
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_feedback
|
48
|
+
return unless feedback?
|
49
|
+
if entries[options.callback]
|
50
|
+
feedback.merge! backend[options.callback]
|
51
|
+
@status = :exclusive if @settings[:exclusive?]
|
52
|
+
|
53
|
+
elsif entries.empty?
|
54
|
+
# show a warn feedback item
|
55
|
+
feedback.add_item(
|
56
|
+
{
|
57
|
+
:title => 'No available callback!' ,
|
58
|
+
:valid => 'no' ,
|
59
|
+
:autocomplete => '' ,
|
60
|
+
:subtitle => 'Please check it later. Background task may still be running.',
|
61
|
+
:icon => ::Alfred::Feedback.CoreServicesIcon('Unsupported') ,
|
62
|
+
}
|
63
|
+
)
|
64
|
+
else
|
65
|
+
# list available callbacks
|
66
|
+
entries.each do |key, entry|
|
67
|
+
feedback.add_item(
|
68
|
+
{
|
69
|
+
:title => "Feedback Callback: #{key}" ,
|
70
|
+
:subtitle => "#{entry[:timestamp]}",
|
71
|
+
:valid => 'no' ,
|
72
|
+
:autocomplete => "--callback '#{key}'" ,
|
73
|
+
:icon => ::Alfred::Feedback.CoreServicesIcon('AliasBadgeIcon') ,
|
74
|
+
}.merge(entry)
|
75
|
+
)
|
76
|
+
end
|
77
|
+
@status = :exclusive if @settings[:exclusive?]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def on_close
|
83
|
+
backend.close
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def on_callback(keyword, entry, feedback_items)
|
88
|
+
add_entry(entry, feedback_items)
|
89
|
+
Alfred::Util.notify("#{keyword} --callback '#{entry[:key]}'",
|
90
|
+
entry[:title] || entry[:key],
|
91
|
+
entry)
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def add_entry(entry, feedback_items)
|
96
|
+
entry.merge!(:timestamp => Time.now)
|
97
|
+
key = entry[:key]
|
98
|
+
new_entries = entries.merge(key => entry)
|
99
|
+
backend[ENTRIES_KEY] = new_entries
|
100
|
+
backend[key] = feedback_items
|
101
|
+
end
|
102
|
+
|
103
|
+
def remove_entry(key)
|
104
|
+
new_entries = entries.delete(key)
|
105
|
+
backend[ENTRIES_KEY] = new_entries
|
106
|
+
backend.delete(key)
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
def entries
|
112
|
+
backend[ENTRIES_KEY]
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def backend
|
117
|
+
@backend ||= Moneta.new(:YAML,
|
118
|
+
:file => File.join(@settings[:backend_dir],
|
119
|
+
@settings[:backend_file]))
|
120
|
+
|
121
|
+
unless @backend.key?(ENTRIES_KEY)
|
122
|
+
@backend[ENTRIES_KEY] = {}
|
123
|
+
end
|
124
|
+
@backend
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Alfred::Handler
|
2
|
+
|
3
|
+
class Config < Base
|
4
|
+
def initialize(alfred, opts = {})
|
5
|
+
super
|
6
|
+
@order = 20
|
7
|
+
@settings = {
|
8
|
+
:setting => alfred.workflow_setting ,
|
9
|
+
:break? => true ,
|
10
|
+
:handler => 'Config'
|
11
|
+
}.update(opts)
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_parser
|
16
|
+
opts.on("-c", "--config CONFIG", "Config Workflow Settings") do |v|
|
17
|
+
options.config = v
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_help
|
22
|
+
{
|
23
|
+
:kind => 'text' ,
|
24
|
+
:title => '-c, --config [query]' ,
|
25
|
+
:subtitle => 'Config Workflow Settings' ,
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_feedback
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'alfred/handler'
|
2
|
+
|
3
|
+
|
4
|
+
module Alfred
|
5
|
+
module Handler
|
6
|
+
|
7
|
+
class HelpItem < ::Hash
|
8
|
+
Base_Order = 10
|
9
|
+
def initialize(attributes = {}, &block)
|
10
|
+
super(&block)
|
11
|
+
initialize_attributes(attributes)
|
12
|
+
end
|
13
|
+
|
14
|
+
def <=>(other)
|
15
|
+
self[:order] <=> other[:order]
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def initialize_attributes(attributes)
|
21
|
+
attributes.each_pair do |att, value|
|
22
|
+
self[att] = value
|
23
|
+
end if attributes
|
24
|
+
self[:order] = Base_Order unless self[:order]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
class Help < Base
|
30
|
+
def initialize(alfred, opts = {})
|
31
|
+
super
|
32
|
+
@settings = {
|
33
|
+
:handler => 'Help' ,
|
34
|
+
:exclusive? => true ,
|
35
|
+
:with_handler_help => true ,
|
36
|
+
:items => [] ,
|
37
|
+
:handler_order => ( Base_Invoke_Order / 10 )
|
38
|
+
}.update(opts)
|
39
|
+
|
40
|
+
@order = @settings[:handler_order]
|
41
|
+
|
42
|
+
if @settings[:items].empty?
|
43
|
+
@load_from_workflow_setting = true
|
44
|
+
else
|
45
|
+
@load_from_workflow_setting = false
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
def on_parser
|
51
|
+
parser.on_tail('-?', '-h', '--help', 'Workflow Helper') do
|
52
|
+
options.help = true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_help
|
57
|
+
{
|
58
|
+
:kind => 'text' ,
|
59
|
+
:valid => 'no' ,
|
60
|
+
:autocomplete => '-h' ,
|
61
|
+
:match? => :always_match? ,
|
62
|
+
:order => (HelpItem::Base_Order * 12) ,
|
63
|
+
:title => '-?, -h, --help [Show Workflow Usage Help]' ,
|
64
|
+
:subtitle => 'Other feedbacks are blocked.' ,
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
def feedback?
|
69
|
+
options.help
|
70
|
+
end
|
71
|
+
|
72
|
+
def on_feedback
|
73
|
+
return unless feedback?
|
74
|
+
|
75
|
+
if @settings[:with_handler_help]
|
76
|
+
@settings[:items].push @core.on_help
|
77
|
+
@core.handler_controller.each do |h|
|
78
|
+
@settings[:items].push h.on_help
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if @load_from_workflow_setting
|
83
|
+
if @core.workflow_setting.has_key?(:help)
|
84
|
+
@settings[:items].push @core.workflow_setting[:help]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
@settings[:items].flatten!.compact!
|
89
|
+
@settings[:items].map! { |i| HelpItem.new(i) }.sort!
|
90
|
+
|
91
|
+
@settings[:items].each do |item|
|
92
|
+
|
93
|
+
case item[:kind]
|
94
|
+
when 'file'
|
95
|
+
item[:path] = File.expand_path(item[:path])
|
96
|
+
# action is handled by fallback action in the main loop
|
97
|
+
feedback.add_file_item(item[:path], item)
|
98
|
+
when 'url'
|
99
|
+
item[:arg] = xml_builder(
|
100
|
+
:handler => @settings[:handler] ,
|
101
|
+
:kind => item[:kind] ,
|
102
|
+
:url => item[:url]
|
103
|
+
)
|
104
|
+
|
105
|
+
feedback.add_item(
|
106
|
+
{
|
107
|
+
:icon => ::Alfred::Feedback.CoreServicesIcon('BookmarkIcon')
|
108
|
+
}.merge(item)
|
109
|
+
)
|
110
|
+
|
111
|
+
when 'text', 'message'
|
112
|
+
item[:arg] = xml_builder(
|
113
|
+
{
|
114
|
+
:handler => @settings[:handler] ,
|
115
|
+
:kind => item[:kind] ,
|
116
|
+
}
|
117
|
+
)
|
118
|
+
|
119
|
+
feedback.add_item(
|
120
|
+
{
|
121
|
+
:valid => 'no' ,
|
122
|
+
:autocomplete => '' ,
|
123
|
+
:icon => ::Alfred::Feedback.CoreServicesIcon('ClippingText') ,
|
124
|
+
}.merge(item)
|
125
|
+
)
|
126
|
+
|
127
|
+
else
|
128
|
+
if item.has_key? :title
|
129
|
+
item[:arg] = xml_builder(
|
130
|
+
{
|
131
|
+
:handler => @settings[:handler] ,
|
132
|
+
:kind => item[:kind] ,
|
133
|
+
}.merge(item)
|
134
|
+
)
|
135
|
+
|
136
|
+
feedback.add_item(
|
137
|
+
{
|
138
|
+
:icon => ::Alfred::Feedback.CoreServicesIcon('HelpIcon'),
|
139
|
+
}.merge(item)
|
140
|
+
)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
@status = :exclusive if @settings[:exclusive?]
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def on_action(arg)
|
150
|
+
return unless action?(arg)
|
151
|
+
|
152
|
+
case arg[:kind]
|
153
|
+
when 'url'
|
154
|
+
::Alfred::Util.open_url(arg[:url])
|
155
|
+
when 'file'
|
156
|
+
%x{open "#{arg[:path]}"}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|