Class Index [+]

Quicksearch

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

typeSpec[RW]
content[RW]

Public Class Methods

new(project, id, name, parent) click to toggle source

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

Public Instance Methods

error(id, message) click to toggle source
     # 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
generate(requestedFormats = nil) click to toggle source

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.

  1. :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
generateIntermediateFormat() click to toggle source

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
interactive?() click to toggle source

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
to_html() click to toggle source

Render the content of the report as HTML (without the framing).

     # File lib/taskjuggler/reports/Report.rb, line 115
115:     def to_html
116:       @content ? @content.to_html : nil
117:     end
warning(id, message) click to toggle source
     # File lib/taskjuggler/reports/Report.rb, line 136
136:     def warning(id, message)
137:       if message && !message.empty?
138:         @project.messageHandler.warning(id, message, @sourceFileInfo)
139:       end
140:     end

Private Instance Methods

a(attribute) click to toggle source

Convenience function to access a report attribute

     # File lib/taskjuggler/reports/Report.rb, line 144
144:     def a(attribute)
145:       get(attribute)
146:     end
copyAuxiliaryFiles() click to toggle source
     # 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
copyDirectory(dirName) click to toggle source
     # 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
dataDirError(dirName, dirs) click to toggle source
     # 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
directoryUpToDate?(auxSrcDir, auxDstDir) click to toggle source
     # 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
generateCSV() click to toggle source

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
generateHTML() click to toggle source

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
generateNiku() click to toggle source

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
generateTJP() click to toggle source

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.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.