Skip to content

Refactor domain model: Ball, Border, Cursor, Destination#4

Merged
gillisd merged 25 commits into
masterfrom
refactoring
Apr 7, 2026
Merged

Refactor domain model: Ball, Border, Cursor, Destination#4
gillisd merged 25 commits into
masterfrom
refactoring

Conversation

@gillisd

@gillisd gillisd commented Apr 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replace Bundle with Ball as the aggregate root — pure data, iteration protocol, no filesystem I/O
  • Extract Border module — fixed delimiter pattern with heuristic recognition for parsing
  • Extract Cursor — pure lexer producing typed tokens (Header, Body, Footer, EOF)
  • Extract Stream — Enumerable assembler that feeds tokens to Entry state machine
  • Extract Destination — filesystem context for writing entries, owns path safety and dry-run
  • Simplify Entry — state machine with write-once setters (Design by Contract), two construction paths
  • Eliminate Config class — border was never configurable in practice, extraction args moved to Destination
  • Remove --border, --border-width, --show-border CLI options
  • Migrate all CLI commands (pack, unpack, list, diff) to new entities
  • 222 rspec + 8 minitest = 230 tests passing, 0 rubocop offenses

Test plan

  • bundle exec rake passes (minitest + rspec + rubocop)
  • codeball pack lib/*.rb | codeball unpack -o /tmp/test round-trips files correctly
  • codeball pack lib/*.rb | codeball list shows file table
  • codeball unpack --dry-run prints preview without writing
  • codeball unpack -o outdir creates output directory and writes files

gillisd added 19 commits March 27, 2026 21:48
Remove .freeze from Zeitwerk::Loader assignment that prevented setup
and eager_load from working. Disable Style/MutableConstant cop to
avoid this class of issue. Add rdoc comments to satisfy
Style/Documentation.
Fill in 82 integration spec bodies across 5 files that were
previously skeletons with skip placeholders. Add ANSI color
matcher from lot project and fix spec helper issues (EXE path
resolution, pack_bundle absolute paths, aggregate_failures).
Refactor bundle.rb parser into small focused methods, extract
regex constants, decompose command methods in unpack/diff/list,
rename ExtractionResult#size to #line_count to avoid Struct
override, extract CommandKit extensions to separate files, split
minitest methods exceeding assertion limits, and fix misc style
offenses in gemspec, spec helpers, and rake tasks.
New entities created alongside existing Bundle for incremental
migration. Ball is the aggregate root (replaces Bundle), Border
is fixed domain knowledge, Cursor replaces primitive line/index
parsing, Destination handles filesystem writes.
Ball#serialize now passes Border::SEPARATOR to Entry#serialize
to avoid ArgumentError. Cursor extracts previous_line and
peek_line methods to eliminate raw lines[idx] access from
public and private methods.
75 unit specs covering all public interfaces. Fix content
fidelity bug in Cursor: collect content lines during walk
instead of reconstructing from index arithmetic afterward.
Move raw_line below private keyword since it's only used
internally by read_content_until_end. Add position guard to
previous_line to prevent silent wrap-around on lines[-1].
DRY binary_entry let in ball_spec.
Parse warnings are advisory (partial success), not errors.
Destination now accumulates results internally, yields per-write
via block, and provides summary(malformed:) instead of requiring
callers to collect results manually.
Pack, unpack, list, and diff commands now use Ball as the
aggregate root and Destination for filesystem writes. Removes
--border, --border-width, --show-border options. Eliminates
Config dependency. Unpack uses pattern matching destructure
for options.
Replace options destructure with direct hash access to prevent
NoMatchingPatternKeyError when --dry-run is absent. Add exit 1
to Diff#read_input on empty input. Normalize Destination#dry_run?
to boolean. Add ExtractionSummary#dry_run? to eliminate LoD
violation in Unpack#print_summary.
When file content doesn't end with \n, the border is glued to
the last content line. Cursor must strip the border suffix from
collected content, same as the old Bundle parser did.
Entry#serialize uses Border::SEPARATOR directly (no parameter).
Entry#write_to and Entry#safe_for? removed (Destination owns
filesystem writes). Delete Bundle, Config, MalformedBundleError.
Remove redundant minitest files (covered by rspec unit specs).
Update integration specs for removed --border/--show-border
options.
Ball specs test iteration filtering, not mime detection. Stubbing
Entry#text? isolates the test from libmagic version differences
across platforms.
Update production code comments and error messages to use
codeball instead of bundle. Fix spec context descriptions
to say parse warnings instead of parse errors.
@gillisd

gillisd commented Apr 6, 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 4 commits April 6, 2026 16:21
Move hand-crafted codeball text builder from unpack_spec to
CLIHelper so diff_spec can reuse it. Use literal border string
since integration specs don't load Codeball in-process.
Cursor is now a pure lexer producing typed tokens (Header, Body,
Footer, EOF). Stream is an Enumerable assembler that pulls tokens
from Cursor, feeds them to Entry via write-once setters, and emits
when Entry reports valid or errored. Entry is a state machine that
enforces its own invariants through Design by Contract. Ball uses
the snowball model -- starts empty, grows via add_entry.

New classes: Header, Body, Footer (SimpleDelegator string wrappers),
Stream (assembler), Cursor::EOF (sentinel). Entry rewritten with
write-once setters and two construction paths (from_file for packing,
token-by-token for parsing). Ball simplified to thin parse factory
with no private class methods.

Renames: parse_warnings -> warnings, each_parse_warning -> each_warning,
parse_warning_count -> warning_count.
@gillisd

gillisd commented Apr 6, 2026

Copy link
Copy Markdown
Owner Author

Code review

Found 1 issue:

  1. Cursor end_marker? matches any END line regardless of path, causing early body termination when file content contains END "other_file" patterns. The old Bundle parser matched END "#{path}" specifically. The body is truncated at the wrong point and Entry errors with a footer mismatch. The integration test "file containing BEGIN/END markers in content" is a false pass -- it reads the pre-existing file from pack_bundle, not the unpacked result.

def end_marker?
current_line&.match?(END_PATTERN)
end

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

gillisd added 2 commits April 6, 2026 23:00
Bare END lines in content no longer terminate body collection.
An END marker is only recognized when preceded by a border line
(border_before_end?) or when it follows a border suffix on the
previous content line (inline_end_marker?). This prevents file
content containing END "path" from being silently truncated.

Fix false-pass in round-trip spec: unpack to separate output
directory so the assertion reads the unpacked file, not the
pre-existing original from create_file.
Remove unused found_end(match) from Cursor. Add nil guards to
Entry#line_count and Entry#text? for incomplete entries. Update
gemspec summary and description to use codeball terminology.
@gillisd
gillisd merged commit 670dca2 into master Apr 7, 2026
1 check passed
@gillisd
gillisd deleted the refactoring branch April 7, 2026 00:23
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