@@ -6,307 +6,12 @@ if VERSION >= v"1.11.0-DEV.469"
66 end
77end
88
9- using JuliaFormatter: JuliaFormatter
10- using JuliaSyntax: JuliaSyntax, @K_str , SyntaxNode, children, kind, parseall, span
11- using Runic: Runic
12-
13- # JuliaFormatter options chosen to be compatible with Runic.
14- # JuliaFormatter handles line wrapping (which Runic doesn't do),
15- # then Runic runs last to canonicalize everything else.
16- const JULIAFORMATTER_OPTIONS = (
17- style = JuliaFormatter. DefaultStyle (),
18- indent = 4 ,
19- margin = 92 ,
20- always_for_in = true ,
21- for_in_replacement = " in" ,
22- # Semantic transformations consistent with Runic
23- always_use_return = true ,
24- import_to_using = true ,
25- pipe_to_function_call = true ,
26- short_to_long_function_def = true ,
27- long_to_short_function_def = false ,
28- conditional_to_if = true ,
29- short_circuit_to_if = false ,
30- # Whitespace options consistent with Runic
31- whitespace_typedefs = true ,
32- whitespace_ops_in_indices = true ,
33- whitespace_in_kwargs = true ,
34- # Annotation/structural changes
35- annotate_untyped_fields_with_any = true ,
36- format_docstrings = true ,
37- remove_extra_newlines = true ,
38- indent_submodule = true ,
39- separate_kwargs_with_semicolon = true ,
40- surround_whereop_typeparameters = true ,
41- disallow_single_arg_nesting = false ,
42- normalize_line_endings = " unix" ,
43- # Line-wrapping-related options
44- trailing_comma = false ,
45- join_lines_based_on_source = true ,
46- # Floating point formatting options
47- trailing_zero = true ,
48- )
49-
50- is_using_or_import (x:: SyntaxNode ) = kind (x) === K " using" || kind (x) === K " import"
51-
52- function find_using_or_import (x:: SyntaxNode )
53- if is_using_or_import (x)
54- return x. parent
55- elseif iszero (length (children (x)))
56- return nothing
57- else
58- for child in children (x)
59- result = find_using_or_import (child)
60- isnothing (result) || return result
61- end
62- return nothing
63- end
64- end
65-
66- # JuliaSyntax nodes report (position, span) in *bytes*.
67- # Julia strings must be sliced using *valid string indices* (start bytes of UTF-8 chars).
68- #
69- # Convert a node's byte range into a safe `UnitRange` of valid string indices.
70- # (O(1): fix endpoints with `thisind`.)
71- function node_char_range (n:: SyntaxNode , src:: AbstractString )
72- startb = n. position
73- nspan = span (n)
74- nspan <= 0 && return startb: (startb - 1 ) # empty range
75- stopb = min (startb + nspan - 1 , ncodeunits (src))
76- return thisind (src, startb): thisind (src, stopb)
77- end
78-
79- function organize_import_blocks_string (s:: AbstractString )
80- jst = parseall (SyntaxNode, String (s))
81- return organize_import_blocks (jst)
82- end
83-
84- function organize_import_blocks_file (f:: AbstractString )
85- return organize_import_blocks_string (read (f, String))
86- end
87-
88- # Sort symbols, but keep the module self-reference (bare and all aliases) first if present
89- function sort_with_self_first (syms:: Vector{String} , self:: String )
90- syms = unique (syms)
91- selfs = self in syms ? [self] : String[]
92- self_aliases = sort! (filter (s -> startswith (s, self * " as " ), syms))
93- rest = sort! (setdiff (syms, [selfs; self_aliases]))
94- return [selfs; self_aliases; rest]
95- end
96-
97- # Organize a single block of adjacent import/using statements
98- function organize_import_block (siblings:: AbstractVector{<:SyntaxNode} , node_text)
99- using_mods = Set {String} ()
100- using_syms = Dict {String, Vector{String}} ()
101- import_mods = Set {String} ()
102- import_syms = Dict {String, Vector{String}} ()
103-
104- for s in siblings
105- isusing = kind (s) === K " using"
106- for a in children (s)
107- if kind (a) === K " :"
108- a_args = children (a)
109- mod = String (node_text (a_args[1 ]))
110- set = get! (() -> String[], isusing ? using_syms : import_syms, mod)
111- for i in 2 : length (a_args)
112- push! (set, String (node_text (a_args[i])))
113- end
114- elseif kind (a) === K " ." || kind (a) === K " importpath"
115- push! (isusing ? using_mods : import_mods, String (node_text (a)))
116- elseif ! isusing && kind (a) === K " as"
117- a_args = children (a)
118- push! (
119- import_mods,
120- String (node_text (a_args[1 ])) * " as " * String (node_text (a_args[end ]))
121- )
122- else
123- error (" Unexpected syntax in using/import statement." )
124- end
125- end
126- end
127-
128- import_lines = String[]
129- for m in import_mods
130- push! (import_lines, " import " * m)
131- end
132- for (m, s) in import_syms
133- push! (import_lines, " import " * m * " : " * join (sort_with_self_first (s, m), " , " ))
134- end
135- using_lines = String[]
136- for m in using_mods
137- push! (using_lines, " using " * m)
138- end
139- for (m, s) in using_syms
140- push! (using_lines, " using " * m * " : " * join (sort_with_self_first (s, m), " , " ))
141- end
142- io = IOBuffer ()
143- if ! isempty (import_lines)
144- print (io, join (sort! (import_lines), " \n " ))
145- end
146- if ! isempty (import_lines) && ! isempty (using_lines)
147- print (io, " \n " )
148- end
149- if ! isempty (using_lines)
150- print (io, join (sort! (using_lines), " \n " ))
151- end
152- return String (take! (io))
153- end
154-
155- function organize_import_blocks (input:: SyntaxNode )
156- # Keep a stable copy for slicing: node positions/spans refer to this text.
157- src0 = JuliaSyntax. sourcetext (input)
158- x = find_using_or_import (input)
159- isnothing (x) && return src0
160- child_nodes = children (x)
161- # Find all groups of adjacent import/using statements
162- groups = Vector{SyntaxNode}[]
163- i = 1
164- while i <= length (child_nodes)
165- if is_using_or_import (child_nodes[i])
166- group_start = i
167- while i <= length (child_nodes) && is_using_or_import (child_nodes[i])
168- i += 1
169- end
170- push! (groups, child_nodes[group_start: (i - 1 )])
171- else
172- i += 1
173- end
174- end
175- # Extract the source text of a node, trimming whitespace (Unicode-safe).
176- # Always slice from src0 (stable offsets), not the rewritten `src`.
177- node_text (n:: SyntaxNode ) = strip (src0[node_char_range (n, src0)])
178- # Rewritten output source.
179- src = src0
180- # Process each group from right to left to preserve positions
181- for siblings in reverse (groups)
182- formatted = organize_import_block (siblings, node_text)
183- # Compute splice bounds using src0 (node offsets), then splice into src.
184- first_pos = first (node_char_range (first (siblings), src0))
185- last_pos = last (node_char_range (last (siblings), src0))
186- # Unicode-safe splice boundaries (never do ±1 on raw integer indices)
187- before =
188- first_pos == firstindex (src) ? " " :
189- src[firstindex (src): prevind (src, first_pos)]
190- after_start = nextind (src, last_pos)
191- after = after_start > lastindex (src) ? " " : src[after_start: end ]
192- src = before * chomp (formatted) * after
193- end
194- return src
195- end
196-
197- const ITENSORFORMATTER_VERSION = pkgversion (@__MODULE__ )
198-
199- # Print a typical cli program help message
200- function print_help ()
201- io = stdout
202- printstyled (io, " NAME" ; bold = true )
203- println (io)
204- println (io, " ITensorFormatter.main - format Julia source code" )
205- println (io)
206- printstyled (io, " SYNOPSIS" ; bold = true )
207- println (io)
208- println (io, " julia -m ITensorFormatter [<options>] <path>..." )
209- println (io)
210- printstyled (io, " DESCRIPTION" ; bold = true )
211- println (io)
212- println (
213- io, """
214- `ITensorFormatter.main` (typically invoked as `julia -m ITensorFormatter`)
215- formats Julia source code using the ITensorFormatter.jl formatter.
216- """
217- )
218- printstyled (io, " OPTIONS" ; bold = true )
219- println (io)
220- println (
221- io, """
222- <path>...
223- Input path(s) (files and/or directories) to process. For directories,
224- all files (recursively) with the '*.jl' suffix are used as input files.
225-
226- --help
227- Print this message.
228-
229- --version
230- Print ITensorFormatter and julia version information.
231- """
232- )
233- return
234- end
235-
236- function print_version ()
237- print (stdout , " itfmt version " )
238- print (stdout , ITENSORFORMATTER_VERSION)
239- print (stdout , " , julia version " )
240- print (stdout , VERSION )
241- println (stdout )
242- return
243- end
244-
245- """
246- ITensorFormatter.main(argv)
247-
248- Format Julia source files. Primarily formats using Runic formatting, but additionally
249- organizes using/import statements by merging adjacent blocks, sorting modules and symbols,
250- and line-wrapping. Accepts file paths and directories as arguments.
251-
252- # Examples
253-
254- ```julia-repl
255- julia> using ITensorFormatter: ITensorFormatter
256-
257- julia> ITensorFormatter.main(["."]);
258-
259- julia> ITensorFormatter.main(["file1.jl", "file2.jl"]);
260-
261- ```
262- """
263- function main (argv)
264- argv_options = filter (startswith (" --" ), argv)
265- if ! isempty (argv_options)
266- if " --help" in argv_options
267- print_help ()
268- return 0
269- elseif " --version" in argv_options
270- print_version ()
271- return 0
272- else
273- return error (" Options not supported: `$argv_options `." )
274- end
275- end
276- # `argv` doesn't have any options, so treat all arguments as file/directory paths.
277- isempty (argv) && return error (" No input paths provided." )
278- inputfiles = String[]
279- for x in argv
280- if isdir (x)
281- Runic. scandir! (inputfiles, x)
282- elseif isfile (x)
283- push! (inputfiles, x) # Assume it is a file for now
284- else
285- error (" Input path is not a file or directory: `$x `." )
286- end
287- end
288- isempty (inputfiles) && return 0
289- # Pass 1: Organize import/using blocks
290- for inputfile in inputfiles
291- content = organize_import_blocks_file (inputfile)
292- write (inputfile, content)
293- end
294- # Pass 2: Formatting via JuliaFormatter
295- JuliaFormatter. format (inputfiles; JULIAFORMATTER_OPTIONS... )
296- # Pass 3: Re-organize imports (fix up any changes from JuliaFormatter, e.g. import_to_using)
297- for inputfile in inputfiles
298- content = organize_import_blocks_file (inputfile)
299- write (inputfile, content)
300- end
301- # Pass 4: Format via JuliaFormatter again to fix import line wrapping
302- JuliaFormatter. format (inputfiles; JULIAFORMATTER_OPTIONS... )
303- # Pass 5: Canonicalize via Runic
304- Runic. main ([" --inplace" ; inputfiles])
305- return 0
306- end
307-
308- @static if isdefined (Base, Symbol (" @main" ))
309- @main
310- end
9+ include (" utils.jl" )
10+ include (" format_imports.jl" )
11+ include (" format_yaml.jl" )
12+ include (" format_project_toml.jl" )
13+ include (" main.jl" )
14+ include (" generate_readme.jl" )
15+ include (" ITensorPkgFormatter.jl" )
31116
31217end
0 commit comments