brice 0.1.1 → 0.2.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/README +1 -1
- data/lib/brice/colours.rb +406 -0
- data/lib/brice/config.rb +36 -20
- data/lib/brice/dsl.rb +19 -31
- data/lib/brice/history.rb +168 -0
- data/lib/brice/rc/010_added_methods.rb +17 -0
- data/lib/{rc/010_libs.rb → brice/rc/020_libs.rb} +4 -5
- data/lib/brice/rc/030_history.rb +7 -0
- data/lib/brice/rc/040_colours.rb +7 -0
- data/lib/brice/rc/050_shortcuts.rb +7 -0
- data/lib/{rc/015_utility_belt.rb → brice/rc/060_utility_belt.rb} +0 -0
- data/lib/brice/rc/070_init.rb +9 -0
- data/lib/{rc/030_prompt.rb → brice/rc/080_prompt.rb} +1 -1
- data/lib/{rc/040_rails.rb → brice/rc/090_rails.rb} +1 -1
- data/lib/{rc/050_devel.rb → brice/rc/100_devel.rb} +0 -0
- data/lib/brice/shortcuts.rb +110 -0
- data/lib/brice/version.rb +3 -3
- data/lib/brice.rb +118 -108
- metadata +21 -16
- data/lib/rc/004_wirble.rb +0 -32
- data/lib/rc/005_added_methods.rb +0 -23
- data/lib/rc/020_init.rb +0 -25
@@ -0,0 +1,168 @@
|
|
1
|
+
#--
|
2
|
+
###############################################################################
|
3
|
+
# #
|
4
|
+
# A component of brice, the extra cool IRb goodness donator #
|
5
|
+
# #
|
6
|
+
# Copyright (C) 2008-2011 Jens Wille #
|
7
|
+
# #
|
8
|
+
# Authors: #
|
9
|
+
# Jens Wille <jens.wille@uni-koeln.de> #
|
10
|
+
# #
|
11
|
+
# brice is free software: you can redistribute it and/or modify it under the #
|
12
|
+
# terms of the GNU Affero General Public License as published by the Free #
|
13
|
+
# Software Foundation, either version 3 of the License, or (at your option) #
|
14
|
+
# any later version. #
|
15
|
+
# #
|
16
|
+
# brice is distributed in the hope that it will be useful, but WITHOUT ANY #
|
17
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
18
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for #
|
19
|
+
# more details. #
|
20
|
+
# #
|
21
|
+
# You should have received a copy of the GNU Affero General Public License #
|
22
|
+
# along with brice. If not, see <http://www.gnu.org/licenses/>. #
|
23
|
+
# #
|
24
|
+
###############################################################################
|
25
|
+
#++
|
26
|
+
|
27
|
+
require 'brice'
|
28
|
+
|
29
|
+
module Brice
|
30
|
+
|
31
|
+
# IRb history support.
|
32
|
+
|
33
|
+
class History
|
34
|
+
|
35
|
+
DEFAULTS = {
|
36
|
+
:path => ENV['IRB_HISTORY_FILE'] || File.join(ENV.user_home, '.irb_history'),
|
37
|
+
:size => (ENV['IRB_HISTORY_SIZE'] || 1000).to_i,
|
38
|
+
:perms => File::WRONLY | File::CREAT | File::TRUNC,
|
39
|
+
:uniq => :reverse,
|
40
|
+
:merge => true
|
41
|
+
}
|
42
|
+
|
43
|
+
def self.init(opt = {})
|
44
|
+
new(opt)
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(opt = {}, history = defined?(Readline::HISTORY) && Readline::HISTORY)
|
48
|
+
DEFAULTS.each { |key, val|
|
49
|
+
instance_variable_set("@#{key}", Brice.opt(opt, key, val))
|
50
|
+
}
|
51
|
+
|
52
|
+
@path = File.expand_path(@path)
|
53
|
+
@reverse = @uniq.to_s == 'reverse'
|
54
|
+
|
55
|
+
init_history(history) if history
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def init_history(history)
|
61
|
+
@history = history
|
62
|
+
|
63
|
+
@libedit = begin
|
64
|
+
Readline.emacs_editing_mode
|
65
|
+
true
|
66
|
+
rescue NotImplementedError
|
67
|
+
false
|
68
|
+
end
|
69
|
+
|
70
|
+
load_history
|
71
|
+
extend_history
|
72
|
+
|
73
|
+
Kernel.at_exit { save_history }
|
74
|
+
end
|
75
|
+
|
76
|
+
def read_history
|
77
|
+
File.foreach(@path) { |line|
|
78
|
+
line.chomp!
|
79
|
+
yield line
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def load_history(history = @history)
|
84
|
+
return unless File.readable?(@path)
|
85
|
+
read_history { |line| history << line }
|
86
|
+
|
87
|
+
return unless @libedit
|
88
|
+
@first_line = read_history { |line| break line }
|
89
|
+
end
|
90
|
+
|
91
|
+
def extend_history
|
92
|
+
@history.extend(Tee) if @merge && class << @history; !include?(Tee); end
|
93
|
+
end
|
94
|
+
|
95
|
+
def save_history
|
96
|
+
if @merge
|
97
|
+
load_history(lines = [])
|
98
|
+
@history.tee! { |t| lines.concat(t) }
|
99
|
+
else
|
100
|
+
lines = @history.to_a
|
101
|
+
end
|
102
|
+
|
103
|
+
lines.unshift(@first_line) if @first_line
|
104
|
+
|
105
|
+
lines.reverse! if @reverse
|
106
|
+
lines.uniq! if @uniq
|
107
|
+
lines.reverse! if @reverse
|
108
|
+
|
109
|
+
lines.slice!(0, lines.size - @size)
|
110
|
+
|
111
|
+
File.open(@path, @perms) { |f| f.puts(lines) }
|
112
|
+
end
|
113
|
+
|
114
|
+
module Tee
|
115
|
+
|
116
|
+
def self.extended(base)
|
117
|
+
class << base
|
118
|
+
alias_method :push_without_tee, :<<
|
119
|
+
alias_method :push_m_without_tee, :push
|
120
|
+
|
121
|
+
alias_method :<<, :push_with_tee
|
122
|
+
alias_method :push, :push_m_with_tee
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def tee
|
127
|
+
@tee ||= []
|
128
|
+
end
|
129
|
+
|
130
|
+
def tee!
|
131
|
+
yield tee
|
132
|
+
ensure
|
133
|
+
tee.clear
|
134
|
+
end
|
135
|
+
|
136
|
+
def push_with_tee(arg)
|
137
|
+
_tee_delete(arg)
|
138
|
+
|
139
|
+
tee << arg
|
140
|
+
push_without_tee(arg)
|
141
|
+
end
|
142
|
+
|
143
|
+
def push_m_with_tee(*args)
|
144
|
+
_tee_delete(*args)
|
145
|
+
|
146
|
+
tee.concat(args)
|
147
|
+
push_m_without_tee(*args)
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def _tee_delete(*args)
|
153
|
+
args.each { |arg| tee.delete(arg) }
|
154
|
+
|
155
|
+
indexes = []
|
156
|
+
|
157
|
+
each_with_index { |line, index|
|
158
|
+
indexes << index if args.include?(line)
|
159
|
+
}
|
160
|
+
|
161
|
+
indexes.reverse_each { |index| delete_at(index) }
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Load AddedMethods[http://added_methods.rubyforge.org/] if one
|
2
|
+
# (or both) of the following environment variables has been set:
|
3
|
+
#
|
4
|
+
# <tt>WATCH_FOR_ADDED_METHODS</tt>:: Regular expression or <tt>true</tt>
|
5
|
+
# <tt>WATCH_FOR_ADDED_METHODS_IN</tt>:: Space- or comma-delimited list of class names
|
6
|
+
|
7
|
+
brice 'added_methods' do |config|
|
8
|
+
|
9
|
+
pattern = ENV['WATCH_FOR_ADDED_METHODS']
|
10
|
+
list = ENV['WATCH_FOR_ADDED_METHODS_IN']
|
11
|
+
|
12
|
+
AddedMethods.init(
|
13
|
+
(pattern != 'true' || list) && Regexp.new(pattern || ''),
|
14
|
+
(list || '').split(/\s|,/).reject { |name| name.empty? }
|
15
|
+
) if pattern || list
|
16
|
+
|
17
|
+
end
|
@@ -2,15 +2,14 @@
|
|
2
2
|
|
3
3
|
brice 'libs' => nil do |config|
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
(config.empty? ? %w[
|
6
|
+
pp
|
7
7
|
yaml
|
8
8
|
tempfile
|
9
9
|
benchmark
|
10
10
|
backports
|
11
11
|
what_methods
|
12
|
-
|
13
|
-
|
14
|
-
libs.each { |lib| brice_require lib }
|
12
|
+
irb/completion
|
13
|
+
] : config).each { |lib| brice_require lib }
|
15
14
|
|
16
15
|
end
|
File without changes
|
@@ -18,7 +18,7 @@ brice 'rails' => nil do |config|
|
|
18
18
|
prompt = File.basename(Dir.pwd) << hint
|
19
19
|
end
|
20
20
|
|
21
|
-
# add "ENV['RAILS_SANDBOX'] = 'true'" in rails
|
21
|
+
# add "ENV['RAILS_SANDBOX'] = 'true'" in rails/lib/commands/console.rb
|
22
22
|
prompt << "#{ENV['RAILS_SANDBOX'] ? '>' : '$'} "
|
23
23
|
|
24
24
|
IRB.conf[:PROMPT] ||= {}
|
File without changes
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#--
|
2
|
+
###############################################################################
|
3
|
+
# #
|
4
|
+
# A component of brice, the extra cool IRb goodness donator #
|
5
|
+
# #
|
6
|
+
# Copyright (C) 2008-2011 Jens Wille #
|
7
|
+
# #
|
8
|
+
# Authors: #
|
9
|
+
# Jens Wille <jens.wille@uni-koeln.de> #
|
10
|
+
# #
|
11
|
+
# brice is free software: you can redistribute it and/or modify it under the #
|
12
|
+
# terms of the GNU Affero General Public License as published by the Free #
|
13
|
+
# Software Foundation, either version 3 of the License, or (at your option) #
|
14
|
+
# any later version. #
|
15
|
+
# #
|
16
|
+
# brice is distributed in the hope that it will be useful, but WITHOUT ANY #
|
17
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
18
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for #
|
19
|
+
# more details. #
|
20
|
+
# #
|
21
|
+
# You should have received a copy of the GNU Affero General Public License #
|
22
|
+
# along with brice. If not, see <http://www.gnu.org/licenses/>. #
|
23
|
+
# #
|
24
|
+
###############################################################################
|
25
|
+
#++
|
26
|
+
|
27
|
+
require 'brice'
|
28
|
+
|
29
|
+
module Brice
|
30
|
+
|
31
|
+
# Convenient shortcut methods.
|
32
|
+
module Shortcuts
|
33
|
+
|
34
|
+
extend self
|
35
|
+
|
36
|
+
def init(opt = {})
|
37
|
+
init_object if Brice.opt(opt, :object)
|
38
|
+
init_ri if Brice.opt(opt, :ri)
|
39
|
+
end
|
40
|
+
|
41
|
+
def init_object
|
42
|
+
Object.send(:include, ObjectShortcuts)
|
43
|
+
end
|
44
|
+
|
45
|
+
def init_ri
|
46
|
+
Module.class_eval {
|
47
|
+
def ri(*args)
|
48
|
+
ri!('--no-pager', *args)
|
49
|
+
end
|
50
|
+
|
51
|
+
def ri!(*args)
|
52
|
+
opts, args = args.partition { |arg| arg.to_s =~ /\A--/ }
|
53
|
+
|
54
|
+
args.empty? ? args << name : args.map! { |arg|
|
55
|
+
arg, method = arg.to_s, nil
|
56
|
+
|
57
|
+
delim = [['#', :instance_method], ['::', :method]].find { |i, m|
|
58
|
+
match = arg.sub!(/\A#{i}/, '')
|
59
|
+
method = begin; send(m, arg); rescue NameError; end
|
60
|
+
|
61
|
+
break i if match || method
|
62
|
+
} or next arg
|
63
|
+
|
64
|
+
"#{method && method.to_s[/\((\w+)\)/, 1] || name}#{delim}#{arg}"
|
65
|
+
}
|
66
|
+
|
67
|
+
system('ri', *opts.concat(args))
|
68
|
+
end
|
69
|
+
}
|
70
|
+
|
71
|
+
instance_eval {
|
72
|
+
def ri(*args); Kernel.ri(*args); end
|
73
|
+
def ri!(*args); Kernel.ri!(*args); end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
module ObjectShortcuts
|
78
|
+
|
79
|
+
# Print object methods, sorted by name. (excluding methods that
|
80
|
+
# exist in the class Object)
|
81
|
+
def po(obj = self)
|
82
|
+
obj.methods.sort - Object.methods
|
83
|
+
end
|
84
|
+
|
85
|
+
# Print object constants, sorted by name.
|
86
|
+
def poc(obj = self)
|
87
|
+
obj.constants.sort if obj.respond_to?(:constants)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Cf. <http://rubyforge.org/snippet/detail.php?type=snippet&id=22>
|
91
|
+
def aorta(obj = self)
|
92
|
+
tempfile = Tempfile.new('aorta')
|
93
|
+
YAML.dump(obj, tempfile)
|
94
|
+
tempfile.close
|
95
|
+
|
96
|
+
path = tempfile.path
|
97
|
+
|
98
|
+
system(ENV['VISUAL'] || ENV['EDITOR'] || 'vi', path)
|
99
|
+
return obj unless File.exists?(path)
|
100
|
+
|
101
|
+
content = YAML.load_file(path)
|
102
|
+
tempfile.unlink
|
103
|
+
content
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
data/lib/brice/version.rb
CHANGED
data/lib/brice.rb
CHANGED
@@ -25,146 +25,156 @@
|
|
25
25
|
#++
|
26
26
|
|
27
27
|
require 'irb'
|
28
|
-
require 'rubygems'
|
29
28
|
require 'nuggets/env/user_home'
|
30
29
|
|
31
|
-
|
32
|
-
lib = "brice/#{lib}"
|
33
|
-
require lib
|
34
|
-
}
|
30
|
+
require 'brice/version'
|
35
31
|
|
36
|
-
|
32
|
+
module Brice
|
33
|
+
|
34
|
+
autoload :Config, 'brice/config'
|
35
|
+
autoload :Colours, 'brice/colours'
|
36
|
+
autoload :DSL, 'brice/dsl'
|
37
|
+
autoload :History, 'brice/history'
|
38
|
+
autoload :Shortcuts, 'brice/shortcuts'
|
39
|
+
|
40
|
+
RC_DIR = __FILE__.sub(/\.rb\z/, '/rc')
|
37
41
|
|
38
42
|
BRICE_HOME = File.join(ENV.user_home, '.brice')
|
39
43
|
|
40
44
|
@verbose = false
|
41
45
|
@quiet = false
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
include DSL
|
47
|
+
extend self
|
48
|
+
include DSL
|
46
49
|
|
47
|
-
|
50
|
+
attr_reader :config, :irb_rc
|
48
51
|
|
49
|
-
|
52
|
+
attr_accessor :verbose, :quiet
|
50
53
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
@config = Config.new(rc_files(true).map { |rc|
|
59
|
-
File.basename(rc, '.rb').sub(/\A\d+_/, '')
|
60
|
-
})
|
54
|
+
# call-seq:
|
55
|
+
# Brice.init { |config| ... }
|
56
|
+
# Brice.init(:verbose => true) { |config| ... }
|
57
|
+
#
|
58
|
+
# Initialize Brice and optionally configure any packages.
|
59
|
+
def init(options = {})
|
60
|
+
@irb_rc = []
|
61
61
|
|
62
|
-
|
63
|
-
|
62
|
+
@config = Config.new(rc_files(true).map { |rc|
|
63
|
+
File.basename(rc, '.rb').sub(/\A\d+_/, '')
|
64
|
+
})
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
66
|
+
options.each { |key, value|
|
67
|
+
if respond_to?(method = "#{key}=")
|
68
|
+
send(method, value)
|
69
|
+
else
|
70
|
+
raise ArgumentError, "illegal option: #{key}"
|
71
|
+
end
|
72
|
+
}
|
71
73
|
|
72
|
-
|
74
|
+
yield config if block_given?
|
73
75
|
|
74
|
-
|
75
|
-
|
76
|
+
load_rc_files(true)
|
77
|
+
finalize_irb_rc!
|
76
78
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
# call-seq:
|
81
|
-
# Brice.config = config
|
82
|
-
#
|
83
|
-
# Set config to +config+. Raises a TypeError if +config+ is not a
|
84
|
-
# Brice::Config.
|
85
|
-
def config=(config)
|
86
|
-
raise TypeError, "expected Brice::Config, got #{config.class}" \
|
87
|
-
unless config.is_a?(Config)
|
79
|
+
config
|
80
|
+
end
|
88
81
|
|
82
|
+
# call-seq:
|
83
|
+
# Brice.config = config
|
84
|
+
#
|
85
|
+
# Set config to +config+. Raises a TypeError if +config+ is not a
|
86
|
+
# Brice::Config.
|
87
|
+
def config=(config)
|
88
|
+
if config.is_a?(Config)
|
89
89
|
@config = config
|
90
|
+
else
|
91
|
+
raise TypeError, "expected Brice::Config, got #{config.class}"
|
90
92
|
end
|
93
|
+
end
|
91
94
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
95
|
+
# call-seq:
|
96
|
+
# Brice.include?(package) => true or false
|
97
|
+
# Brice.have?(package) => true or false
|
98
|
+
#
|
99
|
+
# See whether package +package+ is enabled/included.
|
100
|
+
def include?(package)
|
101
|
+
config.include?(package)
|
102
|
+
end
|
100
103
|
|
101
|
-
|
104
|
+
alias_method :have?, :include?
|
102
105
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
106
|
+
# call-seq:
|
107
|
+
# Brice.rc_files(include_custom_extensions = false) => anArray
|
108
|
+
#
|
109
|
+
# Get the extension files, optionally including custom extensions
|
110
|
+
# if +include_custom_extensions+ is true.
|
111
|
+
def rc_files(include_custom_extensions = false)
|
112
|
+
@rc_files ||= find_rc_files
|
113
|
+
include_custom_extensions ? @rc_files + custom_extensions : @rc_files
|
114
|
+
end
|
112
115
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
116
|
+
# call-seq:
|
117
|
+
# Brice.custom_extensions => anArray
|
118
|
+
#
|
119
|
+
# Get the custom extension files.
|
120
|
+
def custom_extensions
|
121
|
+
@custom_extensions ||= find_rc_files(BRICE_HOME)
|
122
|
+
end
|
120
123
|
|
121
|
-
|
124
|
+
# call-seq:
|
125
|
+
# Brice.opt(opt, key, default = true) -> anObject
|
126
|
+
#
|
127
|
+
# Returns the value of +opt+ at +key+ if present, or +default+
|
128
|
+
# otherwise.
|
129
|
+
def opt(opt, key, default = true)
|
130
|
+
opt.is_a?(Hash) && opt.has_key?(key) ? opt[key] : default
|
131
|
+
end
|
122
132
|
|
123
|
-
|
124
|
-
# load_rc_files(include_custom_extensions = false) => anArray
|
125
|
-
#
|
126
|
-
# Load the extension files, optionally including custom extensions
|
127
|
-
# if +include_custom_extensions+ is true.
|
128
|
-
def load_rc_files(include_custom_extensions = false)
|
129
|
-
Object.send(:include, DSL)
|
133
|
+
private
|
130
134
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
+
# call-seq:
|
136
|
+
# load_rc_files(include_custom_extensions = false) => anArray
|
137
|
+
#
|
138
|
+
# Load the extension files, optionally including custom extensions
|
139
|
+
# if +include_custom_extensions+ is true.
|
140
|
+
def load_rc_files(include_custom_extensions = false)
|
141
|
+
Object.send(:include, DSL)
|
135
142
|
|
136
|
-
|
137
|
-
|
143
|
+
res = rc_files.each { |rc|
|
144
|
+
warn "Loading #{rc}..." if verbose
|
145
|
+
brice_load rc
|
146
|
+
}
|
138
147
|
|
139
|
-
|
140
|
-
|
141
|
-
#
|
142
|
-
# Load the custom extension files.
|
143
|
-
def load_custom_extensions
|
144
|
-
custom_extensions.each { |rc|
|
145
|
-
warn "Loading custom #{rc}..." if verbose
|
146
|
-
brice_load rc
|
147
|
-
}
|
148
|
-
end
|
148
|
+
include_custom_extensions ? res += load_custom_extensions : res
|
149
|
+
end
|
149
150
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
151
|
+
# call-seq:
|
152
|
+
# load_custom_extensions => anArray
|
153
|
+
#
|
154
|
+
# Load the custom extension files.
|
155
|
+
def load_custom_extensions
|
156
|
+
custom_extensions.each { |rc|
|
157
|
+
warn "Loading custom #{rc}..." if verbose
|
158
|
+
brice_load rc
|
159
|
+
}
|
160
|
+
end
|
157
161
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
} unless irb_rc.empty?
|
166
|
-
end
|
162
|
+
# call-seq:
|
163
|
+
# find_rc_files(dir = ...) => anArray
|
164
|
+
#
|
165
|
+
# Find the actual extension files in +dir+.
|
166
|
+
def find_rc_files(dir = RC_DIR)
|
167
|
+
File.directory?(dir) ? Dir["#{dir}/*.rb"].sort : []
|
168
|
+
end
|
167
169
|
|
170
|
+
# call-seq:
|
171
|
+
# finalize_irb_rc! => aProc
|
172
|
+
#
|
173
|
+
# Generate proc for IRB_RC from all added procs.
|
174
|
+
def finalize_irb_rc!
|
175
|
+
IRB.conf[:IRB_RC] = lambda { |context|
|
176
|
+
irb_rc.each { |rc| rc[context] }
|
177
|
+
} unless irb_rc.empty?
|
168
178
|
end
|
169
179
|
|
170
180
|
end
|