Class: TaskJuggler::KeywordDocumentation

Inherits:
Object
  • Object
show all
Includes:
HTMLElements, MessageHandler, Term::ANSIColor
Defined in:
lib/taskjuggler/KeywordDocumentation.rb

Overview

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.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from MessageHandler

#critical, #debug, #error, #fatal, #info, #warning

Constructor Details

#initialize(rule, pattern, syntax, args, optAttrPatterns, manual) ⇒ KeywordDocumentation

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 an Array of TextParser::TokenDoc that describe the arguments of the pattern. optAttrPatterns is an Array with references to TextParser::Patterns that are optional attributes to this keyword.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 50

def initialize(rule, pattern, syntax, args, optAttrPatterns, manual)
  @rule = rule
  @pattern = pattern
  # The unique identifier. Usually the attribute or property name. To
  # disambiguate a .<scope> can be added.
  @keyword = pattern.keyword
  # Similar to @keyword, but without the scope. Since there could be
  # several, this is an Array of String objects.
  @names = []
  @syntax = syntax
  @args = args
  @manual = manual
  # Hash that maps patterns of optional attributes to a boolean value. It
  # is true if the pattern is a scenario specific attribute.
  @optAttrPatterns = optAttrPatterns
  # The above hash is later converted into a list that points to the
  # keyword documentation of the optional attribute.
  @optionalAttributes = []
  @scenarioSpecific = false
  @inheritedFromProject= false
  @inheritedFromParent = false
  @contexts = []
  @seeAlso = []
  # The following are references to the neighboring keyword in an
  # alphabetically sorted list.
  @predecessor = nil
  @successor = nil
  # Array to collect all references to other RichText objects.
  @references = []
end

Instance Attribute Details

#contextsObject

Returns the value of attribute contexts.



41
42
43
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 41

def contexts
  @contexts
end

#inheritedFromParentObject

Returns the value of attribute inheritedFromParent.



41
42
43
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 41

def inheritedFromParent
  @inheritedFromParent
end

#inheritedFromProjectObject

Returns the value of attribute inheritedFromProject.



41
42
43
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 41

def inheritedFromProject
  @inheritedFromProject
end

#keywordObject (readonly)

Returns the value of attribute keyword.



40
41
42
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 40

def keyword
  @keyword
end

#namesObject (readonly)

Returns the value of attribute names.



40
41
42
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 40

def names
  @names
end

#optionalAttributesObject (readonly)

Returns the value of attribute optionalAttributes.



40
41
42
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 40

def optionalAttributes
  @optionalAttributes
end

#patternObject (readonly)

Returns the value of attribute pattern.



40
41
42
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 40

def pattern
  @pattern
end

#predecessorObject

Returns the value of attribute predecessor.



41
42
43
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 41

def predecessor
  @predecessor
end

#referencesObject (readonly)

Returns the value of attribute references.



40
41
42
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 40

def references
  @references
end

#scenarioSpecificObject

Returns the value of attribute scenarioSpecific.



41
42
43
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 41

def scenarioSpecific
  @scenarioSpecific
end

#successorObject

Returns the value of attribute successor.



41
42
43
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 41

def successor
  @successor
end

Instance Method Details

#computeInheritanceObject



163
164
165
166
167
168
169
170
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 163

def computeInheritance
  if (propertySet = findPropertySet)
    keyword = @keyword
    keyword = keyword.split('.')[0] if keyword.include?('.')
    @inheritedFromProject = propertySet.inheritedFromProject?(keyword)
    @inheritedFromParent = propertySet.inheritedFromParent?(keyword)
  end
end

#crossReference(keywords, rules) ⇒ Object

Post process the class member to set cross references to other KeywordDocumentation items.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 105

