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
121 changes: 121 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# AGENTS.md

PathSpec Ruby - .gitignore-style pattern matching in Ruby

## Project overview

Ruby gem implementing .gitignore-style pattern matching with both library API and CLI tool. Supports Ruby 3.2-4.0.1 with comprehensive test coverage and multi-Ruby CI.

## Setup commands

```bash
# Install mise (Ruby version manager)
brew install mise # macOS
# Other platforms: https://mise.jdx.dev/getting-started.html

# Activate mise
eval "$(mise activate zsh)" # or bash, fish, etc.

# Install Ruby and bundler versions
mise install

# Install dependencies
mise run install
# or: bundle install
```

## Testing

```bash
# Run all tests (lint, unit, integration, docs)
mise run test
# or: bundle exec rake

# Unit tests only
mise run test:unit
# or: bundle exec rake spec

# Integration tests (CLI) only
mise run test:integration
# or: bundle exec rake spec_integration

# All specs (unit + integration)
mise run test:all
# or: bundle exec rake spec_all

# Cross-Ruby matrix testing via Docker
mise run test:matrix
# or: bundle exec rake test_matrix
```

## Code style

- Use RuboCop 1.63.5 for linting
- Method length limit: 69 lines
- Use single quotes for strings without interpolation
- Use `%w[]` for word arrays
- Auto-fix with: `bundle exec rubocop --autocorrect`
- For large data arrays: add `# rubocop:disable Metrics/MethodLength`

## Build and release

```bash
# Build gem
mise run build
# or: gem build pathspec.gemspec

# Generate documentation
bundle exec rake docs

# Development install
rake install
```

## Project structure

```
pathspec-ruby/
├── lib/pathspec/ # Core library
│ ├── pathspec.rb # Main PathSpec class
│ └── patterns/ # Pattern implementations
├── bin/pathspec-rb # CLI executable
├── spec/
│ ├── unit/ # Library tests
│ └── integration/ # CLI tests
├── benchmarks/ # Performance tests
├── docs/ # Documentation source
├── Rakefile # Build tasks
├── .tool-versions # Ruby/bundler versions
└── pathspec.gemspec # Gem spec
```

## Development workflow

1. Make changes to `lib/` code
2. Add/update tests in `spec/unit/` for library changes
3. Add/update tests in `spec/integration/` for CLI changes
4. Run `mise run test` - must pass before committing
5. Fix any RuboCop offenses
6. Test cross-Ruby with `mise run test:matrix`
7. Commit with descriptive messages

## CLI tool usage

```bash
bundle exec pathspec-rb -f .gitignore match "file.swp"
bundle exec pathspec-rb -f .gitignore specs_match "file.swp"
bundle exec pathspec-rb -f .gitignore tree ./src
```

## Common issues

**Bundler conflicts**: Always use `mise run install` to ensure correct versions from `.tool-versions`

**RuboCop failures**: Auto-fix with `bundle exec rubocop --autocorrect`. Method length is common issue - extract large data arrays to separate methods with rubocop:disable comments

**CI failures**: Check GitHub Actions. RuboCop and integration test failures are most common causes

## Dependencies

- **Runtime**: None (pure Ruby)
- **Development**: rspec (~> 3.10), rubocop (~> 1.63.0), fakefs (~> 2.5), kramdown (~> 2.3), benchmark-ips (~> 2.0)
68 changes: 37 additions & 31 deletions benchmarks/pattern_scaling.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def generate_test_paths(count = 1000)

# Mix of different path types
extensions = %w[.rb .txt .log .tmp .swp .md .yml .json .xml .css .js .html]
directories = ['src', 'lib', 'test', 'spec', 'config', 'docs', 'bin', 'tmp', 'coverage', 'vendor']
directories = %w[src lib test spec config docs bin tmp coverage vendor]

count.times do |i|
depth = rand(1..4)
Expand All @@ -30,7 +30,24 @@ def generate_test_paths(count = 1000)

