Class: Drupid::Makefile

Inherits:
Object
  • Object
show all
Includes:
Utils
Defined in:
lib/drupid/makefile.rb

Overview

Representation of a Drush makefile.

See also: drupal.org/node/625094

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils

#blah, #bzr, #compare_paths, #curl, #cvs, #debug, #dont_debug, #git, #hg, #ignore_interrupts, #interactive_shell, #odie, #ofail, #ohai, #owarn, #runBabyRun, #svn, #tempdir, #uncompress, #which, #writeFile

Constructor Details

#initialize(path) ⇒ Makefile

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



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/drupid/makefile.rb', line 46

def initialize(path)
  @path      = Pathname.new(path)
  raise "Not an absolute path: #{@path}" unless @path.absolute?
  @core      = nil
  @api       = nil
  @projects  = Hash.new        # (String -> Project)
  @libraries = Hash.new        # (String -> Library)
  @contrib_path   = Pathname.new('sites/all')
  debug "Parsing #{@path}"
  self.reload if @path.exist?
end

Instance Attribute Details

#apiObject (readonly)

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



39
40
41
# File 'lib/drupid/makefile.rb', line 39

def api
  @api
end

#contrib_pathObject

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



42
43
44
# File 'lib/drupid/makefile.rb', line 42

def contrib_path
  @contrib_path
end

#coreObject (readonly)

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



37
38
39
# File 'lib/drupid/makefile.rb', line 37

def core
  @core
end

#pathObject (readonly)

The absolute path to the makefile.



35
36
37
# File 'lib/drupid/makefile.rb', line 35

def path
  @path
end

Instance Method Details

#add_project(p) ⇒ Object

Adds a project to this specification.



279
280
281
# File 'lib/drupid/makefile.rb', line 279

def add_project(p)
  @projects[p.name] = p
end

#delete_project(name) ⇒ Object

Removes the project with the specified name from this specification.



296
297
298
# File 'lib/drupid/makefile.rb', line 296

def delete_project(name)
  @projects.delete(name)
end

#drupal_projectObject

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



311
312
313
# File 'lib/drupid/makefile.rb', line 311

def drupal_project
  @projects['drupal']
end

#each_libraryObject

Iterates over the libraries in this specification.



316
317
318
319
320
321
322
# File 'lib/drupid/makefile.rb', line 316

def each_library
  # For convenience, return the libraries in lexicographical order.
  names = @libraries.keys.sort!
  names.each do |n|
    yield @libraries[n]
  end
end

#each_projectObject

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



301
302
303
304
305
306
307
# File 'lib/drupid/makefile.rb', line 301

def each_project
  # For convenience, return the projects in lexicographical order.
  names = @projects.keys.sort!
  names.each do |n|
    yield @projects[n] unless @projects[n].drupal?
  end
end

#get_library(name) ⇒ Object

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



291
292
293
# File 'lib/drupid/makefile.rb', line 291

def get_library(name)
  @libraries[name]
end

#get_project(name) ⇒ Object

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



285
286
287
# File 'lib/drupid/makefile.rb', line 285

def get_project(name)
  @projects[name]
end

#library_namesObject

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



332
333
334
# File 'lib/drupid/makefile.rb', line 332

def library_names
  @libraries.keys
end

#project_namesObject

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



326
327
328
# File 'lib/drupid/makefile.rb', line 326

def project_names
  @projects.values.reject { |p| p.drupal? }.map { |p| p.name }
end

#reloadObject

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

Raises:



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/drupid/makefile.rb', line 61

