class TaskJuggler::Report
The Report
class holds the fundamental description and functionality to turn the scheduled project into a user readable form. A report may contain other reports.
Attributes
Public Class Methods
Create a new report object.
TaskJuggler::PropertyTreeNode::new
# File lib/taskjuggler/reports/Report.rb, line 47 def initialize(project, id, name, parent) super(project.reports, id, name, parent) @messageHandler = MessageHandlerInstance.instance checkFileName(name) project.addReport(self) # The type specifier must be set for every report. It tells whether this # is a task, resource, text or other report. @typeSpec = nil # Reports don't really have any scenario specific attributes. But the # flag handling code assumes they are. To use flags, we need them as # well. @data = Array.new(@project.scenarioCount, nil) @project.scenarioCount.times do |i| ReportScenario.new(self, i, @scenarioAttributes[i]) end end
Public Instance Methods
The generate function is where the action happens in this class. The report defined by all the class attributes and report elements is generated according the the requested output format(s). requestedFormats can be a list of formats that should be generated (e.
-
:html, :csv, etc.).
# File lib/taskjuggler/reports/Report.rb, line 70 def generate(requestedFormats = nil) oldTimeZone = TjTime.setTimeZone(get('timezone')) generateIntermediateFormat # We either generate the requested formats or the list of formats that # was specified in the report definition. (requestedFormats || get('formats')).each do |format| if @name.empty? error('empty_report_file_name', "Report #{@id} has output formats requested, but the " + "file name is empty.", sourceFileInfo) end case format when :iCal generateICal when :html generateHTML copyAuxiliaryFiles when :csv generateCSV when :ctags generateCTags when :niku generateNiku when :tjp generateTJP when :mspxml generateMspXml else raise 'Unknown report output format #{format}.' end end TjTime.setTimeZone(oldTimeZone) 0 end
Generate an output format agnostic version that can later be turned into the respective output formats.
# File lib/taskjuggler/reports/Report.rb, line 111 def generateIntermediateFormat if get('scenarios').empty? warning('all_scenarios_disabled', "The report #{fullId} has only disabled scenarios. The " + "report will possibly be empty.") end @content = nil case @typeSpec when :accountreport @content = AccountListRE.new(self) when :export @content = ExportRE.new(self) when :iCal @content = ICalReport.new(self) when :niku @content = NikuReport.new(self) when :resourcereport @content = ResourceListRE.new(self) when :tagfile @content = TagFile.new(self) when :textreport @content = TextReport.new(self) when :taskreport @content = TaskListRE.new(self) when :tracereport @content = TraceReport.new(self) when :statusSheet @content = StatusSheetReport.new(self) when :timeSheet @content = TimeSheetReport.new(self) else raise "Unknown report type" end # Most output format can be generated from a common intermediate # representation of the elements. We generate that IR first. @content.generateIntermediateFormat if @content end
Return true if the report should be rendered in the interactive version, false if not. The top-level report defines the output format and the interactive setting.
# File lib/taskjuggler/reports/Report.rb, line 159 def interactive? @project.reportContexts.first.report.get('interactive') end
Render the content of the report as HTML (without the framing).
# File lib/taskjuggler/reports/Report.rb, line 152 def to_html @content ? @content.to_html : nil end
Private Instance Methods
Convenience function to access a report attribute
# File lib/taskjuggler/reports/Report.rb, line 165 def a(attribute) get(attribute) end
# File lib/taskjuggler/reports/Report.rb, line 475 def absoluteFileName(name) (absoluteFileName?(name) ? '' : @project.outputDir) + name end
# File lib/taskjuggler/reports/Report.rb, line 467 def absoluteFileName?(name) if windowsOS? name[0] =~ /[a-zA-Z]/ && name[1] == ?: else name[0] == ?/ end end
# File lib/taskjuggler/reports/Report.rb, line 454 def checkFileName(name) if windowsOS? illegalChars = /[\x00\\\*\?\"<>\|]/ else illegalChars = /[\\?%*:|"<>]/ end if name =~ illegalChars error('invalid_file_name', 'File names may not contain any of the following characters: ' + '\?%*:|\"<>', sourceFileInfo) end end
# File lib/taskjuggler/reports/Report.rb, line 390 def copyAuxiliaryFiles # Don't copy files if output is stdout, the requested by the web server # or the user has specified a custom aux directory. return if @name == '.' || a('interactive') || !a('auxdir').empty? copyDirectory('css') copyDirectory('icons') copyDirectory('scripts') end
# File lib/taskjuggler/reports/Report.rb, line 400 def copyDirectory(dirName) # The directory needs to be in the same directory as the HTML report. auxDstDir = File.dirname(absoluteFileName(@name)) + '/' # Find the data directory that came with the TaskJuggler installation. auxSrcDir = AppConfig.dataDirs("data/#{dirName}")[0] # Raise an error if we haven't found the data directory if auxSrcDir.nil? || !File.exist?(auxSrcDir) dataDirError(dirName, AppConfig.dataSearchDirs("data/#{dirName}")) end # Don't copy directory if all files are up-to-date. return if directoryUpToDate?(auxSrcDir, auxDstDir + dirName) begin # Recursively copy the directory and all content. FileUtils.cp_r(auxSrcDir, auxDstDir) rescue IOError, SystemCallError error('copy_dir', "Cannot copy directory #{auxSrcDir} to " + "#{auxDstDir}.\n#{$!}", sourceFileInfo) end end
# File lib/taskjuggler/reports/Report.rb, line 435 def dataDirError(dirName, dirs) error('data_dir_error', <<"EOT", Cannot find the #{dirName} directory. This is usually the result of an improper TaskJuggler installation. If you know the directory, you can use the TASKJUGGLER_DATA_PATH environment variable to specify the location. The variable should be set to the path without the /data at the end. Multiple directories must be separated by colons. The following directories have been tried: #{dirs.join("\n")} EOT sourceFileInfo ) end
# File lib/taskjuggler/reports/Report.rb, line 421 def directoryUpToDate?(auxSrcDir, auxDstDir) return false unless File.exist?(auxDstDir) Dir.entries(auxSrcDir).each do |file| next if file == '.' || file == '..' srcFile = (auxSrcDir + '/' + file) dstFile = (auxDstDir + '/' + file) return false if !File.exist?(dstFile) || File.mtime(srcFile) > File.mtime(dstFile) end true end
Generate a CSV version of the report.
# File lib/taskjuggler/reports/Report.rb, line 262 def generateCSV # The CSV format can only handle the first element of a report. return nil unless @content unless @content.respond_to?('to_csv') warning('csv_not_supported', "CSV format is not supported for report #{@id} of " + "type #{@typeSpec}.") return nil end return nil unless (csv = @content.to_csv) # Use the CSVFile class to write the Array of Arrays to a colon # separated file. Write to $stdout if the filename was set to '.'. begin fileName = (@name == '.' ? '.' : absoluteFileName(@name) + '.csv') CSVFile.new(csv, ';').write(fileName) rescue IOError, SystemCallError error('write_csv', "Cannot write to file #{fileName}.\n#{$!}", sourceFileInfo) end end
Generate ctags file
# File lib/taskjuggler/reports/Report.rb, line 372 def generateCTags unless @content.respond_to?('to_ctags') warning('ctags_not_supported', "ctags format is not supported for report #{@id} of " + "type #{@typeSpec}.") return nil end begin f = @name == '.' ? $stdout : File.new(absoluteFileName(@name), 'w') f.puts "#{@content.to_ctags}" rescue IOError, SystemCallError error('write_ctags', "Cannot write to file #{@name}.\n#{$!}", sourceFileInfo) end end
Generate an HTML version of the report.
# File lib/taskjuggler/reports/Report.rb, line 170 def generateHTML return nil unless @content unless @content.respond_to?('to_html') warning('html_not_supported', "HTML format is not supported for report #{@id} of " + "type #{@typeSpec}.") return nil end html = HTMLDocument.new head = html.generateHead(@project['name'] + " - #{get('title') || @name}", { 'description' => 'TaskJuggler Report', 'keywords' => 'taskjuggler, project, management' }, a('rawHtmlHead')) if a('selfcontained') auxSrcDir = AppConfig.dataDirs('data/css')[0] cssFileName = (auxSrcDir ? auxSrcDir + '/tjreport.css' : '') # Raise an error if we haven't found the data directory if auxSrcDir.nil? || !File.exist?(cssFileName) dataDirError(cssFileName, AppConfig.dataSearchDirs('data/css')) end cssFile = IO.read(cssFileName) if cssFile.empty? error('css_file_error', "Cannot read '#{cssFileName}'. Make sure the file is not " + "empty and you have read access permission.", sourceFileInfo) end head << XMLElement.new('meta', 'http-equiv' => 'Content-Style-Type', 'content' => 'text/css; charset=utf-8') head << (style = XMLElement.new('style', 'type' => 'text/css')) style << XMLBlob.new("\n" + cssFile) else head << XMLElement.new('link', 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => "#{a('auxdir')}css/tjreport.css") end html.html << XMLComment.new("Dynamic Report ID: " + "#{@project.reportContexts.last.dynamicReportId}") html.html << (body = XMLElement.new('body')) unless a('selfcontained') body << XMLElement.new('script', 'type' => 'text/javascript', 'src' => "#{a('auxdir')}scripts/wz_tooltip.js") body << (noscript = XMLElement.new('noscript')) noscript << (nsdiv = XMLElement.new('div', 'style' => 'text-align:center; ' + 'color:#FF0000')) nsdiv << XMLText.new(<<'EOT' This page requires Javascript for full functionality. Please enable it in your browser settings! EOT ) end # Make sure we have some margins around the report. body << (frame = XMLElement.new('div', 'class' => 'tj_page')) frame << @content.to_html # The footer with some administrative information. frame << (div = XMLElement.new('div', 'class' => 'copyright')) div << XMLText.new(@project['copyright'] + " - ") if @project['copyright'] div << XMLText.new("Project: #{@project['name']} " + "Version: #{@project['version']} - " + "Created on #{TjTime.new.to_s("%Y-%m-%d %H:%M:%S")} " + "with ") div << XMLNamedText.new("#{AppConfig.softwareName}", 'a', 'href' => "#{AppConfig.contact}") div << XMLText.new(" v#{AppConfig.version}") fileName = if a('interactive') || @name == '.' # Interactive HTML reports are always sent to stdout. '.' else # Prepend the specified output directory unless the provided file # name is an absolute file name. absoluteFileName(@name) + '.html' end begin html.write(fileName) rescue IOError, SystemCallError error('write_html', "Cannot write to file #{fileName}.\n#{$!}", sourceFileInfo) end end
Generate the report in iCal format.
# File lib/taskjuggler/reports/Report.rb, line 353 def generateICal unless @content.respond_to?('to_iCal') warning('ical_not_supported', "iCalendar format is not supported for report #{@id} of " + "type #{@typeSpec}.") return nil end begin f = @name == '.' ? $stdout : File.new(absoluteFileName(@name) + '.ics', 'w') f.puts "#{@content.to_iCal}" rescue IOError, SystemCallError error('write_ical', "Cannot write to file #{@name}.\n#{$!}", sourceFileInfo) end end
Generate the report in Microsoft Project
XML format.
# File lib/taskjuggler/reports/Report.rb, line 311 def generateMspXml unless @content.respond_to?('to_mspxml') warning('mspxml_not_supported', "Microsoft Project XML format is not supported for " + "report #{@id} of type #{@typeSpec}.") return nil end begin fileName = '.' if @name == '.' $stdout.write(@content.to_mspxml) else fileName = absoluteFileName(@name) + '.xml' File.open(fileName, 'w') { |f| f.write(@content.to_mspxml) } end rescue IOError, SystemCallError error('write_mspxml', "Cannot write to file #{fileName}.\n#{$!}", sourceFileInfo) end end
Generate Niku report
# File lib/taskjuggler/reports/Report.rb, line 334 def generateNiku unless @content.respond_to?('to_niku') warning('niku_not_supported', "niku format is not supported for report #{@id} of " + "type #{@typeSpec}.") return nil end begin f = @name == '.' ? $stdout : File.new(absoluteFileName(@name) + '.xml', 'w') f.puts "#{@content.to_niku}" rescue IOError, SystemCallError error('write_niku', "Cannot write to file #{@name}.\n#{$!}", sourceFileInfo) end end
Generate the report in TJP format.
# File lib/taskjuggler/reports/Report.rb, line 287 def generateTJP unless @content.respond_to?('to_tjp') warning('tjp_not_supported', "TJP format is not supported for report #{@id} of " + "type #{@typeSpec}.") return nil end begin fileName = '.' if @name == '.' $stdout.write(@content.to_tjp) else fileName = @name fileName += a('definitions').include?('project') ? '.tjp' : '.tji' File.open(fileName, 'w') { |f| f.write(@content.to_tjp) } end rescue IOError, SystemCallError error('write_tjp', "Cannot write to file #{fileName}.\n#{$!}", sourceFileInfo) end end
# File lib/taskjuggler/reports/Report.rb, line 450 def windowsOS? (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil end