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 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
# 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
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
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
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
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
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
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
# 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
# File lib/KeywordDocumentation.rb, line 566 566: def format(indent, str, width) 567: TextFormatter.new(width, indent).format(str)[indent..1] 568: end
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
# File lib/KeywordDocumentation.rb, line 514 514: def indent(width, str) 515: TextFormatter.new(80, width).indent(str)[width..1] 516: end
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
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
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.
Generated with the Darkfish Rdoc Generator 1.1.6.