class TaskJuggler::TjpExportRE

This specialization of ReportBase implements an export of the project data in the TJP syntax format.

Public Class Methods

new(report) click to toggle source

Create a new object and set some default values.

Calls superclass method TaskJuggler::ReportBase.new
# File lib/taskjuggler/reports/TjpExportRE.rb, line 22
def initialize(report)
  super(report)

  @supportedTaskAttrs = %w( booking complete depends flags maxend
                            maxstart minend minstart note priority
                            projectid responsible )
  @supportedResourceAttrs = %w( booking flags shifts vacation workinghours )
end

Public Instance Methods

generateIntermediateFormat() click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 31
def generateIntermediateFormat
  super
end
to_tjp() click to toggle source

Return the project data in TJP syntax format.

# File lib/taskjuggler/reports/TjpExportRE.rb, line 36
def to_tjp
  # Prepare the resource list.
  @resourceList = PropertyList.new(@project.resources)
  @resourceList.setSorting(a('sortResources'))
  @resourceList = filterResourceList(@resourceList, nil, a('hideResource'),
                                     a('rollupResource'), a('openNodes'))
  @resourceList.sort!

  # Prepare the task list.
  @taskList = PropertyList.new(@project.tasks)
  @taskList.setSorting(a('sortTasks'))
  @taskList = filterTaskList(@taskList, nil, a('hideTask'), a('rollupTask'),
                             a('openNodes'))
  @taskList.sort!

  getBookings

  @file = ''

  generateProjectProperty if a('definitions').include?('project')

  generateFlagDeclaration if a('definitions').include?('flags')
  generateProjectIDs if a('definitions').include?('projectids')

  generateShiftList if a('definitions').include?('shifts')

  generateResourceList if a('definitions').include?('resources')
  generateTaskList if a('definitions').include?('tasks')

  generateTaskAttributes unless a('taskAttributes').empty?
  generateResourceAttributes unless a('resourceAttributes').empty?

  @file
end

Private Instance Methods

generateAttribute(property, attrId, indent, scenarioIdx = nil) click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 391
def generateAttribute(property, attrId, indent, scenarioIdx = nil)
  val = scenarioIdx ? property[attrId, scenarioIdx] : property.get(attrId)
  return if val.nil? || (val.is_a?(Array) && val.empty?) ||
            (scenarioIdx && inheritable?(property, attrId, scenarioIdx))

  generateAttributeText(property.getAttribute(attrId, scenarioIdx).to_tjp,
                        indent, scenarioIdx)
end
generateAttributeText(text, indent, scenarioIdx = nil) click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 400
def generateAttributeText(text, indent, scenarioIdx = nil)
  @file << ' ' * indent
  tag = ''
  if !scenarioIdx.nil? && scenarioIdx != 0
    tag = "#{@project.scenario(scenarioIdx).id}:"
    @file << tag
  end
  @file << "#{indentBlock(text, indent + tag.length + 2)}\n"
end
generateBookingsByResource(resource, indent, scenarioIdx) click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 454
def generateBookingsByResource(resource, indent, scenarioIdx)
  # Get the bookings for this resource hashed by task.
  bookings = resource.getBookings(scenarioIdx,
                                  TimeInterval.new(a('start'), a('end')),
                                  false)
  bookings.each do |booking|
    next unless @taskList.include?(booking.task)
    generateAttributeText('booking ' + booking.to_tjp(true), indent,
                          scenarioIdx)
  end
end
generateBookingsByTask(task, indent, scenarioIdx) click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 439
def generateBookingsByTask(task, indent, scenarioIdx)
  return unless @bookings[scenarioIdx].include?(task)

  # Convert Hash into an [ Resource, Booking ] Array sorted by Resource
  # ID. This guarantees a reproducible order.
  resourceBookings = @bookings[scenarioIdx][task].sort do |a, b|
    a[0].fullId <=> b[0].fullId
  end

  resourceBookings.each do |resourceId, booking|
    generateAttributeText('booking ' + booking.to_tjp(false), indent,
                          scenarioIdx)
  end
end
generateCustomAttributeDeclarations(tag, propertySet, attributes) click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 106
def generateCustomAttributeDeclarations(tag, propertySet, attributes)
  # First we search the attribute definitions for any user defined
  # attributes and count them.
  customAttributes = 0
  propertySet.eachAttributeDefinition do |ad|
    customAttributes += 1 if ad.userDefined
  end
  # Return if there are no user defined attributes.
  return if customAttributes == 0

  # Generate definitions for each user defined attribute that is in the
  # taskAttributes list.
  @file << '  extend ' + tag + "{\n"
    propertySet.eachAttributeDefinition do |ad|
      next unless ad.userDefined && attributes.include?(ad.id)

      @file << "    #{ad.objClass.tjpId} #{ad.id} " +
               "#{quotedString(ad.name)}"
      if ad.scenarioSpecific || ad.inheritedFromParent
        @file << " { "
        @file << "scenariospecific " if ad.scenarioSpecific
        @file << "inherit " if ad.inheritedFromParent
        @file << "}"
      end
      @file << "\n"
    end
  @file << "  }\n"
end
generateFlagDeclaration() click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 135
def generateFlagDeclaration
  flags = []

  properties = @resourceList + @taskList

  properties.each do |property|
    a('scenarios').each do |scenarioIdx|
      property['flags', scenarioIdx].each do |flag|
        flags << flag unless flags.include?(flag)
      end
    end
  end
  flags.sort
  unless flags.empty?
    @file << "flags #{flags.join(', ')}\n\n"
  end
end
generateProjectIDs() click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 153
def generateProjectIDs
  # Compile a list of all projectIDs from the tasks in the taskList.
  projectIDs = []
  a('scenarios').each do |scenarioIdx|
    @taskList.each do |task|
      pid = task['projectid', scenarioIdx]
      projectIDs << pid unless pid.nil? || projectIDs.include?(pid)
    end
  end

  @file << "projectids #{projectIDs.join(', ')}\n\n" unless projectIDs.empty?
end
generateProjectProperty() click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 73
def generateProjectProperty
  @file << "project #{@project['projectid']} \"#{@project['name']}\" " +
           "\"#{@project['version']}\" #{@project['start']} - " +
           "#{@project['end']} {\n"
  # Add timingresolution attribute if it's not the default value.
  if @project['scheduleGranularity'] != 3600
    generateAttributeText("timingresolution " +
                          "#{@project['scheduleGranularity'] / 60}min", 2)
  end
  generateAttributeText("timezone \"#{@project['timezone']}\"", 2)
  if @project['alertLevels'].modified?
    generateAttributeText(@project['alertLevels'].to_tjp, 2)
  end
  generateCustomAttributeDeclarations('resource', @project.resources,
                                      a('resourceAttributes'))
  generateCustomAttributeDeclarations('task', @project.tasks,
                                      a('taskAttributes'))
  generateScenarioDefinition(@project.scenario(0), 2)
  @file << "}\n\n"
end
generateResource(resource, indent) click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 199
def generateResource(resource, indent)
  Log.activity if resource.sequenceNo % 100 == 0

  @file << ' ' * indent + "resource #{resource.id} " +
           "#{quotedString(resource.name)}"
  @file << ' {' unless resource.children.empty?
  @file << "\n"

  # Call this function recursively for all children that are included in the
  # resource list as well.
  resource.children.each do |subresource|
    if @resourceList.include?(subresource)
      generateResource(subresource, indent + 2)
    end
  end

  @file << ' ' * indent + "}\n" unless resource.children.empty?
end
generateResourceAttributes() click to toggle source

Generate a list of resource supplement statements that include the rest of the attributes.

# File lib/taskjuggler/reports/TjpExportRE.rb, line 313
def generateResourceAttributes
  @resourceList.each do |resource|
    Log.activity if resource.sequenceNo % 100 == 0
    @file << "supplement resource #{resource.fullId} {\n"
    @project.resources.eachAttributeDefinition do |attrDef|
      id = attrDef.id
      next if (!@supportedResourceAttrs.include?(id) &&
               !attrDef.userDefined) ||
              !a('resourceAttributes').include?(id)

      if attrDef.scenarioSpecific
        a('scenarios').each do |scenarioIdx|
          next if inheritable?(resource, id, scenarioIdx)

          generateAttribute(resource, id, 2, scenarioIdx)
        end
      else
        generateAttribute(resource, id, 2)
      end
    end

    # Since 'booking' is a task attribute, we need a special handling if
    # we want to list them in the resource context.
    if a('resourceAttributes').include?('booking') &&
       a('resourceAttributes')[0] != '*'
      a('scenarios').each do |scenarioIdx|
        generateBookingsByResource(resource, 2, scenarioIdx)
      end
    end

    @file << "}\n"
  end
end
generateResourceList() click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 188
def generateResourceList
  # The resource definitions are generated recursively. So we only need to
  # start it for the top-level resources.
  @resourceList.each do |resource|
    if resource.parent.nil?
      generateResource(resource, 0)
    end
  end
  @file << "\n"
end
generateScenarioDefinition(scenario, indent) click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 94
def generateScenarioDefinition(scenario, indent)
  @file << "#{' ' * indent}scenario #{scenario.id} " +
           "#{quotedString(scenario.name)} {\n"
  scenario.children.each do |sc|
    generateScenarioDefinition(sc, indent + 2)
  end
  @file << "#{' ' * (indent + 2)}active " +
           "#{scenario.get('active') ? 'yes' : 'no'}\n"
  @file << "#{' ' * indent}}\n"
end
generateShift(shift, indent) click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 172
def generateShift(shift, indent)
  @file << ' ' * indent + "shift #{shift.id} " +
           "#{quotedString(shift.name)} {\n"

  a('scenarios').each do |scenarioIdx|
    generateAttribute(shift, 'workinghours', indent + 2, scenarioIdx)
  end

  # Call this method recursively for all children.
  shift.children.each do |subshift|
    generateShift(subshift, indent + 2)
  end

  @file << ' ' * indent + "}\n"
end
generateShiftList() click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 166
def generateShiftList
  @project.shifts.each do |shift|
    generateShift(shift, 0) unless shift.parent
  end
end
generateTask(task, indent) click to toggle source

Generate a task definition. It only contains a very small set of attributes that have to be passed on the the nested tasks at creation time. All other attributes are declared in subsequent supplement statements.

# File lib/taskjuggler/reports/TjpExportRE.rb, line 232
def generateTask(task, indent)
  Log.activity if task.sequenceNo % 100 == 0

  @file << ' ' * indent + "task #{task.subId} " +
           "#{quotedString(task.name)} {\n"

  if a('taskAttributes').include?('depends')
    a('scenarios').each do |scenarioIdx|
      generateTaskDependency(scenarioIdx, task, 'depends', indent + 2)
      generateTaskDependency(scenarioIdx, task, 'precedes', indent + 2)
    end
  end

  # Call this function recursively for all children that are included in the
  # task list as well.
  task.children.each do |subtask|
    if @taskList.include?(subtask)
      generateTask(subtask, indent + 2)
    end
  end

  # Determine whether this task has subtasks that are included in the
  # report or whether this is a leaf task for the report.
  isLeafTask = true
  task.children.each do |subtask|
    if @taskList.include?(subtask)
      isLeafTask = false
      break
    end
  end

  # For leaf tasks we put some attributes right here.
  if isLeafTask
    a('scenarios').each do |scenarioIdx|
      generateAttribute(task, 'start', indent + 2, scenarioIdx)
      if task['milestone', scenarioIdx]
        if task['scheduled', scenarioIdx]
          generateAttributeText('milestone', indent + 2, scenarioIdx)
        end
      else
        generateAttribute(task, 'end', indent + 2, scenarioIdx)
        generateAttributeText('scheduling ' +
                              (task['forward', scenarioIdx] ?
                               'asap' : 'alap'),
                              indent + 2, scenarioIdx)
      end
      if task['scheduled', scenarioIdx] &&
         !inheritable?(task, 'scheduled', scenarioIdx)
        generateAttributeText('scheduled', indent + 2, scenarioIdx)
      end
    end
  end

  @file << ' ' * indent + "}\n"
end
generateTaskAttributes() click to toggle source

Generate a list of task supplement statements that include the rest of the attributes.

