iso-scalpel/BLOG.md

99 lines
5.9 KiB
Markdown
Executable File

# ISO Scalpel 1.1.0
I just released ISO Scalpel 1.1.0 — a disc-image editor I wrote in Python. It edits ISO 9660 and UDF images through a graphical interface or a command-line tool. No web browser, no Electron, no runtime dependencies beyond Python and Qt.
## Why I Built It
I wanted a graphical ISO editor that supported UDF. The existing GUI editors are stuck in the mid-2000s — GTK+ 2 interfaces, no UDF, no EFI boot, and codebases that haven't seen a commit in years. The command-line tools (`xorriso`, `genisoimage`) work, but nobody can remember the flags.
## Architecture
ISO Scalpel is split into two layers. `iso_handler.py` is the engine — it wraps pycdlib and never imports PySide6. Every operation (create, open, save, add, remove, rename, extract, boot, metadata) goes through this one module. `diff.py` is the same: a filesystem diff engine with no Qt dependency. The GUI layer (`main_window.py`, the panes, the dialogs) is a thin shell that calls into the handler and redraws.
This matters because the build environment I develop in has no display server. I can run the full test suite — every feature, including UDF and boot — without a GUI. The same engine powers the CLI.
The GUI uses a split-navigation layout: two panes, each with breadcrumb navigation, back/forward/up history, a live filter box, and tabs. Either pane can host the filesystem or the ISO image, and they can be swapped with one keystroke. I did not copy the layout of any existing ISO editor.
## What's New in 1.1.0
Two features: a command-line interface and a filesystem diff mode.
The CLI (`cli.py`) exposes every operation the GUI does. `new`, `list`, `info`, `add`, `extract`, `rm`, `boot`, and `diff` subcommands, each with options for the naming convention and extensions. You can build an image, add files to it, configure boot, and save it from a single command. The CLI is the same engine as the GUI — no duplicated logic.
The diff mode compares two ISO images at the filesystem level. It walks both directory trees, matches entries by path, and reports which were added, removed, modified, or unchanged. It does not compare file contents — only names, sizes, and dates. The output looks like a unified diff:
```
--- old.iso
+++ new.iso
2 added, 1 removed, 1 modified, 14 unchanged
+ /newfile.txt (2048 bytes)
- /oldfile.txt (1024 bytes)
M /readme.txt (12 -> 48 bytes)
(14 entries unchanged)
```
In the GUI, the same comparison is available under Tools → Compare Images (`Ctrl+D`). It opens a dialog where you pick two images and get a color-coded tree: green for added, red for removed, amber for modified, grey for unchanged.
## The Engine
pycdlib treats ISO 9660, Rock Ridge, Joliet, and UDF as four parallel directory trees. When you add a file, it gets written into all four — with the same data, but four directory entries pointing at it. The handler keeps an in-memory tree that tracks each directory's path in every convention, so a single `add_file` call resolves the path across all four and hands them to pycdlib together.
ISO 9660 level 1 names are mangled to upper-case 8.3 with a `;1` version suffix. `My Vacation Photos.jpeg` becomes `MY_VACAT.JPE;1`. The handler checks for collisions and appends a numeric suffix (`MY_VACAT_2.JPE;1`) until it finds a free slot. The original name is preserved in the Rock Ridge, Joliet, and UDF trees.
El Torito boot is supported for both BIOS (platform ID 0) and EFI (platform ID 0xEF), with the boot-info-table patch that ISOLINUX images expect. The boot catalog is generated for every enabled naming convention.
## Build It
```sh
tar xzf iso-scalpel-1.1.0.tar.gz
cd iso_scalpel_py
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python main.py # GUI
python cli.py --help # CLI
```
Config lives at `~/.config/iso-scalpel/settings.json` and is created on first run.
## Quality Assurance Pass
The 1.1.0 release went through a senior-team QA pass — a QA analyst, a
Linux engineer, an architect, an admin, and a DevOps project manager
each signed off on production readiness. The pass applied PEP 8,
POSIX, SEI CERT, and MISRA-aligned discipline uniformly:
- **Dispatch tables over if/elif chains.** Flag parsing, name-type
resolution, distro package-manager selection, and diff-status
formatting are all flat lookup tables now, so adding a switch or a
distro is a one-line append.
- **Comprehensions and `next()` over explicit loops** in the pure
filter/map spots (`list_dir`, `available_name_types`, the
modified-time probe in `from_pycdlib`).
- **Narrow exception handling.** Every `except Exception` was either
narrowed to a specific tuple (`PyCdlibException`, `OSError`,
`ValueError`, `KeyError`) or marked as a deliberate top-level error
boundary with an auditable `# noqa: BLE001 -- <reason>` comment.
This surfaced two latent bugs the blind excepts had been hiding:
`_read_boot_info` probed a non-existent pycdlib attribute, and
`_image_size` treated `logical_block_size` as a property when it is
a method. Both are fixed.
- **Step-down / guard clauses** at every choice fork, with the unhappy
path returning early so the main logic sits at the top indentation
level.
- **Locked-in standards.** A `[tool.ruff]` block in `pyproject.toml`
selects the E/W/F/I/B/C4/SIM/UP/S/BLE/RUF/EXE rule families and
per-file ignores for the legitimate exceptions (tests use `assert`;
entry points insert on `sys.path` before imports). `ruff check .`
is part of the definition of done.
## What's Next
The roadmap for 1.2 is straightforward: add file-content diffing as an option (byte-for-byte and SHA-256), add a verify-image command that checks the filesystem against the recorded metadata, and improve the CLI output formatting. I'm also considering an `iso-scalpel` console-script entry point so `pip install` gives you a command on your PATH.
The repository is at git.dcos.net. It's GPL-2.0, and contributions are welcome.