ext 0.0.5

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/MIT_LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2008 Miles Georgi, Azimux.com, nopugs.com Consulting
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,108 @@
1
+ Externals is a project that allows you to use the workflow normally made
2
+ possible by svn:externals in an SCM independent manner.
3
+
4
+ I was inspired to create this project because I had several projects that had
5
+ a mix of plugins managed by git and by svn. Git's submodule feature is
6
+ not exactly like svn:externals. Basically, I don't like how you have to manually
7
+ checkout a branch, and I don't like git status not propagating through the
8
+ submodules. Also, the branch tip doesn't automatically move with git-submodule.
9
+ Subversion always checks out the branch tip for subprojects when performing a
10
+ checkout or update.
11
+
12
+ Externals is designed such that adding support for a new SCM, or new project
13
+ types is easy.
14
+
15
+ The externals executable is called ext. Commands come in a long form and a
16
+ short form. The longer form applies the action to the main project. The short
17
+ forms apply the action to all sub projects.
18
+
19
+ The commands and usage are as follows (from 'ext help'):
20
+
21
+ ext [OPTIONS] <command> [repository[:branch]] [path]
22
+ -g, --git same as '--scm git' Uses git to checkout/export the main project
23
+ -s, --svn, --subversion same as '--scm svn' Uses subversion to checkout/export the main project
24
+ -t, --type TYPE The type of project the main project is. For example, 'rails'.
25
+ --scm SCM The SCM used to manage the main project. For example, '--scm svn'.
26
+ -w, --workdir DIR The working directory to execute commands from. Use this if for some reason you
27
+ cannot execute ext from the main project's directory (or if it's just inconvenient, such as in a script
28
+ or in a Capistrano task)
29
+ --help does the same as 'ext help' If you use this with a command
30
+ it will ignore the command and run help instead.
31
+
32
+
33
+
34
+ Commands that apply to the main project or the .externals file:
35
+ update_ignore, install, init, touch_emptydirs, help
36
+
37
+ update_ignore Adds all paths to subprojects that are
38
+ registered in .externals to the ignore feature of the
39
+ main project. This is automatically performed by install,
40
+ and so you probably only will run this if you are manually
41
+ maintaining .externals
42
+
43
+ install Usage: ext install <repository[:branch]> [path]
44
+ Registers <repository> in .externals under the appropriate
45
+ SCM. Checks out the project, and also adds it to the ignore
46
+ feature offered by the SCM of the main project. If the SCM type
47
+ is not obvious from the repository URL, use the --scm, --git,
48
+ or --svn flags.
49
+
50
+ init Creates a .externals file containing only [main]
51
+ It will try to determine the SCM used by the main project,
52
+ as well as the project type. You don't have to specify
53
+ a project type if you don't want to or if your project type
54
+ isn't supported. It just means that when using 'install'
55
+ that you'll want to specify the path.
56
+
57
+ touch_emptydirs Recurses through all directories from the
58
+ top and adds a .emptydir file to any empty directories it
59
+ comes across. Useful for dealing with SCMs that refuse to
60
+ track empty directories (such as git, for example)
61
+
62
+ help You probably just ran this command just now.
63
+
64
+
65
+
66
+ Commands that apply to the main project and all subprojects:
67
+ checkout, export, status, update
68
+
69
+ checkout Usage: ext checkout <repository>
70
+
71
+ Checks out <repository>, and checks out any subprojects
72
+ registered in <repository>'s .externals file.
73
+
74
+ export Usage: ext export <repository>
75
+
76
+ Like checkout except this command fetches as little
77
+ history as possible.
78
+
79
+ status Usage: ext status
80
+
81
+ Prints out the status of the main project, followed by
82
+ the status of each subproject.
83
+
84
+ update Usage: ext update
85
+
86
+ Brings the main project, and all subprojects, up to the
87
+ latest version.
88
+
89
+
90
+
91
+ Commands that only apply to the subprojects:
92
+ co, ex, st, up
93
+
94
+ co Like checkout, but skips the main project and
95
+ only checks out subprojects.
96
+
97
+ ex Like export, but skips the main project.
98
+
99
+ st Like status, but skips the main project.
100
+
101
+ up Like update, but skips the main project.
102
+
103
+
104
+ The externals project is copyright 2008 by Miles Georgi, nopugs.com, azimux.com
105
+ and is released under the MIT license.
106
+
107
+ The license is available in the same directory as this README
108
+ file and is named MIT_LICENSE.txt
data/Rakefile ADDED
@@ -0,0 +1,64 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+
6
+ Rake::TestTask.new('test') do |task|
7
+ task.libs = [File.expand_path('lib'),File.expand_path('test')]
8
+ task.pattern = './test/test_*.rb'
9
+ task.warning = true
10
+ end
11
+
12
+ gem_specification = Gem::Specification.new do |specification|
13
+ specification.name = 'ext'
14
+ specification.version = '0.0.5'
15
+ specification.platform = Gem::Platform::RUBY
16
+ specification.rubyforge_project = 'ext'
17
+
18
+ specification.summary =
19
+ %{Provides an SCM agnostic way to manage subprojects with a workflow similar
20
+ to the svn:externals feature of subversion. It's particularly useful for rails
21
+ projects that have some plugins managed by svn and some managed by git.}
22
+ specification.description =
23
+ %{Provides an SCM agnostic way to manage subprojects with a workflow similar
24
+ to the scm:externals feature of subversion. It's particularly useful for rails
25
+ projects that have some plugins managed by svn and some managed by git.
26
+
27
+ For example, "ext install git://github.com/rails/rails.git" from within a rails
28
+ application directory will realize that this belongs in the vendor/rails folder.
29
+ It will also realize that this URL is a git repository and clone it into that
30
+ folder.
31
+
32
+ It will also add the vendor/rails folder to the ignore feature for the SCM of
33
+ the main project. Let's say that the main project is being managed by
34
+ subversion. In that case it adds "rails" to the svn:ignore property of the
35
+ vendor folder. It also adds the URL to the .externals file so that when this
36
+ project is checked out via "ext checkout" it knows where to fetch the
37
+ subprojects.
38
+
39
+ There are several other useful commands, such as init, touch_emptydirs, add_all,
40
+ export, status. I plan to put up a tutorial at http://nopugs.com/ext-tutorial
41
+
42
+ The reason I made this project is that I was frustrated by two things:
43
+
44
+ 1. In my opinion, the workflow for svn:externals is far superior to
45
+ git-submodule.
46
+
47
+ 2. Even if git-submodule was as useful as svn:externals, I would still like a
48
+ uniform way to fetch all of the subprojects regardless of the SCM used to manage
49
+ the main project.}
50
+
51
+ specification.author = "Miles Georgi"
52
+ specification.email = "azimux@gmail.com"
53
+ specification.homepage = "http://nopugs.com/ext-tutorial"
54
+
55
+ specification.test_files = FileList['test/test_*.rb']
56
+ specification.executables = ['ext', 'ext.bat']
57
+ specification.files = ['Rakefile', 'README', 'MIT_LICENSE.txt'] +
58
+ FileList['lib/**/*.rb']
59
+ #specification.require_path = 'lib'
60
+ end
61
+
62
+ Rake::GemPackageTask.new(gem_specification) do |package|
63
+ package.need_zip = package.need_tar = false
64
+ end
data/bin/ext ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'externals/ext'
4
+
5
+ Externals::Ext.run(*ARGV)
data/bin/ext.bat ADDED
@@ -0,0 +1,6 @@
1
+ @ECHO OFF
2
+ IF NOT "%~f0" == "~f0" GOTO :WinNT
3
+ @"ruby.exe" "ext" %1 %2 %3 %4 %5 %6 %7 %8 %9
4
+ GOTO :EOF
5
+ :WinNT
6
+ @"ruby.exe" "%~dpn0" %*
data/lib/ext/string.rb ADDED
@@ -0,0 +1,9 @@
1
+ String.class_eval do
2
+ def cap_first
3
+ "#{self[0].chr.upcase}#{self[1..(self.length - 1)]}"
4
+ end
5
+
6
+ def classify
7
+ split('_').map(&:cap_first).join
8
+ end
9
+ end
data/lib/ext/symbol.rb ADDED
@@ -0,0 +1,7 @@
1
+ unless Symbol.respond_to? :to_proc
2
+ class Symbol
3
+ def to_proc
4
+ proc { |*args| args[0].send(self, *args[1...args.size]) }
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,35 @@
1
+ module Externals
2
+ class Command
3
+ attr_reader :name, :usage, :summary
4
+ def initialize name, usage, summary = nil
5
+ @name = name
6
+ @usage = usage
7
+ @summary = summary
8
+
9
+ if !@summary
10
+ @summary, @usage = @usage, @summary
11
+ end
12
+ end
13
+
14
+ def to_s
15
+ retval = StringIO.new
16
+ retval.printf "%-16s", name
17
+ if usage
18
+ retval.printf "Usage: #{usage}\n"
19
+ else
20
+ dont_pad_first = true
21
+ end
22
+
23
+ summary.split(/\n/).each_with_index do |line, index|
24
+ if index == 0 && dont_pad_first
25
+ retval.printf "%s\n", line.strip
26
+ else
27
+ retval.printf "%16s%s\n", '', line.strip
28
+ end
29
+ end
30
+
31
+ retval.printf "\n"
32
+ retval.string
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,159 @@
1
+ module Externals
2
+ module Configuration
3
+ SECTION_TITLE_REGEX = /^\s*\[(\w+)\]\s*$/
4
+ SECTION_TITLE_REGEX_NO_GROUPS = /^\s*\[(?:\w+)\]\s*$/
5
+
6
+
7
+ class Section
8
+ attr_accessor :title_string, :body_string, :title, :rows, :scm
9
+
10
+ def main?
11
+ title == 'main'
12
+ end
13
+
14
+ def initialize title_string, body_string, scm = nil
15
+ self.title_string = title_string
16
+ self.body_string = body_string
17
+ self.scm = scm
18
+
19
+ self.title = SECTION_TITLE_REGEX.match(title_string)[1]
20
+
21
+ self.scm ||= self.title
22
+
23
+ raise "Invalid section title: #{title_string}" unless title
24
+
25
+ self.rows = body_string.split(/\n/)
26
+ end
27
+
28
+ def setting key
29
+ if !main?
30
+ raise "this isn't a section of the configuration that can contain settings"
31
+ end
32
+
33
+ rows.each do |row|
34
+ if row =~ /\s*(\w+)\s*=\s*([^#]*)(?:#.*)?$/ && key.to_s == $1
35
+ return $2
36
+ end
37
+ end
38
+ nil
39
+ end
40
+
41
+ def [] key
42
+ setting(key)
43
+ end
44
+
45
+
46
+ def projects
47
+ return @projects if @projects
48
+
49
+ @projects = []
50
+
51
+ if main?
52
+ @projects = [Ext.project_class(self['scm']).new(".", :is_main)]
53
+ else
54
+ rows.each do |row|
55
+ if Project.project_line?(row)
56
+ @projects << Ext.project_class(title).new(row)
57
+ end
58
+ end
59
+ @projects
60
+ end
61
+ end
62
+
63
+ def add_row(row)
64
+ rows << row
65
+
66
+ self.body_string = body_string.chomp + "\n#{row}\n"
67
+ clear_caches
68
+ end
69
+
70
+ def clear_caches
71
+ @projects = nil
72
+ end
73
+
74
+ def to_s
75
+ "#{title_string}#{body_string}"
76
+ end
77
+ end
78
+
79
+ class Configuration
80
+ attr_accessor :file_string
81
+
82
+ def sections
83
+ @sections ||= []
84
+ end
85
+
86
+ def [] title
87
+ title = title.to_s
88
+ sections.detect {|section| section.title == title}
89
+ end
90
+
91
+ def add_empty_section title
92
+ raise "Section already exists" if self[title]
93
+ sections << Section.new("\n\n[#{title.to_s}]\n", "")
94
+ end
95
+
96
+ def self.new_empty
97
+ new nil, true
98
+ end
99
+
100
+ def initialize externals_file = nil, empty = false
101
+ if empty
102
+ self.file_string = ''
103
+ return
104
+ end
105
+
106
+ if !externals_file && File.exists?('.externals')
107
+ open('.externals', 'r') do |f|
108
+ externals_file = f.read
109
+ end
110
+ end
111
+
112
+ externals_file ||= ""
113
+
114
+ self.file_string = externals_file
115
+
116
+ titles = externals_file.grep SECTION_TITLE_REGEX
117
+ bodies = externals_file.split SECTION_TITLE_REGEX_NO_GROUPS
118
+
119
+ if titles.size > 0 && bodies.size > 0
120
+ if titles.size + 1 != bodies.size
121
+ raise "bodies and sections do not match up"
122
+ end
123
+
124
+ bodies = bodies[1..(bodies.size - 1)]
125
+
126
+ (0...(bodies.size)).each do |index|
127
+ sections << Section.new(titles[index], bodies[index])
128
+ end
129
+ end
130
+ end
131
+
132
+ def projects
133
+ retval = []
134
+ sections.each do |section|
135
+ retval += section.projects
136
+ end
137
+
138
+ retval
139
+ end
140
+
141
+ def subprojects
142
+ retval = []
143
+ sections.each do |section|
144
+ retval += section.projects unless section.main?
145
+ end
146
+
147
+ retval
148
+ end
149
+
150
+ def write path = ".externals"
151
+ open(path, 'w') do |f|
152
+ sections.each do |section|
153
+ f.write section.to_s
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,557 @@
1
+ require 'externals/project'
2
+ require 'externals/configuration/configuration'
3
+ require 'optparse'
4
+ require 'externals/command'
5
+ require 'ext/symbol'
6
+
7
+ module Externals
8
+ PROJECT_TYPES_DIRECTORY = File.join(File.dirname(__FILE__), '..', 'externals','project_types')
9
+
10
+ # Full commands operate on the main project as well as the externals
11
+ # short commands only operate on the externals
12
+ # Main commands only operate on the main project
13
+ FULL_COMMANDS_HASH = [
14
+ [:checkout, "ext checkout <repository>", %{
15
+ Checks out <repository>, and checks out any subprojects
16
+ registered in <repository>'s .externals file.}],
17
+ [:export, "ext export <repository>", %{
18
+ Like checkout except this command fetches as little
19
+ history as possible.}],
20
+ [:status, "ext status", %{
21
+ Prints out the status of the main project, followed by
22
+ the status of each subproject.}],
23
+ [:update, "ext update", %{
24
+ Brings the main project, and all subprojects, up to the
25
+ latest version.}]
26
+ ]
27
+ SHORT_COMMANDS_HASH = [
28
+ [:co, "Like checkout, but skips the main project and
29
+ only checks out subprojects."],
30
+ [:ex, "Like export, but skips the main project."],
31
+ [:st, "Like status, but skips the main project."],
32
+ [:up, "Like update, but skips the main project."]
33
+ ]
34
+ MAIN_COMMANDS_HASH = [
35
+ [:update_ignore, "Adds all paths to subprojects that are
36
+ registered in .externals to the ignore feature of the
37
+ main project. This is automatically performed by install,
38
+ and so you probably only will run this if you are manually
39
+ maintaining .externals"],
40
+ [:install, "ext install <repository[:branch]> [path]",
41
+ "Registers <repository> in .externals under the appropriate
42
+ 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,
45
+ --git, or --svn flags."],
46
+ [:init, "Creates a .externals file containing only [main]
47
+ It will try to determine the SCM used by the main project,
48
+ as well as the project type. You don't have to specify
49
+ a project type if you don't want to or if your project type
50
+ isn't supported. It just means that when using 'install'
51
+ that you'll want to specify the path."],
52
+ [:touch_emptydirs, "Recurses through all directories from the
53
+ top and adds a .emptydir file to any empty directories it
54
+ comes across. Useful for dealing with SCMs that refuse to
55
+ track empty directories (such as git, for example)"],
56
+ [:help, "You probably just ran this command just now."]
57
+ ]
58
+
59
+
60
+ FULL_COMMANDS = FULL_COMMANDS_HASH.map(&:first)
61
+ SHORT_COMMANDS = SHORT_COMMANDS_HASH.map(&:first)
62
+ MAIN_COMMANDS = MAIN_COMMANDS_HASH.map(&:first)
63
+
64
+ COMMANDS = FULL_COMMANDS + SHORT_COMMANDS + MAIN_COMMANDS
65
+
66
+
67
+ class Ext
68
+ Dir.entries(File.join(File.dirname(__FILE__), '..', 'ext')).each do |extension|
69
+ require "ext/#{extension}" if extension =~ /.rb$/
70
+ end
71
+
72
+ Dir.entries(File.join(File.dirname(__FILE__), '..', 'externals','scms')).each do |project|
73
+ require "externals/scms/#{project}" if project =~ /_project.rb$/
74
+ end
75
+
76
+
77
+ def self.project_types
78
+ types = Dir.entries(PROJECT_TYPES_DIRECTORY).select do |file|
79
+ file =~ /\.rb$/
80
+ end
81
+
82
+ types.map do |type|
83
+ /^(.*)\.rb$/.match(type)[1]
84
+ end
85
+ end
86
+
87
+ #puts "Project types available: #{project_types.join(' ')}"
88
+
89
+ def self.project_type_files
90
+ project_types.map do |project_type|
91
+ "#{File.join(PROJECT_TYPES_DIRECTORY, project_type)}.rb"
92
+ end
93
+ end
94
+
95
+ project_type_files.each do |file|
96
+ require file
97
+ end
98
+
99
+ def self.new_opts main_options, sub_options
100
+ opts = OptionParser.new
101
+
102
+ opts.banner = "ext [OPTIONS] <command> [repository[:branch]] [path]"
103
+
104
+ project_classes.each do |project_class|
105
+ project_class.fill_in_opts(opts, main_options, sub_options)
106
+ end
107
+
108
+ opts.on("--type TYPE", "-t TYPE", "The type of project the main project is. For example, 'rails'.",
109
+ Integer) {|type| sub_options[:scm] = main_options[:type] = type}
110
+ opts.on("--scm SCM", "-s SCM", "The SCM used to manage the main project. For example, '--scm svn'.",
111
+ Integer) {|scm| sub_options[:scm] = main_options[:scm] = scm}
112
+ opts.on("--workdir DIR", "-w DIR", "The working directory to execute commands from. Use this if for some reason you
113
+ cannot execute ext from the main project's directory (or if it's just inconvenient, such as in a script
114
+ or in a Capistrano task)",
115
+ String) {|dir|
116
+ raise "No such directory: #{dir}" unless File.exists?(dir) && File.directory?(dir)
117
+ main_options[:workdir] = dir
118
+ }
119
+ opts.on("--help", "does the same as 'ext help' If you use this with a command
120
+ it will ignore the command and run help instead.") {main_options[:help] = true}
121
+ end
122
+
123
+ def self.run *arguments
124
+
125
+ main_options = {}
126
+ sub_options = {}
127
+
128
+ opts = new_opts main_options, sub_options
129
+
130
+ args = opts.parse(arguments)
131
+
132
+ unless args.nil? || args.empty?
133
+ command = args[0]
134
+ args = args[1..(args.size - 1)] || []
135
+ end
136
+
137
+ command &&= command.to_sym
138
+
139
+ command = :help if main_options[:help]
140
+
141
+ if !command || command.to_s == ''
142
+ puts "hey... you didn't tell me what you want to do."
143
+ puts "Try 'ext help' for a list of commands"
144
+ exit
145
+ end
146
+
147
+ unless COMMANDS.index command
148
+ puts "unknown command: #{command}"
149
+ puts "for a list of commands try 'ext help'"
150
+ exit
151
+ end
152
+
153
+ Dir.chdir(main_options[:workdir] || ".") do
154
+ new(main_options).send(command, args, sub_options)
155
+ end
156
+ end
157
+
158
+ def print_commands(commands)
159
+ commands.each do |command|
160
+ puts Command.new(*command)
161
+ end
162
+ puts
163
+ end
164
+
165
+ def help(args, options)
166
+ puts "#{self.class.new_opts({},{}).to_s}\n\n"
167
+
168
+ puts "\nCommands that apply to the main project or the .externals file:"
169
+ puts "#{MAIN_COMMANDS.join(', ')}\n\n"
170
+ print_commands(MAIN_COMMANDS_HASH)
171
+
172
+ puts "\nCommands that apply to the main project and all subprojects:"
173
+ puts "#{FULL_COMMANDS.join(', ')}\n\n"
174
+ print_commands(FULL_COMMANDS_HASH)
175
+
176
+ puts "\nCommands that only apply to the subprojects:"
177
+ puts "#{SHORT_COMMANDS.join(', ')}\n\n"
178
+ print_commands(SHORT_COMMANDS_HASH)
179
+ end
180
+
181
+ def self.registered_scms
182
+ return @registered_scms if @registered_scms
183
+ @registered_scms ||= []
184
+
185
+ scmdir = File.join(File.dirname(__FILE__), 'scms')
186
+
187
+ Dir.entries(scmdir).each do |file|
188
+ if file =~ /^(.*)_project\.rb$/
189
+ @registered_scms << $1
190
+ end
191
+ end
192
+
193
+ @registered_scms
194
+ end
195
+
196
+ def projects
197
+ configuration.projects
198
+ end
199
+
200
+ def subprojects
201
+ configuration.subprojects
202
+ end
203
+
204
+ def configuration
205
+ return @configuration if @configuration
206
+
207
+ @configuration = Configuration::Configuration.new
208
+ end
209
+
210
+ def reload_configuration
211
+ @configuration = nil
212
+ configuration
213
+ end
214
+
215
+ def initialize options
216
+ super()
217
+
218
+ scm = configuration['main']
219
+ scm = scm['scm'] if scm
220
+ scm ||= options[:scm]
221
+ #scm ||= infer_scm(repository)
222
+
223
+ type = configuration['main']
224
+ type = type['type'] if type
225
+
226
+ type ||= options[:type]
227
+
228
+ if type
229
+ install_project_type type
230
+ else
231
+ possible_project_types = self.class.project_types.select do |project_type|
232
+ self.class.project_type_detector(project_type).detected?
233
+ end
234
+
235
+ if possible_project_types.size > 1
236
+ raise "We found multiple project types that this could be: #{possible_project_types.join(',')}
237
+ Please use
238
+ the --type option to tell ext which to use."
239
+ else
240
+ possible_project_types.each do |project_type|
241
+ install_project_type project_type
242
+ end
243
+ end
244
+ end
245
+ end
246
+
247
+ def self.project_class(scm)
248
+ Externals.module_eval("#{scm.to_s.cap_first}Project", __FILE__, __LINE__)
249
+ end
250
+
251
+ def self.project_classes
252
+ retval = []
253
+ registered_scms.each do |scm|
254
+ retval << project_class(scm)
255
+ end
256
+
257
+ retval
258
+ end
259
+
260
+ SHORT_COMMANDS.each do |command_name|
261
+ define_method command_name do |args, options|
262
+ project_name_or_path = nil
263
+
264
+ if args && !args.empty?
265
+ project_name_or_path = args.first
266
+ end
267
+
268
+ if project_name_or_path
269
+ project = subprojects.detect do |project|
270
+ project.name == project_name_or_path || project.path == project_name_or_path
271
+ end
272
+
273
+ raise "no such project" unless project
274
+
275
+ project.send command_name, args, options
276
+ else
277
+ subprojects.each {|p| p.send(*([command_name, args, options].flatten))}
278
+ end
279
+ end
280
+ end
281
+
282
+ def install args, options
283
+ init args, options unless File.exists? '.externals'
284
+ row = args.join " "
285
+
286
+ orig_options = options.dup
287
+
288
+ scm = options[:scm]
289
+
290
+ scm ||= infer_scm(row)
291
+
292
+ if !configuration[scm]
293
+ configuration.add_empty_section(scm)
294
+ end
295
+ configuration[scm].add_row(row)
296
+ configuration.write
297
+ reload_configuration
298
+
299
+ project = self.class.project_class(scm).new(row)
300
+
301
+ project.co
302
+
303
+ update_ignore args, orig_options
304
+ end
305
+
306
+ def update_ignore args, options
307
+ #path = args[0]
308
+
309
+
310
+ scm = configuration['main']
311
+ scm = scm['scm'] if scm
312
+
313
+ scm ||= options[:scm]
314
+
315
+ unless scm
316
+ raise "You need to either specify the scm as the first line in .externals (for example, scm = git), or use an option to specify it
317
+ (such as --git or --svn)"
318
+ end
319
+
320
+ project = self.class.project_class(scm).new(".")
321
+
322
+ raise "only makes sense for main project" unless project.main?
323
+
324
+ subprojects.each do |subproject|
325
+ puts "about to add #{subproject.path} to ignore"
326
+ project.update_ignore subproject.path
327
+ puts "finished adding #{subproject.path}"
328
+ end
329
+ end
330
+
331
+ def touch_emptydirs args, options
332
+ require 'find'
333
+
334
+ excludes = ['.','..','.svn', '.git']
335
+
336
+ excludes.dup.each do |exclude|
337
+ excludes << "./#{exclude}"
338
+ end
339
+
340
+ paths = []
341
+
342
+ Find.find('.') do |f|
343
+ if File.directory?(f)
344
+ excluded = false
345
+ File.split(f).each do |part|
346
+ exclude ||= excludes.index(part)
347
+ end
348
+
349
+ if !excluded && ((Dir.entries(f) - excludes).size == 0)
350
+ paths << f
351
+ end
352
+ end
353
+ end
354
+
355
+ paths.each do |p|
356
+ open(File.join(p,".emptydir"), "w").close
357
+ end
358
+
359
+ end
360
+
361
+
362
+ def status args, options
363
+ options ||= {}
364
+ repository = "."
365
+ path = "."
366
+ main_project = nil
367
+ scm = options[:scm]
368
+ scm ||= infer_scm(repository)
369
+
370
+ if !scm
371
+ scm ||= configuration['main']
372
+ scm &&= scm['scm']
373
+ end
374
+
375
+ if !scm
376
+ possible_project_classes = self.class.project_classes.select do |project_class|
377
+ project_class.detected?
378
+ end
379
+
380
+ raise "Could not determine this projects scm" if possible_project_classes.empty?
381
+ if possible_project_classes.size > 1
382
+ raise "This project appears to be managed by multiple SCMs: #{
383
+ possible_project_classes.map(&:to_s).join(',')}
384
+ Please explicitly declare the SCM (by using --git or --svn, or,
385
+ by creating the .externals file manually"
386
+ end
387
+
388
+ scm = possible_project_classes.first.scm
389
+ end
390
+
391
+ unless scm
392
+ raise "You need to either specify the scm as the first line in .externals, or use an option to specify it
393
+ (such as --git or --svn)"
394
+ end
395
+
396
+ main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
397
+ main_project.st
398
+
399
+ self.class.new({}).st [], {} #args, options
400
+ end
401
+
402
+ def update args, options
403
+ options ||= {}
404
+ repository = args[0]
405
+ main_project = nil
406
+ scm = options[:scm]
407
+ scm ||= infer_scm(repository)
408
+
409
+ if !scm
410
+ scm ||= configuration['main']
411
+ scm &&= scm['scm']
412
+ end
413
+
414
+ unless scm
415
+ raise "You need to either specify the scm as the first line in .externals, or use an option to specify it
416
+ (such as --git or --svn)"
417
+ end
418
+
419
+ main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
420
+ main_project.up
421
+
422
+ self.class.new({}).up [], {} #args, options
423
+ end
424
+
425
+ def checkout args, options
426
+ options ||= {}
427
+
428
+ repository = args[0]
429
+ path = args[1] || "."
430
+
431
+ main_project = do_checkout_or_export repository, path, options, :checkout
432
+
433
+ if path == "."
434
+ path = main_project.name
435
+ end
436
+
437
+ Dir.chdir path do
438
+ self.class.new({}).co [], {} #args, options
439
+ end
440
+ end
441
+
442
+ def export args, options
443
+ options ||= {}
444
+
445
+ repository = args[0]
446
+ path = args[1] || "."
447
+
448
+ main_project = do_checkout_or_export repository, path, options, :checkout
449
+
450
+ if path == "."
451
+ path = main_project.name
452
+ end
453
+
454
+ Dir.chdir path do
455
+ self.class.new({}).ex [], {} #args, options
456
+ end
457
+ end
458
+
459
+ def init args, options = {}
460
+ raise ".externals already exists" if File.exists? '.externals'
461
+
462
+ scm = options[:scm]
463
+ type = options[:type]
464
+
465
+ if !scm
466
+ possible_project_classes = self.class.project_classes.select do |project_class|
467
+ project_class.detected?
468
+ end
469
+
470
+ raise "Could not determine this projects scm" if possible_project_classes.empty?
471
+ if possible_project_classes.size > 1
472
+ raise "This project appears to be managed by multiple SCMs: #{
473
+ possible_project_classes.map(&:to_s).join(',')}
474
+ Please explicitly declare the SCM (using --git or --svn, or, by creating .externals manually"
475
+ end
476
+
477
+ scm = possible_project_classes.first.scm
478
+ end
479
+
480
+ if !type
481
+ possible_project_types = self.class.project_types.select do |project_type|
482
+ self.class.project_type_detector(project_type).detected?
483
+ end
484
+
485
+ if possible_project_types.size > 1
486
+ raise "We found multiple project types that this could be: #{possible_project_types.join(',')}
487
+ Please use the --type option to tell ext which to use."
488
+ elsif possible_project_types.size == 0
489
+ puts "WARNING: We could not automatically determine the project type.
490
+ Be sure to specify paths when adding subprojects to your .externals file"
491
+ else
492
+ type = possible_project_types.first
493
+ end
494
+ end
495
+
496
+ config = Configuration::Configuration.new_empty
497
+
498
+ config.sections << Configuration::Section.new("[main]\n",
499
+ "scm = #{scm}\n" +
500
+ "#{'type = ' + type if type}\n")
501
+
502
+ config.write
503
+ reload_configuration
504
+ end
505
+
506
+ def self.project_type_detector name
507
+ Externals.module_eval("#{name.classify}Detector", __FILE__, __LINE__)
508
+ end
509
+
510
+ def install_project_type name
511
+ Externals.module_eval("#{name.classify}ProjectType", __FILE__, __LINE__).install
512
+ end
513
+ #
514
+ #
515
+ # def self.determine_project_type path = "."
516
+ # Dir.chdir path do
517
+ # raise "not done"
518
+ # end
519
+ # end
520
+
521
+ protected
522
+ def do_checkout_or_export repository, path, options, sym
523
+ if File.exists?('.externals')
524
+ raise "seems main project is already checked out here?"
525
+ else
526
+ #We appear to be attempting to checkout/export a main project
527
+ scm = options[:scm]
528
+
529
+ scm ||= infer_scm(repository)
530
+
531
+ if !scm
532
+ scm ||= configuration['main']
533
+ scm &&= scm['scm']
534
+ end
535
+
536
+ unless scm
537
+ raise "You need to either specify the scm as the first line in .externals, or use an option to specify it
538
+ (such as --git or --svn)"
539
+ end
540
+
541
+ main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
542
+
543
+ main_project.send(sym)
544
+ main_project
545
+ end
546
+ end
547
+
548
+ def infer_scm(path)
549
+ self.class.registered_scms.each do |scm|
550
+ return scm if self.class.project_class(scm).scm_path?(path)
551
+ end
552
+ nil
553
+ end
554
+ end
555
+ end
556
+
557
+