Parent

Class Index [+]

Quicksearch

TaskJuggler::KeywordDocumentation

The textual TaskJuggler Project description consists of many keywords. The parser has built-in support to document the meaning and usage of these keywords. Most keywords are unique, but there can be exceptions. To resolve ambiguoties the keywords can be prefixed by a scope. The scope is usually a keyword that describes the context that the ambiguous keyword is used in. This class stores the keyword, the corresponding TextParser::Pattern and the context that the keyword is used in. It also stores information such as the list of optional attributes (keywords used in the context of the current keyword) and whether the keyword is scenario specific or not.

Attributes

keyword[R]
names[R]
pattern[R]
references[R]
optionalAttributes[R]
contexts[RW]
scenarioSpecific[RW]
inheritedFromProject[RW]
inheritedFromParent[RW]
predecessor[RW]
successor[RW]

Public Class Methods

new(rule, pattern, syntax, args, optAttrPatterns, manual) click to toggle source

Construct a new KeywordDocumentation object. rule is the TextParser::Rule and pattern is the corresponding TextParser::Pattern. syntax is an expanded syntax representation of the pattern. args is a Array of ParserTokenDoc that describe the arguments of the pattern. optAttrPatterns is an Array with references to TextParser::Patterns that are optional attributes to this keyword.

    # File lib/KeywordDocumentation.rb, line 44
44:     def initialize(rule, pattern, syntax, args, optAttrPatterns, manual)
45:       @messageHandler = MessageHandler.new(true)
46:       @rule = rule
47:       @pattern = pattern
48:       # The unique identifier. Usually the attribute or property name. To
49:       # disambiguate a .<scope> can be added.
50:       @keyword = pattern.keyword
51:       # Similar to @keyword, but without the scope. Since there could be
52:       # several, this is an Array of String objects.
53:       @names = []
54:       @syntax = syntax
55:       @args = args
56:       @manual = manual
57:       # Hash that maps patterns of optional attributes to a boolean value. It
58:       # is true if the pattern is a scenario specific attribute.
59:       @optAttrPatterns = optAttrPatterns
60:       # The above hash is later converted into a list that points to the
61:       # keyword documentation of the optional attribute.
62:       @optionalAttributes = []
63:       @scenarioSpecific = false
64:       @inheritedFromProject= false
65:       @inheritedFromParent = false
66:       @contexts = []
67:       @seeAlso = []
68:       # The following are references to the neighboring keyword in an
69:       # alphabetically sorted list.
70:       @predecessor = nil
71:       @successor = nil
72:       # Array to collect all references to other RichText objects.
73:       @references = []
74:     end

Public Instance Methods

computeInheritance(keywords, rules) click to toggle source
     # File lib/KeywordDocumentation.rb, line 143
143:     def computeInheritance(keywords, rules)
144:       property = nil
145:       @contexts.each do |kwd|
146:         if %( task resource account report shift scenario).include?(kwd.keyword)
147:           property = kwd.keyword
148:           break
149:         end
150:       end
151:       if property
152:         project = Project.new('id', 'dummy', '1.0', nil)
153:         propertySet = case property
154:                       when 'task'
155:                         project.tasks
156:                       when 'resource'
157:                         project.resources
158:                       when 'account'
159:                         project.accounts
160:                       when 'report'
161:                         project.reports
162:                       when 'shift'
163:                         project.shifts
164:                       when 'scenario'
165:                         project.scenarios
166:                       end
167:         keyword = @keyword
168:         keyword = keyword.split('.')[0] if keyword.include?('.')
169:         @inheritedFromProject = propertySet.inheritedFromProject?(keyword)
170:         @inheritedFromParent = propertySet.inheritedFromParent?(keyword)
171:       end
172:     end
crossReference(keywords, rules) click to toggle source

Post process the class member to set cross references to other KeywordDocumentation items.

     # File lib/KeywordDocumentation.rb, line 99
 99:     def crossReference(keywords, rules)
