Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,22 @@
- Emitted commands use absolute, quoted paths and are shell-neutral (bash/zsh/fish). Only the wrapper function generated by `init` differs per shell.

## Build, Test, and Development Commands
- `rake` / `rake test`: lint, unit, MRI spec; if Spinel is present, also emit `dist/try.c`, compile `dist/try`, native spec, and `runner_and_compare.sh`.
- `rake lint`: `ruby -c` always; `spinel -c` on `try.rb` and `lib/**/*.rb` when Spinel is available.
- `rake spec_spinel`: `make native` (writes `dist/try.c` then compiles), spec the binary, compare outputs with MRI.
- `nix run`: Run the packaged CLI (e.g., `nix run . -- --help`).
- `nix build`: Build the binary derivation; output at `./result/bin/try`.
- `./try.rb init ~/src/tries`: Emit shell function for your shell config.
- `./try.rb cd`: Launch interactive selector; prints `cd` script to stdout.
- `./try.rb clone <git-uri> [name]`: Clone into date-prefixed directory.
- `make native SPINEL=/path/to/spinel`: AOT a native `dist/try`.

## Dual implementation (MRI + Spinel)
`try` must keep working on **both**:
- MRI: `ruby try.rb` / the `try-cli` gem
- Spinel AOT: `spinel try.rb -o dist/try` (see `make native`)

A construct that only one of them can run is a bug. Prefer stdlib and Spinel-friendly Ruby (no `eval`, `method_missing`, `FileUtils`, `IO#raw`/`IO.console`, etc.) or keep an MRI-compatible shim like `TryCompat`. `rake lint` syntax-checks with `ruby -c` always, and with `spinel -c` on those same files when Spinel is on `PATH` (or `SPINEL=`). If Spinel is missing, lint **warns** and continues.

## Coding Style & Naming Conventions
- Ruby, 2-space indent, standard library only; keep it single-file unless necessary.
Expand All @@ -47,8 +58,9 @@
**Important**: Specs must reflect the full feature set of `try`. They serve as the canonical reference for behavior, enabling new implementations (in any language) to be validated against the same test suite. When adding or changing features, update both the relevant spec markdown and add corresponding tests.

## Testing Guidelines
- Primary testing is via the spec system: `./spec/tests/runner.sh ./try.rb`
- Native binary (optional, Spinel): `make native-test` / `bash spec/tests/runner.sh dist/try`
- `rake` runs lint + unit + spec. Lint is `ruby -c` on `try.rb` and `lib/**/*.rb`, plus `spinel -c` on each of `try.rb` and `lib/**/*.rb` when Spinel is installed (`SPINEL=/path/to/spinel` to point at a local build). Missing Spinel is a warning, not a hard fail.
- Primary spec run: `./spec/tests/runner.sh ./try.rb` (MRI).
- Native spec run: `make native-test` / `bash spec/tests/runner.sh dist/try` (Spinel). `make native-compare` / `rake spec_spinel` diffs MRI vs native output (init exec prefix is normalized).
- Manual flows for exploratory testing:
- `TRY_PATH=$(mktemp -d) ./try.rb cd` then create/select directories.
- Validate delete confirmation and scoring by changing `mtime`/`ctime`.
Expand Down
19 changes: 16 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,28 @@ i: install ## Shortcut for install
# Native binary via Spinel (optional)
SPINEL ?= spinel
SPINEL_FLAGS ?= -O s
CC ?= cc
NATIVE = dist/try
NATIVE_C = dist/try.c
SPINEL_BIN := $(shell command -v $(SPINEL) 2>/dev/null)
SPINEL_LIB ?= $(dir $(SPINEL_BIN))../lib

.PHONY: native native-test
.PHONY: native native-test native-compare
native: $(NATIVE)

$(NATIVE): try.rb lib/tui.rb lib/fuzzy.rb
$(NATIVE_C): try.rb lib/tui.rb lib/fuzzy.rb
mkdir -p dist
$(SPINEL) $(SPINEL_FLAGS) try.rb -o $(NATIVE)
$(SPINEL) $(SPINEL_FLAGS) -c try.rb -o $(NATIVE_C)

