Skip to content

Add filter command, expand Ball public API, and unpack -O#6

Open
gillisd wants to merge 21 commits into
masterfrom
feature/expand-ball-public-api
Open

Add filter command, expand Ball public API, and unpack -O#6
gillisd wants to merge 21 commits into
masterfrom
feature/expand-ball-public-api

Conversation

@gillisd

@gillisd gillisd commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a codeball filter subcommand plus supporting Ball public-API
additions and an unpack -O (dump-to-stdout) option, and brings the
branch to a clean, fully-tested, mergeable state.

Features

  • codeball filter PATTERNS... [FILE] — keep or drop entries in a ball by
    shell-style glob (File::FNM_PATHNAME | FNM_EXTGLOB, so * stops at /,
    **/ recurses, {rb,py} brace-expands). -v/--inverse keeps
    non-matching entries. Reads the ball from a FILE argument or stdin.
  • unpack -O — write extracted contents to stdout instead of files
    (raw bytes, analogous to tar -Ox).
  • Expanded Ball/Entry API (remove_entry, each_entry/each_text_entry,
    serialize, etc.) underpinning the above.

Notable fixes made while getting this merge-ready

  • filter FILE/stdin bug: it previously chose FILE-vs-stdin from stdin.tty?,
    so the documented filter PATTERN FILE form silently failed in any
    non-interactive context (pipes, scripts, CI) — the file was swallowed as a
    pattern and empty stdin was read. Now the trailing arg is treated as the ball
    file when it names a real file, else stdin is read. Input is read without
    Kernel#open (no Security/Open).
  • Entry#valid? regression: a DRY refactor had dropped the body check, so
    an entry with a matching header+footer but no body was both valid? and
    truncated?. Restored the body guard.
  • unpack -O fidelity: switched putsprint so raw bytes are emitted
    without an appended newline (true tar -Ox parity), including for binary.
  • Removed throwaway tar2ball.rb/tarscript.rb experiment scripts; ordered the
    gemspec dependencies; excluded local planning docs from VCS/lint.

Testing

bundle exec rake is fully green: Minitest 0 failures, RSpec 239 examples, 0
failures
, RuboCop 52 files, 0 offenses. New integration specs cover
filter (file/stdin, glob semantics, -v, empty input, nonexistent trailing
arg) and unpack -O / not-found / explicit -, plus a unit test for the
Entry#valid? body requirement.

Follow-ups (non-blocking)

  • Ball.load_file raises a bare RuntimeError; consider Errno::ENOENT.
  • No direct unit tests yet for Ball#remove_entry's String-identifier path or
    the Entry#name= setter.
  • Sequencing: this branch predates the released-minitest-reporters change
    (separate PR). Whichever merges second will need a trivial Gemfile rebase.

gillisd added 16 commits July 2, 2026 02:54
- remove_entry
- count
- files
- env.sh off.sh on.sh codeball.txt ball.tar.gz
tar2ball.rb and tarscript.rb were scratch experiments (left-in
binding.irb, undeclared Ronin::Support). They are not required by any
code and were being packaged into the gem. Delete them.
warning was listed after zeitwerk, tripping Gemspec/OrderedDependencies.
Reorder alphabetically. Also gitignore docs/superpowers/ (local planning
artifacts) and exclude docs/**/* from RuboCop so rubocop-md does not lint
planning documents.
filter decided FILE-vs-stdin from stdin.tty?, so the documented
'filter PATTERN FILE' form silently failed in any non-interactive
context (pipes, scripts, CI): the file was swallowed as a pattern and
empty stdin was read. Now split_source treats the trailing arg as the
ball file when it names a real file, else reads stdin, and the ball is
read via File.read/stdin.read (no Kernel#open). Add integration coverage
for file/stdin, glob semantics, -v, empty input, and a nonexistent
trailing arg. Also clears the AbcSize/MethodLength/Security/Open
offenses.
The -O option introduced an open-based read (Security/Open) and pushed
run over AbcSize. Match filter's approach: read via File.read/stdin.read
(no Kernel#open) with an ENOENT guard, and extract
report_warnings/dump_to_stdout/extract_to_disk. Emit entry.contents for
-O. Add integration tests for the -O dump-to-stdout path and the
nonexistent-file error.
A DRY refactor dropped the body check from valid?, so an entry with a
matching header and footer but no body was both valid? and truncated?
(contradictory) -- reachable via Entry#name=. Restore the body guard to
match master's behavior. Empty files are unaffected (they carry a
non-nil empty Body).
dump_to_stdout used puts, which appends a newline to content lacking a
trailing one -- breaking the advertised tar -Ox parity and corrupting
binary output. Use print for a raw byte copy, and assert exact output in
the spec. Also fix an Analagous->Analogous typo in the option help.
@gillisd

gillisd commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

gillisd added 5 commits July 2, 2026 16:19
split_source consumed a lone argument as the ball file whenever it named
an existing file, leaving zero patterns so every entry was silently
dropped; and it treated an unreadable file as the source, raising an
uncaught Errno::EACCES. Only take the trailing arg as the file when a
preceding pattern exists and the file is readable; otherwise treat it as
a pattern and read stdin. Add regression specs for both.
These readers were public, exposing the live internal arrays so callers
could push entries/warnings directly and bypass add_entry's warning
bookkeeping. Restore them to private (as on master); nothing reads them
externally. Add a spec locking the encapsulation.
The class comment claimed Ball does not touch the filesystem, but the
load_file factory reads from disk. Reword to say an instance does no I/O
while the load_file factory reads the source file.
name= had no callers and set header+footer without a body, yielding an
entry that was both invalid? and truncated? -- a third construction path
that violated the documented Header -> Body -> Footer state machine.
Delete it; entries are built via the Stream and Entry.from_file paths.
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