class Drupid::Makefile

Representation of a Drush makefile.

See also: drupal.org/node/625094

Attributes

api[R]

The value of the api field of the makefile (e.g., ‘2’)

contrib_path[RW]

The path for contrib modules and themes (e.g., ‘sites/all’), relative to path.

core[R]

The value of the core field of the makefile (e.g, ‘7.x’)

path[R]

The absolute path to the makefile.

Public Class Methods

new(path) click to toggle source

Creates a new Makefile object. The path must be the path to a .make file (which does not need to exist).

   # File lib/drupid/makefile.rb
45 def initialize(path)
46   @path      = Pathname.new(path)
47   raise "Not an absolute path: #{@path}" unless @path.absolute?
48   @core      = nil
49   @api       = nil
50   @projects  = Hash.new        # (String -> Project)
51   @libraries = Hash.new        # (String -> Library)
52   @contrib_path   = Pathname.new('sites/all')
53   debug "Parsing #{@path}"
54   self.reload if @path.exist?
55 end

Public Instance Methods

add_project(p) click to toggle source

Adds a project to this specification.

    # File lib/drupid/makefile.rb
278 def add_project(p)
279   @projects[p.name] = p
280 end
delete_project(name) click to toggle source

Removes the project with the specified name from this specification.

    # File lib/drupid/makefile.rb
295 def delete_project(name)
296   @projects.delete(name)
297 end
drupal_project() click to toggle source

Returns a Drupid::Project object for the Drupal core specified in the makefile, or nil if the makefile does not specify a Drupal distribution.

    # File lib/drupid/makefile.rb
310 def drupal_project
311   @projects['drupal']
312 end
each_library() { |libraries| ... } click to toggle source

Iterates over the libraries in this specification.

    # File lib/drupid/makefile.rb
315 def each_library
316   # For convenience, return the libraries in lexicographical order.
317   names = @libraries.keys.sort!
318   names.each do |n|
319     yield @libraries[n]
320   end
321 end
each_project() { |projects| ... } click to toggle source

Iterates over the projects in this specification (excluding drupal).

    # File lib/drupid/makefile.rb
300 def each_project
301   # For convenience, return the projects in lexicographical order.
302   names = @projects.keys.sort!
303   names.each do |n|
304     yield @projects[n] unless @projects[n].drupal?
305   end
306 end
get_library(name) click to toggle source

Returns the library with the specified name. or nil if the library is not in this specification.

    # File lib/drupid/makefile.rb
290 def get_library(name)
291   @libraries[name]
292 end
get_project(name) click to toggle source

Returns the project with the specified name, or nil if the project is not in this specification.

    # File lib/drupid/makefile.rb
284 def get_project(name)
285   @projects[name]
286 end
library_names() click to toggle source

Returns a list of the names of the libraries mentioned in this specification.

    # File lib/drupid/makefile.rb
331 def library_names
332   @libraries.keys
333 end
project_names() click to toggle source

Returns a list of the names of the projects mentioned in this specification (excluding drupal).

    # File lib/drupid/makefile.rb
325 def project_names
326   @projects.values.reject { |p| p.drupal? }.map { |p| p.name }
327 end
reload() click to toggle source