# Generate gitignore patterns of varying complexity
def generate_patterns(count)
base_patterns = [
base_patterns = base_gitignore_patterns

# Return the first 'count' patterns, cycling if needed
if count <= base_patterns.length
base_patterns.take(count)
else
patterns = base_patterns.dup
remaining = count - base_patterns.length
remaining.times do |i|
patterns << "generated_pattern_#{i}/**/*"
end
patterns
end
end

# rubocop:disable Metrics/MethodLength
def base_gitignore_patterns
[
'*.log',
'*.tmp',
'*.swp',
Expand Down Expand Up @@ -165,31 +182,20 @@ def generate_patterns(count)
'.yarn/install-state.gz',
'.pnp.*'
]

# Return the first 'count' patterns, cycling if needed
if count <= base_patterns.length
base_patterns.take(count)
else
patterns = base_patterns.dup
remaining = count - base_patterns.length
remaining.times do |i|
patterns << "generated_pattern_#{i}/**/*"
end
patterns
end
end
# rubocop:enable Metrics/MethodLength

puts "PathSpec Performance Benchmark"
puts "=" * 80
puts "Testing pattern matching performance with varying pattern counts"
puts "Hardware: Apple M4 Pro (12 cores: 8 performance + 4 efficiency), 24 GB RAM"
puts 'PathSpec Performance Benchmark'
puts '=' * 80
puts 'Testing pattern matching performance with varying pattern counts'
puts 'Hardware: Apple M4 Pro (12 cores: 8 performance + 4 efficiency), 24 GB RAM'
puts "Ruby Version: #{RUBY_VERSION}"
puts "Test Configuration:"
puts 'Test Configuration:'
puts " - Pattern counts: #{PATTERN_COUNTS.join(', ')}"
puts " - Test paths: 1000 representative file paths"
puts ' - Test paths: 1000 representative file paths'
puts " - Warmup time: #{WARMUP_TIME}s"
puts " - Benchmark time: #{BENCHMARK_TIME}s per test"
puts "=" * 80
puts '=' * 80
puts

# Pre-generate test data
Expand All @@ -203,15 +209,15 @@ def generate_patterns(count)
pathspec = PathSpec.new(patterns, :git)

puts "Benchmarking with #{pattern_count} patterns..."
puts "-" * 80
puts '-' * 80

results[pattern_count] = {}

# Benchmark 1: Single path matching
Benchmark.ips do |x|
x.config(time: BENCHMARK_TIME, warmup: WARMUP_TIME)

x.report("match (single path)") do
x.report('match (single path)') do
test_paths.first(10).each do |path|
pathspec.match(path)
end
Expand All @@ -226,7 +232,7 @@ def generate_patterns(count)
Benchmark.ips do |x|
x.config(time: BENCHMARK_TIME, warmup: WARMUP_TIME)

x.report("match_paths (100 paths)") do
x.report('match_paths (100 paths)') do
pathspec.match_paths(test_paths.first(100), '')
end
end
Expand All @@ -237,19 +243,19 @@ def generate_patterns(count)
Benchmark.ips do |x|
x.config(time: BENCHMARK_TIME, warmup: WARMUP_TIME)

x.report("initialization") do
x.report('initialization') do
PathSpec.new(patterns, :git)
end
end

puts "\n"
end

puts "=" * 80
puts "Benchmark complete!"
puts "=" * 80
puts '=' * 80
puts 'Benchmark complete!'
puts '=' * 80
puts "\nTo analyze results:"
puts "1. Review the iterations/second (i/s) for each pattern count"
puts "2. Compare how performance scales as pattern count increases"
puts "3. Identify which operations are most affected by pattern count"
puts '1. Review the iterations/second (i/s) for each pattern count'
puts '2. Compare how performance scales as pattern count increases'
puts '3. Identify which operations are most affected by pattern count'
puts "\nNote: Higher i/s (iterations per second) indicates better performance"