LiveSync 0.2.2__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {LiveSync-0.2.2.dist-info → LiveSync-0.3.0.dist-info}/METADATA +24 -13
- LiveSync-0.3.0.dist-info/RECORD +10 -0
- livesync/folder.py +7 -12
- livesync/livesync.py +16 -21
- LiveSync-0.2.2.dist-info/RECORD +0 -10
- {LiveSync-0.2.2.dist-info → LiveSync-0.3.0.dist-info}/LICENSE +0 -0
- {LiveSync-0.2.2.dist-info → LiveSync-0.3.0.dist-info}/WHEEL +0 -0
- {LiveSync-0.2.2.dist-info → LiveSync-0.3.0.dist-info}/entry_points.txt +0 -0
- {LiveSync-0.2.2.dist-info → LiveSync-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: LiveSync
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Repeatedly synchronize local workspace with a (slow) remote machine
|
|
5
5
|
Home-page: https://github.com/zauberzeug/livesync
|
|
6
6
|
Author: Zauberzeug GmbH
|
|
@@ -10,8 +10,9 @@ Keywords: sync remote watch filesystem development deploy live hot reload
|
|
|
10
10
|
Requires-Python: >=3.7
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
|
-
Requires-Dist: watchfiles
|
|
14
13
|
Requires-Dist: pathspec
|
|
14
|
+
Requires-Dist: pyjson5
|
|
15
|
+
Requires-Dist: watchfiles
|
|
15
16
|
|
|
16
17
|
# LiveSync
|
|
17
18
|
|
|
@@ -35,28 +36,38 @@ It works best if you have some kind of reload mechanism in place on the target (
|
|
|
35
36
|
## Usage
|
|
36
37
|
|
|
37
38
|
```bash
|
|
38
|
-
|
|
39
|
-
livesync <username>@<host>
|
|
39
|
+
livesync <source> <username>@<host>
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
LiveSync uses rsync (SSH) to copy the files, so the `<username>@<host>` must be accessible via SSH (ideally by key, not password or passphrase, because it will be called over and over).
|
|
43
43
|
|
|
44
44
|
Press `CTRL-C` to abort the synchronization.
|
|
45
45
|
|
|
46
|
+
Positional arguments:
|
|
47
|
+
|
|
48
|
+
- `<source>`
|
|
49
|
+
local folder or VSCode workspace file
|
|
50
|
+
- `<username>@<host>`
|
|
51
|
+
target user and host (e.g. username@hostname)
|
|
52
|
+
|
|
53
|
+
Options:
|
|
54
|
+
|
|
55
|
+
- `--target-root TARGET_ROOT`
|
|
56
|
+
subfolder on target to synchronize to (default: "")
|
|
57
|
+
- `--target-port TARGET_PORT`
|
|
58
|
+
SSH port on target (default: 22)
|
|
59
|
+
- `--on-change ON_CHANGE`
|
|
60
|
+
command to be executed on remote host after any file change (default: None)
|
|
61
|
+
- `--mutex-interval MUTEX_INTERVAL`
|
|
62
|
+
interval in which mutex is updated (default: 10 seconds)
|
|
63
|
+
|
|
46
64
|
### Notes
|
|
47
65
|
|
|
48
66
|
- We suggest you have some auto-reloading in place on the (slow) target machine, like [NiceGUI](https://nicegui.io).
|
|
49
67
|
- Only one user per target host should run LiveSync at a time. Therefore LiveSync provides a mutex mechanism.
|
|
50
|
-
- By default `.git/` folders are not synchronized.
|
|
51
|
-
- All files and directories from the `.gitignore` of any source directory are also excluded from synchronization.
|
|
52
68
|
- You can create a `.syncignore` file in any source directory to skip additional files and directories from syncing.
|
|
53
|
-
- If
|
|
54
|
-
|
|
55
|
-
### Options
|
|
56
|
-
|
|
57
|
-
- `--on-change [command]` command to be executed on remote host after any file change
|
|
58
|
-
- `--source [SOURCE]` source folder on local host instead of VSCode workspace file
|
|
59
|
-
- `--mutex-interval [INTERVAL]` interval for updating the mutex
|
|
69
|
+
- If a `.syncignore` file doesn't exist, it is automatically created containing `.git/`, `__pycache__/`, `.DS_Store`, `*.tmp`, and `.env`.
|
|
70
|
+
- If you pass a VSCode workspace file as `source`, LiveSync will synchronize each directory listed in the `folders` section.
|
|
60
71
|
|
|
61
72
|
## Installation
|
|
62
73
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
livesync/__init__.py,sha256=V9nt0JRzIeiiLDVNDMZ20SqLMSqFh8uPViPsp2WYK6g,60
|
|
2
|
+
livesync/folder.py,sha256=NhorutBdi6_0bhLCPjrVi8TIPZ3TPDQxJRQ0L0-3kRU,3619
|
|
3
|
+
livesync/livesync.py,sha256=PKaw-WD4WOZb-X2n4ihj0C8jEOff4s5dopOGiOM3xI4,2586
|
|
4
|
+
livesync/mutex.py,sha256=C3Az3Exfgj1ohKnhWl77TDvuJlv3q2qieSgtJF__Wdc,1646
|
|
5
|
+
LiveSync-0.3.0.dist-info/LICENSE,sha256=QcBlwggRQYhvfTAE481AAMFQfMS_N6Bj8Svh1T6dsnI,1072
|
|
6
|
+
LiveSync-0.3.0.dist-info/METADATA,sha256=pjVKS1x7LaYr_emVuhAwMUIQyK-WKxJYzPUhnVMs9fc,4285
|
|
7
|
+
LiveSync-0.3.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
8
|
+
LiveSync-0.3.0.dist-info/entry_points.txt,sha256=4dn5YR27lUlJWea3yqLKLqR4MwqjcqzMR5Q4jVFnytI,52
|
|
9
|
+
LiveSync-0.3.0.dist-info/top_level.txt,sha256=mLwExc6wTUGqxvUkYMio5rxGS1h8bvxpNsR2ebfjSL4,9
|
|
10
|
+
LiveSync-0.3.0.dist-info/RECORD,,
|
livesync/folder.py
CHANGED
|
@@ -9,6 +9,7 @@ import pathspec
|
|
|
9
9
|
import watchfiles
|
|
10
10
|
|
|
11
11
|
KWONLY_SLOTS = {'kw_only': True, 'slots': True} if sys.version_info >= (3, 10) else {}
|
|
12
|
+
DEFAULT_IGNORES = ['.git/', '__pycache__/', '.DS_Store', '*.tmp', '.env']
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def run_subprocess(command: str, *, quiet: bool = False) -> None:
|
|
@@ -36,7 +37,7 @@ class Folder:
|
|
|
36
37
|
|
|
37
38
|
# from https://stackoverflow.com/a/22090594/3419103
|
|
38
39
|
match_pattern = pathspec.patterns.gitwildmatch.GitWildMatchPattern
|
|
39
|
-
self._ignore_spec = pathspec.PathSpec.from_lines(match_pattern, self.
|
|
40
|
+
self._ignore_spec = pathspec.PathSpec.from_lines(match_pattern, self.get_ignores())
|
|
40
41
|
|
|
41
42
|
self._stop_watching = asyncio.Event()
|
|
42
43
|
|
|
@@ -48,17 +49,11 @@ class Folder:
|
|
|
48
49
|
def ssh_path(self) -> str:
|
|
49
50
|
return f'{self.target.host}:{self.target_path}'
|
|
50
51
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
self._parse_ignore_file(self.local_path / '.syncignore') + \
|
|
54
|
-
self._parse_ignore_file(self.local_path / '.gitignore')
|
|
55
|
-
|
|
56
|
-
@staticmethod
|
|
57
|
-
def _parse_ignore_file(path: Path) -> List[str]:
|
|
52
|
+
def get_ignores(self) -> List[str]:
|
|
53
|
+
path = self.local_path / '.syncignore'
|
|
58
54
|
if not path.is_file():
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return [line.strip() for line in f.readlines() if not line.startswith('#')]
|
|
55
|
+
path.write_text('\n'.join(DEFAULT_IGNORES))
|
|
56
|
+
return [line.strip() for line in path.read_text().splitlines() if not line.startswith('#')]
|
|
62
57
|
|
|
63
58
|
def get_summary(self) -> str:
|
|
64
59
|
summary = f'{self.local_path} --> {self.ssh_path}\n'
|
|
@@ -90,7 +85,7 @@ class Folder:
|
|
|
90
85
|
def sync(self, post_sync_command: Optional[str] = None) -> None:
|
|
91
86
|
args = '--prune-empty-dirs --delete -avz --checksum --no-t'
|
|
92
87
|
# args += ' --mkdirs' # INFO: this option is not available in rsync < 3.2.3
|
|
93
|
-
args += ''.join(f' --exclude="{e}"' for e in self.
|
|
88
|
+
args += ''.join(f' --exclude="{e}"' for e in self.get_ignores())
|
|
94
89
|
args += f' -e "ssh -p {self.target.port}"'
|
|
95
90
|
run_subprocess(f'rsync {args} {self.local_path}/ {self.ssh_path}/', quiet=True)
|
|
96
91
|
if post_sync_command:
|
livesync/livesync.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import argparse
|
|
3
3
|
import asyncio
|
|
4
|
-
import json
|
|
5
4
|
import sys
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
from typing import List
|
|
8
7
|
|
|
8
|
+
import pyjson5
|
|
9
|
+
|
|
9
10
|
from livesync import Folder, Mutex, Target
|
|
10
11
|
|
|
11
12
|
|
|
@@ -14,37 +15,31 @@ def git_summary(folders: List[Folder]) -> str:
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
async def async_main() -> None:
|
|
17
|
-
parser = argparse.ArgumentParser(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
parser.add_argument('
|
|
18
|
+
parser = argparse.ArgumentParser(
|
|
19
|
+
description='Repeatedly synchronize local directories with remote machine',
|
|
20
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
21
|
+
parser.add_argument('source', type=str, help='local source folder or VSCode workspace file')
|
|
21
22
|
parser.add_argument('--target-root', type=str, default='', help='subfolder on target to synchronize to')
|
|
22
23
|
parser.add_argument('--target-port', type=int, default=22, help='SSH port on target')
|
|
24
|
+
parser.add_argument('--on-change', type=str, help='command to be executed on remote host after any file change')
|
|
25
|
+
parser.add_argument('--mutex-interval', type=int, default=10, help='interval in which mutex is updated')
|
|
23
26
|
parser.add_argument('host', type=str, help='the target host (e.g. username@hostname)')
|
|
24
27
|
args = parser.parse_args()
|
|
28
|
+
source = Path(args.source)
|
|
25
29
|
target = Target(host=args.host, port=args.target_port, root=Path(args.target_root))
|
|
26
30
|
|
|
27
31
|
folders: List[Folder] = []
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if len(workspaces) > 1:
|
|
32
|
-
print('Multiple VSCode workspace files found.')
|
|
33
|
-
print('Provide --source argument or run livesync in a directory with a single *.code-workspace file.')
|
|
34
|
-
sys.exit(1)
|
|
35
|
-
|
|
36
|
-
print(f'Reading VSCode workspace file {workspaces[0]}...')
|
|
37
|
-
|
|
38
|
-
workspace = json.loads(workspaces[0].read_text())
|
|
32
|
+
if source.is_file():
|
|
33
|
+
workspace = pyjson5.decode(source.read_text())
|
|
39
34
|
paths = [Path(f['path']) for f in workspace['folders']]
|
|
40
35
|
folders = [Folder(p, target) for p in paths if p.is_dir()]
|
|
41
36
|
else:
|
|
42
|
-
|
|
43
|
-
if not source_path.is_dir():
|
|
44
|
-
print(f'Invalid source path: {source_path}')
|
|
45
|
-
sys.exit(1)
|
|
37
|
+
folders = [Folder(source, target)]
|
|
46
38
|
|
|
47
|
-
|
|
39
|
+
for folder in folders:
|
|
40
|
+
if not folder.local_path.is_dir():
|
|
41
|
+
print(f'Invalid path: {folder.local_path}')
|
|
42
|
+
sys.exit(1)
|
|
48
43
|
|
|
49
44
|
print('Checking mutex...')
|
|
50
45
|
mutex = Mutex(target)
|
LiveSync-0.2.2.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
livesync/__init__.py,sha256=V9nt0JRzIeiiLDVNDMZ20SqLMSqFh8uPViPsp2WYK6g,60
|
|
2
|
-
livesync/folder.py,sha256=-LlFfZCvtTc_fPOyRCsFLaH1zIwgVo62QgYCRV4ELP4,3774
|
|
3
|
-
livesync/livesync.py,sha256=g2rNyVDkwksr_9yKBcpxIJmu_iQCvlSZFGRcGw_UtfM,2895
|
|
4
|
-
livesync/mutex.py,sha256=C3Az3Exfgj1ohKnhWl77TDvuJlv3q2qieSgtJF__Wdc,1646
|
|
5
|
-
LiveSync-0.2.2.dist-info/LICENSE,sha256=QcBlwggRQYhvfTAE481AAMFQfMS_N6Bj8Svh1T6dsnI,1072
|
|
6
|
-
LiveSync-0.2.2.dist-info/METADATA,sha256=fq3iID0leIkx9zWyZgkQ9-N72Ff7iGYR4SQxTnNPodc,4031
|
|
7
|
-
LiveSync-0.2.2.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
8
|
-
LiveSync-0.2.2.dist-info/entry_points.txt,sha256=4dn5YR27lUlJWea3yqLKLqR4MwqjcqzMR5Q4jVFnytI,52
|
|
9
|
-
LiveSync-0.2.2.dist-info/top_level.txt,sha256=mLwExc6wTUGqxvUkYMio5rxGS1h8bvxpNsR2ebfjSL4,9
|
|
10
|
-
LiveSync-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|