Skip to content

Commit 1b7d301

Browse files
authored
Also format YAML and Project.toml files (#18)
1 parent f004aa5 commit 1b7d301

12 files changed

Lines changed: 671 additions & 307 deletions

.github/dependabot.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
21
version: 2
32
updates:
43
- package-ecosystem: "github-actions"
5-
directory: "/" # Location of package manifests
4+
directory: "/"
65
schedule:
76
interval: "weekly"

Project.toml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "ITensorFormatter"
22
uuid = "b6bf39f1-c9d3-4bad-aad8-593d802f65fd"
3-
version = "0.2.11"
3+
version = "0.2.12"
44
authors = ["ITensor developers <support@itensor.org> and contributors"]
55

66
[workspace]
@@ -9,13 +9,20 @@ projects = ["benchmark", "dev", "docs", "examples", "test"]
99
[deps]
1010
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
1111
JuliaSyntax = "70703baa-626e-46a2-a12c-08ffd08c73b4"
12+
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
1213
Runic = "62bfec6d-59d7-401d-8490-b29ee721c001"
14+
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
15+
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"
1316

1417
[compat]
1518
JuliaFormatter = "2.3"
1619
JuliaSyntax = "0.4.10"
20+
OrderedCollections = "1.8.1"
1721
Runic = "1.5.1"
22+
TOML = "1.0.3"
23+
YAML = "0.4.16"
1824
julia = "1.10"
1925

20-
21-
[apps.itfmt]
26+
[apps]
27+
itfmt = {}
28+
itpkgfmt = { submodule = "ITensorPkgFormatter" }

src/ITensorFormatter.jl

Lines changed: 7 additions & 302 deletions
Original file line numberDiff line numberDiff line change
@@ -6,307 +6,12 @@ if VERSION >= v"1.11.0-DEV.469"
66
end
77
end
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

31217
end

src/ITensorPkgFormatter.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module ITensorPkgFormatter
2+
3+
using ITensorFormatter: ITensorFormatter
4+
5+
function main(argv)
6+
ITensorFormatter.main(argv)
7+
paths = filter(!startswith("--"), argv)
8+
ITensorFormatter.generate_readmes!(paths)
9+
return nothing
10+
end
11+
12+
@static if isdefined(Base, Symbol("@main"))
13+
@main
14+
end
15+
16+
end

0 commit comments

Comments
 (0)