docs: def parse_changelog(path, **kwargs):
      """
      Load and parse changelog file from ``path``, returning data structures.
  
      This function does not alter any files on disk; it is solely for
      introspecting a Releases ``changelog.rst`` and programmatically answering
      questions like "are there any unreleased bugfixes for the 2.3 line?" or
      "what was included in release 1.2.1?".
  
      For example, answering the above questions is as simple as::
  
          changelog = parse_changelog("/path/to/changelog")
          print("Unreleased issues for 2.3.x: {}".format(changelog['2.3']))
          print("Contents of v1.2.1: {}".format(changelog['1.2.1']))
  
      Aside from the documented arguments, any additional keyword arguments are
      passed unmodified into an internal `get_doctree` call (which then passes
      them to `make_app`).
  
      :param str path: A relative or absolute file path string.
  
      :returns:
          A dict whose keys map to lists of ``releases.models.Issue`` objects, as
          follows:
  
          - Actual releases are full version number keys, such as ``"1.2.1"`` or
            ``"2.0.0"``.
          - Unreleased bugs (or bug-like issues; see the Releases docs) are
            stored in minor-release buckets, e.g. ``"1.2"`` or ``"2.0"``.
          - Unreleased features (or feature-like issues) are found in
            ``"unreleased_N_feature"``, where ``N`` is one of the major release
            families (so, a changelog spanning only 1.x will only have
            ``unreleased_1_feature``, whereas one with 1.x and 2.x releases will
            have ``unreleased_1_feature`` and ``unreleased_2_feature``, etc).
  
      .. versionchanged:: 1.6
          Added support for passing kwargs to `get_doctree`/`make_app`.
      """
      app, doctree = get_doctree(path, **kwargs)
      # Have to semi-reproduce the 'find first bullet list' bit from main code,
      # which is unfortunately side-effect-heavy (thanks to Sphinx plugin
      # design).
      first_list = None
      for node in doctree[0]:
          if isinstance(node, bullet_list):
              first_list = node
              break
      # Initial parse into the structures Releases finds useful internally
      releases, manager = construct_releases(first_list.children, app)
      ret = changelog2dict(releases)
      # Stitch them together into something an end-user would find better:
      # - nuke unreleased_N.N_Y as their contents will be represented in the
      # per-line buckets
      for key in ret.copy():
          if key.startswith('unreleased'):
              del ret[key]
      for family in manager:
          # - remove unreleased_bugfix, as they are accounted for in the per-line
          # buckets too. No need to store anywhere.
          manager[family].pop('unreleased_bugfix', None)
          # - bring over each major family's unreleased_feature as
          # unreleased_N_feature
          unreleased = manager[family].pop('unreleased_feature', None)
          if unreleased is not None:
              ret['unreleased_{}_feature'.format(family)] = unreleased
          # - bring over all per-line buckets from manager (flattening)
          # Here, all that's left in the per-family bucket should be lines, not
          # unreleased_*
          ret.update(manager[family])
      return ret