vedeu 0.2.1 → 0.2.2

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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/README.md +1 -2
  4. data/Rakefile +5 -5
  5. data/bin/vedeu +8 -4
  6. data/docs/api.md +3 -1
  7. data/docs/events.md +43 -29
  8. data/docs/getting_started.md +2 -0
  9. data/examples/cursor_app/cursor_app.rb +85 -0
  10. data/examples/lines_app/lines_app.rb +60 -0
  11. data/lib/vedeu.rb +9 -3
  12. data/lib/vedeu/api/api.rb +28 -7
  13. data/lib/vedeu/api/composition.rb +2 -0
  14. data/lib/vedeu/api/helpers.rb +2 -0
  15. data/lib/vedeu/api/interface.rb +2 -20
  16. data/lib/vedeu/api/keymap.rb +2 -0
  17. data/lib/vedeu/api/line.rb +2 -0
  18. data/lib/vedeu/api/menu.rb +3 -1
  19. data/lib/vedeu/api/stream.rb +25 -2
  20. data/lib/vedeu/application.rb +4 -4
  21. data/lib/vedeu/configuration/api.rb +327 -0
  22. data/lib/vedeu/configuration/cli.rb +110 -0
  23. data/lib/vedeu/{configuration.rb → configuration/configuration.rb} +49 -99
  24. data/lib/vedeu/launcher.rb +0 -1
  25. data/lib/vedeu/models/attributes/colour_translator.rb +2 -2
  26. data/lib/vedeu/models/colour.rb +0 -1
  27. data/lib/vedeu/models/composition.rb +2 -8
  28. data/lib/vedeu/models/cursor.rb +261 -0
  29. data/lib/vedeu/models/geometry.rb +11 -19
  30. data/lib/vedeu/models/interface.rb +2 -12
  31. data/lib/vedeu/models/keymap.rb +2 -0
  32. data/lib/vedeu/models/line.rb +2 -0
  33. data/lib/vedeu/models/stream.rb +2 -0
  34. data/lib/vedeu/models/style.rb +2 -0
  35. data/lib/vedeu/output/clear.rb +0 -1
  36. data/lib/vedeu/output/compositor.rb +0 -1
  37. data/lib/vedeu/output/refresh.rb +3 -0
  38. data/lib/vedeu/output/render.rb +12 -6
  39. data/lib/vedeu/output/view.rb +1 -0
  40. data/lib/vedeu/repositories/cursors.rb +98 -0
  41. data/lib/vedeu/repositories/events.rb +0 -1
  42. data/lib/vedeu/repositories/focus.rb +18 -0
  43. data/lib/vedeu/repositories/menus.rb +4 -4
  44. data/lib/vedeu/support/common.rb +2 -1
  45. data/lib/vedeu/support/event.rb +1 -10
  46. data/lib/vedeu/support/grid.rb +1 -2
  47. data/lib/vedeu/{repositories → support}/keymap_validator.rb +5 -4
  48. data/lib/vedeu/support/log.rb +3 -0
  49. data/lib/vedeu/support/menu.rb +4 -1
  50. data/lib/vedeu/support/registrar.rb +2 -1
  51. data/test/integration/defining_interfaces_test.rb +0 -1
  52. data/test/integration/views/basic_view_test.rb +741 -739
  53. data/test/lib/vedeu/api/api_test.rb +14 -3
  54. data/test/lib/vedeu/api/helpers_test.rb +3 -3
  55. data/test/lib/vedeu/api/interface_test.rb +17 -70
  56. data/test/lib/vedeu/api/keymap_test.rb +2 -0
  57. data/test/lib/vedeu/api/line_test.rb +4 -4
  58. data/test/lib/vedeu/api/menu_test.rb +6 -5
  59. data/test/lib/vedeu/api/stream_test.rb +18 -0
  60. data/test/lib/vedeu/configuration/api_test.rb +248 -0
  61. data/test/lib/vedeu/configuration/cli_test.rb +88 -0
  62. data/test/lib/vedeu/configuration/configuration_test.rb +67 -0
  63. data/test/lib/vedeu/input/input_test.rb +2 -2
  64. data/test/lib/vedeu/models/attributes/background_test.rb +3 -3
  65. data/test/lib/vedeu/models/attributes/foreground_test.rb +3 -3
  66. data/test/lib/vedeu/models/composition_test.rb +0 -222
  67. data/test/lib/vedeu/models/cursor_test.rb +164 -0
  68. data/test/lib/vedeu/models/interface_test.rb +0 -11
  69. data/test/lib/vedeu/output/compositor_test.rb +2 -4
  70. data/test/lib/vedeu/output/render_test.rb +4 -41
  71. data/test/lib/vedeu/repositories/cursors_test.rb +13 -0
  72. data/test/lib/vedeu/repositories/focus_test.rb +14 -4
  73. data/test/lib/vedeu/repositories/menus_test.rb +36 -29
  74. data/test/lib/vedeu/{repositories → support}/keymap_validator_test.rb +0 -0
  75. data/test/lib/vedeu/support/menu_test.rb +3 -3
  76. data/test/lib/vedeu/support/registrar_test.rb +6 -0
  77. data/test/lib/vedeu/support/terminal_test.rb +2 -2
  78. data/test/test_helper.rb +1 -1
  79. data/vedeu.gemspec +1 -1
  80. metadata +23 -14
  81. data/elements.txt +0 -118
  82. data/lib/vedeu/support/cursor.rb +0 -96
  83. data/test/lib/vedeu/configuration_test.rb +0 -154
  84. data/test/lib/vedeu/support/cursor_test.rb +0 -79
  85. data/test/support/model_test_data.json +0 -437
@@ -6,6 +6,17 @@ module Vedeu
6
6
  # module expose Vedeu's core functionality.
7
7
  module API
8
8
 
9
+ # Configure Vedeu using a simple configuration DSL.
10
+ #
11
+ # @api public
12
+ # @see Vedeu::Configuration
13
+ # @return []
14
+ def configure(&block)
15
+ fail InvalidSyntax, '`configure` requires a block.' unless block_given?
16
+
17
+ Vedeu::Configuration.configure(&block)
18
+ end
19
+
9
20
  # Returns information about various registered subsystems when used with
10
21
  # a defined method within {Vedeu::API::Defined}.
11
22
  #
@@ -74,17 +85,22 @@ module Vedeu
74
85
  @events ||= Vedeu::Events.new do
75
86
  event(:_clear_) { Terminal.clear_screen }
76
87
  event(:_exit_) { Vedeu::Application.stop }
77
- event(:_focus_by_name_) { |name| Vedeu::Focus.by_name(name) }
78
- event(:_focus_next_) { Vedeu::Focus.next_item }
79
- event(:_focus_prev_) { Vedeu::Focus.prev_item }
80
88
  event(:_keypress_) { |key| Vedeu.keypress(key) }
81
89
  event(:_log_) { |msg| Vedeu.log(msg) }
82
90
  event(:_mode_switch_) { fail ModeSwitch }
83
- event(:_refresh_) { Vedeu::Refresh.all }
84
91
  event(:_resize_, { delay: 0.25 }) { Vedeu.resize }
85
92
  end
86
93
  end
87
94
 
95
+ # Used after defining an interface or interfaces to set the initially
96
+ # focussed interface.
97
+ #
98
+ # @param name [String] The interface to focus; must be defined.
99
+ # @return []
100
+ def focus(name)
101
+ Vedeu.trigger(:_focus_by_name, name)
102
+ end
103
+
88
104
  # Find out how many lines the current terminal is able to display.
89
105
  #
90
106
  # @api public
@@ -100,11 +116,12 @@ module Vedeu
100
116
  # command. This provides the means for you to define your application's
101
117
  # views without their content.
102
118
  #
119
+ # @todo More documentation required.
103
120
  # @api public
104
121
  # @param name [String] The name of the interface. Used to reference the
105
122
  # interface throughout your application's execution lifetime.
106
123
  # @param block [Proc] A set of attributes which define the features of the
107
- # interface. TODO: More help.
124
+ # interface.
108
125
  #
109
126
  # @example
110
127
  # Vedeu.interface 'my_interface' do
@@ -234,6 +251,8 @@ module Vedeu
234
251
 
235
252
  trigger(:_refresh_)
236
253
 
254
+ trigger(:_cursor_refresh_)
255
+
237
256
  true
238
257
  end
239
258
  # :nocov:
@@ -264,8 +283,9 @@ module Vedeu
264
283
  Vedeu.events.unevent(name)
265
284
  end
266
285
 
267
- # Use attributes of another interface whilst defining one. TODO: More help.
286
+ # Use attributes of another interface whilst defining one.
268
287
  #
288
+ # @todo More documentation required.
269
289
  # @api public
270
290
  # @param name [String] The name of the interface you wish to use. Typically
271
291
  # used when defining interfaces to share geometry.
@@ -281,8 +301,9 @@ module Vedeu
281
301
  Vedeu::Interface.new(Vedeu::Interfaces.find(name))
282
302
  end
283
303
 
284
- # Define a view (content) for an interface. TODO: More help.
304
+ # Define a view (content) for an interface.
285
305
  #
306
+ # @todo More documentation required.
286
307
  # @api public
287
308
  # @param name [String] The name of the interface you are targetting for this
288
309
  # view.
@@ -2,6 +2,8 @@ module Vedeu
2
2
  module API
3
3
 
4
4
  # @see Vedeu::Composition
5
+ #
6
+ # @api public
5
7
  class Composition < Vedeu::Composition
6
8
 
7
9
  # Directly write a view buffer to the terminal.
@@ -3,6 +3,8 @@ module Vedeu
3
3
 
4
4
  # Provides colour and style helpers for use in the {API::Interface},
5
5
  # {API::Line} and {API::Stream} classes.
6
+ #
7
+ # @api public
6
8
  module Helpers
7
9
 
8
10
  # Define either or both foreground and background colours for an
@@ -2,6 +2,8 @@ module Vedeu
2
2
  module API
3
3
 
4
4
  # Provides methods to be used to define interfaces or views.
5
+ #
6
+ # @api public
5
7
  class Interface < Vedeu::Interface
6
8
 
7
9
  include Helpers
@@ -26,26 +28,6 @@ module Vedeu
26
28
  attributes[:geometry][:centred] = value
27
29
  end
28
30
 
29
- # Define the cursor visibility for an interface. A `true` value will show
30
- # the cursor, whilst `false` will hide it.
31
- #
32
- # @api public
33
- # @param value [Boolean]
34
- #
35
- # @example
36
- # interface 'my_interface' do
37
- # cursor true
38
- # ...
39
- #
40
- # @return [API::Interface]
41
- def cursor(value)
42
- unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
43
- fail InvalidSyntax, 'Argument must be `true` or `false` for cursor.'
44
- end
45
-
46
- attributes[:cursor] = value
47
- end
48
-
49
31
  # To maintain performance interfaces can be delayed from refreshing too
50
32
  # often, the reduces artefacts particularly when resizing the terminal
51
33
  # screen.
@@ -2,6 +2,8 @@ module Vedeu
2
2
  module API
3
3
 
4
4
  # Provides methods to be used to define keypress mapped to actions.
5
+ #
6
+ # @api public
5
7
  class Keymap < Vedeu::Keymap
6
8
 
7
9
  # Define keypress(es) to perform an action.
@@ -2,6 +2,8 @@ module Vedeu
2
2
  module API
3
3
 
4
4
  # Provides methods to be used to define views.
5
+ #
6
+ # @api public
5
7
  class Line < Vedeu::Line
6
8
 
7
9
  include Helpers
@@ -3,6 +3,8 @@ module Vedeu
3
3
 
4
4
  # Provides the mechanism to create menus within client applications and use
5
5
  # events to drive them.
6
+ #
7
+ # @api public
6
8
  class Menu
7
9
 
8
10
  include Common
@@ -60,7 +62,7 @@ module Vedeu
60
62
  #
61
63
  # @return [Vedeu::Menu]
62
64
  def items(collection = [])
63
- attributes[:items] = Vedeu::Menu.new(collection)
65
+ attributes[:items] = collection
64
66
  end
65
67
 
66
68
  # The name of the menu. Used to reference the menu throughout your
@@ -2,12 +2,14 @@ module Vedeu
2
2
  module API
3
3
 
4
4
  # Provides methods to be used to define views.
5
+ #
6
+ # @api public
5
7
  class Stream < Vedeu::Stream
6
8
 
7
9
  include Helpers
8
10
 
9
11
  # Specify the alignment of the stream within the line. Useful in
