rfmt 1.3.3 → 1.3.4

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: 7d2092d9d53fa47024fcb8a13fb635a6da7eba5d4c51d3a8df854d551380037b
4
- data.tar.gz: 59bb09f50cb7ee5a033328300a581e973879aaad56067d267b041c2070cac8bf
3
+ metadata.gz: 9ac6a3e001e85e27839f04ba78564e5bc16675818b2603a179f62d7fa35c9062
4
+ data.tar.gz: 4957bbc0de84f4e5c72dacf514ea0e87f211979667a50b53f365c005f1ee710a
5
5
  SHA512:
6
- metadata.gz: f7043e22d625b0b6a689f01950a6ed0fae7ab3fa1cfa2dcd51af544d72aadf8b0c3aa65a3465c9f9baabf2f1a000c37bddb2f2f57e2defd11344f6194ad3b68b
7
- data.tar.gz: 407b70b3614da3ed19c74bd43d9491ee9bff1ce6cb1ef7f1e1d2d0b71068afb937bd5409668ae0c7454a6f2d12cb4764fa6df0273bef149980be30e0f9f1d7b6
6
+ metadata.gz: 470807974090b7b41d5c9bf5dd437cd34d80cdbb45ed1c9af2d97f93c0a01c88e5e709dcb47b6e077909be7bbb586f7df4eb798e2403dd2186a4d08b6770463e
7
+ data.tar.gz: 6620293ef2c43673eecbf478e2c4d3ba1b79a0ad71e67ec984547c0aeda36e03f7c10d848c646a74bc392e4057aeb2f4c4dc56d2602a96e295bf77f0fbedbf3e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.3.4] - 2026-01-17
4
+
5
+ ### Added
6
+ - New `rfmt_fast` executable for optimized performance
7
+ - Automatic parallel processing detection logic
8
+ - Enhanced logging and summary display functionality
9
+
10
+ ### Changed
11
+ - Optimize logging and summary display performance
12
+ - Improve parallel execution logic with automatic detection
13
+ - Update README with new features and usage examples
14
+ - Code formatting improvements with Rubocop compliance
15
+
16
+ ### Fixed
17
+ - Logger optimization to reduce overhead
18
+ - Parallel processing logic refinements
19
+
3
20
  ## [1.3.3] - 2026-01-17
4
21
 
5
22
  ### Fixed
data/Cargo.lock CHANGED
@@ -1214,7 +1214,7 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
1214
1214
 
1215
1215
  [[package]]
1216
1216
  name = "rfmt"
