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

Public Instance Methods

computeInheritance(keywords, rules) click to toggle source
     # File lib/KeywordDocumentation.rb, line 142
142:     def computeInheritance(keywords, rules)
143:       property = nil
144:       @contexts.each do |kwd|
145:         if %( task resource account report shift scenario).include?(kwd.keyword)
146:           property = kwd.keyword
147:           break
148:         end
149:       end
150:       if property
151:         project = Project.new('id', 'dummy', '1.0', nil)
152:         propertySet = case property
153:                       when 'task'
154:                         project.tasks
155:                       when 'resource'
156:                         project.resources
157:                       when 'account'
158:                         project.accounts
159:                       when 'report'
160:                         project.reports
161:                       when 'shift'
162:                         project.shifts
163:                       when 'scenario'
164:                         project.scenarios
165:                       end
166:         keyword = @keyword
167:         keyword = keyword.split('.')[0] if keyword.include?('.')
168:         @inheritedFromProject = propertySet.inheritedFromProject?(keyword)
169:         @inheritedFromParent = propertySet.inheritedFromParent?(keyword)
170:       end
171:     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 98
 98:     def crossReference(keywords, rules)
 99:       # Get the attribute or property name of the Keyword. This is not unique
100:       # like @keyword since it's got no scope.
101:       @pattern.terminalTokens(rules).each do |tok|
102:         # Ignore patterns that don't have a real name.
103:         break if tok[0] == '{'
104: 
105:         @names << tok[0]
106:       end
107: 
108:       # Some arguments are references to other patterns. The current keyword
109:       # is added as context to such patterns.
110:       @args.each do |arg|
111:         if arg.pattern && checkReference(arg.pattern)
112:           kwd = keywords[arg.pattern.keyword]
113:           kwd.contexts << self unless kwd.contexts.include?(self)
114:         end
115:       end
116: 
117:       # Optional attributes are treated similarly. In addition we add them to
118:       # the @optionalAttributes list of this keyword.
119:       @optAttrPatterns.each do |pattern, scenarioSpecific|
120:         next unless checkReference(pattern)
121: 
122:         if (kwd = keywords[pattern.keyword]).nil?
123:           token = pattern.terminalToken(rules)
124:           $stderr.puts "Keyword #{keyword} has undocumented optional " +
125:                        "attribute #{token[0]}"
126:         else
127:           @optionalAttributes << kwd
128:           kwd.contexts << self unless kwd.contexts.include?(self)
129:           kwd.scenarioSpecific = true if scenarioSpecific
130:         end
131:       end
132: 
133:       # Resolve the seeAlso patterns to keyword references.
134:       @pattern.seeAlso.sort.each do |also|
135:         if keywords[also].nil?
136:           raise "See also reference #{also} of #{@pattern} is unknown"
137:         end
138:         @seeAlso << keywords[also]
139:       end
140:     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 305
305:     def generateHTML(directory)
306:       html = HTMLDocument.new(:strict)
307:       head = html.generateHead(keyword,
308:                                { 'description' => 'The TaskJuggler Manual',
309:                                  'keywords' =>
310:                                  'taskjuggler, project, management' })
311:       head << @manual.generateStyleSheet
312: 
313:       html << (body = XMLElement.new('body'))
314:       body << @manual.generateHTMLHeader <<
315:         generateHTMLNavigationBar
316: 
317:       # Box with keyword name.
318:       body << (bbox = XMLElement.new('div',
319:         'style' => 'margin-left:5%; margin-right:5%'))
320:       bbox << (p = XMLElement.new('p'))
321:       p << (tab = XMLElement.new('table', 'align' => 'center',
322:                                  'class' => 'table'))
323: 
324:       tab << (tr = XMLElement.new('tr', 'align' => 'left'))
325:       tr << XMLNamedText.new('Keyword', 'td', 'class' => 'tag',
326:                             'style' => 'width:15%')
327:       tr << XMLNamedText.new(title, 'td', 'class' => 'descr',
328:                              'style' => 'width:85%; font-weight:bold')
329: 
330:       # Box with purpose, syntax, arguments and context.
331:       bbox << (p = XMLElement.new('p'))
332:       p << (tab = XMLElement.new('table', 'align' => 'center',
333:                                  'class' => 'table'))
334:       tab << (colgroup = XMLElement.new('colgroup'))
335:       colgroup << XMLElement.new('col', 'width' => '15%')
336:       colgroup << XMLElement.new('col', 'width' => '85%')
337: 
338:       tab << (tr = XMLElement.new('tr', 'align' => 'left'))
339:       tr << XMLNamedText.new('Purpose', 'td', 'class' => 'tag')
340:       tr << (td = XMLElement.new('td', 'class' => 'descr'))
341:       td << newRichText(@pattern.doc).to_html
342:       if @syntax != '[{ <attributes> }]'
343:         tab << (tr = XMLElement.new('tr', 'align' => 'left'))
344:         tr << XMLNamedText.new('Syntax', 'td', 'class' => 'tag')
345:         tr << (td = XMLElement.new('td', 'class' => 'descr'))
346:         td << XMLNamedText.new("#{@syntax}", 'code')
347: 
348:         tab << (tr = XMLElement.new('tr', 'align' => 'left'))
349:         tr << XMLNamedText.new('Arguments', 'td', 'class' => 'tag')
350:         if @args.empty?
351:           tr << XMLNamedText.new('none', 'td', 'class' => 'descr')
352:         else
353:           tr << (td = XMLElement.new('td', 'class' => 'descr'))
354:           td << (tab1 = XMLElement.new('table', 'width' => '100%'))
355:           @args.each do |arg|
356:             tab1 << (tr1 = XMLElement.new('tr'))
357:             if arg.typeSpec.nil? || ('<' + arg.name + '>') == arg.typeSpec
358:               tr1 << XMLNamedText.new("#{arg.name}", 'td', 'width' => '30%')
359:             else
360:               typeSpec = arg.typeSpec
361:               typeName = typeSpec[1..2]
362:               typeSpec[0] = '['
363:               typeSpec[1] = ']'
364:               tr1 << (td = XMLElement.new('td', 'width' => '30%'))
365:               td << XMLText.new("#{arg.name} [")
366:               td << XMLNamedText.new(
367:                 typeName, 'a', 'href' =>
368:                                "The_TaskJuggler_Syntax.html\##{typeName}")
369:               td << XMLText.new(']')
370:             end
371:             tr1 << (td = XMLElement.new('td',
372:               'style' => 'margin-top:2px; margin-bottom:2px;'))
373:             td << newRichText(arg.text ||
374:                               "See [[#{arg.name}]] for details.").to_html
375:           end
376:         end
377:       end
378: 
379:       tab << (tr = XMLElement.new('tr', 'align' => 'left'))
380:       tr << XMLNamedText.new('Context', 'td', 'class' => 'tag')
381:       if @contexts.empty?
382:         tr << (td = XMLElement.new('td', 'class' => 'descr'))
383:         td << XMLNamedText.new('Global scope', 'a',
384:           'href' => 'Getting_Started.html#Structure_of_a_TJP_File')
385:       else
386:         tr << (td = XMLElement.new('td', 'class' => 'descr'))
387:         first = true
388:         @contexts.each do |context|
389:           if first
390:             first = false
391:           else
392:             td << XMLText.new(', ')
393:           end
394:           keywordHTMLRef(td, context)
395:         end
396:       end
397: 
398:       unless @seeAlso.empty?
399:         tab << (tr = XMLElement.new('tr', 'align' => 'left'))
400:         tr << XMLNamedText.new('See also', 'td', 'class' => 'tag')
401:         first = true
402:         tr << (td = XMLElement.new('td', 'class' => 'descr'))
403:         @seeAlso.each do |also|
404:           if first
405:             first = false
406:           else
407:             td << XMLText.new(', ')
408:           end
409:           keywordHTMLRef(td, also)
410:         end
411:       end
412: 
413:       # Box with attributes.
414:       unless @optionalAttributes.empty?
415:         @optionalAttributes.sort! do |a, b|
416:           a.keyword <=> b.keyword
417:         end
418: 
419:         showDetails = false
420:         @optionalAttributes.each do |attr|
421:           if attr.scenarioSpecific || attr.inheritedFromProject ||
422:              attr.inheritedFromParent
423:             showDetails = true
424:             break
425:           end
426:         end
427: 
428:         bbox << (p = XMLElement.new('p'))
429:         p << (tab = XMLElement.new('table', 'align' => 'center',
430:                                    'class' => 'table'))
431:         tab << (tr = XMLElement.new('tr', 'align' => 'left'))
432:         if showDetails
433:           # Table of all attributes with checkmarks for being scenario
434:           # specific, inherited from parent and inherited from global scope.
435:           tr << XMLNamedText.new('Attributes', 'td', 'class' => 'tag',
436:                                  'rowspan' =>
437:                                  "#{@optionalAttributes.length + 1}",
438:                                  'style' => 'width:15%')
439:           tr << XMLNamedText.new('Name', 'td', 'class' => 'tag',
440:                                  'style' => 'width:40%')
441:           tr << XMLNamedText.new('Scen. spec.', 'td', 'class' => 'tag',
442:                                  'style' => 'width:15%')
443:           tr << XMLNamedText.new('Inh. fm. Global', 'td', 'class' => 'tag',
444:                                  'style' => 'width:15%')
445:           tr << XMLNamedText.new('Inh. fm. Parent', 'td', 'class' => 'tag',
446:                                  'style' => 'width:15%')
447:           @optionalAttributes.each do |attr|
448:             tab << (tr = XMLElement.new('tr', 'align' => 'left'))
449:             tr << (td = XMLElement.new('td', 'class' => 'descr'))
450:             keywordHTMLRef(td, attr)
451:             tr << (td = XMLElement.new('td', 'align' => 'center',
452:                                        'class' => 'descr'))
453:             td << XMLText.new('x') if attr.scenarioSpecific
454:             tr << (td = XMLElement.new('td', 'align' => 'center',
455:                                        'class' => 'descr'))
456:             td << XMLText.new('x') if attr.inheritedFromProject
457:             tr << (td = XMLElement.new('td', 'align' => 'center',
458:                                        'class' => 'descr'))
459:             td << XMLText.new('x') if attr.inheritedFromParent
460:           end
461:         else
462:           # Comma separated list of all attributes.
463:           tr << XMLNamedText.new('Attributes', 'td', 'class' => 'tag',
464:                                  'style' => 'width:15%')
465:           tr << (td = XMLElement.new('td', 'class' => 'descr',
466:                                      'style' => 'width:85%'))
467:           first = true
468:           @optionalAttributes.each do |attr|
469:             if first
470:               first = false
471:             else
472:               td << XMLText.new(', ')
473:             end
474:             keywordHTMLRef(td, attr)
475:           end
476:         end
477:       end
478: 
479:       if @pattern.exampleFile
480:         exampleDir = AppConfig.dataDirs('test')[0] + "TestSuite/Syntax/Correct/"
481:         example = TjpExample.new
482:         fileName = "#{exampleDir}/#{@pattern.exampleFile}.tjp"
483:         example.open(fileName)
484:         bbox << (frame = XMLElement.new('div', 'class' => 'codeframe'))
485:         frame << (pre = XMLElement.new('pre', 'class' => 'code'))
486:         unless (text = example.to_s(@pattern.exampleTag))
487:           raise "There is no tag '#{@pattern.exampleTag}' in file " +
488:             "#{fileName}."
489:         end
490:         pre << XMLText.new(text)
491:       end
492: 
493:       body << generateHTMLNavigationBar
494:       body << @manual.generateHTMLFooter
495: 
496:       if directory
497:         html.write(directory + "#{keyword}.html")
498:       else
499:         puts html.to_s
500:       end
501:     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 88
88:     def globalScope?
89:       return true if @contexts.empty?
90:       @contexts.each do |context|
91:         return true if context.keyword == 'properties'
92:       end
93:       false
94:     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 77
77:     def isProperty?
78:       # I haven't found a good way to automatically detect all the various
79:       # report types as properties. The non-nestable ones need to be added
80:       # manually here.
81:       return true if %( export nikureport timesheetreport statussheetreport).
82:                      include?(keyword)
83:       @optionalAttributes.include?(self)
84:     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 175
175:     def title
176:       kwTokens = @keyword.split('.')
177:       if kwTokens.size == 1
178:         title = @keyword
179:       else
180:         title = "#{kwTokens[0]} (#{kwTokens[1]})"
181:       end
182:       title
183:     end
to_s() click to toggle source

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

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

