mirror of
https://github.com/borgbackup/borg.git
synced 2026-06-11 01:41:57 -04:00
migrate to jsonargparse
This commit is contained in:
parent
e3dcf98775
commit
2cff41d894
37 changed files with 287 additions and 267 deletions
|
|
@ -40,6 +40,7 @@ dependencies = [
|
|||
"shtab>=1.8.0",
|
||||
"backports-zstd; python_version < '3.14'", # for python < 3.14.
|
||||
"xxhash>=2.0.0",
|
||||
"jsonargparse @ git+https://github.com/omni-us/jsonargparse.git@main",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ try:
|
|||
import signal
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ..logger import create_logger, setup_logging
|
||||
|
||||
logger = create_logger()
|
||||
|
|
@ -40,6 +42,7 @@ try:
|
|||
from ..helpers import format_file_size
|
||||
from ..helpers import remove_surrogates, text_to_json
|
||||
from ..helpers import DatetimeWrapper, replace_placeholders
|
||||
from ..helpers.jap_helpers import flatten_namespace
|
||||
|
||||
from ..helpers import is_slow_msgpack, is_supported_msgpack, sysinfo
|
||||
from ..helpers import signal_handler, raising_signal_handler, SigHup, SigTerm
|
||||
|
|
@ -63,16 +66,7 @@ STATS_HEADER = " Original size Deduplicated size"
|
|||
PURE_PYTHON_MSGPACK_WARNING = "Using a pure-python msgpack! This will result in lower performance."
|
||||
|
||||
|
||||
def get_func(args):
|
||||
# This works around https://bugs.python.org/issue9351
|
||||
# func is used at the leaf parsers of the argparse parser tree,
|
||||
# fallback_func at next level towards the root,
|
||||
# fallback2_func at the 2nd next level (which is root in our case).
|
||||
for name in "func", "fallback_func", "fallback2_func":
|
||||
func = getattr(args, name, None)
|
||||
if func is not None:
|
||||
return func
|
||||
raise Exception("expected func attributes not found")
|
||||
|
||||
|
||||
|
||||
from .analyze_cmd import AnalyzeMixIn
|
||||
|
|
@ -277,7 +271,7 @@ class Archiver(
|
|||
# Note: We control all inputs.
|
||||
kwargs["help"] = kwargs["help"] % kwargs
|
||||
if not is_append:
|
||||
kwargs["default"] = self.default_sentinel
|
||||
kwargs["default"] = argparse.SUPPRESS
|
||||
|
||||
common_group.add_argument(*args, **kwargs)
|
||||
|
||||
|
|
@ -328,9 +322,8 @@ class Archiver(
|
|||
def build_parser(self):
|
||||
from ._common import define_common_options
|
||||
|
||||
parser = argparse.ArgumentParser(prog=self.prog, description="Borg - Deduplicated Backups", add_help=False)
|
||||
parser = ArgumentParser(prog=self.prog, description="Borg - Deduplicated Backups", add_help=False)
|
||||
# paths and patterns must have an empty list as default everywhere
|
||||
parser.set_defaults(fallback2_func=functools.partial(self.do_maincommand_help, parser), paths=[], patterns=[], pattern_roots=[])
|
||||
parser.common_options = self.CommonOptions(
|
||||
define_common_options, suffix_precedence=("_maincommand", "_midcommand", "_subcommand")
|
||||
)
|
||||
|
|
@ -340,18 +333,16 @@ class Archiver(
|
|||
parser.add_argument("--cockpit", dest="cockpit", action="store_true", help="Start the Borg TUI")
|
||||
parser.common_options.add_common_group(parser, "_maincommand", provide_defaults=True)
|
||||
|
||||
common_parser = argparse.ArgumentParser(add_help=False, prog=self.prog)
|
||||
common_parser.set_defaults(paths=[], patterns=[], pattern_roots=[])
|
||||
common_parser = ArgumentParser(add_help=False, prog=self.prog)
|
||||
parser.common_options.add_common_group(common_parser, "_subcommand")
|
||||
|
||||
mid_common_parser = argparse.ArgumentParser(add_help=False, prog=self.prog)
|
||||
mid_common_parser.set_defaults(paths=[], patterns=[], pattern_roots=[])
|
||||
mid_common_parser = ArgumentParser(add_help=False, prog=self.prog)
|
||||
parser.common_options.add_common_group(mid_common_parser, "_midcommand")
|
||||
|
||||
if parser.prog == "borgfs":
|
||||
return self.build_parser_borgfs(parser)
|
||||
|
||||
subparsers = parser.add_subparsers(title="required arguments", metavar="<command>")
|
||||
subparsers = parser.add_subcommands(required=False, title="required arguments", metavar="<command>")
|
||||
|
||||
self.build_parser_analyze(subparsers, common_parser, mid_common_parser)
|
||||
self.build_parser_benchmarks(subparsers, common_parser, mid_common_parser)
|
||||
|
|
@ -424,8 +415,15 @@ class Archiver(
|
|||
args = self.preprocess_args(args)
|
||||
parser = self.build_parser()
|
||||
args = parser.parse_args(args or ["-h"])
|
||||
args = flatten_namespace(args)
|
||||
|
||||
# Ensure list defaults previously handled by set_defaults are present
|
||||
for list_attr in ("paths", "patterns", "pattern_roots"):
|
||||
if getattr(args, list_attr, None) is None:
|
||||
setattr(args, list_attr, [])
|
||||
|
||||
parser.common_options.resolve(args)
|
||||
func = get_func(args)
|
||||
func = self.get_func(args, parser)
|
||||
if func == self.do_create and args.paths and args.paths_from_stdin:
|
||||
parser.error("Must not pass PATH with --paths-from-stdin.")
|
||||
if args.progress and getattr(args, "output_list", False) and not args.log_json:
|
||||
|
|
@ -451,8 +449,24 @@ class Archiver(
|
|||
if value:
|
||||
setattr(args, name, [replace_placeholders(elem) for elem in value])
|
||||
|
||||
args.func = func
|
||||
|
||||
return args
|
||||
|
||||
def get_func(self, args, parser):
|
||||
if not getattr(args, "subcommand", None):
|
||||
return functools.partial(self.do_maincommand_help, parser)
|
||||
|
||||
method_name = "do_" + args.subcommand.replace(" ", "_").replace("-", "_")
|
||||
func = getattr(self, method_name, None)
|
||||
if func is not None:
|
||||
if method_name == "do_help":
|
||||
return functools.partial(func, parser)
|
||||
return func
|
||||
|
||||
# fallback to general help for e.g., "borg key"
|
||||
return functools.partial(self.do_maincommand_help, parser)
|
||||
|
||||
def prerun_checks(self, logger, is_serve):
|
||||
|
||||
selftest(logger)
|
||||
|
|
@ -485,7 +499,7 @@ class Archiver(
|
|||
def run(self, args):
|
||||
os.umask(args.umask) # early, before opening files
|
||||
self.lock_wait = args.lock_wait
|
||||
func = get_func(args)
|
||||
func = args.func
|
||||
# do not use loggers before this!
|
||||
is_serve = func == self.do_serve
|
||||
self.log_json = args.log_json and not is_serve
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import argparse
|
||||
import functools
|
||||
import os
|
||||
import textwrap
|
||||
|
|
@ -268,6 +269,20 @@ def process_epilog(epilog):
|
|||
|
||||
|
||||
def define_exclude_and_patterns(add_option, *, tag_files=False, strip_components=False):
|
||||
add_option(
|
||||
"--pattern-roots-internal",
|
||||
dest="pattern_roots",
|
||||
action="append",
|
||||
default=[],
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
add_option(
|
||||
"--patterns-internal",
|
||||
dest="patterns",
|
||||
action="append",
|
||||
default=[],
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
add_option(
|
||||
"-e",
|
||||
"--exclude",
|
||||
|
|
@ -275,6 +290,7 @@ def define_exclude_and_patterns(add_option, *, tag_files=False, strip_components
|
|||
dest="patterns",
|
||||
type=parse_exclude_pattern,
|
||||
action="append",
|
||||
default=[],
|
||||
help="exclude paths matching PATTERN",
|
||||
)
|
||||
add_option(
|
||||
|
|
@ -372,7 +388,6 @@ def define_archive_filters_group(
|
|||
metavar="N",
|
||||
dest="first",
|
||||
type=positive_int_validator,
|
||||
default=0,
|
||||
action=Highlander,
|
||||
help="consider the first N archives after other filters are applied",
|
||||
)
|
||||
|
|
@ -381,7 +396,6 @@ def define_archive_filters_group(
|
|||
metavar="N",
|
||||
dest="last",
|
||||
type=positive_int_validator,
|
||||
default=0,
|
||||
action=Highlander,
|
||||
help="consider the last N archives after other filters are applied",
|
||||
)
|
||||
|
|
@ -508,7 +522,7 @@ def define_common_options(add_common_option):
|
|||
"--umask",
|
||||
metavar="M",
|
||||
dest="umask",
|
||||
type=lambda s: int(s, 8),
|
||||
type=lambda s: s if isinstance(s, int) else int(s, 8),
|
||||
default=UMASK_DEFAULT,
|
||||
action=Highlander,
|
||||
help="set umask to M (local only, default: %(default)04o)",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import argparse
|
|||
from collections import defaultdict
|
||||
import os
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, define_archive_filters_group
|
||||
from ..archive import Archive
|
||||
from ..constants import * # NOQA
|
||||
|
|
@ -126,14 +128,12 @@ class AnalyzeMixIn:
|
|||
to recreate existing archives without them.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"analyze",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_analyze.__doc__,
|
||||
epilog=analyze_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="analyze archives",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_analyze)
|
||||
subparsers.add_subcommand("analyze", subparser, help="analyze archives")
|
||||
define_archive_filters_group(subparser)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import argparse
|
||||
from contextlib import contextmanager
|
||||
import functools
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ..constants import * # NOQA
|
||||
from ..crypto.key import FlexiKey
|
||||
from ..helpers import format_file_size
|
||||
|
|
@ -355,18 +356,16 @@ class BenchmarkMixIn:
|
|||
|
||||
benchmark_epilog = process_epilog("These commands do various benchmarks.")
|
||||
|
||||
subparser = subparsers.add_parser(
|
||||
"benchmark",
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description="benchmark command",
|
||||
epilog=benchmark_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="benchmark command",
|
||||
)
|
||||
subparsers.add_subcommand("benchmark", subparser, help="benchmark command")
|
||||
|
||||
benchmark_parsers = subparser.add_subparsers(title="required arguments", metavar="<command>")
|
||||
subparser.set_defaults(fallback_func=functools.partial(self.do_subcommand_help, subparser))
|
||||
benchmark_parsers = subparser.add_subcommands(required=False, title="required arguments", metavar="<command>")
|
||||
|
||||
bench_crud_epilog = process_epilog(
|
||||
"""
|
||||
|
|
@ -409,16 +408,14 @@ class BenchmarkMixIn:
|
|||
Try multiple measurements and having a otherwise idle machine (and network, if you use it).
|
||||
"""
|
||||
)
|
||||
subparser = benchmark_parsers.add_parser(
|
||||
"crud",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_benchmark_crud.__doc__,
|
||||
epilog=bench_crud_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="benchmarks Borg CRUD (create, extract, update, delete).",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_benchmark_crud)
|
||||
benchmark_parsers.add_subcommand("crud", subparser, help="benchmarks Borg CRUD (create, extract, update, delete).")
|
||||
|
||||
subparser.add_argument("path", metavar="PATH", help="path where to create benchmark input data")
|
||||
subparser.add_argument("--json-lines", action="store_true", help="Format output as JSON Lines.")
|
||||
|
|
@ -434,14 +431,12 @@ class BenchmarkMixIn:
|
|||
- enough free memory so there will be no slow down due to paging activity
|
||||
"""
|
||||
)
|
||||
subparser = benchmark_parsers.add_parser(
|
||||
"cpu",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_benchmark_cpu.__doc__,
|
||||
epilog=bench_cpu_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="benchmarks Borg CPU-bound operations.",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_benchmark_cpu)
|
||||
benchmark_parsers.add_subcommand("cpu", subparser, help="benchmarks Borg CPU-bound operations.")
|
||||
subparser.add_argument("--json", action="store_true", help="format output as JSON")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import argparse
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, Highlander
|
||||
from ..archive import ArchiveChecker
|
||||
from ..constants import * # NOQA
|
||||
|
|
@ -60,7 +63,7 @@ class CheckMixIn:
|
|||
repair=args.repair,
|
||||
find_lost_archives=args.find_lost_archives,
|
||||
match=args.match_archives,
|
||||
sort_by=args.sort_by or "ts",
|
||||
sort_by=args.sort_by or "timestamp",
|
||||
first=args.first,
|
||||
last=args.last,
|
||||
older=args.older,
|
||||
|
|
@ -182,16 +185,14 @@ class CheckMixIn:
|
|||
``borg compact`` would remove the archives' data completely.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"check",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_check.__doc__,
|
||||
epilog=check_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="verify the repository",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_check)
|
||||
subparsers.add_subcommand("check", subparser, help="verify the repository")
|
||||
subparser.add_argument(
|
||||
"--repository-only", dest="repo_only", action="store_true", help="only perform repository checks"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository
|
||||
from ..archive import Archive
|
||||
from ..cache import write_chunkindex_to_repo_cache, build_chunkindex_from_repo
|
||||
|
|
@ -257,16 +259,14 @@ class CompactMixIn:
|
|||
thus it cannot compute before/after compaction size statistics).
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"compact",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_compact.__doc__,
|
||||
epilog=compact_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="compact the repository",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_compact)
|
||||
subparsers.add_subcommand("compact", subparser, help="compact the repository")
|
||||
subparser.add_argument(
|
||||
"-n", "--dry-run", dest="dry_run", action="store_true", help="do not change the repository"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ import argparse
|
|||
|
||||
import shtab
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import process_epilog
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import (
|
||||
|
|
@ -750,16 +752,14 @@ class CompletionMixIn:
|
|||
"""
|
||||
)
|
||||
|
||||
subparser = subparsers.add_parser(
|
||||
"completion",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_completion.__doc__,
|
||||
epilog=completion_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="output shell completion script",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_completion)
|
||||
subparsers.add_subcommand("completion", subparser, help="output shell completion script")
|
||||
subparser.add_argument(
|
||||
"shell", metavar="SHELL", choices=shells, help="shell to generate completion for (one of: %(choices)s)"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import subprocess
|
|||
import time
|
||||
from io import TextIOWrapper
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, Highlander
|
||||
from .. import helpers
|
||||
from ..archive import Archive, is_special
|
||||
|
|
@ -772,16 +774,14 @@ class CreateMixIn:
|
|||
"""
|
||||
)
|
||||
|
||||
subparser = subparsers.add_parser(
|
||||
"create",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_create.__doc__,
|
||||
epilog=create_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="create a backup",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_create)
|
||||
subparsers.add_subcommand("create", subparser, help="create a backup")
|
||||
|
||||
# note: --dry-run and --stats are mutually exclusive, but we do not want to abort when
|
||||
# parsing, but rather proceed with the dry-run, but without stats (see run() method).
|
||||
|
|
@ -831,7 +831,7 @@ class CreateMixIn:
|
|||
"--stdin-mode",
|
||||
metavar="M",
|
||||
dest="stdin_mode",
|
||||
type=lambda s: int(s, 8),
|
||||
type=lambda s: s if isinstance(s, int) else int(s, 8),
|
||||
default=STDIN_MODE_DEFAULT,
|
||||
action=Highlander,
|
||||
help="set mode to M in archive for stdin data (default: %(default)04o)",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import argparse
|
||||
import functools
|
||||
import json
|
||||
import textwrap
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ..archive import Archive
|
||||
from ..compress import CompressionSpec
|
||||
from ..constants import * # NOQA
|
||||
|
|
@ -319,18 +320,16 @@ class DebugMixIn:
|
|||
what you are doing or if a trusted developer tells you what to do."""
|
||||
)
|
||||
|
||||
subparser = subparsers.add_parser(
|
||||
"debug",
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description="debugging command (not intended for normal use)",
|
||||
epilog=debug_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="debugging command (not intended for normal use)",
|
||||
)
|
||||
subparsers.add_subcommand("debug", subparser, help="debugging command (not intended for normal use)")
|
||||
|
||||
debug_parsers = subparser.add_subparsers(title="required arguments", metavar="<command>")
|
||||
subparser.set_defaults(fallback_func=functools.partial(self.do_subcommand_help, subparser))
|
||||
debug_parsers = subparser.add_subcommands(required=False, title="required arguments", metavar="<command>")
|
||||
|
||||
debug_info_epilog = process_epilog(
|
||||
"""
|
||||
|
|
@ -339,32 +338,28 @@ class DebugMixIn:
|
|||
already appended at the end of the traceback.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"info",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_info.__doc__,
|
||||
epilog=debug_info_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="show system infos for debugging / bug reports (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_info)
|
||||
debug_parsers.add_subcommand("info", subparser, help="show system infos for debugging / bug reports (debug)")
|
||||
|
||||
debug_dump_archive_items_epilog = process_epilog(
|
||||
"""
|
||||
This command dumps raw (but decrypted and decompressed) archive items (only metadata) to files.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"dump-archive-items",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_dump_archive_items.__doc__,
|
||||
epilog=debug_dump_archive_items_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="dump archive items (metadata) (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_dump_archive_items)
|
||||
debug_parsers.add_subcommand("dump-archive-items", subparser, help="dump archive items (metadata) (debug)")
|
||||
subparser.add_argument("name", metavar="NAME", type=archivename_validator, help="specify the archive name")
|
||||
|
||||
debug_dump_archive_epilog = process_epilog(
|
||||
|
|
@ -372,16 +367,14 @@ class DebugMixIn:
|
|||
This command dumps all metadata of an archive in a decoded form to a file.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"dump-archive",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_dump_archive.__doc__,
|
||||
epilog=debug_dump_archive_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="dump decoded archive metadata (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_dump_archive)
|
||||
debug_parsers.add_subcommand("dump-archive", subparser, help="dump decoded archive metadata (debug)")
|
||||
subparser.add_argument("name", metavar="NAME", type=archivename_validator, help="specify the archive name")
|
||||
subparser.add_argument("path", metavar="PATH", type=str, help="file to dump data into")
|
||||
|
||||
|
|
@ -390,16 +383,14 @@ class DebugMixIn:
|
|||
This command dumps manifest metadata of a repository in a decoded form to a file.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"dump-manifest",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_dump_manifest.__doc__,
|
||||
epilog=debug_dump_manifest_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="dump decoded repository metadata (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_dump_manifest)
|
||||
debug_parsers.add_subcommand("dump-manifest", subparser, help="dump decoded repository metadata (debug)")
|
||||
subparser.add_argument("path", metavar="PATH", type=str, help="file to dump data into")
|
||||
|
||||
debug_dump_repo_objs_epilog = process_epilog(
|
||||
|
|
@ -407,32 +398,28 @@ class DebugMixIn:
|
|||
This command dumps raw (but decrypted and decompressed) repo objects to files.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"dump-repo-objs",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_dump_repo_objs.__doc__,
|
||||
epilog=debug_dump_repo_objs_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="dump repo objects (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_dump_repo_objs)
|
||||
debug_parsers.add_subcommand("dump-repo-objs", subparser, help="dump repo objects (debug)")
|
||||
|
||||
debug_search_repo_objs_epilog = process_epilog(
|
||||
"""
|
||||
This command searches raw (but decrypted and decompressed) repo objects for a specific bytes sequence.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"search-repo-objs",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_search_repo_objs.__doc__,
|
||||
epilog=debug_search_repo_objs_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="search repo objects (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_search_repo_objs)
|
||||
debug_parsers.add_subcommand("search-repo-objs", subparser, help="search repo objects (debug)")
|
||||
subparser.add_argument(
|
||||
"wanted",
|
||||
metavar="WANTED",
|
||||
|
|
@ -445,16 +432,14 @@ class DebugMixIn:
|
|||
This command computes the id-hash for some file content.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"id-hash",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_id_hash.__doc__,
|
||||
epilog=debug_id_hash_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="compute id-hash for some file content (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_id_hash)
|
||||
debug_parsers.add_subcommand("id-hash", subparser, help="compute id-hash for some file content (debug)")
|
||||
subparser.add_argument(
|
||||
"path", metavar="PATH", type=str, help="content for which the id-hash shall get computed"
|
||||
)
|
||||
|
|
@ -465,16 +450,14 @@ class DebugMixIn:
|
|||
This command parses the object file into metadata (as json) and uncompressed data.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"parse-obj",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_parse_obj.__doc__,
|
||||
epilog=debug_parse_obj_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="parse borg object file into meta dict and data",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_parse_obj)
|
||||
debug_parsers.add_subcommand("parse-obj", subparser, help="parse borg object file into meta dict and data")
|
||||
subparser.add_argument("id", metavar="ID", type=str, help="hex object ID to get from the repo")
|
||||
subparser.add_argument(
|
||||
"object_path", metavar="OBJECT_PATH", type=str, help="path of the object file to parse data from"
|
||||
|
|
@ -492,16 +475,14 @@ class DebugMixIn:
|
|||
This command formats the file and metadata into a Borg object file.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"format-obj",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_format_obj.__doc__,
|
||||
epilog=debug_format_obj_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="format file and metadata into a Borg object file",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_format_obj)
|
||||
debug_parsers.add_subcommand("format-obj", subparser, help="format file and metadata into a Borg object file")
|
||||
subparser.add_argument("id", metavar="ID", type=str, help="hex object ID to get from the repo")
|
||||
subparser.add_argument(
|
||||
"binary_path", metavar="BINARY_PATH", type=str, help="path of the file to convert into an object file"
|
||||
|
|
@ -531,16 +512,14 @@ class DebugMixIn:
|
|||
This command gets an object from the repository.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"get-obj",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_get_obj.__doc__,
|
||||
epilog=debug_get_obj_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="get object from repository (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_get_obj)
|
||||
debug_parsers.add_subcommand("get-obj", subparser, help="get object from repository (debug)")
|
||||
subparser.add_argument("id", metavar="ID", type=str, help="hex object ID to get from the repo")
|
||||
subparser.add_argument("path", metavar="PATH", type=str, help="file to write object data into")
|
||||
|
||||
|
|
@ -549,16 +528,14 @@ class DebugMixIn:
|
|||
This command puts an object into the repository.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"put-obj",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_put_obj.__doc__,
|
||||
epilog=debug_put_obj_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="put object to repository (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_put_obj)
|
||||
debug_parsers.add_subcommand("put-obj", subparser, help="put object to repository (debug)")
|
||||
subparser.add_argument("id", metavar="ID", type=str, help="hex object ID to put into the repo")
|
||||
subparser.add_argument("path", metavar="PATH", type=str, help="file to read and create object from")
|
||||
|
||||
|
|
@ -567,16 +544,14 @@ class DebugMixIn:
|
|||
This command deletes objects from the repository.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"delete-obj",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_delete_obj.__doc__,
|
||||
epilog=debug_delete_obj_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="delete object from repository (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_delete_obj)
|
||||
debug_parsers.add_subcommand("delete-obj", subparser, help="delete object from repository (debug)")
|
||||
subparser.add_argument(
|
||||
"ids", metavar="IDs", nargs="+", type=str, help="hex object ID(s) to delete from the repo"
|
||||
)
|
||||
|
|
@ -586,15 +561,13 @@ class DebugMixIn:
|
|||
Convert a Borg profile to a Python cProfile compatible profile.
|
||||
"""
|
||||
)
|
||||
subparser = debug_parsers.add_parser(
|
||||
"convert-profile",
|
||||
parents=[common_parser],
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description=self.do_debug_convert_profile.__doc__,
|
||||
epilog=debug_convert_profile_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="convert Borg profile to Python profile (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_convert_profile)
|
||||
debug_parsers.add_subcommand("convert-profile", subparser, help="convert Borg profile to Python profile (debug)")
|
||||
subparser.add_argument("input", metavar="INPUT", type=str, help="Borg profile")
|
||||
subparser.add_argument("output", metavar="OUTPUT", type=str, help="Output file")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import argparse
|
||||
import logging
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import format_archive, CommandError, bin_to_hex, archivename_validator
|
||||
|
|
@ -80,16 +82,14 @@ class DeleteMixIn:
|
|||
patterns, see :ref:`borg_patterns`).
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"delete",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_delete.__doc__,
|
||||
epilog=delete_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="delete archives",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_delete)
|
||||
subparsers.add_subcommand("delete", subparser, help="delete archives")
|
||||
subparser.add_argument(
|
||||
"-n", "--dry-run", dest="dry_run", action="store_true", help="do not change the repository"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import json
|
|||
import sys
|
||||
import os
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, build_matcher, Highlander
|
||||
from ..archive import Archive
|
||||
from ..constants import * # NOQA
|
||||
|
|
@ -294,16 +296,14 @@ class DiffMixIn:
|
|||
raise argparse.ArgumentTypeError(f"unsupported sort field: {field}")
|
||||
return ",".join(parts)
|
||||
|
||||
subparser = subparsers.add_parser(
|
||||
"diff",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_diff.__doc__,
|
||||
epilog=diff_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="find differences in archive contents",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_diff)
|
||||
subparsers.add_subcommand("diff", subparser, help="find differences in archive contents")
|
||||
subparser.add_argument(
|
||||
"--numeric-ids",
|
||||
dest="numeric_ids",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import argparse
|
|||
import logging
|
||||
import stat
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, with_archive
|
||||
from ._common import build_filter, build_matcher
|
||||
from ..archive import BackupError
|
||||
|
|
@ -155,16 +157,14 @@ class ExtractMixIn:
|
|||
group, permissions, etc.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"extract",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_extract.__doc__,
|
||||
epilog=extract_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="extract archive contents",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_extract)
|
||||
subparsers.add_subcommand("extract", subparser, help="extract archive contents")
|
||||
subparser.add_argument(
|
||||
"--list", dest="output_list", action="store_true", help="output a verbose list of items (files, dirs, ...)"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import collections
|
||||
import functools
|
||||
import textwrap
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers.nanorst import rst_to_terminal
|
||||
|
||||
|
|
@ -523,7 +524,10 @@ class HelpMixIn:
|
|||
borg create --compression obfuscate,250,zstd,3 ...\n\n"""
|
||||
)
|
||||
|
||||
def do_help(self, parser, commands, args):
|
||||
def do_help(self, parser, args):
|
||||
commands = getattr(parser, "_subcommands_action", None)
|
||||
commands = commands._name_parser_map if commands else {}
|
||||
|
||||
if not args.topic:
|
||||
parser.print_help()
|
||||
elif args.topic in self.helptext:
|
||||
|
|
@ -551,10 +555,12 @@ class HelpMixIn:
|
|||
do_maincommand_help = do_subcommand_help
|
||||
|
||||
def build_parser_help(self, subparsers, common_parser, mid_common_parser, parser):
|
||||
subparser = subparsers.add_parser(
|
||||
"help", parents=[common_parser], add_help=False, description="Extra help", help="Extra help"
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description="Extra help",
|
||||
)
|
||||
subparsers.add_subcommand("help", subparser, help="Extra help")
|
||||
subparser.add_argument("--epilog-only", dest="epilog_only", action="store_true")
|
||||
subparser.add_argument("--usage-only", dest="usage_only", action="store_true")
|
||||
subparser.set_defaults(func=functools.partial(self.do_help, parser, subparsers.choices))
|
||||
subparser.add_argument("topic", metavar="TOPIC", type=str, nargs="?", help="additional help on TOPIC")
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import argparse
|
|||
import textwrap
|
||||
from datetime import timedelta
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository
|
||||
from ..archive import Archive
|
||||
from ..constants import * # NOQA
|
||||
|
|
@ -78,16 +80,14 @@ class InfoMixIn:
|
|||
= all chunks in the repository.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"info",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_info.__doc__,
|
||||
epilog=info_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="show repository or archive information",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_info)
|
||||
subparsers.add_subcommand("info", subparser, help="show repository or archive information")
|
||||
subparser.add_argument("--json", action="store_true", help="format output as JSON")
|
||||
define_archive_filters_group(subparser)
|
||||
subparser.add_argument(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import argparse
|
||||
import functools
|
||||
import os
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ..constants import * # NOQA
|
||||
from ..crypto.key import AESOCBRepoKey, CHPORepoKey, Blake2AESOCBRepoKey, Blake2CHPORepoKey
|
||||
from ..crypto.key import AESOCBKeyfileKey, CHPOKeyfileKey, Blake2AESOCBKeyfileKey, Blake2CHPOKeyfileKey
|
||||
|
|
@ -120,18 +121,16 @@ class KeysMixIn:
|
|||
def build_parser_keys(self, subparsers, common_parser, mid_common_parser):
|
||||
from ._common import process_epilog
|
||||
|
||||
subparser = subparsers.add_parser(
|
||||
"key",
|
||||
subparser = ArgumentParser(
|
||||
parents=[mid_common_parser],
|
||||
add_help=False,
|
||||
description="Manage the keyfile or repokey of a repository",
|
||||
epilog="",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="manage the repository key",
|
||||
)
|
||||
subparsers.add_subcommand("key", subparser, help="manage the repository key")
|
||||
|
||||
key_parsers = subparser.add_subparsers(title="required arguments", metavar="<command>")
|
||||
subparser.set_defaults(fallback_func=functools.partial(self.do_subcommand_help, subparser))
|
||||
key_parsers = subparser.add_subcommands(required=False, title="required arguments", metavar="<command>")
|
||||
|
||||
key_export_epilog = process_epilog(
|
||||
"""
|
||||
|
|
@ -164,16 +163,14 @@ class KeysMixIn:
|
|||
HTML template with a QR code and a copy of the ``--paper``-formatted key.
|
||||
"""
|
||||
)
|
||||
subparser = key_parsers.add_parser(
|
||||
"export",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_key_export.__doc__,
|
||||
epilog=key_export_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="export the repository key for backup",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_key_export)
|
||||
key_parsers.add_subcommand("export", subparser, help="export the repository key for backup")
|
||||
subparser.add_argument("path", metavar="PATH", nargs="?", type=PathSpec, help="where to store the backup")
|
||||
subparser.add_argument(
|
||||
"--paper",
|
||||
|
|
@ -206,16 +203,14 @@ class KeysMixIn:
|
|||
key import`` creates a new key file in ``$BORG_KEYS_DIR``.
|
||||
"""
|
||||
)
|
||||
subparser = key_parsers.add_parser(
|
||||
"import",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_key_import.__doc__,
|
||||
epilog=key_import_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="import the repository key from backup",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_key_import)
|
||||
key_parsers.add_subcommand("import", subparser, help="import the repository key from backup")
|
||||
subparser.add_argument(
|
||||
"path", metavar="PATH", nargs="?", type=PathSpec, help="path to the backup ('-' to read from stdin)"
|
||||
)
|
||||
|
|
@ -237,16 +232,14 @@ class KeysMixIn:
|
|||
does not protect future (nor past) backups to the same repository.
|
||||
"""
|
||||
)
|
||||
subparser = key_parsers.add_parser(
|
||||
"change-passphrase",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_key_change_passphrase.__doc__,
|
||||
epilog=change_passphrase_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="change the repository passphrase",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_key_change_passphrase)
|
||||
key_parsers.add_subcommand("change-passphrase", subparser, help="change the repository passphrase")
|
||||
|
||||
change_location_epilog = process_epilog(
|
||||
"""
|
||||
|
|
@ -261,16 +254,14 @@ class KeysMixIn:
|
|||
thus you must ONLY give the key location (keyfile or repokey).
|
||||
"""
|
||||
)
|
||||
subparser = key_parsers.add_parser(
|
||||
"change-location",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_key_change_location.__doc__,
|
||||
epilog=change_location_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="change the key location",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_key_change_location)
|
||||
key_parsers.add_subcommand("change-location", subparser, help="change the key location")
|
||||
subparser.add_argument(
|
||||
"key_mode", metavar="KEY_LOCATION", choices=("repokey", "keyfile"), help="select key location"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import os
|
|||
import textwrap
|
||||
import sys
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, build_matcher, Highlander
|
||||
from ..archive import Archive
|
||||
from ..cache import Cache
|
||||
|
|
@ -103,16 +105,14 @@ class ListMixIn:
|
|||
)
|
||||
+ ItemFormatter.keys_help()
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"list",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_list.__doc__,
|
||||
epilog=list_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="list archive contents",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_list)
|
||||
subparsers.add_subcommand("list", subparser, help="list archive contents")
|
||||
subparser.add_argument(
|
||||
"--short", dest="short", action="store_true", help="only print file/directory names, nothing else"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import argparse
|
||||
import subprocess
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository
|
||||
from ..cache import Cache
|
||||
from ..constants import * # NOQA
|
||||
|
|
@ -45,16 +47,14 @@ class LocksMixIn:
|
|||
trying to access the cache or the repository.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"break-lock",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_break_lock.__doc__,
|
||||
epilog=break_lock_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="break the repository and cache locks",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_break_lock)
|
||||
subparsers.add_subcommand("break-lock", subparser, help="break the repository and cache locks")
|
||||
|
||||
with_lock_epilog = process_epilog(
|
||||
"""
|
||||
|
|
@ -77,15 +77,13 @@ class LocksMixIn:
|
|||
Borg is cautious and does not automatically remove stale locks made by a different host.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"with-lock",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_with_lock.__doc__,
|
||||
epilog=with_lock_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="run a user command with the lock held",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_with_lock)
|
||||
subparsers.add_subcommand("with-lock", subparser, help="run a user command with the lock held")
|
||||
subparser.add_argument("command", metavar="COMMAND", help="command to run")
|
||||
subparser.add_argument("args", metavar="ARGS", nargs=argparse.REMAINDER, help="command arguments")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import argparse
|
||||
import os
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, Highlander
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import RTError
|
||||
|
|
@ -151,15 +153,14 @@ class MountMixIn:
|
|||
the logger to output to a file.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"mount",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_mount.__doc__,
|
||||
epilog=mount_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="mount a repository",
|
||||
)
|
||||
subparsers.add_subcommand("mount", subparser, help="mount a repository")
|
||||
self._define_borg_mount(subparser)
|
||||
|
||||
umount_epilog = process_epilog(
|
||||
|
|
@ -170,16 +171,14 @@ class MountMixIn:
|
|||
command - usually this is either umount or fusermount -u.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"umount",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_umount.__doc__,
|
||||
epilog=umount_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="unmount a repository",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_umount)
|
||||
subparsers.add_subcommand("umount", subparser, help="unmount a repository")
|
||||
subparser.add_argument(
|
||||
"mountpoint", metavar="MOUNTPOINT", type=str, help="mountpoint of the filesystem to unmount"
|
||||
)
|
||||
|
|
@ -196,7 +195,6 @@ class MountMixIn:
|
|||
def _define_borg_mount(self, parser):
|
||||
from ._common import define_exclusion_group, define_archive_filters_group
|
||||
|
||||
parser.set_defaults(func=self.do_mount)
|
||||
parser.add_argument("mountpoint", metavar="MOUNTPOINT", type=str, help="where to mount the filesystem")
|
||||
parser.add_argument(
|
||||
"-f", "--foreground", dest="foreground", action="store_true", help="stay in foreground, do not daemonize"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import logging
|
|||
from operator import attrgetter
|
||||
import os
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, Highlander
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import ArchiveFormatter, interval, sig_int, ProgressIndicatorPercent, CommandError, Error
|
||||
|
|
@ -273,16 +275,14 @@ class PruneMixIn:
|
|||
the ``borg repo-list`` description for more details about the format string).
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"prune",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_prune.__doc__,
|
||||
epilog=prune_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="prune archives",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_prune)
|
||||
subparsers.add_subcommand("prune", subparser, help="prune archives")
|
||||
subparser.add_argument(
|
||||
"-n", "--dry-run", dest="dry_run", action="store_true", help="do not change the repository"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import argparse
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, Highlander
|
||||
from ._common import build_matcher
|
||||
from ..archive import ArchiveRecreater
|
||||
|
|
@ -102,16 +104,14 @@ class RecreateMixIn:
|
|||
if the chunks are still missing.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"recreate",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_recreate.__doc__,
|
||||
epilog=recreate_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help=self.do_recreate.__doc__,
|
||||
)
|
||||
subparser.set_defaults(func=self.do_recreate)
|
||||
subparsers.add_subcommand("recreate", subparser, help=self.do_recreate.__doc__)
|
||||
subparser.add_argument(
|
||||
"--list", dest="output_list", action="store_true", help="output verbose list of items (files, dirs, ...)"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import argparse
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, with_archive
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import archivename_validator
|
||||
|
|
@ -28,16 +30,14 @@ class RenameMixIn:
|
|||
This results in a different archive ID.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"rename",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_rename.__doc__,
|
||||
epilog=rename_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="rename an archive",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_rename)
|
||||
subparsers.add_subcommand("rename", subparser, help="rename an archive")
|
||||
subparser.add_argument(
|
||||
"name", metavar="OLDNAME", type=archivename_validator, help="specify the current archive name"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import argparse
|
||||
from collections import defaultdict
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, Highlander
|
||||
from ..constants import * # NOQA
|
||||
from ..compress import CompressionSpec, ObfuscateSize, Auto, COMPRESSOR_TABLE
|
||||
|
|
@ -180,16 +182,14 @@ class RepoCompressMixIn:
|
|||
You do **not** need to run ``borg compact`` after ``borg repo-compress``.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"repo-compress",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_repo_compress.__doc__,
|
||||
epilog=repo_compress_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help=self.do_repo_compress.__doc__,
|
||||
)
|
||||
subparser.set_defaults(func=self.do_repo_compress)
|
||||
subparsers.add_subcommand("repo-compress", subparser, help=self.do_repo_compress.__doc__)
|
||||
|
||||
subparser.add_argument(
|
||||
"-C",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import argparse
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, with_other_repository, Highlander
|
||||
from ..cache import Cache
|
||||
from ..constants import * # NOQA
|
||||
|
|
@ -190,16 +192,14 @@ class RepoCreateMixIn:
|
|||
Then use ``borg transfer --other-repo ORIG_REPO --from-borg1 ...`` to transfer the archives.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"repo-create",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_repo_create.__doc__,
|
||||
epilog=repo_create_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="create a new, empty repository",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_repo_create)
|
||||
subparsers.add_subcommand("repo-create", subparser, help="create a new, empty repository")
|
||||
subparser.add_argument(
|
||||
"--other-repo",
|
||||
metavar="SRC_REPOSITORY",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import argparse
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository
|
||||
from ..cache import Cache, SecurityManager
|
||||
from ..constants import * # NOQA
|
||||
|
|
@ -102,16 +104,14 @@ class RepoDeleteMixIn:
|
|||
Always first use ``--dry-run --list`` to see what would be deleted.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"repo-delete",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_repo_delete.__doc__,
|
||||
epilog=repo_delete_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="delete a repository",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_repo_delete)
|
||||
subparsers.add_subcommand("repo-delete", subparser, help="delete a repository")
|
||||
subparser.add_argument(
|
||||
"-n", "--dry-run", dest="dry_run", action="store_true", help="do not change the repository"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import argparse
|
||||
import textwrap
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import bin_to_hex, json_print, basic_json_data
|
||||
|
|
@ -63,14 +65,12 @@ class RepoInfoMixIn:
|
|||
This command displays detailed information about the repository.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"repo-info",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_repo_info.__doc__,
|
||||
epilog=repo_info_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="show repository information",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_repo_info)
|
||||
subparsers.add_subcommand("repo-info", subparser, help="show repository information")
|
||||
subparser.add_argument("--json", action="store_true", help="format output as JSON")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import os
|
|||
import textwrap
|
||||
import sys
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, Highlander
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import BaseFormatter, ArchiveFormatter, json_print, basic_json_data
|
||||
|
|
@ -85,16 +87,14 @@ class RepoListMixIn:
|
|||
)
|
||||
+ ArchiveFormatter.keys_help()
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"repo-list",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_repo_list.__doc__,
|
||||
epilog=repo_list_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="list repository contents",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_repo_list)
|
||||
subparsers.add_subcommand("repo-list", subparser, help="list repository contents")
|
||||
subparser.add_argument(
|
||||
"--short", dest="short", action="store_true", help="only print the archive IDs, nothing else"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import argparse
|
|||
import math
|
||||
import os
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from borgstore.store import ItemInfo
|
||||
|
||||
from ._common import with_repository, Highlander
|
||||
|
|
@ -86,16 +88,14 @@ class RepoSpaceMixIn:
|
|||
Reserved space is always rounded up to full reservation blocks of 64 MiB.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"repo-space",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_repo_space.__doc__,
|
||||
epilog=repo_space_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="manage reserved space in a repository",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_repo_space)
|
||||
subparsers.add_subcommand("repo-space", subparser, help="manage reserved space in a repository")
|
||||
subparser.add_argument(
|
||||
"--reserve",
|
||||
metavar="SPACE",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class ServeMixIn:
|
|||
).serve()
|
||||
|
||||
def build_parser_serve(self, subparsers, common_parser, mid_common_parser):
|
||||
from jsonargparse import ArgumentParser
|
||||
from ._common import process_epilog
|
||||
|
||||
serve_epilog = process_epilog(
|
||||
|
|
@ -52,16 +53,14 @@ class ServeMixIn:
|
|||
Existing archives can be read, but no archives can be created or deleted.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"serve",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_serve.__doc__,
|
||||
epilog=serve_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="start the repository server process",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_serve)
|
||||
subparsers.add_subcommand("serve", subparser, help="start the repository server process")
|
||||
subparser.add_argument(
|
||||
"--restrict-to-path",
|
||||
metavar="PATH",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import argparse
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, define_archive_filters_group
|
||||
from ..archive import Archive
|
||||
from ..constants import * # NOQA
|
||||
|
|
@ -80,39 +82,37 @@ class TagMixIn:
|
|||
removed).
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"tag",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_tag.__doc__,
|
||||
epilog=tag_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="tag archives",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_tag)
|
||||
subparsers.add_subcommand("tag", subparser, help="tag archives")
|
||||
subparser.add_argument(
|
||||
"--set",
|
||||
dest="set_tags",
|
||||
metavar="TAG",
|
||||
type=tag_validator,
|
||||
action="append",
|
||||
help="set tags (can be given multiple times)",
|
||||
nargs="+",
|
||||
help="set tags",
|
||||
)
|
||||
subparser.add_argument(
|
||||
"--add",
|
||||
dest="add_tags",
|
||||
metavar="TAG",
|
||||
type=tag_validator,
|
||||
action="append",
|
||||
help="add tags (can be given multiple times)",
|
||||
nargs="+",
|
||||
help="add tags",
|
||||
)
|
||||
subparser.add_argument(
|
||||
"--remove",
|
||||
dest="remove_tags",
|
||||
metavar="TAG",
|
||||
type=tag_validator,
|
||||
action="append",
|
||||
help="remove tags (can be given multiple times)",
|
||||
nargs="+",
|
||||
help="remove tags",
|
||||
)
|
||||
define_archive_filters_group(subparser)
|
||||
subparser.add_argument(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import os
|
|||
import stat
|
||||
import tarfile
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ..archive import Archive, TarfileObjectProcessors, ChunksProcessor
|
||||
from ..compress import CompressionSpec
|
||||
from ..constants import * # NOQA
|
||||
|
|
@ -384,16 +386,14 @@ class TarMixIn:
|
|||
pass over the archive metadata.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"export-tar",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_export_tar.__doc__,
|
||||
epilog=export_tar_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="create tarball from archive",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_export_tar)
|
||||
subparsers.add_subcommand("export-tar", subparser, help="create tarball from archive")
|
||||
subparser.add_argument(
|
||||
"--tar-filter",
|
||||
dest="tar_filter",
|
||||
|
|
@ -460,16 +460,14 @@ class TarMixIn:
|
|||
``--ignore-zeros`` option to skip through the stop markers between them.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"import-tar",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_import_tar.__doc__,
|
||||
epilog=import_tar_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help=self.do_import_tar.__doc__,
|
||||
)
|
||||
subparser.set_defaults(func=self.do_import_tar)
|
||||
subparsers.add_subcommand("import-tar", subparser, help=self.do_import_tar.__doc__)
|
||||
subparser.add_argument(
|
||||
"--tar-filter",
|
||||
dest="tar_filter",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import argparse
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository, with_other_repository, Highlander
|
||||
from ..archive import Archive, cached_hash, DownloadPipeline
|
||||
from ..chunkers import get_chunker
|
||||
|
|
@ -333,16 +335,14 @@ class TransferMixIn:
|
|||
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"transfer",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_transfer.__doc__,
|
||||
epilog=transfer_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="transfer of archives from another repository",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_transfer)
|
||||
subparsers.add_subcommand("transfer", subparser, help="transfer of archives from another repository")
|
||||
subparser.add_argument(
|
||||
"-n", "--dry-run", dest="dry_run", action="store_true", help="do not change repository, just check"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import argparse
|
||||
import logging
|
||||
|
||||
from jsonargparse import ArgumentParser
|
||||
|
||||
from ._common import with_repository
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import format_archive, CommandError, bin_to_hex, archivename_validator
|
||||
|
|
@ -72,16 +74,14 @@ class UnDeleteMixIn:
|
|||
patterns, see :ref:`borg_patterns`).
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"undelete",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_undelete.__doc__,
|
||||
epilog=undelete_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="undelete archives",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_undelete)
|
||||
subparsers.add_subcommand("undelete", subparser, help="undelete archives")
|
||||
subparser.add_argument(
|
||||
"-n", "--dry-run", dest="dry_run", action="store_true", help="do not change the repository"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class VersionMixIn:
|
|||
print(f"{format_version(client_version)} / {format_version(server_version)}")
|
||||
|
||||
def build_parser_version(self, subparsers, common_parser, mid_common_parser):
|
||||
from jsonargparse import ArgumentParser
|
||||
from ._common import process_epilog
|
||||
|
||||
version_epilog = process_epilog(
|
||||
|
|
@ -51,13 +52,11 @@ class VersionMixIn:
|
|||
You can also use ``borg --version`` to display a potentially more precise client version.
|
||||
"""
|
||||
)
|
||||
subparser = subparsers.add_parser(
|
||||
"version",
|
||||
subparser = ArgumentParser(
|
||||
parents=[common_parser],
|
||||
add_help=False,
|
||||
description=self.do_version.__doc__,
|
||||
epilog=version_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
help="display the Borg client and server versions",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_version)
|
||||
subparsers.add_subcommand("version", subparser, help="display the Borg client and server versions")
|
||||
|
|
|
|||
33
src/borg/helpers/jap_helpers.py
Normal file
33
src/borg/helpers/jap_helpers.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import argparse
|
||||
from typing import Any
|
||||
|
||||
|
||||
def flatten_namespace(ns: Any) -> argparse.Namespace:
|
||||
"""
|
||||
Recursively flattens a nested namespace into a single-level namespace.
|
||||
JSONArgparse uses nested namespaces for subcommands, whereas borg's
|
||||
internal dispatch and logic expect a flat namespace.
|
||||
"""
|
||||
flat = argparse.Namespace()
|
||||
|
||||
# Extract the nested path of subcommands
|
||||
subcmds = []
|
||||
current = ns
|
||||
while current and hasattr(current, "subcommand") and current.subcommand:
|
||||
subcmds.append(current.subcommand)
|
||||
current = getattr(current, current.subcommand, None)
|
||||
|
||||
if subcmds:
|
||||
flat.subcommand = " ".join(subcmds)
|
||||
|
||||
def _flatten(source, target):
|
||||
items = vars(source).items() if hasattr(source, '__dict__') else source.items() if hasattr(source, 'items') else []
|
||||
for k, v in items:
|
||||
if isinstance(v, argparse.Namespace) or type(v).__name__ == 'Namespace':
|
||||
_flatten(v, target)
|
||||
else:
|
||||
if k != "subcommand" and not hasattr(target, k):
|
||||
setattr(target, k, v)
|
||||
|
||||
_flatten(ns, flat)
|
||||
return flat
|
||||
|
|
@ -121,7 +121,7 @@ def decode_dict(d, keys, encoding="utf-8", errors="surrogateescape"):
|
|||
|
||||
|
||||
def positive_int_validator(value):
|
||||
"""argparse type for positive integers."""
|
||||
"""argparse type for positive integers, N > 0."""
|
||||
int_value = int(value)
|
||||
if int_value <= 0:
|
||||
raise argparse.ArgumentTypeError("A positive integer is required: %s" % value)
|
||||
|
|
@ -352,7 +352,7 @@ def SortBySpec(text):
|
|||
from ..manifest import AI_HUMAN_SORT_KEYS
|
||||
|
||||
for token in text.split(","):
|
||||
if token not in AI_HUMAN_SORT_KEYS:
|
||||
if token not in AI_HUMAN_SORT_KEYS and token != "ts": # idempotency: do not reject ts
|
||||
raise argparse.ArgumentTypeError("Invalid sort key: %s" % token)
|
||||
return text.replace("timestamp", "ts").replace("archive", "name")
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ def test_tag_set(archivers, request):
|
|||
assert "tags: aa." in output
|
||||
output = cmd(archiver, "tag", "-a", "archive", "--set", "bb")
|
||||
assert "tags: bb." in output
|
||||
output = cmd(archiver, "tag", "-a", "archive", "--set", "bb", "--set", "aa")
|
||||
output = cmd(archiver, "tag", "-a", "archive", "--set", "bb", "aa")
|
||||
assert "tags: aa,bb." in output # sorted!
|
||||
output = cmd(archiver, "tag", "-a", "archive", "--set", "")
|
||||
assert "tags: ." in output # no tags!
|
||||
|
|
@ -46,7 +46,7 @@ def test_tag_set_noclobber_special(archivers, request):
|
|||
output = cmd(archiver, "tag", "-a", "archive", "--set", "clobber")
|
||||
assert "tags: @PROT." in output
|
||||
# it is possible though to use --set if the existing special tags are also given:
|
||||
output = cmd(archiver, "tag", "-a", "archive", "--set", "noclobber", "--set", "@PROT")
|
||||
output = cmd(archiver, "tag", "-a", "archive", "--set", "noclobber", "@PROT")
|
||||
assert "tags: @PROT,noclobber." in output
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue