docs: def mimic_user_input(
          args: List[str],
          source_challenge_response: List[Tuple[SubprocSource,
                                                str,
                                                Union[str, SubprocCommand]]],
          line_terminators: List[str] = None,
          print_stdout: bool = False,
          print_stderr: bool = False,
          print_stdin: bool = False,
          stdin_encoding: str = None,
          stdout_encoding: str = None,
          suppress_decoding_errors: bool = True,
          sleep_time_s: float = 0.1) -> None:
      r"""
      Run an external command. Pretend to be a human by sending text to the
      subcommand (responses) when the external command sends us triggers
      (challenges).
      
      This is a bit nasty.
      
      Args:
          args: command-line arguments
          source_challenge_response: list of tuples of the format ``(challsrc,
              challenge, response)``; see below
          line_terminators: valid line terminators
          print_stdout: 
          print_stderr: 
          print_stdin: 
          stdin_encoding: 
          stdout_encoding: 
          suppress_decoding_errors: trap any ``UnicodeDecodeError``?
          sleep_time_s:
          
      The ``(challsrc, challenge, response)`` tuples have this meaning:
      
      - ``challsrc``: where is the challenge coming from? Must be one of the
        objects :data:`SOURCE_STDOUT` or :data:`SOURCE_STDERR`;
      - ``challenge``: text of challenge
      - ``response``: text of response (send to the subcommand's ``stdin``).
      
      Example (modified from :class:`CorruptedZipReader`):
  
      .. code-block:: python
      
          from cardinal_pythonlib.subproc import *
          
          SOURCE_FILENAME = "corrupt.zip"
          TMP_DIR = "/tmp"
          OUTPUT_FILENAME = "rescued.zip"
      
          cmdargs = [
              "zip",  # Linux zip tool
              "-FF",  # or "--fixfix": "fix very broken things"
              SOURCE_FILENAME,  # input file
              "--temp-path", TMP_DIR,  # temporary storage path
              "--out", OUTPUT_FILENAME  # output file
          ]
  
          # We would like to be able to say "y" automatically to
          # "Is this a single-disk archive?  (y/n):"
          # The source code (api.c, zip.c, zipfile.c), from
          # ftp://ftp.info-zip.org/pub/infozip/src/ , suggests that "-q"
          # should do this (internally "-q" sets "noisy = 0") - but in
          # practice it doesn't work. This is a critical switch.
          # Therefore we will do something very ugly, and send raw text via
          # stdin.
  
          ZIP_PROMPTS_RESPONSES = [
              (SOURCE_STDOUT, "Is this a single-disk archive?  (y/n): ", "y\n"),
              (SOURCE_STDOUT, " or ENTER  (try reading this split again): ", "q\n"),
              (SOURCE_STDERR,
               "zip: malloc.c:2394: sysmalloc: Assertion `(old_top == initial_top (av) "
               "&& old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && "
               "prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) "
               "== 0)' failed.", TERMINATE_SUBPROCESS),
          ]
          ZIP_STDOUT_TERMINATORS = ["\n", "): "]
  
          mimic_user_input(cmdargs,
                           source_challenge_response=ZIP_PROMPTS_RESPONSES,
                           line_terminators=ZIP_STDOUT_TERMINATORS,
                           print_stdout=show_zip_output,
                           print_stdin=show_zip_output)
  
      """  # noqa
      line_terminators = line_terminators or ["\n"]  # type: List[str]
      stdin_encoding = stdin_encoding or sys.getdefaultencoding()
      stdout_encoding = stdout_encoding or sys.getdefaultencoding()
  
      # Launch the command
      p = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=0)
  
      # Launch the asynchronous readers of stdout and stderr
      stdout_queue = Queue()
      # noinspection PyTypeChecker
      stdout_reader = AsynchronousFileReader(
          fd=p.stdout,
          queue=stdout_queue,
          encoding=stdout_encoding,
          line_terminators=line_terminators,
          cmdargs=args,
          suppress_decoding_errors=suppress_decoding_errors
      )
      stdout_reader.start()
      stderr_queue = Queue()
      # noinspection PyTypeChecker
      stderr_reader = AsynchronousFileReader(
          fd=p.stderr,
          queue=stderr_queue,
          encoding=stdout_encoding,  # same as stdout
          line_terminators=line_terminators,
          cmdargs=args,
          suppress_decoding_errors=suppress_decoding_errors
      )
      stderr_reader.start()
  
      while not stdout_reader.eof() or not stderr_reader.eof():
          lines_with_source = []  # type: List[Tuple[SubprocSource, str]]
          while not stdout_queue.empty():
              lines_with_source.append((SOURCE_STDOUT, stdout_queue.get()))
          while not stderr_queue.empty():
              lines_with_source.append((SOURCE_STDERR, stderr_queue.get()))
  
          for src, line in lines_with_source:
              if src is SOURCE_STDOUT and print_stdout:
                  print(line, end="")  # terminator already in line
              if src is SOURCE_STDERR and print_stderr:
                  print(line, end="")  # terminator already in line
              for challsrc, challenge, response in source_challenge_response:
                  # log.critical("challsrc={!r}", challsrc)
                  # log.critical("challenge={!r}", challenge)
                  # log.critical("line={!r}", line)
                  # log.critical("response={!r}", response)
                  if challsrc != src:
                      continue
                  if challenge in line:
                      if response is TERMINATE_SUBPROCESS:
                          log.warning("Terminating subprocess {!r} because input "
                                      "{!r} received", args, challenge)
                          p.kill()
                          return
                      else:
                          p.stdin.write(response.encode(stdin_encoding))
                          p.stdin.flush()
                          if print_stdin:
                              print(response, end="")
  
          # Sleep a bit before asking the readers again.
          sleep(sleep_time_s)
  
      stdout_reader.join()
      stderr_reader.join()
      p.stdout.close()
      p.stderr.close()