10
- # combination with #width to provide simple formatting effects.
12
+ # combination with {#width} to provide simple formatting effects.
11
13
  #
12
14
  # @api public
13
15
  # @param value [Symbol] `:left`, `:centre` and `right` are valid values
@@ -23,13 +25,34 @@ module Vedeu
23
25
  # @return [Symbol]
24
26
  def align(value)
25
27
  unless [:left, :right, :centre].include?(value.to_sym)
26
- fail InvalidSyntax, '`align` requires a value of `:left`, `:right` ' \
28
+ fail InvalidSyntax, '`align` requires a value of `left`, `right` ' \
27
29
  'or `centre`.'
28
30
  end
29
31
 
30
32
  attributes[:align] = value.to_sym
31
33
  end
32
34
 
35
+ # Syntactic sugar used with {#align} to left align content.
36
+ #
37
+ # @return [Symbol]
38
+ def left
39
+ :left
40
+ end
41
+
42
+ # Syntactic sugar used with {#align} to right align content.
43
+ #
44
+ # @return [Symbol]
45
+ def right
46
+ :right
47
+ end
48
+
49
+ # Syntactic sugar used with {#align} to centre align content.
50
+ #
51
+ # @return [Symbol]
52
+ def centre
53
+ :centre
54
+ end
55
+
33
56
  # Add textual data to the stream via this method.
34
57
  #
35
58
  # @api public
@@ -2,7 +2,7 @@ module Vedeu
2
2
 
3
3
  # Orchestrates the running of the main application loop.
4
4
  #
5
- # @api public
5
+ # @api private
6
6
  class Application
7
7
  # :nocov:
8
8
  class << self
@@ -75,8 +75,8 @@ module Vedeu
75
75
  # For an interactive application we capture input, (usually from the user),
76
76
  # and continue the main loop.
77
77
  #
78
- # TODO: It appears for non-interactive applications, we do nothing. Must
79
- # investigate.
78
+ # @todo It appears for non-interactive applications, we do nothing. Must
79
+ # investigate.
80
80
  #
81
81
  # @api private
82
82
  # @return []
@@ -85,7 +85,7 @@ module Vedeu
85
85
  Input.capture
86
86
 
87
87
  else
88
- # TODO: What should happen here?
88
+
89
89
 
90
90
  end
91
91
  end
@@ -0,0 +1,327 @@
1
+ module Vedeu
2
+
3
+ module Configuration
4
+
5
+ # The Configuration::API class parses client application configuration into
6
+ # options used by Vedeu to affect certain behaviours.
7
+ #
8
+ # @api private
9
+ class API
10
+
11
+ include Vedeu::Common
12
+
13
+ # Configure Vedeu via a simple configuration API DSL. Options set here
14
+ # override the default Vedeu configuration set in
15
+ # {Vedeu::Configuration#defaults}.
16
+ #
17
+ # @example
18
+ # Vedeu.configure do
19
+ # ...
20
+ #
21
+ # @param block [Proc]
22
+ # @return [Hash]
23
+ def self.configure(&block)
24
+ new(&block).configuration
25
+ end
26
+
27
+ # Returns an instance of Configuration::API.
28
+ #
29
+ # @param block [Proc]
30
+ # @return [Configuration::API]
31
+ def initialize(&block)
32
+ instance_eval(&block) if block_given?
33
+ end
34
+
35
+ # Returns the configuration options set up by the API DSL.
36
+ #
37
+ # @return [Hash]
38
+ def configuration
39
+ if system_key_options.any?
40
+ options.merge({ system_keys: system_key_options })
41
+
42
+ else
43
+ options
44
+
45
+ end
46
+ end
47
+
48
+ # Sets boolean to allow user input. The default behaviour of Vedeu is to
49
+ # be interactive.
50
+ #
51
+ # @example
52
+ # Vedeu.configure do
53
+ # interactive!
54
+ # ...
55
+ #
56
+ # Vedeu.configure do
57
+ # interactive false
58
+ # ...
59
+ #
60
+ # @param value [Boolean]
61
+ # @return [Boolean]
62
+ def interactive!(value = true)
63
+ options[:interactive] = value
64
+ end
65
+ alias_method :interactive, :interactive!
66
+
67
+ # Sets boolean to prevent user intervention. This is the same as setting
68
+ # {#interactive!} to false.
69
+ #
70
+ # @example
71
+ # Vedeu.configure do
72
+ # standalone!
73
+ # ...
74
+ #
75
+ # @param value [Boolean]
76
+ # @return [Boolean]
77
+ def standalone!(value = true)
78
+ options[:interactive] = !value
79
+ end
80
+ alias_method :standalone, :standalone!
81
+
82
+ # Sets boolean to run the Vedeu main application loop once. In effect,
83
+ # using `run_once!` or setting `run_once` to true will allow Vedeu to
84
+ # initialize, run any client application code, cleanup, then terminate.
85
+ #
86
+ # @example
87
+ # Vedeu.configure do
88
+ # run_once!
89
+ # ...
90
+ #
91
+ # @param value [Boolean]
92
+ # @return [Boolean]
93
+ def run_once!(value = true)
94
+ options[:once] = value
95
+ end
96
+ alias_method :run_once, :run_once!
97
+
98
+ # Sets the terminal mode to `cooked`. Default terminal mode is `raw`.
99
+ #
100
+ # @example
101
+ # Vedeu.configure do
102
+ # cooked!
103
+ # ...
104
+ #
105
+ # @return [Boolean]
106
+ def cooked!
107
+ options[:terminal_mode] = :cooked
108
+ end
109
+ alias_method :cooked, :cooked!
110
+
111
+ # Sets the terminal mode to `raw`. Default terminal mode is `raw`.
112
+ #
113
+ # @example
114
+ # Vedeu.configure do
115
+ # raw!
116
+ # ...
117
+ #
118
+ # @return [Boolean]
119
+ def raw!
120
+ options[:terminal_mode] = :raw
121
+ end
122
+ alias_method :raw, :raw!
123
+
124
+ # Sets boolean to enable/disable debugging. Vedeu's default setting is
125
+ # for debugging to be disabled. Using `debug!` or setting `debug` to true
126
+ # will enable debugging. If `trace!` is used, or `trace` is set to true,
127
+ # debugging will be enabled, overriding any setting here.
128
+ #
129
+ # @example
130
+ # Vedeu.configure do
131
+ # debug!
132
+ # ...
133
+ #
134
+ # Vedeu.configure do
135
+ # debug false
136
+ # ...
137
+ #
138
+ # @param value [Boolean]
139
+ # @return [Boolean]
140
+ def debug!(value = true)
141
+ if options.key?(:trace) && options[:trace] != false
142
+ options[:debug] = true
143
+
144
+ else
145
+ options[:debug] = value
146
+
147
+ end
148
+ end
149
+ alias_method :debug, :debug!
150
+
151
+ # Sets boolean to enable/disable tracing. Vedeu's default setting is for
152
+ # tracing to be disabled. Using `trace!` or setting `trace` to true will
153
+ # enable tracing and debugging.
154
+ #
155
+ # @example
156
+ # Vedeu.configure do
157
+ # trace!
158
+ # ...
159
+ #
160
+ # Vedeu.configure do
161
+ # trace false
162
+ # ...
163
+ #
164
+ # @param value [Boolean]
165
+ # @return [Boolean]
166
+ def trace!(value = true)
167
+ options[:debug] = true if value === true
168
+
169
+ options[:trace] = value
170
+ end
171
+ alias_method :trace, :trace!
172
+
173
+ # Sets the colour mode of the terminal.
174
+ #
175
+ # @example
176
+ # Vedeu.configure do
177
+ # colour_mode 256
178
+ # ...
179
+ #
180
+ # @param value [Fixnum]
181
+ # @return [Boolean]
182
+ def colour_mode(value = nil)
183
+ fail InvalidSyntax, '`colour_mode` must be `8`, `16`, `256`, ' \
184
+ '`16777216`.' unless valid_colour_mode?(value)
185
+
186
+ options[:colour_mode] = value
187
+ end
188
+
189
+ # Sets the key used to exit the client application. The default is `q`.
190
+ #
191
+ # @example
192
+ # Vedeu.configure do
193
+ # exit_key 'x'
194
+ # ...
195
+ #
196
+ # Vedeu.configure do
197
+ # exit_key :f4
198
+ # ...
199
+ #
200
+ # @param value [String|Symbol]
201
+ # @return [String|Symbol]
202
+ def exit_key(value)
203
+ return invalid_key('exit_key') unless valid_key?(value)
204
+
205
+ system_key_options[:exit] = value
206
+ end
207
+
208
+ # Sets the key used to switch focus to the next defined interface. The
209
+ # default is `:tab`.
210
+ #
211
+ # @example
212
+ # Vedeu.configure do
213
+ # focus_next_key 'n'
214
+ # ...
215
+ #
216
+ # Vedeu.configure do
217
+ # focus_next_key :right
218
+ # ...
219
+ #
220
+ # @param value [String|Symbol]
221
+ # @return [String|Symbol]
222
+ def focus_next_key(value)
223
+ return invalid_key('exit_key') unless valid_key?(value)
224
+
225
+ system_key_options[:focus_next] = value
226
+ end
227
+
228
+ # Sets the key used to switch focus to the previous interface. The default
229
+ # is `:shift_tab`.
230
+ #
231
+ # @example
232
+ # Vedeu.configure do
233
+ # focus_prev_key 'p'
234
+ # ...
235
+ #
236
+ # Vedeu.configure do
237
+ # focus_prev_key :left
238
+ # ...
239
+ #
240
+ # @param value [String|Symbol]
241
+ # @return [String|Symbol]
242
+ def focus_prev_key(value)
243
+ return invalid_key('exit_key') unless valid_key?(value)
244
+
245
+ system_key_options[:focus_prev] = value
246
+ end
247
+
248
+ # Sets the key used to switch between raw and cooked mode in Vedeu. The
249
+ # default is `:escape`.
250
+ #
251
+ # @example
252
+ # Vedeu.configure do
253
+ # mode_switch_key 'm'
254
+ # ...
255
+ #
256
+ # Vedeu.configure do
257
+ # mode_switch_key :f1
258
+ # ...
259
+ #
260
+ # @param value [String|Symbol]
261
+ # @return [String|Symbol]
262
+ def mode_switch_key(value)
263
+ return invalid_key('exit_key') unless valid_key?(value)
264
+
265
+ system_key_options[:mode_switch] = value
266
+ end
267
+
268
+ private
269
+
270
+ # Returns the options set via the configuration API DSL or an empty Hash
271
+ # if none were set.
272
+ #
273
+ # @api private
274
+ # @return [Hash]
275
+ def options
276
+ @_options ||= {}
277
+ end
278
+
279
+ # Returns the system keys set via the configuration API DSL or an empty
280
+ # hash if none were redefined.
281
+ #
282
+ # @api private
283
+ # @return [Hash]
284
+ def system_key_options
285
+ @_system_key_options ||= Configuration.default_system_keys
286
+ end
287
+
288
+ # Checks that the value provided to {#colour_mode} is valid.
289
+ #
290
+ # @api private
291
+ # @param value [Fixnum]
292
+ # @return [Boolean]
293
+ def valid_colour_mode?(value)
294
+ value.is_a?(Fixnum) && [8, 16, 256, 16777216].include?(value)
295
+ end
296
+
297
+ # Checks that the value provided to {#exit_key}, {#focus_next_key},
298
+ # {#focus_prev_key} and {#mode_switch_key} is valid. Must be a Symbol or a
299
+ # non-empty String.
300
+ #
301
+ # @api private
302
+ # @param value [String|Symbol]
303
+ # @return [Boolean]
304
+ def valid_key?(value)
305
+ return false unless value.is_a?(String) || value.is_a?(Symbol)
306
+
307
+ return false if value.is_a?(String) && value.size != 1
308
+
309
+ (value.is_a?(String) || value.is_a?(Symbol)) && defined_value?(value)
310
+ end
311
+
312
+ # Raises an exception on behalf of the calling method to report that the
313
+ # value provided is not valid.
314
+ #
315
+ # @api private
316
+ # @param system_key [String] The calling method wishing to raise an
317
+ # exception.
318
+ # @return [InvalidSyntax]
319
+ def invalid_key(system_key)
320
+ fail InvalidSyntax, "`#{system_key}` must be a String or a Symbol."
321
+ end
322
+
323
+ end
324
+
325
+ end
326
+
327
+ end