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.
- filecraft_cli-1.0.0/PKG-INFO +173 -0
- filecraft_cli-1.0.0/README.md +156 -0
- filecraft_cli-1.0.0/pyproject.toml +21 -0
- filecraft_cli-1.0.0/src/file_organiser_python/__init__.py +0 -0
- filecraft_cli-1.0.0/src/file_organiser_python/constants.py +171 -0
- filecraft_cli-1.0.0/src/file_organiser_python/enums.py +8 -0
- filecraft_cli-1.0.0/src/file_organiser_python/history.py +88 -0
- filecraft_cli-1.0.0/src/file_organiser_python/main.py +259 -0
- filecraft_cli-1.0.0/src/file_organiser_python/operations.py +599 -0
- filecraft_cli-1.0.0/src/file_organiser_python/organizer.py +211 -0
- filecraft_cli-1.0.0/src/file_organiser_python/utils.py +57 -0
|
@@ -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
|
+

|
|
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
|
+

|
|
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)"]
|
|
File without changes
|
|
@@ -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,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
|