docs: def _create_parser() -> ArgumentParser:
      """
      Creates argument parser for the CLI.
      :return: the argument parser
      """
      parser = ArgumentParser(prog=EXECUTABLE_NAME, description=f"{DESCRIPTION} (v{VERSION})")
      parser.add_argument(
          f"-{VERBOSE_SHORT_PARAMETER}", action="count", default=0,
          help="increase the level of log verbosity (add multiple increase further)")
      subparsers = parser.add_subparsers(dest=ACTION_CLI_PARAMETER_ACCESS, help="action")
  
      unlock_subparser = subparsers.add_parser(Action.UNLOCK.value, help="release a lock")
      unlock_subparser.add_argument(
          f"-{REGEX_KEY_ENABLED_SHORT_PARAMETER}", action="store_true", default=DEFAULT_REGEX_KEY_ENABLED,
          help="whether the key should be treated as a regular expression and to release all matching locks")
  
      lock_subparser = subparsers.add_parser(Action.LOCK.value, help="acquire a lock")
      lock_and_execute_subparser = subparsers.add_parser(Action.EXECUTE.value, help="call executable whilst holding lock")
  
      for subparser in (lock_subparser, lock_and_execute_subparser):
          subparser.add_argument(
              f"--{SESSION_TTL_LONG_PARAMETER}", type=float, default=DEFAULT_SESSION_TTL,
              help=f"time to live (ttl) in seconds of the session that will be created to hold the lock. Must be between "
                   f"{MIN_LOCK_TIMEOUT_IN_SECONDS}s and {MAX_LOCK_TIMEOUT_IN_SECONDS}s (inclusive). If set to "
                   f"{NO_EXPIRY_SESSION_TTL_CLI_PARAMETER_VALUE}, the session will not expire")
          subparser.add_argument(
              f"--{NON_BLOCKING_LONG_PARAMETER}", action="store_true",
              default=DEFAULT_NON_BLOCKING, help="do not block if cannot lock straight away")
          subparser.add_argument(
              f"--{TIMEOUT_LONG_PARAMETER}", default=DEFAULT_TIMEOUT, type=float,
              help="give up trying to acquire the key after this many seconds (where 0 is never)")
          subparser.add_argument(
              f"--{METADATA_LONG_PARAMETER}", default=DEFAULT_METADATA, type=str, action=_ParseJsonAction,
              help="additional metadata to add to the lock information (will be converted to JSON)")
          subparser.add_argument(
              f"--{ON_BEFORE_LOCK_LONG_PARAMETER}", default=[], type=str, nargs="+", action="append",
              help="path to executable that is to be called before an attempt is made to acquire a lock, where the lock "
                   "key is passed as the first argument. Any failures of this executable are ignored")
          subparser.add_argument(
              f"--{ON_LOCK_ALREADY_LOCKED_LONG_PARAMETER}", default=[], type=str, nargs="+", action="append",
              help="path to executable that is to be called after an attempt has been made to acquire a lock but failed "
                   "due to the lock already been taken, where the lock key is passed as the first argument. Any failures "
                   "of this executable are ignored")
          subparser.add_argument(
              f"-{LOCK_POLL_INTERVAL_SHORT_PARAMETER}", default=DEFAULT_LOCK_POLL_INTERVAL_GENERATOR(1), type=float,
              help="number of seconds between polls to acquire a locked lock")
  
      # XXX: probably a better way of iterating subparsers on `subparsers`
      for subparser in [unlock_subparser, lock_subparser, lock_and_execute_subparser]:
          subparser.add_argument(
              KEY_PARAMETER, type=str, help="the lock identifier")
  
      lock_and_execute_subparser.add_argument(
          EXECUTABLE_PARAMETER, type=str, help="to execute in shell")
  
      return parser