drupid 1.0.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/bin/drupid +270 -0
- data/lib/drupid/component.rb +236 -0
- data/lib/drupid/download_strategy.rb +585 -0
- data/lib/drupid/drush.rb +185 -0
- data/lib/drupid/extend/pathname.rb +114 -0
- data/lib/drupid/library.rb +52 -0
- data/lib/drupid/makefile.rb +423 -0
- data/lib/drupid/patch.rb +92 -0
- data/lib/drupid/platform.rb +234 -0
- data/lib/drupid/platform_project.rb +91 -0
- data/lib/drupid/project.rb +563 -0
- data/lib/drupid/updater.rb +683 -0
- data/lib/drupid/utils.rb +301 -0
- data/lib/drupid/version.rb +230 -0
- data/lib/drupid.rb +56 -0
- metadata +76 -0
@@ -0,0 +1,563 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# Copyright (c) 2012 Lifepillar
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
13
|
+
# copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
# SOFTWARE.
|
22
|
+
|
23
|
+
module Drupid
|
24
|
+
|
25
|
+
# A convenience class that encapsulates methods for detecting
|
26
|
+
# some project's properties from a local copy of a project.
|
27
|
+
class ProjectInfo
|
28
|
+
include Drupid::Utils
|
29
|
+
|
30
|
+
# The project's name.
|
31
|
+
attr :project_name
|
32
|
+
# The project's core compatibility number. See Drupid::VersionCore.
|
33
|
+
attr :project_core
|
34
|
+
# The project's version. See Drupid::Version.
|
35
|
+
attr :project_version
|
36
|
+
# The project's type ('core', 'module', 'theme', 'profile').
|
37
|
+
attr :project_type
|
38
|
+
# The full path to the project's directory.
|
39
|
+
attr :project_dir
|
40
|
+
# The full path to the main .info file of this project
|
41
|
+
attr :info_file
|
42
|
+
|
43
|
+
# The argument must be the path to a .info file or a project directory.
|
44
|
+
# If the path to a directory is passed, then this object will try
|
45
|
+
# automatically detect the .info file inside the directory, which is
|
46
|
+
# not a trivial task because, among the rest,
|
47
|
+
#
|
48
|
+
# - the .info file may be located in some sub-directory;
|
49
|
+
# - there may be more than one .info file;
|
50
|
+
# - the .info file name may be unrelated to the name of its containing
|
51
|
+
# directories.
|
52
|
+
#
|
53
|
+
# Faithful to its name, Drupid won't try to be too keen, and it will raise
|
54
|
+
# an exception if it cannot reliably detect a .info file.
|
55
|
+
def initialize(project_or_info_path)
|
56
|
+
p = Pathname.new(project_or_info_path).realpath # must exist
|
57
|
+
@project_core = nil
|
58
|
+
@project_type = nil
|
59
|
+
@project_version = nil
|
60
|
+
if '.info' == p.extname
|
61
|
+
@project_name = p.basename('.info').to_s
|
62
|
+
@info_file = p
|
63
|
+
grandparent = @info_file.parent.parent
|
64
|
+
if grandparent.basename.to_s == @project_name
|
65
|
+
@project_dir = grandparent
|
66
|
+
else
|
67
|
+
@project_dir = @info_file.parent
|
68
|
+
end
|
69
|
+
else
|
70
|
+
@project_name = p.basename.to_s
|
71
|
+
@project_dir = p
|
72
|
+
@info_file = _identify_main_info_file
|
73
|
+
end
|
74
|
+
debug "Parsing project info from #{@info_file}"
|
75
|
+
_parse_info_file
|
76
|
+
end
|
77
|
+
|
78
|
+
def core_project?
|
79
|
+
@is_core_project
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Returns the absolute path of the "main" .info file of this project.
|
85
|
+
# The first file satisfying one of the following heuristics is returned:
|
86
|
+
#
|
87
|
+
# 1. './#name.info' exists.
|
88
|
+
# 2. './#name/#name.info' exists.
|
89
|
+
# 3. './<any name>.info' exists and no other .info file exists at the top-level.
|
90
|
+
# 4. './#name/<any name>.info' exists and no other .info file exists inside './#name'.
|
91
|
+
# 5. There is a unique .info file, anywhere inside the project's folder.
|
92
|
+
#
|
93
|
+
# If none of the above is satisfied, pick any .info file and set the
|
94
|
+
# project's name after the .info file's 'project' field (if any). Then
|
95
|
+
# return that .info file.
|
96
|
+
#
|
97
|
+
# Finally, if the .info file has no 'project' field, give up
|
98
|
+
# hoping that one day Drupal will have better specifications and that people
|
99
|
+
# will eventually follow the specifications—but complain fiercely
|
100
|
+
# by raising an exception.
|
101
|
+
def _identify_main_info_file
|
102
|
+
attempts = [
|
103
|
+
@project_dir + (@project_name + '.info'),
|
104
|
+
@project_dir + @project_name + (@project_name + '.info'),
|
105
|
+
@project_dir + '*.info',
|
106
|
+
@project_dir + @project_name + '*.info',
|
107
|
+
@project_dir+'**/*.info'
|
108
|
+
]
|
109
|
+
attempts.each do |p|
|
110
|
+
list = Pathname.glob(p)
|
111
|
+
if 1 == list.size and list.first.exist?
|
112
|
+
# Set the project's name after the .info file name
|
113
|
+
@project_name = list.first.basename('.info').to_s
|
114
|
+
return list.first
|
115
|
+
end
|
116
|
+
end
|
117
|
+
# We get here if all the above has failed.
|
118
|
+
Pathname.glob(@project_dir+'**/*.info').each do |p|
|
119
|
+
data = p.open("r").read
|
120
|
+
match = data.match(/project\s*=\s*["']?(.+)["']?/)
|
121
|
+
unless match.nil?
|
122
|
+
@project_name = match[1].strip
|
123
|
+
return p
|
124
|
+
end
|
125
|
+
end
|
126
|
+
# Give up :/
|
127
|
+
raise "The .info file for #{@project_name} cannot be reliably detected"
|
128
|
+
end
|
129
|
+
|
130
|
+
# Extracts the relevant information from the .info file.
|
131
|
+
def _parse_info_file
|
132
|
+
_read_info_file
|
133
|
+
_check_project_name
|
134
|
+
_set_project_version
|
135
|
+
_set_project_type
|
136
|
+
end
|
137
|
+
|
138
|
+
# Reads the content of the .info file into a hash.
|
139
|
+
# Parses only 'simple' key-value pairs (of the form X = v).
|
140
|
+
# Then, check for some other keys useful to determine the project's type
|
141
|
+
# (e.g, 'stylesheets')
|
142
|
+
def _read_info_file
|
143
|
+
@info_data = Hash.new
|
144
|
+
data = @info_file.open("r").read
|
145
|
+
data.each_line do |l|
|
146
|
+
next if l =~ /^\s*$/
|
147
|
+
next if l =~ /^\s*;/
|
148
|
+
if l.match(/^(.+)=(.+)$/)
|
149
|
+
key = $~[1].strip
|
150
|
+
value = $~[2].strip.gsub(/\A["']|["']\Z/, '')
|
151
|
+
@info_data[key] = value
|
152
|
+
end
|
153
|
+
end
|
154
|
+
@info_data['stylesheets'] = true if data.match(/^\s*stylesheets */)
|
155
|
+
@info_data['regions'] = true if data.match(/^\s*regions */)
|
156
|
+
end
|
157
|
+
|
158
|
+
# If the .info file name differs from the name of the containing directory
|
159
|
+
# and the .info file contains a 'project' field, do the following:
|
160
|
+
#
|
161
|
+
# - if <project name>.info does not exist and if the 'project' field is
|
162
|
+
# the same as the .info file name or the directory name, update the project's
|
163
|
+
# name accordingly.
|
164
|
+
#
|
165
|
+
# This check will fix, for example, the project's name for a project like
|
166
|
+
# Google Analytics, whose project's name is 'google_analytics' but the .info
|
167
|
+
# file is called 'googleanalytics.info'.
|
168
|
+
# It will also fix the project's name when the directory name has been
|
169
|
+
# changed and this object has been passed the path to the project's directory
|
170
|
+
# rather than the path to the .info file.
|
171
|
+
#
|
172
|
+
# Testing that <project name>.info does not exist is necessary to avoid
|
173
|
+
# renaming projects when more than one .info file exists in the same directory
|
174
|
+
# (see for example the Entity module).
|
175
|
+
def _check_project_name
|
176
|
+
dirname = @project_dir.basename.to_s
|
177
|
+
if @project_name != dirname and # E.g., 'featured_news' != 'featured_news_feature'
|
178
|
+
!(@info_file.dirname+(dirname+'.info')).exist? and # E.g., '.../featured_news_feature/featured_news_feature.info' does not exist
|
179
|
+
@info_data.has_key?('project')
|
180
|
+
pn = @info_data['project']
|
181
|
+
if pn == @info_file.basename('.info').to_s or pn == dirname
|
182
|
+
@project_name = pn
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def _set_project_core
|
188
|
+
@info_data['core'].match(/^(\d+)\.x$/)
|
189
|
+
raise "Missing mandatory core compatibility for #{@project_name}" unless $1
|
190
|
+
@project_core = VersionCore.new($1)
|
191
|
+
end
|
192
|
+
|
193
|
+
def _set_project_version
|
194
|
+
_set_project_core
|
195
|
+
if @info_data.has_key?('version')
|
196
|
+
v = @info_data['version']
|
197
|
+
v = @project_core.to_s + '-' + v if v !~ /^#{@project_core}-/
|
198
|
+
@project_version = Version.from_s(v)
|
199
|
+
else
|
200
|
+
@project_version = nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# *Requires:* @info_data must not be nil
|
205
|
+
def _set_project_type
|
206
|
+
# Determine whether this is a core project
|
207
|
+
if (@info_data.has_key?('package') and @info_data['package'] =~ /Core/i) or
|
208
|
+
(@info_data.has_key?('project') and @info_data['project'] =~ /drupal/i)
|
209
|
+
@is_core_project = true
|
210
|
+
else
|
211
|
+
@is_core_project = false
|
212
|
+
end
|
213
|
+
# Determine the project's type (module, profile or theme)
|
214
|
+
if @info_file.sub_ext('.profile').exist?
|
215
|
+
@project_type = 'profile'
|
216
|
+
elsif @info_file.sub_ext('.module').exist?
|
217
|
+
@project_type = 'module'
|
218
|
+
elsif @info_data.has_key?('engine') or
|
219
|
+
@info_data.has_key?('Base theme') or
|
220
|
+
@info_data.has_key?('base theme') or
|
221
|
+
@info_data.has_key?('stylesheets') or
|
222
|
+
@info_data.has_key?('regions')
|
223
|
+
@project_type = 'theme'
|
224
|
+
end
|
225
|
+
# If the above didn't work, examine the path the project is in as a last resort.
|
226
|
+
# This is needed, at least, to avoid "type cannot be determined" errors
|
227
|
+
# for some test directories in Drupal Core, which contain an .info file
|
228
|
+
# but no other file :/
|
229
|
+
unless project_type
|
230
|
+
@project_dir.each_filename do |p|
|
231
|
+
case p
|
232
|
+
when 'modules'
|
233
|
+
@project_type = 'module'
|
234
|
+
when 'themes'
|
235
|
+
@project_type = 'theme'
|
236
|
+
when 'profiles'
|
237
|
+
@project_type = 'profile'
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
raise "The project's type for #{@project_name} cannot be determined" unless @project_type
|
242
|
+
end
|
243
|
+
|
244
|
+
end # ProjectInfo
|
245
|
+
|
246
|
+
|
247
|
+
# Base class for projects.
|
248
|
+
class Project < Component
|
249
|
+
include Comparable
|
250
|
+
|
251
|
+
attr :core
|
252
|
+
attr_accessor :location
|
253
|
+
# The type of this project, which is one among 'drupal', 'module', 'theme'
|
254
|
+
# and 'profile', or nil if the type has not been determined or assigned.
|
255
|
+
# Note that this does not coincide with the 'type' field in a Drush makefile,
|
256
|
+
# whose feasible values are 'core', 'module', 'theme', 'profile'.
|
257
|
+
attr_accessor :proj_type
|
258
|
+
attr_accessor :l10n_path
|
259
|
+
attr_accessor :l10n_url
|
260
|
+
|
261
|
+
# Creates a new project with a given name and compatibility number.
|
262
|
+
# Optionally, specify a short version string (i.e., a version string
|
263
|
+
# without core compatibility number).
|
264
|
+
#
|
265
|
+
# Examples:
|
266
|
+
# p = Drupid::Project.new('cck', 6)
|
267
|
+
# p = Drupid::Project.new('views', 7, '1.2')
|
268
|
+
def initialize name, core_num, vers = nil
|
269
|
+
super(name)
|
270
|
+
@core = VersionCore.new(core_num)
|
271
|
+
@core_project = ('drupal' == @name) ? true : nil
|
272
|
+
@version = vers ? Version.from_s(@core.to_s + '-' + vers) : nil
|
273
|
+
@proj_type = ('drupal' == @name) ? 'drupal' : nil
|
274
|
+
@info_file = nil
|
275
|
+
end
|
276
|
+
|
277
|
+
# Returns true if a version is specified for this project, false otherwise.
|
278
|
+
def has_version?
|
279
|
+
nil != @version
|
280
|
+
end
|
281
|
+
|
282
|
+
# Returns the version of this project as a Drupid::Version object,
|
283
|
+
# or nil if this project has not been assigned a version.
|
284
|
+
def version
|
285
|
+
@version
|
286
|
+
end
|
287
|
+
|
288
|
+
# Assigns a version to this project.
|
289
|
+
# The argument must be a String object or a Drupid::Version object.
|
290
|
+
# For the syntax of the String argument, see Drupid::Version.
|
291
|
+
def version=(new_version)
|
292
|
+
if new_version.is_a?(Version)
|
293
|
+
temp_version = new_version
|
294
|
+
elsif new_version.is_a?(String)
|
295
|
+
v = new_version
|
296
|
+
temp_version = Version.from_s(v)
|
297
|
+
else
|
298
|
+
raise NotDrupalVersionError
|
299
|
+
end
|
300
|
+
raise NotDrupalVersionError, "Incompatible version for project #{extended_name}: #{temp_version.long}" if temp_version.core != core
|
301
|
+
@version = temp_version
|
302
|
+
end
|
303
|
+
|
304
|
+
# Updates the version of this project to the latest recommended release;
|
305
|
+
# if no recommended release is available, tries to retrieve the version
|
306
|
+
# of the latest supported release; if no supported release is found, the version
|
307
|
+
# of this project is not modified.
|
308
|
+
# Raises an error if no release history for the project is found (presumably
|
309
|
+
# because no such project exists on drupal.org).
|
310
|
+
#
|
311
|
+
# *Requires:* a network connection.
|
312
|
+
def update_version
|
313
|
+
v = recommended_release
|
314
|
+
v = supported_release unless v
|
315
|
+
if v
|
316
|
+
self.version = core.to_s + '-' + v
|
317
|
+
debug "Version updated: #{extended_name}"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Returns true if this object corresponds to Drupal core;
|
322
|
+
# returns false otherwise.
|
323
|
+
def drupal?
|
324
|
+
'drupal' == proj_type
|
325
|
+
end
|
326
|
+
|
327
|
+
# Returns true if this is a profile; returns false otherwise.
|
328
|
+
def profile?
|
329
|
+
'profile' == proj_type
|
330
|
+
end
|
331
|
+
|
332
|
+
# Returns true if this is a core project; returns false otherwise.
|
333
|
+
def core_project?
|
334
|
+
@core_project
|
335
|
+
end
|
336
|
+
|
337
|
+
def core_project=(c)
|
338
|
+
@core_project = c
|
339
|
+
end
|
340
|
+
|
341
|
+
# See Version for the reason why we define == explicitly.
|
342
|
+
def ==(other)
|
343
|
+
@name == other.name and
|
344
|
+
@core == other.core and
|
345
|
+
@version == other.version
|
346
|
+
end
|
347
|
+
|
348
|
+
# Compares this project with another to determine which is newer.
|
349
|
+
# The comparison returns nil if the two projects have different names
|
350
|
+
# or at least one of them has no version;
|
351
|
+
# otherwise, returns -1 if this project is older than the other,
|
352
|
+
# 1 if this project is more recent than the other,
|
353
|
+
# 0 if this project has the same version as the other.
|
354
|
+
def <=>(other)
|
355
|
+
return nil if @name != other.name
|
356
|
+
c = core <=> other.core
|
357
|
+
if 0 == c
|
358
|
+
return nil unless has_version? and other.has_version?
|
359
|
+
return version <=> other.version
|
360
|
+
else
|
361
|
+
return c
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
# Returns the name and the version of this project as a string, e.g.,
|
366
|
+
# 'media-7.x-2.0-unstable2' or 'drupal-7.14'.
|
367
|
+
# If no version is specified for this project,
|
368
|
+
# returns only the project's name and core compatibility number.
|
369
|
+
def extended_name
|
370
|
+
if has_version?
|
371
|
+
return name + '-' + ((drupal?) ? version.short : version.long)
|
372
|
+
else
|
373
|
+
return name + '-' + core.to_s
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# Returns a list of the names of the extensions (modules and themes) upon
|
378
|
+
# which this project and its subprojects (the projects contained within
|
379
|
+
# this one) depend.
|
380
|
+
# Returns an empty list if no local copy of this project exists.
|
381
|
+
#
|
382
|
+
# If :subprojects is set to false, subprojects' dependencies are not computed.
|
383
|
+
#
|
384
|
+
# Options: subprojects
|
385
|
+
def dependencies options = {}
|
386
|
+
return [] unless exist?
|
387
|
+
deps = Array.new
|
388
|
+
if options.has_key?(:subprojects) and (not options[:subprojects])
|
389
|
+
reload_project_info unless @info_file and @info_file.exist?
|
390
|
+
info_files = [@info_file]
|
391
|
+
else
|
392
|
+
info_files = Dir["#{local_path}/**/*.info"]
|
393
|
+
end
|
394
|
+
info_files.each do |info|
|
395
|
+
f = File.open(info, "r").read
|
396
|
+
f.each_line do |l|
|
397
|
+
matchdata = l.match(/^\s*dependencies\s*\[\s*\]\s*=\s*["']?([^\s("']+)/)
|
398
|
+
if nil != matchdata
|
399
|
+
deps << matchdata[1].strip
|
400
|
+
end
|
401
|
+
matchdata = l.match(/^\s*base +theme\s*=\s*(.+)$/)
|
402
|
+
if nil != matchdata
|
403
|
+
d = matchdata[1].strip
|
404
|
+
deps << d.gsub(/\A["']|["']\Z/, '') # Strip leading and trailing quotes
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
# Remove duplicates and self-dependency
|
409
|
+
deps.uniq!
|
410
|
+
deps.delete(name)
|
411
|
+
return deps
|
412
|
+
end
|
413
|
+
|
414
|
+
# Returns a list of the names of the extensions (modules and themes)
|
415
|
+
# contained in this project.
|
416
|
+
# Returns a list containing only the project's name
|
417
|
+
# if no local copy of this project exists.
|
418
|
+
def extensions
|
419
|
+
return [name] unless exist?
|
420
|
+
# Note that the project's name may be different from the name of the .info file.
|
421
|
+
ext = [name]
|
422
|
+
Dir["#{local_path}/**/*.info"].map do |p|
|
423
|
+
ext << File.basename(p, '.info')
|
424
|
+
end
|
425
|
+
ext.uniq!
|
426
|
+
return ext
|
427
|
+
end
|
428
|
+
|
429
|
+
def reload_project_info
|
430
|
+
project_info = ProjectInfo.new(@local_path)
|
431
|
+
raise "Inconsistent naming: expected #{@name}, got #{project_info.project_name}" unless @name == project_info.project_name
|
432
|
+
raise "Inconsistent core: expected #{@core}, got #{project_info.project_core}" unless @core == project_info.project_core
|
433
|
+
@proj_type = project_info.project_type
|
434
|
+
@core_project = project_info.core_project?
|
435
|
+
@version = project_info.project_version
|
436
|
+
@info_file = project_info.info_file
|
437
|
+
end
|
438
|
+
|
439
|
+
def fetch
|
440
|
+
# Try to get the latest version if:
|
441
|
+
# (1) the project is not local;
|
442
|
+
# (2) it does not have a version already;
|
443
|
+
# (3) no download type has been explicitly given.
|
444
|
+
unless has_version? or download_url =~ /file:\/\// or download_type
|
445
|
+
update_version
|
446
|
+
end
|
447
|
+
# If the project has no version we fetch it even if it is cached.
|
448
|
+
# If the project has a download type, we fetch it even if it is cached
|
449
|
+
# (say the download type is 'git' and the revision is changed in the
|
450
|
+
# makefile, then the cached project must be updated accordingly).
|
451
|
+
if has_version? and !download_type and cached_location.exist?
|
452
|
+
@local_path = cached_location
|
453
|
+
debug "#{extended_name} is cached"
|
454
|
+
else
|
455
|
+
blah "Fetching #{extended_name}"
|
456
|
+
if download_type
|
457
|
+
if download_url
|
458
|
+
src = download_url
|
459
|
+
elsif 'git' == download_type # Download from git.drupal.org
|
460
|
+
src = "http://git.drupal.org/project/#{name}.git"
|
461
|
+
else
|
462
|
+
raise "No download URL specified for #{extended_name}" unless download_url
|
463
|
+
end
|
464
|
+
else
|
465
|
+
src = extended_name
|
466
|
+
end
|
467
|
+
downloader = Drupid.makeDownloader src, cached_location.dirname.to_s, cached_location.basename.to_s, download_specs.merge({:type => download_type})
|
468
|
+
downloader.fetch
|
469
|
+
downloader.stage
|
470
|
+
@local_path = downloader.staged_path
|
471
|
+
end
|
472
|
+
reload_project_info unless drupal?
|
473
|
+
end
|
474
|
+
|
475
|
+
# Returns the relative path where this project should be installed
|
476
|
+
# within a platform.
|
477
|
+
# For example, for a module called 'Foo', it might be something like
|
478
|
+
# 'modules/contrib/foo'.
|
479
|
+
def target_path
|
480
|
+
case proj_type
|
481
|
+
when 'drupal'
|
482
|
+
return Pathname.new('.')
|
483
|
+
when nil
|
484
|
+
raise "Undefined project type for #{name}."
|
485
|
+
else
|
486
|
+
return Pathname.new(proj_type + 's') + subdir + directory_name
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
# Returns the path to a makefile contained in this project, if any.
|
491
|
+
# Returns nil if this project does not contain any makefile.
|
492
|
+
# For an embedded makefile to be recognized, the makefile
|
493
|
+
# itself must be named '#name.make' or 'drupal-org.make'.
|
494
|
+
#
|
495
|
+
# *Requires:* a local copy of this project.
|
496
|
+
def makefile
|
497
|
+
return nil unless self.exist?
|
498
|
+
paths = [
|
499
|
+
local_path + "#{name}.make",
|
500
|
+
local_path + 'drupal-org.make' # Used in Drupal distributions
|
501
|
+
]
|
502
|
+
paths.each do |p|
|
503
|
+
return p if p.exist?
|
504
|
+
end
|
505
|
+
return nil
|
506
|
+
end
|
507
|
+
|
508
|
+
# Compares this project with another, returning an array of differences.
|
509
|
+
# If this project contains a makefile, ignore the content of the following
|
510
|
+
# directories inside the project: libraries, modules, profiles and themes.
|
511
|
+
def file_level_compare_with tgt
|
512
|
+
args = Array.new
|
513
|
+
if makefile
|
514
|
+
args << '-f' << '- /libraries/***' # this syntax requires rsync >=2.6.7.
|
515
|
+
args << '-f' << '- /modules/***'
|
516
|
+
args << '-f' << '- /profiles/***'
|
517
|
+
args << '-f' << '- /themes/***'
|
518
|
+
end
|
519
|
+
if drupal?
|
520
|
+
args << '-f' << '+ /profiles/default/***' # D6
|
521
|
+
args << '-f' << '+ /profiles/minimal/***' # D7
|
522
|
+
args << '-f' << '+ /profiles/standard/***' # D7
|
523
|
+
args << '-f' << '+ /profiles/testing/***' # D7
|
524
|
+
args << '-f' << '- /profiles/***'
|
525
|
+
args << '-f' << '+ /sites/all/README.txt'
|
526
|
+
args << '-f' << '+ /sites/default/default.settings.php'
|
527
|
+
args << '-f' << '- /sites/***'
|
528
|
+
end
|
529
|
+
super(tgt, args)
|
530
|
+
end
|
531
|
+
|
532
|
+
# Returns the version of the latest recommended release of this project
|
533
|
+
# as a string, or nil if no recommended release can be found
|
534
|
+
# or the project does not exist at drupal.org.
|
535
|
+
#
|
536
|
+
# *Requires:* a network connection.
|
537
|
+
#
|
538
|
+
# See also: Drupid::Drush.pm_releases, Drupid::Project.supported_release
|
539
|
+
def recommended_release
|
540
|
+
output = Drush.pm_releases(name + '-' + core.to_s)
|
541
|
+
return unless output =~ /RELEASES FOR/
|
542
|
+
rl = Drush.recommended_release(output)
|
543
|
+
return rl.sub(/^#{core}-/, '') if rl
|
544
|
+
return nil
|
545
|
+
end
|
546
|
+
|
547
|
+
# Returns the version of the latest supported release
|
548
|
+
# as a string, or nil if no supported release can be found
|
549
|
+
# or the project does not exist at drupal.org.
|
550
|
+
#
|
551
|
+
# *Requires:* a network connection.
|
552
|
+
#
|
553
|
+
# See also: Drupid::Drush.pm_releases, Drupid::Project.recommended_release
|
554
|
+
def supported_release
|
555
|
+
output = Drush.pm_releases(name + '-' + core.to_s)
|
556
|
+
return nil unless output =~ /RELEASES FOR/
|
557
|
+
rl = Drush.supported_release(output)
|
558
|
+
return rl.sub(/^#{core}-/, '') if rl
|
559
|
+
return nil
|
560
|
+
end
|
561
|
+
|
562
|
+
end # Project
|
563
|
+
end # Drupid
|