docs: def automodapi_replace(sourcestr, app, dotoctree=True, docname=None,
                         warnings=True):
      """
      Replaces `sourcestr`'s entries of ".. automdapi::" with the
      automodapi template form based on provided options.
  
      This is used with the sphinx event 'source-read' to replace
      `automodapi`_ entries before sphinx actually processes them, as
      automodsumm needs the code to be present to generate stub
      documentation.
  
      Parameters
      ----------
      sourcestr : str
          The string with sphinx source to be checked for automodapi
          replacement.
      app : `sphinx.application.Application`
          The sphinx application.
      dotoctree : bool
          If `True`, a ":toctree:" option will be added in the "..
          automodsumm::" sections of the template, pointing to the
          appropriate "generated" directory based on the Astropy convention
          (e.g. in ``docs/api``)
      docname : str
          The name of the file for this `sourcestr` (if known - if not, it
          can be `None`). If not provided and `dotoctree` is `True`, the
          generated files may end up in the wrong place.
      warnings : bool
          If `False`, all warnings that would normally be issued are
          silenced.
  
      Returns
      -------
      newstr :str
          The string with automodapi entries replaced with the correct
          sphinx markup.
      """
  
      spl = _automodapirex.split(sourcestr)
      if len(spl) > 1:  # automodsumm is in this document
  
          if dotoctree:
              toctreestr = ':toctree: '
              dirnm = app.config.automodapi_toctreedirnm
              if not dirnm.endswith("/"):
                  dirnm += "/"
              if docname is not None:
                  toctreestr += '../' * docname.count('/') + dirnm
              else:
                  toctreestr += dirnm
          else:
              toctreestr = ''
  
          newstrs = [spl[0]]
          for grp in range(len(spl) // 3):
              modnm = spl[grp * 3 + 1]
  
              # find where this is in the document for warnings
              if docname is None:
                  location = None
              else:
                  location = (docname, spl[0].count('\n'))
  
              # initialize default options
              toskip = []
              inhdiag = maindocstr = top_head = True
              hds = '-^'
              allowedpkgnms = []
  
              # look for actual options
              unknownops = []
              for opname, args in _automodapiargsrex.findall(spl[grp * 3 + 2]):
                  if opname == 'skip':
                      toskip.append(args.strip())
                  elif opname == 'no-inheritance-diagram':
                      inhdiag = False
                  elif opname == 'no-main-docstr':
                      maindocstr = False
                  elif opname == 'headings':
                      hds = args
                  elif opname == 'no-heading':
                      top_head = False
                  elif opname == 'allowed-package-names':
                      allowedpkgnms.append(args.strip())
                  else:
                      unknownops.append(opname)
  
              #join all the allowedpkgnms
              if len(allowedpkgnms) == 0:
                  allowedpkgnms = ''
                  onlylocals = True
              else:
                  allowedpkgnms = ':allowed-package-names: ' + ','.join(allowedpkgnms)
                  onlylocals = allowedpkgnms
  
              # get the two heading chars
              if len(hds) < 2:
                  msg = 'Not enough headings (got {0}, need 2), using default -^'
                  if warnings:
                      app.warn(msg.format(len(hds)), location)
                  hds = '-^'
              h1, h2 = hds.lstrip()[:2]
  
              # tell sphinx that the remaining args are invalid.
              if len(unknownops) > 0 and app is not None:
                  opsstrs = ','.join(unknownops)
                  msg = 'Found additional options ' + opsstrs + ' in automodapi.'
                  if warnings:
                      app.warn(msg, location)
  
              ispkg, hascls, hasfuncs = _mod_info(modnm, toskip, onlylocals=onlylocals)
  
              # add automodule directive only if no-main-docstr isn't present
              if maindocstr:
                  automodline = '.. automodule:: {modname}'.format(modname=modnm)
              else:
                  automodline = ''
              if top_head:
                  newstrs.append(automod_templ_modheader.format(modname=modnm,
                      modhds=h1 * len(modnm),
                      pkgormod='Package' if ispkg else 'Module',
                      pkgormodhds=h1 * (8 if ispkg else 7),
                      automoduleline=automodline))
              else:
                  newstrs.append(automod_templ_modheader.format(
                      modname='',
                      modhds='',
                      pkgormod='',
                      pkgormodhds='',
                      automoduleline=automodline))
  
              #construct the options for the class/function sections
              #start out indented at 4 spaces, but need to keep the indentation.
              clsfuncoptions = []
              if toctreestr:
                  clsfuncoptions.append(toctreestr)
              if toskip:
                  clsfuncoptions.append(':skip: ' + ','.join(toskip))
              if allowedpkgnms:
                  clsfuncoptions.append(allowedpkgnms)
              clsfuncoptionstr = '\n    '.join(clsfuncoptions)
  
              if hasfuncs:
                  newstrs.append(automod_templ_funcs.format(
                      modname=modnm,
                      funchds=h2 * 9,
                      clsfuncoptions=clsfuncoptionstr))
  
              if hascls:
                  newstrs.append(automod_templ_classes.format(
                      modname=modnm,
                      clshds=h2 * 7,
                      clsfuncoptions=clsfuncoptionstr))
  
              if inhdiag and hascls:
                  # add inheritance diagram if any classes are in the module
                  newstrs.append(automod_templ_inh.format(
                      modname=modnm,
                      clsinhsechds=h2 * 25,
                      allowedpkgnms=allowedpkgnms))
  
              newstrs.append(spl[grp * 3 + 3])
  
          newsourcestr = ''.join(newstrs)
  
          if app.config.automodapi_writereprocessed:
              # sometimes they are unicode, sometimes not, depending on how
              # sphinx has processed things
              if isinstance(newsourcestr, text_type):
                  ustr = newsourcestr
              else:
                  ustr = newsourcestr.decode(app.config.source_encoding)
  
              if docname is None:
                  with open(os.path.join(app.srcdir, 'unknown.automodapi'), 'a') as f:
                      f.write('\n**NEW DOC**\n\n')
                      f.write(ustr)
              else:
                  env = app.builder.env
                  # Determine the filename associated with this doc (specifically
                  # the extension)
                  filename = docname + os.path.splitext(env.doc2path(docname))[1]
                  filename += '.automodapi'
  
                  with open(os.path.join(app.srcdir, filename), 'w') as f:
                      f.write(ustr)
  
          return newsourcestr
      else:
          return sourcestr