isort 0.1.5 → 0.2.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 +4 -4
- data/CHANGELOG.md +44 -0
- data/README.md +261 -15
- data/lib/isort/version.rb +1 -1
- data/lib/isort.rb +249 -98
- data/sig/isort/file_sorter.rbs +8 -0
- metadata +14 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0b54367ec2e05c1827acb9d80bdc606634b858a98530ccf7c05d0e196269918
|
|
4
|
+
data.tar.gz: 4a7ab81c94061b440edd1f63cfc7569376278c4df39aab331bc84f70c2508ec5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ada7d48c391b8a3e168a8ec8e3bbfb8579312a605789499f1a89188b5f5bd8af45e308ac1d8663c6f4b0c8b743a0c6d1e9b3c18e6072abb94c887cef14b72d66
|
|
7
|
+
data.tar.gz: a1f3ad1c5d95b411dd3292a8c452357f6e81472ace0d686fcd30cfe792a83b7b1dc79d3ef01d6b1bfc0a337fb6edd0057e60560bc769029018c3b7f0e4d1edf9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
## [Released]
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026-01-03
|
|
4
|
+
|
|
5
|
+
Major rewrite with robust new architecture
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Complete architecture rewrite with modular components:
|
|
9
|
+
- `Parser` class for line classification
|
|
10
|
+
- `ImportStatement` class for import metadata
|
|
11
|
+
- `ImportBlock` class for contiguous import groups
|
|
12
|
+
- `FileProcessor` class for file orchestration
|
|
13
|
+
- Comprehensive duplicate import removal
|
|
14
|
+
- Proper handling of shebang and magic comments
|
|
15
|
+
- Support for all six import types: require, require_relative, include, extend, autoload, using
|
|
16
|
+
- Inline comment preservation
|
|
17
|
+
- Leading comment preservation (comments stay with their imports when sorted)
|
|
18
|
+
- Idempotent sorting (running multiple times produces same result)
|
|
19
|
+
- Proper encoding validation (UTF-8)
|
|
20
|
+
- Heredoc preservation
|
|
21
|
+
- Conditional import structure preservation
|
|
22
|
+
- Nested import sorting (inside classes/modules)
|
|
23
|
+
- Mixed quote style support
|
|
24
|
+
- Parenthesized require support
|
|
25
|
+
- 69 comprehensive unit tests covering edge cases
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- Imports are now grouped by type with blank lines between groups
|
|
29
|
+
- Multiple consecutive blank lines are normalized to single blank lines
|
|
30
|
+
- Better error messages for invalid files
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- Comment handling no longer causes unexpected reordering
|
|
34
|
+
- Encoding errors are now properly detected and reported
|
|
35
|
+
- Files with no imports are left untouched
|
|
36
|
+
- Non-import code no longer breaks import sorting
|
|
37
|
+
|
|
3
38
|
## [0.1.3] - 2024-12-28
|
|
4
39
|
|
|
5
40
|
- Initial release
|
|
@@ -17,3 +52,12 @@
|
|
|
17
52
|
|
|
18
53
|
### Added
|
|
19
54
|
- Import sorting functionality support for a whole directory
|
|
55
|
+
|
|
56
|
+
## [0.1.5] - 2024-12-29
|
|
57
|
+
|
|
58
|
+
- Third release
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
- Preserve comments associated with imports
|
|
62
|
+
- Add new line between different groups sorted
|
|
63
|
+
- Don't do anything if no imports are found in a file
|
data/README.md
CHANGED
|
@@ -1,44 +1,290 @@
|
|
|
1
|
-
#
|
|
1
|
+
# isort
|
|
2
2
|
|
|
3
|
-
A Ruby gem that automatically sorts and organizes
|
|
3
|
+
A Ruby gem that automatically sorts and organizes import statements in Ruby files. Inspired by Python's [isort](https://pycqa.github.io/isort/), it brings the same powerful import organization to Ruby projects.
|
|
4
|
+
|
|
5
|
+
[](https://rubygems.org/gems/isort)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Section-based grouping**: Automatically groups imports into stdlib, third-party, first-party, and local sections
|
|
11
|
+
- **Alphabetical sorting**: Sorts imports alphabetically within each section
|
|
12
|
+
- **Comment preservation**: Keeps inline and leading comments attached to their imports
|
|
13
|
+
- **Skip directives**: Skip individual imports or entire files from sorting
|
|
14
|
+
- **Safety modes**: Check, diff, and atomic modes to prevent unwanted changes
|
|
15
|
+
- **Duplicate removal**: Automatically removes duplicate imports
|
|
16
|
+
- **Idempotent**: Running multiple times produces the same result
|
|
17
|
+
- **Directory support**: Process single files or entire directories recursively
|
|
4
18
|
|
|
5
19
|
## Installation
|
|
6
20
|
|
|
21
|
+
Add to your Gemfile:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
gem 'isort'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or install directly:
|
|
28
|
+
|
|
7
29
|
```bash
|
|
8
30
|
gem install isort
|
|
9
31
|
```
|
|
10
32
|
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Sort imports in a file
|
|
37
|
+
isort path/to/file.rb
|
|
38
|
+
|
|
39
|
+
# Sort all Ruby files in a directory
|
|
40
|
+
isort path/to/directory
|
|
41
|
+
|
|
42
|
+
# Check if files need sorting (dry-run)
|
|
43
|
+
isort --check path/to/file.rb
|
|
44
|
+
|
|
45
|
+
# Show diff without modifying
|
|
46
|
+
isort --diff path/to/file.rb
|
|
47
|
+
```
|
|
48
|
+
|
|
11
49
|
## Usage
|
|
12
50
|
|
|
13
|
-
### Command Line
|
|
51
|
+
### Command Line Interface
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
isort [options] [file_or_directory]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Basic Options
|
|
58
|
+
|
|
59
|
+
| Option | Description |
|
|
60
|
+
|--------|-------------|
|
|
61
|
+
| `-f, --file=FILE` | Sort a specific file |
|
|
62
|
+
| `-d, --directory=DIR` | Sort all Ruby files in directory recursively |
|
|
63
|
+
|
|
64
|
+
#### Safety Options
|
|
65
|
+
|
|
66
|
+
| Option | Description |
|
|
67
|
+
|--------|-------------|
|
|
68
|
+
| `-c, --check` | Check if files need sorting without modifying. Returns exit code 1 if changes needed. |
|
|
69
|
+
| `--diff` | Show unified diff of changes without modifying files |
|
|
70
|
+
| `--atomic` | Validate Ruby syntax before and after sorting. Won't save if it would introduce syntax errors. |
|
|
71
|
+
|
|
72
|
+
#### Output Options
|
|
73
|
+
|
|
74
|
+
| Option | Description |
|
|
75
|
+
|--------|-------------|
|
|
76
|
+
| `-q, --quiet` | Suppress all output except errors |
|
|
77
|
+
| `--verbose` | Show detailed output |
|
|
78
|
+
| `-h, --help` | Show help message |
|
|
79
|
+
| `-v, --version` | Show version |
|
|
80
|
+
|
|
81
|
+
### Examples
|
|
14
82
|
|
|
15
83
|
```bash
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
84
|
+
# Sort a single file
|
|
85
|
+
isort app/models/user.rb
|
|
86
|
+
|
|
87
|
+
# Sort with verbose output
|
|
88
|
+
isort --verbose lib/
|
|
89
|
+
|
|
90
|
+
# Check if files are sorted (useful in CI)
|
|
91
|
+
isort --check app/
|
|
92
|
+
|
|
93
|
+
# Preview changes without applying
|
|
94
|
+
isort --diff app/models/user.rb
|
|
95
|
+
|
|
96
|
+
# Safe mode - validates syntax before saving
|
|
97
|
+
isort --atomic lib/complex_file.rb
|
|
19
98
|
```
|
|
20
99
|
|
|
21
|
-
###
|
|
100
|
+
### Ruby API
|
|
22
101
|
|
|
23
102
|
```ruby
|
|
24
103
|
require 'isort'
|
|
25
104
|
|
|
26
|
-
|
|
105
|
+
# Sort imports in a file
|
|
106
|
+
sorter = Isort::FileSorter.new('path/to/file.rb')
|
|
107
|
+
changed = sorter.sort_and_format_imports # Returns true if file was modified
|
|
108
|
+
|
|
109
|
+
# Check mode (dry-run)
|
|
110
|
+
sorter = Isort::FileSorter.new('path/to/file.rb', check: true)
|
|
111
|
+
needs_sorting = sorter.check # Returns true if file needs sorting
|
|
112
|
+
|
|
113
|
+
# Diff mode
|
|
114
|
+
sorter = Isort::FileSorter.new('path/to/file.rb', diff: true)
|
|
115
|
+
diff_output = sorter.diff # Returns diff string or nil
|
|
116
|
+
|
|
117
|
+
# Atomic mode (validates syntax)
|
|
118
|
+
sorter = Isort::FileSorter.new('path/to/file.rb', atomic: true)
|
|
27
119
|
sorter.sort_and_format_imports
|
|
28
120
|
```
|
|
29
121
|
|
|
30
|
-
##
|
|
122
|
+
## Import Sections
|
|
123
|
+
|
|
124
|
+
isort organizes imports into four sections, separated by blank lines:
|
|
125
|
+
|
|
126
|
+
| Section | Order | Import Types |
|
|
127
|
+
|---------|-------|--------------|
|
|
128
|
+
| **stdlib** | 1 | `require` statements for Ruby standard library (json, yaml, csv, etc.) |
|
|
129
|
+
| **thirdparty** | 2 | `require` statements for external gems |
|
|
130
|
+
| **firstparty** | 3 | `include`, `extend`, `autoload`, `using` statements |
|
|
131
|
+
| **localfolder** | 4 | `require_relative` statements |
|
|
132
|
+
|
|
133
|
+
### Before
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
require_relative 'helper'
|
|
137
|
+
require 'yaml'
|
|
138
|
+
extend ActiveSupport::Concern
|
|
139
|
+
require 'json'
|
|
140
|
+
include Enumerable
|
|
141
|
+
require_relative 'version'
|
|
142
|
+
require 'csv'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### After
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
require 'csv'
|
|
149
|
+
require 'json'
|
|
150
|
+
require 'yaml'
|
|
151
|
+
|
|
152
|
+
include Enumerable
|
|
153
|
+
|
|
154
|
+
extend ActiveSupport::Concern
|
|
155
|
+
|
|
156
|
+
require_relative 'helper'
|
|
157
|
+
require_relative 'version'
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Skip Directives
|
|
161
|
+
|
|
162
|
+
### Skip Individual Imports
|
|
163
|
+
|
|
164
|
+
Add `# isort:skip` to keep an import in its original position:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
require 'must_load_first' # isort:skip
|
|
168
|
+
require 'csv'
|
|
169
|
+
require 'json'
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The `must_load_first` import will remain at the top, while others are sorted below it.
|
|
173
|
+
|
|
174
|
+
### Skip Entire Files
|
|
175
|
+
|
|
176
|
+
Add `# isort:skip_file` anywhere in the first 50 lines:
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
# frozen_string_literal: true
|
|
180
|
+
# isort:skip_file
|
|
181
|
+
|
|
182
|
+
require 'special_loader'
|
|
183
|
+
require 'another_special'
|
|
184
|
+
# This file won't be modified by isort
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Supported Import Types
|
|
188
|
+
|
|
189
|
+
isort recognizes and sorts the following Ruby import statements:
|
|
190
|
+
|
|
191
|
+
| Statement | Example |
|
|
192
|
+
|-----------|---------|
|
|
193
|
+
| `require` | `require 'json'` |
|
|
194
|
+
| `require_relative` | `require_relative 'helper'` |
|
|
195
|
+
| `include` | `include Enumerable` |
|
|
196
|
+
| `extend` | `extend ActiveSupport::Concern` |
|
|
197
|
+
| `autoload` | `autoload :MyClass, 'my_class'` |
|
|
198
|
+
| `using` | `using MyRefinement` |
|
|
199
|
+
|
|
200
|
+
## CI/CD Integration
|
|
201
|
+
|
|
202
|
+
Use the `--check` flag in your CI pipeline to ensure imports are sorted:
|
|
203
|
+
|
|
204
|
+
```yaml
|
|
205
|
+
# GitHub Actions example
|
|
206
|
+
- name: Check import sorting
|
|
207
|
+
run: |
|
|
208
|
+
gem install isort
|
|
209
|
+
isort --check app/ lib/
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Exit codes
|
|
214
|
+
# 0 = All files are sorted correctly
|
|
215
|
+
# 1 = Files need sorting or errors occurred
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Safety Features
|
|
219
|
+
|
|
220
|
+
### Atomic Mode
|
|
221
|
+
|
|
222
|
+
The `--atomic` flag validates Ruby syntax before and after sorting:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
isort --atomic file.rb
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- Skips files that already have syntax errors
|
|
229
|
+
- Won't save changes if sorting would introduce syntax errors
|
|
230
|
+
- Provides clear error messages
|
|
231
|
+
|
|
232
|
+
### Preserves Code Structure
|
|
31
233
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- Preserves
|
|
35
|
-
-
|
|
36
|
-
-
|
|
234
|
+
isort only modifies import statements and:
|
|
235
|
+
|
|
236
|
+
- Preserves shebang lines (`#!/usr/bin/env ruby`)
|
|
237
|
+
- Preserves magic comments (`# frozen_string_literal: true`)
|
|
238
|
+
- Preserves inline comments on imports
|
|
239
|
+
- Preserves leading comments above imports
|
|
240
|
+
- Maintains proper indentation
|
|
241
|
+
- Handles encoding correctly
|
|
242
|
+
|
|
243
|
+
## Development
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# Clone the repository
|
|
247
|
+
git clone https://github.com/abhinvv1/isort.git
|
|
248
|
+
cd isort
|
|
249
|
+
|
|
250
|
+
# Install dependencies
|
|
251
|
+
bundle install
|
|
252
|
+
|
|
253
|
+
# Run tests
|
|
254
|
+
bundle exec rspec
|
|
255
|
+
|
|
256
|
+
# Test locally without installing
|
|
257
|
+
bundle exec ruby -Ilib -e "require 'isort'; Isort::CLI.start" -- path/to/file.rb
|
|
258
|
+
```
|
|
37
259
|
|
|
38
260
|
## Contributing
|
|
39
261
|
|
|
40
262
|
Bug reports and pull requests are welcome on GitHub at https://github.com/abhinvv1/isort.
|
|
41
263
|
|
|
264
|
+
1. Fork the repository
|
|
265
|
+
2. Create your feature branch (`git checkout -b feature/my-feature`)
|
|
266
|
+
3. Commit your changes (`git commit -am 'Add my feature'`)
|
|
267
|
+
4. Push to the branch (`git push origin feature/my-feature`)
|
|
268
|
+
5. Create a Pull Request
|
|
269
|
+
|
|
42
270
|
## License
|
|
43
271
|
|
|
44
|
-
The gem is available as open source under the terms of the MIT License.
|
|
272
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
273
|
+
|
|
274
|
+
## Changelog
|
|
275
|
+
|
|
276
|
+
### v0.2.0
|
|
277
|
+
|
|
278
|
+
- Complete reimplementation with robust architecture
|
|
279
|
+
- Section-based import grouping (stdlib, thirdparty, firstparty, localfolder)
|
|
280
|
+
- Added `--check` flag for dry-run mode
|
|
281
|
+
- Added `--diff` flag to preview changes
|
|
282
|
+
- Added `--atomic` flag for syntax validation
|
|
283
|
+
- Added skip directives (`# isort:skip` and `# isort:skip_file`)
|
|
284
|
+
- Automatic duplicate removal
|
|
285
|
+
- Improved comment handling
|
|
286
|
+
- Better detection of imports vs strings containing import keywords
|
|
287
|
+
|
|
288
|
+
### v0.1.x
|
|
289
|
+
|
|
290
|
+
- Initial release with basic import sorting
|
data/lib/isort/version.rb
CHANGED
data/lib/isort.rb
CHANGED
|
@@ -1,136 +1,287 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
2
5
|
require_relative "isort/version"
|
|
6
|
+
require_relative "isort/parser"
|
|
7
|
+
require_relative "isort/import_statement"
|
|
8
|
+
require_relative "isort/import_block"
|
|
9
|
+
require_relative "isort/file_processor"
|
|
10
|
+
require_relative "isort/syntax_validator"
|
|
11
|
+
require_relative "isort/wrap_modes"
|
|
3
12
|
|
|
4
13
|
module Isort
|
|
5
14
|
class Error < StandardError; end
|
|
6
15
|
|
|
7
|
-
|
|
16
|
+
# Raised when the input file already has syntax errors
|
|
17
|
+
class ExistingSyntaxErrors < Error
|
|
8
18
|
def initialize(file_path)
|
|
9
|
-
|
|
19
|
+
super("#{file_path} has existing syntax errors - skipping")
|
|
10
20
|
end
|
|
21
|
+
end
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
non_imports = lines.reject { |line| line =~ /^\s*(require|require_relative|include)\s/ }
|
|
23
|
+
# Raised when isort would introduce syntax errors
|
|
24
|
+
class IntroducedSyntaxErrors < Error
|
|
25
|
+
def initialize(file_path)
|
|
26
|
+
super("isort would introduce syntax errors in #{file_path} - not saving")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
19
29
|
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
# Raised when file contains skip directive
|
|
31
|
+
class FileSkipped < Error
|
|
32
|
+
def initialize(file_path, reason = "skip directive")
|
|
33
|
+
super("#{file_path} skipped due to #{reason}")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
# FileSorter provides the public API for sorting imports in a Ruby file
|
|
38
|
+
class FileSorter
|
|
39
|
+
attr_reader :file_path
|
|
25
40
|
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
def initialize(file_path, options = {})
|
|
42
|
+
@file_path = file_path
|
|
43
|
+
@options = {
|
|
44
|
+
check: false,
|
|
45
|
+
diff: false,
|
|
46
|
+
atomic: false,
|
|
47
|
+
quiet: false
|
|
48
|
+
}.merge(options)
|
|
49
|
+
@processor = FileProcessor.new(file_path, @options)
|
|
28
50
|
end
|
|
29
51
|
|
|
52
|
+
# Sort and format imports in the file
|
|
53
|
+
# Returns true if changes were made (or would be made in check mode)
|
|
30
54
|
def sort_and_format_imports
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
autoloads = extract_lines_with_comments(lines, /^autoload\s/)
|
|
40
|
-
usings = extract_lines_with_comments(lines, /^using\s/)
|
|
41
|
-
others = lines.reject { |line| [requires, require_relatives, includes, extends, autoloads, usings].flatten.include?(line) }
|
|
42
|
-
|
|
43
|
-
# Format and sort each group
|
|
44
|
-
formatted_imports = []
|
|
45
|
-
formatted_imports << format_group("require", requires)
|
|
46
|
-
formatted_imports << format_group("require_relative", require_relatives)
|
|
47
|
-
formatted_imports << format_group("include", includes)
|
|
48
|
-
formatted_imports << format_group("extend", extends)
|
|
49
|
-
formatted_imports << format_group("autoload", autoloads)
|
|
50
|
-
formatted_imports << format_group("using", usings)
|
|
51
|
-
|
|
52
|
-
return if [requires, require_relatives, includes, extends, autoloads, usings].all?(&:empty?)
|
|
53
|
-
|
|
54
|
-
# Combine formatted imports with the rest of the file
|
|
55
|
-
sorted_content = "#{formatted_imports.reject(&:empty?).join("\n")}\n#{others.join}".strip
|
|
56
|
-
|
|
57
|
-
# Add a trailing newline only if imports exist
|
|
58
|
-
sorted_content = "#{sorted_content.rstrip}\n" if !formatted_imports.empty? && !sorted_content.empty?
|
|
59
|
-
|
|
60
|
-
# Write the sorted content back to the file only if imports exist
|
|
61
|
-
File.write(@file_path, sorted_content) unless formatted_imports.empty? && sorted_content.empty?
|
|
55
|
+
@processor.process
|
|
56
|
+
rescue Errno::ENOENT
|
|
57
|
+
raise
|
|
58
|
+
rescue ExistingSyntaxErrors, IntroducedSyntaxErrors, FileSkipped
|
|
59
|
+
raise
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
puts "An error occurred: #{e.message}" unless @options[:quiet]
|
|
62
|
+
raise
|
|
62
63
|
end
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
lines.select { |line| line =~ regex }
|
|
65
|
+
# Check if file would change (dry-run)
|
|
66
|
+
def check
|
|
67
|
+
@processor.check
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
lines.each do |line|
|
|
75
|
-
if line.strip.start_with?("#") || line.strip.empty?
|
|
76
|
-
# If the line is a comment or blank, add it to the buffer
|
|
77
|
-
buffer << line
|
|
78
|
-
elsif line =~ regex
|
|
79
|
-
# If it's an import line matching the regex, attach the buffer as comments
|
|
80
|
-
grouped_lines << (buffer + [line])
|
|
81
|
-
buffer = [] # Reset buffer
|
|
82
|
-
else
|
|
83
|
-
# If it's a non-matching line, reset the buffer
|
|
84
|
-
buffer = []
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
grouped_lines
|
|
70
|
+
# Get diff of changes without applying
|
|
71
|
+
def diff
|
|
72
|
+
@processor.diff
|
|
89
73
|
end
|
|
90
74
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
grouped_lines
|
|
96
|
-
.sort_by { |lines| lines.last.strip } # Sort by the actual import statement
|
|
97
|
-
.map { |lines| lines.join } # Combine comments with the import
|
|
98
|
-
.map(&:strip) # Remove trailing newlines
|
|
99
|
-
.join("\n") # Join all imports in the group with newlines
|
|
100
|
-
.concat("\n") # Add a newline between groups
|
|
75
|
+
# Legacy method for backward compatibility
|
|
76
|
+
# @deprecated Use sort_and_format_imports instead
|
|
77
|
+
def sort_imports
|
|
78
|
+
sort_and_format_imports
|
|
101
79
|
end
|
|
102
|
-
|
|
103
80
|
end
|
|
104
81
|
|
|
82
|
+
# CLI handles command-line interface for the isort gem
|
|
105
83
|
class CLI
|
|
106
84
|
def self.start
|
|
107
|
-
options = {
|
|
108
|
-
|
|
109
|
-
|
|
85
|
+
options = {
|
|
86
|
+
check: false,
|
|
87
|
+
diff: false,
|
|
88
|
+
atomic: false,
|
|
89
|
+
quiet: false,
|
|
90
|
+
verbose: false
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
parser = OptionParser.new do |opts|
|
|
94
|
+
opts.banner = "Usage: isort [options] [file_or_directory]"
|
|
95
|
+
opts.separator ""
|
|
96
|
+
opts.separator "Options:"
|
|
110
97
|
|
|
111
|
-
opts.on("-
|
|
98
|
+
opts.on("-f", "--file=FILE", "File to sort") do |file|
|
|
112
99
|
options[:file] = file
|
|
113
100
|
end
|
|
114
|
-
|
|
101
|
+
|
|
102
|
+
opts.on("-d", "--directory=DIRECTORY", "Directory to sort (recursive)") do |dir|
|
|
115
103
|
options[:directory] = dir
|
|
116
104
|
end
|
|
117
|
-
|
|
105
|
+
|
|
106
|
+
opts.separator ""
|
|
107
|
+
opts.separator "Safety options:"
|
|
108
|
+
|
|
109
|
+
opts.on("-c", "--check", "--check-only",
|
|
110
|
+
"Check if files need sorting without modifying them.",
|
|
111
|
+
"Returns exit code 0 if sorted, 1 if changes needed.") do
|
|
112
|
+
options[:check] = true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
opts.on("--diff", "Show diff of changes without modifying files.") do
|
|
116
|
+
options[:diff] = true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
opts.on("--atomic", "Validate Ruby syntax before and after sorting.",
|
|
120
|
+
"Won't save if it would introduce syntax errors.") do
|
|
121
|
+
options[:atomic] = true
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
opts.separator ""
|
|
125
|
+
opts.separator "Output options:"
|
|
126
|
+
|
|
127
|
+
opts.on("-q", "--quiet", "Suppress all output except errors.") do
|
|
128
|
+
options[:quiet] = true
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
opts.on("--verbose", "Show detailed output.") do
|
|
132
|
+
options[:verbose] = true
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
opts.separator ""
|
|
136
|
+
opts.separator "Information:"
|
|
137
|
+
|
|
138
|
+
opts.on("-h", "--help", "Show this help message.") do
|
|
139
|
+
puts opts
|
|
140
|
+
exit
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
opts.on("-v", "--version", "Show version.") do
|
|
144
|
+
puts "isort #{Isort::VERSION}"
|
|
145
|
+
exit
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Parse arguments
|
|
150
|
+
parser.parse!
|
|
151
|
+
|
|
152
|
+
# Handle positional arguments
|
|
153
|
+
if ARGV.any? && !options[:file] && !options[:directory]
|
|
154
|
+
target = ARGV.first
|
|
155
|
+
if File.directory?(target)
|
|
156
|
+
options[:directory] = target
|
|
157
|
+
elsif File.file?(target)
|
|
158
|
+
options[:file] = target
|
|
159
|
+
else
|
|
160
|
+
puts "Error: #{target} is not a valid file or directory"
|
|
161
|
+
exit 1
|
|
162
|
+
end
|
|
163
|
+
end
|
|
118
164
|
|
|
119
165
|
if options[:file]
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
puts "Imports sorted in #{options[:file]}"
|
|
166
|
+
exit_code = process_file(options[:file], options)
|
|
167
|
+
exit exit_code
|
|
123
168
|
elsif options[:directory]
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
169
|
+
exit_code = process_directory(options[:directory], options)
|
|
170
|
+
exit exit_code
|
|
171
|
+
else
|
|
172
|
+
puts parser
|
|
173
|
+
exit 1
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def self.process_file(file, options)
|
|
178
|
+
unless File.exist?(file)
|
|
179
|
+
puts "Error: File not found: #{file}" unless options[:quiet]
|
|
180
|
+
return 1
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
processor = FileProcessor.new(file, options)
|
|
184
|
+
|
|
185
|
+
if options[:check]
|
|
186
|
+
return handle_check(file, processor, options)
|
|
187
|
+
elsif options[:diff]
|
|
188
|
+
return handle_diff(file, processor, options)
|
|
189
|
+
else
|
|
190
|
+
return handle_sort(file, processor, options)
|
|
191
|
+
end
|
|
192
|
+
rescue ExistingSyntaxErrors => e
|
|
193
|
+
puts "ERROR: #{e.message}" unless options[:quiet]
|
|
194
|
+
return 1
|
|
195
|
+
rescue IntroducedSyntaxErrors => e
|
|
196
|
+
puts "ERROR: #{e.message}" unless options[:quiet]
|
|
197
|
+
return 1
|
|
198
|
+
rescue FileSkipped => e
|
|
199
|
+
puts e.message if options[:verbose]
|
|
200
|
+
return 0
|
|
201
|
+
rescue StandardError => e
|
|
202
|
+
puts "Error processing #{file}: #{e.message}" unless options[:quiet]
|
|
203
|
+
return 1
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def self.handle_check(file, processor, options)
|
|
207
|
+
would_change = processor.check
|
|
208
|
+
if would_change
|
|
209
|
+
puts "#{file} - imports are not sorted" unless options[:quiet]
|
|
210
|
+
1
|
|
211
|
+
else
|
|
212
|
+
puts "#{file} - imports are sorted" if options[:verbose]
|
|
213
|
+
0
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def self.handle_diff(file, processor, options)
|
|
218
|
+
diff_output = processor.diff
|
|
219
|
+
if diff_output && !diff_output.empty?
|
|
220
|
+
puts diff_output
|
|
221
|
+
1
|
|
131
222
|
else
|
|
132
|
-
puts "
|
|
223
|
+
puts "#{file} - no changes" if options[:verbose]
|
|
224
|
+
0
|
|
133
225
|
end
|
|
134
226
|
end
|
|
227
|
+
|
|
228
|
+
def self.handle_sort(file, processor, options)
|
|
229
|
+
changed = processor.process
|
|
230
|
+
if changed
|
|
231
|
+
puts "Imports sorted in #{file}" unless options[:quiet]
|
|
232
|
+
else
|
|
233
|
+
puts "#{file} - no changes needed" if options[:verbose]
|
|
234
|
+
end
|
|
235
|
+
0
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def self.process_directory(dir, options)
|
|
239
|
+
unless Dir.exist?(dir)
|
|
240
|
+
puts "Error: Directory not found: #{dir}" unless options[:quiet]
|
|
241
|
+
return 1
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
files = Dir.glob("#{dir}/**/*.rb")
|
|
245
|
+
if files.empty?
|
|
246
|
+
puts "No Ruby files found in #{dir}" unless options[:quiet]
|
|
247
|
+
return 0
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
total = 0
|
|
251
|
+
changed = 0
|
|
252
|
+
errors = 0
|
|
253
|
+
error_messages = []
|
|
254
|
+
|
|
255
|
+
files.each do |file|
|
|
256
|
+
result = process_file(file, options.merge(quiet: true))
|
|
257
|
+
total += 1
|
|
258
|
+
if result == 0
|
|
259
|
+
changed += 1 if options[:check] || options[:diff]
|
|
260
|
+
else
|
|
261
|
+
errors += 1
|
|
262
|
+
end
|
|
263
|
+
rescue StandardError => e
|
|
264
|
+
errors += 1
|
|
265
|
+
error_messages << "#{file}: #{e.message}"
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
unless options[:quiet]
|
|
269
|
+
if options[:check]
|
|
270
|
+
unsorted = total - changed
|
|
271
|
+
puts "Checked #{total} files: #{changed} sorted, #{unsorted} need sorting"
|
|
272
|
+
elsif options[:diff]
|
|
273
|
+
puts "Checked #{total} files: #{total - changed} would change"
|
|
274
|
+
else
|
|
275
|
+
puts "Sorted imports in #{total} files in directory: #{dir}"
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
if error_messages.any?
|
|
279
|
+
puts "\nErrors encountered:"
|
|
280
|
+
error_messages.each { |err| puts " - #{err}" }
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
errors > 0 || (options[:check] && changed < total) ? 1 : 0
|
|
285
|
+
end
|
|
135
286
|
end
|
|
136
287
|
end
|
data/sig/isort/file_sorter.rbs
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: isort
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- abhinvv1
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: optparse
|
|
@@ -66,11 +66,13 @@ dependencies:
|
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '3.0'
|
|
69
|
-
description:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
description: 'isort automatically sorts and organizes import statements in Ruby files.
|
|
70
|
+
It groups imports into sections (stdlib, third-party, first-party, local), sorts
|
|
71
|
+
alphabetically within each section, removes duplicates, and preserves comments.
|
|
72
|
+
Supports require, require_relative, include, extend, autoload, and using statements.
|
|
73
|
+
Features include: check mode for CI integration, diff preview, atomic mode with
|
|
74
|
+
syntax validation, skip directives for fine-grained control, and recursive directory
|
|
75
|
+
processing. Inspired by Python''s isort.'
|
|
74
76
|
email:
|
|
75
77
|
- abhinav.p@browserstack.com
|
|
76
78
|
executables:
|
|
@@ -97,7 +99,7 @@ metadata:
|
|
|
97
99
|
homepage_uri: https://github.com/abhinvv1/isort
|
|
98
100
|
source_code_uri: https://github.com/abhinvv1/isort
|
|
99
101
|
changelog_uri: https://github.com/abhinvv1/isort
|
|
100
|
-
post_install_message:
|
|
102
|
+
post_install_message:
|
|
101
103
|
rdoc_options: []
|
|
102
104
|
require_paths:
|
|
103
105
|
- lib
|
|
@@ -112,8 +114,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
112
114
|
- !ruby/object:Gem::Version
|
|
113
115
|
version: '0'
|
|
114
116
|
requirements: []
|
|
115
|
-
rubygems_version: 3.
|
|
116
|
-
signing_key:
|
|
117
|
+
rubygems_version: 3.0.9
|
|
118
|
+
signing_key:
|
|
117
119
|
specification_version: 4
|
|
118
|
-
summary:
|
|
120
|
+
summary: Automatic import sorting for Ruby - sort require, include, extend and more
|
|
119
121
|
test_files: []
|