diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d4eb7df..b360be0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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" diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index ec73c6c..90727d6 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -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." diff --git a/Project.toml b/Project.toml index 3c9f060..79f53bf 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorFormatter" uuid = "b6bf39f1-c9d3-4bad-aad8-593d802f65fd" -version = "0.2.15" +version = "0.2.16" authors = ["ITensor developers and contributors"] [workspace] @@ -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" @@ -19,7 +21,9 @@ 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" @@ -27,6 +31,8 @@ julia = "1.10" [apps.itfmt] +julia_flags = ["--compile=min", "--optimize=0"] [apps.itpkgfmt] +julia_flags = ["--compile=min", "--optimize=0"] submodule = "ITensorPkgFormatter" diff --git a/docs/src/reference.md b/docs/src/reference.md index 6105143..387feef 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -1,5 +1,5 @@ # Reference ```@autodocs -Modules = [ITensorFormatter] +Modules = [ITensorFormatter, ITensorFormatter.ITensorPkgFormatter] ``` diff --git a/src/ITensorFormatter.jl b/src/ITensorFormatter.jl index 2c138ee..776aae1 100644 --- a/src/ITensorFormatter.jl +++ b/src/ITensorFormatter.jl @@ -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 @@ -13,5 +13,6 @@ include("format_project_toml.jl") include("main.jl") include("generate_readme.jl") include("ITensorPkgFormatter.jl") +include("precompile_workload.jl") end diff --git a/src/ITensorPkgFormatter.jl b/src/ITensorPkgFormatter.jl index 3e830cd..c58dc9f 100644 --- a/src/ITensorPkgFormatter.jl +++ b/src/ITensorPkgFormatter.jl @@ -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 diff --git a/src/generate_readme.jl b/src/generate_readme.jl index 889be91..e910393 100644 --- a/src/generate_readme.jl +++ b/src/generate_readme.jl @@ -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 @@ -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 diff --git a/src/main.jl b/src/main.jl index fb13a2f..a16919c 100644 --- a/src/main.jl +++ b/src/main.jl @@ -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, @@ -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 [] ...") + println(io, " julia -m ITensorPkgFormatter [] ...") 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, """ - ... - 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, """ + ...\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) @@ -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 diff --git a/src/precompile_workload.jl b/src/precompile_workload.jl new file mode 100644 index 0000000..e5312cd --- /dev/null +++ b/src/precompile_workload.jl @@ -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 diff --git a/test/test_basics.jl b/test/test_basics.jl index f4118c8..9e6eeb6 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -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