Included Modules

Class Index [+]

Quicksearch

TaskJuggler::ProjectFileParser

This class specializes the TextParser class for use with TaskJuggler project files (TJP Files). The primary purpose is to provide functionality that make it more comfortable to define the TaskJuggler syntax in a form that is human creatable but also powerful enough to define the data structures the parser needs to understand the syntax.

By adding some additional information to the syntax rules, we can also generate the complete reference manual from this rule set.

Public Class Methods

new(messageHandler) click to toggle source

Create the parser object. messageHandler is a TjMessageHandler that is used for error reporting.

    # File lib/ProjectFileParser.rb, line 36
36:     def initialize(messageHandler)
37:       super
38: 
39:       @tjMessageHandler = messageHandler
40: 
41:       # Define the token types that the ProjectFileScanner may return for
42:       # variable elements.
43:       @variables = [ :INTEGER, :FLOAT, :DATE, :TIME, :STRING, :LITERAL,
44:                      :ID, :ID_WITH_COLON, :ABSOLUTE_ID, :MACRO ]
45: 
46:       initRules
47:       updateParserTables
48: 
49:       @project = nil
50:     end

Public Instance Methods

close() click to toggle source

Call this function to cleanup the parser structures after the file processing has been completed.

    # File lib/ProjectFileParser.rb, line 70
70:     def close
71:       @scanner.close
72:     end
nextToken() click to toggle source

This function will deliver the next token from the scanner. A token is a two element Array that contains the ID or type of the token as well as the text string of the token.

    # File lib/ProjectFileParser.rb, line 77
77:     def nextToken
78:       @scanner.nextToken
79:     end
open(file, master, fileNameIsBuffer = false) click to toggle source

Call this function with the master file to start processing a TJP file or a set of TJP files.

    # File lib/ProjectFileParser.rb, line 54
54:     def open(file, master, fileNameIsBuffer = false)
55:       @scanner = ProjectFileScanner.new(file, @messageHandler)
56:       # We need the ProjectFileScanner object for error reporting.
57:       if master && !fileNameIsBuffer && file != '.' && file[4, 4] != '.tjp'
58:         error('illegal_extension', "Project file name must end with " +
59:               '\.tjp\ extension')
60:       end
61:       @scanner.open(fileNameIsBuffer)
62: 
63:       @property = nil
64:       @scenarioIdx = 0
65:       initFileStack
66:     end
parseReportAttributes(report, attributes) click to toggle source
     # File lib/ProjectFileParser.rb, line 104
104:     def parseReportAttributes(report, attributes)
105:       open(attributes, false, true)
106:       @property = report
107:       @project = report.project
108:       parse(:reportAttributes)
109:     end
returnToken(token) click to toggle source

This function can be used to return tokens. Returned tokens will be pushed on a LIFO stack. To preserve the order of the original tokens the last token must be returned first. This mechanism is used to implement look-ahead functionality.

    # File lib/ProjectFileParser.rb, line 85
85:     def returnToken(token)
86:       @scanner.returnToken(token)
87:     end
setGlobalMacros() click to toggle source

A set of standard marcros is defined in all files as soon as the project header has been read. Calling this functions gets the values from @project and inserts the Macro objects into the ProjectFileScanner.

     # File lib/ProjectFileParser.rb, line 92
 92:     def setGlobalMacros
 93:       @scanner.addMacro(Macro.new('projectstart', @project['start'].to_s,
 94:                                   @scanner.sourceFileInfo))
 95:       @scanner.addMacro(Macro.new('projectend', @project['end'].to_s,
 96:                                   @scanner.sourceFileInfo))
 97:       @scanner.addMacro(Macro.new('now', @project['now'].to_s,
 98:                                   @scanner.sourceFileInfo))
 99:       @scanner.addMacro(Macro.new('today', @project['now'].
100:                                    to_s(@project['timeFormat']),
101:                                   @scanner.sourceFileInfo))
102:     end

Private Instance Methods

allOrNothingListRule(name, items) click to toggle source

This function creates a set of rules to describe a list of keywords. name is the name of the top-level rule and items can be a Hash or Array. The array just contains the allowed keywords, the Hash contains keyword/description pairs. The description is used to describe the keyword in the manual. The syntax supports two special cases. A ’*’ means all items in the list and ’-’ means the list is empty.

     # File lib/ProjectFileParser.rb, line 280
