Class: Drupid::Project

Inherits:
Component show all
Includes:
Comparable
Defined in:
lib/drupid/project.rb

Overview

Base class for projects.

Direct Known Subclasses

PlatformProject

Instance Attribute Summary collapse

Attributes inherited from Component

#download_specs, #download_type, #download_url, #ignore_paths, #local_path, #name, #overwrite

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Component

#add_download_spec, #add_patch, #cached_location, #clear_patches, #clone, #directory_name, #directory_name=, #each_patch, #exist?, #get_patch, #has_patches?, #ignore_path, #patch, #patched?, #patched_location, #subdir, #subdir=, #to_s

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(name, core_num, vers = nil) ⇒ Project

Creates a new project with a given name and compatibility number. Optionally, specify a short version string (i.e., a version string without core compatibility number).

Examples:

p = Drupid::Project.new('cck', 6)
p = Drupid::Project.new('views', 7, '1.2')


270
271
272
273
274
275
276
277
278
# File 'lib/drupid/project.rb', line 270

def initialize name, core_num, vers = nil
  super(name)
  @core = VersionCore.new(core_num)
  @core_project = ('drupal' == @name) ? true : nil
  @version = (vers.nil? or vers.empty?) ? nil : Version.from_s(@core.to_s + '-' + vers)
  @proj_type = ('drupal' == @name) ? 'drupal' : nil
  @info_file = nil
  @release_xml = nil
end

Instance Attribute Details

#coreObject (readonly)

Returns the value of attribute core.



253
254
255
# File 'lib/drupid/project.rb', line 253

def core
  @core
end

#l10n_pathObject

Returns the value of attribute l10n_path.



260
261
262
# File 'lib/drupid/project.rb', line 260

def l10n_path
  @l10n_path
end

#l10n_urlObject

Returns the value of attribute l10n_url.



261
262
263
# File 'lib/drupid/project.rb', line 261

def l10n_url
  @l10n_url
end

#locationObject

Returns the value of attribute location.



254
255
256
# File 'lib/drupid/project.rb', line 254

def location
  @location
end

#proj_typeObject

The type of this project, which is one among ‘drupal’, ‘module’, ‘theme’ and ‘profile’, or nil if the type has not been determined or assigned. Note that this does not coincide with the ‘type’ field in a Drush makefile, whose feasible values are ‘core’, ‘module’, ‘theme’, ‘profile’.



259
260
261
# File 'lib/drupid/project.rb', line 259

def proj_type
  @proj_type
end

Class Method Details

.from_s(p) ⇒ Object

Creates a new Project instance from the specified string. Note that the string must at least contain the core version of the project.

Raises a Drupid::NotDrupalVersionError if the string cannot be parsed.

Examples:

proj = Drupid::Project.from_s 'drupal-7.23'
proj = Drupid::Project.from_s 'drupal-8.x'
proj = Drupid::Project.from_s 'media-7.x'
proj = Drupid::Project.from_s 'tao-7.x-3.0-beta4'


290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/drupid/project.rb', line 290

def self.from_s p
  matchdata = p.match(/^([^-\/]+)[-\/](\d+.*)$/)
  raise NotDrupalVersionError if matchdata.nil?
  name = matchdata[1]
  vers = nil
  if ('drupal' == name) and (matchdata[2] =~ /^(\d+)\.(\d+)/) # e.g., drupal-8.0-dev
    core = $1.to_i
    vers = matchdata[2]
  else
    matchversion = matchdata[2].match(/^(\d+)\.x-?(.*)/)
    raise NotDrupalVersionError if matchversion.nil?
    core = matchversion[1].to_i
    vers = matchversion[2]
  end
  Project.new(name, core, vers)
end

Instance Method Details

#<=>(other) ⇒ Object

Compares this project with another to determine which is newer. The comparison returns nil if the two projects have different names or at least one of them has no version; otherwise, returns -1 if this project is older than the other, 1 if this project is more recent than the other, 0 if this project has the same version as the other.



