# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4; encoding:utf-8 -*-
#
# Copyright 2022 Kenneth Loafman <kenneth@loafman.com>
#
# This file is part of duplicity.
#
# Duplicity is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# Duplicity is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with duplicity; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"""
Data for parse command line, check for consistency, and set config
"""

import argparse
from dataclasses import dataclass

from duplicity import config
from duplicity import __version__
from duplicity.cli_util import *


@dataclass(frozen=True)
class DuplicityCommands:
    """
    duplicity commands and positional args expected

    NOTE: cli_util must contain a function named check_* for each positional arg,
          for example check_source_path() to check for source path validity.
    """
    cleanup = ["target_url"]
    collection_status = ["target_url"]
    full = ["source_path", "target_url"]
    incremental = ["source_path", "target_url"]
    list_current_files = ["target_url"]
    remove_older_than = ["remove_time", "target_url"]
    remove_all_but_n_full = ["count", "target_url"]
    remove_all_inc_of_but_n_full = ["count", "target_url"]
    restore = ["source_url", "target_dir"]
    verify = ["source_url", "target_dir"]


@dataclass(frozen=True)
class CommandAliases:
    """
    commands and aliases
    """
    cleanup = ["cl"]
    collection_status = ["st"]
    full = ["fb"]
    incremental = ["inc", "ib"]
    list_current_files = ["ls"]
    remove_older_than = ["ro"]
    remove_all_but_n_full = ["ra"]
    remove_all_inc_of_but_n_full = ["ri"]
    restore = ["rb"]
    verify = ["vb"]


all_commands = set()
for var, aliases in CommandAliases.__dict__.items():
    if var.startswith("__"):
        continue
    all_commands.add(var2cmd(var))
    for alias in aliases:
        all_commands.add(alias)


@dataclass(frozen=True)
class OptionKwargs:
    """
    Option kwargs for add_argument
    """
    allow_source_mismatch = {
        "action": "store_true",
        "help": "Allow different source directories",
        "default": dflt(config.allow_source_mismatch)
    }
    archive_dir = {
        "metavar": _("path"),
        "type": check_file,
        "help": "Path to store metadata archives",
        "default": dflt(config.archive_dir)
    }
    asynchronous_upload = {
        "action": "store_const",
        "const": 1,
        "dest": "async_concurrency",
        "help": "Number of async upload tasks, max of 1",
        "default": dflt(config.async_concurrency)
    }
    azure_blob_tier = {
        "metavar": _("Hot|Cool|Archive"),
        "help": "Standard storage tier used for storing backup files (Hot|Cool|Archive)",
        "default": dflt(config.azure_blob_tier)
    }
    azure_max_connections = {
        "metavar": _("number"),
        "type": int,
        "help": "Number of maximum parallel connections to use when the blob size exceeds 64MB",
        "default": dflt(config.azure_max_connections)
    }
    azure_max_block_size = {
        "metavar": _("number"),
        "type": int,
        "help": "Number for the block size to upload a blob if the length is unknown\n"
                "or is larger than the value set by --azure-max-single-put-size\n"
                "The maximum block size the service supports is 100MiB.",
        "default": dflt(config.azure_max_block_size)
    }
    azure_max_single_put_size = {
        "metavar": _("number"),
        "type": int,
        "help": "Largest supported upload size where the Azure library makes only one put call.\n"
                "Used to upload a single block if the content length is known and is less than this",
        "default": dflt(config.azure_max_single_put_size)
    }
    b2_hide_files = {
        "action": "store_true",
        "help": "Whether the B2 backend hides files instead of deleting them",
        "default": dflt(config.b2_hide_files)
    }
    backend_retry_delay = {
        "metavar": _("seconds"),
        "type": int,
        "help": "Delay time before next try after a failure of a backend operation",
        "default": dflt(config.backend_retry_delay)
    }
    cf_backend = {
        "metavar": "pyrax|cloudfiles",
        "help": "Allow the user to switch cloudfiles backend",
        "default": dflt(config.cf_backend)
    }
    compare_data = {
        "action": "store_true",
        "help": "Compare data on verify not only signatures",
        "default": dflt(config.compare_data)
    }
    config_dir = {
        "metavar": _("path"),
        "type": check_file,
        "help": "Path to store configuration files",
        "default": dflt(config.config_dir)
    }
    copy_blocksize = {
        "metavar": _("number"),
        "type": set_kilos,
        "help": "Blocksize to use in copy operations in kilobytes.",
        "default": dflt(config.copy_blocksize)
    }
    copy_links = {
        "action": "store_true",
        "help": "Copy contents of symlinks instead of linking",
        "default": dflt(config.copy_links)
    }
    dry_run = {
        "action": "store_true",
        "help": "Perform dry-run with no writes",
        "default": dflt(config.dry_run)
    }
    encrypt_key = {
        "metavar": _("gpg-key-id"),
        "type": set_encrypt_key,
        "help": "GNUpg key for encryption/decryption",
        "default": dflt(None)
    }
    encrypt_sign_key = {
        "metavar": _("gpg-key-id"),
        "type": set_encrypt_sign_key,
        "help": "GNUpg key for encryption/decryption and signing",
        "default": dflt(None)
    }
    encrypt_secret_keyring = {
        "metavar": _("path"),
        "help": "Path to secret GNUpg keyring",
        "default": dflt(None)
    }
    exclude = {
        "metavar": _("shell_pattern"),
        "action": AddSelectionAction,
        "help": "Exclude globbing pattern",
        "default": dflt(None)
    }
    exclude_device_files = {
        "action": "store_true",
        "help": "Exclude device files",
        "default": dflt(False)
    }
    exclude_filelist = {
        "metavar": _("filename"),
        "action": AddFilelistAction,
        "help": "File with list of file patters to exclude",
        "default": dflt(None)
    }
    exclude_if_present = {
        "metavar": _("filename"),
        "action": AddSelectionAction,
        "help": "Exclude directory if this file is present",
        "default": dflt(None)
    }
    exclude_older_than = {
        "metavar": _("time"),
        "action": AddSelectionAction,
        "help": "Exclude files older than time",
        "default": dflt(None)
    }
    exclude_other_filesystems = {
        "action": "store_true",
        "help": "Exclude other filesystems from backup",
        "default": dflt(False)
    }
    exclude_regexp = {
        "metavar": _("regex"),
        "action": AddSelectionAction,
        "help": "Exclude based on regex pattern",
        "default": dflt(None)
    }
    file_changed = {
        "metavar": _("path"),
        "type": check_file,
        "help": "Whether to collect only the file status, not the whole root",
        "default": dflt(None)
    }
    file_prefix = {
        "metavar": "string",
        "type": make_bytes,
        "help": "String prefix for all duplicity files",
        "default": dflt(config.file_prefix)
    }
    file_prefix_archive = {
        "metavar": "string",
        "type": make_bytes,
        "help": "String prefix for duplicity difftar files",
        "default": dflt(config.file_prefix_archive)
    }
    file_prefix_manifest = {
        "metavar": "string",
        "type": make_bytes,
        "help": "String prefix for duplicity manifest files",
        "default": dflt(config.file_prefix_manifest)
    }
    file_prefix_signature = {
        "metavar": "string",
        "type": make_bytes,
        "help": "String prefix for duplicity signature files",
        "default": dflt(config.file_prefix_signature)
    }
    files_from = {
        "metavar": _("filename"),
        "type": check_file,
        "action": AddFilelistAction,
        "help": "Defines the backup source as a sub-set of the source folder",
        "default": dflt(None)
    }
    filter_globbing = {
        "nargs": 0,
        "action": AddSelectionAction,
        "help": "File selection mode switch, changes the interpretation of any subsequent\n"
                "--exclude* or --include* options to shell globbing.",
        "default": dflt(None)
    }
    filter_ignorecase = {
        "nargs": 0,
        "action": AddSelectionAction,
        "help": "File selection mode switch, changes the interpretation of any subsequent\n"
                "--exclude* or --include* options to case-insensitive matching.",
        "default": dflt(None)
    }
    filter_literal = {
        "nargs": 0,
        "action": AddSelectionAction,
        "help": "File selection mode switch, changes the interpretation of any subsequent\n"
                "--exclude* or --include* options to literal strings.",
        "default": dflt(None)
    }
    filter_regexp = {
        "nargs": 0,
        "action": AddSelectionAction,
        "help": "File selection mode switch, changes the interpretation of any subsequent\n"
                "--exclude* or --include* options to regular expressions.",
        "default": dflt(None)
    }
    filter_strictcase = {
        "nargs": 0,
        "action": AddSelectionAction,
        "help": "File selection mode switch, changes the interpretation of any subsequent\n"
                "--exclude* or --include* options to case-sensitive matching.",
        "default": dflt(None)
    }
    force = {
        "action": "store_true",
        "help": "Force duplicity to actually delete during cleanup",
        "default": dflt(config.force)
    }
    ftp_passive = {
        "action": "store_const",
        "const": "passive",
        "dest": "ftp_connection",
        "help": "Tell FTP to use passive mode",
        "default": dflt(config.ftp_connection)
    }
    ftp_regular = {
        "action": "store_const",
        "const": "regular",
        "dest": "ftp_connection",
        "help": "Tell FTP to use regular mode",
        "default": dflt(config.ftp_connection)
    }
    full_if_older_than = {
        "metavar": _("time"),
        "type": check_time,
        "help": "Perform full backup if last full is older than 'time'",
        "default": dflt(config.full_if_older_than)
    }
    gpg_binary = {
        "metavar": _("path"),
        "type": check_file,
        "help": "Path to GNUpg executable file",
        "default": dflt(config.gpg_binary)
    }
    gpg_options = {
        "metavar": _("options"),
        "action": SplitOptionsAction,
        "help": "Verbatim gpg options.  May be supplied multiple times.",
        "default": dflt(config.gpg_options)
    }
    hidden_encrypt_key = {
        "metavar": _("gpg-key-id"),
        "type": set_hidden_encrypt_key,
        "help": "Hidden GNUpg encryption key",
        "default": dflt(None)
    }
    idr_fakeroot = {
        "metavar": _("path"),
        "type": check_file,
        "help": "Fake root for idrive backend",
        "default": dflt(config.idr_fakeroot)
    }
    ignore_errors = {
        "nargs": 0,
        "action": IgnoreErrorsAction,
        "help": "Ignore most errors during processing",
        "default": dflt(config.ignore_errors)
    }
    imap_full_address = {
        "action": "store_true",
        "help": "Whether to use the full email address as the user name",
        "default": dflt(config.imap_full_address)
    }
    imap_mailbox = {
        "metavar": _("imap_mailbox"),
        "help": "Name of the imap folder to store backups",
        "default": dflt(config.imap_mailbox)
    }
    include = {
        "metavar": _("shell_pattern"),
        "action": AddSelectionAction,
        "help": "Include globbing pattern",
        "default": dflt(None)
    }
    include_filelist = {
        "metavar": _("filename"),
        "action": AddFilelistAction,
        "help": "File with list of file patters to include",
        "default": dflt(None)
    }
    include_regexp = {
        "metavar": _("regex"),
        "action": AddSelectionAction,
        "help": "Include based on regex pattern",
        "default": dflt(None)
    }
    log_fd = {
        "metavar": _("file_descriptor"),
        "type": set_log_fd,
        "help": "Logging file descripto to use",
        "default": dflt(None)
    }
    log_file = {
        "metavar": _("log_filename"),
        "type": set_log_file,
        "help": "Logging filename to use",
        "default": dflt(None)
    }
    log_timestamp = {
        "action": "store_true",
        "help": "Whether to include timestamp and level in log",
        "default": dflt(False)
    }
    max_blocksize = {
        "metavar": _("number"),
        "type": int,
        "help": "Maximum block size for large files in MB",
        "default": dflt(config.max_blocksize)
    }
    metadata_sync_mode = {
        "choices": ("full", "partial"),
        "help": "Only sync required metadata not all",
        "default": dflt(config.metadata_sync_mode)
    }
    mf_purge = {
        "action": "store_true",
        "help": "Option for mediafire to purge files on delete instead of sending to trash",
        "default": dflt(config.mf_purge)
    }
    mp_segment_size = {
        "metavar": _("number"),
        "type": set_megs,
        "help": "Swift backend segment size in megabytes",
        "default": dflt(config.mp_segment_size)
    }
    name = {
        "metavar": _("backup name"),
        "dest": "backup_name",
        "help": "Custom backup name instead of hash",
        "default": dflt(config.backup_name)
    }
    no_compression = {
        "action": "store_false",
        "dest": "compression",
        "help": "If supplied do not perform compression",
        "default": dflt(config.compression)
    }
    no_encryption = {
        "action": "store_false",
        "dest": "encryption",
        "help": "If supplied do not perform encryption",
        "default": dflt(config.encryption)
    }
    no_files_changed = {
        "action": "store_false",
        "dest": "files_changed",
        "help": "If supplied do not collect the files_changed list",
        "default": dflt(config.files_changed)
    }
    no_restore_ownership = {
        "action": "store_false",
        "dest": "restore_ownership",
        "help": "If supplied do not restore uid/gid when finished",
        "default": dflt(config.restore_ownership)
    }
    no_print_statistics = {
        "action": "store_false",
        "dest": "print_statistics",
        "help": "If supplied do not print statistics",
        "default": dflt(config.print_statistics)
    }
    null_separator = {
        "action": "store_true",
        "help": "Whether to split on null instead of newline",
        "default": dflt(config.null_separator)
    }
    num_retries = {
        "metavar": _("number"),
        "type": int,
        "help": "Number of retries on network operations",
        "default": dflt(config.num_retries)
    }
    numeric_owner = {
        "action": "store_true",
        "help": "Keeps number from tar file. Like same option in GNU tar.",
        "default": dflt(config.numeric_owner)
    }
    par2_options = {
        "metavar": _("options"),
        "action": SplitOptionsAction,
        "help": "Verbatim par2 options.  May be supplied multiple times.",
        "default": dflt(config.par2_options)
    }
    par2_redundancy = {
        "metavar": _("number"),
        "type": int,
        "help": "Level of Redundancy in percent for Par2 files",
        "default": dflt(config.par2_redundancy)
    }
    par2_volumes = {
        "metavar": _("number"),
        "type": int,
        "help": "Number of par2 volumes",
        "default": dflt(config.par2_volumes)
    }
    path_to_restore = {
        "metavar": _("path"),
        "type": check_file,
        "dest": "restore_path",
        "help": "File or directory path to restore",
        "default": dflt(config.restore_path)
    }
    progress = {
        "action": "store_true",
        "help": "Display progress for the full and incremental backup operations",
        "default": dflt(config.progress)
    }
    progress_rate = {
        "metavar": _("number"),
        "type": int,
        "help": "Used to control the progress option update rate in seconds",
        "default": dflt(config.progress_rate)
    }
    rename = {
        "metavar": "from to",
        "action": AddRenameAction,
        "nargs": 2,
        "help": "Rename files during restore",
        "default": dflt(config.rename)
    }
    restore_time = {
        "metavar": _("time"),
        "type": check_time,
        "help": "Restores will try to bring back the state as of the following time",
        "default": dflt(config.restore_time)
    }
    rsync_options = {
        "metavar": _("options"),
        "action": SplitOptionsAction,
        "help": "Verbatim rsync options.  May be supplied multiple times.",
        "default": dflt(config.rsync_options)
    }
    s3_endpoint_url = {
        "metavar": _("s3_endpoint_url"),
        "action": "store",
        "help": "Specity S3 endpoint",
        "default": dflt(config.s3_endpoint_url)
    }
    s3_unencrypted_connection = {
        "action": "store_true",
        "help": "Whether to use plain HTTP (without SSL) to send data to S3",
        "default": dflt(config.s3_unencrypted_connection)
    }
    s3_use_deep_archive = {
        "action": "store_true",
        "help": "Whether to use S3 Glacier Deep Archive Storage",
        "default": dflt(config.s3_use_deep_archive)
    }
    s3_use_glacier = {
        "action": "store_true",
        "help": "Whether to use S3 Glacier Storage",
        "default": dflt(config.s3_use_glacier)
    }
    s3_use_glacier_ir = {
        "action": "store_true",
        "help": "Whether to use S3 Glacier IR Storage",
        "default": dflt(config.s3_use_glacier_ir)
    }
    s3_use_ia = {
        "action": "store_true",
        "help": "Whether to use S3 Infrequent Access Storage",
        "default": dflt(config.s3_use_ia)
    }
    s3_use_onezone_ia = {
        "action": "store_true",
        "help": "Whether to use S3 One Zone Infrequent Access Storage",
        "default": dflt(config.s3_use_onezone_ia)
    }
    s3_use_rrs = {
        "action": "store_true",
        "help": "Whether to use S3 Reduced Redundancy Storage",
        "default": dflt(config.s3_use_rrs)
    }
    s3_multipart_chunk_size = {
        "metavar": _("number"),
        "type": set_megs,
        "help": "Chunk size used for S3 multipart uploads.The number of parallel uploads to\n"
                "S3 be given by chunk size / volume size. Use this to maximize the use of\n"
                "your bandwidth",
        "default": dflt(config.s3_multipart_chunk_size)
    }
    s3_multipart_max_procs = {
        "metavar": _("number"),
        "type": int,
        "help": "Number of processes to set the Processor Pool to when uploading multipart\n"
                "uploads to S3. Use this to control the maximum simultaneous uploads to S3",
        "default": dflt(config.s3_multipart_max_procs)
    }
    s3_use_server_side_kms_encryption = {
        u"action": u"store_true",
        u"dest": u"s3_use_sse_kms",
        u"help": u"Allow use of server side KMS encryption",
        u"default": dflt(config.s3_use_sse_kms)
    }
    s3_kms_key_id = {
        "metavar": _("s3_kms_key_id"),
        "action": "store",
        "help": "S3 KMS encryption key id",
        "default": dflt(config.s3_kms_key_id)
    }
    s3_kms_grant = {
        "metavar": _("s3_kms_grant"),
        "action": "store",
        "help": "S3 KMS grant value",
        "default": dflt(config.s3_kms_grant)
    }
    s3_region_name = {
        "metavar": _("s3_region_name"),
        "action": "store",
        "help": "Specity S3 region name",
        "default": dflt(config.s3_region_name)
    }
    swift_storage_policy = {
        "metavar": _("policy"),
        "help": "Option to specify a Swift container storage policy.",
        "default": dflt(config.swift_storage_policy)
    }
    scp_command = {
        "metavar": _("command"),
        "help": "SCP command to use (ssh pexpect backend)",
        "default": dflt(config.scp_command)
    }
    sftp_command = {
        "metavar": _("command"),
        "help": "SFTP command to use (ssh pexpect backend)",
        "default": dflt(config.sftp_command)
    }
    show_changes_in_set = {
        "metavar": _("number"),
        "type": int,
        "help": "Show file changes (new, deleted, changed) in the specified backup\n"
                "set (0 specifies latest, 1 specifies next latest, etc.)",
        "default": dflt(config.show_changes_in_set)
    }
    sign_key = {
        "metavar": _("gpg-key-id"),
        "type": set_sign_key,
        "help": "Sign key for encryption/decryption",
        "default": dflt(None)
    }
    ssh_askpass = {
        "action": "store_true",
        "help": "Ask the user for the SSH password. Not for batch usage",
        "default": dflt(config.ssh_askpass)
    }
    ssh_options = {
        "metavar": _("options"),
        "action": SplitOptionsAction,
        "help": "Verbatim ssh options.  May be supplied multiple times.",
        "default": dflt(config.ssh_options)
    }
    ssl_cacert_file = {
        "metavar": "file",
        "help": _("pem formatted bundle of certificate authorities"),
        "default": dflt(config.ssl_cacert_file)
    }
    ssl_cacert_path = {
        "metavar": "path",
        "help": _("path to a folder with certificate authority files"),
        "default": dflt(config.ssl_cacert_path)
    }
    ssl_no_check_certificate = {
        "action": "store_true",
        "help": "Set to not validate SSL certificates",
        "default": dflt(config.ssl_no_check_certificate)
    }
    tempdir = {
        "metavar": _("path"),
        "type": check_file,
        "dest": "temproot",
        "help": "Working directory for temp files",
        "default": dflt(config.temproot)
    }
    timeout = {
        "metavar": _("seconds"),
        "type": check_timeout,
        "help": "Network timeout in seconds",
        "default": dflt(config.timeout)
    }
    time_separator = {
        "metavar": _("char"),
        "type": check_char,
        "help": "Character used like the ':' in time strings like: 2002-08-06T04:22:00-07:00\n"
                "Note: Not valid for backup commands full and inc, only to read older backups",
        "default": dflt(config.time_separator)
    }
    use_agent = {
        "action": "store_true",
        "help": "Whether to specify --use-agent in GnuPG options",
        "default": dflt(config.use_agent)
    }
    verbosity = {
        "metavar": _("verb"),
        "type": check_verbosity,
        "help": "Logging verbosity: [0-9] or [e, w, n, i, d] or [error, warning, notice, info, debug]",
        "default": dflt(log.NOTICE)
    }
    version = {
        "action": "version",
        "version": f"%(prog)s {__version__}",
        "help": "Display version and exit",
    }
    volsize = {
        "metavar": _("number"),
        "type": set_megs,
        "help": "Volume size to use in MiB",
        "default": dflt(config.volsize)
    }
    webdav_headers = {
        "metavar": "string",
        "help": _("extra headers for Webdav, like 'Cookie,name: value'"),
        "default": dflt(config.webdav_headers)
    }

    # TESTING ONLY - do not use in production
    current_time = {
        "type": int,
        "help": argparse.SUPPRESS
    }
    fail_on_volume = {
        "type": int,
        "help": argparse.SUPPRESS
    }
    pydevd = {
        "action": "store_true",
        "help": argparse.SUPPRESS
    }
    skip_volume = {
        "type": int,
        "help": argparse.SUPPRESS
    }


@dataclass(frozen=True)
class OptionAliases:
    path_to_restore = ["-r"]
    restore_time = ["-t", "--time"]
    verbosity = ["-v"]
    version = ["-V"]


all_options = {var2opt(var) for var in OptionKwargs.__dict__.keys()
               if not var.startswith("__")}

parent_only_options = {
    "--log-fd", "--log-file", "--log-timestamp", "--version",
}

backup_only_options = {
    "--allow-source-mismatch", "--asynchronous-upload", "--dry-run", "--volsize",
}

selection_only_options = {
    "--exclude", "--exclude-device-files", "--exclude-filelist", "--exclude-if-present", "--exclude-older-than",
    "--exclude-other-filesystems", "--exclude-regexp", "--include", "--include-filelist", "--include-regexp",
    "--files-from", "--filter-globbing", "--filter-ignorecase", "--filter-literal", "--filter-regexp",
    "--filter-strictcase",
}

changed_options = {
    "--file-to-restore", "--do-not-restore-ownership",
}

deprecated_options = {
    "--gio", "--old-filenames", "--short-filenames", "--exclude-globbing-filelist",
    "--include-globbing-filelist", "--exclude-filelist-stdin", "--include-filelist-stdin",
    "--s3-multipart-max-timeout", "--s3-use-multiprocessing",
}

deprecated_backup_options = {
    "--time-separator",
}


@dataclass(frozen=True)
class CommandOptions:
    """
    legal options by command
    """
    cleanup = list(
        all_options
        - parent_only_options
        - backup_only_options
        - selection_only_options
    )
    collection_status = list(
        all_options
        - parent_only_options
        - backup_only_options
        - selection_only_options
    )
    full = list(
        all_options
        - parent_only_options
        - deprecated_backup_options
    )
    incremental = list(
        all_options
        - parent_only_options
        - deprecated_backup_options
    )
    list_current_files = list(
        all_options
        - parent_only_options
        - backup_only_options
        - selection_only_options
    )
    remove_older_than = list(
        all_options
        - parent_only_options
        - backup_only_options
        - selection_only_options
    )
    remove_all_but_n_full = list(
        all_options
        - parent_only_options
        - backup_only_options
        - selection_only_options
    )
    remove_all_inc_of_but_n_full = list(
        all_options
        - parent_only_options
        - backup_only_options
        - selection_only_options
    )
    restore = list(
        all_options
        - parent_only_options
        - backup_only_options
        - selection_only_options
    )
    verify = list(
        all_options
        - parent_only_options
        - backup_only_options
    )


trans = {
    # TRANSL: Used in usage help to represent a Unix-style path name. Example:
    # rsync://user[:password]@other_host[:port]//absolute_path
    'absolute_path': _("absolute_path"),

    # TRANSL: Used in usage help. Example:
    # tahoe://alias/some_dir
    'alias': _("alias"),

    # TRANSL: Used in help to represent a "bucket name" for Amazon Web
    # Services' Simple Storage Service (S3). Example:
    # s3://other.host/bucket_name[/prefix]
    'bucket_name': _("bucket_name"),

    # TRANSL: abbreviation for "character" (noun)
    'char': _("char"),

    # TRANSL: noun
    'command': _("command"),

    # TRANSL: Used in usage help to represent the name of a container in
    # Amazon Web Services' Cloudfront. Example:
    # cf+http://container_name
    'container_name': _("container_name"),

    # TRANSL: noun
    'count': _("count"),

    # TRANSL: Used in usage help to represent the name of a file directory
    'directory': _("directory"),

    # TRANSL: Used in usage help to represent the name of a file. Example:
    # --log-file <filename>
    'filename': _("filename"),

    # TRANSL: Used in usage help to represent an ID for a GnuPG key. Example:
    # --encrypt-key <gpg_key_id>
    'gpg_key_id': _("gpg-key-id"),

    # TRANSL: Used in usage help, e.g. to represent the name of a code
    # module. Example:
    # rsync://user[:password]@other.host[:port]::/module/some_dir
    'module': _("module"),

    # TRANSL: Used in usage help to represent a desired number of
    # something. Example:
    # --num-retries <number>
    'number': _("number"),

    # TRANSL: Used in usage help. (Should be consistent with the "Options:"
    # header.) Example:
    # duplicity [full|incremental] [options] source_path target_url
    'options': _("options"),

    # TRANSL: Used in usage help to represent an internet hostname. Example:
    # ftp://user[:password]@other.host[:port]/some_dir
    'other_host': _("other.host"),

    # TRANSL: Used in usage help. Example:
    # ftp://user[:password]@other.host[:port]/some_dir
    'password': _("password"),

    # TRANSL: Used in usage help to represent a Unix-style path name. Example:
    # --archive-dir <path>
    'path': _("path"),

    # TRANSL: Used in usage help to represent a TCP port number. Example:
    # ftp://user[:password]@other.host[:port]/some_dir
    'port': _("port"),

    # TRANSL: Used in usage help. This represents a string to be used as a
    # prefix to names for backup files created by Duplicity. Example:
    # s3://other.host/bucket_name[/prefix]
    'prefix': _("prefix"),

    # TRANSL: Used in usage help to represent a Unix-style path name. Example:
    # rsync://user[:password]@other.host[:port]/relative_path
    'relative_path': _("relative_path"),

    # TRANSL: Used in usage help. Example:
    # --timeout <seconds>
    'seconds': _("seconds"),

    # TRANSL: Used in usage help to represent a "glob" style pattern for
    # matching one or more files, as described in the documentation.
    # Example:
    # --exclude <shell_pattern>
    'shell_pattern': _("shell_pattern"),

    # TRANSL: Used in usage help to represent the name of a single file
    # directory or a Unix-style path to a directory. Example:
    # file:///some_dir
    'some_dir': _("some_dir"),

    # TRANSL: Used in usage help to represent the name of a single file
    # directory or a Unix-style path to a directory where files will be
    # coming FROM. Example:
    # duplicity [full|incremental] [options] source_path target_url
    'source_path': _("source_path"),

    # TRANSL: Used in usage help to represent a URL files will be coming
    # FROM. Example:
    # duplicity [restore] [options] source_url target_dir
    'source_url': _("source_url"),

    # TRANSL: Used in usage help to represent the name of a single file
    # directory or a Unix-style path to a directory. where files will be
    # going TO. Example:
    # duplicity [restore] [options] source_url target_dir
    'target_dir': _("target_dir"),

    # TRANSL: Used in usage help to represent a URL files will be going TO.
    # Example:
    # duplicity [full|incremental] [options] source_path target_url
    'target_url': _("target_url"),

    # TRANSL: Used in usage help to represent a time spec for a previous
    # point in time, as described in the documentation. Example:
    # duplicity remove-older-than time [options] target_url
    'time': _("time"),

    # TRANSL: Used in usage help to represent a user name (i.e. login).
    # Example:
    # ftp://user[:password]@other.host[:port]/some_dir
    'user': _("user"),

    # TRANSL: account id for b2. Example: b2://account_id@bucket/
    'account_id': _("account_id"),

    # TRANSL: application_key for b2.
    # Example: b2://account_id:application_key@bucket/
    'application_key': _("application_key"),

    # TRANSL: remote name for rclone.
    # Example: rclone://remote:/some_dir
    'remote': _("remote"),
}

help_url_formats = _("Backends and their URL formats:") + f"""
  azure://{trans['container_name']}
  b2://{trans['account_id']}[:{trans['application_key']}]@{trans['bucket_name']}/[{trans['some_dir']}/]
  boto3+s3://{trans['bucket_name']}[/{trans['prefix']}]
  cf+http://{trans['container_name']}
  dpbx:///{trans['some_dir']}
  file:///{trans['some_dir']}
  ftp://{trans['user']}[:{trans['password']}]@{trans['other_host']}[:{trans['port']}]/{trans['some_dir']}
  ftps://{trans['user']}[:{trans['password']}]@{trans['other_host']}[:{trans['port']}]/{trans['some_dir']}
  gdocs://{trans['user']}[:{trans['password']}]@{trans['other_host']}/{trans['some_dir']}
  for gdrive:// a <service-account-url> like the following is required
        <serviceaccount-name>@<serviceaccount-name>.iam.gserviceaccount.com
  gdrive://<service-account-url>/target-folder/?driveID=<SHARED DRIVE ID> (for GOOGLE Shared Drive)
  gdrive://<service-account-url>/target-folder/?myDriveFolderID=<google-myDrive-folder-id> (for GOOGLE MyDrive)
  hsi://{trans['user']}[:{trans['password']}]@{trans['other_host']}[:{trans['port']}]/{trans['some_dir']}
  imap://{trans['user']}[:{trans['password']}]@{trans['other_host']}[:{trans['port']}]/{trans['some_dir']}
  mega://{trans['user']}[:{trans['password']}]@{trans['other_host']}/{trans['some_dir']}
  megav2://{trans['user']}[:{trans['password']}]@{trans['other_host']}/{trans['some_dir']}
  mf://{trans['user']}[:{trans['password']}]@{trans['other_host']}/{trans['some_dir']}
  onedrive://{trans['some_dir']}
  pca://{trans['container_name']}
  pydrive://{trans['user']}@{trans['other_host']}/{trans['some_dir']}
  rclone://{trans['remote']}:/{trans['some_dir']}
  rsync://{trans['user']}[:{trans['password']}]@{trans['other_host']}[:{trans['port']}]/{trans['relative_path']}
  rsync://{trans['user']}[:{trans['password']}]@{trans['other_host']}[:{trans['port']}]//{trans['absolute_path']}
  rsync://{trans['user']}[:{trans['password']}]@{trans['other_host']}[:{trans['port']}]::/{trans['module']}/{trans['some_dir']}
  s3+http://{trans['bucket_name']}[/{trans['prefix']}]
  s3://{trans['other_host']}[:{trans['port']}]/{trans['bucket_name']}[/{trans['prefix']}]
  scp://{trans['user']}[:{trans['password']}]@{trans['other_host']}[:{trans['port']}]/{trans['some_dir']}
  ssh://{trans['user']}[:{trans['password']}]@{trans['other_host']}[:{trans['port']}]/{trans['some_dir']}
  swift://{trans['container_name']}
  tahoe://{trans['alias']}/{trans['directory']}
  webdav://{trans['user']}[:{trans['password']}]@{trans['other_host']}/{trans['some_dir']}
  webdavs://{trans['user']}[:{trans['password']}]@{trans['other_host']}/{trans['some_dir']}
"""
