cli-ui 1.5.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ # typed: true
2
+
1
3
  module CLI
2
4
  module UI
3
5
  module Frame
@@ -6,8 +8,18 @@ module CLI
6
8
  STYLE_ENVVAR = 'CLI_STYLE_STACK'
7
9
 
8
10
  class StackItem
9
- attr_reader :color, :frame_style
11
+ extend T::Sig
12
+
13
+ sig { returns(CLI::UI::Color) }
14
+ attr_reader :color
15
+
16
+ sig { returns(CLI::UI::Frame::FrameStyle) }
17
+ attr_reader :frame_style
10
18
 
19
+ sig do
20
+ params(color_name: CLI::UI::Colorable, style_name: FrameStylable)
21
+ .void
22
+ end
11
23
  def initialize(color_name, style_name)
12
24
  @color = CLI::UI.resolve_color(color_name)
13
25
  @frame_style = CLI::UI.resolve_style(style_name)
@@ -15,13 +27,16 @@ module CLI
15
27
  end
16
28
 
17
29
  class << self
30
+ extend T::Sig
31
+
18
32
  # Fetch all items off the frame stack
33
+ sig { returns(T::Array[StackItem]) }
19
34
  def items
20
35
  colors = ENV.fetch(COLOR_ENVVAR, '').split(':').map(&:to_sym)
21
36
  styles = ENV.fetch(STYLE_ENVVAR, '').split(':').map(&:to_sym)
22
37
 
23
- colors.length.times.map do |i|
24
- StackItem.new(colors[i], styles[i] || Frame.frame_style)
38
+ colors.each_with_index.map do |color, i|
39
+ StackItem.new(color, styles[i] || Frame.frame_style)
25
40
  end
26
41
  end
27
42
 
@@ -43,18 +58,20 @@ module CLI
43
58
  # If both an item and a color/style pair are given, raises an +ArgumentError+
44
59
  # If the given item is not a +StackItem+, raises an +ArgumentError+
45
60
  #
61
+ sig do
62
+ params(
63
+ item: T.nilable(StackItem),
64
+ color: T.nilable(CLI::UI::Color),
65
+ style: T.nilable(CLI::UI::Frame::FrameStyle),
66
+ )
67
+ .void
68
+ end
46
69
  def push(item = nil, color: nil, style: nil)
47
- unless item.nil?
48
- unless item.is_a?(StackItem)
49
- raise ArgumentError, 'item must be a StackItem'
50
- end
51
-
52
- unless color.nil? && style.nil?
53
- raise ArgumentError, 'Must give one of item or color: and style:'
54
- end
70
+ if color.nil? != style.nil? || item.nil? == color.nil?
71
+ raise ArgumentError, 'Must give one of item or color: and style:'
55
72
  end
56
73
 
57
- item ||= StackItem.new(color, style)
74
+ item ||= StackItem.new(T.must(color), T.must(style))
58
75
 
59
76
  curr = items
60
77
  curr << item
@@ -63,6 +80,7 @@ module CLI
63
80
  end
64
81
 
65
82
  # Removes and returns the last stack item off the stack
83
+ sig { returns(T.nilable(StackItem)) }
66
84
  def pop
67
85
  curr = items
68
86
  ret = curr.pop
@@ -79,13 +97,14 @@ module CLI
79
97
  # This is done to preserve backward compatibility with earlier versions of cli/ui.
80
98
  # This ensures that any code that relied upon previous stack behavior should continue
81
99
  # to work.
100
+ sig { params(items: T::Array[StackItem]).void }
82
101
  def serialize(items)
83
102
  colors = []
84
103
  styles = []
85
104
 
86
105
  items.each do |item|
87
106
  colors << item.color.name
88
- styles << item.frame_style.name
107
+ styles << item.frame_style.style_name
89
108
  end
90
109
 
91
110
  ENV[COLOR_ENVVAR] = colors.join(':')
@@ -1,3 +1,5 @@
1
+ # typed: true
2
+
1
3
  module CLI
2
4
  module UI
3
5
  module Frame
@@ -12,10 +14,14 @@ module CLI
12
14
  BOTTOM_LEFT = '┗'
13
15
 
14
16
  class << self
15
- def name
16
- 'box'
17
+ extend T::Sig
18
+
19
+ sig { override.returns(Symbol) }
20
+ def style_name
21
+ :box
17
22
  end
18
23
 
24
+ sig { override.returns(String) }
19
25
  def prefix
20
26
  VERTICAL
21
27
  end
@@ -34,7 +40,8 @@ module CLI
34
40
  #
35
41
  # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
36
42
  #
37
- def open(text, color:)
43
+ sig { override.params(text: String, color: CLI::UI::Color).returns(String) }
44
+ def start(text, color:)
38
45
  edge(text, color: color, first: TOP_LEFT)
39
46
  end
40
47
 
@@ -52,6 +59,7 @@ module CLI
52
59
  #
53
60
  # ┣━━ Divider ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
54
61
  #
62
+ sig { override.params(text: String, color: CLI::UI::Color).returns(String) }
55
63
  def divider(text, color:)
56
64
  edge(text, color: color, first: DIVIDER)
57
65
  end
@@ -71,12 +79,16 @@ module CLI
71
79
  #
72
80
  # ┗━━ Close ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
73
81
  #
82
+ sig { override.params(text: String, color: CLI::UI::Color, right_text: T.nilable(String)).returns(String) }
74
83
  def close(text, color:, right_text: nil)
75
84
  edge(text, color: color, right_text: right_text, first: BOTTOM_LEFT)
76
85
  end
77
86
 
78
87
  private
79
88
 
89
+ sig do
90
+ params(text: String, color: CLI::UI::Color, first: String, right_text: T.nilable(String)).returns(String)
91
+ end
80
92
  def edge(text, color:, first:, right_text: nil)
81
93
  color = CLI::UI.resolve_color(color)
82
94
 
@@ -84,7 +96,6 @@ module CLI
84
96
 
85
97
  preamble << color.code << first << (HORIZONTAL * 2)
86
98
 
87
- text ||= ''
88
99
  unless text.empty?
89
100
  preamble << ' ' << CLI::UI.resolve_text("{{#{color.name}:#{text}}}") << ' '
90
101
  end
@@ -1,3 +1,5 @@
1
+ # typed: true
2
+
1
3
  module CLI
2
4
  module UI
3
5
  module Frame
@@ -12,10 +14,14 @@ module CLI
12
14
  BOTTOM_LEFT = '┗'
13
15
 
14
16
  class << self
15
- def name
16
- 'bracket'
17
+ extend T::Sig
18
+
19
+ sig { override.returns(Symbol) }
20
+ def style_name
21
+ :bracket
17
22
  end
18
23
 
24
+ sig { override.returns(String) }
19
25
  def prefix
20
26
  VERTICAL
21
27
  end
@@ -34,7 +40,8 @@ module CLI
34
40
  #
35
41
  # ┏━━ Open
36
42
  #
37
- def open(text, color:)
43
+ sig { override.params(text: String, color: CLI::UI::Color).returns(String) }
44
+ def start(text, color:)
38
45
  edge(text, color: color, first: TOP_LEFT)
39
46
  end
40
47
 
@@ -52,6 +59,7 @@ module CLI
52
59
  #
53
60
  # ┣━━ Divider
54
61
  #
62
+ sig { override.params(text: String, color: CLI::UI::Color).returns(String) }
55
63
  def divider(text, color:)
56
64
  edge(text, color: color, first: DIVIDER)
57
65
  end
@@ -71,12 +79,16 @@ module CLI
71
79
  #
72
80
  # ┗━━ Close
73
81
  #
82
+ sig { override.params(text: String, color: CLI::UI::Color, right_text: T.nilable(String)).returns(String) }
74
83
  def close(text, color:, right_text: nil)
75
84
  edge(text, color: color, right_text: right_text, first: BOTTOM_LEFT)
76
85
  end
77
86
 
78
87
  private
79
88
 
89
+ sig do
90
+ params(text: String, color: CLI::UI::Color, first: String, right_text: T.nilable(String)).returns(String)
91
+ end
80
92
  def edge(text, color:, first:, right_text: nil)
81
93
  color = CLI::UI.resolve_color(color)
82
94
 
@@ -84,7 +96,6 @@ module CLI
84
96
 
85
97
  preamble << color.code << first << (HORIZONTAL * 2)
86
98
 
87
- text ||= ''
88
99
  unless text.empty?
89
100
  preamble << ' ' << CLI::UI.resolve_text("{{#{color.name}:#{text}}}") << ' '
90
101
  end
@@ -117,16 +128,16 @@ module CLI
117
128
  # the final space, since we're going to write over it.
118
129
  preamble_start -= 1 unless preamble_start.zero?
119
130
 
120
- # Prefix_width includes the width of the terminal space, which we
121
- # want to remove. The clamping is done to avoid a negative
122
- # preamble start which can occur for the first frame.
131
+ # Jumping around the line can cause some unwanted flashes
123
132
  o << CLI::UI::ANSI.hide_cursor
124
133
 
125
134
  # reset to column 1 so that things like ^C don't ruin formatting
126
135
  o << "\r"
136
+
127
137
  o << color.code
128
138
  o << print_at_x(preamble_start, preamble + color.code + suffix)
129
139
  o << CLI::UI::Color::RESET.code
140
+ o << CLI::UI::ANSI.show_cursor
130
141
  o << "\n"
131
142
 
132
143
  o
@@ -1,120 +1,117 @@
1
+ # typed: true
2
+
1
3
  require 'cli/ui/frame'
2
4
 
3
5
  module CLI
4
6
  module UI
5
7
  module Frame
6
8
  module FrameStyle
7
- class << self
8
- # rubocop:disable Style/ClassVars
9
- @@loaded_styles = []
9
+ include Kernel
10
+ extend T::Sig
11
+ extend T::Helpers
12
+ abstract!
10
13
 
11
- def loaded_styles
12
- @@loaded_styles.map(&:name)
13
- end
14
+ autoload(:Box, 'cli/ui/frame/frame_style/box')
15
+ autoload(:Bracket, 'cli/ui/frame/frame_style/bracket')
16
+
17
+ MAP = {
18
+ box: -> { FrameStyle::Box },
19
+ bracket: -> { FrameStyle::Bracket },
20
+ }
21
+
22
+ class << self
23
+ extend T::Sig
14
24
 
15
25
  # Lookup a frame style via its name
16
26
  #
17
27
  # ==== Attributes
18
28
  #
19
29
  # * +symbol+ - frame style name to lookup
30
+ sig { params(name: T.any(String, Symbol)).returns(FrameStyle) }
20
31
  def lookup(name)
21
- @@loaded_styles
22
- .find { |style| style.name.to_sym == name }
23
- .tap { |style| raise InvalidFrameStyleName, name if style.nil? }
32
+ MAP.fetch(name.to_sym).call
33
+ rescue KeyError
34
+ raise(InvalidFrameStyleName, name)
24
35
  end
25
-
26
- def extended(base)
27
- @@loaded_styles << base
28
- base.extend(Interface)
29
- end
30
- # rubocop:enable Style/ClassVars
31
36
  end
32
37
 
33
- class InvalidFrameStyleName < ArgumentError
34
- def initialize(name)
35
- super
36
- @name = name
37
- end
38
+ sig { abstract.returns(Symbol) }
39
+ def style_name; end
38
40
 
39
- def message
40
- keys = FrameStyle.loaded_styles.map(&:inspect).join(',')
41
- "invalid frame style: #{@name.inspect}" \
42
- ' -- must be one of CLI::UI::Frame::FrameStyle.loaded_styles ' \
43
- "(#{keys})"
44
- end
41
+ # Returns the character(s) that should be printed at the beginning
42
+ # of lines inside this frame
43
+ sig { abstract.returns(String) }
44
+ def prefix; end
45
+
46
+ # Returns the printing width of the prefix
47
+ sig { returns(Integer) }
48
+ def prefix_width
49
+ CLI::UI::ANSI.printing_width(prefix)
45
50
  end
46
51
 
47
- # Public interface for FrameStyles
48
- # Applied by extending FrameStyle
49
- module Interface
50
- def name
51
- raise NotImplementedError
52
- end
52
+ # Draws the "Open" line for this frame style
53
+ #
54
+ # ==== Attributes
55
+ #
56
+ # * +text+ - (required) the text/title to output in the frame
57
+ #
58
+ # ==== Options
59
+ #
60
+ # * +:color+ - (required) The color of the frame.
61
+ #
62
+ sig { abstract.params(text: String, color: CLI::UI::Color).returns(String) }
63
+ def start(text, color:); end
53
64
 
54
- # Returns the character(s) that should be printed at the beginning
55
- # of lines inside this frame
56
- def prefix
57
- raise NotImplementedError
58
- end
65
+ # Draws the "Close" line for this frame style
66
+ #
67
+ # ==== Attributes
68
+ #
69
+ # * +text+ - (required) the text/title to output in the frame
70
+ #
71
+ # ==== Options
72
+ #
73
+ # * +:color+ - (required) The color of the frame.
74
+ # * +:right_text+ - Text to print at the right of the line. Defaults to nil
75
+ #
76
+ sig { abstract.params(text: String, color: CLI::UI::Color, right_text: T.nilable(String)).returns(String) }
77
+ def close(text, color:, right_text: nil); end
59
78
 
60
- # Returns the printing width of the prefix
61
- def prefix_width
62
- CLI::UI::ANSI.printing_width(prefix)
63
- end
79
+ # Draws a "divider" line for the current frame style
80
+ #
81
+ # ==== Attributes
82
+ #
83
+ # * +text+ - (required) the text/title to output in the frame
84
+ #
85
+ # ==== Options
86
+ #
87
+ # * +:color+ - (required) The color of the frame.
88
+ #
89
+ sig { abstract.params(text: String, color: CLI::UI::Color).returns(String) }
90
+ def divider(text, color:); end
64
91
 
65
- # Draws the "Open" line for this frame style
66
- #
67
- # ==== Attributes
68
- #
69
- # * +text+ - (required) the text/title to output in the frame
70
- #
71
- # ==== Options
72
- #
73
- # * +:color+ - (required) The color of the frame.
74
- #
75
- def open(text, color:)
76
- raise NotImplementedError
77
- end
92
+ sig { params(x: Integer, str: String).returns(String) }
93
+ def print_at_x(x, str)
94
+ CLI::UI::ANSI.cursor_horizontal_absolute(1 + x) + str
95
+ end
78
96
 
79
- # Draws the "Close" line for this frame style
80
- #
81
- # ==== Attributes
82
- #
83
- # * +text+ - (required) the text/title to output in the frame
84
- #
85
- # ==== Options
86
- #
87
- # * +:color+ - (required) The color of the frame.
88
- # * +:right_text+ - Text to print at the right of the line. Defaults to nil
89
- #
90
- def close(text, color:, right_text: nil)
91
- raise NotImplementedError
92
- end
97
+ class InvalidFrameStyleName < ArgumentError
98
+ extend T::Sig
93
99
 
94
- # Draws a "divider" line for the current frame style
95
- #
96
- # ==== Attributes
97
- #
98
- # * +text+ - (required) the text/title to output in the frame
99
- #
100
- # ==== Options
101
- #
102
- # * +:color+ - (required) The color of the frame.
103
- #
104
- def divider(text, color: nil)
105
- raise NotImplementedError
100
+ sig { params(name: T.any(String, Symbol)).void }
101
+ def initialize(name)
102
+ super
103
+ @name = name
106
104
  end
107
105
 
108
- private
109
-
110
- def print_at_x(x, str)
111
- CLI::UI::ANSI.cursor_horizontal_absolute(1 + x) + str
106
+ sig { returns(String) }
107
+ def message
108
+ keys = FrameStyle::MAP.keys.map(&:inspect).join(', ')
109
+ "invalid frame style: #{@name.inspect}" \
110
+ ' -- must be one of CLI::UI::Frame::FrameStyle::MAP ' \
111
+ "(#{keys})"
112
112
  end
113
113
  end
114
114
  end
115
115
  end
116
116
  end
117
117
  end
118
-
119
- require 'cli/ui/frame/frame_style/box'
120
- require 'cli/ui/frame/frame_style/bracket'
data/lib/cli/ui/frame.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  # coding: utf-8
2
+
3
+ # typed: true
4
+
2
5
  require 'cli/ui'