$(NATIVE): $(NATIVE_C)
$(CC) -Os -Wno-all -ffunction-sections -fdata-sections \
-I$(SPINEL_LIB) -I$(SPINEL_LIB)/regexp \
$(NATIVE_C) $(SPINEL_LIB)/libspinel_rt.a \
-lm -lcrypt -Wl,--gc-sections -o $(NATIVE)
strip $(NATIVE)

native-test: $(NATIVE)
bash spec/tests/runner.sh $(NATIVE)

native-compare: $(NATIVE)
bash spec/tests/runner_and_compare.sh ./try.rb $(NATIVE)
50 changes: 46 additions & 4 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,16 +1,58 @@
require 'rake/testtask'
require 'tempfile'

RUBY_SOURCES = FileList['try.rb', 'lib/**/*.rb']

def spinel_cmd
ENV.fetch('SPINEL', 'spinel')
end

def spinel_available?
system('sh', '-c', 'command -v "$1" >/dev/null', '--', spinel_cmd)
end

Rake::TestTask.new(:unit) do |t|
t.libs << 'lib' << 'test'
t.pattern = 'test/**/*_test.rb'
end

desc "Run shell spec compliance tests"
desc "Check syntax with MRI and Spinel (warns if Spinel is missing)"
task :lint do
RUBY_SOURCES.each do |file|
sh 'ruby', '-c', file
end

unless spinel_available?
warn "warning: spinel not found (#{spinel_cmd}); skipping Spinel syntax check"
warn "warning: try must parse and run on both MRI Ruby and Spinel"
next
end

RUBY_SOURCES.each do |file|
Tempfile.create(['try-spinel-syntax', '.c']) do |tmp|
sh spinel_cmd, '-c', file, '-o', tmp.path
end
end
end

desc "Run shell spec compliance tests (MRI)"
task :spec do
sh "bash spec/tests/runner.sh ./try.rb"
sh 'bash', 'spec/tests/runner.sh', './try.rb'
end

desc "Emit dist/try.c, compile dist/try, spec it, and compare with MRI"
task :spec_spinel do
unless spinel_available?
warn "warning: spinel not found (#{spinel_cmd}); skipping native spec + compare"
next
end

sh 'make', 'native', "SPINEL=#{spinel_cmd}"
sh 'bash', 'spec/tests/runner.sh', 'dist/try'
sh 'bash', 'spec/tests/runner_and_compare.sh', './try.rb', 'dist/try'
end

desc "Run all tests (unit + spec)"
task test: [:unit, :spec]
desc "Run all tests (lint + unit + spec; native spec+compare if Spinel is present)"
task test: [:lint, :unit, :spec, :spec_spinel]

task default: :test
7 changes: 7 additions & 0 deletions spec/tests/runner_and_compare.sh
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ compare_test() {
norm1=$(echo "$out1" | sed 's/\x1b\[[0-9;]*m//g')
norm2=$(echo "$out2" | sed 's/\x1b\[[0-9;]*m//g')

# Init snippets invoke MRI via `ruby try.rb` and the native binary via $0.
# Same wrapper otherwise — collapse the exec prefix so the compare is exact.
if [ "$name" = "init command" ]; then
norm1=$(printf '%s\n' "$norm1" | sed -E "s#/usr/bin/env ruby '[^']+'#'TRY'#g; s#'[^']+' exec#'TRY' exec#g")
norm2=$(printf '%s\n' "$norm2" | sed -E "s#/usr/bin/env ruby '[^']+'#'TRY'#g; s#'[^']+' exec#'TRY' exec#g")
fi

if [ "$norm1" = "$norm2" ] && [ "$exit1" = "$exit2" ]; then
echo -en "${GREEN}.${NC}"
TESTS_SAME=$((TESTS_SAME + 1))
Expand Down