diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 465993d..5d2bd46 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -10,10 +10,10 @@ Requirements for initial release. Each maps to roadmap phases. ### Driver Management - [ ] **DRV-01**: User can upload a driver package (ZIP containing INF + supporting files) -- [ ] **DRV-02**: System parses uploaded INF files and extracts valid driver names (DriverDesc) +- [x] **DRV-02**: System parses uploaded INF files and extracts valid driver names (DriverDesc) - [ ] **DRV-03**: User can select driver name from parsed INF dropdown (no free-text) - [ ] **DRV-04**: Driver packages are persisted on Docker volume across container restarts -- [ ] **DRV-05**: System flags unused files in driver packages to help reduce package size +- [x] **DRV-05**: System flags unused files in driver packages to help reduce package size ### Printer Configuration @@ -86,10 +86,10 @@ Which phases cover which requirements. Updated during roadmap creation. | INFRA-01 | Phase 1 | Complete | | INFRA-02 | Phase 1 | Complete | | DRV-01 | Phase 2 | Pending | -| DRV-02 | Phase 2 | Pending | +| DRV-02 | Phase 2 | Complete | | DRV-03 | Phase 2 | Pending | | DRV-04 | Phase 2 | Pending | -| DRV-05 | Phase 2 | Pending | +| DRV-05 | Phase 2 | Complete | | PRNT-01 | Phase 3 | Pending | | PRNT-02 | Phase 3 | Pending | | PRNT-03 | Phase 3 | Pending | diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 0a429be..21d4f13 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -108,7 +108,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| | 1. Foundation | 3/3 | Complete | 2026-04-10 | -| 2. Driver Management | 0/2 | Not started | - | +| 2. Driver Management | 1/2 | In Progress| | | 3. Printer Configuration | 0/3 | Not started | - | | 4. Script Generation | 0/3 | Not started | - | | 5. Package Export | 0/3 | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 050aec4..7bc1c6e 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,14 +3,14 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: executing -stopped_at: Completed 01-02-PLAN.md -last_updated: "2026-04-10T09:36:14.222Z" +stopped_at: Completed 02-01-PLAN.md +last_updated: "2026-04-10T09:57:42.006Z" last_activity: "2026-04-10 — Plan 01-01 complete: Docker scaffold, FastAPI app shell, sidebar templates, 3-test green suite" progress: total_phases: 5 completed_phases: 1 - total_plans: 3 - completed_plans: 3 + total_plans: 5 + completed_plans: 4 percent: 7 --- @@ -51,6 +51,7 @@ Progress: [░░░░░░░░░░] 7% *Updated after each plan completion* | Phase 01-foundation P01-02 | 3 | 2 tasks | 7 files | +| Phase 02 P01 | 3 | 1 tasks | 6 files | ## Accumulated Context @@ -68,6 +69,8 @@ Recent decisions affecting current work: - [Phase 01-foundation]: Deferred SqliteDatabase(None) pattern so tests can patch DB_PATH via monkeypatch without module reload - [Phase 01-foundation]: Full 4-table schema created upfront in phase 1 — later phases add routes/logic only, no schema changes - [Phase 01-foundation]: init_db() added to lifespan (not @app.on_event) consistent with 01-01 established pattern +- [Phase 02]: optionxform=str on RawConfigParser preserves DriverDesc key casing; strings dict uses lowercase keys for case-insensitive token lookup +- [Phase 02]: RawConfigParser(strict=False) required for real INFs that have duplicate option keys within sections ### Pending Todos @@ -80,6 +83,6 @@ None yet. ## Session Continuity -Last session: 2026-04-10T09:33:07.637Z -Stopped at: Completed 01-02-PLAN.md +Last session: 2026-04-10T09:57:42.003Z +Stopped at: Completed 02-01-PLAN.md Resume file: None diff --git a/.planning/phases/02-driver-management/02-01-SUMMARY.md b/.planning/phases/02-driver-management/02-01-SUMMARY.md new file mode 100644 index 0000000..e8bab85 --- /dev/null +++ b/.planning/phases/02-driver-management/02-01-SUMMARY.md @@ -0,0 +1,94 @@ +--- +phase: "02" +plan: "01" +subsystem: inf-parser +tags: [tdd, inf-parsing, encoding-detection, token-resolution, driver-management] +dependency_graph: + requires: [] + provides: [inf-parser-service] + affects: [02-02-upload-endpoint, 02-03-drivers-ui] +tech_stack: + added: [] + patterns: [RawConfigParser-strict-false, BOM-sniffing, optionxform-str, set-dedup-sorted] +key_files: + created: + - imptune/services/__init__.py + - imptune/services/inf_parser.py + - tests/test_inf_parser.py + - tests/fixtures/sample.inf + - tests/fixtures/sample_utf16.inf + - tests/fixtures/sample_multi_model.inf + modified: [] +decisions: + - "optionxform=str on RawConfigParser to preserve DriverDesc key casing; strings dict still uses lowercased keys for case-insensitive %TOKEN% lookup" + - "configparser.RawConfigParser(strict=False) avoids DuplicateOptionError on real INFs with repeated model entries" + - "UTF-16 fixture written as binary via Python encode('utf-16') — not as a text file — to guarantee correct BOM bytes" +metrics: + duration: "~2.5 min" + completed: "2026-04-10" + tasks: 1 + files: 6 +--- + +# Phase 02 Plan 01: INF Parser Service Summary + +**One-liner:** stdlib configparser + BOM-sniffing INF parser with %TOKEN% resolution, multi-model deduplication, architecture detection, and unused-file flagging. + +## What Was Built + +`imptune/services/inf_parser.py` — a pure-function INF parser with: + +- `ParsedInf` dataclass exposing `driver_names`, `inf_filename`, `architecture`, `has_cat_file`, `unused_files` +- `_detect_encoding(raw: bytes) -> str` — BOM-sniffing: `\xff\xfe`/`\xfe\xff` -> `utf-16`, `\xef\xbb\xbf` -> `utf-8-sig`, else `cp1252` +- `_resolve_tokens(value, strings)` — regex `%([^%]+)%` expansion +- `parse_inf(inf_text, inf_filename, zip_names) -> ParsedInf` — `RawConfigParser(strict=False, delimiters=('=',))` with `optionxform=str`; [Manufacturer] -> Models section discovery; NTamd64/NTarm64/NTx86/undecorated detection; set-based dedup; sorted output + +Three fixture files support the test suite: `sample.inf` (ANSI with %TOKEN%), `sample_utf16.inf` (UTF-16 LE BOM binary), `sample_multi_model.inf` (NTamd64 + undecorated sections). + +## Tasks + +| # | Task | Status | Commit | +|---|------|--------|--------| +| 1 | INF parser with TDD (RED then GREEN) | Complete | 290106d (RED), 5056922 (GREEN) | + +## Test Results + +- 16 tests in `tests/test_inf_parser.py` — all pass +- Full suite: 40 tests pass, 0 failures, 0 regressions + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] configparser key lowercasing mangled DriverDesc literal names** +- **Found during:** Task 1, GREEN phase (first test run) +- **Issue:** configparser defaults `optionxform = str.lower`, so the literal key `Acme SuperPrint 9000` was returned as `acme superprint 9000`. The test `assert "Acme SuperPrint 9000" in result.driver_names` failed. +- **Fix:** Set `parser.optionxform = str` to preserve original casing of option keys. The [Strings] dict still explicitly lowercases keys (`strings[key.lower()]`) for case-insensitive token resolution. +- **Files modified:** `imptune/services/inf_parser.py` +- **Commit:** 5056922 + +**Note:** The plan specified `strict=False` and `RawConfigParser` correctly but did not mention `optionxform=str`. This is a real-INF edge case documented in the pitfalls section of 02-RESEARCH.md (implicitly — the note says "Strings dict keys must be lowercased" without clarifying that DriverDesc keys also get lowercased by default). + +### Test Count Deviation + +The plan specified 11 test functions; 16 were written. The extra 5 cover: +- `test_detect_encoding_utf16be` (UTF-16 BE BOM variant) +- `test_architecture_detection_amd64` (split from the combined architecture test) +- `test_architecture_detection_arm64` +- `test_architecture_detection_undecorated` +- `test_architecture_detection_mixed` + +This provides more granular failure diagnosis and meets the `min_lines: 80` artifact requirement. + +## Self-Check + +- [x] `imptune/services/inf_parser.py` exists +- [x] `imptune/services/__init__.py` exists +- [x] `tests/test_inf_parser.py` exists (>80 lines) +- [x] `tests/fixtures/sample.inf` exists +- [x] `tests/fixtures/sample_utf16.inf` exists (UTF-16 LE BOM binary) +- [x] `tests/fixtures/sample_multi_model.inf` exists +- [x] RED commit: 290106d +- [x] GREEN commit: 5056922 + +## Self-Check: PASSED