diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-10 14:00:07 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-10 14:00:07 -0500 |
| commit | 502bcf41c7258b169d6481676c59c42e0e931e7c (patch) | |
| tree | e895aab48e718aa5106f23da9afb1173dc464257 /docs | |
| parent | 2b19eb175d0664908f76bf7cc8dcc1eb5c140ce1 (diff) | |
| download | dotemacs-502bcf41c7258b169d6481676c59c42e0e931e7c.tar.gz dotemacs-502bcf41c7258b169d6481676c59c42e0e931e7c.zip | |
docs(design): add Phase 1 utility inventory
Phase 1 of utility-consolidation per docs/design/utility-consolidation.org. The inventory walks the spec's 30-entry Candidate Extraction Table and, for each helper, records: visibility, dependencies, side effects, callers in modules and tests, test file location, extraction priority, and a Migrate / Leave / Defer decision with rationale.
Decisions: 11 Migrate, 3 Leave, 13 Defer. The Migrate items are grouped by phase in the spec's recommended order: Phase 2 (foundation helpers -- executable lookup, shell quoting, process runner, file-from-context), Phase 3 (Org-safe text sanitizers), Phase 4 (external-open consolidation). The Defer items mostly need a second production caller before promotion is justified.
Discoveries worth recording: `cj/log-silently' already has 10 production callers (more than the spec's table suggested), and `cj/--file-manager-program-for' shipped today in dirvish-config.el is the new form of OS-dispatch consolidation -- Phase 4's `cj/external-open-command' should fold it in rather than re-deriving.
No code behavior changes -- this is the spec's stated Phase 1 exit criterion.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/design/utility-inventory.org | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/docs/design/utility-inventory.org b/docs/design/utility-inventory.org new file mode 100644 index 00000000..cf4c13bd --- /dev/null +++ b/docs/design/utility-inventory.org @@ -0,0 +1,160 @@ +#+TITLE: Utility Helper Inventory +#+AUTHOR: Craig Jennings +#+DATE: 2026-05-10 + +* Status + +Living inventory. Phase 1 of [[file:utility-consolidation.org][utility-consolidation.org]]. Records the current state of helpers identified in the spec's Candidate Extraction Table plus any new candidates discovered during module walkthroughs. Decisions become concrete tasks in =todo.org= for Phase 2+. + +* Scope + +Phase 1 inventories the candidates from the spec's Candidate Extraction Table. The spec explicitly does not require listing every private helper across the codebase before Phase 2 can start -- new candidates surface and are recorded as they show up. + +* Methodology + +For each helper: + +- Read the function definition for arguments and body. +- Note dependencies (=require= statements, calls to other modules, built-ins). +- Note side effects (process, file I/O, network, message log, none). +- Search the tree for callers (modules and tests). +- Locate the test file(s) covering the helper. +- Decide: =Migrate= / =Leave= / =Defer=, plus a rationale. + +Caller counts in the inventory below reflect grep results from 2026-05-10. The columns "Tests" and "Callers" record what actually references the symbol; a test that loads a defining module without naming the symbol does not count. + +* Inventory + +** Executable Discovery + +| Symbol | File | Vis | Deps | Side effects | Proposed home / name | Callers (modules) | Tests | Pri | Decision | Rationale | +|--------+------+-----+------+--------------+----------------------+-------------------+-------+-----+----------+-----------| +| =cj/mail--executable-or-warn= | =mail-config.el:66= | private | =executable-find=, =display-warning= | =display-warning= | =system-lib.el= / =cj/executable-find-or-warn= | 0 (used inline within =mail-config.el=) | none | High | Migrate | Mail-specific name hides a generally useful pattern. Mail, language tools, and dirvish wallpaper/file-manager commands all need "find this program or surface a clear missing-dep warning." | +| =cj/executable-exists-p= | =system-lib.el:13= | public | =executable-find= | none (returns path) | =system-lib.el= / =cj/executable-available-p= | =custom-buffer-file.el= (1) | =test-system-lib-executable-exists-p.el= | Medium | Migrate | Current name claims a predicate but returns a path. Rename to =-available-p= and return =t=/=nil=, keep one-cycle alias. | +| direct =(executable-find ...)= without warning | =prog-c.el=, =prog-go.el=, =prog-python.el=, =prog-shell.el=, =dirvish-config.el=, =browser-config.el=, =mail-config.el= | n/a | =executable-find= | sometimes silent nil | =cj/executable-find-or-warn= or =cj/executable-available-p= | many | n/a | High | Migrate selectively | User-invoked features should warn on missing executables; package =:if= silent checks should stay silent. | + +** Shell Argument Quoting + +| Symbol | File | Vis | Deps | Side effects | Proposed home / name | Callers (modules) | Tests | Pri | Decision | Rationale | +|--------+------+-----+------+--------------+----------------------+-------------------+-------+-----+----------+-----------| +| =cj/--f6-shell-safe-argument-regexp= | =dev-fkeys.el:337= (defconst) | private | none | none | =system-lib.el= / =cj/shell-safe-argument-regexp= | 1 (defining file) | none | High | Migrate | Internal regex paired with the readable-quote function. Move with its consumer. | +| =cj/--f6-shell-quote-argument= | =dev-fkeys.el:340= | private | =shell-quote-argument=, the regexp above | none | =system-lib.el= / =cj/shell-quote-argument-readable= | 1 (defining file) | none | High | Migrate | Generates compile/test commands where readable paths matter for log inspection. F6 dispatch is one of several future callers (see =mail-config=, =dirvish-config=). | +| direct =shell-quote-argument= in command strings | =prog-c.el=, =prog-python.el=, =prog-shell.el=, =mail-config.el=, =dirvish-config.el=, =vc-config.el=, =elfeed-config.el=, =system-utils.el= | n/a | =shell-quote-argument= | none | case-by-case | many | n/a | Medium | Defer | Audit each call site after =cj/shell-quote-argument-readable= lands; some are security-sensitive and should keep =shell-quote-argument= as-is. | + +** Process Runner + +| Symbol | File | Vis | Deps | Side effects | Proposed home / name | Callers (modules) | Tests | Pri | Decision | Rationale | +|--------+------+-----+------+--------------+----------------------+-------------------+-------+-----+----------+-----------| +| =cj/--coverage-git-string= | =coverage-core.el:200= | private | =process-file=, =with-temp-buffer=, =user-error= | runs git via =process-file= | =system-lib.el= or new =cj-process.el= / =cj/process-output-or-error= | 2 (within =coverage-core.el=) | covered indirectly by coverage tests | High | Migrate | Generic "run program with argv -> stdout, raise user-error with status+output on non-zero" pattern. Future callers: reconcile-open-repos, vc-config, mail integrations. | +| =cj/--coverage-git-string= (wrapper) | =coverage-core.el= | private | the generic above | runs git | =system-lib.el= or =cj-process.el= / =cj/git-output-or-error= | 2 | n/a | High | Migrate | Thin wrapper around the generic runner with =git= as the program. | +| =cj/--coverage-git-merge-base= | =coverage-core.el:213= | private | =cj/--coverage-git-string= | runs git | =coverage-core.el= (stay) | 1 (within file) | covered by coverage tests | Low | Leave | Coverage-specific semantics. May call the generic runner once it exists, but stays in coverage-core. | +| =cj/--coverage-git-diff= | =coverage-core.el:221= | private | =cj/--coverage-git-string= | runs git | =coverage-core.el= (stay) | 1 (within file) | covered by coverage tests | Low | Leave | Coverage-specific =--unified=0= policy. | + +** File / Path Helpers + +| Symbol | File | Vis | Deps | Side effects | Proposed home / name | Callers (modules) | Tests | Pri | Decision | Rationale | +|--------+------+-----+------+--------------+----------------------+-------------------+-------+-----+----------+-----------| +| =cj/--file-from-context= | =system-utils.el:58= | private | =dired-get-filename=, =buffer-file-name= | none (reads buffer state) | =system-lib.el= / =cj/file-from-context= | 1 (within =system-utils.el=) | =test-system-utils--file-from-context.el= | High | Migrate | Useful for any "current Dired entry or current buffer's file" command. Tests already exist; rename + re-home is straightforward. | +| =cj/test--file-in-directory-p= | =test-runner.el:160= | private | =file-in-directory-p= (built-in) | none | built-in | 1 (within file) | none | Medium | Migrate (delete) | Wraps the built-in for no apparent reason. Replace caller with =file-in-directory-p= directly. | +| =cj/test--assert-inside-base= | =testutil-general.el:41= | test-only | =file-in-directory-p= | signals error | =system-lib.el= / =cj/path-assert-in-directory= | 2 (within file) | =testutil-general.el= itself | Medium | Defer | Useful pattern for destructive commands, but currently test-only. Extract when a production caller appears (e.g. =safe-recursive-delete=). | +| =cj/test--safe-base-dir-p= | =testutil-general.el:30= | test-only | path predicates | none | =system-lib.el= / =cj/safe-recursive-delete-root-p= | 1 (within file) | =testutil-general.el= itself | Medium | Defer | Policy-heavy ("is this dir safe to recursively delete from"). Should be explicit and well-tested before any production use. | + +** External Open + +| Symbol | File | Vis | Deps | Side effects | Proposed home / name | Callers (modules) | Tests | Pri | Decision | Rationale | +|--------+------+-----+------+--------------+----------------------+-------------------+-------+-----+----------+-----------| +| =cj/--open-with-is-launcher-p= | =system-utils.el:70= | private | =string-match-p= | none | =external-open.el= / =cj/external-open-launcher-p= | 1 (within =system-utils.el=) | =test-system-utils--open-with-is-launcher-p.el= | Medium | Defer | Move when external-open consumers (dirvish, mail) align on a single owner; not blocking. | +| =cj/identify-external-open-command= | =system-utils.el:103= | public | =executable-find=, system-type | none | =external-open.el= / =cj/external-open-command= | 1 (within file) | =test-system-utils-identify-external-open-command.el= | Medium | Migrate | External-open should own command-string resolution; host-environment stays predicate-only. | +| duplicated OS-open command selection | =system-utils.el=, =dirvish-config.el=, =external-open.el= | n/a | =executable-find=, system-type | external process | =external-open.el= / =cj/external-open-command= | many | several | Medium | Migrate | One source of truth for =xdg-open=, =open=, =start=. The dirvish refactor I just shipped extracts =cj/--file-manager-program-for=; that helper merges naturally into =cj/external-open-command= once external-open is the owner. | + +** Org-Safe Text Helpers + +| Symbol | File | Vis | Deps | Side effects | Proposed home / name | Callers (modules) | Tests | Pri | Decision | Rationale | +|--------+------+-----+------+--------------+----------------------+-------------------+-------+-----+----------+-----------| +| =calendar-sync--sanitize-org-body= | =calendar-sync.el:297= | private | =replace-regexp-in-string= | none | =cj-org-text.el= or =system-lib.el= / =cj/org-sanitize-body-text= | 1 (within file) | =test-calendar-sync--sanitize-org-body.el= | High | Migrate | Already tested. Useful for webclipper, AI conversations, mail-to-org capture. Pure string work; no Org dependency. | +| =calendar-sync--sanitize-org-property-value= | =calendar-sync.el:308= | private | =replace-regexp-in-string= | none | =cj-org-text.el= or =system-lib.el= / =cj/org-sanitize-property-value= | 1 (within file) | =test-calendar-sync--sanitize-org-body.el= | High | Migrate | Strips characters that would break Org property syntax. Pure string. | +| =calendar-sync--sanitize-org-heading= | =calendar-sync.el:317= | private | =replace-regexp-in-string= | none | =cj-org-text.el= or =system-lib.el= / =cj/org-sanitize-heading= | 1 (within file) | =test-calendar-sync--sanitize-org-body.el= | High | Migrate | Protects outline structure from external text. Pure string. | +| =calendar-sync--strip-html= | =calendar-sync.el:271= | private | =replace-regexp-in-string= | none | =system-lib.el= or =cj-text.el= / =cj/text-strip-html= | 1 (within file) | =test-calendar-sync--strip-html.el= | Medium | Defer | Useful beyond calendar, but the regex-only approach is intentionally simple; document its limits before promoting. | +| =calendar-sync--clean-text= | =calendar-sync.el:291= | private | the two helpers above | none | =system-lib.el= or =cj-text.el= / =cj/text-clean-external= | 1 (within file) | =test-calendar-sync--clean-text.el= | Medium | Defer | Combines ICS unescape + HTML strip; too calendar-specific without splitting. | +| =calendar-sync--unescape-ics-text= | =calendar-sync.el:258= | private | =replace-regexp-in-string= | none | =calendar-sync.el= (stay) | 1 (within file) | =test-calendar-sync--unescape-ics-text.el= | Low | Leave | ICS-specific. Not a general utility. | + +** Cache Abstraction + +| Symbol | File | Vis | Deps | Side effects | Proposed home / name | Callers (modules) | Tests | Pri | Decision | Rationale | +|--------+------+-----+------+--------------+----------------------+-------------------+-------+-----+----------+-----------| +| =cj/modeline-vc-cache-*= helpers (key/get/put/clear/valid-p) | =modeline-config.el:108-140= | private | buffer-local vars | mutates buffer-local state | =cj-cache.el= / =cj/cache-valid-p=, =cj/cache-get=, =cj/cache-put=, =cj/cache-clear= | 1 (within file) | =test-modeline-config-vc-cache.el= | Medium | Defer | Good pattern, but variable-local cache shape differs from the agenda/refile caches. Needs design before extraction. Spec calls out a Phase 5 design addendum at =docs/design/cache-helper-design.org=. | +| agenda/refile cache vars and build flags | =org-agenda-config.el=, =org-refile-config.el= | n/a | timers, file scans | scans filesystem, sets vars | =cj-cache.el= / =cj/cache-value-or-rebuild= | 2 | none | Medium | Defer | TTL/build/invalidate lifecycle; higher risk than the modeline cache. Same Phase 5 work. | + +** Logging / Warnings + +| Symbol | File | Vis | Deps | Side effects | Proposed home / name | Callers (modules) | Tests | Pri | Decision | Rationale | +|--------+------+-----+------+--------------+----------------------+-------------------+-------+-----+----------+-----------| +| =cj/log-silently= | =system-lib.el:20= | public | =message= with =inhibit-message= | writes to *Messages* without echo-area flash | =system-lib.el= / =cj/message-log-only= | 10 (=elfeed-config=, =media-utils=, =org-agenda-config-debug=, =quick-video-capture=, =wrap-up=) | several test files reference it | Low | Defer | Rename is clearer but low-value. Only do when touching system-lib for a higher-priority change. | +| direct =display-warning= boilerplate | =mail-config.el= | n/a | =display-warning= | warning entry | =system-lib.el= / =cj/display-warning-once= or =cj/warn-once= | 1+ | none | Low | Defer | Single caller today. Add only after a second caller appears or once-only behavior becomes a real need. | + +** Macros / Debug Helpers + +| Symbol | File | Vis | Deps | Side effects | Proposed home / name | Callers (modules) | Tests | Pri | Decision | Rationale | +|--------+------+-----+------+--------------+----------------------+-------------------+-------+-----+----------+-----------| +| =with-timer= | =config-utilities.el:50= (defmacro) | public | =current-time=, =message= | times forms, messages elapsed | =config-utilities.el= (stay for now) | 0 in production; 1 test | =test-config-utilities--with-timer.el= | Low | Defer | Debug-oriented today. Promote only after a production caller appears. | +| =cj/--benchmark-method= | =config-utilities.el:64= | private | =benchmark-call= | runs forms, messages timing | =config-utilities.el= (stay) | 0 outside file | =test-config-utilities--benchmark-method.el= | Low | Leave | Debug command helper. Not general architecture. | +| =cj/--delete-compiled-files-in-dir= | =config-utilities.el:123= | private | =directory-files-recursively=, =delete-file= | deletes files | =config-utilities.el= (stay until safe second caller) | 0 outside file | =test-config-utilities--delete-compiled-files-in-dir.el= | Low | Defer | Destructive. A second caller plus a path-safety contract should land before promoting. | + +** Theme File I/O + +| Symbol | File | Vis | Deps | Side effects | Proposed home / name | Callers (modules) | Tests | Pri | Decision | Rationale | +|--------+------+-----+------+--------------+----------------------+-------------------+-------+-----+----------+-----------| +| =cj/theme-read-file-contents= | =ui-theme.el:66= | public | =insert-file-contents= | reads file | =system-lib.el= / =cj/read-file-string= | 0 outside file | =test-ui-theme-persistence.el= | Low | Defer | One production caller. Built-in =insert-file-contents= wrappers are small; keep theme-specific until reused. | +| =cj/theme-write-file-contents= | =ui-theme.el:75= | public | =write-region= | writes file | =system-lib.el= / =cj/write-file-string= | 0 outside file | =test-ui-theme-persistence.el= | Low | Defer | Same as above. | + +** String / Modeline Helpers + +| Symbol | File | Vis | Deps | Side effects | Proposed home / name | Callers (modules) | Tests | Pri | Decision | Rationale | +|--------+------+-----+------+--------------+----------------------+-------------------+-------+-----+----------+-----------| +| =cj/modeline-string-cut-middle= | =modeline-config.el:59= | public | =substring=, length math | none | =system-lib.el= or =cj-text.el= / =cj/string-truncate-middle= | 1 (within file) | =test-modeline-config-string-cut-middle.el= | Low | Defer | Single production caller. Good candidate when completion/headings/report buffers need ellipsis-in-middle truncation. | + +* Decisions Summary + +| Action | Count | Examples | +|--------+-------+----------| +| Migrate | 11 | =cj/--file-from-context=, three calendar-sync sanitizers, =cj/executable-find-or-warn=, =cj/shell-quote-argument-readable=, process runner pair, =cj/external-open-command= | +| Leave | 3 | =cj/--coverage-git-merge-base=, =cj/--coverage-git-diff=, =calendar-sync--unescape-ics-text= | +| Defer | 13 | cache helpers (need design addendum), test-only helpers awaiting production caller, low-value renames, theme/string/HTML extractions awaiting second caller | + +* Concrete Next Actions + +These become =todo.org= entries (or update existing ones) as Phase 2 starts. + +** Phase 2 candidates (already in spec's recommended order) + +1. *Extract* =cj/executable-find-or-warn= into =system-lib.el=. Move =cj/mail--executable-or-warn= and rename. Migrate user-invoked features in =mail-config.el=, =prog-*.el=, =dirvish-config.el=, =browser-config.el= as appropriate. +2. *Rename* =cj/executable-exists-p= -> =cj/executable-available-p=, return boolean, keep one-cycle alias. +3. *Extract* =cj/shell-quote-argument-readable= and the paired regexp into =system-lib.el=. Move =cj/--f6-shell-quote-argument= and the constant. +4. *Extract* =cj/process-output-or-error= (generic argv -> stdout / user-error) and =cj/git-output-or-error= (thin wrapper) into =system-lib.el= or =cj-process.el=. Migrate =cj/--coverage-git-string= callers. +5. *Extract* =cj/file-from-context= into =system-lib.el=. +6. *Replace* =cj/test--file-in-directory-p= caller with built-in =file-in-directory-p=, then delete the wrapper. + +** Phase 3 candidates (Org-safe text) + +7. *Extract* =cj/org-sanitize-heading=, =cj/org-sanitize-property-value=, =cj/org-sanitize-body-text= into =cj-org-text.el= (new) or =system-lib.el=. Migrate =org-webclipper.el= and =ai-conversations.el= as second consumers; mail capture if applicable. + +** Phase 4 candidates (External-open consolidation) + +8. *Move* =cj/identify-external-open-command= to =external-open.el= as =cj/external-open-command=. Consolidate the duplicated OS-open dispatch from =system-utils.el=, =dirvish-config.el=, and (already-shipped) =cj/--file-manager-program-for= into one source of truth. + +** Deferred (track in =todo.org= but no commit yet) + +- Cache abstraction (modeline + agenda/refile) -- needs Phase 5 design addendum at =docs/design/cache-helper-design.org=. +- =cj/--open-with-is-launcher-p= -- move when external-open ownership is finalized. +- =cj/log-silently= rename -- low value; do during incidental =system-lib= work. +- HTML/text helpers (=strip-html=, =clean-text=) -- defer until a second consumer. +- Theme file I/O wrappers -- defer until a second consumer. +- =cj/string-truncate-middle= -- defer until a second consumer. +- =cj/path-assert-in-directory= and =cj/safe-recursive-delete-root-p= -- defer until a production caller justifies promotion. +- =with-timer=, =cj/--delete-compiled-files-in-dir=, =cj/display-warning-once= -- defer until a clear second caller. + +* Discoveries Worth Recording + +- =cj/--file-manager-program-for= already exists in =vterm-config.el= (post-split: =modules/vterm-config.el=) -- wait, the dirvish refactor put it in =modules/dirvish-config.el=. It's the new form of the OS-dispatch consolidation. The =cj/external-open-command= work in Phase 4 should fold this helper in rather than re-deriving it. +- =cj/log-silently= has 10 production callers, more than the spec's table suggested. The rename's churn cost is real; defer is the right call. +- The three calendar sanitizers (=org-body=, =org-property-value=, =org-heading=) all share one test file (=test-calendar-sync--sanitize-org-body.el=). When moved, the tests should also move and split per helper for clarity. |