Reloads the makefile. This method is invoked automatically at creation time if a path to an existing makefile is provided.

    # File lib/drupid/makefile.rb
 60 def reload
 61   @core     = nil
 62   @api      = nil
 63   @projects = Hash.new
 64   @libraries = Hash.new
 65 
 66   proj_patches = Hash.new
 67   libs_patches = Hash.new
 68   core_num = nil
 69   mf = File.open(@path.to_s, "r").read
 70   # Parse includes directives
 71   while mf.match(/^([ \t]*includes\[.*\]\s*=\s*"?([^\s"]+)"?[ \t]*)$/) do
 72     # TODO: add support for remote includes
 73     url = $2
 74     blah "Including makefile #{url}"
 75     inc = File.open(url, "r").read
 76     mf.sub!($1, inc)
 77   end
 78   if mf.match(/core *= *["']? *(\d+)\.?(\d+)?/) # Get the core number immediately
 79     @core = $~[1] + '.x'
 80     core_num = $~[1].to_i
 81     vers = $~[2] ? $~[1] + '.' + $~[2] : nil
 82     # Create Drupal project
 83     @projects['drupal'] = Project.new('drupal', core_num, vers)
 84   end
 85   raise ParseMakefileError, "The makefile does not contain the mandatory 'core' field" unless core_num
 86   lineno = 0
 87   mf.each_line do |line|
 88     lineno += 1
 89     next if line =~ /^\s*$/
 90     next if line =~ /^\s*;/
 91     next if line =~ /^\s*core/
 92     # match[1] : the key ('core', 'version', 'api', 'projects', 'libraries', 'includes')
 93     # match[2] : the (optional) key arguments (stuff between square brackets)
 94     # match[3] : the same as match[2], but without the leftmost [ and the rightmost ]
 95     # match[4] : the value
 96     # Examples:
 97     # (a) Given 'projects[ctools][version] = 1.0-rc1', we have
 98     # match[1] == 'projects'
 99     # match[2] == '[ctools][version]'
100     # match[3] == 'ctools][version'
101     # match[4] == '1.0-rc1'
102     # (b) Given 'core = 7.x', we have:
103     # match[1] == 'core'
104     # match[3] == nil
105     # match[4] == '7.x'
106     match = line.match(/^\s*([^\s\[=]+)\s*(\[\s*(.*?)\s*\])?\s*=\s*["']?([^\s"'(]+)/)
107     raise ParseMakefileError, "Could not parse line: #{line.strip} (line #{lineno})" if match.nil? or match.size != 5
108     key = match[1]
109     args = (match[3]) ? match[3].split(/\]\s*\[/) : []
110     value = match[4].strip
111     case key
112     when 'api'
113       @api = value
114     when 'projects'
115       if 0 == args.size # e.g., projects[] = views
116         name = value
117         @projects[name] = Project.new(name, core_num)
118       else
119         name = args[0]
120         @projects[name] = Project.new(name, core_num) unless @projects.has_key?(name)
121         case args.size
122         when 1 # e.g., projects[views] = 2.8
123           @projects[name].version = @core+'-'+value.sub(/^#{@core}-/,'')
124         when 2 # e.g., projects[views][version] = 2.8 or projects[calendar][patch][] = 'http://...'
125           case args[1]
126           when 'version'
127             @projects[name].version = @core+'-'+value.sub(/^#{@core}-/,'')
128           when 'patch'
129             patch_key = File.basename(value)
130             patch_url = _normalize_path(value)
131             @projects[name].add_patch(patch_url, patch_key)
132           when 'subdir'
133             @projects[name].subdir = value
134           when 'location'
135             @projects[name].location = _normalize_path(value)
136           when 'directory_name'
137             @projects[name].directory_name = value
138           when 'type'
139             if 'core' == value
140               @projects[name].core_project = true
141             else
142               raise ParseMakefileError, "Illegal value: #{args[1]} (line #{lineno})" unless value =~ /^(module|profile|theme)$/
143               @projects[name].proj_type = value
144             end
145           when 'l10n_path'
146             # TODO: add support for tokens
147             @projects[name].l10n_path = _normalize_path(value)
148           when 'l10n_url'
149             @projects[name].l10n_url = _normalize_path(value)
150           when 'overwrite'
151             @projects[name].overwrite = true if value =~ /TRUE/i
152           else
153             raise ParseMakefileError, "Unknown key: #{args[1]} (line #{lineno})"
154           end
155         when 3 # e.g., projects[mytheme][download][type] = "svn"
156           name = args[0]
157           subkey = args[1]
158           case subkey
159           when 'download'
160             case args[2]
161             when 'type'
162               @projects[name].download_type = value
163             when 'url'
164               @projects[name].download_url = _normalize_path(value)
165             else
166               @projects[name].add_download_spec(args[2], value)
167             end
168           else
169             raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
170           end
171         when 4 # e.g., projects[calendar][patch][rfc-fixes][md5] = "..."
172           name = args[0]
173           subkey = args[1]
174           case subkey
175           when 'patch'
176             patch_key = args[2]
177             proj_patches[name] ||= Hash.new
178             proj_patches[name][patch_key] ||= Hash.new
179             case args[3]
180             when 'url'
181               proj_patches[name][patch_key]['url'] = _normalize_path(value)
182             when 'md5'
183               proj_patches[name][patch_key]['md5'] = value
184             else
185               raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
186             end
187           else
188             raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
189           end
190         else # > 4 arguments
191           raise ParseMakefileError, "Too many arguments (line #{lineno})"
192         end # case
193       end # if
194     when 'libraries'
195       if 0 == args.size
196         raise ParseMakefileError, "Too few arguments (line #{lineno})"
197       else
198         name = args[0]
199         @libraries[name] = Library.new(name) unless @libraries.has_key?(name)
200         case args.size
201         when 1
202           raise ParseMakefileError, "Too few arguments (line #{lineno})"
203         when 2
204           case args[1]
205           when 'patch'
206             patch_key = File.basename(value)
207             patch_url = _normalize_path(value)
208             @libraries[name].add_patch(patch_url, patch_key)
209           when 'subdir'
210             @libraries[name].subdir = value
211           when 'destination'
212             @libraries[name].destination = value
213           when 'directory_name'
214             @libraries[name].directory_name = value
215           else
216             raise ParseMakefileError, "Unknown key: #{args[1]} (line #{lineno})"
217           end
218         when 3 # e.g., libraries[jquery_ui][download][type] = "file"
219           name = args[0]
220           subkey = args[1]
221           case subkey
222           when 'download'
223             case args[2]
224             when 'type'
225               @libraries[name].download_type = value
226             when 'url'
227               @libraries[name].download_url = _normalize_path(value)
228             else
229               @libraries[name].add_download_spec(args[2], value)
230             end
231           else
232             raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
233           end
234         when 4
235           name = args[0]
236           subkey = args[1]
237           case subkey
238           when 'patch'
239             patch_key = args[2]
240             libs_patches[name] ||= Hash.new
241             libs_patches[name][patch_key] ||= Hash.new
242             case args[3]
243             when 'url'
244               libs_patches[name][patch_key]['url'] = _normalize_path(value)
245             when 'md5'
246               libs_patches[name][patch_key]['md5'] = value
247             else
248               raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
249             end
250           else
251             raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
252           end
253         else # > 4 arguments
254           raise ParseMakefileError, "Too many arguments (line #{lineno})"
255         end
256       end
257     when 'includes'
258       owarn "Unexpected 'includes' directive (line #{lineno})"
259     else
260       owarn "Could not parse key: #{key} (line #{lineno})"
261     end
262   end
263   # Add missing patches
264   proj_patches.each do |proj_name, v|
265     v.each do |desc,prop|
266       @projects[proj_name].add_patch(prop['url'], desc, prop['md5'])
267     end
268   end
269   libs_patches.each do |lib_name, v|
270     v.each do |desc,prop|
271       @libraries[lib_name].add_patch(prop['url'], desc, prop['md5'])
272     end
273   end
274   return self
275 end
save(alt_path = @path) click to toggle source

Writes this makefile to disk. An alternative location may be specified as an argument.

    # File lib/drupid/makefile.rb
337 def save(alt_path = @path)
338   File.open(alt_path.to_s, "w").write(to_s)
339 end
to_s() click to toggle source

Returns this makefile as a string.

    # File lib/drupid/makefile.rb
342 def to_s
343   s = String.new
344   s << "core = #{@core}\n"
345   s << "api  = #{@api}\n"
346   s << _project_to_record(drupal_project) if drupal_project
347   s << "\n" unless @projects.empty?
348   self.each_project { |p| s << _project_to_record(p) }
349   s << "\n" unless @libraries.empty?
350   self.each_library { |l| s << _library_to_record(l) }
351   s
352 end

Private Instance Methods

_library_to_record(l) click to toggle source
    # File lib/drupid/makefile.rb
398 def _library_to_record(l)
399   fields = Array.new
400   fields << "[download][type] = \"#{l.download_type}\"" if l.download_type
401   fields << "[download][url] = \"#{_relativize_path(l.download_url)}\"" if l.download_url
402   temp = []
403   l.download_specs.each do |spec,ref|
404     temp << "[download][#{spec}] = \"#{ref}\""
405   end
406   fields = fields + temp.sort!
407   l.each_patch do |pa|
408     fields << "[patch][#{pa.descr}][url] = \"#{pa.url}\""
409     fields << "[patch][#{pa.descr}][md5] = \"#{pa.md5}\"" if pa.md5
410   end
411   fields << "[destination] = \"#{l.destination}\""
412   fields << "[subdir] = \"#{l.subdir}\"" if '.' != l.subdir.to_s
413   fields << "[directory_name] = \"#{l.directory_name}\""
414   s = ''
415   fields.each do |f|
416     s << "libraries[#{l.name}]" + f + "\n"
417   end
418   return s
419 end
_normalize_path(u) click to toggle source
    # File lib/drupid/makefile.rb
356 def _normalize_path(u)
357   return u if u =~ /:\/\// # URL
358   if u =~ /^\//            # Local absolute path
359     return 'file://' + u
360   else                     # Relative path
361     return 'file://' + (path.parent + u).to_s
362   end
363 end
_project_to_record(p) click to toggle source
    # File lib/drupid/makefile.rb
370 def _project_to_record(p)
371   fields = Array.new
372   fields << "[type] = \"#{p.proj_type}\"" if p.proj_type =~ /module|profile|theme/
373   fields << "[version] = \"#{p.version.short}\"" if p.has_version?
374   fields << "[location] = \"#{_relativize_path(p.location)}\"" if p.location
375   fields << "[download][type] = \"#{p.download_type}\"" if p.download_type
376   fields << "[download][url] = \"#{_relativize_path(p.download_url)}\"" if p.download_url
377   temp = []
378   p.download_specs.each do |spec,ref|
379     temp << "[download][#{spec}] = \"#{ref}\""
380   end
381   fields = fields + temp.sort!
382   p.each_patch do |pa|
383     fields << "[patch][#{pa.descr}][url] = \"#{_relativize_path(pa.url)}\""
384     fields << "[patch][#{pa.descr}][md5] = \"#{pa.md5}\"" if pa.md5
385   end
386   fields << "[l10n_path] = \"#{_relativize_path(p.l10n_path)}\"" if p.l10n_path
387   fields << "[l10n_url] = \"#{_relativize_path(p.l10n_url)}\"" if p.l10n_url
388   fields << "[subdir] = \"#{p.subdir}\"" if '.' != p.subdir.to_s
389   fields << "[directory_name] = \"#{p.directory_name}\"" if p.directory_name != p.name
390   return "projects[] = \"#{p.name}\"\n" if 0 == fields.size
391   s = ''
392   fields.each do |f|
393     s << "projects[#{p.name}]" + f + "\n"
394   end
395   return s
396 end
_relativize_path(u) click to toggle source
    # File lib/drupid/makefile.rb
365 def _relativize_path(u)
366   return u unless u =~ /^file:\/\//
367   return Pathname.new(u.sub(/file:\/\//,'')).relative_path_from(path.dirname).to_s
368 end