fasti 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.mcp.json +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +82 -0
- data/.rubocop_todo.yml +89 -0
- data/.serena/project.yml +68 -0
- data/.simplecov +31 -0
- data/.yardopts +9 -0
- data/AGENTS.md +60 -0
- data/CHANGELOG.md +25 -0
- data/CLAUDE.md +1 -0
- data/LICENSE.txt +21 -0
- data/README.md +416 -0
- data/RELEASING.md +202 -0
- data/Rakefile +34 -0
- data/TODO.md +11 -0
- data/benchmark/holiday_cache_benchmark.rb +111 -0
- data/benchmark/memory_benchmark.rb +86 -0
- data/docs/agents/git-pr.md +298 -0
- data/docs/agents/languages.md +388 -0
- data/docs/agents/rubocop.md +55 -0
- data/docs/plans/positional-arguments.md +303 -0
- data/docs/plans/structured-config.md +232 -0
- data/examples/config.rb +80 -0
- data/exe/fasti +6 -0
- data/lib/fasti/calendar.rb +292 -0
- data/lib/fasti/calendar_transition.rb +266 -0
- data/lib/fasti/cli.rb +550 -0
- data/lib/fasti/config/schema.rb +36 -0
- data/lib/fasti/config/types.rb +66 -0
- data/lib/fasti/config.rb +125 -0
- data/lib/fasti/error.rb +6 -0
- data/lib/fasti/formatter.rb +234 -0
- data/lib/fasti/style_parser.rb +211 -0
- data/lib/fasti/version.rb +6 -0
- data/lib/fasti.rb +21 -0
- data/mise.toml +5 -0
- data/sig/fasti.rbs +4 -0
- metadata +181 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "benchmark"
|
5
|
+
require "bundler/setup"
|
6
|
+
require "fasti"
|
7
|
+
|
8
|
+
# Temporary class to simulate old behavior without caching
|
9
|
+
class LegacyCalendar < Fasti::Calendar
|
10
|
+
# Override to disable caching for benchmark comparison
|
11
|
+
def holiday?(day)
|
12
|
+
date = to_date(day)
|
13
|
+
return false unless date
|
14
|
+
|
15
|
+
begin
|
16
|
+
Holidays.on(date, country).any?
|
17
|
+
rescue Holidays::InvalidRegion, StandardError
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Benchmarks month display performance by checking holidays for all days in a month
|
24
|
+
def benchmark_month_display(calendar_class, year, month, country, iterations=100)
|
25
|
+
calendar = calendar_class.new(year, month, country:)
|
26
|
+
|
27
|
+
Benchmark.realtime do
|
28
|
+
iterations.times do
|
29
|
+
# Simulate checking all days in the month (like formatter does)
|
30
|
+
(1..calendar.days_in_month).each do |day|
|
31
|
+
calendar.holiday?(day)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Benchmarks year display performance by checking holidays for all days in all months
|
38
|
+
def benchmark_year_display(calendar_class, year, country, iterations=10)
|
39
|
+
Benchmark.realtime do
|
40
|
+
iterations.times do
|
41
|
+
(1..12).each do |month|
|
42
|
+
calendar = calendar_class.new(year, month, country:)
|
43
|
+
(1..calendar.days_in_month).each do |day|
|
44
|
+
calendar.holiday?(day)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Formats benchmark time results in appropriate units (μs/ms/s)
|
52
|
+
def format_time(seconds)
|
53
|
+
if seconds < 0.001
|
54
|
+
"#{(seconds * 1_000_000).round(2)}μs"
|
55
|
+
elsif seconds < 1
|
56
|
+
"#{(seconds * 1000).round(2)}ms"
|
57
|
+
else
|
58
|
+
"#{seconds.round(3)}s"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Calculates and formats performance improvement ratio between two benchmark times
|
63
|
+
def format_speedup(old_time, new_time)
|
64
|
+
return "N/A" if new_time.zero?
|
65
|
+
|
66
|
+
speedup = old_time / new_time
|
67
|
+
"#{speedup.round(2)}x faster"
|
68
|
+
end
|
69
|
+
|
70
|
+
puts "Fasti Holiday Cache Performance Benchmark"
|
71
|
+
puts "=" * 50
|
72
|
+
puts
|
73
|
+
|
74
|
+
# Test parameters
|
75
|
+
YEAR = 2024
|
76
|
+
COUNTRIES = %i[us jp gb].freeze
|
77
|
+
MONTH_ITERATIONS = 100
|
78
|
+
YEAR_ITERATIONS = 10
|
79
|
+
|
80
|
+
COUNTRIES.each do |country|
|
81
|
+
puts "Country: #{country.upcase}"
|
82
|
+
puts "-" * 20
|
83
|
+
|
84
|
+
# Month display benchmark
|
85
|
+
puts "Month Display (July #{YEAR}, #{MONTH_ITERATIONS} iterations):"
|
86
|
+
|
87
|
+
legacy_month_time = benchmark_month_display(LegacyCalendar, YEAR, 7, country, MONTH_ITERATIONS)
|
88
|
+
cached_month_time = benchmark_month_display(Fasti::Calendar, YEAR, 7, country, MONTH_ITERATIONS)
|
89
|
+
|
90
|
+
puts " Legacy (no cache): #{format_time(legacy_month_time)}"
|
91
|
+
puts " Cached: #{format_time(cached_month_time)}"
|
92
|
+
puts " Improvement: #{format_speedup(legacy_month_time, cached_month_time)}"
|
93
|
+
puts
|
94
|
+
|
95
|
+
# Year display benchmark
|
96
|
+
puts "Year Display (#{YEAR}, #{YEAR_ITERATIONS} iterations):"
|
97
|
+
|
98
|
+
legacy_year_time = benchmark_year_display(LegacyCalendar, YEAR, country, YEAR_ITERATIONS)
|
99
|
+
cached_year_time = benchmark_year_display(Fasti::Calendar, YEAR, country, YEAR_ITERATIONS)
|
100
|
+
|
101
|
+
puts " Legacy (no cache): #{format_time(legacy_year_time)}"
|
102
|
+
puts " Cached: #{format_time(cached_year_time)}"
|
103
|
+
puts " Improvement: #{format_speedup(legacy_year_time, cached_year_time)}"
|
104
|
+
puts
|
105
|
+
puts
|
106
|
+
end
|
107
|
+
|
108
|
+
puts "Benchmark completed!"
|
109
|
+
puts
|
110
|
+
puts "Note: Results may vary based on network latency to holiday data sources"
|
111
|
+
puts "and system performance. Run multiple times for consistent results."
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "fasti"
|
6
|
+
|
7
|
+
# Simple memory measurement helper
|
8
|
+
def measure_memory_usage
|
9
|
+
# Force garbage collection to get accurate measurements
|
10
|
+
GC.start
|
11
|
+
GC.compact if GC.respond_to?(:compact)
|
12
|
+
|
13
|
+
before = GC.stat[:heap_live_slots]
|
14
|
+
yield
|
15
|
+
GC.start
|
16
|
+
after = GC.stat[:heap_live_slots]
|
17
|
+
|
18
|
+
after - before
|
19
|
+
end
|
20
|
+
|
21
|
+
# Formats memory usage in human-readable units (slots/K slots/M slots)
|
22
|
+
def format_memory(slots)
|
23
|
+
# Rough estimation: each object slot ≈ 40 bytes on 64-bit systems
|
24
|
+
bytes = slots * 40
|
25
|
+
if bytes < 1024
|
26
|
+
"#{bytes}B"
|
27
|
+
elsif bytes < 1024 * 1024
|
28
|
+
"#{(bytes / 1024.0).round(2)}KB"
|
29
|
+
else
|
30
|
+
"#{(bytes / (1024.0 * 1024)).round(2)}MB"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
puts "Fasti Memory Usage Benchmark"
|
35
|
+
puts "=" * 40
|
36
|
+
puts
|
37
|
+
|
38
|
+
# Test creating calendars and checking holidays
|
39
|
+
YEAR = 2024
|
40
|
+
COUNTRIES = %i[us jp gb].freeze
|
41
|
+
|
42
|
+
COUNTRIES.each do |country|
|
43
|
+
puts "Country: #{country.upcase}"
|
44
|
+
puts "-" * 15
|
45
|
+
|
46
|
+
# Single month calendar
|
47
|
+
month_memory = measure_memory_usage {
|
48
|
+
calendar = Fasti::Calendar.new(YEAR, 7, country:)
|
49
|
+
(1..calendar.days_in_month).each {|day| calendar.holiday?(day) }
|
50
|
+
}
|
51
|
+
|
52
|
+
# Year worth of calendars (simulating year view)
|
53
|
+
year_memory = measure_memory_usage {
|
54
|
+
(1..12).each do |month|
|
55
|
+
calendar = Fasti::Calendar.new(YEAR, month, country:)
|
56
|
+
(1..calendar.days_in_month).each {|day| calendar.holiday?(day) }
|
57
|
+
end
|
58
|
+
}
|
59
|
+
|
60
|
+
puts " Single month: #{format_memory(month_memory)}"
|
61
|
+
puts " Full year: #{format_memory(year_memory)}"
|
62
|
+
puts
|
63
|
+
end
|
64
|
+
|
65
|
+
# Test cache behavior - multiple accesses to same month
|
66
|
+
puts "Cache Efficiency Test"
|
67
|
+
puts "-" * 20
|
68
|
+
|
69
|
+
cache_memory = measure_memory_usage {
|
70
|
+
calendar = Fasti::Calendar.new(YEAR, 7, country: :us)
|
71
|
+
|
72
|
+
# First pass - cache gets populated
|
73
|
+
(1..calendar.days_in_month).each {|day|
|
74
|
+
calendar.holiday?(day)
|
75
|
+
# Second pass - should use cache
|
76
|
+
calendar.holiday?(day)
|
77
|
+
|
78
|
+
# Third pass - still using cache
|
79
|
+
calendar.holiday?(day)
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
puts "Triple access (cache test): #{format_memory(cache_memory)}"
|
84
|
+
puts
|
85
|
+
puts "Note: Memory measurements are approximate and may vary"
|
86
|
+
puts "based on Ruby version and system configuration."
|
@@ -0,0 +1,298 @@
|
|
1
|
+
# Git Commit and Pull Request Guidelines for AI Agents
|
2
|
+
|
3
|
+
This guide helps AI agents follow the project's Git and GitHub conventions for commits, pull requests, and merges.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
- [Commit Messages](#commit-messages)
|
7
|
+
- [Branch Management](#branch-management)
|
8
|
+
- [Pull Request Creation](#pull-request-creation)
|
9
|
+
- [Pull Request Merging](#pull-request-merging)
|
10
|
+
- [Common Issues and Solutions](#common-issues-and-solutions)
|
11
|
+
|
12
|
+
## Commit Messages
|
13
|
+
|
14
|
+
### Format Requirements
|
15
|
+
|
16
|
+
All commit messages MUST follow this format:
|
17
|
+
|
18
|
+
```
|
19
|
+
:emoji: Subject line in imperative mood
|
20
|
+
|
21
|
+
Detailed explanation of changes (optional)
|
22
|
+
- Bullet points for key changes
|
23
|
+
- Technical details and trade-offs
|
24
|
+
|
25
|
+
:robot: Generated with [Agent Name](http://agent.url)
|
26
|
+
|
27
|
+
Co-Authored-By: Agent Name <agent@email>
|
28
|
+
```
|
29
|
+
|
30
|
+
**Note**: Replace `[Agent Name]`, `http://agent.url`, and `<agent@email>` with your specific AI agent information.
|
31
|
+
|
32
|
+
### Key Rules
|
33
|
+
|
34
|
+
1. **Always use GitHub emoji codes** (`:emoji:`), never raw Unicode emojis
|
35
|
+
2. **Start with emoji**: The first character must be `:`
|
36
|
+
3. **Space after emoji**: `:emoji: Subject` not `:emoji:Subject`
|
37
|
+
4. **Imperative mood**: "Fix bug" not "Fixed bug" or "Fixes bug"
|
38
|
+
5. **No period at end of subject**
|
39
|
+
6. **Include AI attribution** for AI-generated commits
|
40
|
+
|
41
|
+
### Common Emoji Codes
|
42
|
+
|
43
|
+
| Emoji | Code | Use Case |
|
44
|
+
|-------|------|----------|
|
45
|
+
| ⚡ | `:zap:` | Performance improvements |
|
46
|
+
| 🐛 | `:bug:` | Bug fixes |
|
47
|
+
| ✨ | `:sparkles:` | New features |
|
48
|
+
| 📝 | `:memo:` | Documentation |
|
49
|
+
| ♻️ | `:recycle:` | Refactoring |
|
50
|
+
| 🎨 | `:art:` | Code structure/format improvements |
|
51
|
+
| 🔧 | `:wrench:` | Configuration changes |
|
52
|
+
| ✅ | `:white_check_mark:` | Adding/updating tests |
|
53
|
+
| 🔥 | `:fire:` | Removing code/files |
|
54
|
+
| 📦 | `:package:` | Updating dependencies |
|
55
|
+
|
56
|
+
### Commit Command Example
|
57
|
+
|
58
|
+
```bash
|
59
|
+
git commit -m "$(cat <<'EOF'
|
60
|
+
:zap: Optimize Style#call performance with caching
|
61
|
+
|
62
|
+
Cache SGR sequences at initialization to avoid recalculation.
|
63
|
+
- 7-18x performance improvement
|
64
|
+
- 85-91% reduction in object allocations
|
65
|
+
|
66
|
+
:robot: Generated with [Agent Name](http://agent.url)
|
67
|
+
|
68
|
+
Co-Authored-By: Agent Name <agent@email>
|
69
|
+
EOF
|
70
|
+
)"
|
71
|
+
```
|
72
|
+
|
73
|
+
**Important**: Use heredoc with single quotes (`<<'EOF'`) to preserve special characters.
|
74
|
+
|
75
|
+
## Branch Management
|
76
|
+
|
77
|
+
### Branch Naming Convention
|
78
|
+
|
79
|
+
```
|
80
|
+
type/description-with-hyphens
|
81
|
+
```
|
82
|
+
|
83
|
+
Examples:
|
84
|
+
- `feature/style-performance-optimization`
|
85
|
+
- `fix/readme-installation-instructions`
|
86
|
+
- `docs/git-pr-guidelines`
|
87
|
+
- `cleanup/remove-described-class`
|
88
|
+
|
89
|
+
### Creating a New Branch
|
90
|
+
|
91
|
+
```bash
|
92
|
+
git switch -c feature/your-feature-name
|
93
|
+
```
|
94
|
+
|
95
|
+
## Pull Request Creation
|
96
|
+
|
97
|
+
### Using gh CLI
|
98
|
+
|
99
|
+
Always use the `gh` CLI tool for creating pull requests.
|
100
|
+
|
101
|
+
### Basic PR Creation
|
102
|
+
|
103
|
+
```bash
|
104
|
+
gh pr create --title ":emoji: Clear descriptive title" --body "PR description"
|
105
|
+
```
|
106
|
+
|
107
|
+
### PR with Complex Body
|
108
|
+
|
109
|
+
For PR bodies containing backticks or complex markdown, use command substitution with heredoc:
|
110
|
+
|
111
|
+
```bash
|
112
|
+
gh pr create --title ":memo: Update documentation" --body "$(cat <<'EOF'
|
113
|
+
## Summary
|
114
|
+
Brief description of changes
|
115
|
+
|
116
|
+
## Changes
|
117
|
+
- Change 1
|
118
|
+
- Change 2
|
119
|
+
|
120
|
+
## Before
|
121
|
+
```ruby
|
122
|
+
old_code
|
123
|
+
```
|
124
|
+
|
125
|
+
## After
|
126
|
+
```ruby
|
127
|
+
new_code
|
128
|
+
```
|
129
|
+
|
130
|
+
:robot: Generated with [Agent Name](http://agent.url)
|
131
|
+
EOF
|
132
|
+
)"
|
133
|
+
```
|
134
|
+
|
135
|
+
### PR Title Format
|
136
|
+
|
137
|
+
- Must start with GitHub emoji code (same as commits)
|
138
|
+
- Clear, descriptive title
|
139
|
+
- Example: `:zap: Optimize Style#call performance with SGR caching`
|
140
|
+
|
141
|
+
## Pull Request Merging
|
142
|
+
|
143
|
+
### Merge Command Format
|
144
|
+
|
145
|
+
Use merge commits (not squash) with custom messages:
|
146
|
+
|
147
|
+
```bash
|
148
|
+
gh pr merge PR_NUMBER --merge \
|
149
|
+
--subject ":inbox_tray: :emoji: Merge pull request #PR from branch" \
|
150
|
+
--body "Brief description of what was merged"
|
151
|
+
```
|
152
|
+
|
153
|
+
### Merge Commit Convention
|
154
|
+
|
155
|
+
The merge commit automatically gets `:inbox_tray:` prefix, so the format is:
|
156
|
+
|
157
|
+
```
|
158
|
+
:inbox_tray: :original_emoji: Merge pull request #N from user/branch
|
159
|
+
```
|
160
|
+
|
161
|
+
Example:
|
162
|
+
```bash
|
163
|
+
gh pr merge 6 --merge \
|
164
|
+
--subject ":inbox_tray: :zap: Merge pull request #6 from sakuro/feature/style-performance-optimization" \
|
165
|
+
--body "Style#call performance optimization with SGR caching"
|
166
|
+
```
|
167
|
+
|
168
|
+
## Common Issues and Solutions
|
169
|
+
|
170
|
+
### Issue: Commit Hook Rejects Message
|
171
|
+
|
172
|
+
**Error**: "Commit message must start with a GitHub :emoji:"
|
173
|
+
|
174
|
+
**Solution**: Ensure your commit message starts with `:emoji_code:` (colon on both sides)
|
175
|
+
|
176
|
+
### Issue: Raw Emoji in Commit
|
177
|
+
|
178
|
+
**Error**: "Commit message contains raw emojis"
|
179
|
+
|
180
|
+
**Solution**: Replace Unicode emoji (🎉) with GitHub codes (`:tada:`)
|
181
|
+
|
182
|
+
### Issue: Backticks in PR Body
|
183
|
+
|
184
|
+
**Problem**: Backticks in heredoc cause shell interpretation issues
|
185
|
+
|
186
|
+
**Solution**: Use command substitution with heredoc:
|
187
|
+
```bash
|
188
|
+
gh pr create --title "Title" --body "$(cat <<'EOF'
|
189
|
+
Content with `backticks` in markdown
|
190
|
+
EOF
|
191
|
+
)"
|
192
|
+
```
|
193
|
+
|
194
|
+
### Issue: Pre-push Hook Failures
|
195
|
+
|
196
|
+
**Problem**: RuboCop or tests fail during push
|
197
|
+
|
198
|
+
**Solution**:
|
199
|
+
1. Run `rake` locally first
|
200
|
+
2. Fix any issues before pushing
|
201
|
+
3. Use `bundle exec rubocop -A` for safe auto-fixes
|
202
|
+
|
203
|
+
## Staging Changes Safely
|
204
|
+
|
205
|
+
### Avoid Bulk Operations
|
206
|
+
|
207
|
+
**Never use these commands** as they can add unintended files:
|
208
|
+
```bash
|
209
|
+
git add . # Adds ALL files in current directory
|
210
|
+
git add -A # Adds ALL tracked and untracked files
|
211
|
+
git add * # Adds files matching shell glob
|
212
|
+
```
|
213
|
+
|
214
|
+
### Recommended Approaches
|
215
|
+
|
216
|
+
**Option 1: Add specific files explicitly**
|
217
|
+
```bash
|
218
|
+
git add lib/specific_file.rb
|
219
|
+
git add spec/specific_spec.rb
|
220
|
+
git add README.md
|
221
|
+
```
|
222
|
+
|
223
|
+
**Option 2: Review changes first, then stage**
|
224
|
+
```bash
|
225
|
+
# See what would be added
|
226
|
+
git status
|
227
|
+
git diff
|
228
|
+
|
229
|
+
# Add specific files you want to commit
|
230
|
+
git add path/to/changed_file.rb
|
231
|
+
```
|
232
|
+
|
233
|
+
**Option 3: Interactive staging for complex changes**
|
234
|
+
```bash
|
235
|
+
git add -p # Review and stage changes interactively
|
236
|
+
```
|
237
|
+
|
238
|
+
## Best Practices
|
239
|
+
|
240
|
+
1. **Always run tests before pushing**: `rake` or `bundle exec rspec`
|
241
|
+
2. **Check RuboCop**: `bundle exec rubocop` before committing
|
242
|
+
3. **Keep commits focused**: One logical change per commit
|
243
|
+
4. **Stage files explicitly**: Avoid `git add .` - specify files individually
|
244
|
+
5. **Review staged changes**: Use `git diff --cached` before committing
|
245
|
+
6. **Write clear PR descriptions**: Include before/after examples when relevant
|
246
|
+
7. **Link issues**: Use "Fixes #123" in PR descriptions
|
247
|
+
8. **Update documentation**: Keep README and CHANGELOG current
|
248
|
+
|
249
|
+
## Example Workflow
|
250
|
+
|
251
|
+
```bash
|
252
|
+
# 1. Create branch
|
253
|
+
git switch -c fix/performance-issue
|
254
|
+
|
255
|
+
# 2. Make changes
|
256
|
+
# ... edit files ...
|
257
|
+
|
258
|
+
# 3. Run tests and lint
|
259
|
+
rake
|
260
|
+
|
261
|
+
# 4. Stage changes (be specific!)
|
262
|
+
git add lib/performance_fix.rb
|
263
|
+
git add spec/performance_fix_spec.rb
|
264
|
+
|
265
|
+
# 5. Commit with proper message
|
266
|
+
git commit -m "$(cat <<'EOF'
|
267
|
+
:zap: Fix performance regression in render method
|
268
|
+
|
269
|
+
Memoize expensive calculations to avoid recalculation.
|
270
|
+
- 3x faster rendering
|
271
|
+
- Reduces memory allocations
|
272
|
+
|
273
|
+
Fixes #42
|
274
|
+
|
275
|
+
:robot: Generated with [Agent Name](http://agent.url)
|
276
|
+
|
277
|
+
Co-Authored-By: Agent Name <agent@email>
|
278
|
+
EOF
|
279
|
+
)"
|
280
|
+
|
281
|
+
# 6. Push branch
|
282
|
+
git push -u origin fix/performance-issue
|
283
|
+
|
284
|
+
# 7. Create PR
|
285
|
+
gh pr create --title ":zap: Fix performance regression in render method" \
|
286
|
+
--body "Fixes #42 - Memoization of expensive calculations"
|
287
|
+
|
288
|
+
# 8. After approval, merge
|
289
|
+
gh pr merge 7 --merge \
|
290
|
+
--subject ":inbox_tray: :zap: Merge pull request #7 from sakuro/fix/performance-issue" \
|
291
|
+
--body "Performance fix through memoization"
|
292
|
+
```
|
293
|
+
|
294
|
+
## References
|
295
|
+
|
296
|
+
- [GitHub Emoji Codes](https://github.com/ikatyang/emoji-cheat-sheet)
|
297
|
+
- [Conventional Commits](https://www.conventionalcommits.org/)
|
298
|
+
- Project's commit hooks in `.git/hooks/`
|