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/reports/Report.rb, line 37 37: def initialize(project, id, name, parent) 38: super(project.reports, id, name, parent) 39: project.addReport(self) 40: 41: # The type specifier must be set for every report. It tells whether this 42: # is a task, resource, text or other report. 43: @typeSpec = nil 44: end
# File lib/reports/Report.rb, line 111 111: def error(id, message) 112: if message && !message.empty? 113: @project.messageHandler.error(id, message, @sourceFileInfo) 114: else 115: # We have no message, so the error has already been reported to the 116: # MessageHandler. Just trigger another exception to signal the error. 117: raise TjException 118: end 119: 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).
# File lib/reports/Report.rb, line 49 49: def generate 50: generateIntermediateFormat 51: 52: # Then generate the actual output format. 53: get('formats').each do |format| 54: case format 55: when :html 56: generateHTML 57: copyAuxiliaryFiles 58: when :csv 59: generateCSV 60: when :niku 61: generateNiku 62: when :tjp 63: generateTJP 64: else 65: raise 'Unknown report output format #{format}.' 66: end 67: end 68: true 69: end
Generate an output format agnostic version that can later be turned into the respective output formats.
# File lib/reports/Report.rb, line 73 73: def generateIntermediateFormat 74: @content = nil 75: case @typeSpec 76: when :export 77: @content = TjpExportRE.new(self) 78: when :niku 79: @content = NikuReport.new(self) 80: when :resourcereport 81: @content = ResourceListRE.new(self) 82: when :textreport 83: @content = TextReport.new(self) 84: when :taskreport 85: @content = TaskListRE.new(self) 86: when :statusSheet 87: @content = StatusSheetReport.new(self) 88: when :timeSheet 89: @content = TimeSheetReport.new(self) 90: else 91: raise "Unknown report type" 92: end 93: 94: # Most output format can be generated from a common intermediate 95: # representation of the elements. We generate that IR first. 96: @content.generateIntermediateFormat if @content 97: 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/reports/Report.rb, line 107 107: def interactive? 108: @project.reportContexts.first.report.get('interactive') 109: end
Convenience function to access a report attribute
# File lib/reports/Report.rb, line 129 129: def a(attribute) 130: get(attribute) 131: end
# File lib/reports/Report.rb, line 258 258: def copyAuxiliaryFiles 259: # Don't copy files if output is stdout. 260: return if @name == '.' || a('interactive') 261: 262: copyDirectory('css') 263: copyDirectory('icons') 264: copyDirectory('scripts') 265: end
# File lib/reports/Report.rb, line 267 267: def copyDirectory(dirName) 268: # The directory needs to be in the same directory as the HTML report. 269: auxDstDir = (File.dirname((@name[0] == '/' ? '' : @project.outputDir) + 270: @name) + '/').untaint 271: # Find the data directory that came with the TaskJuggler installation. 272: auxSrcDir = AppConfig.dataDirs("data/#{dirName}")[0].untaint 273: # Raise an error if we haven't found the data directory 274: if auxSrcDir.nil? || !File.exists?(auxSrcDir) 275: dataDirError(dirName, AppConfig.dataSearchDirs("data/#{dirName}")) 276: end 277: # Don't copy directory if all files are up-to-date. 278: return if directoryUpToDate?(auxSrcDir, auxDstDir + dirName) 279: 280: # Recursively copy the directory and all content. 281: FileUtils.cp_r(auxSrcDir, auxDstDir) 282: end
# File lib/reports/Report.rb, line 298 298: def dataDirError(dirName, dirs) 299: 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")} 300: ) 301: end
# File lib/reports/Report.rb, line 284 284: def directoryUpToDate?(auxSrcDir, auxDstDir) 285: return false unless File.exists?(auxDstDir.untaint) 286: 287: Dir.entries(auxSrcDir).each do |file| 288: next if file == '.' || file == '..' 289: 290: srcFile = (auxSrcDir + '/' + file).untaint 291: dstFile = (auxDstDir + '/' + file).untaint 292: return false if !File.exist?(dstFile) || 293: File.mtime(srcFile) > File.mtime(dstFile) 294: end 295: true 296: end
Generate a CSV version of the report.
# File lib/reports/Report.rb, line 211 211: def generateCSV 212: return nil unless @content 213: 214: # CSV format can only handle the first element. 215: return nil unless (csv = @content.to_csv) 216: 217: # Use the CSVFile class to write the Array of Arrays to a colon 218: # separated file. Write to $stdout if the filename was set to '.'. 219: begin 220: fileName = (@name == '.' ? @name : 221: (@name[0] == '/' ? '' : @project.outputDir) + 222: @name + '.csv').untaint 223: CSVFile.new(csv, ';').write(fileName) 224: rescue IOError 225: error('write_csv', "Cannot write to file #{fileName}.\n#{$!}") 226: end 227: end
Generate an HTML version of the report.
# File lib/reports/Report.rb, line 134 134: def generateHTML 135: html = HTMLDocument.new(:strict) 136: head = html.generateHead("TaskJuggler Report - #{@name}", 137: 'description' => 'TaskJuggler Report', 138: 'keywords' => 'taskjuggler, project, management') 139: if a('selfcontained') 140: auxSrcDir = AppConfig.dataDirs('data/css')[0] 141: cssFileName = (auxSrcDir ? auxSrcDir + '/tjreport.css' : '') 142: # Raise an error if we haven't found the data directory 143: if auxSrcDir.nil? || !File.exists?(cssFileName) 144: dataDirError(cssFileName, AppConfig.dataSearchDirs('data/css')) 145: end 146: cssFile = IO.read(cssFileName) 147: if cssFile.empty? 148: raise TjException.new, Cannot read '#{cssFileName}'. Make sure the file is not empty and you haveread access permission. 149: end 150: head << XMLElement.new('meta', 'http-equiv' => 'Content-Style-Type', 151: 'content' => 'text/css; charset=utf-8') 152: head << (style = XMLElement.new('style', 'type' => 'text/css')) 153: style << XMLBlob.new("\n" + cssFile) 154: else 155: head << XMLElement.new('link', 'rel' => 'stylesheet', 156: 'type' => 'text/css', 157: 'href' => 'css/tjreport.css') 158: end 159: html << XMLComment.new("Dynamic Report ID: " + 160: "#{@project.reportContexts.last.dynamicReportId}") 161: html << (body = XMLElement.new('body')) 162: 163: unless a('selfcontained') 164: body << XMLElement.new('script', 'type' => 'text/javascript', 165: 'src' => 'scripts/wz_tooltip.js') 166: body << (noscript = XMLElement.new('noscript')) 167: noscript << (nsdiv = XMLElement.new('div', 168: 'style' => 'text-align:center; ' + 169: 'color:#FF0000')) 170: nsdiv << XMLText.new(This page requires Javascript for full functionality. Please enable itin your browser settings! 171: ) 172: end 173: 174: 175: # Make sure we have some margins around the report. 176: body << (frame = XMLElement.new('div', 'class' => 'tj_page')) 177: 178: frame << @content.to_html if @content 179: 180: # The footer with some administrative information. 181: frame << (div = XMLElement.new('div', 'class' => 'copyright')) 182: div << XMLText.new(@project['copyright'] + " - ") if @project['copyright'] 183: div << XMLText.new("Project: #{@project['name']} " + 184: "Version: #{@project['version']} - " + 185: "Created on #{TjTime.now.to_s("%Y-%m-%d %H:%M:%S")} " + 186: "with ") 187: div << XMLNamedText.new("#{AppConfig.softwareName}", 'a', 188: 'href' => "#{AppConfig.contact}") 189: div << XMLText.new(" v#{AppConfig.version}") 190: 191: fileName = 192: if a('interactive') || @name == '.' 193: # Interactive HTML reports are always sent to stdout. 194: '.' 195: else 196: # Prepend the specified output directory unless the provided file 197: # name is an absolute file name. 198: ((@name[0] == '/' ? '' : @project.outputDir) + 199: @name + '.html').untaint 200: end 201: html.write(fileName) 202: end
Generate Niku report
# File lib/reports/Report.rb, line 247 247: def generateNiku 248: begin 249: f = @name == '.' ? $stdout : 250: File.new(((@name[0] == '/' ? '' : @project.outputDir) + 251: @name + '.xml').untaint, 'w') 252: f.puts "#{@content.to_niku}" 253: rescue IOError 254: error('write_niku', "Cannot write to file #{@name}.\n#{$!}") 255: end 256: end
Generate time sheet drafts.
# File lib/reports/Report.rb, line 230 230: def generateTJP 231: begin 232: fileName = '.' 233: if @name == '.' 234: $stdout.write(@content.to_tjp) 235: else 236: fileName = (@name[0] == '/' ? '' : @project.outputDir) + @name 237: fileName += a('definitions').include?('project') ? '.tjp' : '.tji' 238: fileName.untaint 239: File.open(fileName, 'w') { |f| f.write(@content.to_tjp) } 240: end 241: rescue IOError 242: error('write_tjp', "Cannot write to file #{fileName}.\n#{$!}") 243: end 244: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.