1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 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 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
|
#!/usr/bin/env ruby -w
# encoding: UTF-8
#
# = TaskJuggler.rb -- The TaskJuggler III Project Management Software
#
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
# by Chris Schlaeger <cs@taskjuggler.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
require 'drb'
# Only needed during profiling.
#require 'ruby-prof'
require 'taskjuggler/Project'
require 'taskjuggler/MessageHandler'
require 'taskjuggler/Log'
# The TaskJuggler class models the object that provides access to the
# fundamental features of the TaskJuggler software. It can read project
# files, schedule them and generate the reports.
class TaskJuggler
include MessageHandler
attr_reader :project
attr_accessor :maxCpuCores, :warnTsDeltas, :generateTraces
# Create a new TaskJuggler object. _console_ is a boolean that determines
# whether or not messages can be written to $stderr.
def initialize
@project = nil
@parser = nil
@maxCpuCores = 1
@warnTsDeltas = false
@generateTraces = false
TjTime.setTimeZone('UTC')
end
# Read in the files passed as file names in _files_, parse them and
# construct a Project object. In case of success true is returned.
# Otherwise false.
def parse(files, keepParser = false)
# Reset the MessageHandler to clear all errors.
MessageHandlerInstance.instance.clear
Log.enter('parser', 'Parsing files ...')
master = true
@project = nil
#RubyProf.start
@parser = ProjectFileParser.new
files.each do |file|
begin
@parser.open(file, master)
rescue TjException => msg
if msg.message && !msg.message.empty?
critical('parse', msg.message)
end
Log.exit('parser')
return false
end
if master
# The first file is considered the master file.
if (@project = @parser.parse(:project)) == false
Log.exit('parser')
return false
end
master = false
else
# All other files.
@parser.setGlobalMacros
if @parser.parse(:propertiesFile) == false
Log.exit('parser')
return false
end
end
@project.inputFiles << file
@parser.close
end
#profile = RubyProf.stop
#printer = RubyProf::GraphHtmlPrinter.new(profile)
#File.open("profile.html", "w") do |file|
# printer.print(file)
#end
#printer = RubyProf::CallTreePrinter.new(profile)
#File.open("profile.clt", "w") do |file|
# printer.print(file)
#end
# For the report server mode we may need to keep the parser. Otherwise,
# destroy it.
@parser = nil unless keepParser
Log.exit('parser')
MessageHandlerInstance.instance.errors == 0
end
# Parse a file and add the content to the existing project. _fileName_ is
# the name of the file. _rule_ is the TextParser::Rule to start with.
def parseFile(fileName, rule)
begin
@parser.open(fileName, false)
@project.inputFiles << fileName
rescue TjException => msg
if msg.message && !msg.message.empty?
critical('parse_file', msg.message)
end
return nil
end
@parser.setGlobalMacros
return nil if (res = @parser.parse(rule)) == false
@parser.close
res
end
# Schedule all scenarios in the project. Return true if no error was
# detected, false otherwise.
def schedule
Log.enter('scheduler', 'Scheduling project ...')
#puts @project.to_s
@project.warnTsDeltas = @warnTsDeltas
begin
res = @project.schedule
rescue TjException => msg
if msg.message && !msg.message.empty?
critical('scheduling_error', msg.message)
end
return false
end
@project.enableTraceReports(@generateTraces)
Log.exit('scheduler')
res
end
# Generate all specified reports. The project must have been scheduled before
# this method can be called. It returns true if no error occured, false
# otherwise.
def generateReports(outputDir = nil)
@project.checkReports
if outputDir
# Make sure the output directory path always ends with a '/' unless empty.
outputDir += '/' unless outputDir.empty? || outputDir[-1] == '/'
@project.outputDir = outputDir
end
Log.enter('reports', 'Generating reports ...')
begin
#RubyProf.start
@project.generateReports(@maxCpuCores)
#profile = RubyProf.stop
#printer = RubyProf::GraphHtmlPrinter.new(profile)
#File.open("profile.html", "w") do |file|
# printer.print(file)
#end
#printer = RubyProf::CallTreePrinter.new(profile)
#File.open("profile.clt", "w") do |file|
# printer.print(file)
#end
rescue TjException => msg
if msg.message && !msg.message.empty?
critical('generate_reports', msg.message)
end
return false
end
Log.exit('reports')
true
end
# Generate the report with the ID _reportId_. If _regExpMode_ is true,
# _reportId_ is interpreted as a Regular Expression and all reports with
# matching IDs are generated. _formats_ is a list of formats (e. g. :html,
# :csv, etc.). _dynamicAtributes_ is a String that may contain attributes to
# supplement the report definition. The String must be in TJP format and may
# be nil if no additional attributes are provided.
def generateReport(reportId, regExpMode, formats = nil,
dynamicAttributes = nil)
begin
Log.enter('generateReport', 'Generating report #{reportId} ...')
@project.generateReport(reportId, regExpMode, formats, dynamicAttributes)
rescue TjException => msg
if msg.message && !msg.message.empty?
critical('generate_report', msg.message)
end
Log.exit('generateReport')
return false
end
Log.exit('generateReport')
true
end
# List the details of the report with _reportId_ or if _regExpMode_ the
# reports that match the regular expression in _reportId_.
def listReports(reportId, regExpMode)
begin
Log.enter('listReports', 'Generating report list for #{reportId} ...')
@project.listReports(reportId, regExpMode)
rescue TjException => msg
if msg.message && !msg.message.empty?
critical('list_reports', msg.message)
end
Log.exit('listReports')
return false
end
Log.exit('listReports')
true
end
# Generate an export report definition for bookings up to the _freezeDate_.
def freeze(freezeDate, taskBookings)
begin
# Check the master file is really a file and not stdin.
unless (masterFile = @project.inputFiles.masterFile)
error('cannot_freeze_stdin',
"The project freeze feature can only be used when the " +
"master file is a real file, not standard input.")
end
# Derive the file names for the header and bookings file from the base
# name of the master file.
masterFileBase = Dir.pwd + '/' + File.basename(masterFile, '.tjp')
headerFile = masterFileBase + '-header.tji'
bookingsFileBase = masterFileBase + '-bookings'
bookingsFile = bookingsFileBase + '.tji'
if !File.exist?(bookingsFile) || !File.exist?(headerFile)
info('incl_freeze_files',
"Please make sure you include #{headerFile} at " +
"the end of the project header and " +
"#{bookingsFile} at the end of #{masterFile}.")
end
# Generate the project header include file with the new 'now' date.
begin
File.open(headerFile, 'w') do |f|
f.puts("now #{freezeDate}")
end
rescue
error('write_header_incl',
"Cannote write header include file " +
"#{headerFile}")
end
# Generate an export report for the bookings.
report = Report.new(@project, '_bookings_', bookingsFileBase, nil)
report.typeSpec = :export
report.set('formats', [ :tjp ])
report.inheritAttributes
# We export only the tracking scenario.
unless (trackingScenarioIdx = @project['trackingScenarioIdx'])
error('no_tracking_scen', 'No trackingscenario defined')
end
report.set('scenarios', [ trackingScenarioIdx ])
# Only generate bookings up to the freeze date.
report.set('end', freezeDate)
# Show all tasks, sorted by seqno-up.
report.set('hideTask', LogicalExpression.new(LogicalOperation.new(0)))
report.set('sortTasks', [ [ 'seqno', true, -1 ] ])
# Show all resources, sorted by seqno-up.
report.set('hideResource',
LogicalExpression.new(LogicalOperation.new(0)))
report.set('sortResources', [ [ 'seqno', true, -1 ] ])
# Only generate bookings, no other attributes or definitions.
report.set('definitions', [])
# We group the bookings by task or by resource depending on the user
# request.
if taskBookings
report.set('taskAttributes', [ 'booking' ])
report.set('resourceAttributes', [])
else
report.set('taskAttributes', [])
report.set('resourceAttributes', [ 'booking' ])
end
rescue TjException
return false
end
true
end
# Check the content of the file _fileName_ and interpret it as a time sheet.
# If the sheet is syntactically correct and matches the loaded project, true
# is returned. Otherwise false.
def checkTimeSheet(fileName)
begin
Log.enter('checkTimeSheet', 'Parsing #{fileName} ...')
# To use this feature, the user must have specified which scenario is
# the tracking scenario.
unless @project['trackingScenarioIdx']
raise TjException.new, 'No trackingscenario defined'
end
# Make sure we don't use data from old time sheets or Journal entries.
@project.timeSheets.clear
@project['journal'] = Journal.new
return false unless (ts = parseFile(fileName, :timeSheetFile))
return false unless @project.checkTimeSheets
queryAttrs = { 'project' => @project,
'property' => ts.resource,
'scopeProperty' => nil,
'scenarioIdx' => @project['trackingScenarioIdx'],
'start' => ts.interval.start,
'end' => ts.interval.end,
'journalMode' => :journal,
'journalAttributes' => %w( alert property propertyid
headline flags timesheet
summary details ),
'sortJournalEntries' => [ [ :seqno, 1 ] ],
'timeFormat' => '%Y-%m-%d',
'selfContained' => true }
query = Query.new(queryAttrs)
puts ts.resource.query_journal(query).richText.inputText
rescue TjException => msg
if msg.message && !msg.message.empty?
critical('check_time_sheet', msg.message)
end
Log.exit('checkTimeSheet')
return false
end
Log.exit('checkTimeSheet')
true
end
# Check the content of the file _fileName_ and interpret it as a status
# sheet. If the sheet is syntactically correct and matches the loaded
# project, true is returned. Otherwise false.
def checkStatusSheet(fileName)
begin
Log.enter('checkStatusSheet', 'Parsing #{fileName} ...')
# To use this feature, the user must have specified which scenario is
# the tracking scenario.
unless @project['trackingScenarioIdx']
raise TjException.new, 'No trackingscenario defined'
end
return false unless (ss = parseFile(fileName, :statusSheetFile))
queryAttrs = { 'project' => @project,
'property' => ss[0],
'scopeProperty' => nil,
'scenarioIdx' => @project['trackingScenarioIdx'],
'start' => ss[1],
'end' => ss[2],
'timeFormat' => '%Y-%m-%d',
'selfContained' => true }
query = Query.new(queryAttrs)
puts ss[0].query_dashboard(query).richText.inputText
rescue TjException => msg
if msg.message && !msg.message.empty?
critical('check_status_sheet', msg.message)
end
Log.exit('checkStatusSheet')
return false
end
Log.exit('checkStatusSheet')
true
end
# Return the ID of the project or nil if no project has been loaded yet.
def projectId
return nil if @project.nil?
@project['projectid']
end
# Return the name of the project or nil if no project has been loaded yet.
def projectName
return nil if @project.nil?
@project['name']
end
# Return the number of errors that had been reported during processing.
def errors
MessageHandlerInstance.instance.errors
end
end
|