Parent

Files

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]
pattern[R]
references[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:       @rule = rule
45:       @pattern = pattern
46:       @keyword = pattern.keyword
47:       @syntax = syntax
48:       @args = args
49:       # Hash that maps patterns of optional attributes to a boolean value. It
50:       # is true if the pattern is a scenario specific attribute.
51:       @optAttrPatterns = optAttrPatterns
52:       @manual = manual
53:       # The above hash is later converted into a list that points to the
54:       # keyword documentation of the optional attribute.
55:       @optionalAttributes = []
56:       @scenarioSpecific = false
57:       @inheritedFromProject= false
58:       @inheritedFromParent = false
59:       @contexts = []
60:       @seeAlso = []
61:       # The following are references to the neighboring keyword in an
62:       # alphabetically sorted list.
63:       @predecessor = nil
64:       @successor = nil
65:       # Array to collect all references to other RichText objects.
66:       @references = []
67:     end

Public Instance Methods

computeInheritance(keywords, rules) click to toggle source
     # File lib/KeywordDocumentation.rb, line 106
106:     def computeInheritance(keywords, rules)
107:       property = nil
108:       @contexts.each do |kwd|
109:         if %( task resource account report shift scenario).include?(kwd.keyword)
110:           property = kwd.keyword
111:           break
112:         end
113:       end
114:       if property
115:         project = Project.new('id', 'dummy', '1.0', nil)
116:         propertySet = case property
117:                       when 'task'
118:                         project.tasks
119:                       when 'resource'
120:                         project.resources
121:                       when 'account'
122:                         project.accounts
123:                       when 'report'
124:                         project.reports
125:                       when 'shift'
126:                         project.shifts
127:                       when 'scenario'
128:                         project.scenarios
129:                       end
130:         keyword = @keyword
131:         keyword = keyword.split('.')[0] if keyword.include?('.')
132:         @inheritedFromProject = propertySet.inheritedFromProject?(keyword)
133:         @inheritedFromParent = propertySet.inheritedFromParent?(keyword)
134:       end
135:     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 71
 71:     def crossReference(keywords, rules)
 72:       # Some arguments are references to other patterns. The current keyword
 73:       # is added as context to such patterns.
 74:       @args.each do |arg|
 75:         if arg.pattern && checkReference(arg.pattern)
 76:           kwd = keywords[arg.pattern.keyword]
 77:           kwd.contexts << self unless kwd.contexts.include?(self)
 78:         end
 79:       end
 80: 
 81:       # Optional attributes are treated similarly. In addition we add them to
 82:       # the @optionalAttributes list of this keyword.
 83:       @optAttrPatterns.each do |pattern, scenarioSpecific|
 84:         next unless checkReference(pattern)
 85: 
 86:         if (kwd = keywords[pattern.keyword]).nil?
 87:           token = pattern.terminalToken(rules)
 88:           $stderr.puts "Keyword #{keyword} has undocumented optional " +
 89:                        "attribute #{token[0]}"
 90:         else
 91:           @optionalAttributes << kwd
 92:           kwd.contexts << self unless kwd.contexts.include?(self)
 93:           kwd.scenarioSpecific = true if scenarioSpecific
 94:         end
 95:       end
 96: 
 97:       # Resolve the seeAlso patterns to keyword references.
 98:       @pattern.seeAlso.sort.each do |also|
 99:         if keywords[also].nil?
100:           raise "See also reference #{also} of #{@pattern} is unknown"
101:         end
102:         @seeAlso << keywords[also]
103:       end
104:     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 269
269:     def generateHTML(directory)
270:       html = HTMLDocument.new(:strict)
271:       head = html.generateHead(keyword,
272:                                { 'description' => 'The TaskJuggler Manual',
273:                                  'keywords' =>
274:                                  'taskjuggler, project, management' })
275:       head << @manual.generateStyleSheet
276: 
277:       html << (body = XMLElement.new('body'))
278:       body << @manual.generateHTMLHeader <<
279:         generateHTMLNavigationBar
280: 
281:       # Box with keyword name.
282:       body << (bbox = XMLElement.new('div',
283:         'style' => 'margin-left:5%; margin-right:5%'))
284:       bbox << (p = XMLElement.new('p'))
285:       p << (tab = XMLElement.new('table', 'align' => 'center',
286:                                  'class' => 'table'))
287: 
288:       tab << (tr = XMLElement.new('tr', 'align' => 'left'))
289:       tr << XMLNamedText.new('Keyword', 'td', 'class' => 'tag',
290:                             'style' => 'width:15%')
291:       tr << XMLNamedText.new(title, 'td', 'class' => 'descr',
292:                              'style' => 'width:85%; font-weight:bold')
293: 
294:       # Box with purpose, syntax, arguments and context.
295:       bbox << (p = XMLElement.new('p'))
296:       p << (tab = XMLElement.new('table', 'align' => 'center',
297:                                  'class' => 'table'))
298:       tab << (colgroup = XMLElement.new('colgroup'))
299:       colgroup << XMLElement.new('col', 'width' => '15%')
300:       colgroup << XMLElement.new('col', 'width' => '85%')
301: 
302:       tab << (tr = XMLElement.new('tr', 'align' => 'left'))
303:       tr << XMLNamedText.new('Purpose', 'td', 'class' => 'tag')
304:       tr << (td = XMLElement.new('td', 'class' => 'descr'))
305:       td << newRichText(@pattern.doc).to_html
306:       if @syntax != '[{ <attributes> }]'
307:         tab << (tr = XMLElement.new('tr', 'align' => 'left'))
308:         tr << XMLNamedText.new('Syntax', 'td', 'class' => 'tag')
309:         tr << (td = XMLElement.new('td', 'class' => 'descr'))
310:         td << XMLNamedText.new("#{@syntax}", 'code')
311: 
312:         tab << (tr = XMLElement.new('tr', 'align' => 'left'))
313:         tr << XMLNamedText.new('Arguments', 'td', 'class' => 'tag')
314:         if @args.empty?
315:           tr << XMLNamedText.new('none', 'td', 'class' => 'descr')
316:         else
317:           tr << (td = XMLElement.new('td', 'class' => 'descr'))
318:           td << (tab1 = XMLElement.new('table', 'width' => '100%'))
319:           @args.each do |arg|
320:             tab1 << (tr1 = XMLElement.new('tr'))
321:             if arg.typeSpec.nil? || ('<' + arg.name + '>') == arg.typeSpec
322:               tr1 << XMLNamedText.new("#{arg.name}", 'td', 'width' => '30%')
323:             else
324:               typeSpec = arg.typeSpec
325:               typeName = typeSpec[1..2]
326:               typeSpec[0] = '['
327:               typeSpec[1] = ']'
328:               tr1 << (td = XMLElement.new('td', 'width' => '30%'))
329:               td << XMLText.new("#{arg.name} [")
330:               td << XMLNamedText.new(
331:                 typeName, 'a', 'href' =>
332:                                "The_TaskJuggler_Syntax.html\##{typeName}")
333:               td << XMLText.new(']')
334:             end
335:             tr1 << (td = XMLElement.new('td',
336:               'style' => 'margin-top:2px; margin-bottom:2px;'))
337:             td << newRichText(arg.text ||
338:                               "See [[#{arg.name}]] for details.").to_html
339:           end
340:         end
341:       end
342: 
343:       tab << (tr = XMLElement.new('tr', 'align' => 'left'))
344:       tr << XMLNamedText.new('Context', 'td', 'class' => 'tag')
345:       if @contexts.empty?
346:         tr << (td = XMLElement.new('td', 'class' => 'descr'))
347:         td << XMLNamedText.new('Global scope', 'a',
348:           'href' => 'Getting_Started.html#Structure_of_a_TJP_File')
349:       else
350:         tr << (td = XMLElement.new('td', 'class' => 'descr'))
351:         first = true
352:         @contexts.each do |context|
353:           if first
354:             first = false
355:           else
356:             td << XMLText.new(', ')
357:           end
358:           keywordHTMLRef(td, context)
359:         end
360:       end
361: 
362:       unless @seeAlso.empty?
363:         tab << (tr = XMLElement.new('tr', 'align' => 'left'))
364:         tr << XMLNamedText.new('See also', 'td', 'class' => 'tag')
365:         first = true
366:         tr << (td = XMLElement.new('td', 'class' => 'descr'))
367:         @seeAlso.each do |also|
368:           if first
369:             first = false
370:           else
371:             td << XMLText.new(', ')
372:           end
373:           keywordHTMLRef(td, also)
374:         end
375:       end
376: 
377:       # Box with attributes.
378:       unless @optionalAttributes.empty?
379:         @optionalAttributes.sort! do |a, b|
380:           a.keyword <=> b.keyword
381:         end
382: 
383:         showDetails = false
384:         @optionalAttributes.each do |attr|
385:           if attr.scenarioSpecific || attr.inheritedFromProject ||
386:              attr.inheritedFromParent
387:             showDetails = true
388:             break
389:           end
390:         end
391: 
392:         bbox << (p = XMLElement.new('p'))
393:         p << (tab = XMLElement.new('table', 'align' => 'center',
394:                                    'class' => 'table'))
395:         tab << (tr = XMLElement.new('tr', 'align' => 'left'))
396:         if showDetails
397:           # Table of all attributes with checkmarks for being scenario
398:           # specific, inherited from parent and inherited from global scope.
399:           tr << XMLNamedText.new('Attributes', 'td', 'class' => 'tag',
400:                                  'rowspan' =>
401:                                  "#{@optionalAttributes.length + 1}",
402:                                  'style' => 'width:15%')
403:           tr << XMLNamedText.new('Name', 'td', 'class' => 'tag',
404:                                  'style' => 'width:40%')
405:           tr << XMLNamedText.new('Scen. spec.', 'td', 'class' => 'tag',
406:                                  'style' => 'width:15%')
407:           tr << XMLNamedText.new('Inh. fm. Global', 'td', 'class' => 'tag',
408:                                  'style' => 'width:15%')
409:           tr << XMLNamedText.new('Inh. fm. Parent', 'td', 'class' => 'tag',
410:                                  'style' => 'width:15%')
411:           @optionalAttributes.each do |attr|
412:             tab << (tr = XMLElement.new('tr', 'align' => 'left'))
413:             tr << (td = XMLElement.new('td', 'class' => 'descr'))
414:             keywordHTMLRef(td, attr)
415:             tr << (td = XMLElement.new('td', 'align' => 'center',
416:                                        'class' => 'descr'))
417:             td << XMLText.new('x') if attr.scenarioSpecific
418:             tr << (td = XMLElement.new('td', 'align' => 'center',
419:                                        'class' => 'descr'))
420:             td << XMLText.new('x') if attr.inheritedFromProject
421:             tr << (td = XMLElement.new('td', 'align' => 'center',
422:                                        'class' => 'descr'))
423:             td << XMLText.new('x') if attr.inheritedFromParent
424:           end
425:         else
426:           # Comma separated list of all attributes.
427:           tr << XMLNamedText.new('Attributes', 'td', 'class' => 'tag',
428:                                  'style' => 'width:15%')
429:           tr << (td = XMLElement.new('td', 'class' => 'descr',
430:                                      'style' => 'width:85%'))
431:           first = true
432:           @optionalAttributes.each do |attr|
433:             if first
434:               first = false
435:             else
436:               td << XMLText.new(', ')
437:             end
438:             keywordHTMLRef(td, attr)
439:           end
440:         end
441:       end
442: 
443:       if @pattern.exampleFile
444:         exampleDir = AppConfig.dataDirs('test')[0] + "TestSuite/Syntax/Correct/"
445:         example = TjpExample.new
446:         fileName = "#{exampleDir}/#{@pattern.exampleFile}.tjp"
447:         example.open(fileName)
448:         bbox << (frame = XMLElement.new('div', 'class' => 'codeframe'))
449:         frame << (pre = XMLElement.new('pre', 'class' => 'code'))
450:         unless (text = example.to_s(@pattern.exampleTag))
451:           raise "There is no tag '#{@pattern.exampleTag}' in file " +
452:             "#{fileName}."
453:         end
454:         pre << XMLText.new(text)
455:       end
456: 
457:       body << generateHTMLNavigationBar
458:       body << @manual.generateHTMLFooter
459: 
460:       if directory
461:         html.write(directory + "#{keyword}.html")
462:       else
463:         puts html.to_s
464:       end
465:     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 139
139:     def title
140:       kwTokens = @keyword.split('.')
141:       if kwTokens.size == 1
142:         title = @keyword
143:       else
144:         title = "#{kwTokens[0]} (#{kwTokens[1]})"
145:       end
146:       title
147:     end
to_s() click to toggle source

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

     # File lib/KeywordDocumentation.rb, line 151
151:     def to_s
152:       tagW = 13
153:       textW = 79
154: 
155:       # Top line with multiple elements
156:       str = "Keyword:     #{@keyword}     " +
157:             "Scenario Specific: #{@scenarioSpecific ? 'Yes' : 'No'}    " +
158:             "Inherited: #{@inheritedFromParent ? 'Yes' : 'No'}\n\n"
159: 
160:       str += "Purpose:     #{format(tagW, newRichText(@pattern.doc).to_s,
161:                                     textW)}\n\n"
162: 
163:       if @syntax != '[{ <attributes> }]'
164:         str += "Syntax:      #{format(tagW, @syntax, textW)}\n\n"
165: 
166:         str += "Arguments:   "
167:         if @args.empty?
168:           str += format(tagW, "none\n\n", textW)
169:         else
170:           argStr = ''
171:           @args.each do |arg|
172:             argText = arg.text || "See #{arg.name} for details."
173:             if arg.typeSpec.nil? || ('<' + arg.name + '>') == arg.typeSpec
174:               indent = arg.name.length + 2
175:               argStr += "#{arg.name}: " +
176:                       "#{format(indent, argText, textW - tagW)}\n"
177:             else
178:               typeSpec = arg.typeSpec
179:               typeSpec[0] = '['
180:               typeSpec[1] = ']'
181:               indent = arg.name.length + typeSpec.size + 3
182:               argStr += "#{arg.name} #{typeSpec}: " +
183:                       "#{format(indent, argText, textW - tagW)}\n"
184:             end
185:           end
186:           str += indent(tagW, argStr)
187:         end
188:         str += "\n"
189:       end
190: 
191:       str += 'Context:     '
192:       if @contexts.empty?
193:         str += format(tagW, 'Global scope', textW)
194:       else
195:         cxtStr = ''
196:         @contexts.each do |context|
197:           unless cxtStr.empty?
198:             cxtStr += ', '
199:           end
200:           cxtStr += context.keyword
201:         end
202:         str += format(tagW, cxtStr, textW)
203:       end
204: 
205:       str += "\n\nAttributes:  "
206:       if @optionalAttributes.empty?
207:         str += "none\n\n"
208:       else
209:         attrStr = ''
210:         @optionalAttributes.sort! do |a, b|
211:           a.keyword <=> b.keyword
212:         end
213:         showLegend = false
214:         @optionalAttributes.each do |attr|
215:           unless attrStr.empty?
216:             attrStr += ', '
217:           end
218:           attrStr += attr.keyword
219:           if attr.scenarioSpecific || attr.inheritedFromProject ||
220:              attr.inheritedFromParent
221:             first = true
222:             showLegend = true
223:             attrStr += '['
224:             if attr.scenarioSpecific
225:               attrStr += 'sc'
226:               first = false
227:             end
228:             if attr.inheritedFromProject
229:               attrStr += ':' unless first
230:               attrStr += 'ig'
231:               first = false
232:             end
233:             if attr.inheritedFromParent
234:               attrStr += ':' unless first
235:               attrStr += 'ip'
236:             end
237:             attrStr += ']'
238:           end
239:         end
240:         if showLegend
241:           attrStr += "\n\n[sc] : Attribute is scenario specific" +
242:                      "\n[ig] : Attribute is inherited from global attribute" +
243:                      "\n[ip] : Attribute is inherited from parent property"
244:         end
245:         str += format(tagW, attrStr, textW)
246:         str += "\n"
247:       end
248: 
249:       unless @seeAlso.empty?
250:         str += "See also:    "
251:         alsoStr = ''
252:         @seeAlso.each do |also|
253:           unless alsoStr.empty?
254:             alsoStr += ', '
255:           end
256:           alsoStr += also.keyword
257:         end
258:         str += format(tagW, alsoStr, textW)
259:         str += "\n"
260:       end
261: 
262:   #    str += "Rule:    #{@rule.name}\n" if @rule
263:   #    str += "Pattern: #{@pattern.tokens.join(' ')}\n" if @pattern
264:       str
265:     end

