echocas-client 2.1.1 → 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2020 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'thor'
4
+ require 'fileutils'
5
+ require 'yaml'
6
+
7
+ # Important - don't change this line or its position
8
+ MERB_THOR_VERSION = '0.2.1'
9
+
10
+ ##############################################################################
11
+
12
+ module ColorfulMessages
13
+
14
+ # red
15
+ def error(*messages)
16
+ puts messages.map { |msg| "\033[1;31m#{msg}\033[0m" }
17
+ end
18
+
19
+ # yellow
20
+ def warning(*messages)
21
+ puts messages.map { |msg| "\033[1;33m#{msg}\033[0m" }
22
+ end
23
+
24
+ # green
25
+ def success(*messages)
26
+ puts messages.map { |msg| "\033[1;32m#{msg}\033[0m" }
27
+ end
28
+
29
+ alias_method :message, :success
30
+
31
+ # magenta
32
+ def note(*messages)
33
+ puts messages.map { |msg| "\033[1;35m#{msg}\033[0m" }
34
+ end
35
+
36
+ # blue
37
+ def info(*messages)
38
+ puts messages.map { |msg| "\033[1;34m#{msg}\033[0m" }
39
+ end
40
+
41
+ end
42
+
43
+ ##############################################################################
44
+
45
+ require 'rubygems/dependency_installer'
46
+ require 'rubygems/uninstaller'
47
+ require 'rubygems/dependency'
48
+
49
+ module GemManagement
50
+
51
+ include ColorfulMessages
52
+
53
+ # Install a gem - looks remotely and local gem cache;
54
+ # won't process rdoc or ri options.
55
+ def install_gem(gem, options = {})
56
+ refresh = options.delete(:refresh) || []
57
+ from_cache = (options.key?(:cache) && options.delete(:cache))
58
+ if from_cache
59
+ install_gem_from_cache(gem, options)
60
+ else
61
+ version = options.delete(:version)
62
+ Gem.configuration.update_sources = false
63
+
64
+ # Limit source index to install dir
65
+ update_source_index(options[:install_dir]) if options[:install_dir]
66
+
67
+ installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
68
+
69
+ # Force-refresh certain gems by excluding them from the current index
70
+ if !options[:ignore_dependencies] && refresh.respond_to?(:include?) && !refresh.empty?
71
+ source_index = installer.instance_variable_get(:@source_index)
72
+ source_index.gems.each do |name, spec|
73
+ source_index.gems.delete(name) if refresh.include?(spec.name)
74
+ end
75
+ end
76
+
77
+ exception = nil
78
+ begin
79
+ installer.install gem, version
80
+ rescue Gem::InstallError => e
81
+ exception = e
82
+ rescue Gem::GemNotFoundException => e
83
+ if from_cache && gem_file = find_gem_in_cache(gem, version)
84
+ puts "Located #{gem} in gem cache..."
85
+ installer.install gem_file
86
+ else
87
+ exception = e
88
+ end
89
+ rescue => e
90
+ exception = e
91
+ end
92
+ if installer.installed_gems.empty? && exception
93
+ error "Failed to install gem '#{gem} (#{version || 'any version'})' (#{exception.message})"
94
+ end
95
+ ensure_bin_wrapper_for_installed_gems(installer.installed_gems, options)
96
+ installer.installed_gems.each do |spec|
97
+ success "Successfully installed #{spec.full_name}"
98
+ end
99
+ return !installer.installed_gems.empty?
100
+ end
101
+ end
102
+
103
+ # Install a gem - looks in the system's gem cache instead of remotely;
104
+ # won't process rdoc or ri options.
105
+ def install_gem_from_cache(gem, options = {})
106
+ version = options.delete(:version)
107
+ Gem.configuration.update_sources = false
108
+ installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
109
+ exception = nil
110
+ begin
111
+ if gem_file = find_gem_in_cache(gem, version)
112
+ puts "Located #{gem} in gem cache..."
113
+ installer.install gem_file
114
+ else
115
+ raise Gem::InstallError, "Unknown gem #{gem}"
116
+ end
117
+ rescue Gem::InstallError => e
118
+ exception = e
119
+ end
120
+ if installer.installed_gems.empty? && exception
121
+ error "Failed to install gem '#{gem}' (#{e.message})"
122
+ end
123
+ ensure_bin_wrapper_for_installed_gems(installer.installed_gems, options)
124
+ installer.installed_gems.each do |spec|
125
+ success "Successfully installed #{spec.full_name}"
126
+ end
127
+ end
128
+
129
+ # Install a gem from source - builds and packages it first then installs.
130
+ #
131
+ # Examples:
132
+ # install_gem_from_source(source_dir, :install_dir => ...)
133
+ # install_gem_from_source(source_dir, gem_name)
134
+ # install_gem_from_source(source_dir, :skip => [...])
135
+ def install_gem_from_source(source_dir, *args)
136
+ installed_gems = []
137
+ opts = args.last.is_a?(Hash) ? args.pop : {}
138
+ Dir.chdir(source_dir) do
139
+ gem_name = args[0] || File.basename(source_dir)
140
+ gem_pkg_dir = File.join(source_dir, 'pkg')
141
+ gem_pkg_glob = File.join(gem_pkg_dir, "#{gem_name}-*.gem")
142
+ skip_gems = opts.delete(:skip) || []
143
+
144
+ # Cleanup what's already there
145
+ clobber(source_dir)
146
+ FileUtils.mkdir_p(gem_pkg_dir) unless File.directory?(gem_pkg_dir)
147
+
148
+ # Recursively process all gem packages within the source dir
149
+ skip_gems << gem_name
150
+ packages = package_all(source_dir, skip_gems)
151
+
152
+ if packages.length == 1
153
+ # The are no subpackages for the main package
154
+ refresh = [gem_name]
155
+ else
156
+ # Gather all packages into the top-level pkg directory
157
+ packages.each do |pkg|
158
+ FileUtils.copy_entry(pkg, File.join(gem_pkg_dir, File.basename(pkg)))
159
+ end
160
+
161
+ # Finally package the main gem - without clobbering the already copied pkgs
162
+ package(source_dir, false)
163
+
164
+ # Gather subgems to refresh during installation of the main gem
165
+ refresh = packages.map do |pkg|
166
+ File.basename(pkg, '.gem')[/^(.*?)-([\d\.]+)$/, 1] rescue nil
167
+ end.compact
168
+
169
+ # Install subgems explicitly even if ignore_dependencies is set
170
+ if opts[:ignore_dependencies]
171
+ refresh.each do |name|
172
+ gem_pkg = Dir[File.join(gem_pkg_dir, "#{name}-*.gem")][0]
173
+ install_pkg(gem_pkg, opts)
174
+ end
175
+ end
176
+ end
177
+
178
+ ensure_bin_wrapper_for(opts[:install_dir], opts[:bin_dir], *installed_gems)
179
+
180
+ # Finally install the main gem
181
+ if install_pkg(Dir[gem_pkg_glob][0], opts.merge(:refresh => refresh))
182
+ installed_gems = refresh
183
+ else
184
+ installed_gems = []
185
+ end
186
+ end
187
+ installed_gems
188
+ end
189
+
190
+ def install_pkg(gem_pkg, opts = {})
191
+ if (gem_pkg && File.exists?(gem_pkg))
192
+ # Needs to be executed from the directory that contains all packages
193
+ Dir.chdir(File.dirname(gem_pkg)) { install_gem(gem_pkg, opts) }
194
+ else
195
+ false
196
+ end
197
+ end
198
+
199
+ # Uninstall a gem.
200
+ def uninstall_gem(gem, options = {})
201
+ if options[:version] && !options[:version].is_a?(Gem::Requirement)
202
+ options[:version] = Gem::Requirement.new ["= #{options[:version]}"]
203
+ end
204
+ update_source_index(options[:install_dir]) if options[:install_dir]
205
+ Gem::Uninstaller.new(gem, options).uninstall rescue nil
206
+ end
207
+
208
+ def clobber(source_dir)
209
+ Dir.chdir(source_dir) do
210
+ system "#{Gem.ruby} -S rake -s clobber" unless File.exists?('Thorfile')
211
+ end
212
+ end
213
+
214
+ def package(source_dir, clobber = true)
215
+ Dir.chdir(source_dir) do
216
+ if File.exists?('Thorfile')
217
+ thor ":package"
218
+ elsif File.exists?('Rakefile')
219
+ rake "clobber" if clobber
220
+ rake "package"
221
+ end
222
+ end
223
+ Dir[File.join(source_dir, 'pkg/*.gem')]
224
+ end
225
+
226
+ def package_all(source_dir, skip = [], packages = [])
227
+ if Dir[File.join(source_dir, '{Rakefile,Thorfile}')][0]
228
+ name = File.basename(source_dir)
229
+ Dir[File.join(source_dir, '*', '{Rakefile,Thorfile}')].each do |taskfile|
230
+ package_all(File.dirname(taskfile), skip, packages)
231
+ end
232
+ packages.push(*package(source_dir)) unless skip.include?(name)
233
+ end
234
+ packages.uniq
235
+ end
236
+
237
+ def rake(cmd)
238
+ cmd << " >/dev/null" if $SILENT && !Gem.win_platform?
239
+ system "#{Gem.ruby} -S #{which('rake')} -s #{cmd} >/dev/null"
240
+ end
241
+
242
+ def thor(cmd)
243
+ cmd << " >/dev/null" if $SILENT && !Gem.win_platform?
244
+ system "#{Gem.ruby} -S #{which('thor')} #{cmd}"
245
+ end
246
+
247
+ # Use the local bin/* executables if available.
248
+ def which(executable)
249
+ if File.executable?(exec = File.join(Dir.pwd, 'bin', executable))
250
+ exec
251
+ else
252
+ executable
253
+ end
254
+ end
255
+
256
+ # Partition gems into system, local and missing gems
257
+ def partition_dependencies(dependencies, gem_dir)
258
+ system_specs, local_specs, missing_deps = [], [], []
259
+ if gem_dir && File.directory?(gem_dir)
260
+ gem_dir = File.expand_path(gem_dir)
261
+ ::Gem.clear_paths; ::Gem.path.unshift(gem_dir)
262
+ ::Gem.source_index.refresh!
263
+ dependencies.each do |dep|
264
+ gemspecs = ::Gem.source_index.search(dep)
265
+ local = gemspecs.reverse.find { |s| s.loaded_from.index(gem_dir) == 0 }
266
+ if local
267
+ local_specs << local
268
+ elsif gemspecs.last
269
+ system_specs << gemspecs.last
270
+ else
271
+ missing_deps << dep
272
+ end
273
+ end
274
+ ::Gem.clear_paths
275
+ else
276
+ dependencies.each do |dep|
277
+ gemspecs = ::Gem.source_index.search(dep)
278
+ if gemspecs.last
279
+ system_specs << gemspecs.last
280
+ else
281
+ missing_deps << dep
282
+ end
283
+ end
284
+ end
285
+ [system_specs, local_specs, missing_deps]
286
+ end
287
+
288
+ # Create a modified executable wrapper in the specified bin directory.
289
+ def ensure_bin_wrapper_for(gem_dir, bin_dir, *gems)
290
+ options = gems.last.is_a?(Hash) ? gems.last : {}
291
+ options[:no_minigems] ||= []
292
+ if bin_dir && File.directory?(bin_dir)
293
+ gems.each do |gem|
294
+ if gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{gem}-*.gemspec")].last
295
+ spec = Gem::Specification.load(gemspec_path)
296
+ enable_minigems = !options[:no_minigems].include?(spec.name)
297
+ spec.executables.each do |exec|
298
+ executable = File.join(bin_dir, exec)
299
+ message "Writing executable wrapper #{executable}"
300
+ File.open(executable, 'w', 0755) do |f|
301
+ f.write(executable_wrapper(spec, exec, enable_minigems))
302
+ end
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ def ensure_bin_wrapper_for_installed_gems(gemspecs, options)
310
+ if options[:install_dir] && options[:bin_dir]
311
+ gems = gemspecs.map { |spec| spec.name }
312
+ ensure_bin_wrapper_for(options[:install_dir], options[:bin_dir], *gems)
313
+ end
314
+ end
315
+
316
+ private
317
+
318
+ def executable_wrapper(spec, bin_file_name, minigems = true)
319
+ requirements = ['minigems', 'rubygems']
320
+ requirements.reverse! unless minigems
321
+ try_req, then_req = requirements
322
+ <<-TEXT
323
+ #!/usr/bin/env ruby
324
+ #
325
+ # This file was generated by Merb's GemManagement
326
+ #
327
+ # The application '#{spec.name}' is installed as part of a gem, and
328
+ # this file is here to facilitate running it.
329
+
330
+ begin
331
+ require '#{try_req}'
332
+ rescue LoadError
333
+ require '#{then_req}'
334
+ end
335
+
336
+ # use gems dir if ../gems exists - eg. only for ./bin/#{bin_file_name}
337
+ if File.directory?(gems_dir = File.join(File.dirname(__FILE__), '..', 'gems'))
338
+ $BUNDLE = true; Gem.clear_paths; Gem.path.replace([File.expand_path(gems_dir)])
339
+ ENV["PATH"] = "\#{File.dirname(__FILE__)}:\#{gems_dir}/bin:\#{ENV["PATH"]}"
340
+ if (local_gem = Dir[File.join(gems_dir, "specifications", "#{spec.name}-*.gemspec")].last)
341
+ version = File.basename(local_gem)[/-([\\.\\d]+)\\.gemspec$/, 1]
342
+ end
343
+ end
344
+
345
+ version ||= "#{Gem::Requirement.default}"
346
+
347
+ if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
348
+ version = $1
349
+ ARGV.shift
350
+ end
351
+
352
+ gem '#{spec.name}', version
353
+ load '#{bin_file_name}'
354
+ TEXT
355
+ end
356
+
357
+ def find_gem_in_cache(gem, version)
358
+ spec = if version
359
+ version = Gem::Requirement.new ["= #{version}"] unless version.is_a?(Gem::Requirement)
360
+ Gem.source_index.find_name(gem, version).first
361
+ else
362
+ Gem.source_index.find_name(gem).sort_by { |g| g.version }.last
363
+ end
364
+ if spec && File.exists?(gem_file = "#{spec.installation_path}/cache/#{spec.full_name}.gem")
365
+ gem_file
366
+ end
367
+ end
368
+
369
+ def update_source_index(dir)
370
+ Gem.source_index.load_gems_in(File.join(dir, 'specifications'))
371
+ end
372
+
373
+ end
374
+
375
+ ##############################################################################
376
+
377
+ class SourceManager
378
+
379
+ include ColorfulMessages
380
+
381
+ attr_accessor :source_dir
382
+
383
+ def initialize(source_dir)
384
+ self.source_dir = source_dir
385
+ end
386
+
387
+ def clone(name, url)
388
+ FileUtils.cd(source_dir) do
389
+ raise "destination directory already exists" if File.directory?(name)
390
+ system("git clone --depth 1 #{url} #{name}")
391
+ end
392
+ rescue => e
393
+ error "Unable to clone #{name} repository (#{e.message})"
394
+ end
395
+
396
+ def update(name, url)
397
+ if File.directory?(repository_dir = File.join(source_dir, name))
398
+ FileUtils.cd(repository_dir) do
399
+ repos = existing_repos(name)
400
+ fork_name = url[/.com\/+?(.+)\/.+\.git/u, 1]
401
+ if url == repos["origin"]
402
+ # Pull from the original repository - no branching needed
403
+ info "Pulling from origin: #{url}"
404
+ system "git fetch; git checkout master; git rebase origin/master"
405
+ elsif repos.values.include?(url) && fork_name
406
+ # Update and switch to a remote branch for a particular github fork
407
+ info "Switching to remote branch: #{fork_name}"
408
+ system "git checkout -b #{fork_name} #{fork_name}/master"
409
+ system "git rebase #{fork_name}/master"
410
+ elsif fork_name
411
+ # Create a new remote branch for a particular github fork
412
+ info "Adding a new remote branch: #{fork_name}"
413
+ system "git remote add -f #{fork_name} #{url}"
414
+ system "git checkout -b #{fork_name} #{fork_name}/master"
415
+ else
416
+ warning "No valid repository found for: #{name}"
417
+ end
418
+ end
419
+ return true
420
+ else
421
+ warning "No valid repository found at: #{repository_dir}"
422
+ end
423
+ rescue => e
424
+ error "Unable to update #{name} repository (#{e.message})"
425
+ return false
426
+ end
427
+
428
+ def existing_repos(name)
429
+ repos = []
430
+ FileUtils.cd(File.join(source_dir, name)) do
431
+ repos = %x[git remote -v].split("\n").map { |branch| branch.split(/\s+/) }
432
+ end
433
+ Hash[*repos.flatten]
434
+ end
435
+
436
+ end
437
+
438
+ ##############################################################################
439
+
440
+ module MerbThorHelper
441
+
442
+ attr_accessor :force_gem_dir
443
+
444
+ def self.included(base)
445
+ base.send(:include, ColorfulMessages)
446
+ base.extend ColorfulMessages
447
+ end
448
+
449
+ def use_edge_gem_server
450
+ ::Gem.sources << 'http://edge.merbivore.com'
451
+ end
452
+
453
+ def source_manager
454
+ @_source_manager ||= SourceManager.new(source_dir)
455
+ end
456
+
457
+ def extract_repositories(names)
458
+ repos = []
459
+ names.each do |name|
460
+ if repo_url = Merb::Source.repo(name, options[:sources])
461
+ # A repository entry for this dependency exists
462
+ repo = [name, repo_url]
463
+ repos << repo unless repos.include?(repo)
464
+ elsif (repo_name = Merb::Stack.lookup_repository_name(name)) &&
465
+ (repo_url = Merb::Source.repo(repo_name, options[:sources]))
466
+ # A parent repository entry for this dependency exists
467
+ repo = [repo_name, repo_url]
468
+ unless repos.include?(repo)
469
+ puts "Found #{repo_name}/#{name} at #{repo_url}"
470
+ repos << repo
471
+ end
472
+ end
473
+ end
474
+ repos
475
+ end
476
+
477
+ def update_dependency_repositories(dependencies)
478
+ repos = extract_repositories(dependencies.map { |d| d.name })
479
+ update_repositories(repos)
480
+ end
481
+
482
+ def update_repositories(repos)
483
+ repos.each do |(name, url)|
484
+ if File.directory?(repository_dir = File.join(source_dir, name))
485
+ message "Updating or branching #{name}..."
486
+ source_manager.update(name, url)
487
+ else
488
+ message "Cloning #{name} repository from #{url}..."
489
+ source_manager.clone(name, url)
490
+ end
491
+ end
492
+ end
493
+
494
+ def install_dependency(dependency, opts = {})
495
+ version = dependency.version_requirements.to_s
496
+ install_opts = default_install_options.merge(:version => version)
497
+ Merb::Gem.install(dependency.name, install_opts.merge(opts))
498
+ end
499
+
500
+ def install_dependency_from_source(dependency, opts = {})
501
+ matches = Dir[File.join(source_dir, "**", dependency.name, "{Rakefile,Thorfile}")]
502
+ matches.reject! { |m| File.basename(m) == 'Thorfile' }
503
+ if matches.length == 1 && matches[0]
504
+ if File.directory?(gem_src_dir = File.dirname(matches[0]))
505
+ begin
506
+ Merb::Gem.install_gem_from_source(gem_src_dir, default_install_options.merge(opts))
507
+ puts "Installed #{dependency.name}"
508
+ return true
509
+ rescue => e
510
+ warning "Unable to install #{dependency.name} from source (#{e.message})"
511
+ end
512
+ else
513
+ msg = "Unknown directory: #{gem_src_dir}"
514
+ warning "Unable to install #{dependency.name} from source (#{msg})"
515
+ end
516
+ elsif matches.length > 1
517
+ error "Ambigous source(s) for dependency: #{dependency.name}"
518
+ matches.each { |m| puts "- #{m}" }
519
+ end
520
+ return false
521
+ end
522
+
523
+ def clobber_dependencies!
524
+ if options[:force] && gem_dir && File.directory?(gem_dir)
525
+ # Remove all existing local gems by clearing the gems directory
526
+ if dry_run?
527
+ note 'Clearing existing local gems...'
528
+ else
529
+ message 'Clearing existing local gems...'
530
+ FileUtils.rm_rf(gem_dir) && FileUtils.mkdir_p(default_gem_dir)
531
+ end
532
+ elsif !local.empty?
533
+ # Uninstall all local versions of the gems to install
534
+ if dry_run?
535
+ note 'Uninstalling existing local gems:'
536
+ local.each { |gemspec| note "Uninstalled #{gemspec.name}" }
537
+ else
538
+ message 'Uninstalling existing local gems:' if local.size > 1
539
+ local.each do |gemspec|
540
+ Merb::Gem.uninstall(gemspec.name, default_uninstall_options)
541
+ end
542
+ end
543
+ end
544
+ end
545
+
546
+ def display_gemspecs(gemspecs)
547
+ if gemspecs.empty?
548
+ puts "- none"
549
+ else
550
+ gemspecs.each do |spec|
551
+ if hint = Dir[File.join(spec.full_gem_path, '*.strategy')][0]
552
+ strategy = File.basename(hint, '.strategy')
553
+ puts "- #{spec.full_name} (#{strategy})"
554
+ else
555
+ puts "~ #{spec.full_name}" # unknown strategy
556
+ end
557
+ end
558
+ end
559
+ end
560
+
561
+ def display_dependencies(dependencies)
562
+ if dependencies.empty?
563
+ puts "- none"
564
+ else
565
+ dependencies.each { |d| puts "- #{d.name} (#{d.version_requirements})" }
566
+ end
567
+ end
568
+
569
+ def default_install_options
570
+ { :install_dir => gem_dir, :bin_dir => bin_dir, :ignore_dependencies => ignore_dependencies? }
571
+ end
572
+
573
+ def default_uninstall_options
574
+ { :install_dir => gem_dir, :bin_dir => bin_dir, :ignore => true, :all => true, :executables => true }
575
+ end
576
+
577
+ def dry_run?
578
+ options[:"dry-run"]
579
+ end
580
+
581
+ def ignore_dependencies?
582
+ options[:"ignore-dependencies"]
583
+ end
584
+
585
+ # The current working directory, or Merb app root (--merb-root option).
586
+ def working_dir
587
+ @_working_dir ||= File.expand_path(options['merb-root'] || Dir.pwd)
588
+ end
589
+
590
+ # We should have a ./src dir for local and system-wide management.
591
+ def source_dir
592
+ @_source_dir ||= File.join(working_dir, 'src')
593
+ create_if_missing(@_source_dir)
594
+ @_source_dir
595
+ end
596
+
597
+ # If a local ./gems dir is found, return it.
598
+ def gem_dir
599
+ return force_gem_dir if force_gem_dir
600
+ if File.directory?(dir = default_gem_dir)
601
+ dir
602
+ end
603
+ end
604
+
605
+ def default_gem_dir
606
+ File.join(working_dir, 'gems')
607
+ end
608
+
609
+ # If we're in a Merb app, we can have a ./bin directory;
610
+ # create it if it's not there.
611
+ def bin_dir
612
+ @_bin_dir ||= begin
613
+ if gem_dir
614
+ dir = File.join(working_dir, 'bin')
615
+ create_if_missing(dir)
616
+ dir
617
+ end
618
+ end
619
+ end
620
+
621
+ # Helper to create dir unless it exists.
622
+ def create_if_missing(path)
623
+ FileUtils.mkdir(path) unless File.exists?(path)
624
+ end
625
+
626
+ def sudo
627
+ ENV['THOR_SUDO'] ||= "sudo"
628
+ sudo = Gem.win_platform? ? "" : ENV['THOR_SUDO']
629
+ end
630
+
631
+ def local_gemspecs(directory = gem_dir)
632
+ if File.directory?(specs_dir = File.join(directory, 'specifications'))
633
+ Dir[File.join(specs_dir, '*.gemspec')].map do |gemspec_path|
634
+ gemspec = Gem::Specification.load(gemspec_path)
635
+ gemspec.loaded_from = gemspec_path
636
+ gemspec
637
+ end
638
+ else
639
+ []
640
+ end
641
+ end
642
+
643
+ end
644
+
645
+ ##############################################################################
646
+
647
+ $SILENT = true # don't output all the mess some rake package tasks spit out
648
+
649
+ module Merb
650
+
651
+ class Gem < Thor
652
+
653
+ include MerbThorHelper
654
+ extend GemManagement
655
+
656
+ attr_accessor :system, :local, :missing
657
+
658
+ global_method_options = {
659
+ "--merb-root" => :optional, # the directory to operate on
660
+ "--version" => :optional, # gather specific version of gem
661
+ "--ignore-dependencies" => :boolean # don't install sub-dependencies
662
+ }
663
+
664
+ method_options global_method_options
665
+ def initialize(*args); super; end
666
+
667
+ # List gems that match the specified criteria.
668
+ #
669
+ # By default all local gems are listed. When the first argument is 'all' the
670
+ # list is partitioned into system an local gems; specify 'system' to show
671
+ # only system gems. A second argument can be used to filter on a set of known
672
+ # components, like all merb-more gems for example.
673
+ #
674
+ # Examples:
675
+ #
676
+ # merb:gem:list # list all local gems - the default
677
+ # merb:gem:list all # list system and local gems
678
+ # merb:gem:list system # list only system gems
679
+ # merb:gem:list all merb-more # list only merb-more related gems
680
+ # merb:gem:list --version 0.9.8 # list gems that match the version
681
+
682
+ desc 'list [all|local|system] [comp]', 'Show installed gems'
683
+ def list(filter = 'local', comp = nil)
684
+ deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies
685
+ self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
686
+ case filter
687
+ when 'all'
688
+ message 'Installed system gems:'
689
+ display_gemspecs(system)
690
+ message 'Installed local gems:'
691
+ display_gemspecs(local)
692
+ when 'system'
693
+ message 'Installed system gems:'
694
+ display_gemspecs(system)
695
+ when 'local'
696
+ message 'Installed local gems:'
697
+ display_gemspecs(local)
698
+ else
699
+ warning "Invalid listing filter '#{filter}'"
700
+ end
701
+ end
702
+
703
+ # Install the specified gems.
704
+ #
705
+ # All arguments should be names of gems to install.
706
+ #
707
+ # When :force => true then any existing versions of the gems to be installed
708
+ # will be uninstalled first. It's important to note that so-called meta-gems
709
+ # or gems that exactly match a set of Merb::Stack.components will have their
710
+ # sub-gems uninstalled too. For example, uninstalling merb-more will install
711
+ # all contained gems: merb-action-args, merb-assets, merb-gen, ...
712
+ #
713
+ # Examples:
714
+ #
715
+ # merb:gem:install merb-core merb-slices # install all specified gems
716
+ # merb:gem:install merb-core --version 0.9.8 # install a specific version of a gem
717
+ # merb:gem:install merb-core --force # uninstall then subsequently install the gem
718
+ # merb:gem:install merb-core --cache # try to install locally from system gems
719
+ # merb:gem:install merb --merb-edge # install from edge.merbivore.com
720
+
721
+ desc 'install GEM_NAME [GEM_NAME, ...]', 'Install a gem from rubygems'
722
+ method_options "--cache" => :boolean,
723
+ "--dry-run" => :boolean,
724
+ "--force" => :boolean,
725
+ "--merb-edge" => :boolean
726
+ def install(*names)
727
+ opts = { :version => options[:version], :cache => options[:cache] }
728
+ use_edge_gem_server if options[:"merb-edge"]
729
+ current_gem = nil
730
+
731
+ # uninstall existing gems of the ones we're going to install
732
+ uninstall(*names) if options[:force]
733
+
734
+ message "Installing #{names.length} #{names.length == 1 ? 'gem' : 'gems'}..."
735
+ puts "This may take a while..."
736
+
737
+ names.each do |gem_name|
738
+ current_gem = gem_name
739
+ if dry_run?
740
+ note "Installing #{current_gem}..."
741
+ else
742
+ message "Installing #{current_gem}..."
743
+ self.class.install(gem_name, default_install_options.merge(opts))
744
+ end
745
+ end
746
+ rescue => e
747
+ error "Failed to install #{current_gem ? current_gem : 'gem'} (#{e.message})"
748
+ end
749
+
750
+ # Uninstall the specified gems.
751
+ #
752
+ # By default all specified gems are uninstalled. It's important to note that
753
+ # so-called meta-gems or gems that match a set of Merb::Stack.components will
754
+ # have their sub-gems uninstalled too. For example, uninstalling merb-more
755
+ # will install all contained gems: merb-action-args, merb-assets, ...
756
+ #
757
+ # Existing dependencies will be clobbered; when :force => true then all gems
758
+ # will be cleared, otherwise only existing local dependencies of the
759
+ # matching component set will be removed.
760
+ #
761
+ # Examples:
762
+ #
763
+ # merb:gem:uninstall merb-core merb-slices # uninstall all specified gems
764
+ # merb:gem:uninstall merb-core --version 0.9.8 # uninstall a specific version of a gem
765
+
766
+ desc 'uninstall GEM_NAME [GEM_NAME, ...]', 'Unstall a gem'
767
+ method_options "--dry-run" => :boolean
768
+ def uninstall(*names)
769
+ opts = { :version => options[:version] }
770
+ current_gem = nil
771
+ if dry_run?
772
+ note "Uninstalling any existing gems of: #{names.join(', ')}"
773
+ else
774
+ message "Uninstalling any existing gems of: #{names.join(', ')}"
775
+ names.each do |gem_name|
776
+ current_gem = gem_name
777
+ Merb::Gem.uninstall(gem_name, default_uninstall_options) rescue nil
778
+ # if this gem is a meta-gem or a component set name, remove sub-gems
779
+ (Merb::Stack.components(gem_name) || []).each do |comp|
780
+ Merb::Gem.uninstall(comp, default_uninstall_options) rescue nil
781
+ end
782
+ end
783
+ end
784
+ rescue => e
785
+ error "Failed to uninstall #{current_gem ? current_gem : 'gem'} (#{e.message})"
786
+ end
787
+
788
+ # Recreate all gems from gems/cache on the current platform.
789
+ #
790
+ # This task should be executed as part of a deployment setup, where the
791
+ # deployment system runs this after the app has been installed.
792
+ # Usually triggered by Capistrano, God...
793
+ #
794
+ # It will regenerate gems from the bundled gems cache for any gem that has
795
+ # C extensions - which need to be recompiled for the target deployment platform.
796
+ #
797
+ # Note: at least gems/cache and gems/specifications should be in your SCM.
798
+
799
+ desc 'redeploy', 'Recreate all gems on the current platform'
800
+ method_options "--dry-run" => :boolean, "--force" => :boolean
801
+ def redeploy
802
+ require 'tempfile' # for Dir::tmpdir access
803
+ if gem_dir && File.directory?(cache_dir = File.join(gem_dir, 'cache'))
804
+ specs = local_gemspecs
805
+ message "Recreating #{specs.length} gems from cache..."
806
+ puts "This may take a while..."
807
+ specs.each do |gemspec|
808
+ if File.exists?(gem_file = File.join(cache_dir, "#{gemspec.full_name}.gem"))
809
+ gem_file_copy = File.join(Dir::tmpdir, File.basename(gem_file))
810
+ if dry_run?
811
+ note "Recreating #{gemspec.full_name}"
812
+ else
813
+ message "Recreating #{gemspec.full_name}"
814
+ if options[:force] && File.directory?(gem = File.join(gem_dir, 'gems', gemspec.full_name))
815
+ puts "Removing existing #{gemspec.full_name}"
816
+ FileUtils.rm_rf(gem)
817
+ end
818
+ # Copy the gem to a temporary file, because otherwise RubyGems/FileUtils
819
+ # will complain about copying identical files (same source/destination).
820
+ FileUtils.cp(gem_file, gem_file_copy)
821
+ Merb::Gem.install(gem_file_copy, :install_dir => gem_dir, :ignore_dependencies => true)
822
+ File.delete(gem_file_copy)
823
+ end
824
+ end
825
+ end
826
+ else
827
+ error "No application local gems directory found"
828
+ end
829
+ end
830
+
831
+ private
832
+
833
+ # Return dependencies for all installed gems; both system-wide and locally;
834
+ # optionally filters on :version requirement.
835
+ def dependencies
836
+ version_req = if options[:version]
837
+ ::Gem::Requirement.create(options[:version])
838
+ else
839
+ ::Gem::Requirement.default
840
+ end
841
+ if gem_dir
842
+ ::Gem.clear_paths; ::Gem.path.unshift(gem_dir)
843
+ ::Gem.source_index.refresh!
844
+ end
845
+ deps = []
846
+ ::Gem.source_index.each do |fullname, gemspec|
847
+ if version_req.satisfied_by?(gemspec.version)
848
+ deps << ::Gem::Dependency.new(gemspec.name, "= #{gemspec.version}")
849
+ end
850
+ end
851
+ ::Gem.clear_paths if gem_dir
852
+ deps.sort
853
+ end
854
+
855
+ public
856
+
857
+ # Install gem with some default options.
858
+ def self.install(name, options = {})
859
+ defaults = {}
860
+ defaults[:cache] = false unless opts[:install_dir]
861
+ install_gem(name, defaults.merge(options))
862
+ end
863
+
864
+ # Uninstall gem with some default options.
865
+ def self.uninstall(name, options = {})
866
+ defaults = { :ignore => true, :executables => true }
867
+ uninstall_gem(name, defaults.merge(options))
868
+ end
869
+
870
+ end
871
+
872
+ class Tasks < Thor
873
+
874
+ include MerbThorHelper
875
+
876
+ # Show merb.thor version information
877
+ #
878
+ # merb:tasks:version # show the current version info
879
+ # merb:tasks:version --info # show extended version info
880
+
881
+ desc 'version', 'Show verion info'
882
+ method_options "--info" => :boolean
883
+ def version
884
+ message "Currently installed merb.thor version: #{MERB_THOR_VERSION}"
885
+ if options[:version]
886
+ self.options = { :"dry-run" => true }
887
+ self.update # run update task with dry-run enabled
888
+ end
889
+ end
890
+
891
+ # Update merb.thor tasks from remotely available version
892
+ #
893
+ # merb:tasks:update # update merb.thor
894
+ # merb:tasks:update --force # force-update merb.thor
895
+ # merb:tasks:update --dry-run # show version info only
896
+
897
+ desc 'update [URL]', 'Fetch the latest merb.thor and install it locally'
898
+ method_options "--dry-run" => :boolean, "--force" => :boolean
899
+ def update(url = 'http://merbivore.com/merb.thor')
900
+ require 'open-uri'
901
+ require 'rubygems/version'
902
+ remote_file = open(url)
903
+ code = remote_file.read
904
+
905
+ # Extract version information from the source code
906
+ if version = code[/^MERB_THOR_VERSION\s?=\s?('|")([\.\d]+)('|")/,2]
907
+ # borrow version comparison from rubygems' Version class
908
+ current_version = ::Gem::Version.new(MERB_THOR_VERSION)
909
+ remote_version = ::Gem::Version.new(version)
910
+
911
+ if current_version >= remote_version
912
+ puts "currently installed: #{current_version}"
913
+ if current_version != remote_version
914
+ puts "available version: #{remote_version}"
915
+ end
916
+ info "No update of merb.thor necessary#{options[:force] ? ' (forced)' : ''}"
917
+ proceed = options[:force]
918
+ elsif current_version < remote_version
919
+ puts "currently installed: #{current_version}"
920
+ puts "available version: #{remote_version}"
921
+ proceed = true
922
+ end
923
+
924
+ if proceed && !dry_run?
925
+ File.open(File.join(__FILE__), 'w') do |f|
926
+ f.write(code)
927
+ end
928
+ success "Installed the latest merb.thor (v#{version})"
929
+ end
930
+ else
931
+ raise "invalid source-code data"
932
+ end
933
+ rescue OpenURI::HTTPError
934
+ error "Error opening #{url}"
935
+ rescue => e
936
+ error "An error occurred (#{e.message})"
937
+ end
938
+
939
+ end
940
+
941
+ #### MORE LOW-LEVEL TASKS ####
942
+
943
+ class Source < Thor
944
+
945
+ group 'core'
946
+
947
+ include MerbThorHelper
948
+ extend GemManagement
949
+
950
+ attr_accessor :system, :local, :missing
951
+
952
+ global_method_options = {
953
+ "--merb-root" => :optional, # the directory to operate on
954
+ "--ignore-dependencies" => :boolean, # don't install sub-dependencies
955
+ "--sources" => :optional # a yml config to grab sources from
956
+ }
957
+
958
+ method_options global_method_options
959
+ def initialize(*args); super; end
960
+
961
+ # List source repositories, of either local or known sources.
962
+ #
963
+ # Examples:
964
+ #
965
+ # merb:source:list # list all local sources
966
+ # merb:source:list available # list all known sources
967
+
968
+ desc 'list [local|available]', 'Show git source repositories'
969
+ def list(mode = 'local')
970
+ if mode == 'available'
971
+ message 'Available source repositories:'
972
+ repos = self.class.repos(options[:sources])
973
+ repos.keys.sort.each { |name| puts "- #{name}: #{repos[name]}" }
974
+ elsif mode == 'local'
975
+ message 'Current source repositories:'
976
+ Dir[File.join(source_dir, '*')].each do |src|
977
+ next unless File.directory?(src)
978
+ src_name = File.basename(src)
979
+ unless (repos = source_manager.existing_repos(src_name)).empty?
980
+ puts "#{src_name}"
981
+ repos.keys.sort.each { |b| puts "- #{b}: #{repos[b]}" }
982
+ end
983
+ end
984
+ else
985
+ error "Unknown listing: #{mode}"
986
+ end
987
+ end
988
+
989
+ # Install the specified gems.
990
+ #
991
+ # All arguments should be names of gems to install.
992
+ #
993
+ # When :force => true then any existing versions of the gems to be installed
994
+ # will be uninstalled first. It's important to note that so-called meta-gems
995
+ # or gems that exactly match a set of Merb::Stack.components will have their
996
+ # sub-gems uninstalled too. For example, uninstalling merb-more will install
997
+ # all contained gems: merb-action-args, merb-assets, merb-gen, ...
998
+ #
999
+ # Examples:
1000
+ #
1001
+ # merb:source:install merb-core merb-slices # install all specified gems
1002
+ # merb:source:install merb-core --force # uninstall then subsequently install the gem
1003
+ # merb:source:install merb-core --wipe # clear repo then install the gem
1004
+
1005
+ desc 'install GEM_NAME [GEM_NAME, ...]', 'Install a gem from git source/edge'
1006
+ method_options "--dry-run" => :boolean,
1007
+ "--force" => :boolean,
1008
+ "--wipe" => :boolean
1009
+ def install(*names)
1010
+ use_edge_gem_server
1011
+ # uninstall existing gems of the ones we're going to install
1012
+ uninstall(*names) if options[:force] || options[:wipe]
1013
+
1014
+ # We want dependencies instead of just names
1015
+ deps = names.map { |n| ::Gem::Dependency.new(n, ::Gem::Requirement.default) }
1016
+
1017
+ # Selectively update repositories for the matching dependencies
1018
+ update_dependency_repositories(deps) unless dry_run?
1019
+
1020
+ current_gem = nil
1021
+ deps.each do |dependency|
1022
+ current_gem = dependency.name
1023
+ if dry_run?
1024
+ note "Installing #{current_gem} from source..."
1025
+ else
1026
+ message "Installing #{current_gem} from source..."
1027
+ puts "This may take a while..."
1028
+ unless install_dependency_from_source(dependency)
1029
+ raise "gem source not found"
1030
+ end
1031
+ end
1032
+ end
1033
+ rescue => e
1034
+ error "Failed to install #{current_gem ? current_gem : 'gem'} (#{e.message})"
1035
+ end
1036
+
1037
+ # Uninstall the specified gems.
1038
+ #
1039
+ # By default all specified gems are uninstalled. It's important to note that
1040
+ # so-called meta-gems or gems that match a set of Merb::Stack.components will
1041
+ # have their sub-gems uninstalled too. For example, uninstalling merb-more
1042
+ # will install all contained gems: merb-action-args, merb-assets, ...
1043
+ #
1044
+ # Existing dependencies will be clobbered; when :force => true then all gems
1045
+ # will be cleared, otherwise only existing local dependencies of the
1046
+ # matching component set will be removed. Additionally when :wipe => true,
1047
+ # the matching git repositories will be removed from the source directory.
1048
+ #
1049
+ # Examples:
1050
+ #
1051
+ # merb:source:uninstall merb-core merb-slices # uninstall all specified gems
1052
+ # merb:source:uninstall merb-core --wipe # force-uninstall a gem and clear repo
1053
+
1054
+ desc 'uninstall GEM_NAME [GEM_NAME, ...]', 'Unstall a gem (specify --force to remove the repo)'
1055
+ method_options "--version" => :optional, "--dry-run" => :boolean, "--wipe" => :boolean
1056
+ def uninstall(*names)
1057
+ # Remove the repos that contain the gem
1058
+ if options[:wipe]
1059
+ extract_repositories(names).each do |(name, url)|
1060
+ if File.directory?(src = File.join(source_dir, name))
1061
+ if dry_run?
1062
+ note "Removing #{src}..."
1063
+ else
1064
+ info "Removing #{src}..."
1065
+ FileUtils.rm_rf(src)
1066
+ end
1067
+ end
1068
+ end
1069
+ end
1070
+
1071
+ # Use the Merb::Gem#uninstall task to handle this
1072
+ gem_tasks = Merb::Gem.new
1073
+ gem_tasks.options = options
1074
+ gem_tasks.uninstall(*names)
1075
+ end
1076
+
1077
+ # Update the specified source repositories.
1078
+ #
1079
+ # The arguments can be actual repository names (from Merb::Source.repos)
1080
+ # or names of known merb stack gems. If the repo doesn't exist already,
1081
+ # it will be created and cloned.
1082
+ #
1083
+ # merb:source:pull merb-core # update source of specified gem
1084
+ # merb:source:pull merb-slices # implicitly updates merb-more
1085
+
1086
+ desc 'pull REPO_NAME [GEM_NAME, ...]', 'Update git source repository from edge'
1087
+ def pull(*names)
1088
+ repos = extract_repositories(names)
1089
+ update_repositories(repos)
1090
+ unless repos.empty?
1091
+ message "Updated the following repositories:"
1092
+ repos.each { |name, url| puts "- #{name}: #{url}" }
1093
+ else
1094
+ warning "No repositories found to update!"
1095
+ end
1096
+ end
1097
+
1098
+ # Clone a git repository into ./src.
1099
+
1100
+ # The repository can be a direct git url or a known -named- repository.
1101
+ #
1102
+ # Examples:
1103
+ #
1104
+ # merb:source:clone merb-core
1105
+ # merb:source:clone dm-core awesome-repo
1106
+ # merb:source:clone dm-core --sources ./path/to/sources.yml
1107
+ # merb:source:clone git://github.com/sam/dm-core.git
1108
+
1109
+ desc 'clone (REPO_NAME|URL) [DIR_NAME]', 'Clone git source repository by name or url'
1110
+ def clone(repository, name = nil)
1111
+ if repository =~ /^git:\/\//
1112
+ repository_url = repository
1113
+ repository_name = File.basename(repository_url, '.git')
1114
+ elsif url = Merb::Source.repo(repository, options[:sources])
1115
+ repository_url = url
1116
+ repository_name = repository
1117
+ end
1118
+ source_manager.clone(name || repository_name, repository_url)
1119
+ end
1120
+
1121
+ # Git repository sources - pass source_config option to load a yaml
1122
+ # configuration file - defaults to ./config/git-sources.yml and
1123
+ # ~/.merb/git-sources.yml - which you need to create yourself.
1124
+ #
1125
+ # Example of contents:
1126
+ #
1127
+ # merb-core: git://github.com/myfork/merb-core.git
1128
+ # merb-more: git://github.com/myfork/merb-more.git
1129
+
1130
+ def self.repos(source_config = nil)
1131
+ source_config ||= begin
1132
+ local_config = File.join(Dir.pwd, 'config', 'git-sources.yml')
1133
+ user_config = File.join(ENV["HOME"] || ENV["APPDATA"], '.merb', 'git-sources.yml')
1134
+ File.exists?(local_config) ? local_config : user_config
1135
+ end
1136
+ if source_config && File.exists?(source_config)
1137
+ default_repos.merge(YAML.load(File.read(source_config)))
1138
+ else
1139
+ default_repos
1140
+ end
1141
+ end
1142
+
1143
+ def self.repo(name, source_config = nil)
1144
+ self.repos(source_config)[name]
1145
+ end
1146
+
1147
+ # Default Git repositories
1148
+ def self.default_repos
1149
+ @_default_repos ||= {
1150
+ 'merb' => "git://github.com/wycats/merb.git",
1151
+ 'merb-plugins' => "git://github.com/wycats/merb-plugins.git",
1152
+ 'extlib' => "git://github.com/sam/extlib.git",
1153
+ 'dm-core' => "git://github.com/sam/dm-core.git",
1154
+ 'dm-more' => "git://github.com/sam/dm-more.git",
1155
+ 'sequel' => "git://github.com/wayneeseguin/sequel.git",
1156
+ 'do' => "git://github.com/sam/do.git",
1157
+ 'thor' => "git://github.com/wycats/thor.git",
1158
+ 'rake' => "git://github.com/jimweirich/rake.git"
1159
+ }
1160
+ end
1161
+
1162
+ end
1163
+
1164
+ class Dependencies < Thor
1165
+
1166
+ group 'core'
1167
+
1168
+ # The Dependencies tasks will install dependencies based on actual application
1169
+ # dependencies. For this, the application is queried for any dependencies.
1170
+ # All operations will be performed within this context.
1171
+
1172
+ attr_accessor :system, :local, :missing, :extract_dependencies
1173
+
1174
+ include MerbThorHelper
1175
+
1176
+ global_method_options = {
1177
+ "--merb-root" => :optional, # the directory to operate on
1178
+ "--ignore-dependencies" => :boolean, # ignore sub-dependencies
1179
+ "--stack" => :boolean, # gather only stack dependencies
1180
+ "--no-stack" => :boolean, # gather only non-stack dependencies
1181
+ "--extract" => :boolean, # gather dependencies from the app itself
1182
+ "--config-file" => :optional, # gather from the specified yaml config
1183
+ "--version" => :optional # gather specific version of framework
1184
+ }
1185
+
1186
+ method_options global_method_options
1187
+ def initialize(*args); super; end
1188
+
1189
+ # List application dependencies.
1190
+ #
1191
+ # By default all dependencies are listed, partitioned into system, local and
1192
+ # currently missing dependencies. The first argument allows you to filter
1193
+ # on any of the partitionings. A second argument can be used to filter on
1194
+ # a set of known components, like all merb-more gems for example.
1195
+ #
1196
+ # Examples:
1197
+ #
1198
+ # merb:dependencies:list # list all dependencies - the default
1199
+ # merb:dependencies:list local # list only local gems
1200
+ # merb:dependencies:list all merb-more # list only merb-more related dependencies
1201
+ # merb:dependencies:list --stack # list framework dependencies
1202
+ # merb:dependencies:list --no-stack # list 3rd party dependencies
1203
+ # merb:dependencies:list --extract # list dependencies by extracting them
1204
+ # merb:dependencies:list --config-file file.yml # list from the specified config file
1205
+
1206
+ desc 'list [all|local|system|missing] [comp]', 'Show application dependencies'
1207
+ def list(filter = 'all', comp = nil)
1208
+ deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies
1209
+ self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
1210
+ case filter
1211
+ when 'all'
1212
+ message 'Installed system gem dependencies:'
1213
+ display_gemspecs(system)
1214
+ message 'Installed local gem dependencies:'
1215
+ display_gemspecs(local)
1216
+ unless missing.empty?
1217
+ error 'Missing gem dependencies:'
1218
+ display_dependencies(missing)
1219
+ end
1220
+ when 'system'
1221
+ message 'Installed system gem dependencies:'
1222
+ display_gemspecs(system)
1223
+ when 'local'
1224
+ message 'Installed local gem dependencies:'
1225
+ display_gemspecs(local)
1226
+ when 'missing'
1227
+ error 'Missing gem dependencies:'
1228
+ display_dependencies(missing)
1229
+ else
1230
+ warning "Invalid listing filter '#{filter}'"
1231
+ end
1232
+ if missing.size > 0
1233
+ info "Some dependencies are currently missing!"
1234
+ elsif local.size == deps.size
1235
+ info "All dependencies have been bundled with the application."
1236
+ elsif local.size > system.size
1237
+ info "Most dependencies have been bundled with the application."
1238
+ elsif system.size > 0 && local.size > 0
1239
+ info "Some dependencies have been bundled with the application."
1240
+ elsif local.empty? && system.size == deps.size
1241
+ info "All dependencies are available on the system."
1242
+ end
1243
+ end
1244
+
1245
+ # Install application dependencies.
1246
+ #
1247
+ # By default all required dependencies are installed. The first argument
1248
+ # specifies which strategy to use: stable or edge. A second argument can be
1249
+ # used to filter on a set of known components.
1250
+ #
1251
+ # Existing dependencies will be clobbered; when :force => true then all gems
1252
+ # will be cleared first, otherwise only existing local dependencies of the
1253
+ # gems to be installed will be removed.
1254
+ #
1255
+ # Examples:
1256
+ #
1257
+ # merb:dependencies:install # install all dependencies using stable strategy
1258
+ # merb:dependencies:install stable --version 0.9.8 # install a specific version of the framework
1259
+ # merb:dependencies:install stable missing # install currently missing gems locally
1260
+ # merb:dependencies:install stable merb-more # install only merb-more related dependencies
1261
+ # merb:dependencies:install stable --stack # install framework dependencies
1262
+ # merb:dependencies:install stable --no-stack # install 3rd party dependencies
1263
+ # merb:dependencies:install stable --extract # extract dependencies from the actual app
1264
+ # merb:dependencies:install stable --config-file file.yml # read from the specified config file
1265
+ #
1266
+ # In addition to the options above, edge install uses the following:
1267
+ #
1268
+ # merb:dependencies:install edge # install all dependencies using edge strategy
1269
+ # merb:dependencies:install edge --sources file.yml # install edge from the specified git sources config
1270
+
1271
+ desc 'install [stable|edge] [comp]', 'Install application dependencies'
1272
+ method_options "--sources" => :optional, # only for edge strategy
1273
+ "--local" => :boolean, # force local install
1274
+ "--dry-run" => :boolean,
1275
+ "--force" => :boolean
1276
+ def install(strategy = 'stable', comp = nil)
1277
+ if strategy?(strategy)
1278
+ # Force local dependencies by creating ./gems before proceeding
1279
+ create_if_missing(default_gem_dir) if options[:local]
1280
+
1281
+ where = gem_dir ? 'locally' : 'system-wide'
1282
+
1283
+ # When comp == 'missing' then filter on missing dependencies
1284
+ if only_missing = comp == 'missing'
1285
+ message "Preparing to install missing gems #{where} using #{strategy} strategy..."
1286
+ comp = nil
1287
+ clobber = false
1288
+ else
1289
+ message "Preparing to install #{where} using #{strategy} strategy..."
1290
+ clobber = true
1291
+ end
1292
+
1293
+ # If comp given, filter on known stack components
1294
+ deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies
1295
+ self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
1296
+
1297
+ # Only install currently missing gems (for comp == missing)
1298
+ if only_missing
1299
+ deps.reject! { |dep| not missing.include?(dep) }
1300
+ end
1301
+
1302
+ if deps.empty?
1303
+ warning "No dependencies to install..."
1304
+ else
1305
+ puts "#{deps.length} dependencies to install..."
1306
+ puts "This may take a while..."
1307
+ install_dependencies(strategy, deps, clobber)
1308
+ end
1309
+
1310
+ # Show current dependency info now that we're done
1311
+ puts # Seperate output
1312
+ list('local', comp)
1313
+ else
1314
+ warning "Invalid install strategy '#{strategy}'"
1315
+ puts
1316
+ message "Please choose one of the following installation strategies: stable or edge:"
1317
+ puts "$ thor merb:dependencies:install stable"
1318
+ puts "$ thor merb:dependencies:install edge"
1319
+ end
1320
+ end
1321
+
1322
+ # Uninstall application dependencies.
1323
+ #
1324
+ # By default all required dependencies are installed. An optional argument
1325
+ # can be used to filter on a set of known components.
1326
+ #
1327
+ # Existing dependencies will be clobbered; when :force => true then all gems
1328
+ # will be cleared, otherwise only existing local dependencies of the
1329
+ # matching component set will be removed.
1330
+ #
1331
+ # Examples:
1332
+ #
1333
+ # merb:dependencies:uninstall # uninstall all dependencies - the default
1334
+ # merb:dependencies:uninstall merb-more # uninstall merb-more related gems locally
1335
+
1336
+ desc 'uninstall [comp]', 'Uninstall application dependencies'
1337
+ method_options "--dry-run" => :boolean, "--force" => :boolean
1338
+ def uninstall(comp = nil)
1339
+ # If comp given, filter on known stack components
1340
+ deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies
1341
+ self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
1342
+ # Clobber existing local dependencies - based on self.local
1343
+ clobber_dependencies!
1344
+ end
1345
+
1346
+ # Recreate all gems from gems/cache on the current platform.
1347
+ #
1348
+ # Note: use merb:gem:redeploy instead
1349
+
1350
+ desc 'redeploy', 'Recreate all gems on the current platform'
1351
+ method_options "--dry-run" => :boolean, "--force" => :boolean
1352
+ def redeploy
1353
+ warning 'Warning: merb:dependencies:redeploy has been deprecated - use merb:gem:redeploy instead'
1354
+ gem = Merb::Gem.new
1355
+ gem.options = options
1356
+ gem.redeploy
1357
+ end
1358
+
1359
+ # Create a dependencies configuration file.
1360
+ #
1361
+ # A configuration yaml file will be created from the extracted application
1362
+ # dependencies. The format of the configuration is as follows:
1363
+ #
1364
+ # ---
1365
+ # - merb-core (= 0.9.8, runtime)
1366
+ # - merb-slices (= 0.9.8, runtime)
1367
+ #
1368
+ # This format is exactly the same as Gem::Dependency#to_s returns.
1369
+ #
1370
+ # Examples:
1371
+ #
1372
+ # merb:dependencies:configure --force # overwrite the default config file
1373
+ # merb:dependencies:configure --version 0.9.8 # configure specific framework version
1374
+ # merb:dependencies:configure --config-file file.yml # write to the specified config file
1375
+
1376
+ desc 'configure [comp]', 'Create a dependencies config file'
1377
+ method_options "--dry-run" => :boolean, "--force" => :boolean, "--versions" => :boolean
1378
+ def configure(comp = nil)
1379
+ self.extract_dependencies = true # of course we need to consult the app itself
1380
+ # If comp given, filter on known stack components
1381
+ deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies
1382
+
1383
+ # If --versions is set, update the version_requirements with the actual version available
1384
+ if options[:versions]
1385
+ specs = local_gemspecs
1386
+ deps.each do |dep|
1387
+ if spec = specs.find { |s| s.name == dep.name }
1388
+ dep.version_requirements = ::Gem::Requirement.create(spec.version)
1389
+ end
1390
+ end
1391
+ end
1392
+
1393
+ config = YAML.dump(deps.map { |d| d.to_s })
1394
+ puts "#{config}\n"
1395
+ if File.exists?(config_file) && !options[:force]
1396
+ error "File already exists! Use --force to overwrite."
1397
+ else
1398
+ if dry_run?
1399
+ note "Written #{config_file}"
1400
+ else
1401
+ FileUtils.mkdir_p(config_dir) unless File.directory?(config_dir)
1402
+ File.open(config_file, 'w') { |f| f.write config }
1403
+ success "Written #{config_file}"
1404
+ end
1405
+ end
1406
+ rescue
1407
+ error "Failed to write to #{config_file}"
1408
+ end
1409
+
1410
+ ### Helper Methods
1411
+
1412
+ def strategy?(strategy)
1413
+ if self.respond_to?(method = :"#{strategy}_strategy", true)
1414
+ method
1415
+ end
1416
+ end
1417
+
1418
+ def install_dependencies(strategy, deps, clobber = true)
1419
+ if method = strategy?(strategy)
1420
+ # Clobber existing local dependencies
1421
+ clobber_dependencies! if clobber
1422
+
1423
+ # Run the chosen strategy - collect files installed from stable gems
1424
+ installed_from_stable = send(method, deps).map { |d| d.name }
1425
+
1426
+ unless dry_run?
1427
+ # Sleep a bit otherwise the following steps won't see the new files
1428
+ sleep(deps.length) if deps.length > 0 && deps.length <= 10
1429
+
1430
+ # Leave a file to denote the strategy that has been used for this dependency
1431
+ self.local.each do |spec|
1432
+ next unless File.directory?(spec.full_gem_path)
1433
+ unless installed_from_stable.include?(spec.name)
1434
+ FileUtils.touch(File.join(spec.full_gem_path, "#{strategy}.strategy"))
1435
+ else
1436
+ FileUtils.touch(File.join(spec.full_gem_path, "stable.strategy"))
1437
+ end
1438
+ end
1439
+ end
1440
+ return true
1441
+ end
1442
+ false
1443
+ end
1444
+
1445
+ def dependencies
1446
+ if extract_dependencies?
1447
+ # Extract dependencies from the current application
1448
+ deps = Merb::Stack.core_dependencies(gem_dir, ignore_dependencies?)
1449
+ deps += Merb::Dependencies.extract_dependencies(working_dir)
1450
+ else
1451
+ # Use preconfigured dependencies from yaml file
1452
+ deps = config_dependencies
1453
+ end
1454
+
1455
+ stack_components = Merb::Stack.components
1456
+
1457
+ if options[:stack]
1458
+ # Limit to stack components only
1459
+ deps.reject! { |dep| not stack_components.include?(dep.name) }
1460
+ elsif options[:"no-stack"]
1461
+ # Limit to non-stack components
1462
+ deps.reject! { |dep| stack_components.include?(dep.name) }
1463
+ end
1464
+
1465
+ if options[:version]
1466
+ version_req = ::Gem::Requirement.create("= #{options[:version]}")
1467
+ elsif core = deps.find { |d| d.name == 'merb-core' }
1468
+ version_req = core.version_requirements
1469
+ end
1470
+
1471
+ if version_req
1472
+ # Handle specific version requirement for framework components
1473
+ framework_components = Merb::Stack.framework_components
1474
+ deps.each do |dep|
1475
+ if framework_components.include?(dep.name)
1476
+ dep.version_requirements = version_req
1477
+ end
1478
+ end
1479
+ end
1480
+
1481
+ deps
1482
+ end
1483
+
1484
+ def config_dependencies
1485
+ if File.exists?(config_file)
1486
+ self.class.parse_dependencies_yaml(File.read(config_file))
1487
+ else
1488
+ warning "No dependencies.yml file found at: #{config_file}"
1489
+ []
1490
+ end
1491
+ end
1492
+
1493
+ def extract_dependencies?
1494
+ options[:extract] || extract_dependencies
1495
+ end
1496
+
1497
+ def config_file
1498
+ @config_file ||= begin
1499
+ options[:"config-file"] || File.join(working_dir, 'config', 'dependencies.yml')
1500
+ end
1501
+ end
1502
+
1503
+ def config_dir
1504
+ File.dirname(config_file)
1505
+ end
1506
+
1507
+ ### Strategy handlers
1508
+
1509
+ private
1510
+
1511
+ def stable_strategy(deps)
1512
+ installed_from_rubygems = []
1513
+ if core = deps.find { |d| d.name == 'merb-core' }
1514
+ if dry_run?
1515
+ note "Installing #{core.name}..."
1516
+ else
1517
+ if install_dependency(core)
1518
+ installed_from_rubygems << core
1519
+ else
1520
+ msg = "Try specifying a lower version of merb-core with --version"
1521
+ if version_no = core.version_requirements.to_s[/([\.\d]+)$/, 1]
1522
+ num = "%03d" % (version_no.gsub('.', '').to_i - 1)
1523
+ puts "The required version (#{version_no}) probably isn't available as a stable rubygem yet."
1524
+ info "#{msg} #{num.split(//).join('.')}"
1525
+ else
1526
+ puts "The required version probably isn't available as a stable rubygem yet."
1527
+ info msg
1528
+ end
1529
+ end
1530
+ end
1531
+ end
1532
+
1533
+ deps.each do |dependency|
1534
+ next if dependency.name == 'merb-core'
1535
+ if dry_run?
1536
+ note "Installing #{dependency.name}..."
1537
+ else
1538
+ install_dependency(dependency)
1539
+ installed_from_rubygems << dependency
1540
+ end
1541
+ end
1542
+ installed_from_rubygems
1543
+ end
1544
+
1545
+ def edge_strategy(deps)
1546
+ use_edge_gem_server
1547
+ installed_from_rubygems = []
1548
+
1549
+ # Selectively update repositories for the matching dependencies
1550
+ update_dependency_repositories(deps) unless dry_run?
1551
+
1552
+ if core = deps.find { |d| d.name == 'merb-core' }
1553
+ if dry_run?
1554
+ note "Installing #{core.name}..."
1555
+ else
1556
+ if install_dependency_from_source(core)
1557
+ elsif install_dependency(core)
1558
+ info "Installed #{core.name} from rubygems..."
1559
+ installed_from_rubygems << core
1560
+ end
1561
+ end
1562
+ end
1563
+
1564
+ deps.each do |dependency|
1565
+ next if dependency.name == 'merb-core'
1566
+ if dry_run?
1567
+ note "Installing #{dependency.name}..."
1568
+ else
1569
+ if install_dependency_from_source(dependency)
1570
+ elsif install_dependency(dependency)
1571
+ info "Installed #{dependency.name} from rubygems..."
1572
+ installed_from_rubygems << dependency
1573
+ end
1574
+ end
1575
+ end
1576
+
1577
+ installed_from_rubygems
1578
+ end
1579
+
1580
+ ### Class Methods
1581
+
1582
+ public
1583
+
1584
+ def self.list(filter = 'all', comp = nil, options = {})
1585
+ instance = Merb::Dependencies.new
1586
+ instance.options = options
1587
+ instance.list(filter, comp)
1588
+ end
1589
+
1590
+ # Extract application dependencies by querying the app directly.
1591
+ def self.extract_dependencies(merb_root)
1592
+ require 'merb-core'
1593
+ if !@_merb_loaded || Merb.root != merb_root
1594
+ Merb.start_environment(
1595
+ :log_level => :fatal,
1596
+ :testing => true,
1597
+ :adapter => 'runner',
1598
+ :environment => ENV['MERB_ENV'] || 'development',
1599
+ :merb_root => merb_root
1600
+ )
1601
+ @_merb_loaded = true
1602
+ end
1603
+ Merb::BootLoader::Dependencies.dependencies
1604
+ rescue StandardError => e
1605
+ error "Couldn't extract dependencies from application!"
1606
+ error e.message
1607
+ puts "Make sure you're executing the task from your app (--merb-root)"
1608
+ return []
1609
+ rescue SystemExit
1610
+ error "Couldn't extract dependencies from application!"
1611
+ error "application failed to run"
1612
+ puts "Please check if your application runs using 'merb'; for example,"
1613
+ puts "look for any gem version mismatches in dependencies.rb"
1614
+ return []
1615
+ end
1616
+
1617
+ # Parse the basic YAML config data, and process Gem::Dependency output.
1618
+ # Formatting example: merb_helpers (>= 0.9.8, runtime)
1619
+ def self.parse_dependencies_yaml(yaml)
1620
+ dependencies = []
1621
+ entries = YAML.load(yaml) rescue []
1622
+ entries.each do |entry|
1623
+ if matches = entry.match(/^(\S+) \(([^,]+)?, ([^\)]+)\)/)
1624
+ name, version_req, type = matches.captures
1625
+ dependencies << ::Gem::Dependency.new(name, version_req, type.to_sym)
1626
+ else
1627
+ error "Invalid entry: #{entry}"
1628
+ end
1629
+ end
1630
+ dependencies
1631
+ end
1632
+
1633
+ end
1634
+
1635
+ class Stack < Thor
1636
+
1637
+ group 'core'
1638
+
1639
+ # The Stack tasks will install dependencies based on known sets of gems,
1640
+ # regardless of actual application dependency settings.
1641
+
1642
+ DM_STACK = %w[
1643
+ extlib
1644
+ data_objects
1645
+ dm-core
1646
+ dm-aggregates
1647
+ dm-migrations
1648
+ dm-timestamps
1649
+ dm-types
1650
+ dm-validations
1651
+ merb_datamapper
1652
+ ]
1653
+
1654
+ MERB_STACK = %w[
1655
+ extlib
1656
+ merb-core
1657
+ merb-action-args
1658
+ merb-assets
1659
+ merb-cache
1660
+ merb-helpers
1661
+ merb-mailer
1662
+ merb-slices
1663
+ merb-auth
1664
+ merb-auth-core
1665
+ merb-auth-more
1666
+ merb-auth-slice-password
1667
+ merb-param-protection
1668
+ merb-exceptions
1669
+ ] + DM_STACK
1670
+
1671
+ MERB_BASICS = %w[
1672
+ extlib
1673
+ merb-core
1674
+ merb-action-args
1675
+ merb-assets
1676
+ merb-cache
1677
+ merb-helpers
1678
+ merb-mailer
1679
+ merb-slices
1680
+ ]
1681
+
1682
+ # The following sets are meant for repository lookup; unlike the sets above
1683
+ # these correspond to specific git repository items.
1684
+
1685
+ MERB_MORE = %w[
1686
+ merb-action-args
1687
+ merb-assets
1688
+ merb-auth
1689
+ merb-auth-core
1690
+ merb-auth-more
1691
+ merb-auth-slice-password
1692
+ merb-cache
1693
+ merb-exceptions
1694
+ merb-gen
1695
+ merb-haml
1696
+ merb-helpers
1697
+ merb-mailer
1698
+ merb-param-protection
1699
+ merb-slices
1700
+ merb_datamapper
1701
+ ]
1702
+
1703
+ MERB_PLUGINS = %w[
1704
+ merb_activerecord
1705
+ merb_builder
1706
+ merb_jquery
1707
+ merb_laszlo
1708
+ merb_parts
1709
+ merb_screw_unit
1710
+ merb_sequel
1711
+ merb_stories
1712
+ merb_test_unit
1713
+ ]
1714
+
1715
+ DM_MORE = %w[
1716
+ dm-adjust
1717
+ dm-aggregates
1718
+ dm-ar-finders
1719
+ dm-cli
1720
+ dm-constraints
1721
+ dm-is-example
1722
+ dm-is-list
1723
+ dm-is-nested_set
1724
+ dm-is-remixable
1725
+ dm-is-searchable
1726
+ dm-is-state_machine
1727
+ dm-is-tree
1728
+ dm-is-versioned
1729
+ dm-migrations
1730
+ dm-observer
1731
+ dm-querizer
1732
+ dm-serializer
1733
+ dm-shorthand
1734
+ dm-sweatshop
1735
+ dm-tags
1736
+ dm-timestamps
1737
+ dm-types
1738
+ dm-validations
1739
+
1740
+ dm-couchdb-adapter
1741
+ dm-ferret-adapter
1742
+ dm-rest-adapter
1743
+ ]
1744
+
1745
+ DATA_OBJECTS = %w[
1746
+ data_objects
1747
+ do_derby do_hsqldb
1748
+ do_jdbc
1749
+ do_mysql
1750
+ do_postgres
1751
+ do_sqlite3
1752
+ ]
1753
+
1754
+ attr_accessor :system, :local, :missing
1755
+
1756
+ include MerbThorHelper
1757
+
1758
+ global_method_options = {
1759
+ "--merb-root" => :optional, # the directory to operate on
1760
+ "--ignore-dependencies" => :boolean, # skip sub-dependencies
1761
+ "--version" => :optional # gather specific version of framework
1762
+ }
1763
+
1764
+ method_options global_method_options
1765
+ def initialize(*args); super; end
1766
+
1767
+ # List components and their dependencies.
1768
+ #
1769
+ # Examples:
1770
+ #
1771
+ # merb:stack:list # list all standard stack components
1772
+ # merb:stack:list all # list all component sets
1773
+ # merb:stack:list merb-more # list all dependencies of merb-more
1774
+
1775
+ desc 'list [all|comp]', 'List available components (optionally filtered, defaults to merb stack)'
1776
+ def list(comp = 'stack')
1777
+ if comp == 'all'
1778
+ Merb::Stack.component_sets.keys.sort.each do |comp|
1779
+ unless (components = Merb::Stack.component_sets[comp]).empty?
1780
+ message "Dependencies for '#{comp}' set:"
1781
+ components.each { |c| puts "- #{c}" }
1782
+ end
1783
+ end
1784
+ else
1785
+ message "Dependencies for '#{comp}' set:"
1786
+ Merb::Stack.components(comp).each { |c| puts "- #{c}" }
1787
+ end
1788
+ end
1789
+
1790
+ # Install stack components or individual gems - from stable rubygems by default.
1791
+ #
1792
+ # See also: Merb::Dependencies#install and Merb::Dependencies#install_dependencies
1793
+ #
1794
+ # Examples:
1795
+ #
1796
+ # merb:stack:install # install the default merb stack
1797
+ # merb:stack:install basics # install a basic set of dependencies
1798
+ # merb:stack:install merb-core # install merb-core from stable
1799
+ # merb:stack:install merb-more --edge # install merb-core from edge
1800
+ # merb:stack:install merb-core thor merb-slices # install the specified gems
1801
+
1802
+ desc 'install [COMP, ...]', 'Install stack components'
1803
+ method_options "--edge" => :boolean,
1804
+ "--sources" => :optional,
1805
+ "--force" => :boolean,
1806
+ "--dry-run" => :boolean,
1807
+ "--strategy" => :optional
1808
+ def install(*comps)
1809
+ use_edge_gem_server if options[:edge]
1810
+ mngr = self.dependency_manager
1811
+ deps = gather_dependencies(comps)
1812
+ mngr.system, mngr.local, mngr.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
1813
+ mngr.install_dependencies(strategy, deps)
1814
+ end
1815
+
1816
+ # Uninstall stack components or individual gems.
1817
+ #
1818
+ # See also: Merb::Dependencies#uninstall
1819
+ #
1820
+ # Examples:
1821
+ #
1822
+ # merb:stack:uninstall # uninstall the default merb stack
1823
+ # merb:stack:uninstall merb-more # uninstall merb-more
1824
+ # merb:stack:uninstall merb-core thor merb-slices # uninstall the specified gems
1825
+
1826
+ desc 'uninstall [COMP, ...]', 'Uninstall stack components'
1827
+ method_options "--dry-run" => :boolean, "--force" => :boolean
1828
+ def uninstall(*comps)
1829
+ deps = gather_dependencies(comps)
1830
+ self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
1831
+ # Clobber existing local dependencies - based on self.local
1832
+ clobber_dependencies!
1833
+ end
1834
+
1835
+ # Install or uninstall minigems from the system.
1836
+ #
1837
+ # Due to the specific nature of MiniGems it can only be installed system-wide.
1838
+ #
1839
+ # Examples:
1840
+ #
1841
+ # merb:stack:minigems install # install minigems
1842
+ # merb:stack:minigems uninstall # uninstall minigems
1843
+
1844
+ desc 'minigems (install|uninstall)', 'Install or uninstall minigems (needs sudo privileges)'
1845
+ def minigems(action)
1846
+ case action
1847
+ when 'install'
1848
+ Kernel.system "#{sudo} thor merb:stack:install_minigems"
1849
+ when 'uninstall'
1850
+ Kernel.system "#{sudo} thor merb:stack:uninstall_minigems"
1851
+ else
1852
+ error "Invalid command: merb:stack:minigems #{action}"
1853
+ end
1854
+ end
1855
+
1856
+ # hidden minigems install task
1857
+ def install_minigems
1858
+ message "Installing MiniGems"
1859
+ mngr = self.dependency_manager
1860
+ deps = gather_dependencies('minigems')
1861
+ mngr.system, mngr.local, mngr.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
1862
+ mngr.force_gem_dir = ::Gem.dir
1863
+ mngr.install_dependencies(strategy, deps)
1864
+ Kernel.system "#{sudo} minigem install"
1865
+ end
1866
+
1867
+ # hidden minigems uninstall task
1868
+ def uninstall_minigems
1869
+ message "Uninstalling MiniGems"
1870
+ Kernel.system "#{sudo} minigem uninstall"
1871
+ deps = gather_dependencies('minigems')
1872
+ self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
1873
+ # Clobber existing local dependencies - based on self.local
1874
+ clobber_dependencies!
1875
+ end
1876
+
1877
+ protected
1878
+
1879
+ def gather_dependencies(comps = [])
1880
+ if comps.empty?
1881
+ gems = MERB_STACK
1882
+ else
1883
+ gems = comps.map { |c| Merb::Stack.components(c) }.flatten
1884
+ end
1885
+
1886
+ version_req = if options[:version]
1887
+ ::Gem::Requirement.create(options[:version])
1888
+ end
1889
+
1890
+ framework_components = Merb::Stack.framework_components
1891
+
1892
+ gems.map do |gem|
1893
+ if version_req && framework_components.include?(gem)
1894
+ ::Gem::Dependency.new(gem, version_req)
1895
+ else
1896
+ ::Gem::Dependency.new(gem, ::Gem::Requirement.default)
1897
+ end
1898
+ end
1899
+ end
1900
+
1901
+ def strategy
1902
+ options[:strategy] || (options[:edge] ? 'edge' : 'stable')
1903
+ end
1904
+
1905
+ def dependency_manager
1906
+ @_dependency_manager ||= begin
1907
+ instance = Merb::Dependencies.new
1908
+ instance.options = options
1909
+ instance
1910
+ end
1911
+ end
1912
+
1913
+ public
1914
+
1915
+ def self.repository_sets
1916
+ @_repository_sets ||= begin
1917
+ # the component itself as a fallback
1918
+ comps = Hash.new { |(hsh,c)| [c] }
1919
+
1920
+ # git repository based component sets
1921
+ comps["merb"] = ["merb-core"] + MERB_MORE
1922
+ comps["merb-more"] = MERB_MORE.sort
1923
+ comps["merb-plugins"] = MERB_PLUGINS.sort
1924
+ comps["dm-more"] = DM_MORE.sort
1925
+ comps["do"] = DATA_OBJECTS.sort
1926
+
1927
+ comps
1928
+ end
1929
+ end
1930
+
1931
+ def self.component_sets
1932
+ @_component_sets ||= begin
1933
+ # the component itself as a fallback
1934
+ comps = Hash.new { |(hsh,c)| [c] }
1935
+ comps.update(repository_sets)
1936
+
1937
+ # specific set of dependencies
1938
+ comps["stack"] = MERB_STACK.sort
1939
+ comps["basics"] = MERB_BASICS.sort
1940
+
1941
+ # orm dependencies
1942
+ comps["datamapper"] = DM_STACK.sort
1943
+ comps["sequel"] = ["merb_sequel", "sequel"]
1944
+ comps["activerecord"] = ["merb_activerecord", "activerecord"]
1945
+
1946
+ comps
1947
+ end
1948
+ end
1949
+
1950
+ def self.framework_components
1951
+ %w[merb-core merb-more].inject([]) do |all, comp|
1952
+ all + components(comp)
1953
+ end
1954
+ end
1955
+
1956
+ def self.components(comp = nil)
1957
+ if comp
1958
+ component_sets[comp]
1959
+ else
1960
+ comps = %w[merb-core merb-more merb-plugins dm-core dm-more]
1961
+ comps.inject([]) do |all, grp|
1962
+ all + (component_sets[grp] || [])
1963
+ end
1964
+ end
1965
+ end
1966
+
1967
+ def self.select_component_dependencies(dependencies, comp = nil)
1968
+ comps = components(comp) || []
1969
+ dependencies.select { |dep| comps.include?(dep.name) }
1970
+ end
1971
+
1972
+ def self.base_components
1973
+ %w[thor rake extlib]
1974
+ end
1975
+
1976
+ def self.all_components
1977
+ base_components + framework_components
1978
+ end
1979
+
1980
+ # Find the latest merb-core and gather its dependencies.
1981
+ # We check for 0.9.8 as a minimum release version.
1982
+ def self.core_dependencies(gem_dir = nil, ignore_deps = false)
1983
+ @_core_dependencies ||= begin
1984
+ if gem_dir # add local gems to index
1985
+ orig_gem_path = ::Gem.path
1986
+ ::Gem.clear_paths; ::Gem.path.unshift(gem_dir)
1987
+ end
1988
+ deps = []
1989
+ merb_core = ::Gem::Dependency.new('merb-core', '>= 0.9.8')
1990
+ if gemspec = ::Gem.source_index.search(merb_core).last
1991
+ deps << ::Gem::Dependency.new('merb-core', gemspec.version)
1992
+ if ignore_deps
1993
+ deps += gemspec.dependencies.select do |d|
1994
+ base_components.include?(d.name)
1995
+ end
1996
+ else
1997
+ deps += gemspec.dependencies
1998
+ end
1999
+ end
2000
+ ::Gem.path.replace(orig_gem_path) if gem_dir # reset
2001
+ deps
2002
+ end
2003
+ end
2004
+
2005
+ def self.lookup_repository_name(item)
2006
+ set_name = nil
2007
+ # The merb repo contains -more as well, so it needs special attention
2008
+ return 'merb' if self.repository_sets['merb'].include?(item)
2009
+
2010
+ # Proceed with finding the item in a known component set
2011
+ self.repository_sets.find do |set, items|
2012
+ next if set == 'merb'
2013
+ items.include?(item) ? (set_name = set) : nil
2014
+ end
2015
+ set_name
2016
+ end
2017
+
2018
+ end
2019
+
2020
+ end