@@ -21,50 +21,38 @@ function find_using_or_import(x)
2121 return nothing
2222 else
2323 for child in children (x)
24- x = find_using_or_import (child)
25- isnothing (x ) || return x
24+ result = find_using_or_import (child)
25+ isnothing (result ) || return result
2626 end
2727 return nothing
2828 end
2929end
3030
3131char_range (x) = x. position: (x. position + span (x) - 1 )
3232
33- function organize_import_file (f )
34- jst = parseall (SyntaxNode, read (f, String) )
35- return organize_import_block (jst)
33+ function organize_import_blocks_string (s )
34+ jst = parseall (SyntaxNode, s )
35+ return organize_import_blocks (jst)
3636end
37-
38- function organize_import_block (input)
39- # Collect all sibling blocks that are also using/import expressions
40-
41- x = find_using_or_import (input)
42- isnothing (x) && return JuliaSyntax. sourcetext (input)
43-
44- siblings = []
45-
46- child_nodes = children (x)
47- first_ind = findfirst (is_using_or_import, child_nodes)
48-
49- for ind in first_ind: length (child_nodes)
50- if is_using_or_import (child_nodes[ind])
51- push! (siblings, child_nodes[ind])
52- else
53- break
54- end
37+ organize_import_blocks_file (f) = organize_import_blocks_string (read (f, String))
38+
39+ # Sort symbols, but keep the module self-reference first if present
40+ function sort_with_self_first (syms, self)
41+ self′ = pop! (syms, self, nothing )
42+ sorted = sort! (collect (syms))
43+ if self′ != = nothing
44+ pushfirst! (sorted, self)
5545 end
46+ return sorted
47+ end
5648
57- src = JuliaSyntax. sourcetext (input)
58-
59- # Collect all modules and symbols
49+ # Organize a single block of adjacent import/using statements
50+ function organize_import_block (siblings, node_text)
6051 using_mods = Set {String} ()
6152 using_syms = Dict {String, Set{String}} ()
6253 import_mods = Set {String} ()
6354 import_syms = Dict {String, Set{String}} ()
6455
65- # Extract the source text of a node, trimming whitespace
66- node_text (x) = strip (src[char_range (x)])
67-
6856 for s in siblings
6957 isusing = kind (s) === K " using"
7058 for a in children (s)
@@ -86,21 +74,6 @@ function organize_import_block(input)
8674 end
8775 end
8876
89- # Rejoin and sort
90- # TODO : Currently regular string sorting is used, which roughly will correspond to
91- # BlueStyle (modules, types, ..., functions) since usually CamelCase is used for
92- # modules, types, etc, but possibly this can be improved by using information
93- # available from SymbolServer
94- # Sort symbols, but keep the module self-reference first if present
95- function sort_with_self_first (syms, self)
96- self′ = pop! (syms, self, nothing )
97- sorted = sort! (collect (syms))
98- if self′ != = nothing
99- pushfirst! (sorted, self)
100- end
101- return sorted
102- end
103-
10477 import_lines = String[]
10578 for m in import_mods
10679 push! (import_lines, " import " * m)
@@ -121,43 +94,142 @@ function organize_import_block(input)
12194 join (io, sort! (using_lines), " \n " )
12295 str_to_fmt = String (take! (io))
12396
124- # Line wrap the using/import statements only
125- formatted = JuliaFormatter . format_text (str_to_fmt; join_lines_based_on_source = true )
97+ return JuliaFormatter . format_text (str_to_fmt; join_lines_based_on_source = true )
98+ end
12699
127- first_pos = first (char_range (siblings[1 ]))
128- last_pos = last (char_range (siblings[end ]))
100+ function organize_import_blocks (input)
101+ src = JuliaSyntax. sourcetext (input)
102+ x = find_using_or_import (input)
103+ isnothing (x) && return src
129104
130- content = src[ 1 : (first_pos - 1 )] * chomp (formatted) * src[(last_pos + 1 ) : end ]
105+ child_nodes = children (x)
131106
132- return content
107+ # Find all groups of adjacent import/using statements
108+ groups = Vector{Any}[]
109+ i = 1
110+ while i <= length (child_nodes)
111+ if is_using_or_import (child_nodes[i])
112+ group_start = i
113+ while i <= length (child_nodes) && is_using_or_import (child_nodes[i])
114+ i += 1
115+ end
116+ push! (groups, child_nodes[group_start: (i - 1 )])
117+ else
118+ i += 1
119+ end
120+ end
121+
122+ # Extract the source text of a node, trimming whitespace
123+ node_text (n) = strip (src[char_range (n)])
124+
125+ # Process each group from right to left to preserve positions
126+ for siblings in reverse (groups)
127+ formatted = organize_import_block (siblings, node_text)
128+ first_pos = first (char_range (siblings[1 ]))
129+ last_pos = last (char_range (siblings[end ]))
130+ src = src[1 : (first_pos - 1 )] * chomp (formatted) * src[(last_pos + 1 ): end ]
131+ end
132+
133+ return src
134+ end
135+
136+ const ITENSORFORMATTER_VERSION = pkgversion (@__MODULE__ )
137+
138+ # Print a typical cli program help message
139+ function print_help ()
140+ io = stdout
141+ printstyled (io, " NAME" , bold = true )
142+ println (io)
143+ println (io, " ITensorFormatter.main - format Julia source code" )
144+ println (io)
145+ printstyled (io, " SYNOPSIS" , bold = true )
146+ println (io)
147+ println (io, " julia -m ITensorFormatter [<options>] <path>..." )
148+ println (io)
149+ printstyled (io, " DESCRIPTION" , bold = true )
150+ println (io)
151+ println (
152+ io, """
153+ `ITensorFormatter.main` (typically invoked as `julia -m ITensorFormatter`)
154+ formats Julia source code using the ITensorFormatter.jl formatter.
155+ """
156+ )
157+ printstyled (io, " OPTIONS" , bold = true )
158+ println (io)
159+ println (
160+ io, """
161+ <path>...
162+ Input path(s) (files and/or directories) to process. For directories,
163+ all files (recursively) with the '*.jl' suffix are used as input files.
164+
165+ --help
166+ Print this message.
167+
168+ --version
169+ Print ITensorFormatter and julia version information.
170+ """
171+ )
172+ return
173+ end
174+
175+ function print_version ()
176+ print (stdout , " itfmt version " )
177+ print (stdout , ITENSORFORMATTER_VERSION)
178+ print (stdout , " , julia version " )
179+ print (stdout , VERSION )
180+ println (stdout )
181+ return
133182end
134183
135184"""
136185 ITensorFormatter.main(argv)
137186
138187Format Julia source files. Primarily formats using Runic formatting, but additionally
139188organizes using/import statements by merging adjacent blocks, sorting modules and symbols,
140- and line-wrapping. Accepts file paths and directories as arguments. Options starting with
141- `--` are forwarded to Runic, see the
142- [Runic documentation](https://github.com/fredrikekre/Runic.jl) for more details.
189+ and line-wrapping. Accepts file paths and directories as arguments.
190+
191+ # Examples
192+ ```julia-repl
193+ julia> using ITensorFormatter: ITensorFormatter
194+
195+ julia> ITensorFormatter.main(["."]);
196+
197+ julia> ITensorFormatter.main(["file1.jl", "file2.jl"]);
198+
199+ ```
143200"""
144201function main (argv)
202+ argv_options = filter (startswith (" --" ), argv)
203+ if ! isempty (argv_options)
204+ if " --help" in argv_options
205+ print_help ()
206+ return 0
207+ elseif " --version" in argv_options
208+ print_version ()
209+ return 0
210+ else
211+ return error (" Options not supported: `$argv_options `." )
212+ end
213+ end
214+ # `argv` doesn't have any options, so treat all arguments as file/directory paths.
215+ isempty (argv) && return error (" No input paths provided." )
145216 inputfiles = String[]
146- x = filter (! startswith (" --" ), argv)
147217 for x in argv
148- if startswith (x, " --" )
149- # Ignore options for now, they are assumed to be for Runic.
150- elseif isdir (x)
218+ if isdir (x)
151219 Runic. scandir! (inputfiles, x)
152- else # isfile(x)
220+ elseif isfile (x)
153221 push! (inputfiles, x) # Assume it is a file for now
222+ else
223+ error (" Input path is not a file or directory: `$x `." )
154224 end
155225 end
226+ isempty (inputfiles) && return 0
156227 for inputfile in inputfiles
157- content = organize_import_file (inputfile)
228+ content = organize_import_blocks_file (inputfile)
158229 write (inputfile, content)
159230 end
160- Runic. main (argv)
231+ pushfirst! (inputfiles, " --inplace" )
232+ Runic. main (inputfiles)
161233 return 0
162234end
163235
0 commit comments