Add extension normalization command

This commit is contained in:
Rogee
2025-10-30 10:31:53 +08:00
parent f66c59fd57
commit 6a353b5086
35 changed files with 2306 additions and 2 deletions

View File

@@ -0,0 +1,111 @@
# Feature Specification: Extension Command for Multi-Extension Normalization
**Feature Branch**: `004-extension-rename`
**Created**: 2025-10-29
**Status**: Draft
**Input**: User description: "实现扩展名修改Extension命令类似于 replace 命令,可以支持把多个扩展名更改为一个指定的扩展名"
## Clarifications
### Session 2025-10-30
- Q: Should extension comparisons treat casing uniformly or follow the host filesystem? → A: Always case-insensitive
- Q: How should hidden files be handled when `--hidden` is omitted? → A: Exclude hidden entries
- Q: What exit behavior should occur when no files match the given extensions? → A: Exit 0 with notice
- Q: How should files that already have the target extension be represented? → A: Preview as no-change; skip on apply
## User Scenarios & Testing _(mandatory)_
### User Story 1 - Normalize Legacy Extensions in Bulk (Priority: P1)
As a power user cleaning project assets, I want a command to replace multiple file extensions with a single standardized extension so that I can align legacy files (e.g., `.jpeg`, `.JPG`) without hand-editing each one.
**Why this priority**: Delivers the core business value—fast extension normalization across large folders.
**Independent Test**: In a sample directory containing `.jpeg`, `.JPG`, and `.png`, run `renamer extension .jpeg .JPG .png .jpg --dry-run`, verify preview shows the new `.jpg` extension for each, then apply with `--yes` and confirm filesystem updates.
**Acceptance Scenarios**:
1. **Given** files with extensions `.jpeg` and `.JPG`, **When** the user runs `renamer extension .jpeg .JPG .jpg`, **Then** the preview lists each file with the `.jpg` extension and apply renames successfully.
2. **Given** nested directories, **When** the user adds `--recursive`, **Then** all matching extensions in subdirectories are normalized while unrelated files remain untouched.
---
### User Story 2 - Automation-Friendly Extension Updates (Priority: P2)
As an operator integrating renamer into CI scripts, I want deterministic exit codes, ledger entries, and undo support when changing extensions so automated jobs remain auditable and recoverable.
**Why this priority**: Ensures enterprise workflows can rely on extension updates without risking data loss.
**Independent Test**: Run `renamer extension .yaml .yml .yml --yes --path ./fixtures`, verify exit code `0`, inspect `.renamer` ledger for recorded operations, and confirm `renamer undo` restores originals.
**Acceptance Scenarios**:
1. **Given** a non-interactive environment with `--yes`, **When** the command completes without conflicts, **Then** exit code is `0` and ledger metadata captures original extension list and target extension.
2. **Given** a ledger entry exists, **When** `renamer undo` runs, **Then** all files revert to their prior extensions even if the command was executed by automation.
---
### User Story 3 - Validate Extension Inputs and Conflicts (Priority: P3)
As a user preparing an extension migration, I want validation and preview warnings for invalid tokens, duplicate target names, and no-op operations so I can adjust before committing changes.
**Why this priority**: Reduces support load from misconfigured commands and protects against accidental overwrites.
**Independent Test**: Run `renamer extension .mp3 .MP3 mp3 --dry-run`, confirm validation fails because tokens must include leading `.`, and run a scenario where resulting filenames collide to ensure conflicts abort the apply step.
**Acceptance Scenarios**:
1. **Given** invalid input (e.g., missing leading `.` or fewer than two arguments), **When** the command executes, **Then** it exits with non-zero status and prints actionable guidance.
2. **Given** two files that would become the same path after extension normalization, **When** the preview runs, **Then** conflicts are listed and the apply step refuses to proceed until resolved.
---
### Edge Cases
- How does the rename plan surface conflicts when multiple files map to the same normalized extension?
- When the target extension already matches some files, they appear in preview with a “no change” indicator and are skipped during apply without raising errors.
- Hidden files and directories remain excluded unless the user supplies `--hidden`.
- When no files match in preview or apply, the command must surface a “no candidates found” notice while completing successfully.
## Requirements _(mandatory)_
### Functional Requirements
- **FR-001**: CLI MUST provide a dedicated `extension` subcommand that accepts one or more source extensions and a single target extension as positional arguments.
- **FR-002**: Preview → confirm workflow MUST mirror existing commands: list original paths, proposed paths, and highlight extension changes before apply.
- **FR-003**: Executions MUST append ledger entries capturing original extension list, target extension, affected files, and timestamps to support undo.
- **FR-004**: Users MUST be able to undo the most recent extension batch via existing undo mechanics without leaving orphaned files.
- **FR-005**: Command MUST respect global scope flags (`--path`, `--recursive`, `--include-dirs`, `--hidden`, `--extensions`, `--dry-run`, `--yes`) consistent with `list` and `replace`, excluding hidden files and directories unless `--hidden` is explicitly supplied.
- **FR-006**: Extension parsing MUST require leading `.` tokens, deduplicate case-insensitively, warn when duplicates or no-op tokens are supplied, and compare file extensions case-insensitively across all platforms.
- **FR-007**: Preview MUST detect target conflicts (two files mapping to the same new path) and block apply until conflicts are resolved.
- **FR-008**: Invalid invocations (e.g., fewer than two arguments, empty tokens after trimming) MUST exit with non-zero status and provide remediation tips.
- **FR-009**: Help output MUST clearly explain argument order, sequential evaluation rules, and interaction with scope flags.
- **FR-010**: When no files match the provided extensions, preview and apply runs MUST emit a clear “no candidates found” message and exit with status `0`.
- **FR-011**: Preview MUST surface already-targeted files with a “no change” marker, and apply MUST skip them while returning success.
### Key Entities
- **ExtensionRequest**: Working directory, ordered source extension list, target extension, scope flags, dry-run/apply settings.
- **ExtensionSummary**: Totals for candidates, changed files, per-extension match counts, conflicts, and warning messages used for preview and ledger metadata.
## Success Criteria _(mandatory)_
### Measurable Outcomes
- **SC-001**: Users normalize 500 files extensions (preview + apply) in under 2 minutes end-to-end.
- **SC-002**: 95% of beta testers correctly supply arguments (source extensions + target) after reading `renamer extension --help` without additional guidance.
- **SC-003**: Automated regression tests confirm extension change + undo leave the filesystem unchanged in 100% of scripted scenarios.
- **SC-004**: Support tickets related to inconsistent file extensions drop by 30% within the first release cycle after launch.
## Assumptions
- Command name will be `renamer extension` to align with existing verb-noun conventions.
- Source extensions are literal matches with leading dots; wildcard or regex patterns remain out of scope.
- Target extension must include a leading dot and is applied case-sensitively as provided.
- Existing traversal, summary, and ledger infrastructure can be extended from the replace/remove commands.
## Dependencies & Risks
- Requires new extension-specific packages analogous to replace/remove for parser, engine, summary, and CLI wiring.
- Help/quickstart documentation must be updated to explain argument order and extension validation.
- Potential filename conflicts after normalization must be detected pre-apply to avoid overwriting files.