def crossReference(keywords, rules)
  # Get the attribute or property name of the Keyword. This is not unique
  # like @keyword since it's got no scope.
  @pattern.terminalTokens(rules).each do |tok|
    # Ignore patterns that don't have a real name.
    break if tok[0] == '{'

    @names << tok[0]
  end

  # Some arguments are references to other patterns. The current keyword
  # is added as context to such patterns.
  @args.each do |arg|
    if arg.pattern && checkReference(arg.pattern)
      kwd = keywords[arg.pattern.keyword]
      kwd.contexts << self unless kwd.contexts.include?(self)
    end
  end

  # Optional attributes are treated similarly. In addition we add them to
  # the @optionalAttributes list of this keyword.
  @optAttrPatterns.each do |pattern, scenarioSpecific|
    next unless checkReference(pattern)

    # Check if all the attributes are documented. We ignore undocumented
    # keywords that are deprecated or removed.
    if (kwd = keywords[pattern.keyword]).nil?
      unless [ :deprecated, :removed ].include?(pattern.supportLevel)
        token = pattern.terminalTokens(rules)
        $stderr.puts "Keyword #{keyword} has undocumented optional " +
          "attribute #{token[0]}"
      end
    else
      @optionalAttributes << kwd
      kwd.contexts << self unless kwd.contexts.include?(self)
      kwd.scenarioSpecific = true if scenarioSpecific
    end
  end

  # Resolve the seeAlso patterns to keyword references.
  @pattern.seeAlso.sort.each do |also|
    if keywords[also].nil?
      raise "See also reference #{also} of #{@pattern} is unknown"
    end
    @seeAlso << keywords[also]
  end
end

#generateHTML(directory) ⇒ Object

Return a String that represents the keyword documentation in an XML formatted form.



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 329

def generateHTML(directory)
  html = HTMLDocument.new
  head = html.generateHead(keyword,
                           { 'description' => 'The TaskJuggler Manual',
                             'keywords' =>
                             'taskjuggler, project, management' })
  head << @manual.generateStyleSheet

  html.html << BODY.new do
    [
      @manual.generateHTMLHeader,
      generateHTMLNavigationBar,

      DIV.new('style' => 'margin-left:5%; margin-right:5%') do
        [
          generateHTMLKeywordBox,
          generateHTMLSupportLevel,
          generateHTMLDescriptionBox,
          generateHTMLOptionalAttributesBox,
          generateHTMLExampleBox
        ]
      end,
      generateHTMLNavigationBar,
      @manual.generateHTMLFooter
    ]
  end

  if directory
    html.write(directory + "#{keyword}.html")
  else
    puts html.to_s
  end
end

#globalScope?Boolean

Returns true of the keyword can be used outside of any other keyword context.

Returns:

  • (Boolean)


95
96
97
98
99
100
101
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 95

def globalScope?
  return true if @contexts.empty?
  @contexts.each do |context|
    return true if context.keyword == 'properties'
  end
  false
end

#isProperty?Boolean

Returns true of the KeywordDocumentation is documenting a TJP property (task, resources, etc.). A TJP property can be nested.

Returns:

  • (Boolean)


83
84
85
86
87
88
89
90
91
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 83

def isProperty?
  # I haven't found a good way to automatically detect all the various
  # report types as properties. They don't directly include themselves as
  # attributes.
  return true if %w( accountreport export nikureport resourcereport
                     taskreport textreport timesheetreport
                     statussheetreport).include?(keyword)
  @optionalAttributes.include?(self)
end

#listAttribute?Boolean

Returns:

  • (Boolean)


153
154
155
156
157
158
159
160
161
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 153

def listAttribute?
  if (propertySet = findPropertySet)
    keyword = @keyword
    keyword = keyword.split('.')[0] if keyword.include?('.')
    return propertySet.listAttribute?(keyword)
  end

  false
end

#titleObject

Return the keyword name in a more readable form. E.g. ‘foo.bar’ is returned as ‘foo (bar)’. ‘foo’ will remain ‘foo’.



174
175
176
177
178
179
180
181
182
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 174

def title
  kwTokens = @keyword.split('.')
  if kwTokens.size == 1
    title = @keyword
  else
    title = "#{kwTokens[0]} (#{kwTokens[1]})"
  end
  title
end

#to_sObject

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



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/taskjuggler/KeywordDocumentation.rb', line 186

def to_s
  tagW = 13
  textW = 79

  # Top line with multiple elements
  str = "#{blue('Keyword:')}     #{bold(@keyword)}" +
        "#{listAttribute? ? ' (List Attribute)' : '' }\n\n"

  if @pattern.supportLevel != :supported
    msg = supportLevelMessage

    if [ :deprecated, :removed ].include?(@pattern.supportLevel) &&
       @seeAlso.length > 0
      msg += "\n\nPlease use "
      alsoStr = ''
      @seeAlso.each do |also|
        unless alsoStr.empty?
          alsoStr += ', '
        end
        alsoStr += also.keyword
      end
      msg += "#{alsoStr} instead!"
    end

    str += red("Warning:     #{format(tagW, msg, textW)}\n")
  end

  # Don't show further details if the keyword has been removed.
  return str if @pattern.supportLevel == :removed

  str += blue('Purpose:') +
         "     #{format(tagW, newRichText(@pattern.doc).to_s,
                                textW)}\n"
  if @syntax != '[{ <attributes> }]'
    str += blue('Syntax:') + "      #{format(tagW, @syntax, textW)}\n"

    str += blue('Arguments:') + "   "
    if @args.empty?
      str += format(tagW, "none\n", textW)
    else
      argStr = ''
      @args.each do |arg|
        argText = newRichText(arg.text ||
          "See '#{arg.name}' for details.").to_s
        if arg.typeSpec.nil? || ("<#{arg.name}>") == arg.typeSpec
          indent = arg.name.length + 2
          argStr += "#{arg.name}: " +
                    "#{format(indent, argText, textW - tagW)}\n"
        else
          typeSpec = arg.typeSpec
          typeSpec[0] = '['
          typeSpec[-1] = ']'
          indent = arg.name.length + typeSpec.size + 3
          argStr += "#{arg.name} #{typeSpec}: " +
                    "#{format(indent, argText, textW - tagW)}\n"
        end
      end
      str += indent(tagW, argStr)
    end
    str += "\n"
  end

  str += blue('Context:') + '     '
  if @contexts.empty?
    str += format(tagW, 'Global scope', textW)
  else
    cxtStr = ''
    @contexts.each do |context|
      unless cxtStr.empty?
        cxtStr += ', '
      end
      cxtStr += context.keyword
    end
    str += format(tagW, cxtStr, textW)
  end

  str += "\n#{blue('Attributes:')}  "
  if @optionalAttributes.empty?
    str += "none\n\n"
  else
    attrStr = ''
    @optionalAttributes.sort! do |a, b|
      a.keyword <=> b.keyword
    end
    showLegend = false
    @optionalAttributes.each do |attr|
      unless attrStr.empty?
        attrStr += ', '
      end
      attrStr += attr.keyword
      if attr.scenarioSpecific || attr.inheritedFromProject ||
         attr.inheritedFromParent
        first = true
        showLegend = true
        tag = '['
        if attr.scenarioSpecific
          tag += 'sc'
          first = false
        end
        if attr.inheritedFromProject
          tag += ':' unless first
          tag += 'ig'
          first = false
        end
        if attr.inheritedFromParent
          tag += ':' unless first
          tag += 'ip'
        end
        tag += ']'
        attrStr += cyan(tag)
      end
    end
    if showLegend
      attrStr += "\n\n#{cyan('[sc]')} : Attribute is scenario specific" +
                 "\r#{cyan('[ig]')} : " +
                 "Value can be inherited from global setting" +
                 "\r#{cyan('[ip]')} : " +
                 "Value can be inherited from parent property"
    end
    str += format(tagW, attrStr, textW)
    str += "\n"
  end

  unless @seeAlso.empty?
    str += blue('See also:') + "    "
    alsoStr = ''
    @seeAlso.each do |also|
      unless alsoStr.empty?
        alsoStr += ', '
      end
      alsoStr += also.keyword
    end
    str += format(tagW, alsoStr, textW)
    str += "\n"
  end

  #    str += "Rule:    #{@rule.name}\n" if @rule
  #    str += "Pattern: #{@pattern.tokens.join(' ')}\n" if @pattern
  str
end