280:     def allOrNothingListRule(name, items)
281:       newRule(name) {
282:         # A '*' means all possible items should be in the list.
283:         pattern(%( _* ), lambda {
284:           KeywordArray.new([ '*' ])
285:         })
286:         descr('A shortcut for all items')
287:         # A '-' means the list should be empty.
288:         pattern([ '_-' ], lambda {
289:           KeywordArray.new
290:         })
291:         descr('No items')
292:         # Or the list consists of one or more comma separated keywords.
293:         pattern([ "!#{name}_AoN_ruleItems" ], lambda {
294:           KeywordArray.new(@val[0])
295:         })
296:       }
297:       # Create the rule for the comma separated list.
298:       newRule("#{name}_AoN_ruleItems") {
299:         listRule("more#{name}_AoN_ruleItems", "!#{name}_AoN_ruleItem")
300:       }
301:       # Create the rule for the keywords with their description.
302:       newRule("#{name}_AoN_ruleItem") {
303:         if items.is_a?(Array)
304:           items.each { |keyword| singlePattern('_' + keyword) }
305:         else
306:           items.each do |keyword, description|
307:             singlePattern('_' + keyword)
308:             descr(description) if description
309:           end
310:         end
311:       }
312:     end
also(seeAlso) click to toggle source

Add a reference to another pattern. This information is only used to generate the documentation for the patterns of this rule.

     # File lib/ProjectFileParser.rb, line 383
383:     def also(seeAlso)
384:       seeAlso = [ seeAlso ] unless seeAlso.is_a?(Array)
385:       @cr.setSeeAlso(seeAlso)
386:     end
arg(idx, name, text) click to toggle source

Add documentation for the arguments with index idx of the current pattern of the currently processed rule. name is that should be used for this variable. text is the documentation text.

     # File lib/ProjectFileParser.rb, line 371
371:     def arg(idx, name, text)
372:       @cr.setArg(idx, TextParser::TokenDoc.new(name, text))
373:     end
checkBooking(task, resource) click to toggle source

Convenience function to check the integrity of a booking statement.

     # File lib/ProjectFileParser.rb, line 152
152:     def checkBooking(task, resource)
153:       unless task.leaf?
154:         error('booking_no_leaf', "#{task.fullId} is not a leaf task",
155:               @sourceFileInfo[0], task)
156:       end
157:       if task['milestone', @scenarioIdx]
158:         error('booking_milestone', "You cannot add bookings to a milestone",
159:               @sourceFileInfo[0], task)
160:       end
161:       unless resource.leaf?
162:         error('booking_group', "You cannot book a group resource",
163:               @sourceFileInfo[0], task)
164:       end
165:     end
checkContainer(attribute) click to toggle source

Make sure that certain attributes are not used after sub properties have been added to a property.

     # File lib/ProjectFileParser.rb, line 126
126:     def checkContainer(attribute)
127:       if @property.container?
128:         error('container_attribute',
129:               "The attribute #{attribute} may not be used for this property " +
130:               'after sub properties have been added.', @sourceFileInfo[0],
131:               @property)
132:       end
133:     end
checkInterval(iv) click to toggle source

Convenience function to check that an Interval fits completely within the project time frame.

     # File lib/ProjectFileParser.rb, line 137
137:     def checkInterval(iv)
138:       # Make sure the interval is within the project time frame.
139:       if iv.start < @project['start'] || iv.start >= @project['end']
140:         error('interval_start_in_range',
141:               "Start date #{iv.start} must be within the project time frame " +
142:               "(#{@project['start']} - #{@project['end']})")
143:       end
144:       if iv.end <= @project['start'] || iv.end > @project['end']
145:         error('interval_end_in_range',
146:               "End date #{iv.end} must be within the project time frame " +
147:               "(#{@project['start']} - #{@project['end']})")
148:       end
149:     end
columnTitle(colId) click to toggle source

Determine the title of the column with the ID colId. The title may be from the static set or be from a user defined attribute.

     # File lib/ProjectFileParser.rb, line 396
396:     def columnTitle(colId)
397:       TableReport.defaultColumnTitle(colId) ||
398:         @project.attributeName(colId)
399:     end
commaListRule(listItem) click to toggle source
     # File lib/ProjectFileParser.rb, line 327
327:     def commaListRule(listItem)
328:       optional
329:       repeatable
330:       pattern([ '_,', "#{listItem}" ], lambda {
331:         @val[1]
332:       })
333:     end
descr(text) click to toggle source

Add documentation for patterns that only consists of a single terminal token.

     # File lib/ProjectFileParser.rb, line 359
