Skip to content

feat: interactive session with tabular output - #9

Merged
iamteedoh merged 1 commit into
mainfrom
DIR-3-interactive-session-table
Jul 17, 2026
Merged

feat: interactive session with tabular output#9
iamteedoh merged 1 commit into
mainfrom
DIR-3-interactive-session-table

Conversation

@iamteedoh

Copy link
Copy Markdown
Owner

What does this PR do?

Follow-on to #7. Once a path resolved, the tool was still awkward to actually use: every single check meant relaunching the script and retyping every answer, and the results were flat lines that got hard to scan past a handful of paths.

Changes

  • The session stays open. After a result it asks for another path, reusing the class and permission already chosen, until you press q. Only the fully interactive run loops — supplying everything via flags still checks once and exits, which is what keeps the tool usable from cron and CI.
  • An all option for permissions, matching the one the who prompt already had. --perm all (or a at the prompt) checks read, write and execute together; combined with --all it gives the full 3×3 matrix of every permission against every class.
  • Results render as a table: bordered, with headers, one row per path, and alternating row shading so neighbouring rows stay distinct. YES/NO remain green/red.
┌─────────────┬─────────────┬─────────────┬─────────────┬────────────┐
│             │    Owner    │    Group    │    Other    │            │
│ Path        │  R   W   X  │  R   W   X  │  R   W   X  │ Mode       │
├─────────────┼─────────────┼─────────────┼─────────────┼────────────┤
│ /etc/passwd │  ✓   ✓   ·  │  ✓   ·   ·  │  ✓   ·   ·  │ -rw-r--r-- │
└─────────────┴─────────────┴─────────────┴─────────────┴────────────┘

A bug this surfaced

Driving the session interactively caught something worth calling out, because it refines a rule #7 had just documented. #7 said "a regular file is read as a list of paths" — so typing /etc/passwd, the single most obvious thing anyone would permission-check, parsed its contents as paths:

│ nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false │ — │ does not exist │
│ root:*:0:0:System Administrator:/var/root:/bin/sh          │ — │ does not exist │

A regular file is now only read as a list when it looks like one — its first meaningful line being a path (/, ~, $). --file still forces the list reading unconditionally. The README documents the rule.

Design notes for review

  • Checking and rendering are now separate. Rows are collected into arrays first so the table can size each column to the widest value it is about to print.
  • Column widths use explicit display widths, not ${#s}. ${#s} counts bytes outside a UTF-8 locale, so an em dash would measure 3 columns instead of 1 and skew every border. Alignment is verified under LC_ALL=C.
  • Zebra striping sets the background once per row and closes it at end of line. Anything colored inside a row returns to the default foreground with a fg-only reset rather than a full reset, which would drop the row's background partway along the line.

Related issue

DIR-3

Validation

git ls-files '*.sh' | xargs shellcheck          # clean
git ls-files '*.sh' | xargs -n1 bash -n         # clean
./tests/run_tests.sh                            # 47 passed, 0 failed
gitleaks git . --config .gitleaks.toml          # no leaks found
  • 47 tests pass, up from 33, including 11 pty-driven tests. The session loop, the q exit, the table, and the row striping only exist with a real terminal, so tests/interactive_test.py drives them through a pseudo-terminal.
  • Table assertions replace the YES/NO line-format assertions the previous suite used, since this changes that output format.
  • Driven end-to-end on macOS (bash 3.2): the session loops, reuses who/perm, reports a bad path without exiting, and quits cleanly on q.
  • Rebased onto main after feat: check paths directly, with tilde/variable expansion and prompt history #7 merged; main's post-squash tree was identical to the pre-squash tree, so the replay was conflict-free. Verified green on the new base.

Checklist

  • git ls-files '*.sh' | xargs shellcheck passes
  • git ls-files '*.sh' | xargs -n1 bash -n passes
  • gitleaks reports no secrets in Git history
  • Scripts and programs include a GPL-3.0-or-later SPDX header
  • No secrets, tokens, credentials, or private infrastructure details are committed
  • Documentation is updated for user-visible or operational changes
  • The PR title follows Conventional Commits

Checking one path meant relaunching the script and retyping every answer.
Once a result is printed, ask for another path instead of exiting, reusing the
class and permission already chosen, until the user presses q. Only the fully
interactive run loops; supplying everything via flags still checks once and
exits, which is what keeps the tool usable from cron and CI.

Add an "all" option to the permission prompt and to --perm, matching the one
the who prompt already had. Combined with --all this gives the full 3x3 matrix
of every permission against every class.

Report results as a bordered table with headers rather than flat lines, one row
per path, shading alternate rows so neighbouring rows stay distinct. Checking
and rendering are now separate: rows are collected first so the table can size
each column to the widest value it is about to print. Column widths are
computed from explicit display widths rather than ${#s}, which counts bytes
outside a UTF-8 locale and would skew every border once a check mark or an em
dash appears in a cell.

Also stop mistaking a path for a list of paths. Reading /etc/passwd as a list
of paths and reporting that "root:*:0:0:..." does not exist is useless, so a
regular file is only read as a list when it looks like one — its first
meaningful line being a path. --file still forces the list reading.

Cover the table, the matrix, the disambiguation, and the session loop, and add
pty cases for the loop, the q exit, and the row striping.
@iamteedoh
iamteedoh merged commit e1d5e08 into main Jul 17, 2026
3 checks passed
@iamteedoh
iamteedoh deleted the DIR-3-interactive-session-table branch July 17, 2026 00:52
iamteedoh pushed a commit that referenced this pull request Jul 17, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.3.0](v0.2.0...v0.3.0)
(2026-07-17)


### Features

* check paths directly, with tilde/variable expansion and prompt history
([#7](#7))
([c7f3389](c7f3389))
* interactive session with tabular output
([#9](#9))
([e1d5e08](e1d5e08))


### Bug Fixes

* report the real version from --version
([#10](#10))
([0d87357](0d87357))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
iamteedoh added a commit that referenced this pull request Jul 17, 2026
## What does this PR do?

Three releases of behavior landed (#7, #9, #10) and parts of the README
still described the tool as it was before. This audits the whole file
against what v0.3.0 actually does.

### Documents tab completion

The prompt has completed paths ever since readline arrived alongside the
arrow-key history, and nothing said so — which means nobody would ever
try it. Adds a keys table under **Interactive**: `Tab`, `↑`/`↓`,
`Ctrl-A`/`E`/`W`/`U`/`R`, `q`.

The table records what was **verified over a pty**, not what readline is
assumed to do. Two claims were corrected during testing:

- An earlier draft said "press Tab twice to list the candidates". That
is false for `read -e` — it fills the common prefix and beeps without
listing. Re-tested on a 120×40 pty in case readline was suppressing the
list on a 0×0 terminal; it was not.
- The session's history never touches your shell history (`HISTFILE` is
cleared), so that is now stated rather than left to guesswork.

### Corrects what had gone stale

| Was | Now |
|---|---|
| Overview: prompts "for the input file and the permission to check" | a
session taking a path *or* a list file, looping until `q` |
| Feature bullet advertising `owner rwx \| group r-x \| other r--` |
that format no longer exists — replaced with the `Mode` column |
| "Interactive prompts guide the user to select the input file" |
predated `--path` |
| "prompts for the rest when a terminal is attached" | had drifted under
the **Paths vs. lists** heading during earlier edits, reading as though
it were about disambiguation — moved back |
| Prerequisites | now name the Bash 3.2 floor (what macOS ships), the
tty the session needs, and that `getent`/`dscl` are only for *another*
user's `~` |
| Installation: "save the script content to a file" | predated the repo
having releases — clone or download the release |

### Adds an Exit codes section

The README recommends the tool for cron and CI but never documented that
**a denied permission exits `0`** — only usage errors exit `2`. Anyone
writing `dirPathPerms.sh … || alert` would get silence forever. Now
documented, with a grep-based recipe for acting on the result.

## Related issue

DIR-5

## Validation

Documentation-only, so the value is in whether the claims are **true**.
Every runnable claim was executed rather than eyeballed:

- **All 16 documented flags** exercised (`-P`, `--path`, `-f`, `--file`,
`-w` × 4, `-p` × 5, `-a`/`--all`, `--no-color`, `-h`, `-V`, positional,
repeatable `-P`) — all pass.
- **Exit codes verified** against the new table: denied → `0`; missing
list file, unknown option, `--file`+`--path`, and no-tty-no-path → `2`.
- **The CI recipe was run**: it fires on a group-writable directory and
stays silent when nothing matches.
- **Tab completion driven over a pty** for `/etc/pass`, `/var/lo`,
`~/Doc`.
- All README internal anchors resolve.
- Repo gate unchanged and green: shellcheck, `bash -n`, 47/47 tests,
gitleaks.

## Notes for review

Two things deliberately **not** done here:

- **Symlink caveat.** Verifying a README example surfaced a real bug —
`/tmp` reports the symlink's mode (`lrwxr-xr-x`) instead of the target's
(`drwxrwxrwt`), so the tool answers "not group-writable" when it is.
Raised as **DIR-6**. Documenting it as a caveat would enshrine a bug
that should be fixed instead; say the word if you'd rather ship the
caveat.
- **The hero image** in `assets/` still shows `v0.1.0` and the old `File
path:` prompt — the exact UX #7 removed. It needs re-capturing from a
real terminal; there is no terminal-to-image tooling available to
regenerate it here.

`docs:` — README-only, so release-please will not bump the version for
it.

## Checklist

- [x] `git ls-files '*.sh' | xargs shellcheck` passes
- [x] `git ls-files '*.sh' | xargs -n1 bash -n` passes
- [x] gitleaks reports no secrets in Git history
- [x] Scripts and programs include a GPL-3.0-or-later SPDX header
- [x] No secrets, tokens, credentials, or private infrastructure details
are committed
- [x] Documentation is updated for user-visible or operational changes
- [x] The PR title follows Conventional Commits
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant