rfmt 1.3.2 → 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 +4 -4
- data/CHANGELOG.md +29 -0
- data/Cargo.lock +1 -1
- data/README.md +89 -3
- data/exe/rfmt +3 -0
- data/ext/rfmt/Cargo.toml +1 -1
- data/ext/rfmt/src/emitter/mod.rs +37 -60
- data/ext/rfmt/src/lib.rs +0 -11
- data/lib/rfmt/cli.rb +212 -75
- data/lib/rfmt/native_extension_loader.rb +138 -0
- data/lib/rfmt/version.rb +1 -1
- data/lib/rfmt.rb +5 -7
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ac6a3e001e85e27839f04ba78564e5bc16675818b2603a179f62d7fa35c9062
|
|
4
|
+
data.tar.gz: 4957bbc0de84f4e5c72dacf514ea0e87f211979667a50b53f365c005f1ee710a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 470807974090b7b41d5c9bf5dd437cd34d80cdbb45ed1c9af2d97f93c0a01c88e5e709dcb47b6e077909be7bbb586f7df4eb798e2403dd2186a4d08b6770463e
|
|
7
|
+
data.tar.gz: 6620293ef2c43673eecbf478e2c4d3ba1b79a0ad71e67ec984547c0aeda36e03f7c10d848c646a74bc392e4057aeb2f4c4dc56d2602a96e295bf77f0fbedbf3e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
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
|
+
|
|
20
|
+
## [1.3.3] - 2026-01-17
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Add native extension loader for Ruby 3.3+ compatibility (#65)
|
|
24
|
+
- Resolves LoadError on Ruby 3.3+ arm64-darwin systems
|
|
25
|
+
- Implements dynamic path resolution for version-specific directories
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- Remove unnecessary String clones in comment emission (performance optimization)
|
|
29
|
+
- Remove debug logs and obvious comments from codebase
|
|
30
|
+
- Update .gitignore with development artifacts
|
|
31
|
+
|
|
3
32
|
## [1.3.2] - 2026-01-09
|
|
4
33
|
|
|
5
34
|
### Added
|
data/Cargo.lock
CHANGED
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
|
|
185
|
-
|
|
186
|
-
|
|
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
data/ext/rfmt/Cargo.toml
CHANGED
data/ext/rfmt/src/emitter/mod.rs
CHANGED
|
@@ -62,13 +62,9 @@ impl Emitter {
|
|
|
62
62
|
|
|
63
63
|
self.emit_node(ast, 0)?;
|
|
64
64
|
|
|
65
|
-
// Find the last emitted code line for proper blank line handling
|
|
66
65
|
let last_code_line = Self::find_last_code_line(ast);
|
|
67
|
-
|
|
68
|
-
// Emit any remaining comments that weren't emitted
|
|
69
66
|
self.emit_remaining_comments(last_code_line)?;
|
|
70
67
|
|
|
71
|
-
// Ensure file ends with a newline
|
|
72
68
|
if !self.buffer.ends_with('\n') {
|
|
73
69
|
self.buffer.push('\n');
|
|
74
70
|
}
|
|
@@ -186,35 +182,26 @@ impl Emitter {
|
|
|
186
182
|
/// Emit comments that appear before a given line
|
|
187
183
|
/// Uses BTreeMap index for O(log n) lookup instead of O(n) iteration
|
|
188
184
|
fn emit_comments_before(&mut self, line: usize, indent_level: usize) -> Result<()> {
|
|
189
|
-
|
|
185
|
+
self.ensure_indent_cache(indent_level);
|
|
190
186
|
|
|
191
|
-
// Use indexed lookup instead of iterating all comments
|
|
192
187
|
let indices = self.get_comment_indices_before(line);
|
|
193
188
|
|
|
194
|
-
// Build list of comments to emit with their data
|
|
195
189
|
let mut comments_to_emit: Vec<_> = indices
|
|
196
190
|
.into_iter()
|
|
197
191
|
.map(|idx| {
|
|
198
192
|
let comment = &self.all_comments[idx];
|
|
199
|
-
(
|
|
200
|
-
idx,
|
|
201
|
-
comment.text.clone(),
|
|
202
|
-
comment.location.start_line,
|
|
203
|
-
comment.location.end_line,
|
|
204
|
-
)
|
|
193
|
+
(idx, comment.location.start_line, comment.location.end_line)
|
|
205
194
|
})
|
|
206
195
|
.collect();
|
|
207
196
|
|
|
208
|
-
|
|
209
|
-
comments_to_emit.sort_by_key(|(_, _, start, _)| *start);
|
|
197
|
+
comments_to_emit.sort_by_key(|(_, start, _)| *start);
|
|
210
198
|
|
|
211
199
|
let comments_count = comments_to_emit.len();
|
|
212
200
|
let mut last_comment_end_line: Option<usize> = None;
|
|
213
201
|
|
|
214
|
-
for (i, (idx,
|
|
202
|
+
for (i, (idx, comment_start_line, comment_end_line)) in
|
|
215
203
|
comments_to_emit.into_iter().enumerate()
|
|
216
204
|
{
|
|
217
|
-
// Preserve blank lines between comments
|
|
218
205
|
if let Some(prev_end) = last_comment_end_line {
|
|
219
206
|
let gap = comment_start_line.saturating_sub(prev_end);
|
|
220
207
|
for _ in 1..gap {
|
|
@@ -222,11 +209,14 @@ impl Emitter {
|
|
|
222
209
|
}
|
|
223
210
|
}
|
|
224
211
|
|
|
225
|
-
writeln!(
|
|
212
|
+
writeln!(
|
|
213
|
+
self.buffer,
|
|
214
|
+
"{}{}",
|
|
215
|
+
&self.indent_cache[indent_level], &self.all_comments[idx].text
|
|
216
|
+
)?;
|
|
226
217
|
self.emitted_comment_indices.insert(idx);
|
|
227
218
|
last_comment_end_line = Some(comment_end_line);
|
|
228
219
|
|
|
229
|
-
// Add blank line after the LAST comment if there was a gap to the code
|
|
230
220
|
if i == comments_count - 1 && line > comment_end_line + 1 {
|
|
231
221
|
self.buffer.push('\n');
|
|
232
222
|
}
|
|
@@ -255,33 +245,24 @@ impl Emitter {
|
|
|
255
245
|
end_line: usize,
|
|
256
246
|
indent_level: usize,
|
|
257
247
|
) -> Result<()> {
|
|
258
|
-
|
|
248
|
+
self.ensure_indent_cache(indent_level);
|
|
259
249
|
|
|
260
|
-
// Use indexed lookup instead of iterating all comments
|
|
261
250
|
let indices = self.get_comment_indices_in_range(start_line, end_line);
|
|
262
251
|
|
|
263
|
-
// Build list of comments to emit, filtering by end_line
|
|
264
252
|
let mut comments_to_emit: Vec<_> = indices
|
|
265
253
|
.into_iter()
|
|
266
254
|
.filter(|&idx| self.all_comments[idx].location.end_line < end_line)
|
|
267
255
|
.map(|idx| {
|
|
268
256
|
let comment = &self.all_comments[idx];
|
|
269
|
-
(
|
|
270
|
-
idx,
|
|
271
|
-
comment.text.clone(),
|
|
272
|
-
comment.location.start_line,
|
|
273
|
-
comment.location.end_line,
|
|
274
|
-
)
|
|
257
|
+
(idx, comment.location.start_line, comment.location.end_line)
|
|
275
258
|
})
|
|
276
259
|
.collect();
|
|
277
260
|
|
|
278
|
-
|
|
279
|
-
comments_to_emit.sort_by_key(|(_, _, start, _)| *start);
|
|
261
|
+
comments_to_emit.sort_by_key(|(_, start, _)| *start);
|
|
280
262
|
|
|
281
263
|
let mut last_comment_end_line: Option<usize> = None;
|
|
282
264
|
|
|
283
|
-
for (idx,
|
|
284
|
-
// Preserve blank lines between comments
|
|
265
|
+
for (idx, comment_start_line, comment_end_line) in comments_to_emit {
|
|
285
266
|
if let Some(prev_end) = last_comment_end_line {
|
|
286
267
|
let gap = comment_start_line.saturating_sub(prev_end);
|
|
287
268
|
for _ in 1..gap {
|
|
@@ -289,7 +270,11 @@ impl Emitter {
|
|
|
289
270
|
}
|
|
290
271
|
}
|
|
291
272
|
|
|
292
|
-
writeln!(
|
|
273
|
+
writeln!(
|
|
274
|
+
self.buffer,
|
|
275
|
+
"{}{}",
|
|
276
|
+
&self.indent_cache[indent_level], &self.all_comments[idx].text
|
|
277
|
+
)?;
|
|
293
278
|
self.emitted_comment_indices.insert(idx);
|
|
294
279
|
last_comment_end_line = Some(comment_end_line);
|
|
295
280
|
}
|
|
@@ -306,39 +291,35 @@ impl Emitter {
|
|
|
306
291
|
indent_level: usize,
|
|
307
292
|
prev_line: usize,
|
|
308
293
|
) -> Result<()> {
|
|
309
|
-
|
|
294
|
+
self.ensure_indent_cache(indent_level);
|
|
310
295
|
|
|
311
|
-
// Use indexed lookup instead of iterating all comments
|
|
312
296
|
let indices = self.get_comment_indices_in_range(start_line, end_line);
|
|
313
297
|
|
|
314
|
-
// Build list of comments to emit, filtering by end_line
|
|
315
298
|
let mut comments_to_emit: Vec<_> = indices
|
|
316
299
|
.into_iter()
|
|
317
300
|
.filter(|&idx| self.all_comments[idx].location.end_line < end_line)
|
|
318
301
|
.map(|idx| {
|
|
319
302
|
let comment = &self.all_comments[idx];
|
|
320
|
-
(
|
|
321
|
-
idx,
|
|
322
|
-
comment.text.clone(),
|
|
323
|
-
comment.location.start_line,
|
|
324
|
-
comment.location.end_line,
|
|
325
|
-
)
|
|
303
|
+
(idx, comment.location.start_line, comment.location.end_line)
|
|
326
304
|
})
|
|
327
305
|
.collect();
|
|
328
306
|
|
|
329
|
-
|
|
330
|
-
comments_to_emit.sort_by_key(|(_, _, start, _)| *start);
|
|
307
|
+
comments_to_emit.sort_by_key(|(_, start, _)| *start);
|
|
331
308
|
|
|
332
309
|
let mut last_end_line: usize = prev_line;
|
|
333
310
|
|
|
334
|
-
for (idx,
|
|
311
|
+
for (idx, comment_start_line, comment_end_line) in comments_to_emit {
|
|
335
312
|
// Preserve blank lines between previous content and this comment
|
|
336
313
|
let gap = comment_start_line.saturating_sub(last_end_line);
|
|
337
314
|
for _ in 1..gap {
|
|
338
315
|
self.buffer.push('\n');
|
|
339
316
|
}
|
|
340
317
|
|
|
341
|
-
writeln!(
|
|
318
|
+
writeln!(
|
|
319
|
+
self.buffer,
|
|
320
|
+
"{}{}",
|
|
321
|
+
&self.indent_cache[indent_level], &self.all_comments[idx].text
|
|
322
|
+
)?;
|
|
342
323
|
self.emitted_comment_indices.insert(idx);
|
|
343
324
|
last_end_line = comment_end_line;
|
|
344
325
|
}
|
|
@@ -352,15 +333,12 @@ impl Emitter {
|
|
|
352
333
|
// Use indexed lookup for O(log n) access
|
|
353
334
|
let indices = self.get_comment_indices_on_line(line);
|
|
354
335
|
|
|
355
|
-
//
|
|
356
|
-
let indices_to_emit: Vec<
|
|
357
|
-
.into_iter()
|
|
358
|
-
.map(|idx| (idx, self.all_comments[idx].text.clone()))
|
|
359
|
-
.collect();
|
|
336
|
+
// Collect indices only (no text clone needed)
|
|
337
|
+
let indices_to_emit: Vec<usize> = indices;
|
|
360
338
|
|
|
361
|
-
// Now emit the collected comments
|
|
362
|
-
for
|
|
363
|
-
write!(self.buffer, " {}", text)?;
|
|
339
|
+
// Now emit the collected comments by accessing text at write time
|
|
340
|
+
for idx in indices_to_emit {
|
|
341
|
+
write!(self.buffer, " {}", &self.all_comments[idx].text)?;
|
|
364
342
|
self.emitted_comment_indices.insert(idx);
|
|
365
343
|
}
|
|
366
344
|
|
|
@@ -1311,9 +1289,9 @@ impl Emitter {
|
|
|
1311
1289
|
Ok(())
|
|
1312
1290
|
}
|
|
1313
1291
|
|
|
1314
|
-
///
|
|
1315
|
-
|
|
1316
|
-
|
|
1292
|
+
/// Ensure indent cache has entries up to and including the given level
|
|
1293
|
+
/// This allows pre-building the cache before borrowing self.indent_cache
|
|
1294
|
+
fn ensure_indent_cache(&mut self, level: usize) {
|
|
1317
1295
|
while self.indent_cache.len() <= level {
|
|
1318
1296
|
let len = self.indent_cache.len();
|
|
1319
1297
|
let indent = match self.config.formatting.indent_style {
|
|
@@ -1322,13 +1300,12 @@ impl Emitter {
|
|
|
1322
1300
|
};
|
|
1323
1301
|
self.indent_cache.push(indent);
|
|
1324
1302
|
}
|
|
1325
|
-
&self.indent_cache[level]
|
|
1326
1303
|
}
|
|
1327
1304
|
|
|
1328
1305
|
/// Emit indentation
|
|
1329
1306
|
fn emit_indent(&mut self, level: usize) -> Result<()> {
|
|
1330
|
-
|
|
1331
|
-
write!(self.buffer, "{}",
|
|
1307
|
+
self.ensure_indent_cache(level);
|
|
1308
|
+
write!(self.buffer, "{}", &self.indent_cache[level])?;
|
|
1332
1309
|
Ok(())
|
|
1333
1310
|
}
|
|
1334
1311
|
|
data/ext/rfmt/src/lib.rs
CHANGED
|
@@ -14,25 +14,16 @@ use magnus::{define_module, function, prelude::*, Error, Ruby};
|
|
|
14
14
|
use parser::{PrismAdapter, RubyParser};
|
|
15
15
|
|
|
16
16
|
fn format_ruby_code(ruby: &Ruby, source: String, json: String) -> Result<String, Error> {
|
|
17
|
-
log::info!("format_ruby_code called");
|
|
18
17
|
let policy = SecurityPolicy::default();
|
|
19
18
|
|
|
20
19
|
policy
|
|
21
20
|
.validate_source_size(&source)
|
|
22
21
|
.map_err(|e| e.to_magnus_error(ruby))?;
|
|
23
22
|
|
|
24
|
-
log::debug!("Source code validated, size: {} bytes", source.len());
|
|
25
|
-
|
|
26
23
|
let parser = PrismAdapter::new();
|
|
27
24
|
let ast = parser.parse(&json).map_err(|e| e.to_magnus_error(ruby))?;
|
|
28
25
|
|
|
29
|
-
// Load configuration from file or use defaults
|
|
30
|
-
log::info!("Attempting to discover config file...");
|
|
31
26
|
let config = Config::discover().map_err(|e| e.to_magnus_error(ruby))?;
|
|
32
|
-
log::info!(
|
|
33
|
-
"Config loaded successfully, line_length: {}",
|
|
34
|
-
config.formatting.line_length
|
|
35
|
-
);
|
|
36
27
|
let mut emitter = Emitter::with_source(config, source);
|
|
37
28
|
|
|
38
29
|
let formatted = emitter.emit(&ast).map_err(|e| e.to_magnus_error(ruby))?;
|
|
@@ -56,7 +47,6 @@ fn rust_version() -> String {
|
|
|
56
47
|
#[magnus::init]
|
|
57
48
|
fn init(_ruby: &Ruby) -> Result<(), Error> {
|
|
58
49
|
logging::RfmtLogger::init();
|
|
59
|
-
log::info!("Initializing rfmt Rust extension");
|
|
60
50
|
|
|
61
51
|
let module = define_module("Rfmt")?;
|
|
62
52
|
|
|
@@ -64,6 +54,5 @@ fn init(_ruby: &Ruby) -> Result<(), Error> {
|
|
|
64
54
|
module.define_singleton_method("parse_to_json", function!(parse_to_json, 1))?;
|
|
65
55
|
module.define_singleton_method("rust_version", function!(rust_version, 0))?;
|
|
66
56
|
|
|
67
|
-
log::info!("rfmt Rust extension initialized successfully");
|
|
68
57
|
Ok(())
|
|
69
58
|
}
|
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,
|
|
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 =
|
|
72
|
-
|
|
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)', :
|
|
80
|
+
say '✓ All files are already formatted (cached)', :cyan
|
|
86
81
|
return
|
|
87
82
|
end
|
|
88
83
|
|
|
89
|
-
# Show progress message
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
process_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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
234
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
340
|
+
display_normal_summary(stats[:changed], unchanged_count, total_files)
|
|
248
341
|
end
|
|
249
342
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rfmt
|
|
4
|
+
# Handles loading of native extension across different Ruby versions
|
|
5
|
+
# Ruby 3.3+ places native extensions in version-specific subdirectories
|
|
6
|
+
module NativeExtensionLoader
|
|
7
|
+
class << self
|
|
8
|
+
# Load the native extension, trying multiple possible paths
|
|
9
|
+
# @return [Boolean] true if successfully loaded
|
|
10
|
+
# @raise [LoadError] if the extension cannot be found
|
|
11
|
+
def load_extension
|
|
12
|
+
debug_log "Loading native extension for Ruby #{RUBY_VERSION}"
|
|
13
|
+
|
|
14
|
+
possible_paths = build_possible_paths
|
|
15
|
+
debug_log "Trying paths: #{possible_paths.inspect}"
|
|
16
|
+
|
|
17
|
+
load_from_paths(possible_paths) || raise(LoadError, build_error_message(possible_paths))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# Try loading from multiple paths
|
|
23
|
+
# @param paths [Array<String>] paths to try
|
|
24
|
+
# @return [Boolean, nil] true if loaded, nil otherwise
|
|
25
|
+
def load_from_paths(paths)
|
|
26
|
+
paths.each do |path|
|
|
27
|
+
if try_load_extension(path)
|
|
28
|
+
debug_log "Successfully loaded from: #{path}"
|
|
29
|
+
return true
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Build list of possible paths for the native extension
|
|
36
|
+
# @return [Array<String>] paths to try, in order of preference
|
|
37
|
+
def build_possible_paths
|
|
38
|
+
paths = []
|
|
39
|
+
|
|
40
|
+
# Ruby 3.3+ style: version-specific subdirectory
|
|
41
|
+
paths << version_specific_path if ruby_version >= '3.3'
|
|
42
|
+
|
|
43
|
+
# Ruby 3.0-3.2 style: might use version directory
|
|
44
|
+
paths << version_specific_path if ruby_version >= '3.0' && ruby_version < '3.3'
|
|
45
|
+
|
|
46
|
+
# Legacy/fallback: direct placement
|
|
47
|
+
paths << File.join(__dir__, 'rfmt')
|
|
48
|
+
|
|
49
|
+
# Additional fallback: check for .bundle extension explicitly
|
|
50
|
+
paths << File.join(__dir__, 'rfmt.bundle')
|
|
51
|
+
|
|
52
|
+
paths.uniq
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Get version-specific path
|
|
56
|
+
# @return [String] path with version directory
|
|
57
|
+
def version_specific_path
|
|
58
|
+
File.join(__dir__, ruby_version_dir, 'rfmt')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Try to load extension from a specific path
|
|
62
|
+
# @param path [String] path to try
|
|
63
|
+
# @return [Boolean] true if successful, false otherwise
|
|
64
|
+
def try_load_extension(path)
|
|
65
|
+
require path
|
|
66
|
+
true
|
|
67
|
+
rescue LoadError => e
|
|
68
|
+
debug_log "Failed to load from #{path}: #{e.message}"
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Get Ruby version for comparison
|
|
73
|
+
# @return [String] Ruby version string
|
|
74
|
+
def ruby_version
|
|
75
|
+
RUBY_VERSION
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Get Ruby version directory name (e.g., "3.3" for Ruby 3.3.0)
|
|
79
|
+
# @return [String] version directory name
|
|
80
|
+
def ruby_version_dir
|
|
81
|
+
RUBY_VERSION.split('.')[0..1].join('.')
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Build detailed error message when extension cannot be loaded
|
|
85
|
+
# @param tried_paths [Array<String>] paths that were tried
|
|
86
|
+
# @return [String] error message
|
|
87
|
+
def build_error_message(tried_paths)
|
|
88
|
+
[
|
|
89
|
+
error_header,
|
|
90
|
+
format_tried_paths(tried_paths),
|
|
91
|
+
error_explanation,
|
|
92
|
+
workaround_instructions
|
|
93
|
+
].join("\n")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Error message header
|
|
97
|
+
# @return [String] header text
|
|
98
|
+
def error_header
|
|
99
|
+
"Unable to load rfmt native extension for Ruby #{RUBY_VERSION}.\n"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Format list of tried paths
|
|
103
|
+
# @param paths [Array<String>] paths that were tried
|
|
104
|
+
# @return [String] formatted path list
|
|
105
|
+
def format_tried_paths(paths)
|
|
106
|
+
"Tried the following paths:\n#{paths.map { |p| " - #{p}" }.join("\n")}\n"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Error explanation text
|
|
110
|
+
# @return [String] explanation
|
|
111
|
+
def error_explanation
|
|
112
|
+
"This might be a packaging issue with the gem for your Ruby version.\n"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Workaround instructions
|
|
116
|
+
# @return [String] instructions
|
|
117
|
+
def workaround_instructions
|
|
118
|
+
<<~MSG.chomp
|
|
119
|
+
Workaround:
|
|
120
|
+
1. Check if rfmt.bundle exists in: #{__dir__}/
|
|
121
|
+
2. If it's in a subdirectory, create a symlink:
|
|
122
|
+
cd #{__dir__}
|
|
123
|
+
ln -sf <subdirectory>/rfmt.bundle rfmt.bundle
|
|
124
|
+
|
|
125
|
+
Please report this issue at: https://github.com/fs0414/rfmt/issues
|
|
126
|
+
MSG
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Log debug information if RFMT_DEBUG is set
|
|
130
|
+
# @param message [String] message to log
|
|
131
|
+
def debug_log(message)
|
|
132
|
+
return unless ENV['RFMT_DEBUG']
|
|
133
|
+
|
|
134
|
+
warn "[RFMT::NativeExtensionLoader] #{message}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
data/lib/rfmt/version.rb
CHANGED
data/lib/rfmt.rb
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'rfmt/version'
|
|
4
|
-
require_relative 'rfmt/
|
|
4
|
+
require_relative 'rfmt/native_extension_loader'
|
|
5
5
|
require_relative 'rfmt/prism_bridge'
|
|
6
6
|
|
|
7
|
+
# Load native extension with version-aware loader
|
|
8
|
+
Rfmt::NativeExtensionLoader.load_extension
|
|
9
|
+
|
|
7
10
|
module Rfmt
|
|
8
11
|
class Error < StandardError; end
|
|
9
12
|
# Errors from Rust side
|
|
@@ -99,14 +102,9 @@ module Rfmt
|
|
|
99
102
|
# @param force [Boolean] Overwrite existing file if true
|
|
100
103
|
# @return [Boolean] true if file was created, false if already exists
|
|
101
104
|
def self.init(path = '.rfmt.yml', force: false)
|
|
102
|
-
if File.exist?(path) && !force
|
|
103
|
-
warn "Configuration file already exists: #{path}"
|
|
104
|
-
warn 'Use force: true to overwrite'
|
|
105
|
-
return false
|
|
106
|
-
end
|
|
105
|
+
return false if File.exist?(path) && !force
|
|
107
106
|
|
|
108
107
|
File.write(path, DEFAULT_CONFIG)
|
|
109
|
-
puts "Created rfmt configuration file: #{path}"
|
|
110
108
|
true
|
|
111
109
|
end
|
|
112
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.
|
|
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-
|
|
11
|
+
date: 2026-01-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|
|
@@ -56,6 +56,7 @@ files:
|
|
|
56
56
|
- lib/rfmt/cache.rb
|
|
57
57
|
- lib/rfmt/cli.rb
|
|
58
58
|
- lib/rfmt/configuration.rb
|
|
59
|
+
- lib/rfmt/native_extension_loader.rb
|
|
59
60
|
- lib/rfmt/prism_bridge.rb
|
|
60
61
|
- lib/rfmt/prism_node_extractor.rb
|
|
61
62
|
- lib/rfmt/version.rb
|