code: def print_docstring(obj, file):
      """Prints a classes's docstring to a file."""
  
      doc = ClassDoc(obj) if inspect.isclass(obj) else FunctionDoc(obj)
  
      printf = functools.partial(print, file=file)
  
      printf(h1(obj.__name__))
      printf(md_line(concat_lines(doc["Summary"])))
      printf(md_line(concat_lines(doc["Extended Summary"])))
  
      # We infer the type annotations from the signatures, and therefore rely on the signature
      # instead of the docstring for documenting parameters
      try:
          signature = inspect.signature(obj)
      except ValueError:
          signature = (
              inspect.Signature()
          )  # TODO: this is necessary for Cython classes, but it's not correct
      params_desc = {
          param.name: " ".join(param.desc)
          for param in doc["Parameters"]
      }
  
      # Parameters
      if signature.parameters:
          printf(h2("Parameters"))
      for param in signature.parameters.values():
          # Name
          printf(f"- **{param.name}**\n")
          # Type annotation
          if param.annotation is not param.empty:
              anno = inspect.formatannotation(param.annotation)
              anno = anno.strip("'")
              printf(f"     *Type* → *{anno}*\n")
          # Default value
          if param.default is not param.empty:
              printf(f"     *Default* → `{param.default}`\n")
          printf("\n", file=file)
          # Description
          desc = params_desc[param.name]
          if desc:
              printf(f"    {desc}\n")
      printf("")
  
      # Attributes
      if doc["Attributes"]:
          printf(h2("Attributes"))
      for attr in doc["Attributes"]:
          # Name
          printf(f"- **{attr.name}**", end="")
          # Type annotation
          if attr.type:
              printf(f" (*{attr.type}*)", end="")
          printf("\n", file=file)
          # Description
          desc = " ".join(attr.desc)
          if desc:
              printf(f"    {desc}\n")
      printf("")
  
      # Examples
      if doc["Examples"]:
          printf(h2("Examples"))
  
          parser = doctest.DocTestParser()
          lines = parser.parse(
              inspect.cleandoc("\n".join(doc["Examples"]))
          )
          in_code = False
  
          for line in lines:
              if isinstance(line, doctest.Example):
                  example = line
  
                  # Start code fences for new examples
                  if not in_code:
                      printf("```python")
                      in_code = True
  
                  printf(example.source, end="")
                  if example.want:
                      # Close code fences for example source
                      printf("```")
                      # Enclose the output in code fences
                      printf("```")
                      printf(example.want, end="")
                      printf("```")
                      in_code = False
  
              elif text := line:
                  # Close code fences for example source
                  if in_code and text.strip():
                      printf("```")
                      in_code = False
                  printf(text, end="")
          else:
              # Close code fences for example source
              if in_code:
                  printf("```")
                  in_code = False
  
      printf("")
  
      import river.base
      import river.ensemble
  
      # Methods
      if inspect.isclass(obj) and doc["Methods"]:
          printf(h2("Methods"))
          printf_indent = lambda x, **kwargs: printf(
              f"    {x}", **kwargs
          )
  
          for meth in doc["Methods"]:
              base_method_names = {"clone", "mutate"}
  
              if (
                  issubclass(obj, river.base.Base)
                  and not obj is river.base.Base
                  and meth.name in base_method_names
              ):
                  continue
  
              container_method_names = {
                  "append",
                  "clear",
                  "copy",
                  "count",
                  "extend",
                  "index",
                  "insert",
                  "pop",
                  "popitem",
                  "keys",
                  "fromkeys",
                  "get",
                  "remove",
                  "reverse",
                  "sort",
                  "setdefault",
                  "values",
                  "update",
                  "items",
              }
  
              if (
                  issubclass(
                      obj, (collections.UserList, collections.UserDict)
                  )
                  and not obj is river.base.Ensemble
                  and meth.name in container_method_names
              ):
                  continue
  
              printf(md_line(f'???- abstract "{meth.name}"'))
  
              # Parse method docstring
              docstring = find_method_docstring(
                  klass=obj, method=meth.name
              )
              if not docstring:
                  continue
              meth_doc = FunctionDoc(func=None, doc=docstring)
  
              printf_indent(md_line(" ".join(meth_doc["Summary"])))
              if meth_doc["Extended Summary"]:
                  printf_indent(
                      md_line(" ".join(meth_doc["Extended Summary"]))
                  )
  
              # We infer the type annotations from the signatures, and therefore rely on the signature
              # instead of the docstring for documenting parameters
              signature = find_method_signature(obj, meth.name)
              params_desc = {
                  param.name: " ".join(param.desc)
                  for param in doc["Parameters"]
              }
  
              # Parameters
              if (
                  len(signature.parameters) > 1
              ):  # signature is never empty, but self doesn't count
                  printf_indent("**Parameters**\n")
              for param in signature.parameters.values():
                  if param.name == "self":
                      continue
                  # Name
                  printf_indent(f"- **{param.name}**", end="")
                  # Type annotation
                  if param.annotation is not param.empty:
                      printf_indent(
                          f" — *{inspect.formatannotation(param.annotation)}*",
                          end="",
                      )
                  # Default value
                  if param.default is not param.empty:
                      printf_indent(
                          f" — defaults to `{param.default}`", end=""
                      )
                  printf_indent("", file=file)
                  # Description
                  desc = params_desc.get(param.name)
                  if desc:
                      printf_indent(f"    {desc}")
              printf_indent("")
  
              # Returns
              if meth_doc["Returns"]:
                  printf_indent("**Returns**\n")
                  return_val = meth_doc["Returns"][0]
                  if signature.return_annotation is not inspect._empty:
                      if inspect.isclass(signature.return_annotation):
                          printf_indent(
                              f"*{signature.return_annotation.__name__}*: ",
                              end="",
                          )
                      else:
                          printf_indent(
                              f"*{signature.return_annotation}*: ",
                              end="",
                          )
                  printf_indent(return_val.type)
                  printf_indent("")
  
              # Add a space between methods
              printf("<span />")
  
      # Notes
      if doc["Notes"]:
          printf(h2("Notes"))
          printf(md_line("\n".join(doc["Notes"])))
  
      # References
      if doc["References"]:
          printf(md_line("\n".join(doc["References"])))
  
imports: import argparse
  import collections
  import doctest
  import functools
  import importlib
  import inspect
  import os
  import pathlib
  import re
  import shutil
  from numpydoc.docscrape import ClassDoc, FunctionDoc
label_desc: Use image node_id 13 for calling a global function with example usage: print_docstring(obj, file) without return types