filecraft-cli 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.
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: filecraft-cli
3
+ Version: 1.0.0
4
+ Summary:
5
+ Author: MURTAZA PATEL
6
+ Author-email: murtazapatel89100@gmail.com
7
+ Requires-Python: >=3.10,<3.15
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Requires-Dist: typer (>=0.24.1,<0.25.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+ # Filecraft (Python implementation)
18
+
19
+ ![Filecraft Banner](../assets/Filecraft-banner.png)
20
+
21
+ This is the Python implementation of Filecraft, focused on safe and repeatable file organization workflows.
22
+
23
+ ## Requirements
24
+
25
+ - Python `>=3.10,<3.15`
26
+
27
+ ## Distribution
28
+
29
+ - Package target: PyPI (`filecraft-cli`)
30
+ - CLI command: `filecraft`
31
+ - Standalone binary: `Filecraft` (PyInstaller)
32
+
33
+ ## Install / Run
34
+
35
+ ### Option 1: Local development (Poetry)
36
+
37
+ ```bash
38
+ poetry install
39
+ poetry run filecraft --help
40
+ ```
41
+
42
+ ### Option 2: PyPI install (release users)
43
+
44
+ ```bash
45
+ pip install filecraft-cli
46
+ filecraft --help
47
+ ```
48
+
49
+ ### Option 2: Run module directly (without installing script)
50
+
51
+ ```bash
52
+ PYTHONPATH=src python -m file_organiser_python.main --help
53
+ ```
54
+
55
+ ## Commands
56
+
57
+ - `filecraft rename`
58
+ - `filecraft separate`
59
+ - `filecraft merge`
60
+ - `filecraft revert`
61
+
62
+ All commands support `--dry-run` to preview actions without moving files.
63
+ Working directory flags are validated before any `--target-dir` creation prompt.
64
+
65
+ ## `rename`
66
+
67
+ Renames files in `working_dir` and moves them to `target_dir`.
68
+ By default names are numeric (`1.ext`, `2.ext`, ...); with `--rename-with` they become prefixed (`name_1.ext`, `name_2.ext`, ...).
69
+
70
+ ### Options
71
+
72
+ - `--working-dir PATH` (default: current directory)
73
+ - `--target-dir PATH` (default: current directory)
74
+ - `--dry-run`
75
+ - `--history` (save history file for revert)
76
+ - `--rename-with TEXT` (optional base name prefix, e.g. `invoice`)
77
+
78
+ If `--target-dir` is provided and does not exist, the CLI prompts to create it (`y/n`).
79
+ Declining exits with a `--target-dir` error.
80
+
81
+ ### Rename Example
82
+
83
+ ```bash
84
+ filecraft rename --working-dir ./downloads --target-dir ./renamed --history
85
+ filecraft rename --working-dir ./downloads --target-dir ./renamed --rename-with invoice
86
+ ```
87
+
88
+ ## `separate`
89
+
90
+ Separates files according to mode.
91
+
92
+ ### Separate Modes
93
+
94
+ - `extension`: Move files of a specific extension (e.g. `.pdf`) into `TARGET/PDF`
95
+ - `date`: Move files modified on a specific date (or today) into `TARGET/YYYY-MM-DD`
96
+ - `extension_and_date`: Combine both filters into `TARGET/YYYY-MM-DD/EXT`
97
+ - `file`: Sort all files by file type category (`IMAGES`, `VIDEOS`, `DOCUMENTS`, `ARCHIVES`, etc.)
98
+
99
+ ### Separate Options
100
+
101
+ - `--mode [extension|date|extension_and_date|file]` (default: `extension`)
102
+ - `--extension TEXT` (required for `extension` and `extension_and_date`)
103
+ - `--file-type TEXT` (optional for `file`; accepts category like `documents` or extension like `pdf`)
104
+ - `--date YYYY-MM-DD` (used by `date` and `extension_and_date`; validated)
105
+ - `--working-dir PATH` (default: current directory)
106
+ - `--target-dir PATH` (default: current directory)
107
+ - `--dry-run`
108
+ - `--history`
109
+
110
+ ### Separate Examples
111
+
112
+ ```bash
113
+ filecraft separate --mode extension --extension pdf --working-dir ./in --target-dir ./out
114
+ filecraft separate --mode date --date 2026-03-01 --working-dir ./in --target-dir ./out
115
+ filecraft separate --mode extension_and_date --extension .jpg --date 2026-03-01 --working-dir ./in --target-dir ./out
116
+ filecraft separate --mode file --working-dir ./in --target-dir ./out
117
+ filecraft separate --mode file --file-type pdf --working-dir ./in --target-dir ./out
118
+ ```
119
+
120
+ ## `revert`
121
+
122
+ Reverts moves using a history file.
123
+
124
+ ### Revert Options
125
+
126
+ - `--directory PATH` (searches latest history in that directory; default: current directory)
127
+ - `--history-file PATH` (use a specific history file)
128
+ - `--dry-run`
129
+ - `--keep-history` (do not delete history file after successful revert)
130
+
131
+ ### Revert Examples
132
+
133
+ ```bash
134
+ filecraft revert --directory ./out
135
+ filecraft revert --history-file ./out/.organizer_history_2026-03-01_12-00-00-123456.json
136
+ ```
137
+
138
+ ## `merge`
139
+
140
+ Merges files from multiple `working_dir` locations into a single `target_dir`.
141
+
142
+ ### Merge Modes
143
+
144
+ - `extension`: Merge files of a specific extension into `TARGET/EXT`
145
+ - `date`: Merge files modified on a specific date (or today) into `TARGET/YYYY-MM-DD`
146
+ - `extension_and_date`: Combine both filters into `TARGET/YYYY-MM-DD/EXT`
147
+ - `file`: Merge all files by file type category (`IMAGES`, `VIDEOS`, `DOCUMENTS`, `ARCHIVES`, etc.)
148
+
149
+ ### Merge Options
150
+
151
+ - `--mode [extension|date|extension_and_date|file]` (default: `extension`)
152
+ - `--extension TEXT` (required for `extension` and `extension_and_date`)
153
+ - `--date YYYY-MM-DD` (used by `date` and `extension_and_date`; validated)
154
+ - `--working-dir PATH` (required, repeat for multiple source directories)
155
+ - `--target-dir PATH` (default: current directory)
156
+ - `--dry-run`
157
+ - `--history`
158
+
159
+ ### Merge Examples
160
+
161
+ ```bash
162
+ filecraft merge --mode extension --extension pdf --working-dir ./downloads --working-dir ./desktop --target-dir ./merged
163
+ filecraft merge --mode date --date 2026-03-01 --working-dir ./in1 --working-dir ./in2 --target-dir ./merged
164
+ filecraft merge --mode extension_and_date --extension .jpg --date 2026-03-01 --working-dir ./camera --working-dir ./phone --target-dir ./merged
165
+ filecraft merge --mode file --working-dir ./in1 --working-dir ./in2 --target-dir ./merged
166
+ ```
167
+
168
+ ## Notes
169
+
170
+ - History files are saved with a timestamped name like `.organizer_history_YYYY-MM-DD_HH-MM-SS-ffffff.json`.
171
+ - File collisions are handled safely by appending suffixes like `_1`, `_2`, etc.
172
+ - Compound extensions such as `.tar.gz` are recognized when sorting by file type.
173
+
@@ -0,0 +1,156 @@
1
+ # Filecraft (Python implementation)
2
+
3
+ ![Filecraft Banner](../assets/Filecraft-banner.png)
4
+
5
+ This is the Python implementation of Filecraft, focused on safe and repeatable file organization workflows.
6
+
7
+ ## Requirements
8
+
9
+ - Python `>=3.10,<3.15`
10
+
11
+ ## Distribution
12
+
13
+ - Package target: PyPI (`filecraft-cli`)
14
+ - CLI command: `filecraft`
15
+ - Standalone binary: `Filecraft` (PyInstaller)
16
+
17
+ ## Install / Run
18
+
19
+ ### Option 1: Local development (Poetry)
20
+
21
+ ```bash
22
+ poetry install
23
+ poetry run filecraft --help
24
+ ```
25
+
26
+ ### Option 2: PyPI install (release users)
27
+
28
+ ```bash
29
+ pip install filecraft-cli
30
+ filecraft --help
31
+ ```
32
+
33
+ ### Option 2: Run module directly (without installing script)
34
+
35
+ ```bash
36
+ PYTHONPATH=src python -m file_organiser_python.main --help
37
+ ```
38
+
39
+ ## Commands
40
+
41
+ - `filecraft rename`
42
+ - `filecraft separate`
43
+ - `filecraft merge`
44
+ - `filecraft revert`
45
+
46
+ All commands support `--dry-run` to preview actions without moving files.
47
+ Working directory flags are validated before any `--target-dir` creation prompt.
48
+
49
+ ## `rename`
50
+
51
+ Renames files in `working_dir` and moves them to `target_dir`.
52
+ By default names are numeric (`1.ext`, `2.ext`, ...); with `--rename-with` they become prefixed (`name_1.ext`, `name_2.ext`, ...).
53
+
54
+ ### Options
55
+
56
+ - `--working-dir PATH` (default: current directory)
57
+ - `--target-dir PATH` (default: current directory)
58
+ - `--dry-run`
59
+ - `--history` (save history file for revert)
60
+ - `--rename-with TEXT` (optional base name prefix, e.g. `invoice`)
61
+
62
+ If `--target-dir` is provided and does not exist, the CLI prompts to create it (`y/n`).
63
+ Declining exits with a `--target-dir` error.
64
+
65
+ ### Rename Example
66
+
67
+ ```bash
68
+ filecraft rename --working-dir ./downloads --target-dir ./renamed --history
69
+ filecraft rename --working-dir ./downloads --target-dir ./renamed --rename-with invoice
70
+ ```
71
+
72
+ ## `separate`
73
+
74
+ Separates files according to mode.
75
+
76
+ ### Separate Modes
77
+
78
+ - `extension`: Move files of a specific extension (e.g. `.pdf`) into `TARGET/PDF`
79
+ - `date`: Move files modified on a specific date (or today) into `TARGET/YYYY-MM-DD`
80
+ - `extension_and_date`: Combine both filters into `TARGET/YYYY-MM-DD/EXT`
81
+ - `file`: Sort all files by file type category (`IMAGES`, `VIDEOS`, `DOCUMENTS`, `ARCHIVES`, etc.)
82
+
83
+ ### Separate Options
84
+
85
+ - `--mode [extension|date|extension_and_date|file]` (default: `extension`)
86
+ - `--extension TEXT` (required for `extension` and `extension_and_date`)
87
+ - `--file-type TEXT` (optional for `file`; accepts category like `documents` or extension like `pdf`)
88
+ - `--date YYYY-MM-DD` (used by `date` and `extension_and_date`; validated)
89
+ - `--working-dir PATH` (default: current directory)
90
+ - `--target-dir PATH` (default: current directory)
91
+ - `--dry-run`
92
+ - `--history`
93
+
94
+ ### Separate Examples
95
+
96
+ ```bash
97
+ filecraft separate --mode extension --extension pdf --working-dir ./in --target-dir ./out
98
+ filecraft separate --mode date --date 2026-03-01 --working-dir ./in --target-dir ./out
99
+ filecraft separate --mode extension_and_date --extension .jpg --date 2026-03-01 --working-dir ./in --target-dir ./out
100
+ filecraft separate --mode file --working-dir ./in --target-dir ./out
101
+ filecraft separate --mode file --file-type pdf --working-dir ./in --target-dir ./out
102
+ ```
103
+
104
+ ## `revert`
105
+
106
+ Reverts moves using a history file.
107
+
108
+ ### Revert Options
109
+
110
+ - `--directory PATH` (searches latest history in that directory; default: current directory)
111
+ - `--history-file PATH` (use a specific history file)
112
+ - `--dry-run`
113
+ - `--keep-history` (do not delete history file after successful revert)
114
+
115
+ ### Revert Examples
116
+
117
+ ```bash
118
+ filecraft revert --directory ./out
119
+ filecraft revert --history-file ./out/.organizer_history_2026-03-01_12-00-00-123456.json
120
+ ```
121
+
122
+ ## `merge`
123
+
124
+ Merges files from multiple `working_dir` locations into a single `target_dir`.
125
+
126
+ ### Merge Modes
127
+
128
+ - `extension`: Merge files of a specific extension into `TARGET/EXT`
129
+ - `date`: Merge files modified on a specific date (or today) into `TARGET/YYYY-MM-DD`
130
+ - `extension_and_date`: Combine both filters into `TARGET/YYYY-MM-DD/EXT`
131
+ - `file`: Merge all files by file type category (`IMAGES`, `VIDEOS`, `DOCUMENTS`, `ARCHIVES`, etc.)
132
+
133
+ ### Merge Options
134
+
135
+ - `--mode [extension|date|extension_and_date|file]` (default: `extension`)
136
+ - `--extension TEXT` (required for `extension` and `extension_and_date`)
137
+ - `--date YYYY-MM-DD` (used by `date` and `extension_and_date`; validated)
138
+ - `--working-dir PATH` (required, repeat for multiple source directories)
139
+ - `--target-dir PATH` (default: current directory)
140
+ - `--dry-run`
141
+ - `--history`
142
+
143
+ ### Merge Examples
144
+
145
+ ```bash
146
+ filecraft merge --mode extension --extension pdf --working-dir ./downloads --working-dir ./desktop --target-dir ./merged
147
+ filecraft merge --mode date --date 2026-03-01 --working-dir ./in1 --working-dir ./in2 --target-dir ./merged
148
+ filecraft merge --mode extension_and_date --extension .jpg --date 2026-03-01 --working-dir ./camera --working-dir ./phone --target-dir ./merged
149
+ filecraft merge --mode file --working-dir ./in1 --working-dir ./in2 --target-dir ./merged
150
+ ```
151
+
152
+ ## Notes
153
+
154
+ - History files are saved with a timestamped name like `.organizer_history_YYYY-MM-DD_HH-MM-SS-ffffff.json`.
155
+ - File collisions are handled safely by appending suffixes like `_1`, `_2`, etc.
156
+ - Compound extensions such as `.tar.gz` are recognized when sorting by file type.
@@ -0,0 +1,21 @@
1
+ [project]
2
+ name = "filecraft-cli"
3
+ version = "1.0.0"
4
+ description = ""
5
+ authors = [{ name = "MURTAZA PATEL", email = "murtazapatel89100@gmail.com" }]
6
+ readme = "README.md"
7
+ requires-python = ">=3.10,<3.15"
8
+ dependencies = ["typer (>=0.24.1,<0.25.0)"]
9
+
10
+ [tool.poetry]
11
+ packages = [{ include = "file_organiser_python", from = "src" }]
12
+
13
+ [tool.poetry.scripts]
14
+ filecraft = "file_organiser_python.main:app"
15
+
16
+ [build-system]
17
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
18
+ build-backend = "poetry.core.masonry.api"
19
+
20
+ [dependency-groups]
21
+ dev = ["black (>=26.1.0,<27.0.0)", "pyinstaller (==6.19.0)"]
@@ -0,0 +1,171 @@
1
+ # -------------------------
2
+ # Images
3
+ # -------------------------
4
+
5
+ IMAGE_EXTENSIONS = {
6
+ ".jpg",
7
+ ".jpeg",
8
+ ".png",
9
+ ".gif",
10
+ ".bmp",
11
+ ".tiff",
12
+ ".tif",
13
+ ".webp",
14
+ ".svg",
15
+ ".heic",
16
+ ".heif",
17
+ ".ico",
18
+ ".raw",
19
+ ".cr2",
20
+ ".nef",
21
+ ".orf",
22
+ ".sr2",
23
+ ".psd",
24
+ ".ai",
25
+ ".eps",
26
+ }
27
+
28
+ # -------------------------
29
+ # Documents
30
+ # -------------------------
31
+
32
+ DOCUMENT_EXTENSIONS = {
33
+ ".pdf",
34
+ ".doc",
35
+ ".docx",
36
+ ".odt",
37
+ ".rtf",
38
+ ".txt",
39
+ ".md",
40
+ ".pages",
41
+ ".tex",
42
+ }
43
+
44
+ # -------------------------
45
+ # Spreadsheets
46
+ # -------------------------
47
+
48
+ SPREADSHEET_EXTENSIONS = {".xls", ".xlsx", ".xlsm", ".ods", ".csv", ".tsv"}
49
+
50
+ # -------------------------
51
+ # Presentations
52
+ # -------------------------
53
+
54
+ PRESENTATION_EXTENSIONS = {".ppt", ".pptx", ".odp", ".key"}
55
+
56
+ # -------------------------
57
+ # Videos
58
+ # -------------------------
59
+
60
+ VIDEO_EXTENSIONS = {
61
+ ".mp4",
62
+ ".mkv",
63
+ ".mov",
64
+ ".avi",
65
+ ".wmv",
66
+ ".flv",
67
+ ".webm",
68
+ ".mpeg",
69
+ ".mpg",
70
+ ".3gp",
71
+ ".m4v",
72
+ }
73
+
74
+ # -------------------------
75
+ # Audio
76
+ # -------------------------
77
+
78
+ AUDIO_EXTENSIONS = {
79
+ ".mp3",
80
+ ".wav",
81
+ ".aac",
82
+ ".flac",
83
+ ".ogg",
84
+ ".m4a",
85
+ ".wma",
86
+ ".aiff",
87
+ ".alac",
88
+ }
89
+
90
+ # -------------------------
91
+ # Archives / Compressed
92
+ # -------------------------
93
+
94
+ ARCHIVE_EXTENSIONS = {
95
+ ".zip",
96
+ ".rar",
97
+ ".7z",
98
+ ".tar",
99
+ ".gz",
100
+ ".bz2",
101
+ ".xz",
102
+ ".tgz",
103
+ ".tar.gz",
104
+ }
105
+
106
+ # -------------------------
107
+ # Executables
108
+ # -------------------------
109
+
110
+ EXECUTABLE_EXTENSIONS = {
111
+ ".exe",
112
+ ".msi",
113
+ ".apk",
114
+ ".app",
115
+ ".deb",
116
+ ".rpm",
117
+ ".dmg",
118
+ ".bin",
119
+ ".sh",
120
+ ".bat",
121
+ }
122
+
123
+ # -------------------------
124
+ # Code / Programming
125
+ # -------------------------
126
+
127
+ CODE_EXTENSIONS = {
128
+ ".py",
129
+ ".js",
130
+ ".ts",
131
+ ".jsx",
132
+ ".tsx",
133
+ ".java",
134
+ ".c",
135
+ ".cpp",
136
+ ".h",
137
+ ".hpp",
138
+ ".cs",
139
+ ".go",
140
+ ".rs",
141
+ ".php",
142
+ ".rb",
143
+ ".swift",
144
+ ".kt",
145
+ ".html",
146
+ ".css",
147
+ ".scss",
148
+ ".json",
149
+ ".xml",
150
+ ".yaml",
151
+ ".yml",
152
+ ".sql",
153
+ }
154
+
155
+ # -------------------------
156
+ # Fonts
157
+ # -------------------------
158
+
159
+ FONT_EXTENSIONS = {".ttf", ".otf", ".woff", ".woff2", ".eot"}
160
+
161
+ # -------------------------
162
+ # Disk Images
163
+ # -------------------------
164
+
165
+ DISK_IMAGE_EXTENSIONS = {".iso", ".img", ".vhd", ".vmdk"}
166
+
167
+ # -------------------------
168
+ # HISTORY FILE NAME PREFIX
169
+ # -------------------------
170
+
171
+ HISTORY_FILE_PREFIX = ".organizer_history_"
@@ -0,0 +1,8 @@
1
+ from enum import Enum
2
+
3
+
4
+ class SeparateChoices(Enum):
5
+ EXTENSION = "extension"
6
+ DATE = "date"
7
+ EXTENSION_AND_DATE = "extension_and_date"
8
+ FILE = "file"
@@ -0,0 +1,88 @@
1
+ from pathlib import Path
2
+ import json
3
+ from typing import Dict, Optional
4
+
5
+ from file_organiser_python.utils import build_non_conflicting_path
6
+
7
+
8
+ def save_history(
9
+ history_path: Path,
10
+ revert_map: Dict[str, str],
11
+ operation: str = "rename",
12
+ ) -> None:
13
+ data = {
14
+ "operation": operation,
15
+ "mappings": revert_map,
16
+ }
17
+
18
+ history_path.parent.mkdir(parents=True, exist_ok=True)
19
+ with open(history_path, "w", encoding="utf-8") as f:
20
+ json.dump(data, f, indent=4)
21
+
22
+
23
+ def load_latest_history(directory: Path) -> Path | None:
24
+ history_files = list(directory.glob(".organizer_history_*.json"))
25
+
26
+ if not history_files:
27
+ return None
28
+
29
+ return max(history_files, key=lambda f: f.stat().st_mtime)
30
+
31
+
32
+ def read_history(history_path: Path) -> Dict[str, str]:
33
+ with open(history_path, "r", encoding="utf-8") as f:
34
+ data = json.load(f)
35
+
36
+ return data.get("mappings", {})
37
+
38
+
39
+ def delete_history(history_path: Path) -> None:
40
+ if history_path.exists():
41
+ history_path.unlink()
42
+
43
+
44
+ def revert_history(
45
+ history_path: Optional[Path] = None,
46
+ directory: Optional[Path] = None,
47
+ dry_run: bool = False,
48
+ delete_after_revert: bool = True,
49
+ ) -> int:
50
+ if history_path is None:
51
+ if directory is None:
52
+ directory = Path.cwd()
53
+
54
+ history_path = load_latest_history(directory)
55
+ if history_path is None:
56
+ print(f"No history file found in {directory}")
57
+ return 0
58
+
59
+ with open(history_path, "r", encoding="utf-8") as f:
60
+ data = json.load(f)
61
+
62
+ mappings: Dict[str, str] = data.get("mappings", {})
63
+ if not mappings:
64
+ print(f"No mappings found in history file: {history_path}")
65
+ return 0
66
+
67
+ reverted_count = 0
68
+ for current, original in mappings.items():
69
+ current_path = Path(current)
70
+ original_path = Path(original)
71
+
72
+ if not current_path.exists():
73
+ continue
74
+
75
+ if dry_run:
76
+ print(f"[DRY RUN] Would move {current_path} -> {original_path}")
77
+ reverted_count += 1
78
+ continue
79
+
80
+ original_path.parent.mkdir(parents=True, exist_ok=True)
81
+ destination_path = build_non_conflicting_path(original_path)
82
+ current_path.rename(destination_path)
83
+ reverted_count += 1
84
+
85
+ if reverted_count and delete_after_revert and not dry_run:
86
+ delete_history(history_path)
87
+
88
+ return reverted_count