rfmt 0.3.0 → 0.4.1
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 +4 -4
- data/CHANGELOG.md +28 -0
- data/Cargo.lock +1 -1
- data/README.md +65 -7
- data/ext/rfmt/Cargo.toml +1 -1
- data/ext/rfmt/src/ast/mod.rs +2 -0
- data/ext/rfmt/src/config/mod.rs +1 -0
- data/ext/rfmt/src/emitter/mod.rs +10 -3
- data/ext/rfmt/src/lib.rs +4 -1
- data/ext/rfmt/src/logging/logger.rs +11 -2
- data/lib/rfmt/cli.rb +38 -12
- data/lib/rfmt/rfmt.so +0 -0
- data/lib/rfmt/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f49bc8ba6c22392ac19d4f52cb9f82ee0476f849326745d821b952a75b13bb70
|
|
4
|
+
data.tar.gz: 59f68f137d3b769e17463c8c47b5ee3add7c06234caf50b3a20c03a34bf32cc0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8bd5393bab836f18a889ddac8e00e997bc7e1d45a6e9b2e5f4d232dd9589ce367ed76c60129631ca6a1626ba76031a8517cdd96e05ad41ebe960468f9933fb1e
|
|
7
|
+
data.tar.gz: f6692b5a16f284c59e5d8281c5bb11e96ce6f70aee47f6656c8e13986e20b27874f2dbcbee1168a6314a67f234909bb4e94a55903bcecddd072c1b9804475212
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.4.1] - 2025-11-28
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- CLI exec message output optimization for better user experience
|
|
7
|
+
- RuboCop compliance issues resolved
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Improved output formatting with colored success/failure messages
|
|
11
|
+
- Debug logs now only shown with `--verbose` flag or debug environment variables
|
|
12
|
+
- Enhanced progress indicators during file processing
|
|
13
|
+
|
|
14
|
+
## [0.4.0] - 2025-11-26
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Verbose mode option (`--verbose` flag) for detailed output during formatting
|
|
18
|
+
- Git commit hook configuration with Lefthook integration for automatic formatting
|
|
19
|
+
- RubyGems badge and installation instructions in README
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Improved documentation structure and readability in user guides (English and Japanese)
|
|
23
|
+
- Enhanced logging system with verbose output support
|
|
24
|
+
- Updated benchmark documentation in README
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Command formatting to execution conversion issues
|
|
28
|
+
- Documentation version command display
|
|
29
|
+
- Various code quality improvements based on Clippy suggestions
|
|
30
|
+
|
|
3
31
|
## [0.3.0] - 2025-11-25
|
|
4
32
|
|
|
5
33
|
### Changed
|
data/Cargo.lock
CHANGED
data/README.md
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
A Ruby code formatter written in Rust
|
|
6
6
|
|
|
7
|
-
[](https://
|
|
8
|
-
[](https://github.com/fujitanisora/rfmt/actions)
|
|
7
|
+
[](https://rubygems.org/gems/rfmt)
|
|
9
8
|
[](https://opensource.org/licenses/MIT)
|
|
10
9
|
|
|
11
10
|
[Installation](#installation) •
|
|
@@ -20,6 +19,8 @@ A Ruby code formatter written in Rust
|
|
|
20
19
|
|
|
21
20
|
## What is rfmt?
|
|
22
21
|
|
|
22
|
+
[RubyGems reference](https://rubygems.org/gems/rfmt)
|
|
23
|
+
|
|
23
24
|
**rfmt** is a Ruby code formatter that enforces consistent style across your codebase. Key characteristics:
|
|
24
25
|
|
|
25
26
|
- **Opinionated**: Minimal configuration with consistent output
|
|
@@ -44,7 +45,7 @@ Enforces code style rules:
|
|
|
44
45
|
|
|
45
46
|
## Performance Benchmarks
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
Execution time comparison on a Rails project (111 files, 3,241 lines):
|
|
48
49
|
|
|
49
50
|
| Test Type | Files | rfmt | RuboCop | Ratio |
|
|
50
51
|
|-----------|-------|------|---------|-------|
|
|
@@ -52,6 +53,13 @@ Performance comparison with RuboCop on a Rails project (111 files, 3,241 lines):
|
|
|
52
53
|
| Directory | 14 | 176ms | 1.68s | 9.6x |
|
|
53
54
|
| Full Project (check) | 111 | 172ms | 4.36s | 25.4x |
|
|
54
55
|
|
|
56
|
+
**About this comparison:**
|
|
57
|
+
- RuboCop times include startup overhead and loading all cops (linting rules)
|
|
58
|
+
- RuboCop was run with default configuration (all cops enabled)
|
|
59
|
+
- rfmt is a formatting-only tool with minimal overhead
|
|
60
|
+
- Both tools were measured in check mode (no file modifications)
|
|
61
|
+
- Results are averages from 10 runs per test
|
|
62
|
+
|
|
55
63
|
**Observations:**
|
|
56
64
|
- rfmt execution time remains constant (172-191ms) regardless of file count
|
|
57
65
|
- Low variance across runs (standard deviation: 8-23ms)
|
|
@@ -59,7 +67,7 @@ Performance comparison with RuboCop on a Rails project (111 files, 3,241 lines):
|
|
|
59
67
|
**Test Environment:**
|
|
60
68
|
- CPU: Apple Silicon (arm64)
|
|
61
69
|
- Ruby: 3.4.5
|
|
62
|
-
- rfmt: 0.
|
|
70
|
+
- rfmt: 0.3.0, RuboCop: 1.81.7
|
|
63
71
|
|
|
64
72
|
See [detailed benchmark report](docs/benchmark.md) for complete data.
|
|
65
73
|
|
|
@@ -146,13 +154,13 @@ rfmt init --force
|
|
|
146
154
|
Format a single file:
|
|
147
155
|
|
|
148
156
|
```bash
|
|
149
|
-
rfmt
|
|
157
|
+
rfmt exec lib/user.rb
|
|
150
158
|
```
|
|
151
159
|
|
|
152
160
|
Format multiple files:
|
|
153
161
|
|
|
154
162
|
```bash
|
|
155
|
-
rfmt
|
|
163
|
+
rfmt exec lib/**/*.rb
|
|
156
164
|
```
|
|
157
165
|
|
|
158
166
|
Check if files need formatting (CI/CD):
|
|
@@ -164,7 +172,15 @@ rfmt check .
|
|
|
164
172
|
Show diff without modifying files:
|
|
165
173
|
|
|
166
174
|
```bash
|
|
167
|
-
rfmt
|
|
175
|
+
rfmt exec lib/user.rb --diff
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Enable verbose output for debugging:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
rfmt exec lib/user.rb --verbose
|
|
182
|
+
# or use environment variable
|
|
183
|
+
DEBUG=1 rfmt exec lib/user.rb
|
|
168
184
|
```
|
|
169
185
|
|
|
170
186
|
### Ruby API
|
|
@@ -255,6 +271,48 @@ class User < ApplicationRecord
|
|
|
255
271
|
end
|
|
256
272
|
```
|
|
257
273
|
|
|
274
|
+
## Development
|
|
275
|
+
|
|
276
|
+
### Setup
|
|
277
|
+
|
|
278
|
+
After cloning the repository:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
bundle install
|
|
282
|
+
bundle exec lefthook install
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Git Hooks
|
|
286
|
+
|
|
287
|
+
This project uses [lefthook](https://github.com/evilmartians/lefthook) for automated validation before push:
|
|
288
|
+
|
|
289
|
+
**Pre-push checks:**
|
|
290
|
+
- RuboCop (Ruby linting)
|
|
291
|
+
- cargo fmt --check (Rust formatting)
|
|
292
|
+
- cargo clippy (Rust linting)
|
|
293
|
+
|
|
294
|
+
**Skip hooks temporarily:**
|
|
295
|
+
```bash
|
|
296
|
+
# Skip all hooks for this push
|
|
297
|
+
LEFTHOOK=0 git push
|
|
298
|
+
|
|
299
|
+
# Skip specific hook
|
|
300
|
+
LEFTHOOK_EXCLUDE=rubocop git push
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Running Tests
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# Ruby tests
|
|
307
|
+
bundle exec rspec
|
|
308
|
+
|
|
309
|
+
# Rust tests
|
|
310
|
+
cargo test --manifest-path ext/rfmt/Cargo.toml
|
|
311
|
+
|
|
312
|
+
# All tests
|
|
313
|
+
bundle exec rake dev:test_all
|
|
314
|
+
```
|
|
315
|
+
|
|
258
316
|
## Documentation
|
|
259
317
|
|
|
260
318
|
Documentation is available in the [docs](docs/) directory:
|
data/ext/rfmt/Cargo.toml
CHANGED
data/ext/rfmt/src/ast/mod.rs
CHANGED
|
@@ -174,6 +174,7 @@ impl Node {
|
|
|
174
174
|
|
|
175
175
|
/// Add metadata to the node
|
|
176
176
|
#[cfg(test)]
|
|
177
|
+
#[allow(dead_code)]
|
|
177
178
|
pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
|
|
178
179
|
self.metadata = metadata;
|
|
179
180
|
self
|
|
@@ -181,6 +182,7 @@ impl Node {
|
|
|
181
182
|
|
|
182
183
|
/// Add comments to the node
|
|
183
184
|
#[cfg(test)]
|
|
185
|
+
#[allow(dead_code)]
|
|
184
186
|
pub fn with_comments(mut self, comments: Vec<Comment>) -> Self {
|
|
185
187
|
self.comments = comments;
|
|
186
188
|
self
|
data/ext/rfmt/src/config/mod.rs
CHANGED
data/ext/rfmt/src/emitter/mod.rs
CHANGED
|
@@ -309,11 +309,18 @@ impl Emitter {
|
|
|
309
309
|
/// Emit if/unless/elsif/else node
|
|
310
310
|
/// is_elsif: true if this is an elsif clause (don't emit 'end')
|
|
311
311
|
/// keyword: "if" or "unless"
|
|
312
|
-
fn emit_if_unless(
|
|
312
|
+
fn emit_if_unless(
|
|
313
|
+
&mut self,
|
|
314
|
+
node: &Node,
|
|
315
|
+
indent_level: usize,
|
|
316
|
+
is_elsif: bool,
|
|
317
|
+
keyword: &str,
|
|
318
|
+
) -> Result<()> {
|
|
313
319
|
// Check if this is a postfix if (modifier form)
|
|
314
320
|
// In postfix if, the statements come before the if keyword in source
|
|
315
321
|
let is_postfix = if let (Some(predicate), Some(statements)) =
|
|
316
|
-
(node.children.first(), node.children.get(1))
|
|
322
|
+
(node.children.first(), node.children.get(1))
|
|
323
|
+
{
|
|
317
324
|
statements.location.start_offset < predicate.location.start_offset
|
|
318
325
|
} else {
|
|
319
326
|
false
|
|
@@ -399,7 +406,7 @@ impl Emitter {
|
|
|
399
406
|
NodeType::ElseNode => {
|
|
400
407
|
// This is an else clause
|
|
401
408
|
self.emit_indent(indent_level)?;
|
|
402
|
-
|
|
409
|
+
writeln!(self.buffer, "else")?;
|
|
403
410
|
|
|
404
411
|
// Emit else body (first child of ElseNode)
|
|
405
412
|
if let Some(else_statements) = consequent.children.first() {
|
data/ext/rfmt/src/lib.rs
CHANGED
|
@@ -29,7 +29,10 @@ fn format_ruby_code(ruby: &Ruby, source: String, json: String) -> Result<String,
|
|
|
29
29
|
// Load configuration from file or use defaults
|
|
30
30
|
log::info!("Attempting to discover config file...");
|
|
31
31
|
let config = Config::discover().map_err(|e| e.to_magnus_error(ruby))?;
|
|
32
|
-
log::info!(
|
|
32
|
+
log::info!(
|
|
33
|
+
"Config loaded successfully, line_length: {}",
|
|
34
|
+
config.formatting.line_length
|
|
35
|
+
);
|
|
33
36
|
let mut emitter = Emitter::with_source(config, source);
|
|
34
37
|
|
|
35
38
|
let formatted = emitter.emit(&ast).map_err(|e| e.to_magnus_error(ruby))?;
|
|
@@ -22,10 +22,20 @@ impl RfmtLogger {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
pub fn init() {
|
|
25
|
+
// Check for debug mode via environment variables
|
|
26
|
+
let debug_mode = std::env::var("DEBUG").is_ok()
|
|
27
|
+
|| std::env::var("RFMT_DEBUG").is_ok()
|
|
28
|
+
|| std::env::var("RUST_LOG").is_ok();
|
|
29
|
+
|
|
25
30
|
let level = std::env::var("RFMT_LOG")
|
|
26
31
|
.ok()
|
|
27
32
|
.and_then(|s| s.parse().ok())
|
|
28
|
-
.unwrap_or(
|
|
33
|
+
.unwrap_or(if debug_mode {
|
|
34
|
+
LevelFilter::Info
|
|
35
|
+
} else {
|
|
36
|
+
// In normal mode, only show warnings and errors
|
|
37
|
+
LevelFilter::Warn
|
|
38
|
+
});
|
|
29
39
|
let logger = Self::new(level);
|
|
30
40
|
log::set_boxed_logger(Box::new(logger)).expect("Failed to initialize logger");
|
|
31
41
|
log::set_max_level(LevelFilter::Trace);
|
|
@@ -63,7 +73,6 @@ impl Log for RfmtLogger {
|
|
|
63
73
|
#[cfg(test)]
|
|
64
74
|
mod tests {
|
|
65
75
|
use super::*;
|
|
66
|
-
use log::{debug, error, info, trace, warn};
|
|
67
76
|
use std::sync::{Arc, Mutex};
|
|
68
77
|
|
|
69
78
|
struct TestWriter {
|
data/lib/rfmt/cli.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'thor'
|
|
4
|
+
|
|
5
|
+
# Check for verbose flag before loading rfmt to set debug mode early
|
|
6
|
+
ENV['RFMT_DEBUG'] = '1' if ARGV.include?('-v') || ARGV.include?('--verbose')
|
|
7
|
+
|
|
4
8
|
require 'rfmt'
|
|
5
9
|
require 'rfmt/configuration'
|
|
6
10
|
require 'rfmt/cache'
|
|
@@ -43,7 +47,7 @@ module Rfmt
|
|
|
43
47
|
class_option :config, type: :string, desc: 'Path to configuration file'
|
|
44
48
|
class_option :verbose, type: :boolean, aliases: '-v', desc: 'Verbose output'
|
|
45
49
|
|
|
46
|
-
desc '
|
|
50
|
+
desc 'exec [FILES]', 'Format Ruby files'
|
|
47
51
|
option :write, type: :boolean, default: true, desc: 'Write formatted output'
|
|
48
52
|
option :check, type: :boolean, desc: "Check if files are formatted (don't write)"
|
|
49
53
|
option :diff, type: :boolean, desc: 'Show diff of changes'
|
|
@@ -52,7 +56,7 @@ module Rfmt
|
|
|
52
56
|
option :jobs, type: :numeric, desc: 'Number of parallel jobs (default: CPU count)'
|
|
53
57
|
option :cache, type: :boolean, default: true, desc: 'Use cache to skip unchanged files'
|
|
54
58
|
option :cache_dir, type: :string, desc: 'Cache directory (default: ~/.cache/rfmt)'
|
|
55
|
-
def
|
|
59
|
+
def exec(*files)
|
|
56
60
|
config = load_config
|
|
57
61
|
files = files.empty? ? config.files_to_format : files.flatten
|
|
58
62
|
|
|
@@ -72,15 +76,20 @@ module Rfmt
|
|
|
72
76
|
original_count = files.size
|
|
73
77
|
files = files.select { |file| cache.needs_formatting?(file) }
|
|
74
78
|
skipped = original_count - files.size
|
|
75
|
-
say "Skipped #{skipped} unchanged file(s) (
|
|
79
|
+
say "ℹ Skipped #{skipped} unchanged file(s) (cached)", :cyan if skipped.positive? && options[:verbose]
|
|
76
80
|
end
|
|
77
81
|
|
|
78
82
|
if files.empty?
|
|
79
|
-
say 'All files are already formatted', :green
|
|
83
|
+
say '✓ All files are already formatted (cached)', :green
|
|
80
84
|
return
|
|
81
85
|
end
|
|
82
86
|
|
|
83
|
-
|
|
87
|
+
# Show progress message
|
|
88
|
+
if files.size == 1
|
|
89
|
+
say "Processing #{files.first}...", :blue
|
|
90
|
+
else
|
|
91
|
+
say "Processing #{files.size} file(s)...", :blue
|
|
92
|
+
end
|
|
84
93
|
|
|
85
94
|
results = if options[:parallel] && files.size > 1
|
|
86
95
|
format_files_parallel(files)
|
|
@@ -92,7 +101,7 @@ module Rfmt
|
|
|
92
101
|
|
|
93
102
|
desc 'check [FILES]', 'Check if files need formatting'
|
|
94
103
|
def check(*files)
|
|
95
|
-
invoke :
|
|
104
|
+
invoke :exec, files, check: true, write: false
|
|
96
105
|
end
|
|
97
106
|
|
|
98
107
|
desc 'version', 'Show version'
|
|
@@ -202,7 +211,8 @@ module Rfmt
|
|
|
202
211
|
show_diff(result[:file], result[:original], result[:formatted])
|
|
203
212
|
elsif options[:write]
|
|
204
213
|
File.write(result[:file], result[:formatted])
|
|
205
|
-
|
|
214
|
+
# Always show formatted files (not just in verbose mode)
|
|
215
|
+
say "✓ Formatted #{result[:file]}", :green
|
|
206
216
|
|
|
207
217
|
# Update cache after successful write
|
|
208
218
|
cache&.mark_formatted(result[:file])
|
|
@@ -210,7 +220,8 @@ module Rfmt
|
|
|
210
220
|
puts result[:formatted]
|
|
211
221
|
end
|
|
212
222
|
else
|
|
213
|
-
|
|
223
|
+
# Show already formatted files in non-check mode
|
|
224
|
+
say "✓ #{result[:file]} already formatted", :cyan unless options[:check]
|
|
214
225
|
|
|
215
226
|
# Update cache even if no changes (file was checked)
|
|
216
227
|
cache&.mark_formatted(result[:file])
|
|
@@ -220,10 +231,25 @@ module Rfmt
|
|
|
220
231
|
# Save cache to disk
|
|
221
232
|
cache&.save
|
|
222
233
|
|
|
223
|
-
# Summary
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
234
|
+
# Summary - always show a summary message
|
|
235
|
+
if error_count.positive?
|
|
236
|
+
say "\n✗ Failed: #{error_count} error(s) occurred", :red
|
|
237
|
+
elsif options[:check] && failed_count.positive?
|
|
238
|
+
say "\n✗ Check failed: #{failed_count} file(s) need formatting", :yellow
|
|
239
|
+
elsif changed_count.positive?
|
|
240
|
+
# Success message with appropriate details
|
|
241
|
+
say "\n✓ Success! Formatted #{changed_count} file(s)", :green
|
|
242
|
+
elsif results.size == 1
|
|
243
|
+
say "\n✓ Success! File is already formatted", :green
|
|
244
|
+
else
|
|
245
|
+
say "\n✓ Success! All #{results.size} files are already formatted", :green
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Detailed summary in verbose mode
|
|
249
|
+
if options[:verbose]
|
|
250
|
+
say "Total: #{results.size} file(s) processed", :blue
|
|
251
|
+
say "Changed: #{changed_count} file(s)", :yellow if changed_count.positive?
|
|
252
|
+
end
|
|
227
253
|
|
|
228
254
|
exit(1) if (options[:check] && failed_count.positive?) || error_count.positive?
|
|
229
255
|
end
|
data/lib/rfmt/rfmt.so
CHANGED
|
Binary file
|
data/lib/rfmt/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rfmt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- fujitani sora
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-11-
|
|
11
|
+
date: 2025-11-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|