100:       # Get the attribute or property name of the Keyword. This is not unique
101:       # like @keyword since it's got no scope.
102:       @pattern.terminalTokens(rules).each do |tok|
103:         # Ignore patterns that don't have a real name.
104:         break if tok[0] == '{'
105: 
106:         @names << tok[0]
107:       end
108: 
109:       # Some arguments are references to other patterns. The current keyword
110:       # is added as context to such patterns.
111:       @args.each do |arg|
112:         if arg.pattern && checkReference(arg.pattern)
113:           kwd = keywords[arg.pattern.keyword]
114:           kwd.contexts << self unless kwd.contexts.include?(self)
115:         end
116:       end
117: 
118:       # Optional attributes are treated similarly. In addition we add them to
119:       # the @optionalAttributes list of this keyword.
120:       @optAttrPatterns.each do |pattern, scenarioSpecific|
121:         next unless checkReference(pattern)
122: 
123:         if (kwd = keywords[pattern.keyword]).nil?
124:           token = pattern.terminalToken(rules)
125:           $stderr.puts "Keyword #{keyword} has undocumented optional " +
126:                        "attribute #{token[0]}"
127:         else
128:           @optionalAttributes << kwd
129:           kwd.contexts << self unless kwd.contexts.include?(self)
130:           kwd.scenarioSpecific = true if scenarioSpecific
131:         end
132:       end
133: 
134:       # Resolve the seeAlso patterns to keyword references.
135:       @pattern.seeAlso.sort.each do |also|
136:         if keywords[also].nil?
137:           raise "See also reference #{also} of #{@pattern} is unknown"
138:         end
139:         @seeAlso << keywords[also]
140:       end
141:     end
generateHTML(directory) click to toggle source

Return a String that represents the keyword documentation in an XML formatted form.

     # File lib/KeywordDocumentation.rb, line 306
