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,423 @@
|
|
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
|
+
class ParseMakefileError < RuntimeError
|
26
|
+
end
|
27
|
+
|
28
|
+
# Representation of a Drush makefile.
|
29
|
+
#
|
30
|
+
# See also: http://drupal.org/node/625094
|
31
|
+
class Makefile
|
32
|
+
include Drupid::Utils
|
33
|
+
|
34
|
+
# The absolute path to the makefile.
|
35
|
+
attr :path
|
36
|
+
# The value of the core field of the makefile (e.g, '7.x')
|
37
|
+
attr :core
|
38
|
+
# The value of the api field of the makefile (e.g., '2')
|
39
|
+
attr :api
|
40
|
+
# The path for contrib modules and themes (e.g., 'sites/all'),
|
41
|
+
# relative to #path.
|
42
|
+
attr_accessor :contrib_path
|
43
|
+
|
44
|
+
# Creates a new Makefile object. The path must be the path
|
45
|
+
# to a .make file (which does not need to exist).
|
46
|
+
def initialize(path)
|
47
|
+
@path = Pathname.new(path)
|
48
|
+
raise "Not an absolute path: #{@path}" unless @path.absolute?
|
49
|
+
@core = nil
|
50
|
+
@api = nil
|
51
|
+
@projects = Hash.new # (String -> Project)
|
52
|
+
@libraries = Hash.new # (String -> Library)
|
53
|
+
@contrib_path = Pathname.new('sites/all')
|
54
|
+
debug "Parsing #{@path}"
|
55
|
+
self.reload if @path.exist?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Reloads the makefile.
|
59
|
+
# This method is invoked automatically at creation time
|
60
|
+
# if a path to an existing makefile is provided.
|
61
|
+
def reload
|
62
|
+
@core = nil
|
63
|
+
@api = nil
|
64
|
+
@projects = Hash.new
|
65
|
+
@libraries = Hash.new
|
66
|
+
|
67
|
+
proj_patches = Hash.new
|
68
|
+
libs_patches = Hash.new
|
69
|
+
core_num = nil
|
70
|
+
mf = File.open(@path.to_s, "r").read
|
71
|
+
# Parse includes directives
|
72
|
+
while mf.match(/^([ \t]*includes\[.*\]\s*=\s*"?([^\s"]+)"?[ \t]*)$/) do
|
73
|
+
# TODO: add support for remote includes
|
74
|
+
url = $2
|
75
|
+
blah "Including makefile #{url}"
|
76
|
+
inc = File.open(url, "r").read
|
77
|
+
mf.sub!($1, inc)
|
78
|
+
end
|
79
|
+
if mf.match(/core *= *["']? *(\d+)\.?(\d+)?/) # Get the core number immediately
|
80
|
+
@core = $~[1] + '.x'
|
81
|
+
core_num = $~[1].to_i
|
82
|
+
vers = $~[2] ? $~[1] + '.' + $~[2] : nil
|
83
|
+
# Create Drupal project
|
84
|
+
@projects['drupal'] = Project.new('drupal', core_num, vers)
|
85
|
+
end
|
86
|
+
raise ParseMakefileError, "The makefile does not contain the mandatory 'core' field" unless core_num
|
87
|
+
lineno = 0
|
88
|
+
mf.each_line do |line|
|
89
|
+
lineno += 1
|
90
|
+
next if line =~ /^\s*$/
|
91
|
+
next if line =~ /^\s*;/
|
92
|
+
next if line =~ /^\s*core/
|
93
|
+
# match[1] : the key ('core', 'version', 'api', 'projects', 'libraries', 'includes')
|
94
|
+
# match[2] : the (optional) key arguments (stuff between square brackets)
|
95
|
+
# match[3] : the same as match[2], but without the leftmost [ and the rightmost ]
|
96
|
+
# match[4] : the value
|
97
|
+
# Examples:
|
98
|
+
# (a) Given 'projects[ctools][version] = 1.0-rc1', we have
|
99
|
+
# match[1] == 'projects'
|
100
|
+
# match[2] == '[ctools][version]'
|
101
|
+
# match[3] == 'ctools][version'
|
102
|
+
# match[4] == '1.0-rc1'
|
103
|
+
# (b) Given 'core = 7.x', we have:
|
104
|
+
# match[1] == 'core'
|
105
|
+
# match[3] == nil
|
106
|
+
# match[4] == '7.x'
|
107
|
+
match = line.match(/^\s*([^\s\[=]+)\s*(\[\s*(.*?)\s*\])?\s*=\s*["']?([^\s"'(]+)/)
|
108
|
+
raise ParseMakefileError, "Could not parse line: #{line.strip} (line #{lineno})" if match.nil? or match.size != 5
|
109
|
+
key = match[1]
|
110
|
+
args = (match[3]) ? match[3].split(/\]\s*\[/) : []
|
111
|
+
value = match[4].strip
|
112
|
+
case key
|
113
|
+
when 'api'
|
114
|
+
@api = value
|
115
|
+
when 'projects'
|
116
|
+
if 0 == args.size # e.g., projects[] = views
|
117
|
+
name = value
|
118
|
+
@projects[name] = Project.new(name, core_num)
|
119
|
+
else
|
120
|
+
name = args[0]
|
121
|
+
@projects[name] = Project.new(name, core_num) unless @projects.has_key?(name)
|
122
|
+
case args.size
|
123
|
+
when 1 # e.g., projects[views] = 2.8
|
124
|
+
@projects[name].version = @core+'-'+value.sub(/^#{@core}-/,'')
|
125
|
+
when 2 # e.g., projects[views][version] = 2.8 or projects[calendar][patch][] = 'http://...'
|
126
|
+
case args[1]
|
127
|
+
when 'version'
|
128
|
+
@projects[name].version = @core+'-'+value.sub(/^#{@core}-/,'')
|
129
|
+
when 'patch'
|
130
|
+
patch_key = File.basename(value)
|
131
|
+
patch_url = _normalize_path(value)
|
132
|
+
@projects[name].add_patch(patch_url, patch_key)
|
133
|
+
when 'subdir'
|
134
|
+
@projects[name].subdir = value
|
135
|
+
when 'location'
|
136
|
+
@projects[name].location = _normalize_path(value)
|
137
|
+
when 'directory_name'
|
138
|
+
@projects[name].directory_name = value
|
139
|
+
when 'type'
|
140
|
+
if 'core' == value
|
141
|
+
@projects[name].core_project = true
|
142
|
+
else
|
143
|
+
raise ParseMakefileError, "Illegal value: #{args[1]} (line #{lineno})" unless value =~ /^(module|profile|theme)$/
|
144
|
+
@projects[name].proj_type = value
|
145
|
+
end
|
146
|
+
when 'l10n_path'
|
147
|
+
# TODO: add support for tokens
|
148
|
+
@projects[name].l10n_path = _normalize_path(value)
|
149
|
+
when 'l10n_url'
|
150
|
+
@projects[name].l10n_url = _normalize_path(value)
|
151
|
+
when 'overwrite'
|
152
|
+
@projects[name].overwrite = true if value =~ /TRUE/i
|
153
|
+
else
|
154
|
+
raise ParseMakefileError, "Unknown key: #{args[1]} (line #{lineno})"
|
155
|
+
end
|
156
|
+
when 3 # e.g., projects[mytheme][download][type] = "svn"
|
157
|
+
name = args[0]
|
158
|
+
subkey = args[1]
|
159
|
+
case subkey
|
160
|
+
when 'download'
|
161
|
+
case args[2]
|
162
|
+
when 'type'
|
163
|
+
@projects[name].download_type = value
|
164
|
+
when 'url'
|
165
|
+
@projects[name].download_url = _normalize_path(value)
|
166
|
+
else
|
167
|
+
@projects[name].add_download_spec(args[2], value)
|
168
|
+
end
|
169
|
+
else
|
170
|
+
raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
|
171
|
+
end
|
172
|
+
when 4 # e.g., projects[calendar][patch][rfc-fixes][md5] = "..."
|
173
|
+
name = args[0]
|
174
|
+
subkey = args[1]
|
175
|
+
case subkey
|
176
|
+
when 'patch'
|
177
|
+
patch_key = args[2]
|
178
|
+
proj_patches[name] ||= Hash.new
|
179
|
+
proj_patches[name][patch_key] ||= Hash.new
|
180
|
+
case args[3]
|
181
|
+
when 'url'
|
182
|
+
proj_patches[name][patch_key]['url'] = _normalize_path(value)
|
183
|
+
when 'md5'
|
184
|
+
proj_patches[name][patch_key]['md5'] = value
|
185
|
+
else
|
186
|
+
raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
|
187
|
+
end
|
188
|
+
else
|
189
|
+
raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
|
190
|
+
end
|
191
|
+
else # > 4 arguments
|
192
|
+
raise ParseMakefileError, "Too many arguments (line #{lineno})"
|
193
|
+
end # case
|
194
|
+
end # if
|
195
|
+
when 'libraries'
|
196
|
+
if 0 == args.size
|
197
|
+
raise ParseMakefileError, "Too few arguments (line #{lineno})"
|
198
|
+
else
|
199
|
+
name = args[0]
|
200
|
+
@libraries[name] = Library.new(name) unless @libraries.has_key?(name)
|
201
|
+
case args.size
|
202
|
+
when 1
|
203
|
+
raise ParseMakefileError, "Too few arguments (line #{lineno})"
|
204
|
+
when 2
|
205
|
+
case args[1]
|
206
|
+
when 'patch'
|
207
|
+
patch_key = File.basename(value)
|
208
|
+
patch_url = _normalize_path(value)
|
209
|
+
@libraries[name].add_patch(patch_url, patch_key)
|
210
|
+
when 'subdir'
|
211
|
+
@libraries[name].subdir = value
|
212
|
+
when 'destination'
|
213
|
+
@libraries[name].destination = value
|
214
|
+
when 'directory_name'
|
215
|
+
@libraries[name].directory_name = value
|
216
|
+
else
|
217
|
+
raise ParseMakefileError, "Unknown key: #{args[1]} (line #{lineno})"
|
218
|
+
end
|
219
|
+
when 3 # e.g., libraries[jquery_ui][download][type] = "file"
|
220
|
+
name = args[0]
|
221
|
+
subkey = args[1]
|
222
|
+
case subkey
|
223
|
+
when 'download'
|
224
|
+
case args[2]
|
225
|
+
when 'type'
|
226
|
+
@libraries[name].download_type = value
|
227
|
+
when 'url'
|
228
|
+
@libraries[name].download_url = _normalize_path(value)
|
229
|
+
else
|
230
|
+
@libraries[name].add_download_spec(args[2], value)
|
231
|
+
end
|
232
|
+
else
|
233
|
+
raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
|
234
|
+
end
|
235
|
+
when 4
|
236
|
+
name = args[0]
|
237
|
+
subkey = args[1]
|
238
|
+
case subkey
|
239
|
+
when 'patch'
|
240
|
+
patch_key = args[2]
|
241
|
+
libs_patches[name] ||= Hash.new
|
242
|
+
libs_patches[name][patch_key] ||= Hash.new
|
243
|
+
case args[3]
|
244
|
+
when 'url'
|
245
|
+
libs_patches[name][patch_key]['url'] = _normalize_path(value)
|
246
|
+
when 'md5'
|
247
|
+
libs_patches[name][patch_key]['md5'] = value
|
248
|
+
else
|
249
|
+
raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
|
250
|
+
end
|
251
|
+
else
|
252
|
+
raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
|
253
|
+
end
|
254
|
+
else # > 4 arguments
|
255
|
+
raise ParseMakefileError, "Too many arguments (line #{lineno})"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
when 'includes'
|
259
|
+
owarn "Unexpected 'includes' directive (line #{lineno})"
|
260
|
+
else
|
261
|
+
owarn "Could not parse key: #{key} (line #{lineno})"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
# Add missing patches
|
265
|
+
proj_patches.each do |proj_name, v|
|
266
|
+
v.each do |desc,prop|
|
267
|
+
@projects[proj_name].add_patch(prop['url'], desc, prop['md5'])
|
268
|
+
end
|
269
|
+
end
|
270
|
+
libs_patches.each do |lib_name, v|
|
271
|
+
v.each do |desc,prop|
|
272
|
+
@libraries[lib_name].add_patch(prop['url'], desc, prop['md5'])
|
273
|
+
end
|
274
|
+
end
|
275
|
+
return self
|
276
|
+
end
|
277
|
+
|
278
|
+
# Adds a project to this specification.
|
279
|
+
def add_project(p)
|
280
|
+
@projects[p.name] = p
|
281
|
+
end
|
282
|
+
|
283
|
+
# Returns the project with the specified name,
|
284
|
+
# or nil if the project is not in this specification.
|
285
|
+
def get_project(name)
|
286
|
+
@projects[name]
|
287
|
+
end
|
288
|
+
|
289
|
+
# Returns the library with the specified name.
|
290
|
+
# or nil if the library is not in this specification.
|
291
|
+
def get_library(name)
|
292
|
+
@libraries[name]
|
293
|
+
end
|
294
|
+
|
295
|
+
# Removes the project with the specified name from this specification.
|
296
|
+
def delete_project(name)
|
297
|
+
@projects.delete(name)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Iterates over the projects in this specification (excluding drupal).
|
301
|
+
def each_project
|
302
|
+
# For convenience, return the projects in lexicographical order.
|
303
|
+
names = @projects.keys.sort!
|
304
|
+
names.each do |n|
|
305
|
+
yield @projects[n] unless @projects[n].drupal?
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# Returns a Drupid::Project object for the Drupal core specified
|
310
|
+
# in the makefile, or nil if the makefile does not specify a Drupal distribution.
|
311
|
+
def drupal_project
|
312
|
+
@projects['drupal']
|
313
|
+
end
|
314
|
+
|
315
|
+
# Iterates over the libraries in this specification.
|
316
|
+
def each_library
|
317
|
+
# For convenience, return the libraries in lexicographical order.
|
318
|
+
names = @libraries.keys.sort!
|
319
|
+
names.each do |n|
|
320
|
+
yield @libraries[n]
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# Returns a list of the names of the projects mentioned
|
325
|
+
# in this specification (excluding drupal).
|
326
|
+
def project_names
|
327
|
+
@projects.values.reject { |p| p.drupal? }.map { |p| p.name }
|
328
|
+
end
|
329
|
+
|
330
|
+
# Returns a list of the names of the libraries mentioned
|
331
|
+
# in this specification.
|
332
|
+
def library_names
|
333
|
+
@libraries.keys
|
334
|
+
end
|
335
|
+
|
336
|
+
# Writes this makefile to disk.
|
337
|
+
# An alternative location may be specified as an argument.
|
338
|
+
def save(alt_path = @path)
|
339
|
+
File.open(alt_path.to_s, "w").write(to_s)
|
340
|
+
end
|
341
|
+
|
342
|
+
# Returns this makefile as a string.
|
343
|
+
def to_s
|
344
|
+
s = String.new
|
345
|
+
s << "core = #{@core}\n"
|
346
|
+
s << "api = #{@api}\n"
|
347
|
+
s << _project_to_record(drupal_project) if drupal_project
|
348
|
+
s << "\n" unless @projects.empty?
|
349
|
+
self.each_project { |p| s << _project_to_record(p) }
|
350
|
+
s << "\n" unless @libraries.empty?
|
351
|
+
self.each_library { |l| s << _library_to_record(l) }
|
352
|
+
s
|
353
|
+
end
|
354
|
+
|
355
|
+
private
|
356
|
+
|
357
|
+
def _normalize_path(u)
|
358
|
+
return u if u =~ /:\/\// # URL
|
359
|
+
if u =~ /^\// # Local absolute path
|
360
|
+
return 'file://' + u
|
361
|
+
else # Relative path
|
362
|
+
return 'file://' + (path.parent + u).to_s
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def _relativize_path(u)
|
367
|
+
return u unless u =~ /^file:\/\//
|
368
|
+
return Pathname.new(u.sub(/file:\/\//,'')).relative_path_from(path.dirname).to_s
|
369
|
+
end
|
370
|
+
|
371
|
+
def _project_to_record(p)
|
372
|
+
fields = Array.new
|
373
|
+
fields << "[type] = \"#{p.proj_type}\"" if p.proj_type =~ /module|profile|theme/
|
374
|
+
fields << "[version] = \"#{p.version.short}\"" if p.has_version?
|
375
|
+
fields << "[location] = \"#{_relativize_path(p.location)}\"" if p.location
|
376
|
+
fields << "[download][type] = \"#{p.download_type}\"" if p.download_type
|
377
|
+
fields << "[download][url] = \"#{_relativize_path(p.download_url)}\"" if p.download_url
|
378
|
+
temp = []
|
379
|
+
p.download_specs.each do |spec,ref|
|
380
|
+
temp << "[download][#{spec}] = \"#{ref}\""
|
381
|
+
end
|
382
|
+
fields = fields + temp.sort!
|
383
|
+
p.each_patch do |pa|
|
384
|
+
fields << "[patch][#{pa.descr}][url] = \"#{_relativize_path(pa.url)}\""
|
385
|
+
fields << "[patch][#{pa.descr}][md5] = \"#{pa.md5}\"" if pa.md5
|
386
|
+
end
|
387
|
+
fields << "[l10n_path] = \"#{_relativize_path(p.l10n_path)}\"" if p.l10n_path
|
388
|
+
fields << "[l10n_url] = \"#{_relativize_path(p.l10n_url)}\"" if p.l10n_url
|
389
|
+
fields << "[subdir] = \"#{p.subdir}\"" if '.' != p.subdir.to_s
|
390
|
+
fields << "[directory_name] = \"#{p.directory_name}\"" if p.directory_name != p.name
|
391
|
+
return "projects[] = \"#{p.name}\"\n" if 0 == fields.size
|
392
|
+
s = ''
|
393
|
+
fields.each do |f|
|
394
|
+
s << "projects[#{p.name}]" + f + "\n"
|
395
|
+
end
|
396
|
+
return s
|
397
|
+
end
|
398
|
+
|
399
|
+
def _library_to_record(l)
|
400
|
+
fields = Array.new
|
401
|
+
fields << "[download][type] = \"#{l.download_type}\"" if l.download_type
|
402
|
+
fields << "[download][url] = \"#{_relativize_path(l.download_url)}\"" if l.download_url
|
403
|
+
temp = []
|
404
|
+
l.download_specs.each do |spec,ref|
|
405
|
+
temp << "[download][#{spec}] = \"#{ref}\""
|
406
|
+
end
|
407
|
+
fields = fields + temp.sort!
|
408
|
+
l.each_patch do |pa|
|
409
|
+
fields << "[patch][#{pa.descr}][url] = \"#{pa.url}\""
|
410
|
+
fields << "[patch][#{pa.descr}][md5] = \"#{pa.md5}\"" if pa.md5
|
411
|
+
end
|
412
|
+
fields << "[destination] = \"#{l.destination}\""
|
413
|
+
fields << "[subdir] = \"#{l.subdir}\"" if '.' != l.subdir.to_s
|
414
|
+
fields << "[directory_name] = \"#{l.directory_name}\""
|
415
|
+
s = ''
|
416
|
+
fields.each do |f|
|
417
|
+
s << "libraries[#{l.name}]" + f + "\n"
|
418
|
+
end
|
419
|
+
return s
|
420
|
+
end
|
421
|
+
|
422
|
+
end # class Makefile
|
423
|
+
end # Drupid
|
data/lib/drupid/patch.rb
ADDED
@@ -0,0 +1,92 @@
|
|
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
|
+
class Patch
|
25
|
+
include Drupid::Utils
|
26
|
+
|
27
|
+
attr :url
|
28
|
+
attr :md5
|
29
|
+
attr :descr
|
30
|
+
attr :cached_location
|
31
|
+
|
32
|
+
def initialize url, descr, md5 = nil
|
33
|
+
@url = url
|
34
|
+
@descr = descr
|
35
|
+
@md5 = md5
|
36
|
+
@cached_location = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Downloads the patch into the current directory.
|
40
|
+
def fetch
|
41
|
+
dst = Pathname.pwd+File.basename(@url.to_s)
|
42
|
+
begin
|
43
|
+
curl @url.to_s, '-o', dst
|
44
|
+
rescue
|
45
|
+
raise "Patch #{File.basename(@url.to_s)} could not be fetched."
|
46
|
+
end
|
47
|
+
@cached_location = dst
|
48
|
+
debug "Patch downloaded into #{@cached_location}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Applies this patch in the current directory.
|
52
|
+
# Raises an error if the patch cannot be applied.
|
53
|
+
def apply
|
54
|
+
debug "Applying patch at #{Dir.pwd}"
|
55
|
+
raise "Patch not fetched." if !(@cached_location and @cached_location.exist?)
|
56
|
+
patch_levels = ['-p1', '-p0']
|
57
|
+
patched = false
|
58
|
+
output = ''
|
59
|
+
# First try with git apply
|
60
|
+
patch_levels.each do |pl|
|
61
|
+
begin
|
62
|
+
runBabyRun 'git', ['apply', '--check', pl, @cached_location], :redirect_stderr_to_stdout => true
|
63
|
+
runBabyRun 'git', ['apply', pl, @cached_location], :redirect_stderr_to_stdout => true
|
64
|
+
patched = true
|
65
|
+
break
|
66
|
+
rescue => ex
|
67
|
+
output << ex.to_s
|
68
|
+
end
|
69
|
+
end
|
70
|
+
if not patched
|
71
|
+
patch_levels.each do |pl|
|
72
|
+
begin
|
73
|
+
runBabyRun 'patch', ['--no-backup-if-mismatch', '-f', pl, '-d', Dir.pwd, '-i', @cached_location], :redirect_stderr_to_stdout => true
|
74
|
+
patched = true
|
75
|
+
break
|
76
|
+
rescue => ex
|
77
|
+
output << ex.to_s
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
if not patched
|
82
|
+
if descr and descr != @cached_location.basename.to_s
|
83
|
+
d = " (#{descr})"
|
84
|
+
else
|
85
|
+
d = ''
|
86
|
+
end
|
87
|
+
raise "Patch #{@cached_location.basename}#{d} could not be applied.\n" + output
|
88
|
+
end
|
89
|
+
return true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|