432
433
434
435
436
437
438
439
440
441
# File 'lib/drupid/project.rb', line 432

def <=>(other)
  return nil if @name != other.name
  c = core <=> other.core
  if 0 == c
    return nil unless has_version? and other.has_version?
    return version <=> other.version
  else
    return c
  end
end

#==(other) ⇒ Object

See Version for the reason why we define == explicitly.



420
421
422
423
424
# File 'lib/drupid/project.rb', line 420

def ==(other)
  @name == other.name and
  @core == other.core and
  @version == other.version
end

#best_release(version_list) ⇒ Object

Returns the current version of the project when version_list is empty.



607
608
609
610
611
612
613
614
615
616
617
618
# File 'lib/drupid/project.rb', line 607

def best_release version_list
  if self.has_version? # Exclude releases older than the current one
    version_list = version_list.select { |v| v >= self.version }
  end
  return self.version if version_list.empty?
  stable_releases = version_list.select { |v| v.stable? }
  if stable_releases.empty?
    version_list.max { |a,b| a.better b }
  else
    stable_releases.max { |a,b| a.better b }
  end
end

#core_project=(c) ⇒ Object



415
416
417
# File 'lib/drupid/project.rb', line 415

def core_project=(c)
  @core_project = c
end

#core_project?Boolean

Returns true if this is a core project; returns false otherwise.

Returns:

  • (Boolean)


411
412
413
# File 'lib/drupid/project.rb', line 411

def core_project?
  @core_project
end

#dependencies(options = {}) ⇒ Object

Returns a list of the names of the extensions (modules and themes) upon which this project and its subprojects (the projects contained within this one) depend. Returns an empty list if no local copy of this project exists.

If :subprojects is set to false, subprojects’ dependencies are not computed.

Options: subprojects



463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/drupid/project.rb', line 463