306:     def generateHTML(directory)
307:       html = HTMLDocument.new(:strict)
308:       head = html.generateHead(keyword,
309:                                { 'description' => 'The TaskJuggler Manual',
310:                                  'keywords' =>
311:                                  'taskjuggler, project, management' })
312:       head << @manual.generateStyleSheet
313: 
314:       html << (body = XMLElement.new('body'))
315:       body << @manual.generateHTMLHeader <<
316:         generateHTMLNavigationBar
317: 
318:       # Box with keyword name.
319:       body << (bbox = XMLElement.new('div',
320:         'style' => 'margin-left:5%; margin-right:5%'))
321:       bbox << (p = XMLElement.new('p'))
322:       p << (tab = XMLElement.new('table', 'align' => 'center',
323:                                  'class' => 'table'))
324: 
325:       tab << (tr = XMLElement.new('tr', 'align' => 'left'))
326:       tr << XMLNamedText.new('Keyword', 'td', 'class' => 'tag',
327:                             'style' => 'width:15%')
328:       tr << XMLNamedText.new(title, 'td', 'class' => 'descr',
329:                              'style' => 'width:85%; font-weight:bold')
330: 
331:       # Box with purpose, syntax, arguments and context.
332:       bbox << (p = XMLElement.new('p'))
333:       p << (tab = XMLElement.new('table', 'align' => 'center',
334:                                  'class' => 'table'))
335:       tab << (colgroup = XMLElement.new('colgroup'))
336:       colgroup << XMLElement.new('col', 'width' => '15%')
337:       colgroup << XMLElement.new('col', 'width' => '85%')
338: 
339:       tab << (tr = XMLElement.new('tr', 'align' => 'left'))
340:       tr << XMLNamedText.new('Purpose', 'td', 'class' => 'tag')
341:       tr << (td = XMLElement.new('td', 'class' => 'descr'))
342:       td << newRichText(@pattern.doc).to_html
343:       if @syntax != '[{ <attributes> }]'
344:         tab << (tr = XMLElement.new('tr', 'align' => 'left'))
345:         tr << XMLNamedText.new('Syntax', 'td', 'class' => 'tag')
346:         tr << (td = XMLElement.new('td', 'class' => 'descr'))
347:         td << XMLNamedText.new("#{@syntax}", 'code')
348: 
349:         tab << (tr = XMLElement.new('tr', 'align' => 'left'))
350:         tr << XMLNamedText.new('Arguments', 'td', 'class' => 'tag')
351:         if @args.empty?
352:           tr << XMLNamedText.new('none', 'td', 'class' => 'descr')
353:         else
354:           tr << (td = XMLElement.new('td'))
355:           td << (tab1 = XMLElement.new('table', 'class' => 'attrtable',
356:                                        'style' => 'width:100%;'))
357:           @args.each do |arg|
358:             tab1 << (tr1 = XMLElement.new('tr'))
359:             if arg.typeSpec.nil? || ('<' + arg.name + '>') == arg.typeSpec
360:               tr1 << XMLNamedText.new("#{arg.name}", 'td', 'class' => 'attrtag',
361:                                                            'width' => '30%')
362:             else
363:               typeSpec = arg.typeSpec
364:               typeName = typeSpec[1..2]
365:               typeSpec[0] = '['
366:               typeSpec[1] = ']'
367:               tr1 << (td = XMLElement.new('td', 'class' => 'attrtag',
368:                                                 'width' => '30%'))
369:               td << XMLText.new("#{arg.name} [")
370:               td << XMLNamedText.new(
371:                 typeName, 'a', 'href' =>
372:                                "The_TaskJuggler_Syntax.html\##{typeName}")
373:               td << XMLText.new(']')
374:             end
375:             tr1 << (td = XMLElement.new('td', 'class' => 'attrdescr',
376:               'style' => 'margin-top:2px; margin-bottom:2px;'))
377:             td << newRichText(arg.text ||
378:                               "See [[#{arg.name}]] for details.").to_html
379:           end
380:         end
381:       end
382: 
383:       tab << (tr = XMLElement.new('tr', 'align' => 'left'))
384:       tr << XMLNamedText.new('Context', 'td', 'class' => 'tag')
385:       if @contexts.empty?
386:         tr << (td = XMLElement.new('td', 'class' => 'descr'))
387:         td << XMLNamedText.new('Global scope', 'a',
388:           'href' => 'Getting_Started.html#Structure_of_a_TJP_File')
389:       else
390:         tr << (td = XMLElement.new('td', 'class' => 'descr'))
391:         first = true
392:         @contexts.each do |context|
393:           if first
394:             first = false
395:           else
396:             td << XMLText.new(', ')
397:           end
398:           keywordHTMLRef(td, context)
399:         end
400:       end
401: 
402:       unless @seeAlso.empty?
403:         tab << (tr = XMLElement.new('tr', 'align' => 'left'))
404:         tr << XMLNamedText.new('See also', 'td', 'class' => 'tag')
405:         first = true
406:         tr << (td = XMLElement.new('td', 'class' => 'descr'))
407:         @seeAlso.each do |also|
408:           if first
409:             first = false
410:           else
411:             td << XMLText.new(', ')
412:           end
413:           keywordHTMLRef(td, also)
414:         end
415:       end
416: 
417:       # Box with attributes.
418:       unless @optionalAttributes.empty?
419:         @optionalAttributes.sort! do |a, b|
420:           a.keyword <=> b.keyword
421:         end
422: 
423:         showDetails = false
424:         @optionalAttributes.each do |attr|
425:           if attr.scenarioSpecific || attr.inheritedFromProject ||
426:              attr.inheritedFromParent
427:             showDetails = true
428:             break
429:           end
430:         end
431: 
432:         bbox << (p = XMLElement.new('p'))
433:         p << (tab = XMLElement.new('table', 'align' => 'center',
434:                                    'class' => 'table'))
435:         tab << (tr = XMLElement.new('tr', 'align' => 'left'))
436:         if showDetails
437:           # Table of all attributes with checkmarks for being scenario
438:           # specific, inherited from parent and inherited from global scope.
439:           tr << XMLNamedText.new('Attributes', 'td', 'class' => 'tag',
440:                                  'rowspan' =>
441:                                  "#{@optionalAttributes.length + 1}",
442:                                  'style' => 'width:15%')
443:           tr << XMLNamedText.new('Name', 'td', 'class' => 'tag',
444:                                  'style' => 'width:40%')
445:           tr << XMLNamedText.new('Scen. spec.', 'td', 'class' => 'tag',
446:                                  'style' => 'width:15%')
447:           tr << XMLNamedText.new('Inh. fm. Global', 'td', 'class' => 'tag',
448:                                  'style' => 'width:15%')
449:           tr << XMLNamedText.new('Inh. fm. Parent', 'td', 'class' => 'tag',
450:                                  'style' => 'width:15%')
451:           @optionalAttributes.each do |attr|
452:             tab << (tr = XMLElement.new('tr', 'align' => 'left'))
453:             tr << (td = XMLElement.new('td', 'class' => 'descr'))
454:             keywordHTMLRef(td, attr)
455:             tr << (td = XMLElement.new('td', 'align' => 'center',
456:                                        'class' => 'descr'))
457:             td << XMLText.new('x') if attr.scenarioSpecific
458:             tr << (td = XMLElement.new('td', 'align' => 'center',
459:                                        'class' => 'descr'))
460:             td << XMLText.new('x') if attr.inheritedFromProject
461:             tr << (td = XMLElement.new('td', 'align' => 'center',
462:                                        'class' => 'descr'))
463:             td << XMLText.new('x') if attr.inheritedFromParent
464:           end
465:         else
466:           # Comma separated list of all attributes.
467:           tr << XMLNamedText.new('Attributes', 'td', 'class' => 'tag',
468:                                  'style' => 'width:15%')
469:           tr << (td = XMLElement.new('td', 'class' => 'descr',
470:                                      'style' => 'width:85%'))
471:           first = true
472:           @optionalAttributes.each do |attr|
473:             if first
474:               first = false
475:             else
476:               td << XMLText.new(', ')
477:             end
478:             keywordHTMLRef(td, attr)
479:           end
480:         end
481:       end
482: 
483:       if @pattern.exampleFile
484:         exampleDir = AppConfig.dataDirs('test')[0] + "TestSuite/Syntax/Correct/"
485:         example = TjpExample.new
486:         fileName = "#{exampleDir}/#{@pattern.exampleFile}.tjp"
487:         example.open(fileName)
488:         bbox << (frame = XMLElement.new('div', 'class' => 'codeframe'))
489:         frame << (pre = XMLElement.new('pre', 'class' => 'code'))
490:         unless (text = example.to_s(@pattern.exampleTag))
491:           raise "There is no tag '#{@pattern.exampleTag}' in file " +
492:             "#{fileName}."
493:         end
494:         pre << XMLText.new(text)
495:       end
496: 
497:       body << generateHTMLNavigationBar
498:       body << @manual.generateHTMLFooter
499: 
500:       if directory
501:         html.write(directory + "#{keyword}.html")
502:       else
503:         puts html.to_s
504:       end
505:     end
globalScope?() click to toggle source