3
6
  require 'cli/ui/frame/frame_stack'
4
7
  require 'cli/ui/frame/frame_style'
@@ -7,9 +10,12 @@ module CLI
7
10
  module UI
8
11
  module Frame
9
12
  class UnnestedFrameException < StandardError; end
13
+ DEFAULT_FRAME_COLOR = CLI::UI.resolve_color(:cyan)
14
+
10
15
  class << self
11
- DEFAULT_FRAME_COLOR = CLI::UI.resolve_color(:cyan)
16
+ extend T::Sig
12
17
 
18
+ sig { returns(FrameStyle) }
13
19
  def frame_style
14
20
  @frame_style ||= FrameStyle::Box
15
21
  end
@@ -22,6 +28,7 @@ module CLI
22
28
  #
23
29
  # * +symbol+ or +FrameStyle+ - the default frame style to use for frames
24
30
  #
31
+ sig { params(frame_style: FrameStylable).void }
25
32
  def frame_style=(frame_style)
26
33
  @frame_style = CLI::UI.resolve_style(frame_style)
27
34
  end
@@ -67,13 +74,25 @@ module CLI
67
74
  # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
68
75
  #
69
76
  #
77
+ sig do
78
+ type_parameters(:T).params(
79
+ text: String,
80
+ color: Colorable,
81
+ failure_text: T.nilable(String),
82
+ success_text: T.nilable(String),
83
+ timing: T.any(T::Boolean, Numeric),
84
+ frame_style: FrameStylable,
85
+ block: T.nilable(T.proc.returns(T.type_parameter(:T))),
86
+ ).returns(T.nilable(T.type_parameter(:T)))
87
+ end
70
88
  def open(
71
89
  text,
72
90
  color: DEFAULT_FRAME_COLOR,
73
91
  failure_text: nil,
74
92
  success_text: nil,
75
- timing: nil,
76
- frame_style: self.frame_style
93
+ timing: block_given?,
94
+ frame_style: self.frame_style,
95
+ &block
77
96
  )
78
97
  frame_style = CLI::UI.resolve_style(frame_style)
79
98
  color = CLI::UI.resolve_color(color)
@@ -91,7 +110,7 @@ module CLI
91
110
  t_start = Time.now
92
111
  CLI::UI.raw do
93
112
  print(prefix.chop)
94
- puts frame_style.open(text, color: color)
113
+ puts frame_style.start(text, color: color)
95
114
  end
96
115
  FrameStack.push(color: color, style: frame_style)
97
116
 
@@ -103,15 +122,15 @@ module CLI
103
122
  success = yield
104
123
  rescue
105
124
  closed = true
106
- t_diff = elasped(t_start, timing)
125
+ t_diff = elapsed(t_start, timing)
107
126
  close(failure_text, color: :red, elapsed: t_diff)
108
127
  raise
109
128
  else
110
129
  success
111
130
  ensure
112
131
  unless closed
113
- t_diff = elasped(t_start, timing)
114
- if success != false
132
+ t_diff = elapsed(t_start, timing)
133
+ if T.unsafe(success) != false
115
134
  close(success_text, color: color, elapsed: t_diff)
116
135
  else
117
136
  close(failure_text, color: :red, elapsed: t_diff)
@@ -145,16 +164,17 @@ module CLI
145
164
  #
146
165
  # MUST be inside an open frame or it raises a +UnnestedFrameException+
147
166
  #
167
+ sig { params(text: T.nilable(String), color: T.nilable(Colorable), frame_style: T.nilable(FrameStylable)).void }
148
168
  def divider(text, color: nil, frame_style: nil)
149
169
  fs_item = FrameStack.pop
150
170
  raise UnnestedFrameException, 'No frame nesting to unnest' unless fs_item
151
171
 
152
- color = CLI::UI.resolve_color(color) || fs_item.color
153
- frame_style = CLI::UI.resolve_style(frame_style) || fs_item.frame_style
172
+ divider_color = CLI::UI.resolve_color(color || fs_item.color)
173
+ frame_style = CLI::UI.resolve_style(frame_style || fs_item.frame_style)
154
174
 
