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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1344ebd75598608c54dc975f8afecc17be05152039542ac1c1e80007086530fc
4
- data.tar.gz: b5a29adb45e5163fdfdbbea2526d4ac72bdbca410ee86e888ecb224f3b98c922
3
+ metadata.gz: f49bc8ba6c22392ac19d4f52cb9f82ee0476f849326745d821b952a75b13bb70
4
+ data.tar.gz: 59f68f137d3b769e17463c8c47b5ee3add7c06234caf50b3a20c03a34bf32cc0
5
5
  SHA512:
6
- metadata.gz: 71db0a1b94b8a2288a5fae7e3a09ec41fac6e834abd9760fd45b4ac7dfef71baac7ada32582a6018b74fa22a6d0711f529b8372c491179dfbe66cc9c56c287aa
7
- data.tar.gz: c7dc265d4ba68a15c653715bd26d8c087be6d361a7bb3170bd59057c0b9a65cfbf2093c02ff8528dcffd8a2b4ca445faef2fdc4fa4c5035016360d64ad297b9e
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
@@ -1219,7 +1219,7 @@ checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
1219
1219
 
1220
1220
  [[package]]
1221
1221
  name = "rfmt"
1222
- version = "0.3.0"
1222
+ version = "0.4.1"
1223
1223
  dependencies = [
1224
1224
  "anyhow",
1225
1225
  "clap",
data/README.md CHANGED
@@ -4,8 +4,7 @@
4
4
 
5
5
  A Ruby code formatter written in Rust
6
6
 
7
- [![Gem Version](https://badge.fury.io/rb/rfmt.svg)](https://badge.fury.io/rb/rfmt)
8
- [![Test Status](https://github.com/fujitanisora/rfmt/workflows/test/badge.svg)](https://github.com/fujitanisora/rfmt/actions)
7
+ [![Gem Version](https://badge.fury.io/rb/rfmt.svg)](https://rubygems.org/gems/rfmt)
9
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- Performance comparison with RuboCop on a Rails project (111 files, 3,241 lines):
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.2.4, RuboCop: 1.81.7
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 format lib/user.rb
157
+ rfmt exec lib/user.rb
150
158
  ```
151
159
 
152
160
  Format multiple files:
153
161
 
154
162
  ```bash
155
- rfmt format lib/**/*.rb
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 format lib/user.rb --diff
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
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rfmt"
3
- version = "0.3.0"
3
+ version = "0.4.1"
4
4
  edition = "2021"
5
5
  authors = ["fujitani sora <fujitanisora0414@gmail.com>"]
6
6
  license = "MIT"
@@ -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
@@ -181,6 +181,7 @@ impl Config {
181
181
  }
182
182
 
183
183
  /// Get the indent string based on configuration
184
+ #[cfg(test)]
184
185
  pub fn indent_string(&self) -> String {
185
186
  match self.formatting.indent_style {
186
187
  IndentStyle::Spaces => " ".repeat(self.formatting.indent_width),
@@ -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(&mut self, node: &Node, indent_level: usize, is_elsif: bool, keyword: &str) -> Result<()> {
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
- write!(self.buffer, "else\n")?;
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!("Config loaded successfully, line_length: {}", config.formatting.line_length);
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(LevelFilter::Info);
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 'format [FILES]', 'Format Ruby files'
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 format(*files)
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) (cache hit)", :blue if skipped.positive? && options[:verbose]
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
- say "Formatting #{files.size} file(s)...", :blue if options[:verbose]
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 :format, files, check: true, write: false
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
- say "Formatted #{result[:file]}", :green if options[:verbose]
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
- say "#{result[:file]} already formatted", :blue if options[:verbose]
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
- say "\n#{results.size} file(s) processed", :blue if options[:verbose]
225
- say "#{changed_count} file(s) changed", :yellow if changed_count.positive? && options[:verbose]
226
- say "#{error_count} error(s)", :red if error_count.positive?
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rfmt
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.1'
5
5
  end
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.3.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-25 00:00:00.000000000 Z
11
+ date: 2025-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys