diff --git a/src/borg/archiver.py b/src/borg/archiver.py index 64abf65d5..9eee69f20 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -341,7 +341,7 @@ class Archiver: if not args.path: self.print_error("input file to import key from expected") return EXIT_ERROR - if not os.path.exists(args.path): + if args.path != '-' and not os.path.exists(args.path): self.print_error("input file does not exist: " + args.path) return EXIT_ERROR manager.import_keyfile(args) @@ -2695,7 +2695,7 @@ class Archiver: subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', type=location_validator(archive=False)) subparser.add_argument('path', metavar='PATH', nargs='?', type=str, - help='path to the backup') + help='path to the backup (\'-\' to read from stdin)') subparser.add_argument('--paper', dest='paper', action='store_true', help='interactively import from a backup done with ``--paper``') diff --git a/src/borg/crypto/keymanager.py b/src/borg/crypto/keymanager.py index 5d96a1628..5f7e5978f 100644 --- a/src/borg/crypto/keymanager.py +++ b/src/borg/crypto/keymanager.py @@ -4,7 +4,7 @@ import textwrap from binascii import unhexlify, a2b_base64, b2a_base64 from hashlib import sha256 -from ..helpers import Manifest, NoManifestError, Error, yes, bin_to_hex +from ..helpers import Manifest, NoManifestError, Error, yes, bin_to_hex, open_file_or_stdin from ..repository import Repository from .key import KeyfileKey, KeyfileNotFoundError, KeyBlobStorage, identify_key @@ -130,7 +130,7 @@ class KeyManager: def import_keyfile(self, args): file_id = KeyfileKey.FILE_ID first_line = file_id + ' ' + bin_to_hex(self.repository.id) + '\n' - with open(args.path, 'r') as fd: + with open_file_or_stdin(args.path, 'r') as fd: file_first_line = fd.read(len(first_line)) if file_first_line != first_line: if not file_first_line.startswith(file_id): diff --git a/src/borg/helpers.py b/src/borg/helpers.py index 1554eefee..dfed99e35 100644 --- a/src/borg/helpers.py +++ b/src/borg/helpers.py @@ -2162,3 +2162,17 @@ def popen_with_error_handling(cmd_line: str, log_prefix='', **kwargs): except PermissionError: logger.error('%spermission denied: %s', log_prefix, command[0]) return + + +def open_file_or_stdin(path, mode): + if path == '-': + if 'b' in mode: + return sys.stdin.buffer + else: + return sys.stdin + else: + return open(path, mode) + + +def is_terminal(fd=sys.stdout): + return hasattr(fd, 'isatty') and fd.isatty() and (sys.platform != 'win32' or 'ANSICON' in os.environ) diff --git a/src/borg/nanorst.py b/src/borg/nanorst.py index ba4ad2a34..33a5e541c 100644 --- a/src/borg/nanorst.py +++ b/src/borg/nanorst.py @@ -1,7 +1,8 @@ import io -import os import sys +from .helpers import is_terminal + class TextPecker: def __init__(self, s): @@ -205,7 +206,7 @@ def rst_to_terminal(rst, references=None, destination=sys.stdout): If *destination* is a file-like object connected to a terminal, enrich text with suitable ANSI escapes. Otherwise return plain text. """ - if hasattr(destination, 'isatty') and destination.isatty() and (sys.platform != 'win32' or 'ANSICON' in os.environ): + if is_terminal(destination): rst_state_hook = ansi_escapes else: rst_state_hook = None diff --git a/src/borg/testsuite/helpers.py b/src/borg/testsuite/helpers.py index 14d4783c9..9dba4d5e0 100644 --- a/src/borg/testsuite/helpers.py +++ b/src/borg/testsuite/helpers.py @@ -28,6 +28,7 @@ from ..helpers import swidth_slice from ..helpers import chunkit from ..helpers import safe_ns, safe_s, SUPPORT_32BIT_PLATFORMS from ..helpers import popen_with_error_handling +from ..helpers import open_file_or_stdin from . import BaseTestCase, FakeInputs @@ -942,3 +943,8 @@ class TestPopenWithErrorHandling: def test_shell(self): with pytest.raises(AssertionError): popen_with_error_handling('', shell=True) + + +def test_open_file_or_stdin(): + assert open_file_or_stdin('-', 'r') is sys.stdin + assert open_file_or_stdin('-', 'rb') is sys.stdin.buffer