docs: def _processDocstring(self, node, tail='', **kwargs):
          """
          Handles a docstring for functions, classes, and modules.
  
          Basically just figures out the bounds of the docstring and sends it
          off to the parser to do the actual work.
          """
          typeName = type(node).__name__
          # Modules don't have lineno defined, but it's always 0 for them.
          curLineNum = startLineNum = 0
          if typeName != 'Module':
              startLineNum = curLineNum = node.lineno - 1
          # Figure out where both our enclosing object and our docstring start.
          line = ''
          while curLineNum < len(self.lines):
              line = self.lines[curLineNum]
              match = AstWalker.__docstrMarkerRE.match(line)
              if match:
                  break
              curLineNum += 1
          docstringStart = curLineNum
          # Figure out where our docstring ends.
          if not AstWalker.__docstrOneLineRE.match(line):
              # Skip for the special case of a single-line docstring.
              curLineNum += 1
              while curLineNum < len(self.lines):
                  line = self.lines[curLineNum]
                  if line.find(match.group(2)) >= 0:
                      break
                  curLineNum += 1
          endLineNum = curLineNum + 1
  
          # Isolate our enclosing object's declaration.
          defLines = self.lines[startLineNum: docstringStart]
          # Isolate our docstring.
          self.docLines = self.lines[docstringStart: endLineNum]
  
          # If we have a docstring, extract information from it.
          if self.docLines:
              # Get rid of the docstring delineators.
              self.docLines[0] = AstWalker.__docstrMarkerRE.sub('',
                                                                self.docLines[0])
              self.docLines[-1] = AstWalker.__docstrMarkerRE.sub('',
                                                                 self.docLines[-1])
              # Handle special strings within the docstring.
              docstringConverter = self.__alterDocstring(
                  tail, self.__writeDocstring())
              for lineInfo in enumerate(self.docLines):
                  docstringConverter.send(lineInfo)
              docstringConverter.send((len(self.docLines) - 1, None))
  
          # Add a Doxygen @brief tag to any single-line description.
          if self.options.autobrief:
              safetyCounter = 0
              while len(self.docLines) > 0 and self.docLines[0].lstrip('#').strip() == '':
                  del self.docLines[0]
                  self.docLines.append('')
                  safetyCounter += 1
                  if safetyCounter >= len(self.docLines):
                      # Escape the effectively empty docstring.
                      break
              if len(self.docLines) == 1 or (len(self.docLines) >= 2 and (
                  self.docLines[1].strip(whitespace + '#') == '' or
                      self.docLines[1].strip(whitespace + '#').startswith('@'))):
                  self.docLines[0] = "## @brief {0}".format(self.docLines[0].lstrip('#'))
                  if len(self.docLines) > 1 and self.docLines[1] == '# @par':
                      self.docLines[1] = '#'
  
          if defLines:
              match = AstWalker.__indentRE.match(defLines[0])
              indentStr = match and match.group(1) or ''
              self.docLines = [AstWalker.__newlineRE.sub(indentStr + '#', docLine)
                               for docLine in self.docLines]
  
          # Taking away a docstring from an interface method definition sometimes
          # leaves broken code as the docstring may be the only code in it.
          # Here we manually insert a pass statement to rectify this problem.
          if typeName != 'Module':
              if docstringStart < len(self.lines):
                  match = AstWalker.__indentRE.match(self.lines[docstringStart])
                  indentStr = match and match.group(1) or ''
              else:
                  indentStr = ''
              containingNodes = kwargs.get('containingNodes', []) or []
              fullPathNamespace = self._getFullPathName(containingNodes)
              parentType = fullPathNamespace[-2][1]
              if parentType == 'interface' and typeName == 'FunctionDef' \
                 or fullPathNamespace[-1][1] == 'interface':
                  defLines[-1] = '{0}{1}{2}pass'.format(defLines[-1],
                                                        linesep, indentStr)
              elif self.options.autobrief and typeName == 'ClassDef':
                  # If we're parsing docstrings separate out class attribute
                  # definitions to get better Doxygen output.
                  for firstVarLineNum, firstVarLine in enumerate(self.docLines):
                      if '@property\t' in firstVarLine:
                          break
                  lastVarLineNum = len(self.docLines)
                  if lastVarLineNum > 0 and '@property\t' in firstVarLine:
                      while lastVarLineNum > firstVarLineNum:
                          lastVarLineNum -= 1
                          if '@property\t' in self.docLines[lastVarLineNum]:
                              break
                      lastVarLineNum += 1
                      if firstVarLineNum < len(self.docLines):
                          indentLineNum = endLineNum
                          indentStr = ''
                          while not indentStr and indentLineNum < len(self.lines):
                              match = AstWalker.__indentRE.match(self.lines[indentLineNum])
                              indentStr = match and match.group(1) or ''
                              indentLineNum += 1
                          varLines = ['{0}{1}'.format(linesep, docLine).replace(
                                      linesep, linesep + indentStr)
                                      for docLine in self.docLines[
                                          firstVarLineNum: lastVarLineNum]]
                          defLines.extend(varLines)
                          self.docLines[firstVarLineNum: lastVarLineNum] = []
                          # After the property shuffling we will need to relocate
                          # any existing namespace information.
                          namespaceLoc = defLines[-1].find('\n# @namespace')
                          if namespaceLoc >= 0:
                              self.docLines[-1] += defLines[-1][namespaceLoc:]
                              defLines[-1] = defLines[-1][:namespaceLoc]
  
          # For classes and functions, apply our changes and reverse the
          # order of the declaration and docstring, and for modules just
          # apply our changes.
          if typeName != 'Module':
              self.lines[startLineNum: endLineNum] = self.docLines + defLines
          else:
              self.lines[startLineNum: endLineNum] = defLines + self.docLines