highline 2.0.0.pre.develop.2 → 2.0.0.pre.develop.4
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.
- checksums.yaml +4 -4
- data/Changelog.md +28 -0
- data/README.md +188 -0
- data/Rakefile +0 -11
- data/highline.gemspec +2 -4
- data/lib/highline.rb +170 -73
- data/lib/highline/builtin_styles.rb +18 -2
- data/lib/highline/color_scheme.rb +15 -5
- data/lib/highline/compatibility.rb +6 -1
- data/lib/highline/custom_errors.rb +43 -6
- data/lib/highline/import.rb +10 -3
- data/lib/highline/list.rb +95 -7
- data/lib/highline/list_renderer.rb +36 -17
- data/lib/highline/menu.rb +73 -18
- data/lib/highline/paginator.rb +10 -0
- data/lib/highline/question.rb +98 -41
- data/lib/highline/question/answer_converter.rb +19 -0
- data/lib/highline/question_asker.rb +21 -17
- data/lib/highline/simulate.rb +10 -1
- data/lib/highline/statement.rb +62 -38
- data/lib/highline/string.rb +26 -25
- data/lib/highline/string_extensions.rb +60 -32
- data/lib/highline/style.rb +125 -25
- data/lib/highline/template_renderer.rb +25 -1
- data/lib/highline/terminal.rb +124 -1
- data/lib/highline/terminal/io_console.rb +6 -75
- data/lib/highline/terminal/ncurses.rb +7 -8
- data/lib/highline/terminal/unix_stty.rb +35 -81
- data/lib/highline/version.rb +1 -1
- data/lib/highline/wrapper.rb +9 -0
- data/test/test_highline.rb +1 -1
- metadata +6 -13
- data/INSTALL +0 -59
- data/README.rdoc +0 -77
- data/setup.rb +0 -1360
data/lib/highline/style.rb
CHANGED
@@ -1,48 +1,77 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
3
|
#--
|
4
|
-
# color_scheme.rb
|
4
|
+
# originally color_scheme.rb
|
5
5
|
#
|
6
6
|
# Created by Richard LeBer on 2011-06-27.
|
7
7
|
# Copyright 2011. All rights reserved
|
8
8
|
#
|
9
9
|
# This is Free Software. See LICENSE and COPYING for details
|
10
10
|
|
11
|
+
|
11
12
|
class HighLine
|
12
13
|
|
14
|
+
# Creates a style using {.find_or_create_style} or
|
15
|
+
# {.find_or_create_style_list}
|
16
|
+
# @param args [Array<Style, Hash, String>] style properties
|
17
|
+
# @return [Style]
|
13
18
|
def self.Style(*args)
|
14
19
|
args = args.compact.flatten
|
15
20
|
if args.size==1
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
elsif arg.to_s.downcase =~ /^on_rgb_([a-f0-9]{6})$/
|
34
|
-
Style.rgb($1).on
|
21
|
+
find_or_create_style(args.first)
|
22
|
+
else
|
23
|
+
find_or_create_style_list(*args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Search for a Style with the given properties and return it.
|
28
|
+
# If there's no matched Style, it creates one.
|
29
|
+
# You can pass a Style, String or a Hash.
|
30
|
+
# @param arg [Style, String, Hash] style properties
|
31
|
+
# @return [Style] found or creted Style
|
32
|
+
def self.find_or_create_style(arg)
|
33
|
+
if arg.is_a?(Style)
|
34
|
+
Style.list[arg.name] || Style.index(arg)
|
35
|
+
elsif arg.is_a?(::String) && arg =~ /^\e\[/ # arg is a code
|
36
|
+
if styles = Style.code_index[arg]
|
37
|
+
styles.first
|
35
38
|
else
|
36
|
-
|
39
|
+
Style.new(:code=>arg)
|
37
40
|
end
|
41
|
+
elsif style = Style.list[arg]
|
42
|
+
style
|
43
|
+
elsif HighLine.color_scheme && HighLine.color_scheme[arg]
|
44
|
+
HighLine.color_scheme[arg]
|
45
|
+
elsif arg.is_a?(Hash)
|
46
|
+
Style.new(arg)
|
47
|
+
elsif arg.to_s.downcase =~ /^rgb_([a-f0-9]{6})$/
|
48
|
+
Style.rgb($1)
|
49
|
+
elsif arg.to_s.downcase =~ /^on_rgb_([a-f0-9]{6})$/
|
50
|
+
Style.rgb($1).on
|
38
51
|
else
|
39
|
-
|
40
|
-
Style.list[name] || Style.new(:list=>args)
|
52
|
+
raise NameError, "#{arg.inspect} is not a defined Style"
|
41
53
|
end
|
42
54
|
end
|
43
55
|
|
56
|
+
# Find a Style list or create a new one.
|
57
|
+
# @param args [Array<Symbol>] an Array of Symbols of each style
|
58
|
+
# that will be on the style list.
|
59
|
+
# @return [Style] Style list
|
60
|
+
# @example Creating a Style list of the combined RED and BOLD styles.
|
61
|
+
# style_list = HighLine.find_or_create_style_list(:red, :bold)
|
62
|
+
|
63
|
+
def self.find_or_create_style_list(*args)
|
64
|
+
name = args
|
65
|
+
Style.list[name] || Style.new(:list=>args)
|
66
|
+
end
|
67
|
+
|
68
|
+
# ANSI styles to be used by HighLine.
|
44
69
|
class Style
|
45
70
|
|
71
|
+
# Index the given style.
|
72
|
+
# Uses @code_index (Hash) as repository.
|
73
|
+
# @param style [Style]
|
74
|
+
# @return [Style] the given style
|
46
75
|
def self.index(style)
|
47
76
|
if style.name
|
48
77
|
@styles ||= {}
|
@@ -57,6 +86,9 @@ class HighLine
|
|
57
86
|
style
|
58
87
|
end
|
59
88
|
|
89
|
+
# Clear all custom Styles, restoring the Style index to
|
90
|
+
# builtin styles only.
|
91
|
+
# @return [void]
|
60
92
|
def self.clear_index
|
61
93
|
# reset to builtin only styles
|
62
94
|
@styles = list.select { |name, style| style.builtin }
|
@@ -64,16 +96,40 @@ class HighLine
|
|
64
96
|
@styles.each { |name, style| index(style) }
|
65
97
|
end
|
66
98
|
|
99
|
+
# Converts all given color codes to hexadecimal and
|
100
|
+
# join them in a single string. If any given color code
|
101
|
+
# is already a String, doesn't perform any convertion.
|
102
|
+
#
|
103
|
+
# @param colors [Array<Numeric, String>] color codes
|
104
|
+
# @return [String] all color codes joined
|
105
|
+
# @example
|
106
|
+
# HighLine::Style.rgb_hex(9, 10, "11") # => "090a11"
|
67
107
|
def self.rgb_hex(*colors)
|
68
108
|
colors.map do |color|
|
69
109
|
color.is_a?(Numeric) ? '%02x'%color : color.to_s
|
70
110
|
end.join
|
71
111
|
end
|
72
112
|
|
113
|
+
# Split an rgb code string into its 3 numerical compounds.
|
114
|
+
# @param hex [String] rgb code string like "010F0F"
|
115
|
+
# @return [Array<Numeric>] numerical compounds like [1, 15, 15]
|
116
|
+
# @example
|
117
|
+
# HighLine::Style.rgb_parts("090A0B") # => [9, 10, 11]
|
118
|
+
|
73
119
|
def self.rgb_parts(hex)
|
74
120
|
hex.scan(/../).map{|part| part.to_i(16)}
|
75
121
|
end
|
76
122
|
|
123
|
+
# Search for or create a new Style from the colors provided.
|
124
|
+
# @param colors (see .rgb_hex)
|
125
|
+
# @return [Style] a Style with the rgb colors provided.
|
126
|
+
# @example Creating a new Style based on rgb code
|
127
|
+
# rgb_style = HighLine::Style.rgb(9, 10, 11)
|
128
|
+
#
|
129
|
+
# rgb_style.name # => :rgb_090a0b
|
130
|
+
# rgb_style.code # => "\e[38;5;16m"
|
131
|
+
# rgb_style.rgb # => [9, 10, 11]
|
132
|
+
#
|
77
133
|
def self.rgb(*colors)
|
78
134
|
hex = rgb_hex(*colors)
|
79
135
|
name = ('rgb_' + hex).to_sym
|
@@ -85,34 +141,58 @@ class HighLine
|
|
85
141
|
end
|
86
142
|
end
|
87
143
|
|
144
|
+
# Returns the rgb number to be used as escape code on ANSI terminals.
|
145
|
+
# @param parts [Array<Numeric>] three numerical codes for red, green and blue
|
146
|
+
# @return [Numeric] to be used as escape code on ANSI terminals
|
88
147
|
def self.rgb_number(*parts)
|
89
148
|
parts = parts.flatten
|
90
149
|
16 + parts.inject(0) {|kode, part| kode*6 + (part/256.0*6.0).floor}
|
91
150
|
end
|
92
151
|
|
152
|
+
# From an ANSI number (color escape code), craft an 'rgb_hex' code of it
|
153
|
+
# @param ansi_number [Integer] ANSI escape code
|
154
|
+
# @return [String] all color codes joined as {.rgb_hex}
|
93
155
|
def self.ansi_rgb_to_hex(ansi_number)
|
94
156
|
raise "Invalid ANSI rgb code #{ansi_number}" unless (16..231).include?(ansi_number)
|
95
157
|
parts = (ansi_number-16).to_s(6).rjust(3,'0').scan(/./).map{|d| (d.to_i*255.0/6.0).ceil}
|
96
158
|
rgb_hex(*parts)
|
97
159
|
end
|
98
160
|
|
161
|
+
# @return [Hash] list of all cached Styles
|
99
162
|
def self.list
|
100
163
|
@styles ||= {}
|
101
164
|
end
|
102
165
|
|
166
|
+
# @return [Hash] list of all cached Style codes
|
103
167
|
def self.code_index
|
104
168
|
@code_index ||= {}
|
105
169
|
end
|
106
170
|
|
171
|
+
# Remove any ANSI color escape sequence of the given String.
|
172
|
+
# @param string [String]
|
173
|
+
# @return [String]
|
107
174
|
def self.uncolor(string)
|
108
175
|
string.gsub(/\e\[\d+(;\d+)*m/, '')
|
109
176
|
end
|
110
177
|
|
111
|
-
|
112
|
-
|
178
|
+
# Style name
|
179
|
+
# @return [Symbol] the name of the Style
|
180
|
+
attr_reader :name
|
181
|
+
|
182
|
+
# When a compound Style, returns a list of its components.
|
183
|
+
# @return [Array<Symbol>] components of a Style list
|
184
|
+
attr_reader :list
|
185
|
+
|
186
|
+
# @return [Array] the three numerical rgb codes. Ex: [10, 12, 127]
|
187
|
+
attr_accessor :rgb
|
188
|
+
|
189
|
+
# @return [Boolean] true if the Style is builtin or not.
|
190
|
+
attr_accessor :builtin
|
113
191
|
|
114
192
|
# Single color/styles have :name, :code, :rgb (possibly), :builtin
|
115
193
|
# Compound styles have :name, :list, :builtin
|
194
|
+
#
|
195
|
+
# @param defn [Hash] options for the Style to be created.
|
116
196
|
def initialize(defn = {})
|
117
197
|
@definition = defn
|
118
198
|
@name = defn[:name]
|
@@ -129,18 +209,27 @@ class HighLine
|
|
129
209
|
self.class.index self unless defn[:no_index]
|
130
210
|
end
|
131
211
|
|
212
|
+
# Duplicate current Style using the same definition used to create it.
|
213
|
+
# @return [Style] duplicated Style
|
132
214
|
def dup
|
133
215
|
self.class.new(@definition)
|
134
216
|
end
|
135
217
|
|
218
|
+
# @return [Hash] the definition used to create this Style
|
136
219
|
def to_hash
|
137
220
|
@definition
|
138
221
|
end
|
139
222
|
|
223
|
+
# Uses the Style definition to add ANSI color escape codes
|
224
|
+
# to a a given String
|
225
|
+
# @param string [String] to be colored
|
226
|
+
# @return [String] an ANSI colored string
|
140
227
|
def color(string)
|
141
228
|
code + string + HighLine::CLEAR
|
142
229
|
end
|
143
230
|
|
231
|
+
# @return [String] all codes of the Style list joined together (if a Style list)
|
232
|
+
# @return [String] the Style code
|
144
233
|
def code
|
145
234
|
if @list
|
146
235
|
@list.map{|element| HighLine.Style(element).code}.join
|
@@ -149,18 +238,25 @@ class HighLine
|
|
149
238
|
end
|
150
239
|
end
|
151
240
|
|
241
|
+
# @return [Integer] the RED component of the rgb code
|
152
242
|
def red
|
153
243
|
@rgb && @rgb[0]
|
154
244
|
end
|
155
245
|
|
246
|
+
# @return [Integer] the GREEN component of the rgb code
|
156
247
|
def green
|
157
248
|
@rgb && @rgb[1]
|
158
249
|
end
|
159
250
|
|
251
|
+
# @return [Integer] the BLUE component of the rgb code
|
160
252
|
def blue
|
161
253
|
@rgb && @rgb[2]
|
162
254
|
end
|
163
255
|
|
256
|
+
# Duplicate Style with some minor changes
|
257
|
+
# @param new_name [Symbol]
|
258
|
+
# @param options [Hash] Style attributes to be changed
|
259
|
+
# @return [Style] new Style with changed attributes
|
164
260
|
def variant(new_name, options={})
|
165
261
|
raise "Cannot create a variant of a style list (#{inspect})" if @list
|
166
262
|
new_code = options[:code] || code
|
@@ -172,15 +268,19 @@ class HighLine
|
|
172
268
|
self.class.new(self.to_hash.merge(:name=>new_name, :code=>new_code, :rgb=>new_rgb))
|
173
269
|
end
|
174
270
|
|
271
|
+
# Uses the color as background and return a new style.
|
272
|
+
# @return [Style]
|
175
273
|
def on
|
176
274
|
new_name = ('on_'+@name.to_s).to_sym
|
177
275
|
self.class.list[new_name] ||= variant(new_name, :increment=>10)
|
178
276
|
end
|
179
277
|
|
278
|
+
# @return [Style] a brighter version of this Style
|
180
279
|
def bright
|
181
280
|
create_bright_variant(:bright)
|
182
281
|
end
|
183
282
|
|
283
|
+
# @return [Style] a lighter version of this Style
|
184
284
|
def light
|
185
285
|
create_bright_variant(:light)
|
186
286
|
end
|
@@ -3,13 +3,29 @@
|
|
3
3
|
require 'forwardable'
|
4
4
|
|
5
5
|
class HighLine
|
6
|
+
# Renders an erb template taking a {Question} and a {HighLine} instance
|
7
|
+
# as context.
|
6
8
|
class TemplateRenderer
|
7
9
|
extend Forwardable
|
8
10
|
|
9
11
|
def_delegators :@highline, :color, :list, :key
|
10
12
|
def_delegators :@source, :answer_type, :prompt, :header, :answer
|
11
13
|
|
12
|
-
|
14
|
+
# @return [ERB] ERB template being rendered
|
15
|
+
attr_reader :template
|
16
|
+
|
17
|
+
# @return [Question, Menu] Question instance used as context
|
18
|
+
attr_reader :source
|
19
|
+
|
20
|
+
# @return [HighLine] HighLine instance used as context
|
21
|
+
attr_reader :highline
|
22
|
+
|
23
|
+
# Initializes the TemplateRenderer object with its template and
|
24
|
+
# HighLine and Question contexts.
|
25
|
+
#
|
26
|
+
# @param template [ERB] ERB template.
|
27
|
+
# @param source [Question] question object.
|
28
|
+
# @param highline [HighLine] HighLine instance.
|
13
29
|
|
14
30
|
def initialize(template, source, highline)
|
15
31
|
@template = template
|
@@ -17,20 +33,28 @@ class HighLine
|
|
17
33
|
@highline = highline
|
18
34
|
end
|
19
35
|
|
36
|
+
# @return [String] rendered template
|
20
37
|
def render
|
21
38
|
template.result(binding)
|
22
39
|
end
|
23
40
|
|
41
|
+
# Returns an error message when the called method
|
42
|
+
# is not available.
|
43
|
+
# @return [String] error message.
|
24
44
|
def method_missing(method, *args)
|
25
45
|
"Method #{method} with args #{args.inspect} " +
|
26
46
|
"is not available on #{self.inspect}. " +
|
27
47
|
"Try #{methods(false).sort.inspect}"
|
28
48
|
end
|
29
49
|
|
50
|
+
# @return [Question, Menu] {#source} attribute.
|
30
51
|
def menu
|
31
52
|
source
|
32
53
|
end
|
33
54
|
|
55
|
+
# If some constant is missing at this TemplateRenderer instance,
|
56
|
+
# get it from HighLine. Useful to get color and style contants.
|
57
|
+
# @param name [Symbol] automatically passed constant's name as Symbol
|
34
58
|
def self.const_missing(name)
|
35
59
|
HighLine.const_get(name)
|
36
60
|
end
|
data/lib/highline/terminal.rb
CHANGED
@@ -12,7 +12,14 @@
|
|
12
12
|
require "highline/compatibility"
|
13
13
|
|
14
14
|
class HighLine
|
15
|
+
# Basic Terminal class which HighLine will direct
|
16
|
+
# input and output to.
|
17
|
+
# The specialized Terminals all decend from this HighLine::Terminal class
|
15
18
|
class Terminal
|
19
|
+
|
20
|
+
# Probe for and return a suitable Terminal instance
|
21
|
+
# @param input [IO] input stream
|
22
|
+
# @param output [IO] output stream
|
16
23
|
def self.get_terminal(input, output)
|
17
24
|
terminal = nil
|
18
25
|
|
@@ -34,22 +41,37 @@ class HighLine
|
|
34
41
|
terminal
|
35
42
|
end
|
36
43
|
|
37
|
-
|
44
|
+
# @return [IO] input stream
|
45
|
+
attr_reader :input
|
46
|
+
|
47
|
+
# @return [IO] output stream
|
48
|
+
attr_reader :output
|
38
49
|
|
50
|
+
# Creates a terminal instance based on given input and output streams.
|
51
|
+
# @param input [IO] input stream
|
52
|
+
# @param output [IO] output stream
|
39
53
|
def initialize(input, output)
|
40
54
|
@input = input
|
41
55
|
@output = output
|
42
56
|
end
|
43
57
|
|
58
|
+
# An initialization callback.
|
59
|
+
# It is called by {.get_terminal}.
|
44
60
|
def initialize_system_extensions
|
45
61
|
end
|
46
62
|
|
63
|
+
# @return [Array<Integer, Integer>] two value terminal
|
64
|
+
# size like [columns, lines]
|
47
65
|
def terminal_size
|
66
|
+
[80, 24]
|
48
67
|
end
|
49
68
|
|
69
|
+
# Enter Raw No Echo mode.
|
50
70
|
def raw_no_echo_mode
|
51
71
|
end
|
52
72
|
|
73
|
+
# Yieds a block to be executed in Raw No Echo mode and
|
74
|
+
# then restore the terminal state.
|
53
75
|
def raw_no_echo_mode_exec
|
54
76
|
raw_no_echo_mode
|
55
77
|
yield
|
@@ -57,22 +79,123 @@ class HighLine
|
|
57
79
|
restore_mode
|
58
80
|
end
|
59
81
|
|
82
|
+
# Restore terminal to its default mode
|
60
83
|
def restore_mode
|
61
84
|
end
|
62
85
|
|
86
|
+
# Get one character from the terminal
|
87
|
+
# @return [String] one character
|
63
88
|
def get_character
|
64
89
|
end
|
65
90
|
|
91
|
+
# Get one line from the terminal and format accordling.
|
92
|
+
# Use readline if question has readline mode set.
|
93
|
+
# @param question [HighLine::Question]
|
94
|
+
# @param highline [HighLine]
|
95
|
+
# @param options [Hash]
|
96
|
+
def get_line(question, highline, options={})
|
97
|
+
raw_answer =
|
98
|
+
if question.readline
|
99
|
+
get_line_with_readline(question, highline, options={})
|
100
|
+
else
|
101
|
+
get_line_default(highline)
|
102
|
+
end
|
103
|
+
|
104
|
+
question.format_answer(raw_answer)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Get one line using #readline_read
|
108
|
+
# @param (see #get_line)
|
109
|
+
def get_line_with_readline(question, highline, options={})
|
110
|
+
require "readline" # load only if needed
|
111
|
+
|
112
|
+
question_string = highline.render_statement(question)
|
113
|
+
|
114
|
+
raw_answer = readline_read(question_string, question)
|
115
|
+
|
116
|
+
if !raw_answer and highline.track_eof?
|
117
|
+
raise EOFError, "The input stream is exhausted."
|
118
|
+
end
|
119
|
+
|
120
|
+
raw_answer || ""
|
121
|
+
end
|
122
|
+
|
123
|
+
# Use readline to read one line
|
124
|
+
# @param prompt [String] Readline prompt
|
125
|
+
# @param question [HighLine::Question] question from where to get
|
126
|
+
# autocomplete candidate strings
|
127
|
+
def readline_read(prompt, question)
|
128
|
+
# prep auto-completion
|
129
|
+
unless question.selection.empty?
|
130
|
+
Readline.completion_proc = lambda do |str|
|
131
|
+
question.selection.grep(/\A#{Regexp.escape(str)}/)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# work-around ugly readline() warnings
|
136
|
+
old_verbose = $VERBOSE
|
137
|
+
$VERBOSE = nil
|
138
|
+
|
139
|
+
raw_answer = run_preserving_stty do
|
140
|
+
Readline.readline(prompt, true)
|
141
|
+
end
|
142
|
+
|
143
|
+
$VERBOSE = old_verbose
|
144
|
+
|
145
|
+
raw_answer
|
146
|
+
end
|
147
|
+
|
148
|
+
# Get one line from terminal using default #gets method.
|
149
|
+
# @param highline (see #get_line)
|
150
|
+
def get_line_default(highline)
|
151
|
+
raise EOFError, "The input stream is exhausted." if highline.track_eof? and
|
152
|
+
highline.input.eof?
|
153
|
+
highline.input.gets
|
154
|
+
end
|
155
|
+
|
156
|
+
# @!group Enviroment queries
|
157
|
+
|
158
|
+
# Running on JRuby?
|
66
159
|
def jruby?
|
67
160
|
defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
68
161
|
end
|
69
162
|
|
163
|
+
# Running on Rubinius?
|
70
164
|
def rubinius?
|
71
165
|
defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
|
72
166
|
end
|
73
167
|
|
168
|
+
# Running on Windows?
|
74
169
|
def windows?
|
75
170
|
defined?(RUBY_PLATFORM) && (RUBY_PLATFORM =~ /mswin|mingw|cygwin/)
|
76
171
|
end
|
172
|
+
|
173
|
+
# @!endgroup
|
174
|
+
|
175
|
+
# Returns the class name as String. Useful for debuggin.
|
176
|
+
# @return [String] class name. Ex: "HighLine::Terminal::IOConsole"
|
177
|
+
def character_mode
|
178
|
+
self.class.name
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
# Yield a block using stty shell commands to preserve the terminal state.
|
184
|
+
def run_preserving_stty
|
185
|
+
save_stty
|
186
|
+
yield
|
187
|
+
ensure
|
188
|
+
restore_stty
|
189
|
+
end
|
190
|
+
|
191
|
+
# Saves terminal state using shell stty command.
|
192
|
+
def save_stty
|
193
|
+
@stty_save = `stty -g`.chomp rescue nil
|
194
|
+
end
|
195
|
+
|
196
|
+
# Restores terminal state using shell stty command.
|
197
|
+
def restore_stty
|
198
|
+
system("stty", @stty_save) if @stty_save
|
199
|
+
end
|
77
200
|
end
|
78
201
|
end
|