359:     def descr(text)
360:       if @cr.patterns[1].length != 1 ||
361:          (@cr.patterns[1][0][0] != :literal &&
362:           @cr.patterns[1][0][0] != :variable)
363:         raise 'descr() may only be used for patterns with terminal tokens.'
364:       end
365:       arg(0, nil, text)
366:     end
doc(keyword, text) click to toggle source

Add documentation for the current pattern of the currently processed rule.

     # File lib/ProjectFileParser.rb, line 353
353:     def doc(keyword, text)
354:       @cr.setDoc(keyword, text)
355:     end
example(file, tag = nil) click to toggle source

Add a TJP file or parts of it as an example. The TJP file must be in the directory test/TestSuite/Syntax/Correct. tag can be used to identify that only a part of the file should be included.

     # File lib/ProjectFileParser.rb, line 391
391:     def example(file, tag = nil)
392:       @cr.setExample(file, tag)
393:     end
extendPropertySetDefinition(type, default) click to toggle source

The TaskJuggler syntax can be extended by the user when the properties are extended with user-defined attributes. These attribute definitions introduce keywords that have to be processed like the build-in keywords. The parser therefor needs to adapt on the fly to the new syntax. By calling this function, a TaskJuggler property can be extended with a new attribute. @propertySet determines what property should be extended. type is the attribute type, default is the default value.

     # File lib/ProjectFileParser.rb, line 174
174:     def extendPropertySetDefinition(type, default)
175:       if @propertySet.knownAttribute?(@val[1])
176:         error('extend_redefinition',
177:               "The extended attribute #{@val[1]} has already been defined.")
178:       end
179: 
180:       # Determine the values for scenarioSpecific and inheritable.
181:       inherit = false
182:       scenarioSpecific = false
183:       unless @val[3].nil?
184:         @val[3].each do |option|
185:           case option
186:           when 'inherit'
187:             inherit = true
188:           when 'scenariospecific'
189:             scenarioSpecific = true
190:           end
191:         end
192:       end
193:       # Register the new Attribute type with the Property set it should belong
194:       # to.
195:       @propertySet.addAttributeType(AttributeDefinition.new(
196:         @val[1], @val[2], type, inherit, false, scenarioSpecific, default, true))
197: 
198:       # Add the new user-defined attribute as reportable attribute to the parser
199:       # rule.
200:       oldCurrentRule = @cr
201:       @cr = @rules[:reportableAttributes]
202:       singlePattern('_' + @val[1])
203:       descr(@val[2])
204:       @cr = oldCurrentRule
205: 
206:       scenarioSpecific
207:     end
initFileStack() click to toggle source

To manage certain variables that have file scope throughout a hierachie of nested include files, we use a @fileStack to track those variables. The values primarily live in their class instance variables. But upon return from an included file, we need to restore the old values. This function creates or resets the stack.

     # File lib/ProjectFileParser.rb, line 407
407:     def initFileStack
408:       @fileStackVariables = %( taskprefix reportprefix
409:                                 resourceprefix accountprefix )
410:       stackEntry = {}
411:       @fileStackVariables.each do |var|
412:         stackEntry[var] = ''
413:         instance_variable_set('@' + var, '')
414:       end
415:       @fileStack = [ stackEntry ]
416:     end
lastSyntaxToken(idx) click to toggle source

Restrict the syntax documentation of the previously defined pattern to the first idx tokens.

     # File lib/ProjectFileParser.rb, line 377
377:     def lastSyntaxToken(idx)
378:       @cr.setLastSyntaxToken(idx)
379:     end
listRule(name, listItem) click to toggle source
     # File lib/ProjectFileParser.rb, line 314
314:     def listRule(name, listItem)
315:       pattern([ "#{listItem}", "!#{name}" ], lambda {
316:         if @val[1] && @val[1].include?(@val[0])
317:           error('duplicate_in_list',
318:                 "Duplicate items in list.")
319:         end
320:         [ @val[0] ] + (@val[1].nil? ? [] : @val[1])
321:       })
322:       newRule(name) {
323:         commaListRule(listItem)
324:       }
325:     end
newReport(id, name, type, sourceFileInfo) click to toggle source

This method is a convenience wrapper around Project.new. It checks if the report name already exists. It also triggers the attribute inheritance. name is the name of the report, type is the report type. sourceFileInfo is a SourceFileInfo of the report definition. The method returns the newly created Report.

     # File lib/ProjectFileParser.rb, line 240
240:     def newReport(id, name, type, sourceFileInfo)
241:       @reportCounter += 1
242:       if name != '.'
243:         if @project.reportByName(name)
244:           error('report_redefinition',
245:                 "A report with the name #{name} has already been defined.")
246:         end
247:       end
248:       @property = Report.new(@project, id || "report#{@reportCounter}",
249:                              name, nil)
250:       @property.typeSpec = type
251:       @property.sourceFileInfo = sourceFileInfo
252:       @property.set('formats', [ :tjp ])
253:       @property.inheritAttributes
254:       @property
255:     end
newRichText(text, sfi, tokenSet = nil) click to toggle source

This function is primarily a wrapper around the RichText constructor. It catches all RichTextScanner processing problems and converts the exception data into a MessageHandler message that points to the correct location. This is necessary, because the RichText parser knows nothing about the actual input file. So we have to map the error location in the RichText input stream back to the position in the project file. sfi is the SourceFileInfo of the input string. To limit the supported set of variable tokens, a subset can be provided by tokenSet.

     # File lib/ProjectFileParser.rb, line 217
217:     def newRichText(text, sfi, tokenSet = nil)
218:       rText = RichText.new(text, RTFHandlers.create(@project, sfi))
219:       unless (rti = rText.generateIntermediateFormat( [ 0, 0, 0], tokenSet))
220:         rText.messageHandler.messages.each do |msg|
221:           # Map the SourceFileInfo of the RichText back to the original
222:           # location in the input file.
223:           sfi = SourceFileInfo.new(sfi.fileName,
224:                                    sfi.lineNo + msg.sourceFileInfo.lineNo - 1,
225:                                    0)
226:           # Then replay the error message.
227:           @messageHandler.addMessage(msg.type, msg.id, msg.message, sfi,
228:                                      msg.line)
229:         end
230:       end
231:       rti.sectionNumbers = false
232:       rti
233:     end
optionsRule(attributes) click to toggle source

Create pattern that turns the rule into the definition for optional attributes. attributes is the rule that lists these attributes.

     # File lib/ProjectFileParser.rb, line 337
337:     def optionsRule(attributes)
338:       optional
339:       pattern([ '_{', "!#{attributes}", '_}' ], lambda {
340:         @val[1]
341:       })
342:     end
popFileStack() click to toggle source

Pop the last stack entry from the @fileStack and restore the class variables according to the now top-entry.

     # File lib/ProjectFileParser.rb, line 429
429:     def popFileStack
430:       @fileStack.pop
431:       stackEntry = @fileStack.last
432:       @fileStackVariables.each do |var|
433:         instance_variable_set('@' + var, stackEntry[var])
434:       end
435:       # Include files can only occur at global level or in the project header.
436:       # In both cases, the @property was nil on including and must be reset to
437:       # nil again after the include file.
438:       @property = nil
439:     end
pushFileStack() click to toggle source

Push a new set of variables onto the @fileStack.

     # File lib/ProjectFileParser.rb, line 419
419:     def pushFileStack
420:       stackEntry = {}
421:       @fileStackVariables.each do |var|
422:         stackEntry[var] = instance_variable_get('@' + var)
423:       end
424:       @fileStack << stackEntry
425:     end
setLimit(name, value, interval) click to toggle source

If the @limitResources list is not empty, we have to create a Limits object for each Resource. Otherwise, one Limits object is enough.

     # File lib/ProjectFileParser.rb, line 260
260:     def setLimit(name, value, interval)
261:       if @limitResources.empty?
262:         @limits.setLimit(name, value, interval)
263:       else
264:         @limitResources.each do |resource|
265:           @limits.setLimit(name, value, interval, resource)
266:         end
267:       end
268:     end
singlePattern(item) click to toggle source

Create a pattern with just a single item. The pattern returns the value of that item.

     # File lib/ProjectFileParser.rb, line 346
346:     def singlePattern(item)
347:       pattern([ item ], lambda {
348:         @val[0]
349:       })
350:     end
weekDay(name) click to toggle source

Utility function that convers English weekday names into their index number and does some error checking. It returns 0 for ‘sun’, 1 for ‘mon’ and so on.

     # File lib/ProjectFileParser.rb, line 116
116:     def weekDay(name)
117:       names = %( sun mon tue wed thu fri sat )
118:       if (day = names.index(@val[0])).nil?
119:         error('weekday', "Weekday name expected (#{names.join(', ')})")
120:       end
121:       day
122:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.