1217
- version = "1.3.3"
1217
+ version = "1.3.4"
1218
1218
  dependencies = [
1219
1219
  "anyhow",
1220
1220
  "clap",
data/README.md CHANGED
@@ -166,6 +166,12 @@ Format multiple files:
166
166
  rfmt lib/**/*.rb
167
167
  ```
168
168
 
169
+ Format all files in your project:
170
+
171
+ ```bash
172
+ rfmt .
173
+ ```
174
+
169
175
  Check if files need formatting (CI/CD):
170
176
 
171
177
  ```bash
@@ -178,12 +184,92 @@ Show diff without modifying files:
178
184
  rfmt lib/user.rb --diff
179
185
  ```
180
186
 
187
+ Quiet mode (minimal output):
188
+
189
+ ```bash
190
+ rfmt --quiet lib/**/*.rb
191
+ ```
192
+
181
193
  Enable verbose output for debugging:
182
194
 
183
195
  ```bash
184
- rfmt lib/user.rb --verbose
185
- # or use environment variable
186
- DEBUG=1 rfmt lib/user.rb
196
+ rfmt --verbose lib/user.rb
197
+ ```
198
+
199
+ #### Common Options
200
+
201
+ | Option | Description |
202
+ |--------|-------------|
203
+ | `--check` | Check formatting without writing files |
204
+ | `--diff` | Show diff of changes |
205
+ | `--quiet` | Minimal output |
206
+ | `--verbose` | Detailed output with timing |
207
+
208
+ ### Output Modes
209
+
210
+ **Normal mode** (default):
211
+ ```bash
212
+ $ rfmt app/
213
+ Processing 25 file(s)...
214
+ ✓ Formatted app/controllers/users_controller.rb
215
+ ✓ Formatted app/models/user.rb
216
+
217
+ ✓ Processed 25 files
218
+ (3 formatted, 22 unchanged)
219
+ ```
220
+
221
+ **Quiet mode** (`--quiet` or `-q`):
222
+ ```bash
223
+ $ rfmt --quiet app/
224
+ ✓ 3 files formatted
225
+ ```
226
+
227
+ **Verbose mode** (`--verbose` or `-v`):
228
+ ```bash
229
+ $ rfmt --verbose app/
230
+ Processing 25 file(s)...
231
+ Using sequential processing for 25 files
232
+ ✓ Formatted app/controllers/users_controller.rb
233
+ ✓ app/models/application_record.rb already formatted
234
+ ...
235
+
236
+ ✓ Processed 25 files
237
+ (3 formatted, 22 unchanged)
238
+
239
+ Details:
240
+ Total files: 25
241
+ Total time: 0.45s
242
+ Files/sec: 55.6
243
+ ```
244
+
245
+ ### Parallel Processing
246
+
247
+ rfmt automatically chooses the optimal processing mode:
248
+
249
+ - **< 20 files**: Sequential processing (fastest for small batches)
250
+ - **20-49 files**: Automatic based on average file size
251
+ - **≥ 50 files**: Parallel processing (utilizes multiple cores)
252
+
253
+ You can override this behavior:
254
+
255
+ ```bash
256
+ # Force parallel processing
257
+ rfmt --parallel app/
258
+
259
+ # Force sequential processing
260
+ rfmt --no-parallel app/
261
+ ```
262
+
263
+ ### Cache Management
264
+
265
+ rfmt uses caching to improve performance on large codebases:
266
+
267
+ ```bash
268
+ # Clear cache if needed
269
+ rfmt cache clear
270
+
271
+ # View cache statistics
272
+ rfmt cache stats
187
273
  ```
188
274
 
189
275
  ### Ruby API
data/exe/rfmt CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ # Suppress gem warnings for performance (saves ~2 seconds)
5
+ $VERBOSE = nil unless ENV['RFMT_VERBOSE']
6
+
4
7
  require 'rfmt/cli'
5
8
 
6
9
  # Known subcommands - if first arg is not one of these, prepend 'format'
data/ext/rfmt/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rfmt"
3
- version = "1.3.3"
3
+ version = "1.3.4"
4
4
  edition = "2021"
5
5
  authors = ["fujitani sora <fujitanisora0414@gmail.com>"]
6
6
  license = "MIT"
data/lib/rfmt/cli.rb CHANGED
@@ -44,6 +44,10 @@ module Rfmt
44
44
 
45
45
  # Command Line Interface for rfmt
46
46
  class CLI < Thor
47
+ # Constants
48
+ PROGRESS_THRESHOLD = 20 # Show progress for file counts >= this
49
+ PROGRESS_INTERVAL = 10 # Update progress every N files
50
+
47
51
  class_option :config, type: :string, desc: 'Path to configuration file'
48
52
  class_option :verbose, type: :boolean, aliases: '-v', desc: 'Verbose output'
49
53
 
@@ -54,10 +58,11 @@ module Rfmt
54
58
  option :check, type: :boolean, desc: "Check if files are formatted (don't write)"
55
59
  option :diff, type: :boolean, desc: 'Show diff of changes'
56
60
  option :diff_format, type: :string, default: 'unified', desc: 'Diff format: unified, side_by_side, or color'
57
- option :parallel, type: :boolean, default: true, desc: 'Process files in parallel'
61
+ option :parallel, type: :boolean, desc: 'Use parallel processing (auto-disabled for <20 files)'
58
62
  option :jobs, type: :numeric, desc: 'Number of parallel jobs (default: CPU count)'
59
63
  option :cache, type: :boolean, default: true, desc: 'Use cache to skip unchanged files'
60
64
  option :cache_dir, type: :string, desc: 'Cache directory (default: ~/.cache/rfmt)'
65
+ option :quiet, type: :boolean, aliases: '-q', desc: 'Minimal output (errors and summary only)'
61
66
  def format(*files)
62
67
  config = load_config
63
68
  files = files.empty? ? config.files_to_format : files.flatten
@@ -67,33 +72,32 @@ module Rfmt
67
72
  return
68
73
  end
69
74
 
70
- # Initialize cache
71
- cache = if options[:cache]
72
- cache_opts = options[:cache_dir] ? { cache_dir: options[:cache_dir] } : {}
73
- Cache.new(**cache_opts)
74
- end
75
-
76
- # Filter files using cache
77
- if cache
78
- original_count = files.size
79
- files = files.select { |file| cache.needs_formatting?(file) }
80
- skipped = original_count - files.size
81
- say "ℹ Skipped #{skipped} unchanged file(s) (cached)", :cyan if skipped.positive? && options[:verbose]
82
- end
75
+ # Initialize and use cache if enabled
76
+ cache = initialize_cache_if_enabled
77
+ files = filter_files_with_cache(files, cache)
83
78
 
84
79
  if files.empty?
85
- say '✓ All files are already formatted (cached)', :green
80
+ say '✓ All files are already formatted (cached)', :cyan
86
81
  return
87
82
  end
88
83
 
89
- # Show progress message
90
- if files.size == 1
91
- say "Processing #{files.first}...", :blue
92
- else
93
- say "Processing #{files.size} file(s)...", :blue
84
+ # Show progress message (unless in quiet mode)
85
+ unless options[:quiet]
86
+ if files.size == 1
87
+ say "Processing #{files.first}...", :blue
88
+ else
89
+ say "Processing #{files.size} file(s)...", :blue
90
+ end
94
91
  end
95
92
 
96
- results = if options[:parallel] && files.size > 1
93
+ use_parallel = should_use_parallel?(files)
94
+
95
+ if options[:verbose] && files.size > 1
96
+ mode = use_parallel ? "parallel (#{options[:jobs] || 'auto'} jobs)" : 'sequential'
97
+ say "Using #{mode} processing for #{files.size} files", :blue
98
+ end
99
+
100
+ results = if use_parallel
97
101
  format_files_parallel(files)
98
102
  else
99
103
  format_files_sequential(files)
@@ -140,6 +144,39 @@ module Rfmt
140
144
 
141
145
  private
142
146
 
147
+ # Intelligently decide whether to use parallel processing
148
+ def should_use_parallel?(files)
149
+ return false if files.size <= 1
150
+
151
+ # Check if parallel option was explicitly set via command line
152
+ # Thor sets options[:parallel] to true/false for --parallel/--no-parallel
153
+ # and nil when not specified
154
+ return options[:parallel] unless options[:parallel].nil?
155
+
156
+ # Auto decision based on workload characteristics
157
+ # Calculate total size for better decision
158
+ total_size = files.sum do |f|
159
+ File.size(f)
160
+ rescue StandardError
161
+ 0
162
+ end
163
+ avg_size = total_size / files.size.to_f
164
+
165
+ # Decision matrix:
166
+ # - Less than 20 files: sequential (overhead > benefit)
167
+ # - 20-50 files with small size (<10KB avg): sequential
168
+ # - 20-50 files with large size (>10KB avg): parallel
169
+ # - More than 50 files: always parallel
170
+
171
+ if files.size < 20
172
+ false
173
+ elsif files.size < 50
174
+ avg_size > 10_000 # 10KB threshold
175
+ else
176
+ true
177
+ end
178
+ end
179
+
143
180
  def load_config
144
181
  if options[:config]
145
182
  Configuration.new(file: options[:config])
@@ -148,25 +185,68 @@ module Rfmt
148
185
  end
149
186
  end
150
187
 
188
+ def initialize_cache_if_enabled
189
+ return nil unless options[:cache]
190
+
191
+ cache_opts = options[:cache_dir] ? { cache_dir: options[:cache_dir] } : {}
192
+ Cache.new(**cache_opts)
193
+ end
194
+
195
+ def filter_files_with_cache(files, cache)
196
+ return files unless cache
197
+
198
+ original_count = files.size
199
+ filtered = files.select { |file| cache.needs_formatting?(file) }
200
+
201
+ log_cache_skip(original_count - filtered.size)
202
+ filtered
203
+ end
204
+
205
+ def log_cache_skip(skipped_count)
206
+ return unless skipped_count.positive? && options[:verbose]
207
+
208
+ say "ℹ Skipped #{skipped_count} unchanged file(s) (cached)", :cyan
209
+ end
210
+
151
211
  def format_files_sequential(files)
152
- files.map do |file|
212
+ show_progress = should_show_progress?(files)
213
+
214
+ files.map.with_index do |file, index|
215
+ display_progress(index, files.size) if show_progress && (index % PROGRESS_INTERVAL).zero?
153
216
  format_single_file(file)
154
217
  end
155
218
  end
156
219
 
220
+ def should_show_progress?(files)
221
+ !options[:quiet] && files.size >= PROGRESS_THRESHOLD
222
+ end
223
+
224
+ def display_progress(index, total)
225
+ percentage = ((index.to_f / total) * 100).round
226
+ say "[#{index}/#{total}] #{percentage}% complete...", :blue
227
+ end
228
+
157
229
  def format_files_parallel(files)
158
230
  require 'parallel'
159
231
 
160
- # Determine number of processes to use
161
- process_count = options[:jobs] || Parallel.processor_count
162
-
163
- say "Processing #{files.size} files with #{process_count} parallel jobs...", :blue if options[:verbose]
232
+ process_count = determine_process_count
233
+ log_parallel_processing(files.size, process_count)
164
234
 
165
235
  Parallel.map(files, in_processes: process_count) do |file|
166
236
  format_single_file(file)
167
237
  end
168
238
  end
169
239
 
240
+ def determine_process_count
241
+ options[:jobs] || Parallel.processor_count
242
+ end
243
+
244
+ def log_parallel_processing(file_count, process_count)
245
+ return unless options[:verbose]
246
+
247
+ say "Processing #{file_count} files with #{process_count} parallel jobs...", :blue
248
+ end
249
+
170
250
  def format_single_file(file)
171
251
  start_time = Time.now
172
252
  source = File.read(file)
@@ -191,69 +271,126 @@ module Rfmt
191
271
  end
192
272
 
193
273
  def handle_results(results, cache = nil)
194
- failed_count = 0
195
- changed_count = 0
196
- error_count = 0
274
+ stats = process_results(results, cache)
275
+ stats[:total_duration] = results.sum { |r| r[:duration] || 0 }
276
+ cache&.save
277
+ display_summary(stats, results.size)
278
+ exit(1) if should_exit_with_error?(stats)
279
+ end
280
+
281
+ def process_results(results, cache)
282
+ stats = { changed: 0, errors: 0, failed: 0, duration: 0 }
197
283
 
198
284
  results.each do |result|
199
285
  if result[:error]
200
- say "Error in #{result[:file]}: #{result[:error]}", :red
201
- error_count += 1
202
- next
286
+ handle_error_result(result, stats)
287
+ elsif result[:changed]
288
+ handle_changed_result(result, stats, cache)
289
+ else
290
+ handle_unchanged_result(result, cache)
203
291
  end
292
+ end
204
293
 
205
- if result[:changed]
206
- changed_count += 1
207
-
208
- if options[:check]
209
- say "#{result[:file]} needs formatting", :yellow
210
- failed_count += 1
211
- show_diff(result[:file], result[:original], result[:formatted]) if options[:diff]
212
- elsif options[:diff]
213
- show_diff(result[:file], result[:original], result[:formatted])
214
- elsif options[:write]
215
- File.write(result[:file], result[:formatted])
216
- # Always show formatted files (not just in verbose mode)
217
- say "✓ Formatted #{result[:file]}", :green
218
-
219
- # Update cache after successful write
220
- cache&.mark_formatted(result[:file])
221
- else
222
- puts result[:formatted]
223
- end
224
- else
225
- # Show already formatted files in non-check mode
226
- say "✓ #{result[:file]} already formatted", :cyan unless options[:check]
294
+ stats
295
+ end
227
296
 
228
- # Update cache even if no changes (file was checked)
229
- cache&.mark_formatted(result[:file])
230
- end
297
+ def handle_error_result(result, stats)
298
+ say "Error in #{result[:file]}: #{result[:error]}", :red
299
+ stats[:errors] += 1
300
+ end
301
+
302
+ def handle_changed_result(result, stats, cache)
303
+ stats[:changed] += 1
304
+
305
+ if options[:check]
306
+ say "#{result[:file]} needs formatting", :yellow
307
+ stats[:failed] += 1
308
+ show_diff(result[:file], result[:original], result[:formatted]) if options[:diff]
309
+ elsif options[:diff]
310
+ show_diff(result[:file], result[:original], result[:formatted])
311
+ elsif options[:write]
312
+ write_formatted_file(result, cache)
313
+ else
314
+ puts result[:formatted]
231
315
  end
316
+ end
232
317
 
233
- # Save cache to disk
234
- cache&.save
318
+ def handle_unchanged_result(result, cache)
319
+ say "✓ #{result[:file]} already formatted", :white if options[:verbose] && !options[:check]
320
+ cache&.mark_formatted(result[:file])
321
+ end
235
322
 
236
- # Summary - always show a summary message
237
- if error_count.positive?
238
- say "\n✗ Failed: #{error_count} error(s) occurred", :red
239
- elsif options[:check] && failed_count.positive?
240
- say "\n✗ Check failed: #{failed_count} file(s) need formatting", :yellow
241
- elsif changed_count.positive?
242
- # Success message with appropriate details
243
- say "\n✓ Success! Formatted #{changed_count} file(s)", :green
244
- elsif results.size == 1
245
- say "\n✓ Success! File is already formatted", :green
323
+ def write_formatted_file(result, cache)
324
+ File.write(result[:file], result[:formatted])
325
+ say " Formatted #{result[:file]}", :green unless options[:quiet]
326
+ cache&.mark_formatted(result[:file])
327
+ end
328
+
329
+ def display_summary(stats, total_files)
330
+ @last_stats = stats # Store for verbose details
331
+ unchanged_count = total_files - stats[:changed] - stats[:errors]
332
+
333
+ if stats[:errors].positive?
334
+ display_error_summary(stats[:errors])
335
+ elsif options[:check] && stats[:failed].positive?
336
+ display_check_failed_summary(stats[:failed])
337
+ elsif options[:quiet]
338
+ display_quiet_summary(stats[:changed])
246
339
  else
247
- say "\n✓ Success! All #{results.size} files are already formatted", :green
340
+ display_normal_summary(stats[:changed], unchanged_count, total_files)
248
341
  end
249
342
 
250
- # Detailed summary in verbose mode
251
- if options[:verbose]
252
- say "Total: #{results.size} file(s) processed", :blue
253
- say "Changed: #{changed_count} file(s)", :yellow if changed_count.positive?
343
+ display_verbose_details(total_files) if options[:verbose] && !options[:quiet]
344
+ end
345
+
346
+ def display_error_summary(error_count)
347
+ say "\n✗ Failed: #{error_count} error(s) occurred", :red
348
+ end
349
+
350
+ def display_check_failed_summary(failed_count)
351
+ say "\n✗ Check failed: #{failed_count} file(s) need formatting", :yellow
352
+ end
353
+
354
+ def display_quiet_summary(changed_count)
355
+ say "✓ #{changed_count} files formatted", :cyan if changed_count.positive?
356
+ end
357
+
358
+ def display_normal_summary(changed_count, unchanged_count, total_files)
359
+ if total_files == 1
360
+ if changed_count.positive?
361
+ say "\n✓ Formatted 1 file", :cyan
362
+ else
363
+ say "\n✓ File is already formatted", :cyan
364
+ end
365
+ else
366
+ say "\n✓ Processed #{total_files} files", :cyan
367
+ display_file_breakdown(changed_count, unchanged_count)
254
368
  end
369
+ end
370
+
371
+ def display_file_breakdown(changed_count, unchanged_count)
372
+ return unless changed_count.positive? || unchanged_count.positive?
373
+
374
+ parts = []
375
+ parts << "#{changed_count} formatted" if changed_count.positive?
376
+ parts << "#{unchanged_count} unchanged" if unchanged_count.positive?
377
+ say " (#{parts.join(', ')})", :white
378
+ end
379
+
380
+ def display_verbose_details(total_files)
381
+ say "\nDetails:", :blue
382
+ say " Total files: #{total_files}", :blue
383
+
384
+ # Duration is collected if available
385
+ return unless defined?(@last_stats) && @last_stats[:total_duration]
386
+
387
+ duration = @last_stats[:total_duration].round(2)
388
+ say " Total time: #{duration}s", :blue
389
+ say " Files/sec: #{(total_files / duration).round(1)}", :blue if duration.positive?
390
+ end
255
391
 
256
- exit(1) if (options[:check] && failed_count.positive?) || error_count.positive?
392
+ def should_exit_with_error?(stats)
393
+ (options[:check] && stats[:failed].positive?) || stats[:errors].positive?
257
394
  end
258
395
 
259
396
  def show_diff(file, original, formatted)
data/lib/rfmt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rfmt
4
- VERSION = '1.3.3'
4
+ VERSION = '1.3.4'
5
5
  end
data/lib/rfmt.rb CHANGED
@@ -102,14 +102,9 @@ module Rfmt
102
102
  # @param force [Boolean] Overwrite existing file if true
103
103
  # @return [Boolean] true if file was created, false if already exists
104
104
  def self.init(path = '.rfmt.yml', force: false)
105
- if File.exist?(path) && !force
106
- warn "Configuration file already exists: #{path}"
107
- warn 'Use force: true to overwrite'
108
- return false
109
- end
105
+ return false if File.exist?(path) && !force
110
106
 
111
107
  File.write(path, DEFAULT_CONFIG)
112
- puts "Created rfmt configuration file: #{path}"
113
108
  true
114
109
  end
115
110
 
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: 1.3.3
4
+ version: 1.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - fujitani sora
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-16 00:00:00.000000000 Z
11
+ date: 2026-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys