nucleon 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/Gemfile +4 -8
  2. data/Gemfile.lock +0 -28
  3. data/README.rdoc +13 -5
  4. data/Rakefile +9 -1
  5. data/VERSION +1 -1
  6. data/bin/nucleon +55 -0
  7. data/lib/core/codes.rb +107 -0
  8. data/lib/core/config/collection.rb +57 -0
  9. data/lib/core/config/options.rb +70 -0
  10. data/lib/core/config.rb +342 -0
  11. data/lib/core/core.rb +54 -0
  12. data/lib/core/errors.rb +84 -0
  13. data/lib/core/facade.rb +283 -0
  14. data/lib/core/gems.rb +80 -0
  15. data/lib/core/manager.rb +594 -0
  16. data/lib/core/mixin/action/commit.rb +58 -0
  17. data/lib/core/mixin/action/project.rb +53 -0
  18. data/lib/core/mixin/action/push.rb +52 -0
  19. data/lib/core/mixin/config/collection.rb +53 -0
  20. data/lib/core/mixin/config/options.rb +39 -0
  21. data/lib/core/mixin/macro/object_interface.rb +361 -0
  22. data/lib/core/mixin/macro/plugin_interface.rb +380 -0
  23. data/lib/core/mixin/settings.rb +46 -0
  24. data/lib/core/mixin/sub_config.rb +148 -0
  25. data/lib/core/mod/hash.rb +29 -0
  26. data/lib/core/plugin/action.rb +371 -0
  27. data/lib/core/plugin/base.rb +313 -0
  28. data/lib/core/plugin/command.rb +98 -0
  29. data/lib/core/plugin/event.rb +53 -0
  30. data/lib/core/plugin/extension.rb +12 -0
  31. data/lib/core/plugin/project.rb +890 -0
  32. data/lib/core/plugin/template.rb +80 -0
  33. data/lib/core/plugin/translator.rb +38 -0
  34. data/lib/core/util/cli.rb +353 -0
  35. data/lib/core/util/console.rb +237 -0
  36. data/lib/core/util/data.rb +404 -0
  37. data/lib/core/util/disk.rb +114 -0
  38. data/lib/core/util/git.rb +43 -0
  39. data/lib/core/util/liquid.rb +17 -0
  40. data/lib/core/util/logger.rb +147 -0
  41. data/lib/core/util/package.rb +93 -0
  42. data/lib/core/util/shell.rb +239 -0
  43. data/lib/nucleon/action/add.rb +69 -0
  44. data/lib/nucleon/action/create.rb +52 -0
  45. data/lib/nucleon/action/extract.rb +49 -0
  46. data/lib/nucleon/action/remove.rb +51 -0
  47. data/lib/nucleon/action/save.rb +53 -0
  48. data/lib/nucleon/action/update.rb +37 -0
  49. data/lib/nucleon/command/bash.rb +146 -0
  50. data/lib/nucleon/event/regex.rb +52 -0
  51. data/lib/nucleon/project/git.rb +465 -0
  52. data/lib/nucleon/project/github.rb +108 -0
  53. data/lib/nucleon/template/json.rb +16 -0
  54. data/lib/nucleon/template/wrapper.rb +16 -0
  55. data/lib/nucleon/template/yaml.rb +16 -0
  56. data/lib/nucleon/translator/json.rb +27 -0
  57. data/lib/nucleon/translator/yaml.rb +27 -0
  58. data/lib/nucleon.rb +18 -15
  59. data/locales/en.yml +3 -132
  60. data/nucleon.gemspec +66 -27
  61. data/spec/core/util/console_spec.rb +489 -0
  62. metadata +109 -96
@@ -0,0 +1,890 @@
1
+
2
+ module Nucleon
3
+ module Plugin
4
+ class Project < Base
5
+
6
+ @@projects = {}
7
+
8
+ #---
9
+
10
+ def self.collection
11
+ @@projects
12
+ end
13
+
14
+ #-----------------------------------------------------------------------------
15
+ # Constructor / Destructor
16
+
17
+ def self.open(directory, provider, options = {})
18
+ config = Config.ensure(options)
19
+ directory = File.expand_path(Util::Disk.filename(directory))
20
+
21
+ if ! @@projects.has_key?(directory) || config.get(:reset, false)
22
+ logger.info("Creating new project at #{directory} with #{provider}")
23
+
24
+ return Nucleon.project(config.import({
25
+ :name => directory,
26
+ :directory => directory
27
+ }), provider)
28
+
29
+ else
30
+ logger.info("Opening existing project at #{directory}")
31
+ end
32
+
33
+ @@projects[directory]
34
+ end
35
+
36
+ #-----------------------------------------------------------------------------
37
+ # Project plugin interface
38
+
39
+ def normalize
40
+ super
41
+
42
+ extension(:normalize)
43
+
44
+ set_directory(Util::Disk.filename(get(:directory, Dir.pwd)))
45
+ set_url(get(:url)) if get(:url, false)
46
+
47
+ myself.plugin_name = path if myself.plugin_name == plugin_provider
48
+
49
+ if keys = delete(:keys, nil)
50
+ set(:private_key, keys[:private_key])
51
+ set(:public_key, keys[:public_key])
52
+ end
53
+
54
+ init_project
55
+ extension(:init)
56
+
57
+ pull if get(:pull, false)
58
+ end
59
+
60
+ #---
61
+
62
+ def init_project
63
+ init_auth
64
+ init_parent
65
+ init_remotes
66
+ load_revision
67
+ end
68
+
69
+ #-----------------------------------------------------------------------------
70
+ # Plugin operations
71
+
72
+ def register
73
+ super
74
+ # TODO: Scan project directory looking for plugins
75
+ end
76
+
77
+ #-----------------------------------------------------------------------------
78
+ # Checks
79
+
80
+ def can_persist?
81
+ return top?(directory) if directory
82
+ false
83
+ end
84
+
85
+ #---
86
+
87
+ def top?(path)
88
+ return true if File.directory?(path)
89
+ false
90
+ end
91
+
92
+ #---
93
+
94
+ def subproject?(path)
95
+ false
96
+ end
97
+
98
+ #---
99
+
100
+ def project_directory?(path, require_top_level = false)
101
+ path = File.expand_path(path)
102
+ return true if File.directory?(path) && (! require_top_level || top?(path))
103
+ false
104
+ end
105
+ protected :project_directory?
106
+
107
+ #-----------------------------------------------------------------------------
108
+ # Property accessor / modifiers
109
+
110
+ def reference
111
+ get(:reference, nil)
112
+ end
113
+
114
+ #---
115
+
116
+ def private_key
117
+ get(:private_key, nil)
118
+ end
119
+
120
+ def private_key_str
121
+ return Util::Disk.read(private_key) if private_key
122
+ nil
123
+ end
124
+
125
+ def public_key
126
+ get(:public_key, nil)
127
+ end
128
+
129
+ def public_key_str
130
+ return Util::Disk.read(public_key) if public_key
131
+ nil
132
+ end
133
+
134
+ #---
135
+
136
+ def url(default = nil)
137
+ get(:url, default)
138
+ end
139
+
140
+ #---
141
+
142
+ def set_url(url)
143
+ if url && url = extension_set(:set_url, url.strip)
144
+ logger.info("Setting project #{name} url to #{url}")
145
+
146
+ set(:url, url)
147
+ set_remote(:origin, url)
148
+ end
149
+ end
150
+
151
+ #---
152
+
153
+ def edit_url(default = nil)
154
+ get(:edit, default)
155
+ end
156
+
157
+ #---
158
+
159
+ def set_edit_url(url)
160
+ url = url.strip
161
+ if url && url = extension_set(:set_edit_url, url)
162
+ logger.info("Setting project #{name} edit url to #{url}")
163
+
164
+ set(:edit, url)
165
+ set_remote(:edit, url)
166
+ end
167
+ end
168
+
169
+ #---
170
+
171
+ def directory(default = nil)
172
+ get(:directory, default)
173
+ end
174
+
175
+ #---
176
+
177
+ def path
178
+ if parent.nil?
179
+ return directory
180
+ end
181
+ directory.gsub(parent.directory + File::SEPARATOR, '')
182
+ end
183
+
184
+ #---
185
+
186
+ def set_directory(directory)
187
+ if Util::Data.empty?(directory)
188
+ current_directory = Dir.pwd
189
+ else
190
+ current_directory = File.expand_path(Util::Disk.filename(directory))
191
+ end
192
+
193
+ if current_directory = extension_set(:set_directory, current_directory)
194
+ logger.info("Setting project #{name} directory to #{current_directory}")
195
+
196
+ @@projects.delete(get(:directory)) if get(:directory)
197
+ @@projects[current_directory] = myself
198
+
199
+ set(:directory, current_directory)
200
+ end
201
+ end
202
+ protected :set_directory
203
+
204
+ #---
205
+
206
+ def set_location(directory)
207
+ set_directory(directory)
208
+
209
+ yield if block_given?
210
+
211
+ init_project
212
+ end
213
+
214
+ #---
215
+
216
+ def parent(default = nil)
217
+ get(:parent, default)
218
+ end
219
+
220
+ #---
221
+
222
+ def subprojects(default = nil)
223
+ get(:subprojects, default)
224
+ end
225
+
226
+ #---
227
+
228
+ def revision(default = nil)
229
+ get(:revision, default).to_s
230
+ end
231
+
232
+ #---
233
+
234
+ def config(name, options = {})
235
+ localize do
236
+ config = Config.ensure(options)
237
+ can_persist? && block_given? ? yield(config) : nil
238
+ end
239
+ end
240
+
241
+ #---
242
+
243
+ def set_config(name, value, options = {})
244
+ localize do
245
+ config = Config.ensure(options)
246
+
247
+ if can_persist? && value = extension_set(:set_config, value, { :name => name, :config => config })
248
+ logger.info("Setting project #{self.name} configuration: #{name} = #{value.inspect}")
249
+
250
+ yield(config, value) if block_given?
251
+ end
252
+ end
253
+ end
254
+
255
+ #---
256
+
257
+ def delete_config(name, options = {})
258
+ localize do
259
+ config = Config.ensure(options)
260
+
261
+ if can_persist? && extension_check(:delete_config, { :name => name, :config => config })
262
+ logger.info("Removing project #{self.name} configuration: #{name}")
263
+
264
+ yield(config) if block_given?
265
+ end
266
+ end
267
+ end
268
+
269
+ #---
270
+
271
+ def subproject_config(options = {})
272
+ result = {}
273
+
274
+ localize do
275
+ if can_persist?
276
+ config = Config.ensure(options)
277
+ result = yield(config) if block_given?
278
+
279
+ extension(:subproject_config, { :config => result })
280
+
281
+ logger.debug("Subproject configuration: #{result.inspect}")
282
+ end
283
+ end
284
+ result
285
+ end
286
+ protected :subproject_config
287
+
288
+ #-----------------------------------------------------------------------------
289
+ # Project operations
290
+
291
+ def init_auth
292
+ if can_persist?
293
+ localize do
294
+ logger.info("Initializing project #{name} authorization")
295
+ yield if block_given?
296
+ end
297
+ else
298
+ logger.warn("Project #{name} does not meet the criteria for persistence can not be authorized")
299
+ end
300
+ end
301
+ protected :init_auth
302
+
303
+ #---
304
+
305
+ def init_parent
306
+ delete(:parent)
307
+
308
+ logger.info("Initializing project #{name} parents")
309
+
310
+ if top?(directory)
311
+ logger.debug("Project #{name} has no parents to initialize")
312
+ else
313
+ search_dir = directory
314
+ last_dir = nil
315
+
316
+ while File.directory?((search_dir = File.expand_path('..', search_dir)))
317
+ logger.debug("Scanning directory #{search_dir} for parent project")
318
+
319
+ unless last_dir.nil? || last_dir != search_dir
320
+ break
321
+ end
322
+ if project_directory?(search_dir)
323
+ logger.debug("Directory #{search_dir} is a valid parent for this #{plugin_provider} project")
324
+
325
+ project = myself.class.open(search_dir, plugin_provider)
326
+
327
+ extension(:init_parent, { :parent => project })
328
+
329
+ set(:parent, project)
330
+ logger.debug("Setting parent to #{parent.inspect}")
331
+ break;
332
+ end
333
+ last_dir = search_dir
334
+ end
335
+ end
336
+ end
337
+ protected :init_parent
338
+
339
+ #---
340
+
341
+ def load_revision
342
+ if can_persist?
343
+ localize do
344
+ logger.info("Loading project #{name} revision")
345
+
346
+ current_revision = revision.to_s
347
+ current_revision = yield if block_given?
348
+
349
+ if current_revision && extended_revision = extension_set(:load_revision, current_revision).to_s.strip
350
+ set(:revision, extended_revision)
351
+ checkout(extended_revision) if current_revision != extended_revision
352
+
353
+ logger.debug("Loaded revision: #{revision}")
354
+
355
+ load_subprojects
356
+ end
357
+ end
358
+ else
359
+ logger.warn("Project #{name} does not meet the criteria for persistence and has no revision")
360
+ end
361
+ end
362
+ protected :load_revision
363
+
364
+ #---
365
+
366
+ def checkout(revision)
367
+ if can_persist?
368
+ localize do
369
+ if extension_check(:checkout, { :revision => revision })
370
+ logger.info("Checking out project #{name} revision: #{revision}")
371
+
372
+ success = true
373
+ success = yield(success) if block_given?
374
+
375
+ if success
376
+ set(:revision, revision)
377
+
378
+ extension(:checkout_success, { :revision => revision })
379
+ load_subprojects
380
+ end
381
+ end
382
+ end
383
+ else
384
+ logger.warn("Project #{name} does not meet the criteria for persistence and can not checkout a revision")
385
+ end
386
+ end
387
+
388
+ #---
389
+
390
+ def commit(files = '.', options = {})
391
+ success = false
392
+
393
+ if can_persist?
394
+ localize do
395
+ config = Config.ensure(options)
396
+
397
+ if extension_check(:commit, { :files => files, :config => config })
398
+ logger.info("Committing changes to project #{name}: #{files.inspect}")
399
+
400
+ time = Time.new.strftime("%Y-%m-%d %H:%M:%S")
401
+ user = config.delete(:user, ENV['USER'] + '@' + fact(:hostname))
402
+
403
+ message = config.get(:message, '')
404
+ message = 'Saving state: ' + ( files.is_a?(Array) ? "\n\n" + files.join("\n") : files.to_s ) if message.empty?
405
+
406
+ user = 'UNKNOWN' unless user && ! user.empty?
407
+
408
+ logger.debug("Commit by #{user} at #{time} with #{message}")
409
+ success = yield(config, time, user, message) if block_given?
410
+
411
+ if success
412
+ load_revision
413
+
414
+ extension(:commit_success, { :files => files })
415
+
416
+ if ! parent.nil? && config.get(:propogate, true)
417
+ logger.debug("Commit to parent as parent exists and propogate option given")
418
+
419
+ parent.load_revision
420
+ parent.commit(directory, config.import({
421
+ :message => "Updating #{path}: #{message}"
422
+ }))
423
+ end
424
+ end
425
+ end
426
+ end
427
+ else
428
+ logger.warn("Project #{name} does not meet the criteria for persistence and can be committed to")
429
+ end
430
+ success
431
+ end
432
+
433
+ #-----------------------------------------------------------------------------
434
+ # Subproject operations
435
+
436
+ def load_subprojects(options = {})
437
+ subprojects = {}
438
+
439
+ if can_persist?
440
+ config = Config.ensure(options)
441
+
442
+ logger.info("Loading sub projects for project #{name}")
443
+
444
+ subproject_config(config).each do |path, data|
445
+ project_path = File.join(directory, path)
446
+
447
+ if File.directory?(project_path)
448
+ logger.debug("Checking if project path #{project_path} is a valid sub project")
449
+
450
+ add_project = true
451
+ add_project = yield(project_path, data) if block_given?
452
+
453
+ if add_project
454
+ logger.debug("Directory #{project_path} is a valid sub project for this #{plugin_provider} project")
455
+
456
+ project = myself.class.open(project_path, plugin_provider)
457
+
458
+ extension(:load_project, { :project => project })
459
+ subprojects[path] = project
460
+ else
461
+ logger.warn("Directory #{project_path} is not a valid sub project for this #{plugin_provider} project")
462
+ end
463
+ else
464
+ logger.warn("Sub project configuration points to a location that is not a directory: #{project_path}")
465
+ end
466
+ end
467
+ else
468
+ logger.warn("Project #{name} does not meet the criteria for persistence and can not have sub projects")
469
+ end
470
+ set(:subprojects, subprojects)
471
+ end
472
+ protected :load_subprojects
473
+
474
+ #---
475
+
476
+ def add_subproject(path, url, revision, options = {})
477
+ success = true
478
+
479
+ if can_persist?
480
+ localize do
481
+ config = Config.ensure(options).import({ :path => path, :url => url, :revision => revision })
482
+
483
+ if extension_check(:add_project, { :config => config })
484
+ logger.info("Adding a sub project to #{config[:path]} from #{config[:url]} at #{config[:revision]}")
485
+
486
+ success = yield(config) if block_given?
487
+
488
+ if success
489
+ extension(:add_project_success, { :config => config })
490
+
491
+ config.init(:files, '.')
492
+ config.init(:message, "Adding project #{config[:url]} to #{config[:path]}")
493
+
494
+ commit(config[:files], { :message => config[:message] })
495
+ update_subprojects
496
+ end
497
+ else
498
+ success = false
499
+ end
500
+ end
501
+ else
502
+ logger.warn("Project #{name} does not meet the criteria for persistence and can not have sub projects")
503
+ end
504
+ success
505
+ end
506
+
507
+ #---
508
+
509
+ def delete_subproject(path)
510
+ success = true
511
+
512
+ if can_persist?
513
+ localize do
514
+ config = Config.new({ :path => path })
515
+
516
+ if extension_check(:delete_project, { :config => config })
517
+ logger.info("Deleting a sub project at #{config[:path]}")
518
+
519
+ success = yield(config) if block_given?
520
+
521
+ if success
522
+ extension(:delete_project_success, { :config => config })
523
+
524
+ config.init(:files, '.')
525
+ config.init(:message, "Removing project at #{config[:path]}")
526
+
527
+ commit(config[:files], { :message => config[:message] })
528
+ update_subprojects
529
+ end
530
+ end
531
+ end
532
+ else
533
+ logger.warn("Project #{name} does not meet the criteria for persistence and can not have sub projects")
534
+ end
535
+ success
536
+ end
537
+
538
+ #---
539
+
540
+ def update_subprojects
541
+ if can_persist?
542
+ localize do
543
+ if extension_check(:update_projects)
544
+ logger.info("Updating sub projects in project #{name}")
545
+
546
+ success = false
547
+ success = yield if block_given?
548
+
549
+ if success
550
+ extension(:update_projects_success)
551
+ load_subprojects
552
+ end
553
+ end
554
+ end
555
+ else
556
+ logger.warn("Project #{name} does not meet the criteria for persistence and can not have sub projects")
557
+ end
558
+ end
559
+ protected :update_subprojects
560
+
561
+ #---
562
+
563
+ def foreach!
564
+ if can_persist?
565
+ localize do
566
+ logger.info("Iterating through all sub projects of project #{name}")
567
+
568
+ subprojects.each do |path, project|
569
+ extension(:process_project, { :project => project })
570
+
571
+ logger.debug("Running process on sub project #{path}")
572
+ yield(path, project)
573
+ end
574
+ end
575
+ else
576
+ logger.warn("Project #{name} does not meet the criteria for persistence and can not have sub projects")
577
+ end
578
+ end
579
+
580
+ #-----------------------------------------------------------------------------
581
+ # Remote operations
582
+
583
+ def init_remotes
584
+ if can_persist?
585
+ localize do
586
+ logger.info("Initializing project #{name} remotes")
587
+
588
+ origin_url = url
589
+ origin_url = yield if block_given?
590
+
591
+ if origin_url && origin_url = extension_set(:init_remotes, origin_url).to_s.strip
592
+ set(:url, origin_url)
593
+ set_edit_url(translate_edit_url(url))
594
+ end
595
+ end
596
+ else
597
+ logger.warn("Project #{name} does not meet the criteria for persistence and can not have remotes")
598
+ end
599
+ end
600
+ protected :init_remotes
601
+
602
+ #---
603
+
604
+ def remote(name)
605
+ url = nil
606
+ if can_persist?
607
+ localize do
608
+ logger.info("Fetching remote url for #{name}")
609
+ url = yield if block_given?
610
+ end
611
+ end
612
+ url
613
+ end
614
+
615
+ #---
616
+
617
+ def set_remote(name, url)
618
+ if can_persist?
619
+ localize do
620
+ if ! url.strip.empty? && url = extension_set(:set_remote, url, { :name => name })
621
+ delete_remote(name)
622
+
623
+ logger.info("Setting project remote #{name} to #{url}")
624
+ yield(url) if block_given?
625
+ end
626
+ end
627
+ else
628
+ logger.warn("Project #{self.name} does not meet the criteria for persistence and can not have remotes")
629
+ end
630
+ end
631
+
632
+ #---
633
+
634
+ def add_remote_url(name, url, options = {})
635
+ if can_persist?
636
+ localize do
637
+ config = Config.ensure(options)
638
+
639
+ if url = extension_set(:add_remote_url, url, { :name => name, :config => config })
640
+ logger.info("Adding project remote url #{url} to #{name}")
641
+ yield(config, url) if block_given?
642
+ end
643
+ end
644
+ else
645
+ logger.warn("Project #{self.name} does not meet the criteria for persistence and can not have remotes")
646
+ end
647
+ end
648
+
649
+ #---
650
+
651
+ def set_host_remote(name, hosts, path, options = {})
652
+ if can_persist?
653
+ localize do
654
+ config = Config.ensure(options).import({ :path => path })
655
+ hosts = array(hosts)
656
+
657
+ unless hosts.empty?
658
+ if hosts = extension_set(:set_host_remote, hosts, { :name => name, :config => config })
659
+ unless ! hosts || hosts.empty?
660
+ path = config.delete(:path)
661
+
662
+ logger.info("Setting host remote #{name} for #{hosts.inspect} at #{path}")
663
+ set_remote(name, translate_url(hosts.shift, path, config.export))
664
+
665
+ hosts.each do |host|
666
+ logger.debug("Adding remote url to #{host}")
667
+ add_remote_url(name, translate_url(host, path, config.export), config)
668
+ end
669
+ end
670
+ end
671
+ end
672
+ end
673
+ else
674
+ logger.warn("Project #{self.name} does not meet the criteria for persistence and can not have remotes")
675
+ end
676
+ end
677
+
678
+ #---
679
+
680
+ def delete_remote(name)
681
+ if can_persist?
682
+ localize do
683
+ if extension_check(:delete_remote, { :name => name })
684
+ logger.info("Deleting project remote #{name}")
685
+ yield if block_given?
686
+ end
687
+ end
688
+ else
689
+ logger.warn("Project #{self.name} does not meet the criteria for persistence and can not have remotes")
690
+ end
691
+ end
692
+
693
+ #-----------------------------------------------------------------------------
694
+ # Remote operations
695
+
696
+ def pull(remote = :origin, options = {})
697
+ success = false
698
+
699
+ if can_persist?
700
+ localize do
701
+ config = Config.ensure(options).import({ :remote => remote })
702
+
703
+ if extension_check(:pull, { :directory => directory, :config => config })
704
+ remote = config.delete(:remote)
705
+
706
+ logger.info("Pulling from #{remote} into #{directory}")
707
+
708
+ success = yield(config, remote) if block_given?
709
+
710
+ if success
711
+ load_revision
712
+ update_subprojects
713
+
714
+ extension(:pull_success, { :directory => directory, :remote => remote, :config => config })
715
+
716
+ if ! parent.nil? && config.get(:propogate, true)
717
+ logger.debug("Commit to parent as parent exists and propogate option was given")
718
+
719
+ parent.commit(directory, config.import({
720
+ :message => "Pulling updates for subproject #{path}",
721
+ :allow_empty => true
722
+ }))
723
+ end
724
+ end
725
+ end
726
+ end
727
+ else
728
+ logger.warn("Project #{name} does not meet the criteria for persistence and can not pull from remotes")
729
+ end
730
+ success
731
+ end
732
+
733
+ #---
734
+
735
+ def push(remote = :edit, options = {})
736
+ success = false
737
+
738
+ if can_persist?
739
+ localize do
740
+ config = Config.ensure(options).import({ :remote => remote })
741
+
742
+ if extension_check(:push, { :directory => directory, :config => config })
743
+ remote = config.delete(:remote)
744
+
745
+ logger.info("Pushing to #{remote} from #{directory}")
746
+
747
+ success = yield(config, remote) if block_given?
748
+
749
+ if success
750
+ config.delete(:revision)
751
+
752
+ extension(:push_success, { :directory => directory, :remote => remote, :config => config })
753
+
754
+ if config.get(:propogate, true)
755
+ logger.debug("Pushing sub projects as propogate option was given")
756
+
757
+ foreach! do |path, project|
758
+ project.push(remote, config)
759
+ end
760
+ end
761
+ end
762
+ end
763
+ end
764
+ else
765
+ logger.warn("Project #{name} does not meet the criteria for persistence and can not push to remotes")
766
+ end
767
+ success
768
+ end
769
+
770
+ #-----------------------------------------------------------------------------
771
+ # Utilities
772
+
773
+ def self.build_info(type, data)
774
+ data = data.split(/\s*,\s*/) if data.is_a?(String)
775
+ super(type, data)
776
+ end
777
+
778
+ #---
779
+
780
+ def self.translate(data)
781
+ options = super(data)
782
+
783
+ case data
784
+ when String
785
+ options = { :url => data }
786
+ when Hash
787
+ options = data
788
+ end
789
+
790
+ if options.has_key?(:url)
791
+ if matches = translate_reference(options[:url])
792
+ options[:provider] = matches[:provider]
793
+ options[:url] = matches[:url]
794
+ options[:revision] = matches[:revision] unless options.has_key?(:revision)
795
+
796
+ logger.debug("Translating project options: #{options.inspect}")
797
+ end
798
+ end
799
+ options
800
+ end
801
+
802
+ #---
803
+
804
+ def self.translate_reference(reference, editable = false)
805
+ # ex: github:::username/project[branch/revision]
806
+ if reference && reference.match(/^\s*([a-zA-Z0-9_-]+):::([^\]\s]+)\s*(?:\[\s*([^\]\s]+)\s*\])?\s*$/)
807
+ provider = $1
808
+ url = $2
809
+ revision = $3
810
+
811
+ logger.debug("Translating project reference: #{provider} #{url} #{revision}")
812
+
813
+ if provider && Manager.connection.loaded_plugins(:project).keys.include?(provider.to_sym)
814
+ klass = Nucleon.class_const([ :nucleon, :project, provider ])
815
+ expanded_url = klass.send(:expand_url, url, editable) if klass.respond_to?(:expand_url)
816
+ end
817
+ expanded_url = url unless expanded_url
818
+
819
+ info = {
820
+ :provider => provider,
821
+ :reference => url,
822
+ :url => expanded_url,
823
+ :revision => revision
824
+ }
825
+
826
+ logger.debug("Project reference info: #{info.inspect}")
827
+ return info
828
+ end
829
+ nil
830
+ end
831
+
832
+ #---
833
+
834
+ def translate_reference(reference, editable = false)
835
+ myself.class.translate_reference(reference, editable)
836
+ end
837
+
838
+ #---
839
+
840
+ def translate_url(host, path, options = {})
841
+ config = Config.ensure(options)
842
+ url = "#{host}/#{path}"
843
+
844
+ if block_given?
845
+ temp_url = yield(config)
846
+ url = temp_url if temp_url
847
+ end
848
+ url
849
+ end
850
+
851
+ #---
852
+
853
+ def translate_edit_url(url, options = {})
854
+ config = Config.ensure(options)
855
+
856
+ if block_given?
857
+ temp_url = yield(config)
858
+ url = temp_url if temp_url
859
+ end
860
+ url
861
+ end
862
+
863
+ #---
864
+
865
+ def localize
866
+ prev_directory = Dir.pwd
867
+ Dir.chdir(directory)
868
+
869
+ result = safe_exec(true) do
870
+ yield
871
+ end
872
+
873
+ Dir.chdir(prev_directory)
874
+ result
875
+ end
876
+
877
+ #---
878
+
879
+ def local_path(file_path)
880
+ file_path.gsub(directory + File::SEPARATOR, '')
881
+ end
882
+
883
+ #---
884
+
885
+ def full_path(local_path)
886
+ File.join(directory, local_path)
887
+ end
888
+ end
889
+ end
890
+ end