11module ITensorFormatter
22
3- using JuliaFormatter
4- using JuliaSyntax: @K_str , JuliaSyntax, SyntaxNode, children, kind, parseall, span
5- using Runic
3+ if VERSION >= v " 1.11.0-DEV.469"
4+ let str = " public main"
5+ eval (Meta. parse (str))
6+ end
7+ end
8+
9+ using JuliaFormatter: JuliaFormatter
10+ using JuliaSyntax: JuliaSyntax, @K_str , SyntaxNode, children, kind, parseall, span
11+ using Runic: Runic
612
713function is_using_or_import (x)
814 return kind (x) === K " using" || kind (x) === K " import"
@@ -22,7 +28,7 @@ function find_using_or_import(x)
2228 end
2329end
2430
25- char_range (x) = x. position: (x. position + span (x))
31+ char_range (x) = x. position: (x. position + span (x) - 1 )
2632
2733function organize_import_file (f)
2834 jst = parseall (SyntaxNode, read (f, String))
@@ -33,50 +39,49 @@ function organize_import_block(input)
3339 # Collect all sibling blocks that are also using/import expressions
3440
3541 x = find_using_or_import (input)
36- isnothing (x) && return
42+ isnothing (x) && return JuliaSyntax . sourcetext (input)
3743
3844 siblings = []
3945
4046 child_nodes = children (x)
41- ind = findfirst (is_using_or_import, child_nodes)
47+ first_ind = findfirst (is_using_or_import, child_nodes)
4248
43- while ! isnothing (ind)
44- push! (siblings, popat! (child_nodes, ind))
45- ind = findfirst (is_using_or_import, child_nodes)
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
4655 end
4756
57+ src = JuliaSyntax. sourcetext (input)
58+
4859 # Collect all modules and symbols
4960 using_mods = Set {String} ()
5061 using_syms = Dict {String, Set{String}} ()
5162 import_mods = Set {String} ()
5263 import_syms = Dict {String, Set{String}} ()
5364
54- # Joins e.g. [".", ".", "Foo", "Bar"] (from "using ..Foo.Bar") to "..Foo.Bar"
55- function module_join (x)
56- io = IOBuffer ()
57- for y in children (x)[1 : (end - 1 )]
58- print (io, y. val)
59- y. val == " ." && continue
60- print (io, " ." )
61- end
62- print (io, children (x)[end ]. val)
63- return String (take! (io))
64- end
65+ # Extract the source text of a node, trimming whitespace
66+ node_text (x) = strip (src[char_range (x)])
6567
6668 for s in siblings
6769 isusing = kind (s) === K " using"
6870 for a in children (s)
6971 if kind (a) === K " :"
7072 a_args = children (a)
71- mod = module_join (a_args[1 ])
73+ mod = node_text (a_args[1 ])
7274 set = get! (Set, isusing ? using_syms : import_syms, mod)
7375 for i in 2 : length (a_args)
74- push! (set, join (y . val for y in children (a_args[i])))
76+ push! (set, String ( node_text (a_args[i])))
7577 end
76- elseif kind (a) === K " ."
77- push! (isusing ? using_mods : import_mods, module_join (a))
78+ elseif kind (a) === K " ." || kind (a) === K " importpath"
79+ push! (isusing ? using_mods : import_mods, String (node_text (a)))
80+ elseif ! isusing && kind (a) === K " as"
81+ a_args = children (a)
82+ push! (import_mods, node_text (a_args[1 ]) * " as " * node_text (a_args[end ]))
7883 else
79- error (" unreachable? " )
84+ error (" Unexpected syntax in using/import statement. " )
8085 end
8186 end
8287 end
@@ -86,66 +91,78 @@ function organize_import_block(input)
8691 # BlueStyle (modules, types, ..., functions) since usually CamelCase is used for
8792 # modules, types, etc, but possibly this can be improved by using information
8893 # 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+
89104 import_lines = String[]
90105 for m in import_mods
91106 push! (import_lines, " import " * m)
92107 end
93108 for (m, s) in import_syms
94- push! (import_lines, " import " * m * " : " * join (sort! ( collect (s) ), " , " ))
109+ push! (import_lines, " import " * m * " : " * join (sort_with_self_first (s, m ), " , " ))
95110 end
96111 using_lines = String[]
97112 for m in using_mods
98113 push! (using_lines, " using " * m)
99114 end
100115 for (m, s) in using_syms
101- push! (using_lines, " using " * m * " : " * join (sort! ( collect (s) ), " , " ))
116+ push! (using_lines, " using " * m * " : " * join (sort_with_self_first (s, m ), " , " ))
102117 end
103118 io = IOBuffer ()
104119 join (io, sort! (import_lines), " \n " )
105- length (import_lines) > 0 && print (io, " \n \n" )
120+ length (import_lines) > 0 && length (using_lines) > 0 && print (io, " \n " )
106121 join (io, sort! (using_lines), " \n " )
107122 str_to_fmt = String (take! (io))
108123
109124 # Line wrap the using/import statements only
110125 formatted = JuliaFormatter. format_text (str_to_fmt; join_lines_based_on_source = true )
111126
112- src = JuliaSyntax. sourcetext (input)
113-
114- statement_range = char_range (siblings[1 ])
115127 first_pos = first (char_range (siblings[1 ]))
128+ last_pos = last (char_range (siblings[end ]))
116129
117- content = src[1 : first_pos- 1 ] * formatted
118-
119- content_to_append = " "
120-
121- last_pos = last (char_range (siblings[1 ]))
122-
123- # Get the content between the using/import statements
124- for s in siblings[2 : end ]
125- statement_range = char_range (s)
126- content_to_append *= src[last_pos + 1 : (first (statement_range) - 1 )]
127- last_pos = last (statement_range)
128- end
129-
130- if length (content_to_append) > 1
131- content_to_append = " \n " * content_to_append
132- end
133-
134- # Tack this onto the end
135- content *= content_to_append * src[(last_pos + 1 ): end ]
130+ content = src[1 : (first_pos - 1 )] * chomp (formatted) * src[(last_pos + 1 ): end ]
136131
137132 return content
138133end
139134
140- function (@main )(ARGS )
141- for file in ARGS
142- _, ext = splitext (file)
143- ext == " .jl" || error (" Only .jl files are supported" )
144- content = organize_import_file (file)
145- write (file, content)
135+ """
136+ ITensorFormatter.main(argv)
137+
138+ Format Julia source files. Primarily formats using Runic formatting, but additionally
139+ organizes 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.
143+ """
144+ function main (argv)
145+ inputfiles = String[]
146+ x = filter (! startswith (" --" ), argv)
147+ for x in argv
148+ if startswith (x, " --" )
149+ # Ignore options for now, they are assumed to be for Runic.
150+ elseif isdir (x)
151+ Runic. scandir! (inputfiles, x)
152+ else # isfile(x)
153+ push! (inputfiles, x) # Assume it is a file for now
154+ end
155+ end
156+ for inputfile in inputfiles
157+ content = organize_import_file (inputfile)
158+ write (inputfile, content)
146159 end
147- Runic. main (ARGS )
160+ Runic. main (argv )
148161 return 0
149- end
162+ end
163+
164+ @static if isdefined (Base, Symbol (" @main" ))
165+ @main
166+ end
150167
151168end
0 commit comments