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/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
# 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
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
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
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
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
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
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
# 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
# File lib/KeywordDocumentation.rb, line 570 570: def format(indent, str, width) 571: TextFormatter.new(width, indent).format(str)[indent..1] 572: end
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
# File lib/KeywordDocumentation.rb, line 518 518: def indent(width, str) 519: TextFormatter.new(80, width).indent(str)[width..1] 520: end
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
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
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.
Generated with the Darkfish Rdoc Generator 1.1.6.