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.
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/taskjuggler/KeywordDocumentation.rb, line 46 46: def initialize(rule, pattern, syntax, args, optAttrPatterns, manual) 47: @messageHandler = MessageHandler.new(true) 48: @rule = rule 49: @pattern = pattern 50: # The unique identifier. Usually the attribute or property name. To 51: # disambiguate a .<scope> can be added. 52: @keyword = pattern.keyword 53: # Similar to @keyword, but without the scope. Since there could be 54: # several, this is an Array of String objects. 55: @names = [] 56: @syntax = syntax 57: @args = args 58: @manual = manual 59: # Hash that maps patterns of optional attributes to a boolean value. It 60: # is true if the pattern is a scenario specific attribute. 61: @optAttrPatterns = optAttrPatterns 62: # The above hash is later converted into a list that points to the 63: # keyword documentation of the optional attribute. 64: @optionalAttributes = [] 65: @scenarioSpecific = false 66: @inheritedFromProject= false 67: @inheritedFromParent = false 68: @contexts = [] 69: @seeAlso = [] 70: # The following are references to the neighboring keyword in an 71: # alphabetically sorted list. 72: @predecessor = nil 73: @successor = nil 74: # Array to collect all references to other RichText objects. 75: @references = [] 76: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 145 145: def computeInheritance(keywords, rules) 146: property = nil 147: @contexts.each do |kwd| 148: if %( task resource account report shift scenario).include?(kwd.keyword) 149: property = kwd.keyword 150: break 151: end 152: end 153: if property 154: project = Project.new('id', 'dummy', '1.0', nil) 155: propertySet = case property 156: when 'task' 157: project.tasks 158: when 'resource' 159: project.resources 160: when 'account' 161: project.accounts 162: when 'report' 163: project.reports 164: when 'shift' 165: project.shifts 166: when 'scenario' 167: project.scenarios 168: end 169: keyword = @keyword 170: keyword = keyword.split('.')[0] if keyword.include?('.') 171: @inheritedFromProject = propertySet.inheritedFromProject?(keyword) 172: @inheritedFromParent = propertySet.inheritedFromParent?(keyword) 173: end 174: end
Post process the class member to set cross references to other KeywordDocumentation items.
# File lib/taskjuggler/KeywordDocumentation.rb, line 101 101: def crossReference(keywords, rules) 102: # Get the attribute or property name of the Keyword. This is not unique 103: # like @keyword since it's got no scope. 104: @pattern.terminalTokens(rules).each do |tok| 105: # Ignore patterns that don't have a real name. 106: break if tok[0] == '{' 107: 108: @names << tok[0] 109: end 110: 111: # Some arguments are references to other patterns. The current keyword 112: # is added as context to such patterns. 113: @args.each do |arg| 114: if arg.pattern && checkReference(arg.pattern) 115: kwd = keywords[arg.pattern.keyword] 116: kwd.contexts << self unless kwd.contexts.include?(self) 117: end 118: end 119: 120: # Optional attributes are treated similarly. In addition we add them to 121: # the @optionalAttributes list of this keyword. 122: @optAttrPatterns.each do |pattern, scenarioSpecific| 123: next unless checkReference(pattern) 124: 125: if (kwd = keywords[pattern.keyword]).nil? 126: token = pattern.terminalToken(rules) 127: $stderr.puts "Keyword #{keyword} has undocumented optional " + 128: "attribute #{token[0]}" 129: else 130: @optionalAttributes << kwd 131: kwd.contexts << self unless kwd.contexts.include?(self) 132: kwd.scenarioSpecific = true if scenarioSpecific 133: end 134: end 135: 136: # Resolve the seeAlso patterns to keyword references. 137: @pattern.seeAlso.sort.each do |also| 138: if keywords[also].nil? 139: raise "See also reference #{also} of #{@pattern} is unknown" 140: end 141: @seeAlso << keywords[also] 142: end 143: end
Return a String that represents the keyword documentation in an XML formatted form.
# File lib/taskjuggler/KeywordDocumentation.rb, line 309 309: def generateHTML(directory) 310: html = HTMLDocument.new(:strict) 311: head = html.generateHead(keyword, 312: { 'description' => 'The TaskJuggler Manual', 313: 'keywords' => 314: 'taskjuggler, project, management' }) 315: head << @manual.generateStyleSheet 316: 317: html.html << BODY.new do 318: [ 319: @manual.generateHTMLHeader, 320: generateHTMLNavigationBar, 321: 322: DIV.new('style' => 'margin-left:5%; margin-right:5%') do 323: [ 324: generateHTMLKeywordBox, 325: generateHTMLDescriptionBox, 326: generateHTMLOptionalAttributesBox, 327: generateHTMLExampleBox 328: ] 329: end, 330: generateHTMLNavigationBar, 331: @manual.generateHTMLFooter 332: ] 333: end 334: 335: if directory 336: html.write(directory + "#{keyword}.html") 337: else 338: puts html.to_s 339: end 340: end
Returns true of the keyword can be used outside of any other keyword context.
# File lib/taskjuggler/KeywordDocumentation.rb, line 91 91: def globalScope? 92: return true if @contexts.empty? 93: @contexts.each do |context| 94: return true if context.keyword == 'properties' 95: end 96: false 97: end
Returns true of the KeywordDocumentation is documenting a TJP property (task, resources, etc.). A TJP property can be nested.
# File lib/taskjuggler/KeywordDocumentation.rb, line 80 80: def isProperty? 81: # I haven't found a good way to automatically detect all the various 82: # report types as properties. The non-nestable ones need to be added 83: # manually here. 84: return true if %( export nikureport timesheetreport statussheetreport). 85: include?(keyword) 86: @optionalAttributes.include?(self) 87: end
Return the keyword name in a more readable form. E.g. ‘foo.bar’ is returned as ‘foo (bar)’. ‘foo’ will remain ‘foo’.
# File lib/taskjuggler/KeywordDocumentation.rb, line 178 178: def title 179: kwTokens = @keyword.split('.') 180: if kwTokens.size == 1 181: title = @keyword 182: else 183: title = "#{kwTokens[0]} (#{kwTokens[1]})" 184: end 185: title 186: end
Return the complete documentation of this keyword as formatted text string.
# File lib/taskjuggler/KeywordDocumentation.rb, line 190 190: def to_s 191: tagW = 13 192: textW = 79 193: 194: # Top line with multiple elements 195: str = "Keyword: #{@keyword} " + 196: "Scenario Specific: #{@scenarioSpecific ? 'Yes' : 'No'} " + 197: "Inherited: #{@inheritedFromParent ? 'Yes' : 'No'}\n\n" 198: 199: str += "Purpose: #{format(tagW, newRichText(@pattern.doc).to_s, 200: textW)}\n\n" 201: 202: if @syntax != '[{ <attributes> }]' 203: str += "Syntax: #{format(tagW, @syntax, textW)}\n\n" 204: 205: str += "Arguments: " 206: if @args.empty? 207: str += format(tagW, "none\n\n", textW) 208: else 209: argStr = '' 210: @args.each do |arg| 211: argText = newRichText(arg.text || 212: "See '#{arg.name}' for details.").to_s 213: if arg.typeSpec.nil? || ("<#{arg.name}>") == arg.typeSpec 214: indent = arg.name.length + 2 215: argStr += "#{arg.name}: " + 216: "#{format(indent, argText, textW - tagW)}\n" 217: else 218: typeSpec = arg.typeSpec 219: typeSpec[0] = '[' 220: typeSpec[1] = ']' 221: indent = arg.name.length + typeSpec.size + 3 222: argStr += "#{arg.name} #{typeSpec}: " + 223: "#{format(indent, argText, textW - tagW)}\n" 224: end 225: end 226: str += indent(tagW, argStr) 227: end 228: str += "\n" 229: end 230: 231: str += 'Context: ' 232: if @contexts.empty? 233: str += format(tagW, 'Global scope', textW) 234: else 235: cxtStr = '' 236: @contexts.each do |context| 237: unless cxtStr.empty? 238: cxtStr += ', ' 239: end 240: cxtStr += context.keyword 241: end 242: str += format(tagW, cxtStr, textW) 243: end 244: 245: str += "\n\nAttributes: " 246: if @optionalAttributes.empty? 247: str += "none\n\n" 248: else 249: attrStr = '' 250: @optionalAttributes.sort! do |a, b| 251: a.keyword <=> b.keyword 252: end 253: showLegend = false 254: @optionalAttributes.each do |attr| 255: unless attrStr.empty? 256: attrStr += ', ' 257: end 258: attrStr += attr.keyword 259: if attr.scenarioSpecific || attr.inheritedFromProject || 260: attr.inheritedFromParent 261: first = true 262: showLegend = true 263: attrStr += '[' 264: if attr.scenarioSpecific 265: attrStr += 'sc' 266: first = false 267: end 268: if attr.inheritedFromProject 269: attrStr += ':' unless first 270: attrStr += 'ig' 271: first = false 272: end 273: if attr.inheritedFromParent 274: attrStr += ':' unless first 275: attrStr += 'ip' 276: end 277: attrStr += ']' 278: end 279: end 280: if showLegend 281: attrStr += "\n\n[sc] : Attribute is scenario specific" + 282: "\n[ig] : Attribute is inherited from global attribute" + 283: "\n[ip] : Attribute is inherited from parent property" 284: end 285: str += format(tagW, attrStr, textW) 286: str += "\n" 287: end 288: 289: unless @seeAlso.empty? 290: str += "See also: " 291: alsoStr = '' 292: @seeAlso.each do |also| 293: unless alsoStr.empty? 294: alsoStr += ', ' 295: end 296: alsoStr += also.keyword 297: end 298: str += format(tagW, alsoStr, textW) 299: str += "\n" 300: end 301: 302: # str += "Rule: #{@rule.name}\n" if @rule 303: # str += "Pattern: #{@pattern.tokens.join(' ')}\n" if @pattern 304: str 305: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 344 344: def checkReference(pattern) 345: if pattern.keyword.nil? 346: $stderr.puts "Pattern #{pattern} is undocumented but referenced by " + 347: "#{@keyword}." 348: false 349: end 350: true 351: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 405 405: def format(indent, str, width) 406: TextFormatter.new(width, indent).format(str)[indent..1] 407: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 512 512: def generateHTMLAlsoLine 513: unless @seeAlso.empty? 514: descr = [] 515: @seeAlso.each do |a| 516: descr << ', ' unless descr.empty? 517: descr << A.new('href' => "#{a.keyword}.html") { a.title } 518: end 519: generateHTMLTableLine('See also', descr) 520: end 521: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 457 457: def generateHTMLArgumentsLine 458: return nil unless @syntax != '[{ <attributes> }]' 459: 460: if @args.empty? 461: generateHTMLTableLine('Arguments', 'none') 462: else 463: rows = [] 464: first = true 465: @args.each do |arg| 466: if first 467: col1 = 'Arguments' 468: col1rows = @args.length 469: first = false 470: else 471: col1 = col1rows = nil 472: end 473: if arg.typeSpec.nil? || ('<' + arg.name + '>') == arg.typeSpec 474: col2 = "#{arg.name}" 475: else 476: typeSpec = arg.typeSpec 477: typeName = typeSpec[1..2] 478: typeSpec[0] = '[' 479: typeSpec[1] = ']' 480: col2 = [ 481: "#{arg.name} [", 482: A.new('href' => 483: "The_TaskJuggler_Syntax.html" + 484: "\##{typeName}") { typeName }, 485: ']' 486: ] 487: end 488: col3 = newRichText(arg.text || 489: "See [[#{arg.name}]] for details.").to_html 490: rows << generateHTMLTableLine(col1, col2, col3, col1rows) 491: end 492: rows 493: end 494: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 496 496: def generateHTMLContextLine 497: if @contexts.empty? 498: descr = A.new('href' => 499: 'Getting_Started.html#Structure_of_a_TJP_File') do 500: 'Global scope' 501: end 502: else 503: descr = [] 504: @contexts.each do |c| 505: descr << ', ' unless descr.empty? 506: descr << A.new('href' => "#{c.keyword}.html") { c.title } 507: end 508: end 509: generateHTMLTableLine('Context', descr) 510: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 425 425: def generateHTMLDescriptionBox 426: # Box with purpose, syntax, arguments and context. 427: P.new do 428: TABLE.new({ 'align' => 'center', 'class' => 'table' }) do 429: [ 430: COLGROUP.new do 431: [ 432: COL.new('width' => '16%'), 433: COL.new('width' => '24%'), 434: COL.new('width' => '60%') 435: ] 436: end, 437: generateHTMLPurposeLine, 438: generateHTMLSyntaxLine, 439: generateHTMLArgumentsLine, 440: generateHTMLContextLine, 441: generateHTMLAlsoLine 442: ] 443: end 444: end 445: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 620 620: def generateHTMLExampleBox 621: if @pattern.exampleFile 622: exampleDir = AppConfig.dataDirs('test')[0] + "TestSuite/Syntax/Correct/" 623: example = TjpExample.new 624: fileName = "#{exampleDir}/#{@pattern.exampleFile}.tjp" 625: example.open(fileName) 626: unless (text = example.to_s(@pattern.exampleTag)) 627: raise "There is no tag '#{@pattern.exampleTag}' in file " + 628: "#{fileName}." 629: end 630: 631: DIV.new('class' => 'codeframe') do 632: PRE.new('class' => 'code') { text } 633: end 634: end 635: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 409 409: def generateHTMLKeywordBox 410: # Box with keyword name. 411: P.new do 412: TABLE.new('align' => 'center', 'class' => 'table') do 413: TR.new('align' => 'left') do 414: [ 415: TD.new({ 'class' => 'tag', 416: 'style' => 'width:16%'}) { 'Keyword' }, 417: TD.new({ 'class' => 'descr', 418: 'style' => 'width:84%; font-weight:bold' }) { title } 419: ] 420: end 421: end 422: end 423: end
Generate the navigation bar.
# File lib/taskjuggler/KeywordDocumentation.rb, line 358 358: def generateHTMLNavigationBar 359: @manual.generateHTMLNavigationBar( 360: @predecessor ? @predecessor.title : nil, 361: @predecessor ? "#{@predecessor.keyword}.html" : nil, 362: @successor ? @successor.title : nil, 363: @successor ? "#{@successor.keyword}.html" : nil) 364: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 537 537: def generateHTMLOptionalAttributesBox 538: # Box with attributes. 539: unless @optionalAttributes.empty? 540: @optionalAttributes.sort! do |a, b| 541: a.keyword <=> b.keyword 542: end 543: 544: showDetails = false 545: @optionalAttributes.each do |attr| 546: if attr.scenarioSpecific || attr.inheritedFromProject || 547: attr.inheritedFromParent 548: showDetails = true 549: break 550: end 551: end 552: 553: P.new do 554: TABLE.new('align' => 'center', 'class' => 'table') do 555: if showDetails 556: # Table of all attributes with checkmarks for being scenario 557: # specific, inherited from parent and inherited from global 558: # scope. 559: rows = [] 560: rows << COLGROUP.new do 561: [ 16, 24, 20, 20, 20 ].map { |p| COL.new('width' => "#{p}%") } 562: end 563: rows << TR.new('align' => 'left') do 564: [ 565: TD.new('class' => 'tag', 566: 'rowspan' => "#{@optionalAttributes.length + 1}") do 567: 'Attributes' 568: end, 569: TD.new('class' => 'tag') { 'Name' }, 570: TD.new('class' => 'tag') { 'Scen. spec.' }, 571: TD.new('class' => 'tag') { 'Inh. fm. Global' }, 572: TD.new('class' => 'tag') { 'Inh. fm. Parent' } 573: ] 574: end 575: 576: @optionalAttributes.each do |attr| 577: rows << TR.new('align' => 'left') do 578: [ 579: TD.new('align' => 'left', 'class' => 'descr') do 580: A.new('href' => "#{attr.keyword}.html") { attr.title } 581: end, 582: TD.new('align' => 'center', 'class' => 'descr') do 583: 'x' if attr.scenarioSpecific 584: end, 585: TD.new('align' => 'center', 'class' => 'descr') do 586: 'x' if attr.inheritedFromProject 587: end, 588: TD.new('align' => 'center', 'class' => 'descr') do 589: 'x' if attr.inheritedFromParent 590: end 591: ] 592: end 593: end 594: rows 595: else 596: # Comma separated list of all attributes. 597: TR.new('align' => 'left') do 598: [ 599: TD.new('class' => 'tag', 'style' => 'width:16%') do 600: 'Attributes' 601: end, 602: TD.new('class' => 'descr', 'style' => 'width:84%') do 603: list = [] 604: @optionalAttributes.each do |attr| 605: list << ', ' unless list.empty? 606: list << A.new('href' => "#{attr.keyword}.html") do 607: attr.title 608: end 609: end 610: list 611: end 612: ] 613: end 614: end 615: end 616: end 617: end 618: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 447 447: def generateHTMLPurposeLine 448: generateHTMLTableLine('Purpose', newRichText(@pattern.doc).to_html) 449: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 451 451: def generateHTMLSyntaxLine 452: if @syntax != '[{ <attributes> }]' 453: generateHTMLTableLine('Syntax', CODE.new { @syntax }) 454: end 455: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 523 523: def generateHTMLTableLine(col1, col2, col3 = nil, col1rows = nil) 524: TR.new('align' => 'left') do 525: columns = [] 526: attrs = { 'class' => 'tag' } 527: attrs['rowspan'] = col1rows.to_s if col1rows 528: columns << TD.new(attrs) { col1 } if col1 529: attrs = { 'class' => 'descr' } 530: attrs['colspan'] = '2' unless col3 531: columns << TD.new(attrs) { col2 } 532: columns << TD.new('class' => 'descr') { col3 } if col3 533: columns 534: end 535: end
# File lib/taskjuggler/KeywordDocumentation.rb, line 353 353: def indent(width, str) 354: TextFormatter.new(80, width).indent(str)[width..1] 355: end
Return a HTML object with a link to the manual page for the keyword.
# File lib/taskjuggler/KeywordDocumentation.rb, line 367 367: def keywordHTMLRef(parent, keyword) 368: parent << XMLNamedText.new(keyword.title, 369: 'a', 'href' => "#{keyword.keyword}.html") 370: end
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/taskjuggler/KeywordDocumentation.rb, line 389 389: def listHTMLAttributes(list, width) 390: td = XMLElement.new('td', 'class' => 'descr', 391: 'style' => "width:#{width}%") 392: first = true 393: list.each do |attr| 394: if first 395: first = false 396: else 397: td << XMLText.new(', ') 398: end 399: keywordHTMLRef(td, attr) 400: end 401: 402: td 403: end
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/taskjuggler/KeywordDocumentation.rb, line 375 375: def newRichText(text) 376: rText = RichText.new(text, [], @messageHandler) 377: unless (rti = rText.generateIntermediateFormat) 378: @messageHandler.error('rich_text', 379: "Error in RichText of rule #{@keyword}") 380: end 381: @references += rti.internalReferences 382: rti 383: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.