Private Instance Methods

checkReference(pattern) click to toggle source
     # File lib/KeywordDocumentation.rb, line 505
505:     def checkReference(pattern)
506:       if pattern.keyword.nil?
507:         $stderr.puts "Pattern #{pattern} is undocumented but referenced by " +
508:                      "#{@keyword}."
509:         false
510:       end
511:       true
512:     end
format(indent, str, width) click to toggle source
     # File lib/KeywordDocumentation.rb, line 566
566:     def format(indent, str, width)
567:       TextFormatter.new(width, indent).format(str)[indent..1]
568:     end
generateHTMLNavigationBar() click to toggle source

Generate the navigation bar.

     # File lib/KeywordDocumentation.rb, line 519
519:     def generateHTMLNavigationBar
520:       @manual.generateHTMLNavigationBar(
521:         @predecessor ? @predecessor.title : nil,
522:         @predecessor ? "#{@predecessor.keyword}.html" : nil,
523:         @successor ? @successor.title : nil,
524:         @successor ? "#{@successor.keyword}.html" : nil)
525:     end
indent(width, str) click to toggle source
     # File lib/KeywordDocumentation.rb, line 514
514:     def indent(width, str)
515:       TextFormatter.new(80, width).indent(str)[width..1]
516:     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 528
528:     def keywordHTMLRef(parent, keyword)
529:       parent << XMLNamedText.new(keyword.title,
530:                                  'a', 'href' => "#{keyword.keyword}.html")
531:     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 550
550:     def listHTMLAttributes(list, width)
551:       td = XMLElement.new('td', 'class' => 'descr',
552:                           'style' => "width:#{width}%")
553:       first = true
554:       list.each do |attr|
555:         if first
556:           first = false
557:         else
558:           td << XMLText.new(', ')
559:         end
560:         keywordHTMLRef(td, attr)
561:       end
562: 
563:       td
564:     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 536
536:     def newRichText(text)
537:       rText = RichText.new(text, [], @messageHandler)
538:       unless (rti = rText.generateIntermediateFormat)
539:         @messageHandler.error('rich_text',
540:                               "Error in RichText of rule #{@keyword}")
541:       end
542:       @references += rti.internalReferences
543:       rti
544:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.