Returns true of the keyword can be used outside of any other keyword context.

    # File lib/KeywordDocumentation.rb, line 89
89:     def globalScope?
90:       return true if @contexts.empty?
91:       @contexts.each do |context|
92:         return true if context.keyword == 'properties'
93:       end
94:       false
95:     end
isProperty?() click to toggle source

Returns true of the KeywordDocumentation is documenting a TJP property (task, resources, etc.). A TJP property can be nested.

    # File lib/KeywordDocumentation.rb, line 78
78:     def isProperty?
79:       # I haven't found a good way to automatically detect all the various
80:       # report types as properties. The non-nestable ones need to be added
81:       # manually here.
82:       return true if %( export nikureport timesheetreport statussheetreport).
83:                      include?(keyword)
84:       @optionalAttributes.include?(self)
85:     end
title() click to toggle source

Return the keyword name in a more readable form. E.g. ‘foo.bar’ is returned as ‘foo (bar)’. ‘foo’ will remain ‘foo’.

     # File lib/KeywordDocumentation.rb, line 176
176:     def title
177:       kwTokens = @keyword.split('.')
178:       if kwTokens.size == 1
179:         title = @keyword
180:       else
181:         title = "#{kwTokens[0]} (#{kwTokens[1]})"
182:       end
183:       title
184:     end
to_s() click to toggle source

