fsync 1.0.0__tar.gz

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.
fsync-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: fsync
3
+ Version: 1.0.0
4
+ Summary: Local file synchronization for scattered shared libraries
5
+ Author: Xaeian
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/Xaeian/FSync
8
+ Keywords: sync,files,libraries,backup
9
+ Requires-Python: >=3.12
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: xaeian
12
+ Provides-Extra: diff
13
+ Requires-Dist: rich; extra == "diff"
14
+ Provides-Extra: all
15
+ Requires-Dist: rich; extra == "all"
16
+
17
+ ## 📖 FSync
18
+
19
+ This tool **eliminates the problem of scattered libraries** by synchronizing 🔄 selected files spread across different projects on your machine _(locally)_. No more chaos and manual file copying.
20
+
21
+ At first glance, centralizing libraries seems like a great idea. It makes work easier and avoids code duplication. In practice, however, some complications arise:
22
+
23
+ - You don't always want to update a library in a project you're no longer developing, but it still needs to work.
24
+ - Some libraries must be part of the repository. When you hand off/close a project, you want everything in one place, without having to pull additional dependencies from external sources.
25
+ - It's better when all resources are in the project directory. It simplifies configuration _(Makefile, CMake)_, eliminates path and version issues, and improves IDE integration.
26
+
27
+ This solution is perfect if you run many smaller projects and care about efficient library management _(code that appears across multiple projects)_. If you make frequent changes, want to avoid messy code, but don't have time to spend hours organizing dependencies: this tool is for you! The program is dead simple. What matters is efficient and effective work, without unnecessary bureaucracy. The open source community can do its thing, but here the priority is a happy client and a working project done ⚡**fast** and 👍**good enough**.
28
+
29
+ ### 🧐 Problems!
30
+
31
+ - ❌ **Possible accidental overwrites**: if you edit two versions of a library at the same time.
32
+ - ✅ Avoid this, but if it happens, every overwritten version is saved as a timestamped backup, so you can always recover your changes.
33
+ - ❌ **No environment isolation**: different projects may require different versions of the same library.
34
+ - ✅ Not a problem! Just create separate entries for different versions, keeping synchronization independent. You can also comment out entries for libraries that shouldn't be updated anymore.
35
+ - ❌ **Code duplication across repositories**: instead of one library copy, you have several in different projects.
36
+ - ✅ That's the point! Each client should have their own library version, with no dependencies on other repos. Full control, zero unnecessary complications.
37
+
38
+ ### 🤔 Alternatives?
39
+
40
+ Of course you can approach this more professionally by:
41
+
42
+ - Versioning libraries as separate projects/repositories and updating them as needed.
43
+ - Using Git **Submodules**, which allows tracking library versions in the repository.
44
+ - External package managers _(`pip`, `npm`, `cargo`)_ that simplify dependency management.
45
+
46
+ If any of our libraries reach a stable version that we don't chaotically change every project, and they're good enough, it's worth considering one of the above solutions.
47
+
48
+ ### ⚙️ Config
49
+
50
+ The **`sync.json`** file defines file synchronization configuration. Each entry is a key _(filename)_ and a list of paths to synchronize. Keys starting with `#` are treated as commented out and skipped.
51
+
52
+ Paths can use shorthand notation via the **`dict.ini`** file, which defines aliases for frequently repeated locations. In `sync.json` paths you can reference these aliases using `{key}` notation.
53
+
54
+ #### Example
55
+
56
+ Running the program with the `-e`, `--example` flag will create example config files locally.
57
+
58
+ File `dict.ini`
59
+
60
+ ```ini
61
+ web = C:/Users/Me/Projects/WebPage/backend
62
+ staff = C:/Users/Me/Desktop/MyStaff/test
63
+ work = C:/Users/Me/Work/Drivers/repos
64
+ ```
65
+
66
+ File `sync.json`
67
+
68
+ ```json
69
+ {
70
+ "serial.c": ["{staff}/serial.c", "{work}/PLC/serial_port.c"],
71
+ "utils.py": ["{web}/lib/utils.py", "{work}/PLC/misc.py"],
72
+ "#old_lib.c": ["{staff}/old_lib.c", "{work}/legacy/old_lib.c"]
73
+ }
74
+ ```
75
+
76
+ ### 📦 Install
77
+
78
+ ```sh
79
+ pip install fsync # basic
80
+ pip install fsync[diff] # + rich (diff display)
81
+ ```
82
+
83
+ > Also available as standalone `.exe` on [GitHub Releases](https://github.com/Xaeian/FSync/releases).
84
+
85
+ ### 🚀 Use
86
+
87
+ First, set the workspace: the directory containing your config files (`sync.json`, `dict.ini`) and where backups will be stored:
88
+
89
+ ```sh
90
+ fsync -w C:/Projects/sync # specified path
91
+ fsync -w # current directory
92
+ ```
93
+
94
+ Running the program generates a report:
95
+
96
+ ```bash
97
+ fsync
98
+ ```
99
+
100
+ To synchronize _(i.e. update older file versions)_, just add the `-u`, `--update` flag:
101
+
102
+ ```bash
103
+ fsync -u
104
+ ```
105
+
106
+ For each pair of files with discrepancies, tags are generated. You can use them to inspect differences between files with the `-d`, `--diff` flag:
107
+
108
+ ```bash
109
+ fsync -d 1.1
110
+ ```
@@ -0,0 +1,31 @@
1
+ # fsync/__init__.py
2
+
3
+ """
4
+ FSync: local file synchronization tool.
5
+
6
+ Keeps scattered copies of shared libraries in sync across projects.
7
+ Works as CLI (`py -m fsync`), pip-installed command (`fsync`),
8
+ or importable module.
9
+
10
+ Example:
11
+ >>> from fsync.main import main
12
+ >>> from fsync.utils import backup_file
13
+ >>> from fsync.diff import show
14
+ """
15
+
16
+ __version__ = "1.0.0"
17
+ __repo__ = "Xaeian/FSync"
18
+ __python__ = ">=3.12"
19
+ __description__ = "Local file synchronization for scattered shared libraries"
20
+ __author__ = "Xaeian"
21
+ __keywords__ = ["sync", "files", "libraries", "backup"]
22
+ __dependencies__ = ["xaeian"]
23
+ __scripts__ = {
24
+ "fsync": "fsync.__main__:main",
25
+ }
26
+
27
+ from .utils import backup_file, BACKUP_DIR, BACKUP_KEEP
28
+
29
+ __all__ = [
30
+ "backup_file", "BACKUP_DIR", "BACKUP_KEEP",
31
+ ]
@@ -0,0 +1,4 @@
1
+ # fsync/__main__.py
2
+
3
+ from .main import main
4
+ main()
@@ -0,0 +1,19 @@
1
+ # fsync/args.py
2
+
3
+ import argparse
4
+
5
+ def load_args():
6
+ parser = argparse.ArgumentParser(description="FSync: File synchronization tool")
7
+ parser.add_argument("-w", "--workspace", type=str, nargs="?", const=".", default=None,
8
+ help="Set workspace directory (default: current dir)")
9
+ parser.add_argument("-u", "--update", action="store_true",
10
+ help="Update files to latest version (most recently modified)")
11
+ parser.add_argument("-i", "--info", action="store_true",
12
+ help="Display all synchronized files")
13
+ parser.add_argument("-e", "--example", action="store_true",
14
+ help="Create example config files")
15
+ parser.add_argument("-d", "--diff", type=str, nargs="?",
16
+ help="Compare files by tag: <latest>.<obsolete>", default="")
17
+ parser.add_argument("-v", "--version", action="store_true",
18
+ help="Program version and repository")
19
+ return parser.parse_args()
@@ -0,0 +1,32 @@
1
+ # fsync/diff.py
2
+
3
+ __extras__ = ("diff", ["rich"])
4
+
5
+ import sys, difflib
6
+ from xaeian import FILE, PATH, Print, Color as c, replace_map
7
+
8
+ def show(file_a:str, file_b:str, mapping:dict|None=None):
9
+ try:
10
+ from rich.console import Console
11
+ from rich.syntax import Syntax
12
+ except ImportError:
13
+ p = Print()
14
+ p.err(f"Missing {c.TURQUS}rich{c.END} package for diff display")
15
+ p.run(f"Install: {c.YELLOW}pip{c.END} install fsync[diff]")
16
+ sys.exit(1)
17
+ lines_a = FILE.load_lines(file_a)
18
+ lines_b = FILE.load_lines(file_b)
19
+ if mapping:
20
+ label_a = replace_map(PATH.normalize(file_a), mapping)
21
+ label_b = replace_map(PATH.normalize(file_b), mapping)
22
+ else:
23
+ label_a, label_b = file_a, file_b
24
+ result = "".join(difflib.unified_diff(
25
+ lines_a, lines_b,
26
+ fromfile=label_a, tofile=label_b,
27
+ ))
28
+ if result:
29
+ Console().print(Syntax(
30
+ result, "diff", theme="ansi_dark",
31
+ line_numbers=True, background_color=None,
32
+ ))
@@ -0,0 +1,30 @@
1
+ # fsync/example.py
2
+
3
+ """Generate example config files (dict.ini, sync.json)."""
4
+
5
+ import os, platform
6
+ from xaeian import JSON, INI, FILE, Print, Color as c
7
+
8
+ def create():
9
+ p = Print()
10
+ user = os.getlogin()
11
+ if platform.system() == "Windows": base = f"C:/Users/{user}"
12
+ else: base = f"/home/{user}"
13
+ mydict = {
14
+ "web": f"{base}/Projects/WebPage/backend",
15
+ "staff": f"{base}/Desktop/MyStaff/test",
16
+ "work": f"{base}/Work/Drivers/repos",
17
+ }
18
+ sync = {
19
+ "serial.c": ["{staff}/serial.c", "{work}/PLC/serial_port.c"],
20
+ "utils.py": ["{web}/lib/utils.py", "{work}/PLC/misc.py"],
21
+ }
22
+ for name, data, saver in [
23
+ ("dict.ini", mydict, INI.save),
24
+ ("sync.json", sync, JSON.save_pretty),
25
+ ]:
26
+ if FILE.exists(name):
27
+ p.err(f"File {c.RED}{name}{c.END} already exists. Delete it to generate example")
28
+ else:
29
+ saver(name, data)
30
+ p.ok(f"Template {c.GREEN}{name}{c.END} generated")
@@ -0,0 +1,175 @@
1
+ # fsync/main.py
2
+
3
+ """
4
+ FSync: file synchronization engine.
5
+
6
+ Core sync logic: load workspace, resolve paths, detect
7
+ changes, update obsolete files with backup rotation.
8
+
9
+ Example:
10
+ >>> from fsync.main import main
11
+ >>> main()
12
+ """
13
+
14
+ import os, sys
15
+ from datetime import datetime
16
+ from xaeian import JSON, INI, FILE, DIR, PATH, replace_map, Ico, Print, Color as c
17
+ from xaeian.files import set_context
18
+ from .args import load_args
19
+ from . import __version__, __repo__
20
+
21
+ WORKSPACE_FILE = PATH.expand("~/.fsync.txt")
22
+
23
+ def main():
24
+ p = Print()
25
+ args = load_args()
26
+
27
+ if args.version:
28
+ print(f"FSync {c.BLUE}{__version__}{c.END}")
29
+ print(f"Repo: {c.GREY}https://{c.END}github.com/{__repo__}")
30
+ sys.exit(0)
31
+
32
+ #------------------------------------------------------------------------------------- Workspace
33
+ if args.workspace is not None:
34
+ ws = os.path.abspath(PATH.expand(args.workspace))
35
+ if not PATH.is_dir(ws):
36
+ p.err(f"Directory {c.GREY}{ws}{c.END} doesn't exist")
37
+ sys.exit(1)
38
+ FILE.save(WORKSPACE_FILE, ws)
39
+ p.ok(f"Set directory {c.ORANGE}{ws}{c.END} as workspace")
40
+ if not any([args.update, args.info, args.example, args.diff]):
41
+ sys.exit(0)
42
+
43
+ if not PATH.is_file(WORKSPACE_FILE):
44
+ p.err(f"No workspace set. Use {c.GREY}-w --workspace{c.END} to set one")
45
+ sys.exit(1)
46
+ workspace = FILE.load(WORKSPACE_FILE).strip()
47
+ if not PATH.is_dir(workspace):
48
+ p.err(f"Workspace {c.ORANGE}{workspace}{c.END} doesn't exist")
49
+ sys.exit(1)
50
+
51
+ set_context(root_path=workspace)
52
+
53
+ if args.example:
54
+ from . import example
55
+ example.create()
56
+ sys.exit(0)
57
+
58
+ #---------------------------------------------------------------------------------------- Config
59
+
60
+ sync = JSON.load("sync.json")
61
+ if not sync:
62
+ p.err(f"Missing or invalid {c.RED}sync.json{c.END}")
63
+ sys.exit(1)
64
+ sync = {k: v for k, v in sync.items() if not k.startswith("#")}
65
+ if not sync:
66
+ p.err(f"No active entries in {c.RED}sync.json{c.END}")
67
+ sys.exit(1)
68
+ mydict = INI.load("dict.ini")
69
+ if not mydict:
70
+ p.err(f"Missing or invalid {c.RED}dict.ini{c.END}")
71
+ sys.exit(1)
72
+
73
+ #---------------------------------------------------------------------------------- Resolve paths
74
+ resolved = {}
75
+ for name, paths in sync.items():
76
+ resolved[name] = [replace_map(x, mydict, "{", "}") for x in paths]
77
+
78
+ #------------------------------------------------------------------------------------- Validate
79
+ errors = False
80
+ for name, paths in resolved.items():
81
+ seen = set()
82
+ for path in paths:
83
+ if not PATH.is_file(path):
84
+ p.err(f"Path {c.ORANGE}{path}{c.END} in {c.RED}{name}{c.END} doesn't exist")
85
+ errors = True
86
+ if path in seen:
87
+ p.err(f"Path {c.ORANGE}{path}{c.END} in {c.RED}{name}{c.END} duplicated")
88
+ errors = True
89
+ seen.add(path)
90
+ if errors:
91
+ sys.exit(1)
92
+
93
+ if args.diff is None:
94
+ p.wrn(f"Flag {c.GREY}-d{c.END} requires tag, e.g. {c.GREY}-d {c.MAGNTA}1.1{c.END}")
95
+ args.diff = ""
96
+
97
+ #----------------------------------------------------------------------------------------- Diff
98
+ diff_ctx = {}
99
+ if args.diff:
100
+ diff_ctx["latest_nbr"], diff_ctx["obsolete_nbr"] = map(int, args.diff.split("."))
101
+
102
+ #----------------------------------------------------------------------------------------- Sync
103
+ update_flag = False
104
+ nbr_last = 0
105
+
106
+ def sync_file(name:str, paths:list[str]):
107
+ nonlocal update_flag, nbr_last
108
+ paths = [PATH.normalize(x) for x in paths]
109
+ paths = [x for x in paths if PATH.is_file(x)]
110
+ if len(paths) < 2: return
111
+ hashs = [FILE.hash(f, algo="md5") for f in paths]
112
+ stamps = [FILE.mtime(f) for f in paths]
113
+ dts = [datetime.fromtimestamp(s).strftime("%Y-%m-%d %H:%M:%S") for s in stamps]
114
+ if len(set(hashs)) == 1 and not args.info: return
115
+ latest_id = stamps.index(max(stamps))
116
+ latest_hash = hashs[latest_id]
117
+ latest_file = paths[latest_id]
118
+ latest_dt = dts[latest_id]
119
+ nbr_last += 1
120
+ nbr_obsolete = 0
121
+ if diff_ctx and diff_ctx.get("latest_nbr") == nbr_last:
122
+ diff_ctx["latest_file"] = latest_file
123
+ diff_ctx["name"] = name
124
+ p(f"{Ico.INF} {c.YELLOW}{nbr_last}{c.GREY}.x{c.END} Latest"
125
+ f" {c.BLUE}{name}{c.END}: {c.GREY}{latest_file}{c.END}"
126
+ f" {c.TEAL}{latest_dt}{c.END}")
127
+ for file, hash, dt, stamp in zip(paths, hashs, dts, stamps):
128
+ tag = lambda clr: f"{Ico.GAP} {clr}{nbr_last}.{nbr_obsolete}{c.END}"
129
+ if hash != latest_hash:
130
+ update_flag = True
131
+ nbr_obsolete += 1
132
+ color = c.YELLOW
133
+ if(diff_ctx and diff_ctx.get("latest_nbr") == nbr_last
134
+ and diff_ctx.get("obsolete_nbr") == nbr_obsolete):
135
+ diff_ctx["obsolete_file"] = file
136
+ color = c.GREEN
137
+ if args.update:
138
+ backed = utils.backup_file(file, name)
139
+ try:
140
+ DIR.copy(latest_file, file)
141
+ msg = f"{c.GREY}{file}{c.END} updated"
142
+ if backed: msg += f" {c.GREY}(backup){c.END}"
143
+ msg += f" {c.GREEN}OK{c.END}"
144
+ p(f"{tag(color)} {msg}")
145
+ except Exception:
146
+ p(f"{tag(color)} {c.GREY}{file}{c.END} update {c.RED}FAILED{c.END}")
147
+ else:
148
+ p(f"{tag(color)} Obsolete: {c.GREY}{file}{c.END} {c.ORANGE}{dt}{c.END}")
149
+ if stamp == os.path.getctime(file):
150
+ p.wrn("File was created recently, make sure it's not actually newer!")
151
+ elif (args.update or args.info) and file != latest_file:
152
+ p.ok(f"{c.GREY}{file}{c.END} is up-to-date")
153
+
154
+ from . import utils
155
+
156
+ for name, paths in resolved.items():
157
+ sync_file(name, paths)
158
+
159
+ if not update_flag:
160
+ p.inf(f"All files are in the same version {c.GREY}(no update needed){c.END}")
161
+ elif not args.update:
162
+ p.run(f"Update older files using {c.YELLOW}-u{c.END} {c.GREY}--update{c.END}")
163
+ p.run(f"Display file changes using {c.YELLOW}-d{c.END} {c.GREY}--diff{c.END}")
164
+ if args.diff:
165
+ if not diff_ctx or "obsolete_file" not in diff_ctx:
166
+ p.err(f"Invalid tag {c.GREEN}{args.diff}{c.END} for comparing files")
167
+ sys.exit(1)
168
+ p.inf(f"Diff compare {c.BLUE}{diff_ctx['name']}{c.END}"
169
+ f" tag {c.GREEN}{diff_ctx['latest_nbr']}.{diff_ctx['obsolete_nbr']}{c.END}:")
170
+ reversed_dict = {PATH.normalize(v): f"{{{k}}}" for k, v in mydict.items()}
171
+ from . import diff
172
+ diff.show(diff_ctx["obsolete_file"], diff_ctx["latest_file"], reversed_dict)
173
+
174
+ if __name__ == "__main__":
175
+ main()
@@ -0,0 +1,36 @@
1
+ # fsync/utils.py
2
+
3
+ import os
4
+ from datetime import datetime
5
+ from xaeian import FILE, DIR, PATH
6
+
7
+ BACKUP_DIR = "backups"
8
+ BACKUP_KEEP = 10
9
+
10
+ def _find_backups(name: str) -> list[str]:
11
+ """Find all backups for given sync name, sorted oldest first."""
12
+ dir = PATH.resolve(f"{BACKUP_DIR}/{name}")
13
+ if not os.path.isdir(dir): return []
14
+ return [f"{dir}/{f}" for f in sorted(os.listdir(dir))]
15
+
16
+ def backup_file(path: str, name: str) -> bool:
17
+ """
18
+ Backup file before overwriting.
19
+ Layout: backups/ary.c/2025-02-24@14-30-00.c
20
+ Skips if identical to latest backup. Rotates beyond BACKUP_KEEP.
21
+ """
22
+ file_hash = FILE.hash(path, algo="md5")
23
+ existing = _find_backups(name)
24
+ if existing and FILE.hash(existing[-1], algo="md5") == file_hash:
25
+ return False
26
+ dir = PATH.resolve(f"{BACKUP_DIR}/{name}")
27
+ DIR.ensure(f"{dir}/")
28
+ ext = PATH.ext(name)
29
+ stamp = datetime.now().strftime("%Y-%m-%d@%H-%M-%S")
30
+ dst = f"{dir}/{stamp}{ext}"
31
+ DIR.copy(path, dst)
32
+ # Rotate
33
+ all_backups = _find_backups(name)
34
+ while len(all_backups) > BACKUP_KEEP:
35
+ os.remove(all_backups.pop(0))
36
+ return True
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: fsync
3
+ Version: 1.0.0
4
+ Summary: Local file synchronization for scattered shared libraries
5
+ Author: Xaeian
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/Xaeian/FSync
8
+ Keywords: sync,files,libraries,backup
9
+ Requires-Python: >=3.12
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: xaeian
12
+ Provides-Extra: diff
13
+ Requires-Dist: rich; extra == "diff"
14
+ Provides-Extra: all
15
+ Requires-Dist: rich; extra == "all"
16
+
17
+ ## 📖 FSync
18
+
19
+ This tool **eliminates the problem of scattered libraries** by synchronizing 🔄 selected files spread across different projects on your machine _(locally)_. No more chaos and manual file copying.
20
+
21
+ At first glance, centralizing libraries seems like a great idea. It makes work easier and avoids code duplication. In practice, however, some complications arise:
22
+
23
+ - You don't always want to update a library in a project you're no longer developing, but it still needs to work.
24
+ - Some libraries must be part of the repository. When you hand off/close a project, you want everything in one place, without having to pull additional dependencies from external sources.
25
+ - It's better when all resources are in the project directory. It simplifies configuration _(Makefile, CMake)_, eliminates path and version issues, and improves IDE integration.
26
+
27
+ This solution is perfect if you run many smaller projects and care about efficient library management _(code that appears across multiple projects)_. If you make frequent changes, want to avoid messy code, but don't have time to spend hours organizing dependencies: this tool is for you! The program is dead simple. What matters is efficient and effective work, without unnecessary bureaucracy. The open source community can do its thing, but here the priority is a happy client and a working project done ⚡**fast** and 👍**good enough**.
28
+
29
+ ### 🧐 Problems!
30
+
31
+ - ❌ **Possible accidental overwrites**: if you edit two versions of a library at the same time.
32
+ - ✅ Avoid this, but if it happens, every overwritten version is saved as a timestamped backup, so you can always recover your changes.
33
+ - ❌ **No environment isolation**: different projects may require different versions of the same library.
34
+ - ✅ Not a problem! Just create separate entries for different versions, keeping synchronization independent. You can also comment out entries for libraries that shouldn't be updated anymore.
35
+ - ❌ **Code duplication across repositories**: instead of one library copy, you have several in different projects.
36
+ - ✅ That's the point! Each client should have their own library version, with no dependencies on other repos. Full control, zero unnecessary complications.
37
+
38
+ ### 🤔 Alternatives?
39
+
40
+ Of course you can approach this more professionally by:
41
+
42
+ - Versioning libraries as separate projects/repositories and updating them as needed.
43
+ - Using Git **Submodules**, which allows tracking library versions in the repository.
44
+ - External package managers _(`pip`, `npm`, `cargo`)_ that simplify dependency management.
45
+
46
+ If any of our libraries reach a stable version that we don't chaotically change every project, and they're good enough, it's worth considering one of the above solutions.
47
+
48
+ ### ⚙️ Config
49
+
50
+ The **`sync.json`** file defines file synchronization configuration. Each entry is a key _(filename)_ and a list of paths to synchronize. Keys starting with `#` are treated as commented out and skipped.
51
+
52
+ Paths can use shorthand notation via the **`dict.ini`** file, which defines aliases for frequently repeated locations. In `sync.json` paths you can reference these aliases using `{key}` notation.
53
+
54
+ #### Example
55
+
56
+ Running the program with the `-e`, `--example` flag will create example config files locally.
57
+
58
+ File `dict.ini`
59
+
60
+ ```ini
61
+ web = C:/Users/Me/Projects/WebPage/backend
62
+ staff = C:/Users/Me/Desktop/MyStaff/test
63
+ work = C:/Users/Me/Work/Drivers/repos
64
+ ```
65
+
66
+ File `sync.json`
67
+
68
+ ```json
69
+ {
70
+ "serial.c": ["{staff}/serial.c", "{work}/PLC/serial_port.c"],
71
+ "utils.py": ["{web}/lib/utils.py", "{work}/PLC/misc.py"],
72
+ "#old_lib.c": ["{staff}/old_lib.c", "{work}/legacy/old_lib.c"]
73
+ }
74
+ ```
75
+
76
+ ### 📦 Install
77
+
78
+ ```sh
79
+ pip install fsync # basic
80
+ pip install fsync[diff] # + rich (diff display)
81
+ ```
82
+
83
+ > Also available as standalone `.exe` on [GitHub Releases](https://github.com/Xaeian/FSync/releases).
84
+
85
+ ### 🚀 Use
86
+
87
+ First, set the workspace: the directory containing your config files (`sync.json`, `dict.ini`) and where backups will be stored:
88
+
89
+ ```sh
90
+ fsync -w C:/Projects/sync # specified path
91
+ fsync -w # current directory
92
+ ```
93
+
94
+ Running the program generates a report:
95
+
96
+ ```bash
97
+ fsync
98
+ ```
99
+
100
+ To synchronize _(i.e. update older file versions)_, just add the `-u`, `--update` flag:
101
+
102
+ ```bash
103
+ fsync -u
104
+ ```
105
+
106
+ For each pair of files with discrepancies, tags are generated. You can use them to inspect differences between files with the `-d`, `--diff` flag:
107
+
108
+ ```bash
109
+ fsync -d 1.1
110
+ ```
@@ -0,0 +1,15 @@
1
+ pyproject.toml
2
+ readme.md
3
+ fsync/__init__.py
4
+ fsync/__main__.py
5
+ fsync/args.py
6
+ fsync/diff.py
7
+ fsync/example.py
8
+ fsync/main.py
9
+ fsync/utils.py
10
+ fsync.egg-info/PKG-INFO
11
+ fsync.egg-info/SOURCES.txt
12
+ fsync.egg-info/dependency_links.txt
13
+ fsync.egg-info/entry_points.txt
14
+ fsync.egg-info/requires.txt
15
+ fsync.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ fsync = fsync.__main__:main
@@ -0,0 +1,7 @@
1
+ xaeian
2
+
3
+ [all]
4
+ rich
5
+
6
+ [diff]
7
+ rich
@@ -0,0 +1 @@
1
+ fsync
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fsync"
7
+ version = "1.0.0"
8
+ description = "Local file synchronization for scattered shared libraries"
9
+ readme = "readme.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.12"
12
+ authors = [{name = "Xaeian"}]
13
+ keywords = ["sync", "files", "libraries", "backup"]
14
+ dependencies = ["xaeian"]
15
+
16
+ [project.optional-dependencies]
17
+ diff = ["rich"]
18
+ all = ["rich"]
19
+
20
+ [project.scripts]
21
+ fsync = "fsync.__main__:main"
22
+
23
+ [project.urls]
24
+ Repository = "https://github.com/Xaeian/FSync"
25
+
26
+ [tool.setuptools.packages.find]
27
+ include = ["fsync*"]
fsync-1.0.0/readme.md ADDED
@@ -0,0 +1,94 @@
1
+ ## 📖 FSync
2
+
3
+ This tool **eliminates the problem of scattered libraries** by synchronizing 🔄 selected files spread across different projects on your machine _(locally)_. No more chaos and manual file copying.
4
+
5
+ At first glance, centralizing libraries seems like a great idea. It makes work easier and avoids code duplication. In practice, however, some complications arise:
6
+
7
+ - You don't always want to update a library in a project you're no longer developing, but it still needs to work.
8
+ - Some libraries must be part of the repository. When you hand off/close a project, you want everything in one place, without having to pull additional dependencies from external sources.
9
+ - It's better when all resources are in the project directory. It simplifies configuration _(Makefile, CMake)_, eliminates path and version issues, and improves IDE integration.
10
+
11
+ This solution is perfect if you run many smaller projects and care about efficient library management _(code that appears across multiple projects)_. If you make frequent changes, want to avoid messy code, but don't have time to spend hours organizing dependencies: this tool is for you! The program is dead simple. What matters is efficient and effective work, without unnecessary bureaucracy. The open source community can do its thing, but here the priority is a happy client and a working project done ⚡**fast** and 👍**good enough**.
12
+
13
+ ### 🧐 Problems!
14
+
15
+ - ❌ **Possible accidental overwrites**: if you edit two versions of a library at the same time.
16
+ - ✅ Avoid this, but if it happens, every overwritten version is saved as a timestamped backup, so you can always recover your changes.
17
+ - ❌ **No environment isolation**: different projects may require different versions of the same library.
18
+ - ✅ Not a problem! Just create separate entries for different versions, keeping synchronization independent. You can also comment out entries for libraries that shouldn't be updated anymore.
19
+ - ❌ **Code duplication across repositories**: instead of one library copy, you have several in different projects.
20
+ - ✅ That's the point! Each client should have their own library version, with no dependencies on other repos. Full control, zero unnecessary complications.
21
+
22
+ ### 🤔 Alternatives?
23
+
24
+ Of course you can approach this more professionally by:
25
+
26
+ - Versioning libraries as separate projects/repositories and updating them as needed.
27
+ - Using Git **Submodules**, which allows tracking library versions in the repository.
28
+ - External package managers _(`pip`, `npm`, `cargo`)_ that simplify dependency management.
29
+
30
+ If any of our libraries reach a stable version that we don't chaotically change every project, and they're good enough, it's worth considering one of the above solutions.
31
+
32
+ ### ⚙️ Config
33
+
34
+ The **`sync.json`** file defines file synchronization configuration. Each entry is a key _(filename)_ and a list of paths to synchronize. Keys starting with `#` are treated as commented out and skipped.
35
+
36
+ Paths can use shorthand notation via the **`dict.ini`** file, which defines aliases for frequently repeated locations. In `sync.json` paths you can reference these aliases using `{key}` notation.
37
+
38
+ #### Example
39
+
40
+ Running the program with the `-e`, `--example` flag will create example config files locally.
41
+
42
+ File `dict.ini`
43
+
44
+ ```ini
45
+ web = C:/Users/Me/Projects/WebPage/backend
46
+ staff = C:/Users/Me/Desktop/MyStaff/test
47
+ work = C:/Users/Me/Work/Drivers/repos
48
+ ```
49
+
50
+ File `sync.json`
51
+
52
+ ```json
53
+ {
54
+ "serial.c": ["{staff}/serial.c", "{work}/PLC/serial_port.c"],
55
+ "utils.py": ["{web}/lib/utils.py", "{work}/PLC/misc.py"],
56
+ "#old_lib.c": ["{staff}/old_lib.c", "{work}/legacy/old_lib.c"]
57
+ }
58
+ ```
59
+
60
+ ### 📦 Install
61
+
62
+ ```sh
63
+ pip install fsync # basic
64
+ pip install fsync[diff] # + rich (diff display)
65
+ ```
66
+
67
+ > Also available as standalone `.exe` on [GitHub Releases](https://github.com/Xaeian/FSync/releases).
68
+
69
+ ### 🚀 Use
70
+
71
+ First, set the workspace: the directory containing your config files (`sync.json`, `dict.ini`) and where backups will be stored:
72
+
73
+ ```sh
74
+ fsync -w C:/Projects/sync # specified path
75
+ fsync -w # current directory
76
+ ```
77
+
78
+ Running the program generates a report:
79
+
80
+ ```bash
81
+ fsync
82
+ ```
83
+
84
+ To synchronize _(i.e. update older file versions)_, just add the `-u`, `--update` flag:
85
+
86
+ ```bash
87
+ fsync -u
88
+ ```
89
+
90
+ For each pair of files with discrepancies, tags are generated. You can use them to inspect differences between files with the `-d`, `--diff` flag:
91
+
92
+ ```bash
93
+ fsync -d 1.1
94
+ ```
fsync-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+