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.
Create a new report object.
# File lib/taskjuggler/reports/Report.rb, line 39 39: def initialize(project, id, name, parent) 40: super(project.reports, id, name, parent) 41: project.addReport(self) 42: 43: # The type specifier must be set for every report. It tells whether this 44: # is a task, resource, text or other report. 45: @typeSpec = nil 46: end
# File lib/taskjuggler/reports/Report.rb, line 126 126: def error(id, message) 127: if message && !message.empty? 128: @project.messageHandler.error(id, message, @sourceFileInfo) 129: else 130: # We have no message, so the error has already been reported to the 131: # MessageHandler. Just trigger another exception to signal the error. 132: raise TjException 133: end 134: end
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 53 53: def generate(requestedFormats = nil) 54: oldTimeZone = TjTime.setTimeZone(get('timezone')) 55: 56: generateIntermediateFormat 57: 58: # We either generate the requested formats or the list of formats that 59: # was specified in the report definition. 60: (requestedFormats || get('formats')).each do |format| 61: if @name.empty? 62: error('empty_report_file_name', 63: "Report #{@id} has output formats requested, but the " + 64: "file name is empty.") 65: end 66: 67: case format 68: when :html 69: generateHTML 70: copyAuxiliaryFiles 71: when :csv 72: generateCSV 73: when :niku 74: generateNiku 75: when :tjp 76: generateTJP 77: else 78: raise 'Unknown report output format #{format}.' 79: end 80: end 81: 82: TjTime.setTimeZone(oldTimeZone) 83: 0 84: end
Generate an output format agnostic version that can later be turned into the respective output formats.
# File lib/taskjuggler/reports/Report.rb, line 88 88: def generateIntermediateFormat 89: @content = nil 90: case @typeSpec 91: when :export 92: @content = TjpExportRE.new(self) 93: when :niku 94: @content = NikuReport.new(self) 95: when :resourcereport 96: @content = ResourceListRE.new(self) 97: when :textreport 98: @content = TextReport.new(self) 99: when :taskreport 100: @content = TaskListRE.new(self) 101: when :statusSheet 102: @content = StatusSheetReport.new(self) 103: when :timeSheet 104: @content = TimeSheetReport.new(self) 105: else 106: raise "Unknown report type" 107: end 108: 109: # Most output format can be generated from a common intermediate 110: # representation of the elements. We generate that IR first. 111: @content.generateIntermediateFormat if @content 112: 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 122 122: def interactive? 123: @project.reportContexts.first.report.get('interactive') 124: end
Convenience function to access a report attribute
# File lib/taskjuggler/reports/Report.rb, line 144 144: def a(attribute) 145: get(attribute) 146: end
# File lib/taskjuggler/reports/Report.rb, line 295 295: def copyAuxiliaryFiles 296: # Don't copy files if output is stdout. 297: return if @name == '.' || a('interactive') 298: 299: copyDirectory('css') 300: copyDirectory('icons') 301: copyDirectory('scripts') 302: end
# File lib/taskjuggler/reports/Report.rb, line 304 304: def copyDirectory(dirName) 305: # The directory needs to be in the same directory as the HTML report. 306: auxDstDir = (File.dirname((@name[0] == '/' ? '' : @project.outputDir) + 307: @name) + '/').untaint 308: # Find the data directory that came with the TaskJuggler installation. 309: auxSrcDir = AppConfig.dataDirs("data/#{dirName}")[0].untaint 310: # Raise an error if we haven't found the data directory 311: if auxSrcDir.nil? || !File.exists?(auxSrcDir) 312: dataDirError(dirName, AppConfig.dataSearchDirs("data/#{dirName}")) 313: end 314: # Don't copy directory if all files are up-to-date. 315: return if directoryUpToDate?(auxSrcDir, auxDstDir + dirName) 316: 317: # Recursively copy the directory and all content. 318: FileUtils.cp_r(auxSrcDir, auxDstDir) 319: end
# File lib/taskjuggler/reports/Report.rb, line 335 335: def dataDirError(dirName, dirs) 336: error('data_dir_error', Cannot find the #{dirName} directory. This is usually the result of animproper TaskJuggler installation. If you know the directory, you can use theTASKJUGGLER_DATA_PATH environment variable to specify the location. Thevariable should be set to the path without the /data at the end. Multipledirectories must be separated by colons. The following directories have beentried:#{dirs.join("\n")} 337: ) 338: end
# File lib/taskjuggler/reports/Report.rb, line 321 321: def directoryUpToDate?(auxSrcDir, auxDstDir) 322: return false unless File.exists?(auxDstDir.untaint) 323: 324: Dir.entries(auxSrcDir).each do |file| 325: next if file == '.' || file == '..' 326: 327: srcFile = (auxSrcDir + '/' + file).untaint 328: dstFile = (auxDstDir + '/' + file).untaint 329: return false if !File.exist?(dstFile) || 330: File.mtime(srcFile) > File.mtime(dstFile) 331: end 332: true 333: end
Generate a CSV version of the report.
# File lib/taskjuggler/reports/Report.rb, line 234 234: def generateCSV 235: # The CSV format can only handle the first element of a report. 236: return nil unless @content 237: 238: unless @content.respond_to?('to_csv') 239: warning('csv_not_supported', 240: "CSV format is not supported for report #{@id} of " + 241: "type #{@typeSpec}.") 242: return nil 243: end 244: 245: return nil unless (csv = @content.to_csv) 246: 247: # Use the CSVFile class to write the Array of Arrays to a colon 248: # separated file. Write to $stdout if the filename was set to '.'. 249: begin 250: fileName = (@name == '.' ? @name : 251: (@name[0] == '/' ? '' : @project.outputDir) + 252: @name + '.csv').untaint 253: CSVFile.new(csv, ';').write(fileName) 254: rescue IOError 255: error('write_csv', "Cannot write to file #{fileName}.\n#{$!}") 256: end 257: end
Generate an HTML version of the report.
# File lib/taskjuggler/reports/Report.rb, line 149 149: def generateHTML 150: return nil unless @content 151: 152: unless @content.respond_to?('to_html') 153: warning('html_not_supported', 154: "HTML format is not supported for report #{@id} of " + 155: "type #{@typeSpec}.") 156: return nil 157: end 158: 159: html = HTMLDocument.new(:strict) 160: head = html.generateHead("TaskJuggler Report - #{@name}", 161: 'description' => 'TaskJuggler Report', 162: 'keywords' => 'taskjuggler, project, management') 163: if a('selfcontained') 164: auxSrcDir = AppConfig.dataDirs('data/css')[0] 165: cssFileName = (auxSrcDir ? auxSrcDir + '/tjreport.css' : '') 166: # Raise an error if we haven't found the data directory 167: if auxSrcDir.nil? || !File.exists?(cssFileName) 168: dataDirError(cssFileName, AppConfig.dataSearchDirs('data/css')) 169: end 170: cssFile = IO.read(cssFileName) 171: if cssFile.empty? 172: error('css_file_error', 173: "Cannot read '#{cssFileName}'. Make sure the file is not " + 174: "empty and you have read access permission.") 175: end 176: head << XMLElement.new('meta', 'http-equiv' => 'Content-Style-Type', 177: 'content' => 'text/css; charset=utf-8') 178: head << (style = XMLElement.new('style', 'type' => 'text/css')) 179: style << XMLBlob.new("\n" + cssFile) 180: else 181: head << XMLElement.new('link', 'rel' => 'stylesheet', 182: 'type' => 'text/css', 183: 'href' => 'css/tjreport.css') 184: end 185: html << XMLComment.new("Dynamic Report ID: " + 186: "#{@project.reportContexts.last.dynamicReportId}") 187: html << (body = XMLElement.new('body')) 188: 189: unless a('selfcontained') 190: body << XMLElement.new('script', 'type' => 'text/javascript', 191: 'src' => 'scripts/wz_tooltip.js') 192: body << (noscript = XMLElement.new('noscript')) 193: noscript << (nsdiv = XMLElement.new('div', 194: 'style' => 'text-align:center; ' + 195: 'color:#FF0000')) 196: nsdiv << XMLText.new(This page requires Javascript for full functionality. Please enable itin your browser settings! 197: ) 198: end 199: 200: 201: # Make sure we have some margins around the report. 202: body << (frame = XMLElement.new('div', 'class' => 'tj_page')) 203: 204: frame << @content.to_html 205: 206: # The footer with some administrative information. 207: frame << (div = XMLElement.new('div', 'class' => 'copyright')) 208: div << XMLText.new(@project['copyright'] + " - ") if @project['copyright'] 209: div << XMLText.new("Project: #{@project['name']} " + 210: "Version: #{@project['version']} - " + 211: "Created on #{TjTime.new.to_s("%Y-%m-%d %H:%M:%S")} " + 212: "with ") 213: div << XMLNamedText.new("#{AppConfig.softwareName}", 'a', 214: 'href' => "#{AppConfig.contact}") 215: div << XMLText.new(" v#{AppConfig.version}") 216: 217: fileName = 218: if a('interactive') || @name == '.' 219: # Interactive HTML reports are always sent to stdout. 220: '.' 221: else 222: # Prepend the specified output directory unless the provided file 223: # name is an absolute file name. 224: ((@name[0] == '/' ? '' : @project.outputDir) + 225: @name + '.html').untaint 226: end 227: html.write(fileName) 228: end
Generate Niku report
# File lib/taskjuggler/reports/Report.rb, line 277 277: def generateNiku 278: unless @content.respond_to?('to_niku') 279: warning('niku_not_supported', 280: "niku format is not supported for report #{@id} of " + 281: "type #{@typeSpec}.") 282: return nil 283: end 284: 285: begin 286: f = @name == '.' ? $stdout : 287: File.new(((@name[0] == '/' ? '' : @project.outputDir) + 288: @name + '.xml').untaint, 'w') 289: f.puts "#{@content.to_niku}" 290: rescue IOError 291: error('write_niku', "Cannot write to file #{@name}.\n#{$!}") 292: end 293: end
Generate time sheet drafts.
# File lib/taskjuggler/reports/Report.rb, line 260 260: def generateTJP 261: begin 262: fileName = '.' 263: if @name == '.' 264: $stdout.write(@content.to_tjp) 265: else 266: fileName = (@name[0] == '/' ? '' : @project.outputDir) + @name 267: fileName += a('definitions').include?('project') ? '.tjp' : '.tji' 268: fileName.untaint 269: File.open(fileName, 'w') { |f| f.write(@content.to_tjp) } 270: end 271: rescue IOError 272: error('write_tjp', "Cannot write to file #{fileName}.\n#{$!}") 273: end 274: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.