ext 0.0.10 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/externals/ext.rb CHANGED
@@ -1,10 +1,16 @@
1
1
  require 'externals/project'
2
+ require 'externals/old_project'
2
3
  require 'externals/configuration/configuration'
4
+ require 'externals/configuration/old_configuration'
3
5
  require 'optparse'
4
6
  require 'externals/command'
5
- require 'ext/symbol'
7
+ require 'extensions/symbol'
6
8
 
7
9
  module Externals
10
+ #exit status
11
+ OBSOLETE_EXTERNALS_FILE = 15
12
+
13
+
8
14
  PROJECT_TYPES_DIRECTORY = File.join(File.dirname(__FILE__), '..', 'externals','project_types')
9
15
 
10
16
  # Full commands operate on the main project as well as the externals
@@ -37,11 +43,11 @@ module Externals
37
43
  main project. This is automatically performed by install,
38
44
  and so you probably only will run this if you are manually
39
45
  maintaining .externals"],
40
- [:install, "ext install <repository[:branch]> [path]",
46
+ [:install, "ext install <repository> [-b <branch>] [path]",
41
47
  "Registers <repository> in .externals under the appropriate
42
48
  SCM. Checks out the project, and also adds it to the ignore
43
- feature offered by the SCM of the main project. If the SCM
44
- type is not obvious from the repository URL, use the --scm,
49
+ feature offered by the SCM of the main project. If the SCM
50
+ type is not obvious from the repository URL, use the --scm,
45
51
  --git, or --svn flags."],
46
52
  [:init, "Creates a .externals file containing only [main]
47
53
  It will try to determine the SCM used by the main project,
@@ -53,7 +59,9 @@ module Externals
53
59
  top and adds a .emptydir file to any empty directories it
54
60
  comes across. Useful for dealing with SCMs that refuse to
55
61
  track empty directories (such as git, for example)"],
56
- [:help, "You probably just ran this command just now."]
62
+ [:help, "You probably just ran this command just now."],
63
+ [:upgrade_externals_file, "Converts the old format that stored
64
+ as [main][svn][git] to [<path1>][<path2>]..."],
57
65
  ]
58
66
 
59
67
 
@@ -65,18 +73,24 @@ module Externals
65
73
 
66
74
 
67
75
  class Ext
68
- Dir.entries(File.join(File.dirname(__FILE__), '..', 'ext')).each do |extension|
69
- require "ext/#{extension}" if extension =~ /.rb$/
76
+ Dir.entries(File.join(File.dirname(__FILE__), '..', 'extensions')).each do |extension|
77
+ require "extensions/#{extension}" if extension =~ /.rb$/
70
78
  end
71
79
 
72
80
  Dir.entries(File.join(File.dirname(__FILE__), '..', 'externals','scms')).each do |project|
73
81
  require "externals/scms/#{project}" if project =~ /_project.rb$/
74
82
  end
75
-
83
+
84
+ Dir.entries(File.join(File.dirname(__FILE__), '..', 'externals','old_scms')).each do |project|
85
+ require "externals/old_scms/#{project}" if project =~ /_project.rb$/
86
+ end
87
+
76
88
  Dir.entries(PROJECT_TYPES_DIRECTORY).each do |type|
77
89
  require File.join(PROJECT_TYPES_DIRECTORY, type) if type =~ /\.rb$/
78
90
  end
79
91
 
92
+ attr_accessor :path_calculator
93
+
80
94
  def self.project_types
81
95
  types = Dir.entries(PROJECT_TYPES_DIRECTORY).select do |file|
82
96
  file =~ /\.rb$/
@@ -102,16 +116,18 @@ module Externals
102
116
  def self.new_opts main_options, sub_options
103
117
  opts = OptionParser.new
104
118
 
105
- opts.banner = "ext [OPTIONS] <command> [repository[:branch]] [path]"
119
+ opts.banner = "ext [OPTIONS] <command> [repository] [-b <branch>] [path]"
106
120
 
107
121
  project_classes.each do |project_class|
108
122
  project_class.fill_in_opts(opts, main_options, sub_options)
109
123
  end
110
124
 
111
125
  opts.on("--type TYPE", "-t TYPE", "The type of project the main project is. For example, 'rails'.",
112
- Integer) {|type| sub_options[:scm] = main_options[:type] = type}
126
+ String) {|type| sub_options[:scm] = main_options[:type] = type}
113
127
  opts.on("--scm SCM", "-s SCM", "The SCM used to manage the main project. For example, '--scm svn'.",
114
- Integer) {|scm| sub_options[:scm] = main_options[:scm] = scm}
128
+ String) {|scm| sub_options[:scm] = main_options[:scm] = scm}
129
+ opts.on("--branch BRANCH", "-b BRANCH", "The branch you want the subproject to checkout when doing 'ext install'",
130
+ String) {|branch| sub_options[:branch] = main_options[:branch] = branch}
115
131
  opts.on("--workdir DIR", "-w DIR", "The working directory to execute commands from. Use this if for some reason you
116
132
  cannot execute ext from the main project's directory (or if it's just inconvenient, such as in a script
117
133
  or in a Capistrano task)",
@@ -153,10 +169,29 @@ module Externals
153
169
  exit
154
170
  end
155
171
 
172
+
156
173
  Dir.chdir(main_options[:workdir] || ".") do
174
+ if command == :upgrade_externals_file
175
+ main_options[:upgrade_externals_file] = true
176
+ elsif command != :help
177
+ if externals_file_obsolete?
178
+ puts "your .externals file Appears to be in an obsolete format"
179
+ puts "Please run 'ext upgrade_externals_file' to migrate it to the new format"
180
+ exit OBSOLETE_EXTERNALS_FILE
181
+ end
182
+ end
183
+
157
184
  new(main_options).send(command, args, sub_options)
158
185
  end
159
186
  end
187
+
188
+ def self.externals_file_obsolete?
189
+ return false if !File.exists?('.externals')
190
+
191
+ open('.externals', 'r') do |f|
192
+ f.read =~ /^\s*\[git\]\s*$|^\s*\[main\]\s*$|^\s*\[svn\]\s*$/
193
+ end
194
+ end
160
195
 
161
196
  def print_commands(commands)
162
197
  commands.each do |command|
@@ -198,17 +233,36 @@ module Externals
198
233
  end
199
234
 
200
235
  def projects
201
- configuration.projects
236
+ p = []
237
+ configuration.sections.each do |section|
238
+ p << Ext.project_class(section[:scm]||infer_scm(section[:repository])).new(
239
+ section.attributes.merge(:path => section.title))
240
+ end
241
+ p
202
242
  end
203
243
 
204
244
  def subprojects
205
- configuration.subprojects
245
+ s = []
246
+ projects.each do |project|
247
+ s << project unless project.main_project?
248
+ end
249
+ s
250
+ end
251
+
252
+ def main_project
253
+ projects.detect {|p| p.main_project?}
206
254
  end
207
255
 
208
256
  def configuration
209
257
  return @configuration if @configuration
210
258
 
211
- @configuration = Configuration::Configuration.new
259
+ file_string = ''
260
+ if File.exists? '.externals'
261
+ open('.externals', 'r') do |f|
262
+ file_string = f.read
263
+ end
264
+ end
265
+ @configuration = Configuration::Configuration.new(file_string)
212
266
  end
213
267
 
214
268
  def reload_configuration
@@ -219,12 +273,37 @@ module Externals
219
273
  def initialize options
220
274
  super()
221
275
 
222
- scm = configuration['main']
276
+ if options[:upgrade_externals_file]
277
+ type ||= options[:type]
278
+
279
+
280
+ if type
281
+ install_project_type type
282
+ else
283
+
284
+ possible_project_types = self.class.project_types.select do |project_type|
285
+ self.class.project_type_detector(project_type).detected?
286
+ end
287
+
288
+ if possible_project_types.size > 1
289
+ raise "We found multiple project types that this could be: #{possible_project_types.join(',')}
290
+ Please use
291
+ the --type option to tell ext which to use."
292
+ elsif possible_project_types.empty?
293
+ install_project_type OldConfiguration::Configuration.new[:main][:type]
294
+ else
295
+ install_project_type possible_project_types.first
296
+ end
297
+ end
298
+ return
299
+ end
300
+
301
+ scm = configuration['.']
223
302
  scm = scm['scm'] if scm
224
303
  scm ||= options[:scm]
225
304
  #scm ||= infer_scm(repository)
226
305
 
227
- type = configuration['main']
306
+ type = configuration['.']
228
307
  type = type['type'] if type
229
308
 
230
309
  type ||= options[:type]
@@ -252,6 +331,22 @@ Please use
252
331
  Externals.module_eval("#{scm.to_s.cap_first}Project", __FILE__, __LINE__)
253
332
  end
254
333
 
334
+ def self.project_classes
335
+ retval = []
336
+ registered_scms.each do |scm|
337
+ retval << project_class(scm)
338
+ end
339
+
340
+ retval
341
+ end
342
+ def self.project_class(scm)
343
+ Externals.module_eval("#{scm.to_s.cap_first}Project", __FILE__, __LINE__)
344
+ end
345
+
346
+ def self.old_project_class(scm)
347
+ Externals.module_eval("Old#{scm.to_s.cap_first}Project", __FILE__, __LINE__)
348
+ end
349
+
255
350
  def self.project_classes
256
351
  retval = []
257
352
  registered_scms.each do |scm|
@@ -285,30 +380,38 @@ Please use
285
380
 
286
381
  def install args, options
287
382
  init args, options unless File.exists? '.externals'
288
- row = args.join " "
383
+ repository = args[0]
384
+ path = args[1]
289
385
 
290
386
  orig_options = options.dup
291
387
 
292
388
  scm = options[:scm]
293
389
 
294
- scm ||= infer_scm(row)
295
-
390
+ scm ||= infer_scm(repository)
391
+
296
392
  unless scm
297
- raise "Unable to determine SCM from the repository name.
393
+ raise "Unable to determine SCM from the repository name.
298
394
  You need to either specify the scm used to manage the subproject
299
395
  that you are installing. Use an option to specify it
300
396
  (such as --git or --svn)"
301
397
  end
302
398
 
303
- if !configuration[scm]
304
- configuration.add_empty_section(scm)
305
- end
306
- configuration[scm].add_row(row)
307
- configuration.write
399
+ project = self.class.project_class(scm).new(:repository => repository,
400
+ :path => path || path_calculator.new, :scm => scm)
401
+ path = project.path
402
+
403
+ raise "no path" unless path
404
+
405
+ raise "already exists" if configuration[path]
406
+
407
+ project.branch = options[:branch] if options[:branch]
408
+
409
+ attributes = project.attributes.dup
410
+ attributes.delete(:path)
411
+ configuration[path] = project.attributes
412
+ configuration.write '.externals'
308
413
  reload_configuration
309
414
 
310
- project = self.class.project_class(scm).new(row)
311
-
312
415
  project.co
313
416
 
314
417
  update_ignore args, orig_options
@@ -318,7 +421,7 @@ that you are installing. Use an option to specify it
318
421
  #path = args[0]
319
422
 
320
423
 
321
- scm = configuration['main']
424
+ scm = configuration['.']
322
425
  scm = scm['scm'] if scm
323
426
 
324
427
  scm ||= options[:scm]
@@ -328,9 +431,9 @@ that you are installing. Use an option to specify it
328
431
  (such as --git or --svn)"
329
432
  end
330
433
 
331
- project = self.class.project_class(scm).new(".")
434
+ project = self.class.project_class(scm).new(:path => ".")
332
435
 
333
- raise "only makes sense for main project" unless project.main?
436
+ raise "only makes sense for main project" unless project.main_project?
334
437
 
335
438
  subprojects.each do |subproject|
336
439
  puts "about to add #{subproject.path} to ignore"
@@ -372,14 +475,14 @@ that you are installing. Use an option to specify it
372
475
 
373
476
  def status args, options
374
477
  options ||= {}
375
- repository = "."
376
- path = "."
377
- main_project = nil
478
+ #repository = "."
479
+ #path = "."
480
+ #main_project = nil
378
481
  scm = options[:scm]
379
- scm ||= infer_scm(repository)
482
+ #scm ||= infer_scm(repository)
380
483
 
381
484
  if !scm
382
- scm ||= configuration['main']
485
+ scm ||= configuration['.']
383
486
  scm &&= scm['scm']
384
487
  end
385
488
 
@@ -404,21 +507,22 @@ by creating the .externals file manually"
404
507
  (such as --git or --svn)"
405
508
  end
406
509
 
407
- main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
408
- main_project.st
510
+ #main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
511
+ project = main_project
512
+ project.scm ||= scm
513
+ project.st
409
514
 
410
515
  self.class.new({}).st [], {} #args, options
411
516
  end
412
517
 
413
518
  def update args, options
414
519
  options ||= {}
415
- repository = args[0]
416
- main_project = nil
520
+ #repository = args[0]
417
521
  scm = options[:scm]
418
- scm ||= infer_scm(repository)
522
+ #scm ||= infer_scm(repository)
419
523
 
420
524
  if !scm
421
- scm ||= configuration['main']
525
+ scm ||= configuration['.']
422
526
  scm &&= scm['scm']
423
527
  end
424
528
 
@@ -427,8 +531,10 @@ by creating the .externals file manually"
427
531
  (such as --git or --svn)"
428
532
  end
429
533
 
430
- main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
431
- main_project.up
534
+ #main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
535
+ project = main_project
536
+ project.scm ||= scm
537
+ project.up
432
538
 
433
539
  self.class.new({}).up [], {} #args, options
434
540
  end
@@ -506,20 +612,46 @@ Please use the --type option to tell ext which to use."
506
612
 
507
613
  config = Configuration::Configuration.new_empty
508
614
 
509
- config.sections << Configuration::Section.new("[main]\n",
510
- "scm = #{scm}\n" +
511
- "#{'type = ' + type if type}\n")
615
+ raise ".externals already exists" if File.exists?('.externals')
616
+
617
+ config.add_empty_section '.'
618
+
619
+ config['.'][:scm] = scm
620
+ config['.'][:type] = type if type
512
621
 
513
- config.write
622
+ config.write '.externals'
514
623
  reload_configuration
515
624
  end
516
625
 
626
+ def upgrade_externals_file args, options = {}
627
+ old = OldConfiguration::Configuration.new
628
+
629
+ config = Configuration::Configuration.new_empty
630
+
631
+ main = old['main']
632
+ config.add_empty_section '.'
633
+
634
+ config['.'][:scm] = main[:scm]
635
+ config['.'][:type] = main[:type]
636
+
637
+ old.subprojects.each do |subproject|
638
+ path = subproject.path
639
+ config.add_empty_section path
640
+ config[path][:repository] = subproject.repository
641
+ config[path][:scm] = subproject.scm
642
+ config[path][:branch] = subproject.branch if subproject.branch
643
+ end
644
+
645
+ config.write('.externals')
646
+ end
647
+
517
648
  def self.project_type_detector name
518
649
  Externals.module_eval("#{name.classify}Detector", __FILE__, __LINE__)
519
650
  end
520
651
 
521
652
  def install_project_type name
522
653
  Externals.module_eval("#{name.classify}ProjectType", __FILE__, __LINE__).install
654
+ self.path_calculator = Externals.module_eval("#{name.classify}ProjectType::DefaultPathCalculator", __FILE__, __LINE__)
523
655
  end
524
656
  #
525
657
  #
@@ -549,7 +681,8 @@ Please use the --type option to tell ext which to use."
549
681
  (such as --git or --svn)"
550
682
  end
551
683
 
552
- main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
684
+ main_project = self.class.project_class(scm).new(:repository => repository,
685
+ :path => path)
553
686
 
554
687
  main_project.send(sym)
555
688
  main_project
@@ -0,0 +1,114 @@
1
+ module Externals
2
+ # this regular expression will match a quoted string
3
+ # it will allow for " to be escaped with "" within the string
4
+ quoted = '(?:"(?:(?:[^"]*(?:"")?)*)")'
5
+
6
+ # this regular expression will match strings of text that are not quoted. They must appear at the start of a line or after a ,
7
+ # it will also match empty strings like ,,
8
+ unquoted = '(?:[^"\\s][^\\s$]*)'
9
+
10
+ column = "(#{quoted}|#{unquoted})"
11
+ PROJECT_LINE_REGEX = Regexp.new("^\\s*#{column}(?:\\s+#{column})?\\s*$")
12
+
13
+ class OldProject
14
+ attr_accessor :branch, :repository, :path
15
+ attr_writer :is_main, :name
16
+
17
+ def name
18
+ @name ||= (extract_name(repository))
19
+ end
20
+
21
+ def main?
22
+ @is_main
23
+ end
24
+
25
+ def self.scm
26
+ raise "subclass responsibility"
27
+ end
28
+
29
+
30
+ def scm
31
+ self.class.scm
32
+ end
33
+
34
+
35
+ def initialize row_string, is_main = false
36
+ #raise "Abstract class" if self.class == Project
37
+
38
+ #It's the main project
39
+ self.is_main = is_main || row_string == "."
40
+
41
+ if row_string =~ PROJECT_LINE_REGEX
42
+ repbranch = trim_quotes($1)
43
+ self.path = trim_quotes($2)
44
+
45
+ repbranch = repbranch.strip if repbranch
46
+
47
+ if repbranch =~ /^([^\n]*):(\w+)$/
48
+ self.repository = $1
49
+ self.branch = $2
50
+ else
51
+ self.repository = repbranch
52
+ end
53
+
54
+ self.path = self.path.strip if self.path
55
+ else
56
+ raise "poorly formatted .externals entry: #{row_string}"
57
+ end
58
+ end
59
+
60
+ [:co, :ex].each do |method_name|
61
+ define_method method_name do |args|
62
+ raise "subclass responsibility"
63
+ end
64
+ end
65
+
66
+ def update_ignore path
67
+ if !ignore_contains?(path)
68
+ append_ignore path
69
+ end
70
+ end
71
+
72
+ def checkout *args
73
+ co(*args)
74
+ end
75
+
76
+ def export *args
77
+ ex(*args)
78
+ end
79
+
80
+ def extract_name repository
81
+ if repository =~ /\/([\w_-]+)(?:\.git)?$/
82
+ $1
83
+ end
84
+ end
85
+
86
+ def path
87
+ @path || default_path
88
+ end
89
+
90
+ def parent_path
91
+ File.dirname path
92
+ end
93
+
94
+ def self.project_line? line
95
+ #Make sure it's not a comment
96
+ return false if line =~ /^\s*#/
97
+
98
+ line =~ PROJECT_LINE_REGEX
99
+ end
100
+
101
+
102
+
103
+ protected
104
+ def trim_quotes value
105
+ if value
106
+ if [value[0].chr, value[-1].chr] == ['"', '"']
107
+ value[1..-2]
108
+ else
109
+ value
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end