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: @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
# 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
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
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
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
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
# 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
# File lib/KeywordDocumentation.rb, line 532 532: def format(indent, str, width) 533: TextFormatter.new(width, indent).format(str)[indent..1] 534: end
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
# File lib/KeywordDocumentation.rb, line 478 478: def indent(width, str) 479: TextFormatter.new(0, width).indent(str)[width..1] 480: end
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
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
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.
Generated with the Darkfish Rdoc Generator 1.1.6.