Return the complete documentation of this keyword as formatted text string.

     # File lib/KeywordDocumentation.rb, line 188
188:     def to_s
189:       tagW = 13
190:       textW = 79
191: 
192:       # Top line with multiple elements
193:       str = "Keyword:     #{@keyword}     " +
194:             "Scenario Specific: #{@scenarioSpecific ? 'Yes' : 'No'}    " +
195:             "Inherited: #{@inheritedFromParent ? 'Yes' : 'No'}\n\n"
196: 
197:       str += "Purpose:     #{format(tagW, newRichText(@pattern.doc).to_s,
198:                                     textW)}\n\n"
199: 
200:       if @syntax != '[{ <attributes> }]'
201:         str += "Syntax:      #{format(tagW, @syntax, textW)}\n\n"
202: 
203:         str += "Arguments:   "
204:         if @args.empty?
205:           str += format(tagW, "none\n\n", textW)
206:         else
207:           argStr = ''
208:           @args.each do |arg|
209:             argText = arg.text || "See '#{arg.name}' for details."
210:             if arg.typeSpec.nil? || ("<#{arg.name}>") == arg.typeSpec
211:               indent = arg.name.length + 2
212:               argStr += "#{arg.name}: " +
213:                         "#{format(indent, argText, textW - tagW)}\n"
214:             else
215:               typeSpec = arg.typeSpec
216:               typeSpec[0] = '['
217:               typeSpec[1] = ']'
218:               indent = arg.name.length + typeSpec.size + 3
219:               argStr += "#{arg.name} #{typeSpec}: " +
220:                         "#{format(indent, argText, textW - tagW)}\n"
221:             end
222:           end
223:           str += indent(tagW, argStr)
224:         end
225:         str += "\n"
226:       end
227: 
228:       str += 'Context:     '
229:       if @contexts.empty?
230:         str += format(tagW, 'Global scope', textW)
231:       else
232:         cxtStr = ''
233:         @contexts.each do |context|
234:           unless cxtStr.empty?
235:             cxtStr += ', '
236:           end
237:           cxtStr += context.keyword
238:         end
239:         str += format(tagW, cxtStr, textW)
240:       end
241: 
242:       str += "\n\nAttributes:  "
243:       if @optionalAttributes.empty?
244:         str += "none\n\n"
245:       else
246:         attrStr = ''
247:         @optionalAttributes.sort! do |a, b|
248:           a.keyword <=> b.keyword
249:         end
250:         showLegend = false
251:         @optionalAttributes.each do |attr|
252:           unless attrStr.empty?
253:             attrStr += ', '
254:           end
255:           attrStr += attr.keyword
256:           if attr.scenarioSpecific || attr.inheritedFromProject ||
257:              attr.inheritedFromParent
258:             first = true
259:             showLegend = true
260:             attrStr += '['
261:             if attr.scenarioSpecific
262:               attrStr += 'sc'
263:               first = false
264:             end
265:             if attr.inheritedFromProject
266:               attrStr += ':' unless first
267:               attrStr += 'ig'
268:               first = false
269:             end
270:             if attr.inheritedFromParent
271:               attrStr += ':' unless first
272:               attrStr += 'ip'
273:             end
274:             attrStr += ']'
275:           end
276:         end
277:         if showLegend
278:           attrStr += "\n\n[sc] : Attribute is scenario specific" +
279:                      "\n[ig] : Attribute is inherited from global attribute" +
280:                      "\n[ip] : Attribute is inherited from parent property"
281:         end
282:         str += format(tagW, attrStr, textW)
283:         str += "\n"
284:       end
285: 
286:       unless @seeAlso.empty?
287:         str += "See also:    "
288:         alsoStr = ''
289:         @seeAlso.each do |also|
290:           unless alsoStr.empty?
291:             alsoStr += ', '
292:           end
293:           alsoStr += also.keyword
294:         end
295:         str += format(tagW, alsoStr, textW)
296:         str += "\n"
297:       end
298: 
299:   #    str += "Rule:    #{@rule.name}\n" if @rule
300:   #    str += "Pattern: #{@pattern.tokens.join(' ')}\n" if @pattern
301:       str
302:     end