def reload
  @core     = nil
  @api      = nil
  @projects = Hash.new
  @libraries = Hash.new

  proj_patches = Hash.new
  libs_patches = Hash.new
  core_num = nil
  mf = File.open(@path.to_s, "r").read
  # Parse includes directives
  while mf.match(/^([ \t]*includes\[.*\]\s*=\s*"?([^\s"]+)"?[ \t]*)$/) do
    # TODO: add support for remote includes
    url = $2
    blah "Including makefile #{url}"
    inc = File.open(url, "r").read
    mf.sub!($1, inc)
  end
  if mf.match(/core *= *["']? *(\d+)\.?(\d+)?/) # Get the core number immediately
    @core = $~[1] + '.x'
    core_num = $~[1].to_i
    vers = $~[2] ? $~[1] + '.' + $~[2] : nil
    # Create Drupal project
    @projects['drupal'] = Project.new('drupal', core_num, vers)
  end
  raise ParseMakefileError, "The makefile does not contain the mandatory 'core' field" unless core_num
  lineno = 0
  mf.each_line do |line|
    lineno += 1
    next if line =~ /^\s*$/
    next if line =~ /^\s*;/
    next if line =~ /^\s*core/
    # match[1] : the key ('core', 'version', 'api', 'projects', 'libraries', 'includes')
    # match[2] : the (optional) key arguments (stuff between square brackets)
    # match[3] : the same as match[2], but without the leftmost [ and the rightmost ]
    # match[4] : the value
    # Examples:
    # (a) Given 'projects[ctools][version] = 1.0-rc1', we have
    # match[1] == 'projects'
    # match[2] == '[ctools][version]'
    # match[3] == 'ctools][version'
    # match[4] == '1.0-rc1'
    # (b) Given 'core = 7.x', we have:
    # match[1] == 'core'
    # match[3] == nil
    # match[4] == '7.x'
    match = line.match(/^\s*([^\s\[=]+)\s*(\[\s*(.*?)\s*\])?\s*=\s*["']?([^\s"'(]+)/)
    raise ParseMakefileError, "Could not parse line: #{line.strip} (line #{lineno})" if match.nil? or match.size != 5
    key = match[1]
    args = (match[3]) ? match[3].split(/\]\s*\[/) : []
    value = match[4].strip
    case key
    when 'api'
      @api = value
    when 'projects'
      if 0 == args.size # e.g., projects[] = views
        name = value
        @projects[name] = Project.new(name, core_num)
      else
        name = args[0]
        @projects[name] = Project.new(name, core_num) unless @projects.has_key?(name)
        case args.size
        when 1 # e.g., projects[views] = 2.8
          @projects[name].version = @core+'-'+value.sub(/^#{@core}-/,'')
        when 2 # e.g., projects[views][version] = 2.8 or projects[calendar][patch][] = 'http://...'
          case args[1]
          when 'version'
            @projects[name].version = @core+'-'+value.sub(/^#{@core}-/,'')
          when 'patch'
            patch_key = File.basename(value)
            patch_url = _normalize_path(value)
            @projects[name].add_patch(patch_url, patch_key)
          when 'subdir'
            @projects[name].subdir = value
          when 'location'
            @projects[name].location = _normalize_path(value)
          when 'directory_name'
            @projects[name].directory_name = value
          when 'type'
            if 'core' == value
              @projects[name].core_project = true
            else
              raise ParseMakefileError, "Illegal value: #{args[1]} (line #{lineno})" unless value =~ /^(module|profile|theme)$/
              @projects[name].proj_type = value
            end
          when 'l10n_path'
            # TODO: add support for tokens
            @projects[name].l10n_path = _normalize_path(value)
          when 'l10n_url'
            @projects[name].l10n_url = _normalize_path(value)
          when 'overwrite'
            @projects[name].overwrite = true if value =~ /TRUE/i
          else
            raise ParseMakefileError, "Unknown key: #{args[1]} (line #{lineno})"
          end
        when 3 # e.g., projects[mytheme][download][type] = "svn"
          name = args[0]
          subkey = args[1]
          case subkey
          when 'download'
            case args[2]
            when 'type'
              @projects[name].download_type = value
            when 'url'
              @projects[name].download_url = _normalize_path(value)
            else
              @projects[name].add_download_spec(args[2], value)
            end
          else
            raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
          end
        when 4 # e.g., projects[calendar][patch][rfc-fixes][md5] = "..."
          name = args[0]
          subkey = args[1]
          case subkey
          when 'patch'
            patch_key = args[2]
            proj_patches[name] ||= Hash.new
            proj_patches[name][patch_key] ||= Hash.new
            case args[3]
            when 'url'
              proj_patches[name][patch_key]['url'] = _normalize_path(value)
            when 'md5'
              proj_patches[name][patch_key]['md5'] = value
            else
              raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
            end
          else
            raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
          end
        else # > 4 arguments
          raise ParseMakefileError, "Too many arguments (line #{lineno})"
        end # case
      end # if
    when 'libraries'
      if 0 == args.size
        raise ParseMakefileError, "Too few arguments (line #{lineno})"
      else
        name = args[0]
        @libraries[name] = Library.new(name) unless @libraries.has_key?(name)
        case args.size
        when 1
          raise ParseMakefileError, "Too few arguments (line #{lineno})"
        when 2
          case args[1]
          when 'patch'
            patch_key = File.basename(value)
            patch_url = _normalize_path(value)
            @libraries[name].add_patch(patch_url, patch_key)
          when 'subdir'
            @libraries[name].subdir = value
          when 'destination'
            @libraries[name].destination = value
          when 'directory_name'
            @libraries[name].directory_name = value
          else
            raise ParseMakefileError, "Unknown key: #{args[1]} (line #{lineno})"
          end
        when 3 # e.g., libraries[jquery_ui][download][type] = "file"
          name = args[0]
          subkey = args[1]
          case subkey
          when 'download'
            case args[2]
            when 'type'
              @libraries[name].download_type = value
            when 'url'
              @libraries[name].download_url = _normalize_path(value)
            else
              @libraries[name].add_download_spec(args[2], value)
            end
          else
            raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
          end
        when 4
          name = args[0]
          subkey = args[1]
          case subkey
          when 'patch'
            patch_key = args[2]
            libs_patches[name] ||= Hash.new
            libs_patches[name][patch_key] ||= Hash.new
            case args[3]
            when 'url'
              libs_patches[name][patch_key]['url'] = _normalize_path(value)
            when 'md5'
              libs_patches[name][patch_key]['md5'] = value
            else
              raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
            end
          else
            raise ParseMakefileError, "Unknown key: #{subkey} (line #{lineno})"
          end
        else # > 4 arguments
          raise ParseMakefileError, "Too many arguments (line #{lineno})"
        end
      end
    when 'includes'
      owarn "Unexpected 'includes' directive (line #{lineno})"
    else
      owarn "Could not parse key: #{key} (line #{lineno})"
    end
  end
  # Add missing patches
  proj_patches.each do |proj_name, v|
    v.each do |desc,prop|
      @projects[proj_name].add_patch(prop['url'], desc, prop['md5'])
    end
  end
  libs_patches.each do |lib_name, v|
    v.each do |desc,prop|
      @libraries[lib_name].add_patch(prop['url'], desc, prop['md5'])
    end
  end
  return self
end

#save(alt_path = @path) ⇒ Object

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



338
339
340
# File 'lib/drupid/makefile.rb', line 338

def save(alt_path = @path)
  File.open(alt_path.to_s, "w").write(to_s)
end

#to_sObject

Returns this makefile as a string.



343
344
345
346
347
348
349
350
351
352
353
# File 'lib/drupid/makefile.rb', line 343

def to_s
  s = String.new
  s << "core = #{@core}\n"
  s << "api  = #{@api}\n"
  s << _project_to_record(drupal_project) if drupal_project
  s << "\n" unless @projects.empty?
  self.each_project { |p| s << _project_to_record(p) }
  s << "\n" unless @libraries.empty?
  self.each_library { |l| s << _library_to_record(l) }
  s
end