mimedy 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,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ name: Lint and format
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v7
14
+ - uses: astral-sh/setup-uv@v10.2.0
15
+ - run: uv run --locked ruff check
16
+ - run: uv run --locked ruff format --check
17
+
18
+ test:
19
+ name: Tests (Python ${{ matrix.python-version }})
20
+ runs-on: ubuntu-latest
21
+ strategy:
22
+ # Run every version even if one fails, to see the whole picture
23
+ fail-fast: false
24
+ matrix:
25
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
26
+ steps:
27
+ - uses: actions/checkout@v7
28
+ - uses: astral-sh/setup-uv@v10.2.0
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+ - run: uv run --locked pytest
@@ -0,0 +1,25 @@
1
+ name: Publish
2
+
3
+ # Runs when a release is published on GitHub (e.g. tag v1.0.0)
4
+ on:
5
+ release:
6
+ types: [published]
7
+
8
+ jobs:
9
+ publish:
10
+ name: Publish to PyPI
11
+ runs-on: ubuntu-latest
12
+ environment:
13
+ name: pypi
14
+ url: https://pypi.org/p/mimedy
15
+ permissions:
16
+ # Lets PyPI verify this workflow's identity: no password or token needed
17
+ id-token: write
18
+ contents: read
19
+ steps:
20
+ - uses: actions/checkout@v7
21
+ - uses: astral-sh/setup-uv@v10.2.0
22
+ # Never publish a version that fails its own tests
23
+ - run: uv run --locked pytest
24
+ - run: uv build
25
+ - run: uv publish --trusted-publishing always
@@ -0,0 +1,25 @@
1
+ # Personal config (see config.example.yaml)
2
+ config.yaml
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.py[cod]
7
+ *.egg-info/
8
+ build/
9
+ dist/
10
+
11
+ # Virtual environment
12
+ .venv/
13
+
14
+ # Tooling caches
15
+ .pytest_cache/
16
+ .ruff_cache/
17
+ .mypy_cache/
18
+ .coverage
19
+ htmlcov/
20
+
21
+ # OS / editors
22
+ .DS_Store
23
+ Thumbs.db
24
+ .vscode/
25
+ .idea/
@@ -0,0 +1,7 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.16.9
4
+ hooks:
5
+ - id: ruff-check
6
+ args: [--fix]
7
+ - id: ruff-format
@@ -0,0 +1 @@
1
+ 3.10
mimedy-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Olivier Chapeau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
mimedy-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,383 @@
1
+ Metadata-Version: 2.5
2
+ Name: mimedy
3
+ Version: 1.0.0
4
+ Summary: Organize messy folders by real file type, detected from content with Google Magika
5
+ Project-URL: Homepage, https://github.com/ochapeau/mimedy
6
+ Project-URL: Repository, https://github.com/ochapeau/mimedy
7
+ Project-URL: Issues, https://github.com/ochapeau/mimedy/issues
8
+ Project-URL: Releases, https://github.com/ochapeau/mimedy/releases
9
+ Author: Olivier Chapeau
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: cli,downloads,files,magika,mime,mimetype,organizer
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: System :: Filesystems
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: magika>=0.6.2
27
+ Requires-Dist: pyyaml>=6.0.2
28
+ Requires-Dist: typer>=0.16
29
+ Description-Content-Type: text/markdown
30
+
31
+ # 🗂️ mimedy
32
+
33
+ [![CI](https://github.com/ochapeau/mimedy/actions/workflows/ci.yml/badge.svg)](https://github.com/ochapeau/mimedy/actions/workflows/ci.yml)
34
+
35
+ **Range un dossier en désordre selon le vrai type de chaque fichier, pas selon son extension.**\
36
+ **Tidies up a messy folder by each file's real type, not by its extension.**
37
+
38
+ *mimedy = **MIME** + **tidy** (ranger) : le type MIME de chaque fichier décide de sa place.*\
39
+ *mimedy = **MIME** + **tidy**: each file's MIME type decides where it belongs.*
40
+
41
+ [Français](#français) · [English](#english)
42
+
43
+ ---
44
+
45
+ <a id="français"></a>
46
+
47
+ ## 🇫🇷 Français
48
+
49
+ ### Pourquoi ?
50
+
51
+ Un dossier `Téléchargements` finit toujours par ressembler à ça : des PDF, des captures d'écran, des archives, un `.json` exporté un jour, un fichier sans extension dont personne ne se souvient…
52
+
53
+ La plupart des outils de rangement se fient à l'extension. Or une extension peut mentir, manquer ou être fausse. **mimedy** utilise [Magika](https://github.com/google/magika), le modèle de deep learning de Google qui identifie un fichier **à partir de son contenu**, pour décider où il doit aller.
54
+
55
+ ```text
56
+ $ mimedy ~/Downloads
57
+ [INFO] Loaded config from /Users/me/.config/mimedy/config.yaml
58
+ [INFO] Organizing /Users/me/Downloads
59
+ [INFO] 'export.csv' → Data/
60
+ [INFO] 'holidays.jpg' → Photos/
61
+ [INFO] 'invoice' → PDF/
62
+ [INFO] 'report.pdf' → PDF/report (1).pdf
63
+ [INFO] 'script.py' → Python/
64
+ [WARNING] Skipped 'locked.txt': Magika could not read the file (permission_error)
65
+ [INFO] 5 files to move into 4 folders, 1 skipped
66
+ Move 5 files? [y/N]: y
67
+ [INFO] Done: 5 moved, 0 failed
68
+ ```
69
+
70
+ ### Fonctionnalités
71
+
72
+ - 🧠 **Détection par le contenu** : Magika reconnaît plus de 200 types de fichiers, même renommés ou sans extension.
73
+ - 🪜 **Règles en cascade** : fichiers cachés, fichiers volumineux, extensions, types MIME, puis le groupe Magika en dernier recours.
74
+ - 📋 **Plan puis confirmation** : tous les déplacements sont affichés avant d'être effectués, et rien ne bouge sans votre accord. `--dry-run` s'arrête au plan.
75
+ - 🧯 **Robuste** : un fichier illisible ou verrouillé est signalé et ignoré, sans interrompre le rangement.
76
+ - 🛡️ **Aucun écrasement** : si `photo.jpg` existe déjà, le nouveau fichier devient `photo (1).jpg`.
77
+ - 🐛 **Mode `--verbose`** : indique pour chaque fichier la règle qui a décidé de sa destination.
78
+ - ⚙️ **Configuration YAML** simple, entièrement facultative.
79
+
80
+ ### Installation
81
+
82
+ Prérequis : Python 3.10+ et [uv](https://docs.astral.sh/uv/).
83
+
84
+ ```bash
85
+ git clone https://github.com/ochapeau/mimedy.git
86
+ cd mimedy
87
+ uv sync
88
+ ```
89
+
90
+ ### Utilisation
91
+
92
+ > 💡 mimedy affiche toujours le plan complet et demande confirmation avant de déplacer quoi que ce soit.
93
+
94
+ ```bash
95
+ # Afficher le plan, puis confirmer
96
+ uv run mimedy ~/Downloads
97
+
98
+ # Afficher le plan seulement
99
+ uv run mimedy ~/Downloads --dry-run
100
+
101
+ # Sans confirmation, par exemple dans un script
102
+ uv run mimedy ~/Downloads --yes --config my-config.yaml
103
+
104
+ # Comprendre pourquoi un fichier va à tel endroit
105
+ uv run mimedy ~/Downloads --dry-run --verbose
106
+ ```
107
+
108
+ | Option | Description |
109
+ | :--- | :--- |
110
+ | `DIRECTORY` | Dossier à ranger *(obligatoire)*. |
111
+ | `--config`, `-c PATH` | Fichier de configuration YAML *(par défaut : voir [Configuration](#configuration))*. |
112
+ | `--dry-run`, `-n` | Affiche les déplacements prévus sans les effectuer. |
113
+ | `--yes`, `-y` | Déplace sans demander de confirmation. |
114
+ | `--verbose`, `-v` | Affiche la règle appliquée à chaque fichier. |
115
+ | `--lowercase`, `-l` | Garde en minuscules les dossiers nommés d'après Magika (`video/` au lieu de `Video/`). |
116
+ | `--version`, `-V` | Affiche la version. |
117
+ | `--help`, `-h` | Affiche l'aide. |
118
+ | `--init-config` | Crée une configuration d'exemple commentée à l'emplacement par défaut (sans jamais écraser une configuration existante). |
119
+ | `--install-completion` | Active l'autocomplétion des options avec Tab dans votre shell (une seule fois suffit). |
120
+
121
+ Seuls les fichiers situés directement dans le dossier sont traités. Les sous-dossiers existants ne sont pas touchés, ce qui permet de relancer l'outil sans risque.
122
+
123
+ | Code de sortie | Signification |
124
+ | :-: | :--- |
125
+ | `0` | Tout s'est bien passé, ou il n'y avait rien à ranger. |
126
+ | `1` | Au moins un fichier n'a pas pu être traité, ou la confirmation a été refusée. |
127
+ | `2` | Arguments ou configuration invalides. |
128
+
129
+ ### Configuration
130
+
131
+ Sans `--config`, mimedy lit le fichier de configuration personnel, s'il existe :
132
+
133
+ | Système | Emplacement |
134
+ |---|---|
135
+ | Linux, macOS | `~/.config/mimedy/config.yaml` (ou `$XDG_CONFIG_HOME/mimedy/config.yaml`) |
136
+ | Windows | `%APPDATA%\mimedy\config.yaml` |
137
+
138
+ S'il n'existe pas, les valeurs par défaut sont utilisées. Un fichier passé avec `--config` doit en revanche exister. `mimedy --help` affiche l'emplacement exact sur votre machine.
139
+
140
+ Pour démarrer, créez une configuration d'exemple commentée, puis adaptez-la :
141
+
142
+ ```bash
143
+ mimedy --init-config
144
+ # Created config file: /Users/me/.config/mimedy/config.yaml
145
+ ```
146
+
147
+ ```yaml
148
+ hidden: "Hidden" # fichiers commençant par '.'
149
+
150
+ large_files:
151
+ threshold_mb: 500 # en Mo décimaux, comme le Finder ou l'Explorateur
152
+ target_dir: "Large"
153
+
154
+ extensions: # correspondance exacte sur l'extension
155
+ .blend: "Blender"
156
+ .csv: "Data"
157
+
158
+ mimetypes: # type MIME détecté par Magika
159
+ application/pdf: "PDF"
160
+ image/jpeg: "Photos"
161
+ ```
162
+
163
+ Toutes les clés sont facultatives : celles qui manquent reprennent leur valeur par défaut. Les destinations peuvent contenir des sous-dossiers (`"Code/Python"`). [`config.example.yaml`](src/mimedy/config.example.yaml), le fichier copié par `--init-config`, contient un exemple complet et commenté.
164
+
165
+ ### Comment un fichier est-il classé ?
166
+
167
+ Les règles sont évaluées dans cet ordre ; **la première qui correspond l'emporte** :
168
+
169
+ | # | Règle | Exemple |
170
+ |:-:|:---|:---|
171
+ | 1 | Fichier caché | `.env` → `Hidden/` |
172
+ | 2 | Fichier volumineux | `film.mkv` (2 Go) → `Large/` |
173
+ | 3 | Extension | `scene.blend` → `Blender/` |
174
+ | 4 | Type MIME (Magika) | `facture` *(PDF sans extension)* → `PDF/` |
175
+ | 5 | Groupe Magika | `script.py` → `Code/` |
176
+
177
+ Les règles 1 à 3 ne lisent pas le contenu des fichiers : elles sont instantanées. Magika n'est appelé que si elles ne suffisent pas.
178
+
179
+ ### Structure du projet
180
+
181
+ ```text
182
+ mimedy/
183
+ ├── pyproject.toml
184
+ ├── src/mimedy/
185
+ │ ├── main.py # Point d'entrée de la CLI (Typer)
186
+ │ ├── config.py # Chargement et validation de la configuration
187
+ │ ├── config.example.yaml # Configuration d'exemple commentée
188
+ │ ├── organizer.py # Règles de classement, planification et déplacements
189
+ │ └── errors.py # Exceptions du projet
190
+ └── tests/ # Tests pytest (configuration, règles, plan, CLI)
191
+ ```
192
+
193
+ ### Développement
194
+
195
+ Le code est vérifié par [Ruff](https://docs.astral.sh/ruff/) (lint et formatage) à chaque commit, et testé avec [pytest](https://docs.pytest.org/). L'intégration continue lance les deux à chaque push, sur Python 3.10 à 3.13.
196
+
197
+ ```bash
198
+ uv run pre-commit install # vérifications automatiques à chaque commit
199
+ uv run pytest # lancer les tests
200
+ ```
201
+
202
+ ### Feuille de route
203
+
204
+ - [x] Configuration typée et validée
205
+ - [x] Configuration globale dans `~/.config/mimedy/`
206
+ - [x] Tests automatisés et intégration continue
207
+ - [ ] Publication sur PyPI (`uv tool install mimedy`)
208
+ - [ ] Interface en terminal (TUI), en option
209
+
210
+ ### Licence
211
+
212
+ Distribué sous licence MIT. Voir [LICENSE](LICENSE).
213
+
214
+ ---
215
+
216
+ <a id="english"></a>
217
+
218
+ ## 🇬🇧 English
219
+
220
+ ### Why?
221
+
222
+ A `Downloads` folder always ends up looking the same: PDFs, screenshots, archives, a `.json` exported at some point, a file with no extension that nobody remembers…
223
+
224
+ Most organizing tools trust file extensions. But an extension can lie, be missing, or simply be wrong. **mimedy** uses [Magika](https://github.com/google/magika), Google's deep learning model that identifies a file **from its content**, to decide where it belongs.
225
+
226
+ ```text
227
+ $ mimedy ~/Downloads
228
+ [INFO] Loaded config from /Users/me/.config/mimedy/config.yaml
229
+ [INFO] Organizing /Users/me/Downloads
230
+ [INFO] 'export.csv' → Data/
231
+ [INFO] 'holidays.jpg' → Photos/
232
+ [INFO] 'invoice' → PDF/
233
+ [INFO] 'report.pdf' → PDF/report (1).pdf
234
+ [INFO] 'script.py' → Python/
235
+ [WARNING] Skipped 'locked.txt': Magika could not read the file (permission_error)
236
+ [INFO] 5 files to move into 4 folders, 1 skipped
237
+ Move 5 files? [y/N]: y
238
+ [INFO] Done: 5 moved, 0 failed
239
+ ```
240
+
241
+ ### Features
242
+
243
+ - 🧠 **Content-based detection**: Magika recognizes over 200 file types, even when renamed or missing an extension.
244
+ - 🪜 **Cascading rules**: hidden files, large files, extensions, MIME types, then the Magika group as a fallback.
245
+ - 📋 **Plan, then confirm**: every move is shown before it happens, and nothing moves without your approval. `--dry-run` stops at the plan.
246
+ - 🧯 **Robust**: an unreadable or locked file is reported and skipped, without stopping the run.
247
+ - 🛡️ **Never overwrites**: if `photo.jpg` already exists, the new file becomes `photo (1).jpg`.
248
+ - 🐛 **`--verbose` mode**: shows which rule decided each file's destination.
249
+ - ⚙️ **Simple YAML configuration**, entirely optional.
250
+
251
+ ### Installation
252
+
253
+ Requirements: Python 3.10+ and [uv](https://docs.astral.sh/uv/).
254
+
255
+ ```bash
256
+ git clone https://github.com/ochapeau/mimedy.git
257
+ cd mimedy
258
+ uv sync
259
+ ```
260
+
261
+ ### Usage
262
+
263
+ > 💡 mimedy always shows the full plan and asks for confirmation before moving anything.
264
+
265
+ ```bash
266
+ # Show the plan, then confirm
267
+ uv run mimedy ~/Downloads
268
+
269
+ # Only show the plan
270
+ uv run mimedy ~/Downloads --dry-run
271
+
272
+ # No confirmation, e.g. in a script
273
+ uv run mimedy ~/Downloads --yes --config my-config.yaml
274
+
275
+ # Understand why a file goes where it goes
276
+ uv run mimedy ~/Downloads --dry-run --verbose
277
+ ```
278
+
279
+ | Option | Description |
280
+ | :--- | :--- |
281
+ | `DIRECTORY` | Folder to organize *(required)*. |
282
+ | `--config`, `-c PATH` | YAML configuration file *(default: see [Configuration](#configuration-1))*. |
283
+ | `--dry-run`, `-n` | Show planned moves without performing them. |
284
+ | `--yes`, `-y` | Move without asking for confirmation. |
285
+ | `--verbose`, `-v` | Show which rule applied to each file. |
286
+ | `--lowercase`, `-l` | Keep folders named after Magika groups lowercase (`video/` instead of `Video/`). |
287
+ | `--version`, `-V` | Show the version. |
288
+ | `--help`, `-h` | Show the help. |
289
+ | `--init-config` | Create a commented example config at the default location (never overwrites an existing one). |
290
+ | `--install-completion` | Enable Tab completion of the options in your shell (once is enough). |
291
+
292
+ Only files directly inside the folder are processed. Existing subfolders are left untouched, so the tool can safely be run again.
293
+
294
+ | Exit code | Meaning |
295
+ | :-: | :--- |
296
+ | `0` | Everything went fine, or there was nothing to organize. |
297
+ | `1` | At least one file could not be processed, or the confirmation was declined. |
298
+ | `2` | Invalid arguments or configuration. |
299
+
300
+ ### Configuration
301
+
302
+ Without `--config`, mimedy reads your personal configuration file, if it exists:
303
+
304
+ | System | Location |
305
+ |---|---|
306
+ | Linux, macOS | `~/.config/mimedy/config.yaml` (or `$XDG_CONFIG_HOME/mimedy/config.yaml`) |
307
+ | Windows | `%APPDATA%\mimedy\config.yaml` |
308
+
309
+ If it doesn't exist, the defaults are used. A file passed with `--config`, however, must exist. `mimedy --help` shows the exact location on your machine.
310
+
311
+ To get started, create a commented example config, then adapt it:
312
+
313
+ ```bash
314
+ mimedy --init-config
315
+ # Created config file: /Users/me/.config/mimedy/config.yaml
316
+ ```
317
+
318
+ ```yaml
319
+ hidden: "Hidden" # files starting with '.'
320
+
321
+ large_files:
322
+ threshold_mb: 500 # decimal MB, like Finder or Explorer
323
+ target_dir: "Large"
324
+
325
+ extensions: # exact extension match
326
+ .blend: "Blender"
327
+ .csv: "Data"
328
+
329
+ mimetypes: # MIME type detected by Magika
330
+ application/pdf: "PDF"
331
+ image/jpeg: "Photos"
332
+ ```
333
+
334
+ Every key is optional: missing ones fall back to their default value. Destinations may include subfolders (`"Code/Python"`). See [`config.example.yaml`](src/mimedy/config.example.yaml), the file copied by `--init-config`, for a complete, commented example.
335
+
336
+ ### How is a file classified?
337
+
338
+ Rules are evaluated in this order; **the first match wins**:
339
+
340
+ | # | Rule | Example |
341
+ |:-:|:---|:---|
342
+ | 1 | Hidden file | `.env` → `Hidden/` |
343
+ | 2 | Large file | `movie.mkv` (2 GB) → `Large/` |
344
+ | 3 | Extension | `scene.blend` → `Blender/` |
345
+ | 4 | MIME type (Magika) | `invoice` *(PDF with no extension)* → `PDF/` |
346
+ | 5 | Magika group | `script.py` → `Code/` |
347
+
348
+ Rules 1 to 3 don't read file contents, so they are instant. Magika only runs when they aren't enough.
349
+
350
+ ### Project structure
351
+
352
+ ```text
353
+ mimedy/
354
+ ├── pyproject.toml
355
+ ├── src/mimedy/
356
+ │ ├── main.py # CLI entry point (Typer)
357
+ │ ├── config.py # Configuration loading and validation
358
+ │ ├── config.example.yaml # Commented example configuration
359
+ │ ├── organizer.py # Classification rules, planning and moves
360
+ │ └── errors.py # Project exceptions
361
+ └── tests/ # pytest tests (configuration, rules, plan, CLI)
362
+ ```
363
+
364
+ ### Development
365
+
366
+ Code is checked by [Ruff](https://docs.astral.sh/ruff/) (linting and formatting) on every commit, and tested with [pytest](https://docs.pytest.org/). Continuous integration runs both on every push, on Python 3.10 to 3.13.
367
+
368
+ ```bash
369
+ uv run pre-commit install # automatic checks on every commit
370
+ uv run pytest # run the tests
371
+ ```
372
+
373
+ ### Roadmap
374
+
375
+ - [x] Typed, validated configuration
376
+ - [x] Global configuration in `~/.config/mimedy/`
377
+ - [x] Automated tests and continuous integration
378
+ - [ ] Publish on PyPI (`uv tool install mimedy`)
379
+ - [ ] Optional terminal interface (TUI)
380
+
381
+ ### License
382
+
383
+ Released under the MIT License. See [LICENSE](LICENSE).