Private Instance Methods

checkReference(pattern) click to toggle source
     # File lib/KeywordDocumentation.rb, line 509
509:     def checkReference(pattern)
510:       if pattern.keyword.nil?
511:         $stderr.puts "Pattern #{pattern} is undocumented but referenced by " +
512:                      "#{@keyword}."
513:         false
514:       end
515:       true
516:     end
format(indent, str, width) click to toggle source
     # File lib/KeywordDocumentation.rb, line 570
570:     def format(indent, str, width)
571:       TextFormatter.new(width, indent).format(str)[indent..1]
572:     end
generateHTMLNavigationBar() click to toggle source

Generate the navigation bar.

     # File lib/KeywordDocumentation.rb, line 523
523:     def generateHTMLNavigationBar
524:       @manual.generateHTMLNavigationBar(
525:         @predecessor ? @predecessor.title : nil,
526:         @predecessor ? "#{@predecessor.keyword}.html" : nil,
527:         @successor ? @successor.title : nil,
528:         @successor ? "#{@successor.keyword}.html" : nil)
529:     end
indent(width, str) click to toggle source
     # File lib/KeywordDocumentation.rb, line 518
518:     def indent(width, str)
519:       TextFormatter.new(80, width).indent(str)[width..1]
520:     end
keywordHTMLRef(parent, keyword) click to toggle source

Return a HTML object with a link to the manual page for the keyword.

     # File lib/KeywordDocumentation.rb, line 532
532:     def keywordHTMLRef(parent, keyword)
533:       parent << XMLNamedText.new(keyword.title,
534:                                  'a', 'href' => "#{keyword.keyword}.html")
535:     end
listHTMLAttributes(list, width) click to toggle source

Utility function to turn a list of keywords into a comma separated list of HTML references to the files of these keywords. All embedded in a table cell element. list is the KeywordDocumentation list. width is the percentage width of the cell.

     # File lib/KeywordDocumentation.rb, line 554
554:     def listHTMLAttributes(list, width)
555:       td = XMLElement.new('td', 'class' => 'descr',
556:                           'style' => "width:#{width}%")
557:       first = true
558:       list.each do |attr|
559:         if first
560:           first = false
561:         else
562:           td << XMLText.new(', ')
563:         end
564:         keywordHTMLRef(td, attr)
565:       end
566: 
567:       td
568:     end
newRichText(text) 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 an error message.

     # File lib/KeywordDocumentation.rb, line 540
540:     def newRichText(text)
541:       rText = RichText.new(text, [], @messageHandler)
542:       unless (rti = rText.generateIntermediateFormat)
543:         @messageHandler.error('rich_text',
544:                               "Error in RichText of rule #{@keyword}")
545:       end
546:       @references += rti.internalReferences
547:       rti
548:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.