# File lib/taskjuggler/reports/TjpExportRE.rb, line 349
def generateTaskAttributes
  @taskList.each do |task|
    Log.activity if task.sequenceNo % 100 == 0

    @file << "supplement task #{task.fullId} {\n"
    # Declare adopted tasks.
    adoptees = ""
    task.adoptees.each do |adoptee|
      next unless @taskList.include?(adoptee)

      adoptees += ', ' unless adoptees.empty?
      adoptees += adoptee.fullId
    end
    generateAttributeText("adopt #{adoptees}", 2) unless adoptees.empty?

    @project.tasks.eachAttributeDefinition do |attrDef|
      id = attrDef.id

      next if (!@supportedTaskAttrs.include?(id) && !attrDef.userDefined) ||
              !a('taskAttributes').include?(id)

      if attrDef.scenarioSpecific
        a('scenarios').each do |scenarioIdx|
          # Some attributes need special treatment.
          case id
          when 'depends'
            next     # already taken care of
          when 'booking'
            generateBookingsByTask(task, 2, scenarioIdx)
          else
            generateAttribute(task, id, 2, scenarioIdx)
          end
        end
      else
        generateAttribute(task, id, 2)
      end
    end

    @file << "}\n"
  end
end
generateTaskDependency(scenarioIdx, task, tag, indent) click to toggle source

Generate 'depends' or 'precedes' attributes for a task.

# File lib/taskjuggler/reports/TjpExportRE.rb, line 289
def generateTaskDependency(scenarioIdx, task, tag, indent)
  return unless a('taskAttributes').include?('depends')

  taskDeps = task[tag, scenarioIdx]
  unless taskDeps.empty?
    str = "#{tag} "
    first = true
    taskDeps.each do |dep|
      next if inheritable?(task, tag, scenarioIdx, dep) ||
              (task.parent && task.parent[tag, scenarioIdx].include?(dep))

      if first
        first = false
      else
        str << ', '
      end
      str << dep.task.fullId
    end
    generateAttributeText(str, indent, scenarioIdx) unless first
  end
end
generateTaskList() click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 218
def generateTaskList
  # The task definitions are generated recursively. So we only need to start
  # it for the top-level tasks.
  @taskList.each do |task|
    if task.parent.nil?
      generateTask(task, 0)
    end
  end
end
getBookings() click to toggle source

Get the booking data for all resources that should be included in the report.

# File lib/taskjuggler/reports/TjpExportRE.rb, line 412
def getBookings
  @bookings = {}
  if a('taskAttributes').include?('booking') ||
     a('resourceAttributes').include?('booking')
    a('scenarios').each do |scenarioIdx|
      @bookings[scenarioIdx] = {}
      @resourceList.each do |resource|
        # Get the bookings for this resource hashed by task.
        bookings = resource.getBookings(
          scenarioIdx, TimeInterval.new(a('start'), a('end')))
        next if bookings.nil?

        # Now convert/add them to a tripple-stage hash by scenarioIdx, task
        # and then resource.
        bookings.each do |task, booking|
          next unless @taskList.include?(task)

          if !@bookings[scenarioIdx].include?(task)
            @bookings[scenarioIdx][task] = {}
          end
          @bookings[scenarioIdx][task][resource] = booking
        end
      end
    end
  end
end
indentBlock(text, indent) click to toggle source

This utility function is used to indent multi-line attributes. All attributes should be filtered through this function. Attributes that contain line breaks will be indented properly. In addition to the indentation specified by indent all but the first line will be indented after the first word of the first line. The text may not end with a line break.

# File lib/taskjuggler/reports/TjpExportRE.rb, line 473
def indentBlock(text, indent)
  out = ''
  firstSpace = 0
  text.length.times do |i|
    if firstSpace == 0 && text[i] == ?\ # There must be a space after ?
      firstSpace = i
    end
    out << text[i]
    if text[i] == ?\n
      out += ' ' * (indent + firstSpace - 1)
    end
  end
  out
end
inheritable?(property, attrId, scenarioIdx, listItem = nil) click to toggle source

Return true if the attribute value for attrId can be inherited from the parent scenario.

# File lib/taskjuggler/reports/TjpExportRE.rb, line 499
def inheritable?(property, attrId, scenarioIdx, listItem = nil)
  parentScenario = @project.scenario(scenarioIdx).parent
  return false unless parentScenario

  parentScenarioIdx = @project.scenarioIdx(parentScenario)
  parentAttr = property[attrId, parentScenarioIdx]
  if parentAttr.is_a?(Array) && listItem
    return parentAttr.include?(listItem)
  else
    return property[attrId, scenarioIdx] == parentAttr
  end
end
quotedString(str) click to toggle source
# File lib/taskjuggler/reports/TjpExportRE.rb, line 488
def quotedString(str)
  if str.include?("\n")
    "-8<-\n#{str}\n->8-"
  else
    escaped = str.gsub("\"", '\"')
    "\"#{escaped}\""
  end
end