def dependencies options = {}
  return [] unless exist?
  deps = Array.new
  if options.has_key?(:subprojects) and (not options[:subprojects])
    reload_project_info unless @info_file and @info_file.exist?
    info_files = [@info_file]
  else
    info_files = Dir["#{local_path}/**/*.info"]
  end
  info_files.each do |info|
    f = File.open(info, "r").read
    f.each_line do |l|
      matchdata = l.match(/^\s*dependencies\s*\[\s*\]\s*=\s*["']?([^\s("']+)/)
      if nil != matchdata
        deps << matchdata[1].strip
      end
      matchdata = l.match(/^\s*base +theme\s*=\s*(.+)$/)
      if nil != matchdata
        d = matchdata[1].strip
        deps << d.gsub(/\A["']|["']\Z/, '') # Strip leading and trailing quotes
      end
    end
  end
  # Remove duplicates and self-dependency
  deps.uniq!
  deps.delete(name)
  return deps
end

#drupal?Boolean

Returns true if this object corresponds to Drupal core; returns false otherwise.

Returns:

  • (Boolean)


401
402
403
# File 'lib/drupid/project.rb', line 401

def drupal?
  'drupal' == proj_type
end

#extended_nameObject

Returns the name and the version of this project as a string, e.g., ‘media-7.x-2.0-unstable2’ or ‘drupal-7.14’. If no version is specified for this project, returns only the project’s name and core compatibility number.



447
448
449
450
451
452
453
# File 'lib/drupid/project.rb', line 447

def extended_name
  if has_version?
    return name + '-' + ((drupal?) ? version.short : version.long)
  else
    return name + '-' + core.to_s
  end
end

#extensionsObject

Returns a list of the names of the extensions (modules and themes) contained in this project. Returns a list containing only the project’s name if no local copy of this project exists.



496
497
498
499
500
501
502
503
504
505
# File 'lib/drupid/project.rb', line 496

def extensions
  return [name] unless exist?
  # Note that the project's name may be different from the name of the .info file.
  ext = [name]
  Dir["#{local_path}/**/*.info"].map do |p|
    ext << File.basename(p, '.info')
  end
  ext.uniq!
  return ext
end

#fetchObject



517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
# File 'lib/drupid/project.rb', line 517

def fetch
  if self.version.nil? and download_url.nil? and download_type.nil?
    debug "Updating version of #{self.extended_name}"
    self.update_version
  end
  # If the project has no version we fetch it even if it is cached.
  # If the project has a download type, we fetch it even if it is cached
  # (say the download type is 'git' and the revision is changed in the
  # makefile, then the cached project must be updated accordingly).
  if self.has_version? and self.download_type.nil? and self.cached_location.exist?
    @local_path = self.cached_location
    debug "#{self.extended_name} is cached"
  else
    blah "Fetching #{self.extended_name}"
    if 'git' == self.download_type and self.download_url.nil?
      self.download_url = "http://git.drupal.org/project/#{name}.git"
    end
    self.update_download_url if self.download_url.nil?
    raise "No download URL defined for #{self.extended_name}" unless self.download_url
    downloader = Drupid.makeDownloader  self.download_url.to_s,
                                        self.cached_location.dirname.to_s,
                                        self.cached_location.basename.to_s,
                                        self.download_specs.merge({:type => download_type})
    downloader.fetch
    downloader.stage
    @local_path = downloader.staged_path
  end
  self.reload_project_info unless self.drupal?
end

#fetch_release_historyObject

Fetches the release history for this project from updates.drupal.org.



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/drupid/project.rb', line 380

def fetch_release_history
  begin
    debug "Getting release history from http://updates.drupal.org/release-history/#{self.name}/#{self.core}"
    dont_debug do
      @release_xml = Nokogiri::XML(open("http://updates.drupal.org/release-history/#{self.name}/#{self.core}"))
    end
    if @release_xml.at_xpath('/error')
      debug "No release history for the given project"
      @relese_xml = nil
    else
      debug "Release history retrieved"
    end
  rescue => ex
    owarn "Could not fetch release history for #{self.extended_name}"
    debug "fetch_release_history: #{ex}"
    @release_xml = nil
  end
end

#file_level_compare_with(tgt) ⇒ Object

Compares this project with another, returning an array of differences. If this project contains a makefile, ignore the content of the following directories inside the project: libraries, modules, profiles and themes.



583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# File 'lib/drupid/project.rb', line 583

def file_level_compare_with tgt
  args = Array.new
  if makefile
    args << '-f' << '- /libraries/***' # this syntax requires rsync >=2.6.7.
    args << '-f' << '- /modules/***'
    args << '-f' << '- /profiles/***'
    args << '-f' << '- /themes/***'
  end
  if drupal?
    args << '-f' << '+ /profiles/default/***'  # D6
    args << '-f' << '+ /profiles/minimal/***'  # D7
    args << '-f' << '+ /profiles/standard/***' # D7
    args << '-f' << '+ /profiles/testing/***'  # D7
    args << '-f' << '- /profiles/***'
    args << '-f' << '+ /sites/all/README.txt'
    args << '-f' << '+ /sites/default/default.settings.php'
    args << '-f' << '- /sites/***'
  end
  super(tgt, args)
end

#has_version?Boolean

Returns true if a version is specified for this project, false otherwise.

Returns:

  • (Boolean)


308
309
310
# File 'lib/drupid/project.rb', line 308

def has_version?
  nil != @version
end

#makefileObject

Returns the path to a makefile contained in this project, if any. Returns nil if this project does not contain any makefile. For an embedded makefile to be recognized, the makefile itself must be named ‘#name.make’ or ‘drupal-org.make’.

Requires: a local copy of this project.



568
569
570
571
572
573
574
575
576
577
578
# File 'lib/drupid/project.rb', line 568

def makefile
  return nil unless self.exist?
  paths = [
    local_path + "#{name}.make",
    local_path + 'drupal-org.make' # Used in Drupal distributions
  ]
  paths.each do |p|
    return p if p.exist?
  end
  return nil
end

#profile?Boolean

Returns true if this is a profile; returns false otherwise.

Returns:

  • (Boolean)


406
407
408
# File 'lib/drupid/project.rb', line 406

def profile?
  'profile' == proj_type
end

#reload_project_infoObject



507
508
509
510
511
512
513
514
515
# File 'lib/drupid/project.rb', line 507

def reload_project_info
  project_info = ProjectInfo.new(@local_path)
  raise "Inconsistent naming: expected #{@name}, got #{project_info.project_name}" unless @name == project_info.project_name
  raise "Inconsistent core: expected #{@core}, got #{project_info.project_core}" unless @core == project_info.project_core
  @proj_type = project_info.project_type
  @core_project = project_info.core_project?
  @version = project_info.project_version
  @info_file = project_info.info_file
end

#target_pathObject

Returns the relative path where this project should be installed within a platform. For example, for a module called ‘Foo’, it might be something like ‘modules/contrib/foo’.



551
552
553
554
555
556
557
558
559
560
# File 'lib/drupid/project.rb', line 551

def target_path
  case proj_type
  when 'drupal'
    return Pathname.new('.')
  when nil
    raise "Undefined project type for #{name}."
  else
    return Pathname.new(proj_type + 's') + subdir + directory_name
  end
end

#update_download_urlObject

Updates the download URL for the current version of this project.

Retrieves the release information for this project from updates.drupal.org.



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/drupid/project.rb', line 361

def update_download_url
  return unless self.has_version?
  self.fetch_release_history if @release_xml.nil?
  return if @release_xml.nil?
  if @release_xml.at_xpath('/project/releases/release/files/file/variant').nil?
    variant = ''
  else
    variant = "[variant='profile-only']"
  end
  v = self.drupal? ? self.version.short : self.version.long
  url_node = @release_xml.at_xpath("/project/releases/release[status='published'][version='#{v}']/files/file#{variant}/url")
  if url_node.nil?
    owarn "Could not get download URL from http://updates.drupal.org for #{extended_name}"
  else
    self.download_url = url_node.child.to_s
  end
end

#update_versionObject

Updates the version of this project to the latest (stable) release.

Retrieves the release information for this project from updates.drupal.org.

See also: Drupid::Project.best_release



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/drupid/project.rb', line 340

def update_version
  self.fetch_release_history if @release_xml.nil?
  return self.version unless @release_xml
  version_list = []
  version_nodes = @release_xml.xpath("/project/releases/release[status='published']/version")
  version_nodes.each do |n|
    v = n.child.to_s
    # Remove version specifications that do not start with self.core.
    # This seemingly superfluous test is needed because, for example,
    # imce-7.x has a 'master' version (oh my!).
    next if v !~ /^#{self.core.to_i}\./
    v.sub!("#{self.core}-", '') unless self.drupal?
    version_list.push(Version.new(self.core, v))
  end
  self.version = self.best_release(version_list)
  debug "Version updated: #{extended_name}"
end

#versionObject

Returns the version of this project as a Drupid::Version object, or nil if this project has not been assigned a version.



314
315
316
# File 'lib/drupid/project.rb', line 314

def version
  @version
end

#version=(new_version) ⇒ Object

Assigns a version to this project. The argument must be a String object or a Drupid::Version object. For the syntax of the String argument, see Drupid::Version.



321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/drupid/project.rb', line 321

def version=(new_version)
  return @version = nil if new_version.nil?
  if new_version.is_a?(Version)
    temp_version = new_version
  elsif new_version.is_a?(String)
    v = new_version
    temp_version = Version.from_s(v)
  else
    raise NotDrupalVersionError
  end
  raise NotDrupalVersionError, "Incompatible version for project #{self.extended_name}: #{temp_version.long}" if temp_version.core != self.core
  @version = temp_version
end