More work on ssh copy
This commit is contained in:
parent
fadae42715
commit
4b6bfaffba
@ -5,7 +5,9 @@
|
|||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
from typing import Iterable, Iterator, List, Optional, Sequence, Tuple
|
from typing import (
|
||||||
|
Iterable, Iterator, List, NamedTuple, Optional, Sequence, Tuple
|
||||||
|
)
|
||||||
|
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
from kitty.cli_stub import CopyCLIOptions
|
from kitty.cli_stub import CopyCLIOptions
|
||||||
@ -24,8 +26,15 @@ Interpret file arguments as glob patterns.
|
|||||||
The destination on the remote computer to copy to. Relative paths are resolved
|
The destination on the remote computer to copy to. Relative paths are resolved
|
||||||
relative to HOME on the remote machine. When this option is not specified, the
|
relative to HOME on the remote machine. When this option is not specified, the
|
||||||
local file path is used as the remote destination (with the HOME directory
|
local file path is used as the remote destination (with the HOME directory
|
||||||
getting automatically replaced by the remote HOME). Note that enviroment
|
getting automatically replaced by the remote HOME). Note that environment
|
||||||
variables and ~ are not expanded.
|
variables and ~ are not expanded.
|
||||||
|
|
||||||
|
|
||||||
|
--exclude
|
||||||
|
type=list
|
||||||
|
A glob pattern. Files whose names would match this pattern after transfer
|
||||||
|
are excluded from being transferred. Useful when adding directories. Can
|
||||||
|
be specified multiple times, if any of the patterns match the file will be excluded.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@ -58,7 +67,23 @@ class CopyCLIError(ValueError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def parse_copy_instructions(val: str) -> Iterable[Tuple[str, CopyCLIOptions]]:
|
def get_arcname(loc: str, dest: Optional[str], home: str) -> str:
|
||||||
|
if dest:
|
||||||
|
arcname = dest
|
||||||
|
else:
|
||||||
|
arcname = os.path.normpath(loc)
|
||||||
|
if arcname.startswith(home):
|
||||||
|
arcname = os.path.relpath(arcname, home)
|
||||||
|
arcname = os.path.normpath(arcname).replace(os.sep, '/')
|
||||||
|
return arcname
|
||||||
|
|
||||||
|
|
||||||
|
class CopyInstruction(NamedTuple):
|
||||||
|
arcname: str
|
||||||
|
exclude_patterns: Tuple[str, ...]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_copy_instructions(val: str) -> Iterable[Tuple[str, CopyInstruction]]:
|
||||||
opts, args = parse_copy_args(shlex.split(val))
|
opts, args = parse_copy_args(shlex.split(val))
|
||||||
locations: List[str] = []
|
locations: List[str] = []
|
||||||
for a in args:
|
for a in args:
|
||||||
@ -67,5 +92,7 @@ def parse_copy_instructions(val: str) -> Iterable[Tuple[str, CopyCLIOptions]]:
|
|||||||
raise CopyCLIError('No files to copy specified')
|
raise CopyCLIError('No files to copy specified')
|
||||||
if len(locations) > 1 and opts.dest:
|
if len(locations) > 1 and opts.dest:
|
||||||
raise CopyCLIError('Specifying a remote location with more than one file is not supported')
|
raise CopyCLIError('Specifying a remote location with more than one file is not supported')
|
||||||
|
home = os.path.expanduser('~')
|
||||||
for loc in locations:
|
for loc in locations:
|
||||||
yield loc, opts
|
arcname = get_arcname(loc, opts.dest, home)
|
||||||
|
yield loc, CopyInstruction(arcname, tuple(opts.exclude))
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
|
import fnmatch
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@ -15,7 +16,7 @@ import traceback
|
|||||||
from base64 import standard_b64decode
|
from base64 import standard_b64decode
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import (
|
from typing import (
|
||||||
Any, Dict, Iterator, List, NoReturn, Optional, Set, Tuple, Union
|
Any, Callable, Dict, Iterator, List, NoReturn, Optional, Set, Tuple, Union
|
||||||
)
|
)
|
||||||
|
|
||||||
from kitty.constants import cache_dir, shell_integration_dir, terminfo_dir
|
from kitty.constants import cache_dir, shell_integration_dir, terminfo_dir
|
||||||
@ -66,10 +67,13 @@ def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str]) -> bytes:
|
|||||||
tf.addfile(ans, io.BytesIO(data))
|
tf.addfile(ans, io.BytesIO(data))
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def filter_files(tarinfo: tarfile.TarInfo) -> Optional[tarfile.TarInfo]:
|
def filter_from_globs(*pats: str) -> Callable[[tarfile.TarInfo], Optional[tarfile.TarInfo]]:
|
||||||
if tarinfo.name.endswith('ssh/bootstrap.sh'):
|
def filter(tarinfo: tarfile.TarInfo) -> Optional[tarfile.TarInfo]:
|
||||||
return None
|
for pat in pats:
|
||||||
return normalize_tarinfo(tarinfo)
|
if fnmatch.fnmatch(tarinfo.name, pat):
|
||||||
|
return None
|
||||||
|
return normalize_tarinfo(tarinfo)
|
||||||
|
return filter
|
||||||
|
|
||||||
from kitty.shell_integration import get_effective_ksi_env_var
|
from kitty.shell_integration import get_effective_ksi_env_var
|
||||||
if ssh_opts.shell_integration == 'inherit':
|
if ssh_opts.shell_integration == 'inherit':
|
||||||
@ -94,10 +98,13 @@ def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str]) -> bytes:
|
|||||||
buf = io.BytesIO()
|
buf = io.BytesIO()
|
||||||
with tarfile.open(mode='w:bz2', fileobj=buf, encoding='utf-8') as tf:
|
with tarfile.open(mode='w:bz2', fileobj=buf, encoding='utf-8') as tf:
|
||||||
rd = ssh_opts.remote_dir.rstrip('/')
|
rd = ssh_opts.remote_dir.rstrip('/')
|
||||||
|
for location, ci in ssh_opts.copy.items():
|
||||||
|
tf.add(location, arcname=ci.arcname, filter=filter_from_globs(*ci.exclude_patterns))
|
||||||
add_data_as_file(tf, 'kitty-ssh-kitten-data.sh', env_script)
|
add_data_as_file(tf, 'kitty-ssh-kitten-data.sh', env_script)
|
||||||
if ksi:
|
if ksi:
|
||||||
tf.add(shell_integration_dir, arcname=rd + '/shell-integration', filter=filter_files)
|
arcname = rd + '/shell-integration'
|
||||||
tf.add(terminfo_dir, arcname='.terminfo', filter=filter_files)
|
tf.add(shell_integration_dir, arcname=arcname, filter=filter_from_globs(f'{arcname}/ssh/bootstrap.*'))
|
||||||
|
tf.add(terminfo_dir, arcname='.terminfo', filter=normalize_tarinfo)
|
||||||
return buf.getvalue()
|
return buf.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# generated by gen-config.py DO NOT edit
|
# generated by gen-config.py DO NOT edit
|
||||||
|
|
||||||
import typing
|
import typing
|
||||||
import kitty.cli_stub
|
import kittens.ssh.copy
|
||||||
|
|
||||||
|
|
||||||
option_names = ( # {{{
|
option_names = ( # {{{
|
||||||
@ -12,7 +12,7 @@ class Options:
|
|||||||
hostname: str = '*'
|
hostname: str = '*'
|
||||||
remote_dir: str = '.local/share/kitty-ssh-kitten'
|
remote_dir: str = '.local/share/kitty-ssh-kitten'
|
||||||
shell_integration: str = 'inherit'
|
shell_integration: str = 'inherit'
|
||||||
copy: typing.Dict[str, kitty.cli_stub.CLIOptions] = {}
|
copy: typing.Dict[str, kittens.ssh.copy.CopyInstruction] = {}
|
||||||
env: typing.Dict[str, str] = {}
|
env: typing.Dict[str, str] = {}
|
||||||
config_paths: typing.Tuple[str, ...] = ()
|
config_paths: typing.Tuple[str, ...] = ()
|
||||||
config_overrides: typing.Tuple[str, ...] = ()
|
config_overrides: typing.Tuple[str, ...] = ()
|
||||||
|
|||||||
@ -4,9 +4,7 @@
|
|||||||
import posixpath
|
import posixpath
|
||||||
from typing import Any, Dict, Iterable, Optional, Tuple
|
from typing import Any, Dict, Iterable, Optional, Tuple
|
||||||
|
|
||||||
from kitty.cli_stub import CopyCLIOptions
|
from ..copy import CopyInstruction, parse_copy_instructions
|
||||||
|
|
||||||
from ..copy import parse_copy_instructions
|
|
||||||
|
|
||||||
DELETE_ENV_VAR = '_delete_this_env_var_'
|
DELETE_ENV_VAR = '_delete_this_env_var_'
|
||||||
|
|
||||||
@ -33,7 +31,7 @@ def env(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, str]]:
|
|||||||
yield val, DELETE_ENV_VAR
|
yield val, DELETE_ENV_VAR
|
||||||
|
|
||||||
|
|
||||||
def copy(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, CopyCLIOptions]]:
|
def copy(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, CopyInstruction]]:
|
||||||
yield from parse_copy_instructions(val)
|
yield from parse_copy_instructions(val)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user