Private Instance Methods

checkReference(pattern) click to toggle source
     # File lib/KeywordDocumentation.rb, line 469
469:     def checkReference(pattern)
470:       if pattern.keyword.nil?
471:         $stderr.puts "Pattern #{pattern} is undocumented but referenced by " +
472:                      "#{@keyword}."
473:         false
474:       end
475:       true
476:     end
format(indent, str, width) click to toggle source
     # File lib/KeywordDocumentation.rb, line 532
532:     def format(indent, str, width)
533:       TextFormatter.new(width, indent).format(str)[indent..1]
534:     end
generateHTMLNavigationBar() click to toggle source

Generate the navigation bar.

     # File lib/KeywordDocumentation.rb, line 483
483:     def generateHTMLNavigationBar
484:       @manual.generateHTMLNavigationBar(
485:         @predecessor ? @predecessor.title : nil,
486:         @predecessor ? "#{@predecessor.keyword}.html" : nil,
487:         @successor ? @successor.title : nil,
488:         @successor ? "#{@successor.keyword}.html" : nil)
489:     end
indent(width, str) click to toggle source
     # File lib/KeywordDocumentation.rb, line 478
478:     def indent(width, str)
479:       TextFormatter.new(0, width).indent(str)[width..1]
480:     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 492
492:     def keywordHTMLRef(parent, keyword)
493:       parent << XMLNamedText.new(keyword.title,
494:                                  'a', 'href' => "#{keyword.keyword}.html")
495:     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 517
517:     def listHTMLAttributes(list, width)
518:       td = XMLElement.new('td', 'class' => 'descr', 'style' => "width:#{width}%")
519:       first = true
520:       list.each do |attr|
521:         if first
522:           first = false
523:         else
524:           td << XMLText.new(', ')
525:         end
526:         keywordHTMLRef(td, attr)
527:       end
528: 
529:       td
530:     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 500
500:     def newRichText(text)
501:       begin
502:         rText = RichText.new(text)
503:         rti = rText.generateIntermediateFormat
504:         @references += rti.internalReferences
505:       rescue RichTextException => msg
506:         $stderr.puts "Error in RichText of rule #{@keyword}\n" +
507:                      "Line #{msg.lineNo}: #{msg.text}\n" +
508:                      "#{msg.line}"
509:       end
510:       rti
511:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.