sprout 0.7.153-darwin
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/TODO +12 -0
- data/bin/sprout +128 -0
- data/doc/Bundle +14 -0
- data/doc/Generator +35 -0
- data/doc/Library +63 -0
- data/doc/Task +21 -0
- data/doc/Tool +20 -0
- data/lib/platform.rb +109 -0
- data/lib/progress_bar.rb +330 -0
- data/lib/sprout.rb +456 -0
- data/lib/sprout/builder.rb +35 -0
- data/lib/sprout/commands/generate.rb +14 -0
- data/lib/sprout/general_tasks.rb +5 -0
- data/lib/sprout/generator.rb +6 -0
- data/lib/sprout/generator/base_mixins.rb +132 -0
- data/lib/sprout/generator/named_base.rb +216 -0
- data/lib/sprout/log.rb +46 -0
- data/lib/sprout/process_runner.rb +46 -0
- data/lib/sprout/project_model.rb +114 -0
- data/lib/sprout/remote_file_loader.rb +233 -0
- data/lib/sprout/remote_file_target.rb +96 -0
- data/lib/sprout/simple_resolver.rb +88 -0
- data/lib/sprout/tasks/gem_wrap_task.rb +192 -0
- data/lib/sprout/tasks/library_task.rb +103 -0
- data/lib/sprout/tasks/sftp_task.rb +245 -0
- data/lib/sprout/tasks/tool_task.rb +541 -0
- data/lib/sprout/tasks/zip_task.rb +158 -0
- data/lib/sprout/template_resolver.rb +207 -0
- data/lib/sprout/user.rb +359 -0
- data/lib/sprout/version.rb +10 -0
- data/lib/sprout/zip_util.rb +61 -0
- data/rakefile.rb +129 -0
- data/samples/gem_wrap/rakefile.rb +17 -0
- metadata +159 -0
data/lib/sprout.rb
ADDED
@@ -0,0 +1,456 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'archive/tar/minitar'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/clean'
|
5
|
+
|
6
|
+
$:.push(File.dirname(__FILE__))
|
7
|
+
require 'progress_bar'
|
8
|
+
require 'sprout/log'
|
9
|
+
require 'sprout/user'
|
10
|
+
require 'sprout/zip_util'
|
11
|
+
require 'sprout/remote_file_target'
|
12
|
+
require 'sprout/remote_file_loader'
|
13
|
+
require 'sprout/simple_resolver'
|
14
|
+
require 'sprout/template_resolver'
|
15
|
+
|
16
|
+
require 'rubygems/installer'
|
17
|
+
require 'rubygems/source_info_cache'
|
18
|
+
require 'rubygems/version'
|
19
|
+
require 'rubygems/digest/md5'
|
20
|
+
|
21
|
+
require 'sprout/project_model'
|
22
|
+
require 'sprout/builder'
|
23
|
+
require 'sprout/version'
|
24
|
+
require 'sprout/tasks/tool_task'
|
25
|
+
require 'sprout/general_tasks'
|
26
|
+
|
27
|
+
module Sprout
|
28
|
+
class SproutError < StandardError #:nodoc:
|
29
|
+
end
|
30
|
+
|
31
|
+
# Sprouts is an open-source, cross-platform project generation and configuration tool
|
32
|
+
# for ActionScript 2, ActionScript 3, Adobe AIR and Flex projects. It is built on top
|
33
|
+
# of Ruby Gems, Rails Generators and is intended to work on any platform that Ruby runs
|
34
|
+
# on including specifically, Windows XP, Windows Vista, Cygwin, OS X and Linux.
|
35
|
+
#
|
36
|
+
# Sprouts can be separated into some core concepts as follows:
|
37
|
+
#
|
38
|
+
# ----
|
39
|
+
# == Tools
|
40
|
+
# :include: ../doc/Tool
|
41
|
+
#
|
42
|
+
# ----
|
43
|
+
# == Libraries
|
44
|
+
# :include: ../doc/Library
|
45
|
+
#
|
46
|
+
# ----
|
47
|
+
# == Bundles
|
48
|
+
# :include: ../doc/Bundle
|
49
|
+
#
|
50
|
+
# ----
|
51
|
+
# == Generators
|
52
|
+
# :include: ../doc/Generator
|
53
|
+
#
|
54
|
+
# ----
|
55
|
+
# == Tasks
|
56
|
+
# :include: ../doc/Task
|
57
|
+
#
|
58
|
+
# ----
|
59
|
+
# == Sprout
|
60
|
+
#
|
61
|
+
# Tools, Libraries and Bundles are distributed as RubyGems and given a specific gem name suffix. For some examples:
|
62
|
+
# sprout-flex2sdk-tool
|
63
|
+
# sprout-asunit-library
|
64
|
+
# sprout-as3-bundle
|
65
|
+
#
|
66
|
+
# The Sprout application provides shared functionality for each of the different types of Sprouts.
|
67
|
+
#
|
68
|
+
# The Sprout command line tool primarily provides access to project generators from any sprout bundle that is available
|
69
|
+
# to your system, either locally or from the network.
|
70
|
+
#
|
71
|
+
# When executed from the system path, this class will download and install a named bundle, and execute a +project+
|
72
|
+
# generator within that bundle. Following is an example:
|
73
|
+
#
|
74
|
+
# sprout -n as3 SomeProject
|
75
|
+
#
|
76
|
+
# The previous command will download and install the latest version of the sprout-as3-bundle gem and initiate the
|
77
|
+
# project_generator with a single argument of 'SomeProject'. If the string passed to the -n parameter begins with
|
78
|
+
# 'sprout-' it will be unmodified for the lookup. For example:
|
79
|
+
#
|
80
|
+
# spout -n sprout-as3-bundle SomeProject
|
81
|
+
#
|
82
|
+
# will not have duplicate strings prepended or suffixed.
|
83
|
+
#
|
84
|
+
# ----
|
85
|
+
# Some additional resources or references:
|
86
|
+
#
|
87
|
+
# Rake:
|
88
|
+
# http://rake.rubyforge.org
|
89
|
+
# http://martinfowler.com/articles/rake.html
|
90
|
+
#
|
91
|
+
# RubyGems:
|
92
|
+
# * http://www.rubygems.org
|
93
|
+
# * http://www.linuxjournal.com/article/8967
|
94
|
+
#
|
95
|
+
# Ruby Raven (Mostly Inspiration)
|
96
|
+
# * http://raven.rubyforge.org
|
97
|
+
#
|
98
|
+
class Sprout
|
99
|
+
@@default_rakefiles = ['rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb'].freeze
|
100
|
+
|
101
|
+
@@name = 'Sprouts'
|
102
|
+
@@cache = 'cache'
|
103
|
+
@@lib = 'lib'
|
104
|
+
@@spec = 'sprout.spec'
|
105
|
+
@@home = File.expand_path(File.dirname(File.dirname(__FILE__)))
|
106
|
+
|
107
|
+
# Execute a generator that is available locally or from the network.
|
108
|
+
# * +sprout_name+ A full gem name (ex., sprout-as3-bundle) that contains a generator that matches +generator_name+
|
109
|
+
# * +generator_name+ A string like 'project' or 'class' that maps to a generator
|
110
|
+
# * +params+ Arbitrary parameters to pass to the generator
|
111
|
+
# * +project_path+ Optional parameter. Will default to the nearest folder that contains a valid Rakefile.
|
112
|
+
# This Rakefile will usually be loaded by the referenced Generator, and it should have a configured ProjectModel
|
113
|
+
# defined in it.
|
114
|
+
def self.generate(sprout_name, generator_name, params, project_path=nil)
|
115
|
+
Rails::Generator::Base.use_sprout_sources!(sprout_name, project_path)
|
116
|
+
generator = Rails::Generator::Base.instance(generator_name, params)
|
117
|
+
generator.command(:create).invoke!
|
118
|
+
end
|
119
|
+
|
120
|
+
# Remove all installed RubyGems that begin with the string 'sprout' and clear the local sprout cache
|
121
|
+
def self.remove_all
|
122
|
+
# Set up sudo prefix if not on win machine
|
123
|
+
# Only show confirmation if there is at least one installed sprout gem
|
124
|
+
confirmation = false
|
125
|
+
count = 0
|
126
|
+
# For each sprout found, remove it!
|
127
|
+
Rails::Generator::GemGeneratorSource.new().each_sprout do |sprout|
|
128
|
+
count += 1
|
129
|
+
command = "#{get_gem_preamble} uninstall -x -a -i -q #{sprout.name}"
|
130
|
+
|
131
|
+
if(!confirmation)
|
132
|
+
break unless confirmation = remove_gems_confirmation
|
133
|
+
end
|
134
|
+
raise ">> Exited with errors: #{$?}" unless system(command)
|
135
|
+
end
|
136
|
+
|
137
|
+
if(confirmation)
|
138
|
+
puts "All Sprout gems have been successfully uninstalled"
|
139
|
+
elsif(count > 0)
|
140
|
+
puts "Some Sprout gems have been left on the system"
|
141
|
+
else
|
142
|
+
puts "No Sprout gems were found on this system"
|
143
|
+
end
|
144
|
+
|
145
|
+
# Now clear out the cache
|
146
|
+
cache = File.dirname(File.dirname(Sprout.sprout_cache))
|
147
|
+
|
148
|
+
if(File.exists?(cache))
|
149
|
+
puts "\n[WARNING]\n\nAbout to irrevocably destroy the sprout cache at:\n\n#{cache}\n\n"
|
150
|
+
puts "Are you absolutely sure? [Yn]"
|
151
|
+
response = $stdin.gets.chomp!
|
152
|
+
if(response.downcase.index('y'))
|
153
|
+
FileUtils.rm_rf(cache)
|
154
|
+
else
|
155
|
+
puts "Leaving the Sprout file cache in tact...."
|
156
|
+
end
|
157
|
+
else
|
158
|
+
puts "No cached files found on this system"
|
159
|
+
end
|
160
|
+
|
161
|
+
puts "To completely remove sprouts now, run:"
|
162
|
+
puts " #{get_gem_preamble} uninstall sprout"
|
163
|
+
end
|
164
|
+
|
165
|
+
# Build up the platform-specific preamble required
|
166
|
+
# to call the gem binary from Kernel.execute
|
167
|
+
def self.get_gem_preamble
|
168
|
+
usr = User.new()
|
169
|
+
if(!usr.is_a?(WinUser))
|
170
|
+
# Everyone but Win and Cygwin users get 'sudo '
|
171
|
+
return "sudo gem"
|
172
|
+
elsif(!usr.is_a?(CygwinUser))
|
173
|
+
# We're in the DOS Shell
|
174
|
+
return "ruby #{get_executable_from_path('gem')}"
|
175
|
+
end
|
176
|
+
# We're either a CygwinUser or some other non-sudo supporter
|
177
|
+
return 'gem'
|
178
|
+
end
|
179
|
+
|
180
|
+
# Retrieve the full path to an executable that is
|
181
|
+
# available in the system path
|
182
|
+
def self.get_executable_from_path(exe)
|
183
|
+
path = ENV['PATH']
|
184
|
+
file_path = nil
|
185
|
+
path.split(get_path_delimiter).each do |p|
|
186
|
+
file_path = File.join(p, exe)
|
187
|
+
# file_path = file_path.split("/").join("\\")
|
188
|
+
# file_path = file_path.split("\\").join("/")
|
189
|
+
if(File.exists?(file_path))
|
190
|
+
return User.clean_path(file_path)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
return nil
|
194
|
+
end
|
195
|
+
|
196
|
+
def self.get_path_delimiter
|
197
|
+
usr = User.new
|
198
|
+
if(usr.is_a?(WinUser) && !usr.is_a?(CygwinUser))
|
199
|
+
return ';'
|
200
|
+
else
|
201
|
+
return ':'
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def self.remove_gems_confirmation
|
206
|
+
msg =<<EOF
|
207
|
+
About to uninstall all RubyGems that match 'sprout-'....
|
208
|
+
Are you sure you want to do this? [Yn]
|
209
|
+
EOF
|
210
|
+
puts msg
|
211
|
+
response = $stdin.gets.chomp!
|
212
|
+
if(response.downcase.index('y'))
|
213
|
+
return true
|
214
|
+
end
|
215
|
+
return false
|
216
|
+
end
|
217
|
+
|
218
|
+
# Retrieve the file target to an executable by sprout name. Usually, these are tool sprouts.
|
219
|
+
# * +name+ Full sprout gem name that contains an executable file
|
220
|
+
# * +archive_path+ Optional parameter for tools that contain more than one executable, or for
|
221
|
+
# when you don't want to use the default executable presented by the tool. For example, the Flex 2 SDK
|
222
|
+
# has many executables, when this method is called for them, one might use something like:
|
223
|
+
# Sprout::Sprout.get_executable('sprout-flex2sdk-tool', 'bin/mxmlc')
|
224
|
+
# * +version+ Optional parameter to specify a particular gem version for this executable
|
225
|
+
def self.get_executable(name, archive_path=nil, version=nil)
|
226
|
+
target = self.sprout(name, version)
|
227
|
+
if(archive_path)
|
228
|
+
# If caller sent in a relative path to an executable (e.g., bin/mxmlc), use it
|
229
|
+
exe = File.join(target.installed_path, archive_path)
|
230
|
+
if(User.new.is_a?(WinUser) && !archive_path.match(/.exe$/))
|
231
|
+
# If we're on Win (even Cygwin), add .exe to support custom binaries (see sprout-flex2sdk-tool)
|
232
|
+
exe << '.exe'
|
233
|
+
end
|
234
|
+
elsif(target.url)
|
235
|
+
# Otherwise, use the default path to an executable if the RemoteFileTarget has a url prop
|
236
|
+
exe = File.join(target.installed_path, target.archive_path)
|
237
|
+
else
|
238
|
+
# Otherwise attempt to run the feature from the system path
|
239
|
+
exe = target.archive_path
|
240
|
+
end
|
241
|
+
|
242
|
+
if(File.exists?(exe) && File.stat(exe).mode != 33261)
|
243
|
+
File.chmod 0755, exe
|
244
|
+
end
|
245
|
+
|
246
|
+
return exe
|
247
|
+
end
|
248
|
+
|
249
|
+
# Allows us to easily download and install RubyGem sprouts by name and
|
250
|
+
# version.
|
251
|
+
# Returns a RubyGem Gem Spec[http://rubygems.org/read/chapter/20]
|
252
|
+
# when installation is complete. If the installed gem has a Ruby file
|
253
|
+
# configured to 'autorequire', that file will also be required by this
|
254
|
+
# method so that any provided Ruby functionality will be immediately
|
255
|
+
# available to client scripts. If the installed gem contains a
|
256
|
+
# 'sprout.spec' file, any RemoteFileTargets will be resolved synchronously
|
257
|
+
# and those files will be available in the Sprout::Sprout.cache.
|
258
|
+
#
|
259
|
+
def self.sprout(name, version=nil)
|
260
|
+
name = sprout_to_gem_name(name)
|
261
|
+
gem_spec = self.find_gem_spec(name, version)
|
262
|
+
sprout_spec_path = File.join(gem_spec.full_gem_path, @@spec)
|
263
|
+
|
264
|
+
if(gem_spec.autorequire)
|
265
|
+
$:.push(File.join(gem_spec.full_gem_path, 'lib'))
|
266
|
+
require gem_spec.autorequire
|
267
|
+
end
|
268
|
+
if(File.exists?(sprout_spec_path))
|
269
|
+
# Ensure the requisite files get downloaded and unpacked
|
270
|
+
Builder.build(sprout_spec_path, gem_file_cache(gem_spec.name, gem_spec.version))
|
271
|
+
else
|
272
|
+
return gem_spec
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Return sprout-#{name}-bundle for any name that does not begin with 'sprout-'. This was used early on in development
|
277
|
+
# but should possibly be removed as we move forward and try to support arbitrary RubyGems.
|
278
|
+
def self.sprout_to_gem_name(name)
|
279
|
+
if(!name.match(/^sprout-/))
|
280
|
+
name = "sprout-#{name}-bundle"
|
281
|
+
end
|
282
|
+
return name
|
283
|
+
end
|
284
|
+
|
285
|
+
# Return the home directory for this Sprout installation
|
286
|
+
def self.home
|
287
|
+
return @@home
|
288
|
+
end
|
289
|
+
|
290
|
+
# Return the location on disk where this installation of Sprouts stores it's cached files.
|
291
|
+
# If the currently installed version of Sprouts were 0.7 and your system username were 'foo'
|
292
|
+
# this would return the following locations:
|
293
|
+
# * +OSX+ /Users/foo/Library/Sprouts/cache/0.7
|
294
|
+
# * +Windows+ C:/Documents And Settings/foo/Local Settings/Application Data/Sprouts/cache/0.7
|
295
|
+
# * +Linux+ ~/.sprouts/cache/0.7
|
296
|
+
def self.sprout_cache
|
297
|
+
home = User.application_home(@@name)
|
298
|
+
return File.join(home, @@cache, "#{VERSION::MAJOR}.#{VERSION::MINOR}")
|
299
|
+
end
|
300
|
+
|
301
|
+
# Return the +sprout_cache+ combined with the passed in +name+ and +version+ so that you will get
|
302
|
+
# a cache location for a specific gem.
|
303
|
+
def self.gem_file_cache(name, version)
|
304
|
+
return File.join(sprout_cache, "#{name}-#{version}")
|
305
|
+
end
|
306
|
+
|
307
|
+
# Retrieve the RubyGems gem spec for a particular gem +name+ that meets the provided +requirements+.
|
308
|
+
# +requirements+ are provided as a string value like:
|
309
|
+
# '>= 0.0.1'
|
310
|
+
# or
|
311
|
+
# '0.0.1'
|
312
|
+
# This method will actually download and install the provided gem by +name+ and +requirements+ if
|
313
|
+
# it is not found locally on the system.
|
314
|
+
def self.find_gem_spec(name, requirements=nil, recursed=false)
|
315
|
+
specs = Gem::cache.search(/.*#{name}$/)
|
316
|
+
requirement = nil
|
317
|
+
if(requirements)
|
318
|
+
requirement = Gem::Requirement.new(requirements)
|
319
|
+
end
|
320
|
+
specs.each do |spec|
|
321
|
+
if(requirements)
|
322
|
+
if(requirement.satisfied_by?(spec.version))
|
323
|
+
return spec
|
324
|
+
end
|
325
|
+
else
|
326
|
+
return spec
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
if(recursed)
|
331
|
+
raise SproutError.new("Gem Spec not found for #{name} #{requirements}")
|
332
|
+
else
|
333
|
+
msg = ">> Loading gem [#{name}]"
|
334
|
+
msg << " #{requirements}" if requirements
|
335
|
+
msg << " from #{gem_sources.join(', ')} with it's dependencies"
|
336
|
+
Log.puts msg
|
337
|
+
parts = []
|
338
|
+
parts << "ins"
|
339
|
+
parts << "-r"
|
340
|
+
parts << name
|
341
|
+
# This url should be removed once released, released gems should be hosted from the rubyforge
|
342
|
+
# project, and development gems will be hosted on our domain.
|
343
|
+
parts << "--source #{gem_sources.join(' --source ')}" if(Log.debug || name.index('sprout-'))
|
344
|
+
parts << "-v #{requirements}" unless requirements.nil?
|
345
|
+
|
346
|
+
self.load_gem(parts.join(" "))
|
347
|
+
Gem::cache.refresh!
|
348
|
+
return find_gem_spec(name, requirements, true)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def self.load_gem(args)
|
353
|
+
# This must use a 'system' call because RubyGems
|
354
|
+
# sends an 'exit'?
|
355
|
+
system("#{get_gem_preamble} #{args}")
|
356
|
+
end
|
357
|
+
|
358
|
+
##
|
359
|
+
# List of files to ignore when copying project templates
|
360
|
+
# These files will not be copied
|
361
|
+
@@COPY_IGNORE_FILES = ['.', '..', '.svn', '.DS_Store', 'CVS', '.cvs' 'Thumbs.db', '__MACOSX', '.Trashes', 'Desktop DB', 'Desktop DF']
|
362
|
+
# Do not copy files found in the ignore_files list
|
363
|
+
def self.ignore_file? file
|
364
|
+
@@COPY_IGNORE_FILES.each do |name|
|
365
|
+
if(name == file)
|
366
|
+
return true
|
367
|
+
end
|
368
|
+
end
|
369
|
+
return false
|
370
|
+
end
|
371
|
+
|
372
|
+
def self.gem_sources=(sources) # :nodoc:
|
373
|
+
if(sources.is_a?(String))
|
374
|
+
# TODO: Clean up the string that is sent in,
|
375
|
+
# maybe even split space or comma-delimited?
|
376
|
+
sources = [sources]
|
377
|
+
end
|
378
|
+
@@gem_sources = sources
|
379
|
+
end
|
380
|
+
|
381
|
+
# TODO: Should be updated after release so that all gems are
|
382
|
+
# loaded form rubyforge instead of projectsprouts, only development
|
383
|
+
# gems will continue to be hosted at this default domain.
|
384
|
+
def self.gem_sources # :nodoc:
|
385
|
+
@@gem_sources ||= ['http://gems.rubyforge.org']
|
386
|
+
end
|
387
|
+
|
388
|
+
def self.project_name=(name) # :nodoc:
|
389
|
+
@@project_name = name
|
390
|
+
end
|
391
|
+
|
392
|
+
# Return the current project_name assuming someone has already set it, otherwise return an empty string
|
393
|
+
def self.project_name
|
394
|
+
@@project_name ||= ''
|
395
|
+
end
|
396
|
+
|
397
|
+
def self.project_path=(path) # :nodoc:
|
398
|
+
@@project_rakefile = child_rakefile(path)
|
399
|
+
@@project_path = path
|
400
|
+
end
|
401
|
+
|
402
|
+
# project_path should step backward in the file system
|
403
|
+
# until it encounters a rakefile. The parent directory
|
404
|
+
# of that rakefile should be returned.
|
405
|
+
# If no rakefile is found, it should return Dir.pwd
|
406
|
+
def self.project_path
|
407
|
+
@@project_path ||= get_implicit_project_path(Dir.pwd)
|
408
|
+
end
|
409
|
+
|
410
|
+
# Return the rakefile in the current +project_path+
|
411
|
+
def self.project_rakefile
|
412
|
+
return @@project_rakefile ||= nil
|
413
|
+
end
|
414
|
+
|
415
|
+
private
|
416
|
+
|
417
|
+
# Look in the provided +dir+ for files that meet the criteria to be a valid Rakefile.
|
418
|
+
def self.child_rakefile(dir)
|
419
|
+
@@default_rakefiles.each do |file|
|
420
|
+
rake_path = File.join(dir, file)
|
421
|
+
if(File.exists?(rake_path))
|
422
|
+
return rake_path
|
423
|
+
end
|
424
|
+
end
|
425
|
+
return nil
|
426
|
+
end
|
427
|
+
|
428
|
+
def self.get_implicit_project_path(path)
|
429
|
+
# We have recursed to the root of the filesystem, return nil
|
430
|
+
if(path.nil? || path == '/' || path.match(/[A-Z]\:\//))
|
431
|
+
return Dir.pwd
|
432
|
+
end
|
433
|
+
# Look for a rakefile as a child of the current path
|
434
|
+
if(child_rakefile(path))
|
435
|
+
return path
|
436
|
+
end
|
437
|
+
# No rakefile and no root found, check in parent dir
|
438
|
+
return Sprout.get_implicit_project_path(File.dirname(path))
|
439
|
+
end
|
440
|
+
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
# Set an array of URLs to use as gem repositories when loading Sprout gems.
|
445
|
+
# Any rakefile that requires the sprout gem can use this method as follows:
|
446
|
+
#
|
447
|
+
# set_sources ['http://gems.yourdomain.com']
|
448
|
+
#
|
449
|
+
def set_sources(sources)
|
450
|
+
Sprout::Sprout.gem_sources = sources
|
451
|
+
end
|
452
|
+
|
453
|
+
# Helper method that will download and install remote sprouts by name and version
|
454
|
+
def sprout(name, version=nil)
|
455
|
+
Sprout::Sprout.sprout(name, version)
|
456
|
+
end
|