sprout 0.7.219-i686-darwin10

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