guard 2.8.2 → 2.9.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.
- checksums.yaml +4 -4
- data/README.md +0 -7
- data/lib/guard.rb +220 -152
- data/lib/guard.rb.orig +213 -155
- data/lib/guard/aruba_adapter.rb +2 -2
- data/lib/guard/cli.rb +8 -13
- data/lib/guard/cli.rb.orig +12 -10
- data/lib/guard/commander.rb +15 -7
- data/lib/guard/commands/all.rb +3 -0
- data/lib/guard/commands/change.rb +3 -0
- data/lib/guard/commands/pause.rb +2 -0
- data/lib/guard/commands/reload.rb +4 -0
- data/lib/guard/commands/scope.rb +3 -0
- data/lib/guard/config.rb +24 -0
- data/lib/guard/deprecated/dsl.rb +45 -0
- data/lib/guard/deprecated/guard.rb +166 -0
- data/lib/guard/deprecated/guardfile.rb +84 -0
- data/lib/guard/dsl.rb +24 -13
- data/lib/guard/dsl.rb.orig +378 -0
- data/lib/guard/dsl_describer.rb +8 -2
- data/lib/guard/dsl_describer.rb.orig +11 -3
- data/lib/guard/guardfile.rb +32 -44
- data/lib/guard/guardfile/evaluator.rb +13 -6
- data/lib/guard/guardfile/generator.rb +4 -3
- data/lib/guard/interactor.rb +7 -3
- data/lib/guard/internals/debugging.rb +1 -0
- data/lib/guard/internals/environment.rb +93 -0
- data/lib/guard/internals/helpers.rb +13 -0
- data/lib/guard/internals/traps.rb +10 -0
- data/lib/guard/jobs/pry_wrapper.rb +4 -3
- data/lib/guard/jobs/sleep.rb +2 -0
- data/lib/guard/metadata.rb +190 -0
- data/lib/guard/notifier.rb +124 -99
- data/lib/guard/notifier.rb.orig +124 -99
- data/lib/guard/notifier/detected.rb +83 -0
- data/lib/guard/notifiers/emacs.rb +2 -1
- data/lib/guard/notifiers/tmux.rb +173 -177
- data/lib/guard/plugin/base.rb +2 -0
- data/lib/guard/plugin_util.rb +26 -32
- data/lib/guard/reevaluator.rb +3 -3
- data/lib/guard/reevaluator.rb.orig +22 -0
- data/lib/guard/runner.rb +1 -0
- data/lib/guard/session.rb +5 -0
- data/lib/guard/sheller.rb +2 -2
- data/lib/guard/templates/Guardfile +4 -0
- data/lib/guard/templates/Guardfile.orig +2 -0
- data/lib/guard/terminal.rb +1 -0
- data/lib/guard/ui.rb +4 -1
- data/lib/guard/version.rb +1 -1
- data/lib/guard/version.rb.orig +1 -1
- data/lib/guard/watcher.rb +3 -1
- data/lib/guard/watcher.rb.orig +122 -0
- data/man/guard.1 +1 -4
- data/man/guard.1.html +1 -4
- metadata +17 -25
- data/lib/guard/commander.rb.orig +0 -103
- data/lib/guard/commands/all.rb.orig +0 -36
- data/lib/guard/commands/reload.rb.orig +0 -34
- data/lib/guard/commands/scope.rb.orig +0 -36
- data/lib/guard/deprecated_methods.rb +0 -72
- data/lib/guard/deprecated_methods.rb.orig +0 -71
- data/lib/guard/deprecator.rb +0 -133
- data/lib/guard/deprecator.rb.orig +0 -206
- data/lib/guard/guard.rb +0 -100
- data/lib/guard/guard.rb.orig +0 -42
- data/lib/guard/guardfile.rb.orig +0 -43
- data/lib/guard/guardfile/evaluator.rb.orig +0 -275
- data/lib/guard/internals/debugging.rb.orig +0 -0
- data/lib/guard/internals/environment.rb.orig +0 -0
- data/lib/guard/internals/tracing.rb.orig +0 -0
- data/lib/guard/notifiers/base.rb.orig +0 -221
- data/lib/guard/notifiers/tmux.rb.orig +0 -339
- data/lib/guard/plugin_util.rb.orig +0 -186
- data/lib/guard/runner.rb.orig +0 -210
- data/lib/guard/setuper.rb +0 -359
- data/lib/guard/setuper.rb.orig +0 -395
- data/lib/guard/ui.rb.orig +0 -278
data/lib/guard.rb.orig
CHANGED
@@ -1,213 +1,271 @@
|
|
1
|
-
require "
|
1
|
+
require "thread"
|
2
|
+
require "listen"
|
3
|
+
|
4
|
+
require "guard/config"
|
5
|
+
require "guard/deprecated/guard" unless Guard::Config.new.strict?
|
6
|
+
|
7
|
+
require "guard/internals/debugging"
|
8
|
+
require "guard/internals/traps"
|
9
|
+
require "guard/internals/helpers"
|
10
|
+
|
11
|
+
require "guard/metadata"
|
12
|
+
require "guard/options"
|
2
13
|
|
3
14
|
require "guard/commander"
|
4
|
-
require "guard/deprecated_methods"
|
5
|
-
require "guard/deprecator"
|
6
15
|
require "guard/dsl"
|
7
|
-
require "guard/group"
|
8
|
-
require "guard/guardfile"
|
9
16
|
require "guard/interactor"
|
10
17
|
require "guard/notifier"
|
11
18
|
require "guard/plugin_util"
|
12
19
|
require "guard/runner"
|
13
|
-
require "guard/setuper"
|
14
20
|
require "guard/sheller"
|
15
21
|
require "guard/ui"
|
16
22
|
require "guard/watcher"
|
17
|
-
require "guard/reevaluator"
|
18
23
|
|
19
24
|
# Guard is the main module for all Guard related modules and classes.
|
20
25
|
# Also Guard plugins should use this namespace.
|
21
26
|
#
|
22
27
|
module Guard
|
23
|
-
|
24
|
-
|
25
|
-
extend Commander
|
26
|
-
extend DeprecatedMethods
|
27
|
-
extend Setuper
|
28
|
+
Deprecated::Guard.add_deprecated(self) unless Config.new.strict?
|
28
29
|
|
29
30
|
class << self
|
30
|
-
|
31
|
+
attr_reader :listener
|
31
32
|
|
32
|
-
|
33
|
-
@scope = new_scope
|
34
|
-
@scope.dup.freeze
|
35
|
-
end
|
33
|
+
include Internals::Helpers
|
36
34
|
|
37
|
-
|
38
|
-
<<<<<<< HEAD
|
39
|
-
fail "::Guard.setup() not called" if @scope.nil?
|
40
|
-
=======
|
41
|
-
>>>>>>> origin/api_safe_refactoring
|
42
|
-
@scope.dup.freeze
|
43
|
-
end
|
44
|
-
attr_reader :runner, :listener
|
45
|
-
|
46
|
-
# Smart accessor for retrieving specific plugins at once.
|
47
|
-
#
|
48
|
-
# @see Guard.plugin
|
49
|
-
# @see Guard.group
|
50
|
-
# @see Guard.groups
|
51
|
-
#
|
52
|
-
# @example Filter plugins by String or Symbol
|
53
|
-
# Guard.plugins('rspec')
|
54
|
-
# Guard.plugins(:rspec)
|
35
|
+
# Initializes the Guard singleton:
|
55
36
|
#
|
56
|
-
#
|
57
|
-
#
|
37
|
+
# * Initialize the internal Guard state;
|
38
|
+
# * Create the interactor
|
39
|
+
# * Select and initialize the file change listener.
|
58
40
|
#
|
59
|
-
# @
|
60
|
-
#
|
41
|
+
# @option options [Boolean] clear if auto clear the UI should be done
|
42
|
+
# @option options [Boolean] notify if system notifications should be shown
|
43
|
+
# @option options [Boolean] debug if debug output should be shown
|
44
|
+
# @option options [Array<String>] group the list of groups to start
|
45
|
+
# @option options [Array<String>] watchdir the directories to watch
|
46
|
+
# @option options [String] guardfile the path to the Guardfile
|
61
47
|
#
|
62
|
-
# @
|
63
|
-
# plugins
|
64
|
-
# @return [Plugin, Array<Plugin>] the filtered plugin(s)
|
48
|
+
# @return [Guard] the Guard singleton
|
65
49
|
#
|
66
|
-
def plugins(filter = nil)
|
67
|
-
@plugins ||= []
|
68
50
|
|
69
|
-
|
51
|
+
# TODO: this method has too many instance variables
|
52
|
+
# and some are mock and leak between tests,
|
53
|
+
# so ideally there should be a guard "instance"
|
54
|
+
# object that can be created anew between tests
|
55
|
+
def setup(opts = {})
|
56
|
+
# NOTE: must be set before anything calls Guard.options
|
57
|
+
reset_options(opts)
|
70
58
|
|
71
|
-
|
72
|
-
|
73
|
-
@plugins.select do |plugin|
|
74
|
-
plugin.name == filter.to_s.downcase.gsub("-", "")
|
75
|
-
end
|
76
|
-
when Regexp
|
77
|
-
@plugins.select do |plugin|
|
78
|
-
plugin.name =~ filter
|
79
|
-
end
|
80
|
-
when Hash
|
81
|
-
@plugins.select do |plugin|
|
82
|
-
filter.all? do |k, v|
|
83
|
-
case k
|
84
|
-
when :name
|
85
|
-
plugin.name == v.to_s.downcase.gsub("-", "")
|
86
|
-
when :group
|
87
|
-
plugin.group.name == v.to_sym
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
59
|
+
# NOTE: must be set before anything calls Guard::UI.debug
|
60
|
+
::Guard::Internals::Debugging.start if options[:debug]
|
92
61
|
|
93
|
-
|
62
|
+
@queue = Queue.new
|
63
|
+
self.watchdirs = Array(options[:watchdir])
|
64
|
+
|
65
|
+
::Guard::UI.reset_and_clear
|
66
|
+
|
67
|
+
@listener = _setup_listener
|
68
|
+
|
69
|
+
_reset_all
|
70
|
+
evaluate_guardfile
|
71
|
+
setup_scope
|
72
|
+
|
73
|
+
::Guard::Notifier.connect(notify: options[:notify])
|
74
|
+
|
75
|
+
traps = Internals::Traps
|
76
|
+
traps.handle("USR1") { async_queue_add([:guard_pause, :paused]) }
|
77
|
+
traps.handle("USR2") { async_queue_add([:guard_pause, :unpaused]) }
|
78
|
+
|
79
|
+
@interactor = ::Guard::Interactor.new(options[:no_interactions])
|
80
|
+
traps.handle("INT") { @interactor.handle_interrupt }
|
81
|
+
|
82
|
+
self
|
94
83
|
end
|
95
84
|
|
96
|
-
|
85
|
+
attr_reader :interactor
|
86
|
+
|
87
|
+
# Used only by tests (for all I know...)
|
88
|
+
def clear_options
|
89
|
+
@options = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
# Initializes the groups array with the default group(s).
|
97
93
|
#
|
98
|
-
# @see
|
99
|
-
# @see Guard.group
|
100
|
-
# @see Guard.groups
|
94
|
+
# @see DEFAULT_GROUPS
|
101
95
|
#
|
102
|
-
|
103
|
-
|
104
|
-
|
96
|
+
def reset_groups
|
97
|
+
@groups = DEFAULT_GROUPS.map { |name| Group.new(name) }
|
98
|
+
end
|
99
|
+
|
100
|
+
# Initializes the plugins array to an empty array.
|
105
101
|
#
|
106
|
-
# @
|
107
|
-
# Guard.plugin(/rsp.+/)
|
102
|
+
# @see Guard.plugins
|
108
103
|
#
|
109
|
-
|
110
|
-
|
104
|
+
def reset_plugins
|
105
|
+
@plugins = []
|
106
|
+
end
|
107
|
+
|
108
|
+
attr_reader :watchdirs
|
109
|
+
|
110
|
+
# Stores the scopes defined by the user via the `--group` / `-g` option (to
|
111
|
+
# run only a specific group) or the `--plugin` / `-P` option (to run only a
|
112
|
+
# specific plugin).
|
111
113
|
#
|
112
|
-
# @
|
113
|
-
#
|
114
|
-
# @return [Plugin, nil] the plugin found, nil otherwise
|
114
|
+
# @see CLI#start
|
115
|
+
# @see Dsl#scope
|
115
116
|
#
|
116
|
-
def
|
117
|
-
|
117
|
+
def setup_scope(scope = {})
|
118
|
+
# TODO: there should be a special Scope class instead
|
119
|
+
scope = _prepare_scope(scope)
|
120
|
+
|
121
|
+
::Guard.scope = {
|
122
|
+
groups: scope[:groups].map { |item| ::Guard.add_group(item) },
|
123
|
+
plugins: scope[:plugins].map { |item| ::Guard.plugin(item) },
|
124
|
+
}
|
118
125
|
end
|
119
126
|
|
120
|
-
#
|
127
|
+
# Evaluates the Guardfile content. It displays an error message if no
|
128
|
+
# Guard plugins are instantiated after the Guardfile evaluation.
|
121
129
|
#
|
122
|
-
# @see Guard
|
123
|
-
#
|
124
|
-
|
130
|
+
# @see Guard::Guardfile::Evaluator#evaluate_guardfile
|
131
|
+
#
|
132
|
+
def evaluate_guardfile
|
133
|
+
evaluator = Guard::Guardfile::Evaluator.new(options)
|
134
|
+
evaluator.evaluate_guardfile
|
135
|
+
msg = "No plugins found in Guardfile, please add at least one."
|
136
|
+
::Guard::UI.error msg if _pluginless_guardfile?
|
137
|
+
end
|
138
|
+
|
139
|
+
# Asynchronously trigger changes
|
125
140
|
#
|
126
|
-
#
|
127
|
-
# Guard.groups('backend')
|
128
|
-
# Guard.groups(:backend)
|
141
|
+
# Currently supported args:
|
129
142
|
#
|
130
|
-
#
|
131
|
-
# Guard.groups(/(back|front)end/)
|
143
|
+
# old style hash: {modified: ['foo'], added: ['bar'], removed: []}
|
132
144
|
#
|
133
|
-
#
|
134
|
-
# @return [Array<Group>] the filtered group(s)
|
145
|
+
# new style signals with args: [:guard_pause, :unpaused ]
|
135
146
|
#
|
136
|
-
def
|
137
|
-
@
|
147
|
+
def async_queue_add(changes)
|
148
|
+
@queue << changes
|
138
149
|
|
139
|
-
|
150
|
+
# Putting interactor in background puts guard into foreground
|
151
|
+
# so it can handle change notifications
|
152
|
+
Thread.new { interactor.background }
|
153
|
+
end
|
140
154
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
155
|
+
def pending_changes?
|
156
|
+
! @queue.empty?
|
157
|
+
end
|
158
|
+
|
159
|
+
def watchdirs=(dirs)
|
160
|
+
dirs = [Dir.pwd] if dirs.empty?
|
161
|
+
@watchdirs = dirs.map { |dir| File.expand_path dir }
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
# Initializes the listener and registers a callback for changes.
|
167
|
+
#
|
168
|
+
def _setup_listener
|
169
|
+
if options[:listen_on]
|
170
|
+
Listen.on(options[:listen_on], &_listener_callback)
|
146
171
|
else
|
147
|
-
|
172
|
+
listener_options = {}
|
173
|
+
[:latency, :force_polling, :wait_for_delay].each do |option|
|
174
|
+
listener_options[option] = options[option] if options[option]
|
175
|
+
end
|
176
|
+
listen_args = watchdirs + [listener_options]
|
177
|
+
Listen.to(*listen_args, &_listener_callback)
|
148
178
|
end
|
149
179
|
end
|
150
180
|
|
151
|
-
#
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
#
|
167
|
-
def group(filter)
|
168
|
-
groups(filter).first
|
181
|
+
# Process the change queue, running tasks within the main Guard thread
|
182
|
+
def _process_queue
|
183
|
+
actions, changes = [], { modified: [], added: [], removed: [] }
|
184
|
+
|
185
|
+
while pending_changes?
|
186
|
+
if (item = @queue.pop).first.is_a?(Symbol)
|
187
|
+
actions << item
|
188
|
+
else
|
189
|
+
item.each { |key, value| changes[key] += value }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
_run_actions(actions)
|
194
|
+
return if changes.values.all?(&:empty?)
|
195
|
+
Runner.new.run_on_changes(*changes.values)
|
169
196
|
end
|
170
197
|
|
171
|
-
#
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
# @option options [Array<Watcher>] watchers the list of declared watchers
|
177
|
-
# @option options [Array<Hash>] callbacks the list of callbacks
|
178
|
-
# @return [Plugin] the added Guard plugin
|
179
|
-
# @see Plugin
|
180
|
-
#
|
181
|
-
def add_plugin(name, options = {})
|
182
|
-
instance = ::Guard::PluginUtil.new(name).initialize_plugin(options)
|
183
|
-
@plugins << instance
|
184
|
-
instance
|
198
|
+
# TODO: Guard::Watch or Guard::Scope should provide this
|
199
|
+
def _scoped_watchers
|
200
|
+
watchers = []
|
201
|
+
Runner.new.send(:_scoped_plugins) { |guard| watchers += guard.watchers }
|
202
|
+
watchers
|
185
203
|
end
|
186
204
|
|
187
|
-
#
|
188
|
-
def
|
189
|
-
|
190
|
-
|
205
|
+
# Check if any of the changes are actually watched for
|
206
|
+
def _relevant_changes?(changes)
|
207
|
+
files = changes.values.flatten(1)
|
208
|
+
watchers = _scoped_watchers
|
209
|
+
watchers.any? { |watcher| files.any? { |file| watcher.match(file) } }
|
191
210
|
end
|
192
211
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
212
|
+
def _relative_pathnames(paths)
|
213
|
+
paths.map { |path| _relative_pathname(path) }
|
214
|
+
end
|
215
|
+
|
216
|
+
def _run_actions(actions)
|
217
|
+
actions.each do |action_args|
|
218
|
+
args = action_args.dup
|
219
|
+
namespaced_action = args.shift
|
220
|
+
action = namespaced_action.to_s.sub(/^guard_/, "")
|
221
|
+
if ::Guard.respond_to?(action)
|
222
|
+
::Guard.send(action, *args)
|
223
|
+
else
|
224
|
+
fail "Unknown action: #{action.inspect}"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def _listener_callback
|
230
|
+
lambda do |modified, added, removed|
|
231
|
+
relative_paths = {
|
232
|
+
modified: _relative_pathnames(modified),
|
233
|
+
added: _relative_pathnames(added),
|
234
|
+
removed: _relative_pathnames(removed)
|
235
|
+
}
|
236
|
+
|
237
|
+
async_queue_add(relative_paths) if _relevant_changes?(relative_paths)
|
208
238
|
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def _reset_all
|
242
|
+
reset_groups
|
243
|
+
reset_plugins
|
244
|
+
reset_scope
|
245
|
+
end
|
246
|
+
|
247
|
+
def _pluginless_guardfile?
|
248
|
+
# no Reevaluator means there was no Guardfile configured that could be
|
249
|
+
# reevaluated, so we don't have a pluginless guardfile, because we don't
|
250
|
+
# have a Guardfile to begin with...
|
251
|
+
#
|
252
|
+
# But, if we have a Guardfile, we'll at least have the built-in
|
253
|
+
# Reevaluator, so the following will work:
|
254
|
+
|
255
|
+
# TODO: this is a workaround for tests
|
256
|
+
return true if plugins.empty?
|
257
|
+
|
258
|
+
plugins.map(&:name) == ["reevaluator"]
|
259
|
+
end
|
209
260
|
|
210
|
-
|
261
|
+
def _reset_for_tests
|
262
|
+
@options = nil
|
263
|
+
@queue = nil
|
264
|
+
@watchdirs = nil
|
265
|
+
@watchdirs = nil
|
266
|
+
@listener = nil
|
267
|
+
@interactor = nil
|
268
|
+
@scope = nil
|
211
269
|
end
|
212
270
|
end
|
213
271
|
end
|
data/lib/guard/aruba_adapter.rb
CHANGED
@@ -38,8 +38,8 @@ module Guard
|
|
38
38
|
# The ruby interpreter would pipe this to STDERR and exit 1 in the case
|
39
39
|
# of an unhandled exception
|
40
40
|
b = e.backtrace
|
41
|
-
|
42
|
-
@stderr.puts
|
41
|
+
@stderr.puts "#{b.shift}: #{e.message} (#{e.class})"
|
42
|
+
@stderr.puts b.map { |s| "\tfrom #{s}" }.join("\n")
|
43
43
|
1
|
44
44
|
rescue SystemExit => e
|
45
45
|
e.status
|
data/lib/guard/cli.rb
CHANGED
@@ -3,7 +3,8 @@ require "thor"
|
|
3
3
|
require "guard"
|
4
4
|
require "guard/version"
|
5
5
|
require "guard/dsl_describer"
|
6
|
-
require "guard/guardfile"
|
6
|
+
require "guard/guardfile/evaluator"
|
7
|
+
require "guard/guardfile/generator"
|
7
8
|
|
8
9
|
module Guard
|
9
10
|
# Facade for the Guard command line interface managed by
|
@@ -70,12 +71,6 @@ module Guard
|
|
70
71
|
aliases: "-B",
|
71
72
|
banner: "Turn off warning when Bundler is not present"
|
72
73
|
|
73
|
-
# DEPRECATED
|
74
|
-
method_option :show_deprecations,
|
75
|
-
type: :boolean,
|
76
|
-
default: false,
|
77
|
-
banner: "DEPRECATED: it does nothing"
|
78
|
-
|
79
74
|
# Listen options
|
80
75
|
method_option :latency,
|
81
76
|
type: :numeric,
|
@@ -131,6 +126,7 @@ module Guard
|
|
131
126
|
# @see Guard::DslDescriber.notifiers
|
132
127
|
#
|
133
128
|
def notifiers
|
129
|
+
::Guard.reset_options(options)
|
134
130
|
::Guard::DslDescriber.new(options).notifiers
|
135
131
|
end
|
136
132
|
|
@@ -170,24 +166,23 @@ module Guard
|
|
170
166
|
_verify_bundler_presence unless options[:no_bundler_warning]
|
171
167
|
|
172
168
|
::Guard.reset_options(options) # Since UI.deprecated uses config
|
173
|
-
::Guard.reset_evaluator(options) # for initialize_all_templates
|
174
169
|
|
175
|
-
|
176
|
-
|
170
|
+
generator = Guardfile::Generator.new(abort_on_existence: options[:bare])
|
171
|
+
generator.create_guardfile
|
177
172
|
|
178
173
|
# Note: this reset "hack" will be fixed after refactoring
|
179
174
|
::Guard.reset_plugins
|
180
175
|
|
181
176
|
# Evaluate because it might have existed and creating was skipped
|
182
|
-
::Guard.
|
177
|
+
::Guard::Guardfile::Evaluator.new(Guard.options).evaluate_guardfile
|
183
178
|
|
184
179
|
return if options[:bare]
|
185
180
|
|
186
181
|
if plugin_names.empty?
|
187
|
-
|
182
|
+
generator.initialize_all_templates
|
188
183
|
else
|
189
184
|
plugin_names.each do |plugin_name|
|
190
|
-
|
185
|
+
generator.initialize_template(plugin_name)
|
191
186
|
end
|
192
187
|
end
|
193
188
|
end
|