Skip to content
Merged
10 changes: 3 additions & 7 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@ repos:
- repo: "local"
hooks:
- id: "itensor-formatter"
name: "ITensor Formatter"
entry: "itfmt"
pass_filenames: true
always_run: false
types:
- "file"
files: "\\.jl$"
name: "ITensor Pkg Formatter"
entry: "itpkgfmt ."
language: "system"
pass_filenames: false
- repo: "meta"
hooks:
- id: "check-hooks-apply"
Expand Down
10 changes: 3 additions & 7 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
- id: "itensor-formatter"
name: "ITensor Formatter"
entry: "itfmt"
pass_filenames: true
always_run: false
types:
- "file"
files: "\\.jl$"
name: "ITensor Pkg Formatter"
entry: "itpkgfmt ."
language: "system"
pass_filenames: false
description: "Code formatter for the ITensor ecosystem built on Runic and JuliaFormatter."
8 changes: 7 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "ITensorFormatter"
uuid = "b6bf39f1-c9d3-4bad-aad8-593d802f65fd"
version = "0.2.15"
version = "0.2.16"
authors = ["ITensor developers <support@itensor.org> and contributors"]

[workspace]
Expand All @@ -10,7 +10,9 @@ projects = ["benchmark", "dev", "docs", "examples", "test"]
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
JuliaSyntax = "70703baa-626e-46a2-a12c-08ffd08c73b4"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
Runic = "62bfec6d-59d7-401d-8490-b29ee721c001"
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"
Expand All @@ -19,14 +21,18 @@ YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"
JuliaFormatter = "2.3"
JuliaSyntax = "0.4.10"
Literate = "2.21"
Logging = "1.10"
OrderedCollections = "1.8.1"
PrecompileTools = "1"
Runic = "1.5.1"
TOML = "1.0.3"
YAML = "0.4.16"
julia = "1.10"


[apps.itfmt]
julia_flags = ["--compile=min", "--optimize=0"]

[apps.itpkgfmt]
julia_flags = ["--compile=min", "--optimize=0"]
submodule = "ITensorPkgFormatter"
2 changes: 1 addition & 1 deletion docs/src/reference.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Reference

```@autodocs
Modules = [ITensorFormatter]
Modules = [ITensorFormatter, ITensorFormatter.ITensorPkgFormatter]
```
3 changes: 2 additions & 1 deletion src/ITensorFormatter.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module ITensorFormatter

if VERSION >= v"1.11.0-DEV.469"
let str = "public main"
let str = "public ITensorPkgFormatter, main"
eval(Meta.parse(str))
end
end
Expand All @@ -13,5 +13,6 @@ include("format_project_toml.jl")
include("main.jl")
include("generate_readme.jl")
include("ITensorPkgFormatter.jl")
include("precompile_workload.jl")

end
8 changes: 7 additions & 1 deletion src/ITensorPkgFormatter.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
module ITensorPkgFormatter

using ITensorFormatter: ITensorFormatter
using Logging: Logging

"""
$(ITensorFormatter.help_markdown())
"""
function main(argv)
ITensorFormatter.main(argv)
paths = filter(!startswith("--"), argv)
ITensorFormatter.generate_readmes!(paths)
Logging.with_logger(Logging.NullLogger()) do
return ITensorFormatter.generate_readmes!(paths)
end
return nothing
end

Expand Down
3 changes: 1 addition & 2 deletions src/generate_readme.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ function isitensorpkg(path::AbstractString)
return isdir(path) &&
isfile(joinpath(path, "Project.toml")) &&
isdir(joinpath(path, "src")) &&
isdir(joinpath(path, "test")) &&
isdir(joinpath(path, "docs")) &&
isfile(joinpath(path, "docs", "make_readme.jl"))
end
Expand All @@ -23,7 +22,7 @@ function generate_readme!(path::AbstractString)
end
try
cd(joinpath(path, "docs")) do
include("make_readme.jl")
include(joinpath(pwd(), "make_readme.jl"))
return nothing
end
catch e
Expand Down
119 changes: 63 additions & 56 deletions src/main.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ const JULIAFORMATTER_OPTIONS = (
for_in_replacement = "in",
# Semantic transformations consistent with Runic
always_use_return = true,
import_to_using = true,
# Ideally we would use `import_to_using = true`, however that changes the import
# formatting, which requires reformatting imports again which is expensive without
# a better solution besides running JuliaFormatter twice.
import_to_using = false,
pipe_to_function_call = true,
short_to_long_function_def = true,
long_to_short_function_def = false,
Expand Down Expand Up @@ -53,42 +56,63 @@ format_runic!(path::AbstractString) = format_runic!([path])
const ITENSORFORMATTER_VERSION = pkgversion(@__MODULE__)

# Print a typical cli program help message
function print_help()
io = stdout
function print_help(io::IO = stdout)
printstyled(io, "NAME"; bold = true)
println(io)
println(io, " ITensorFormatter.main - format Julia source code")
println(
io,
" ITensorPkgFormatter.main - format ITensor Julia packages and generate READMEs"
)
println(io)
printstyled(io, "SYNOPSIS"; bold = true)
println(io)
println(io, " julia -m ITensorFormatter [<options>] <path>...")
println(io, " julia -m ITensorPkgFormatter [<options>] <path>...")
println(io)
printstyled(io, "DESCRIPTION"; bold = true)
println(io)
println(
io, """
`ITensorFormatter.main` (typically invoked as `julia -m ITensorFormatter`)
formats Julia source code using the ITensorFormatter.jl formatter.
io,
"""
`ITensorFormatter.main` (typically invoked as `julia -m ITensorFormatter` or `itfmt`)
formats Julia source code, Project.toml, and optionally YAML files using the ITensorFormatter.jl formatter.
"""
)
println(
io,
"""
`ITensorPkgFormatter.main` (typically invoked as `julia -m ITensorPkgFormatter` or `itpkgfmt`)
performs all formatting as above, and additionally generates README documentation for each provided ITensor package directory.
"""
)
printstyled(io, "OPTIONS"; bold = true)
println(io)
println(
io, """
<path>...
Input path(s) (files and/or directories) to process. For directories,
all files (recursively) with the '*.jl' suffix are used as input files.

--help
Print this message.

--version
Print ITensorFormatter and julia version information.
io,
"""
<path>...\n\
Input path(s) (files and/or directories) to process. For directories,\n\
all files (recursively) with the '*.jl' suffix are used as input files.\n\
--help\n\
Print this message.\n\
--version\n\
Print ITensorFormatter and julia version information.\n\
--yaml\n\
Also format YAML files (*.yml, *.yaml). Disabled by default.\n """
)
return
return nothing
end

# One shared generator for the plain-text help
function help_text()
# ensure no ANSI styling sneaks into docstring text
return sprint(io -> print_help(IOContext(io, :color => false)))
end

# Docstring-friendly wrapper
help_markdown() = "```text\n" * help_text() * "\n```"

function print_version()
print(stdout, "itfmt version ")
print(stdout, ITENSORFORMATTER_VERSION)
Expand All @@ -100,62 +124,45 @@ end

function process_args(argv)
format = true
format_yaml = false
argv_options = filter(startswith("--"), argv)
if !isempty(argv_options)
if "--help" in argv_options
print_help()
return String[], !format
elseif "--version" in argv_options
print_version()
return String[], !format
else
return error("Options not supported: `$argv_options`.")
end
end
# `argv` doesn't have any options, so treat all arguments as file/directory paths.
return argv, format
end

"""
ITensorFormatter.main(argv)

Format Julia source files. Primarily formats using Runic formatting, but additionally
organizes using/import statements by merging adjacent blocks, sorting modules and symbols,
and line-wrapping. Accepts file paths and directories as arguments.

# Examples

```julia-repl
julia> using ITensorFormatter: ITensorFormatter
if "--help" in argv_options
print_help()
return (; paths = String[], format = false, format_yaml = false)
elseif "--version" in argv_options
print_version()
return (; paths = String[], format = false, format_yaml = false)
end

julia> ITensorFormatter.main(["."]);
format_yaml = "--yaml" ∈ argv_options
unknown = setdiff(argv_options, ["--yaml"])
!isempty(unknown) && error("Options not supported: `$unknown`.")

julia> ITensorFormatter.main(["file1.jl", "file2.jl"]);
paths = filter(x -> !startswith(x, "--"), argv)
return (; paths, format, format_yaml)
end

```
"""
$(help_markdown())
"""
function main(argv)
paths, format = process_args(argv)
(; paths, format, format_yaml) = process_args(argv)
!format && return 0
isempty(paths) && return error("No input paths provided.")
jlfiles = filterpaths(isjlfile, paths)
yamlfiles = filterpaths(isyamlfile, paths)
yamlfiles = format_yaml ? filterpaths(isyamlfile, paths) : String[]
projectomls = filterpaths(isprojecttoml, paths)
# Pass 1: Organize import/using blocks
format_imports!(jlfiles)
# Pass 2: Format via JuliaFormatter
format_juliaformatter!(jlfiles)
# Pass 3: Re-organize imports again (fix up any changes from JuliaFormatter, e.g.
# import_to_using)
format_imports!(jlfiles)
# Pass 4: Format via JuliaFormatter again to fix import line wrapping
format_juliaformatter!(jlfiles)
# Pass 5: Canonicalize via Runic
# Pass 3: Canonicalize via Runic
format_runic!(jlfiles)
# Pass 6: Format YAML files
format_yamls!(yamlfiles)
# Pass 7: Format Project.toml files
# Pass 4: Format Project.toml files
format_project_tomls!(projectomls)
# Pass 5: Format YAML files (optional)
format_yaml && format_yamls!(yamlfiles)
return 0
end

Expand Down
28 changes: 28 additions & 0 deletions src/precompile_workload.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using PrecompileTools: @compile_workload, @setup_workload

@setup_workload begin
tmp = mktempdir()
try
# Make tmp look like an ITensor package dir (so generate_readme! runs)
for d in ("src", "docs", "examples")
mkpath(joinpath(tmp, d))
end

# Minimal files so the formatter has something to do
write(joinpath(tmp, "src", "Example.jl"), "module Example\nx=1\nend\n")
write(joinpath(tmp, "config.yaml"), "a: 1\nb: 2\n")
write(
joinpath(tmp, "Project.toml"),
"name = \"Example\"\nuuid = \"00000000-0000-0000-0000-000000000000\"\nversion = \"0.1.0\"\n"
)

@compile_workload begin
# Ideally we might use `ITensorPkgFormatter.main([tmp])` to include
# precompilation of README generation, however it seems tricky to get that
# working because of the file generation involved in that workflow.
main([tmp])
end
finally
rm(tmp; recursive = true, force = true)
end
end
15 changes: 15 additions & 0 deletions test/test_basics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,21 @@ end
@test_throws ErrorException ITensorFormatter.main(["--bad"])
end

@testset "--yaml gates YAML parsing/formatting" begin
mktempdir() do dir
# Intentionally invalid YAML so we can detect whether YAML formatting ran.
ypath = joinpath(dir, "bad.yaml")
write(ypath, "a: [\n")

# Default: YAML formatting is off, so this should succeed.
ret = @suppress ITensorFormatter.main([dir])
@test ret == 0

# Opt-in: should attempt YAML parsing and throw.
@test_throws Exception ITensorFormatter.main(["--yaml", dir])
end
end

@testset "no arguments" begin
@test_throws ErrorException ITensorFormatter.main(String[])
end
Expand Down
Loading