155
175
  CLI::UI.raw do
156
176
  print(prefix.chop)
157
- puts frame_style.divider(text, color: color)
177
+ puts frame_style.divider(text.to_s, color: divider_color)
158
178
  end
159
179
 
160
180
  FrameStack.push(fs_item)
@@ -184,21 +204,25 @@ module CLI
184
204
  #
185
205
  # MUST be inside an open frame or it raises a +UnnestedFrameException+
186
206
  #
207
+ sig do
208
+ params(
209
+ text: T.nilable(String),
210
+ color: T.nilable(Colorable),
211
+ elapsed: T.nilable(Numeric),
212
+ frame_style: T.nilable(FrameStylable),
213
+ ).void
214
+ end
187
215
  def close(text, color: nil, elapsed: nil, frame_style: nil)
188
216
  fs_item = FrameStack.pop
189
217
  raise UnnestedFrameException, 'No frame nesting to unnest' unless fs_item
190
218
 
191
- color = CLI::UI.resolve_color(color) || fs_item.color
192
- frame_style = CLI::UI.resolve_style(frame_style) || fs_item.frame_style
193
-
194
- kwargs = {}
195
- if elapsed
196
- kwargs[:right_text] = "(#{elapsed.round(2)}s)"
197
- end
219
+ close_color = CLI::UI.resolve_color(color || fs_item.color)
220
+ frame_style = CLI::UI.resolve_style(frame_style || fs_item.frame_style)
221
+ elapsed_string = elapsed ? "(#{elapsed.round(2)}s)" : nil
198
222
 
199
223
  CLI::UI.raw do
200
224
  print(prefix.chop)
201
- puts frame_style.close(text, color: color, **kwargs)
225
+ puts frame_style.close(text.to_s, color: close_color, right_text: elapsed_string)
202
226
  end
203
227
  end
204
228
 
@@ -208,11 +232,12 @@ module CLI
208
232
  #
209
233
  # * +:color+ - The color of the prefix. Defaults to +Thread.current[:cliui_frame_color_override]+
210
234
  #
235
+ sig { params(color: T.nilable(Colorable)).returns(String) }
211
236
  def prefix(color: Thread.current[:cliui_frame_color_override])
212
237
  +''.tap do |output|
213
238
  items = FrameStack.items
214
239
 
215
- items[0..-2].each do |item|
240
+ items[0..-2].to_a.each do |item|
216
241
  output << item.color.code << item.frame_style.prefix
217
242
  end
218
243
 
@@ -227,6 +252,7 @@ module CLI
227
252
  end
228
253
 
229
254
  # The width of a prefix given the number of Frames in the stack
255
+ sig { returns(Integer) }
230
256
  def prefix_width
231
257
  w = FrameStack.items.reduce(0) do |width, item|
232
258
  width + item.frame_style.prefix_width
@@ -241,7 +267,12 @@ module CLI
241
267
  #
242
268
  # * +color+ - The color to override to
243
269
  #
244
- def with_frame_color_override(color)
270
+ sig do
271
+ type_parameters(:T)
272
+ .params(color: Colorable, block: T.proc.returns(T.type_parameter(:T)))
273
+ .returns(T.type_parameter(:T))
274
+ end
275
+ def with_frame_color_override(color, &block)
245
276
  prev = Thread.current[:cliui_frame_color_override]
246
277
  Thread.current[:cliui_frame_color_override] = color
247
278
  yield
@@ -254,13 +285,13 @@ module CLI
254
285
  # If timing is:
255
286
  # Numeric: return it
256
287
  # false: return nil
257
- # true or nil: defaults to Time.new
258
- # Time: return the difference with start
259
- def elasped(start, timing)
288
+ # true: defaults to Time.new
289
+ sig { params(start: Time, timing: T.any(Numeric, T::Boolean)).returns(T.nilable(Numeric)) }
290
+ def elapsed(start, timing)
260
291
  return timing if timing.is_a?(Numeric)
261
292
  return if timing.is_a?(FalseClass)
262
293
 
263
- timing = Time.new if timing.is_a?(TrueClass) || timing.nil?
294
+ timing = Time.new
264
295
  timing - start
265
296
  end
266
297
  end