diff --git a/.gitignore b/.gitignore index f16e612..3bcefaa 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,6 @@ Manifest.toml benchmark/*.json docs/build/ +docs/LocalPreferences.toml +examples/LocalPreferences.toml test/LocalPreferences.toml diff --git a/Project.toml b/Project.toml index 7374470..30b5fef 100644 --- a/Project.toml +++ b/Project.toml @@ -4,19 +4,23 @@ authors = ["ITensor developers and contributors"] version = "0.1.0" [deps] +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" Git = "d7ba0133-e1db-5d97-8f8c-041e4b3a1eb2" Git_jll = "f8c6e375-362e-5223-8a59-34ff63f689eb" LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433" PkgSkeleton = "d254efa0-af53-535e-b7f1-03c1c9fbcbe7" Preferences = "21216c6a-2e73-6563-6e65-726566657250" +Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" [compat] Aqua = "0.8.9" +DocStringExtensions = "0.9.3" Git = "1.3.1" Git_jll = "2.46.2" LibGit2 = "1.10" PkgSkeleton = "1.3.1" Preferences = "1.4.3" +Suppressor = "0.2.8" Test = "1.10" julia = "1.10" diff --git a/docs/make.jl b/docs/make.jl index 082855d..6f45e66 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -16,7 +16,7 @@ makedocs(; edit_link="main", assets=String[], ), - pages=["Home" => "index.md"], + pages=["Home" => "index.md", "Reference" => "reference.md"], ) deploydocs(; diff --git a/docs/src/reference.md b/docs/src/reference.md new file mode 100644 index 0000000..98dea6f --- /dev/null +++ b/docs/src/reference.md @@ -0,0 +1,7 @@ +## Reference + +```@docs +ITensorPkgSkeleton.generate +ITensorPkgSkeleton.default_templates +ITensorPkgSkeleton.all_templates +``` diff --git a/examples/Project.toml b/examples/Project.toml index 668a5cb..2f9e3f0 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -1,2 +1,3 @@ [deps] +Git_jll = "f8c6e375-362e-5223-8a59-34ff63f689eb" ITensorPkgSkeleton = "3d388ab1-018a-49f4-ae50-18094d5f71ea" diff --git a/src/ITensorPkgSkeleton.jl b/src/ITensorPkgSkeleton.jl index d4dcaa1..4de80eb 100644 --- a/src/ITensorPkgSkeleton.jl +++ b/src/ITensorPkgSkeleton.jl @@ -1,9 +1,18 @@ module ITensorPkgSkeleton +# Declare `ITensorPkgSkeleton.generate` as public in a +# backwards-compatible way. +# See: https://discourse.julialang.org/t/is-compat-jl-worth-it-for-the-public-keyword/119041 +if VERSION >= v"1.11.0-DEV.469" + eval(Meta.parse("public all_templates, default_templates, generate")) +end + +using DocStringExtensions: SIGNATURES using Git: git using LibGit2: LibGit2 using PkgSkeleton: PkgSkeleton using Preferences: Preferences +using Suppressor: @suppress # Configure `Git.jl`/`Git_jll.jl` to # use the local installation of git. @@ -37,56 +46,81 @@ function default_branch_name() end function change_branch_name(path, branch_name) - original_dir = pwd() - cd(path) - original_branch_name = readchomp(`$(git()) branch --show-current`) - run(`$(git()) branch -m $original_branch_name $branch_name`) - cd(original_dir) + cd(path) do + original_branch_name = readchomp(`$(git()) branch --show-current`) + run(`$(git()) branch -m $original_branch_name $branch_name`) + return nothing + end return nothing end -function default_path() - # TODO: Use something like `joinpath(first(DEPOT_PATH), "dev", pkg_name)` - # to make it more general. - return joinpath(homedir(), ".julia", "dev") +function set_remote_url(path, pkgname, ghuser) + url = "git@github.com:$ghuser/$pkgname.jl.git" + cd(joinpath(path, pkgname)) do + try + @suppress begin + run(`$(git()) ls-remote -h "$url" HEAD $("&>") /dev/null`) + run(`$(git()) add origin $url`) + end + catch + end + return nothing + end + return nothing end -default_templates() = ["default"] +# https://pkgdocs.julialang.org/v1/api/#Pkg.develop +function default_path() + return joinpath(DEPOT_PATH[1], "dev") +end -default_user() = "ITensor" +default_ghuser() = "ITensor" +default_username() = "ITensor developers" +default_useremail() = "support@itensor.org" function default_user_replacements() return ( - GHUSER=default_user(), USERNAME="ITensor developers", USEREMAIL="support@itensor.org" + ghuser=default_ghuser(), username=default_username(), useremail=default_useremail() ) end +# See: +# https://discourse.julialang.org/t/remove-a-field-from-a-namedtuple/34664 +# https://github.com/JuliaLang/julia/pull/55270 +# https://github.com/JuliaLang/julia/issues/34772 +function delete(nt::NamedTuple{names}, key::Symbol) where {names} + return NamedTuple{filter(≠(key), names)}(nt) +end + #= This processes inputs like: ```julia -ITensorPkgSkeleton.generate("NewPkg"; user_replacements=(DOWNSTREAMPKGS=("ITensors",))) -ITensorPkgSkeleton.generate("NewPkg"; user_replacements=(DOWNSTREAMPKGS=(repo="ITensors",))) -ITensorPkgSkeleton.generate("NewPkg"; user_replacements=(DOWNSTREAMPKGS=(user="ITensor", repo="ITensors",))) +ITensorPkgSkeleton.generate("NewPkg"; downstreampkgs=["ITensors"]) +ITensorPkgSkeleton.generate("NewPkg"; downstreampkgs=[(ghuser="ITensor", repo="ITensors")]) ``` =# -function format_downstream_pkgs(user_replacements) - if !haskey(user_replacements, :DOWNSTREAMPKGS) +function format_downstreampkgs(user_replacements) + if !haskey(user_replacements, :downstreampkgs) return user_replacements end - DOWNSTREAMPKGS = "" - for user_repo in user_replacements.DOWNSTREAMPKGS - user, repo = if user_repo isa AbstractString + if isempty(user_replacements.downstreampkgs) + return delete(user_replacements, :downstreampkgs) + end + downstreampkgs = "" + for ghuser_and_or_repo in user_replacements.downstreampkgs + ghuser, repo = if ghuser_and_or_repo isa AbstractString # Only the repo was passed as a standalone value. - default_user(), user_repo + default_ghuser(), ghuser_and_or_repo else # The user and repo were passed in a NamedTuple, # or just the repo was passed in a NamedTuple. - get(user_repo, :user, default_user()), user_repo.repo + get(ghuser_and_or_repo, :user, default_ghuser()), ghuser_and_or_repo.repo end - DOWNSTREAMPKGS *= " - {user: $(user), repo: $(repo).jl}\n" + downstreampkgs *= " - {user: $(ghuser), repo: $(repo).jl}\n" end - DOWNSTREAMPKGS = chop(DOWNSTREAMPKGS) - return merge(user_replacements, (; DOWNSTREAMPKGS)) + # Remove extraneous trailing newline character. + downstreampkgs = chop(downstreampkgs) + return merge(user_replacements, (; downstreampkgs)) end function set_default_template_path(template) @@ -103,23 +137,100 @@ function is_git_repo(path) end end +""" +$(SIGNATURES) + +All available templates when constructing a package. Includes the following templates: `$(all_templates())` +""" +all_templates() = readdir(joinpath(pkgdir(ITensorPkgSkeleton), "templates")) + +""" +$(SIGNATURES) + +Default templates when constructing a package. Includes the following templates: `$(default_templates())` +""" +default_templates() = all_templates() + +function to_pkgskeleton(user_replacements) + return Dict(uppercase.(string.(keys(user_replacements))) .=> values(user_replacements)) +end + +""" +$(SIGNATURES) + +!!! warning + This function might overwrite existing code if you specify a path to a package that already exists, use with caution! See [`PkgSkeleton.jl`](https://github.com/tpapp/PkgSkeleton.jl) for more details. If you are updating an existing package, make sure you save everything you want to keep (for example, commit all of your changes if it is a git repository). + +Generate a package template for a package, by default in the ITensor organization, or update an existing package. This is a wrapper around [`PkgSkeleton.generate`](https://github.com/tpapp/PkgSkeleton.jl) but with extra functionality, custom templates used in the ITensor organization, and defaults biased towards creating a package in the ITensor organization. + +# Examples + +```jldoctest +julia> using ITensorPkgSkeleton: ITensorPkgSkeleton; + +julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir()); + +julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir()); + +julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), templates=ITensorPkgSkeleton.default_templates()); + +julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), templates=["github"]); + +julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), templates=["src", "github"]); + +julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), ignore_templates=["src", "github"]); + +julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), ghuser="MyOrg"); + +julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), downstreampkgs=["ITensors", "ITensorMPS"]); + +julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), downstreampkgs=[(user="ITensor", repo="ITensors")]); +``` + +# Arguments + +- `pkgname::AbstractString`: Name of the package (without the `.jl` extension). Replaces `{PKGNAME}` in the template. + +# Keywords + +- `path::AbstractString`: Path where the package will be generated. Defaults to the [development directory](https://pkgdocs.julialang.org/v1/api/#Pkg.develop), i.e. `$(default_path())`. +- `templates`: A list of templates to use. Select a subset of `ITensorPkgSkeleton.all_templates() = $(all_templates())`. Defaults to `ITensorPkgSkeleton.default_templates() = $(default_templates())`. +- `ignore_templates`: A list of templates to ignore. This is the same as setting `templates=setdiff(templates, ignore_templates)`. +- `downstreampkgs`: Specify the downstream packages that depend on this package. Setting this will create a workflow where the downstream tests will be run alongside the tests for this package in Github Actions to ensure that changes to your package don't break the specified downstream packages. Specify it as a list of packages, for example `["DownstreamPkg1", "DownstreamPkg2"]`, which assumes the packages are in the `$(default_ghuser())` organization. Alternatively, specify the organization with `[(user="Org1", repo="DownstreamPkg1"), (user="Org2", repo="DownstreamPkg2")]`; . Defaults to an empty list. +- `uuid`: Replaces `{UUID}` in the template. Defaults to the existing UUID in the `Project.toml` if the path points to an existing package, otherwise generates one randomly with `UUIDs.uuid4()`. +- `year`: Replaces `{YEAR}` in the template. Year the package/repository was created. Defaults to the current year. +""" function generate( - pkg_name; path=default_path(), templates=default_templates(), user_replacements=(;) + pkgname; + path=default_path(), + templates=default_templates(), + ignore_templates=[], + user_replacements..., ) - # Set default values. + pkgpath = joinpath(path, pkgname) user_replacements = merge(default_user_replacements(), user_replacements) + # Process downstream package information. + user_replacements = format_downstreampkgs(user_replacements) + templates = setdiff(templates, ignore_templates) + # Check if there are downstream tests. + if haskey(user_replacements, :downstreampkgs) && + !isempty(user_replacements.downstreampkgs) + templates = [templates; ["downstreampkgs"]] + else + templates = setdiff(templates, ["downstreampkgs"]) + end # Fill in default path if missing. templates = set_default_template_path.(templates) - # Process downstream package information. - user_replacements = format_downstream_pkgs(user_replacements) - pkg_path = joinpath(path, pkg_name) - is_new_repo = !is_git_repo(pkg_path) + is_new_repo = !is_git_repo(pkgpath) branch_name = default_branch_name() - user_replacements_dict = Dict(keys(user_replacements) .=> values(user_replacements)) - PkgSkeleton.generate(pkg_path; templates, user_replacements=user_replacements_dict) + user_replacements_pkgskeleton = to_pkgskeleton(user_replacements) + @suppress PkgSkeleton.generate( + pkgpath; templates, user_replacements=user_replacements_pkgskeleton + ) if is_new_repo # Change the default branch if this is a new repository. - change_branch_name(pkg_path, branch_name) + change_branch_name(pkgpath, branch_name) + set_remote_url(path, pkgname, user_replacements.ghuser) end return nothing end diff --git a/templates/default/benchmark/benchmarks.jl b/templates/benchmark/benchmark/benchmarks.jl similarity index 100% rename from templates/default/benchmark/benchmarks.jl rename to templates/benchmark/benchmark/benchmarks.jl diff --git a/templates/default/docs/Project.toml b/templates/docs/docs/Project.toml similarity index 100% rename from templates/default/docs/Project.toml rename to templates/docs/docs/Project.toml diff --git a/templates/default/docs/make.jl b/templates/docs/docs/make.jl similarity index 100% rename from templates/default/docs/make.jl rename to templates/docs/docs/make.jl diff --git a/templates/default/docs/make_index.jl b/templates/docs/docs/make_index.jl similarity index 100% rename from templates/default/docs/make_index.jl rename to templates/docs/docs/make_index.jl diff --git a/templates/downstream/.github/workflows/Downstream.yml b/templates/downstreampkgs/.github/workflows/Downstream.yml similarity index 100% rename from templates/downstream/.github/workflows/Downstream.yml rename to templates/downstreampkgs/.github/workflows/Downstream.yml diff --git a/templates/default/examples/Project.toml b/templates/examples/examples/Project.toml similarity index 100% rename from templates/default/examples/Project.toml rename to templates/examples/examples/Project.toml diff --git a/templates/default/examples/README.jl b/templates/examples/examples/README.jl similarity index 100% rename from templates/default/examples/README.jl rename to templates/examples/examples/README.jl diff --git a/templates/default/.JuliaFormatter.toml b/templates/formatter/.JuliaFormatter.toml similarity index 100% rename from templates/default/.JuliaFormatter.toml rename to templates/formatter/.JuliaFormatter.toml diff --git a/templates/default/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/templates/github/.github/ISSUE_TEMPLATE/BUG_REPORT.md similarity index 100% rename from templates/default/.github/ISSUE_TEMPLATE/BUG_REPORT.md rename to templates/github/.github/ISSUE_TEMPLATE/BUG_REPORT.md diff --git a/templates/default/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md b/templates/github/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md similarity index 100% rename from templates/default/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md rename to templates/github/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md diff --git a/templates/default/.github/PULL_REQUEST_TEMPLATE.md b/templates/github/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from templates/default/.github/PULL_REQUEST_TEMPLATE.md rename to templates/github/.github/PULL_REQUEST_TEMPLATE.md diff --git a/templates/default/.github/dependabot.yml b/templates/github/.github/dependabot.yml similarity index 100% rename from templates/default/.github/dependabot.yml rename to templates/github/.github/dependabot.yml diff --git a/templates/default/.github/workflows/CI.yml b/templates/github/.github/workflows/CI.yml similarity index 100% rename from templates/default/.github/workflows/CI.yml rename to templates/github/.github/workflows/CI.yml diff --git a/templates/default/.github/workflows/CompatHelper.yml b/templates/github/.github/workflows/CompatHelper.yml similarity index 100% rename from templates/default/.github/workflows/CompatHelper.yml rename to templates/github/.github/workflows/CompatHelper.yml diff --git a/templates/default/.github/workflows/FormatCheck.yml b/templates/github/.github/workflows/FormatCheck.yml similarity index 100% rename from templates/default/.github/workflows/FormatCheck.yml rename to templates/github/.github/workflows/FormatCheck.yml diff --git a/templates/default/.github/workflows/LiterateCheck.yml b/templates/github/.github/workflows/LiterateCheck.yml similarity index 100% rename from templates/default/.github/workflows/LiterateCheck.yml rename to templates/github/.github/workflows/LiterateCheck.yml diff --git a/templates/default/.github/workflows/Register.yml b/templates/github/.github/workflows/Register.yml similarity index 100% rename from templates/default/.github/workflows/Register.yml rename to templates/github/.github/workflows/Register.yml diff --git a/templates/default/.github/workflows/TagBot.yml b/templates/github/.github/workflows/TagBot.yml similarity index 100% rename from templates/default/.github/workflows/TagBot.yml rename to templates/github/.github/workflows/TagBot.yml diff --git a/templates/default/.gitignore b/templates/gitignore/.gitignore similarity index 100% rename from templates/default/.gitignore rename to templates/gitignore/.gitignore diff --git a/templates/default/LICENSE b/templates/license/LICENSE similarity index 92% rename from templates/default/LICENSE rename to templates/license/LICENSE index c931e76..cbe94c1 100644 --- a/templates/default/LICENSE +++ b/templates/license/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 ITensor developers and contributors +Copyright (c) {YEAR} ITensor developers and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/templates/default/.pre-commit-config.yaml b/templates/precommit/.pre-commit-config.yaml similarity index 100% rename from templates/default/.pre-commit-config.yaml rename to templates/precommit/.pre-commit-config.yaml diff --git a/templates/default/Project.toml b/templates/project/Project.toml similarity index 100% rename from templates/default/Project.toml rename to templates/project/Project.toml diff --git a/templates/default/README.md b/templates/readme/README.md similarity index 100% rename from templates/default/README.md rename to templates/readme/README.md diff --git a/templates/default/make_readme.jl b/templates/readme/make_readme.jl similarity index 100% rename from templates/default/make_readme.jl rename to templates/readme/make_readme.jl diff --git a/templates/default/src/{PKGNAME}.jl b/templates/src/src/{PKGNAME}.jl similarity index 100% rename from templates/default/src/{PKGNAME}.jl rename to templates/src/src/{PKGNAME}.jl diff --git a/templates/default/test/Project.toml b/templates/test/test/Project.toml similarity index 100% rename from templates/default/test/Project.toml rename to templates/test/test/Project.toml diff --git a/templates/default/test/runtests.jl b/templates/test/test/runtests.jl similarity index 100% rename from templates/default/test/runtests.jl rename to templates/test/test/runtests.jl diff --git a/templates/default/test/test_aqua.jl b/templates/test/test/test_aqua.jl similarity index 100% rename from templates/default/test/test_aqua.jl rename to templates/test/test/test_aqua.jl diff --git a/templates/default/test/test_basics.jl b/templates/test/test/test_basics.jl similarity index 100% rename from templates/default/test/test_basics.jl rename to templates/test/test/test_basics.jl diff --git a/templates/default/test/test_examples.jl b/templates/test/test/test_examples.jl similarity index 100% rename from templates/default/test/test_examples.jl rename to templates/test/test/test_examples.jl diff --git a/test/test_basics.jl b/test/test_basics.jl index 1b9e412..9e6f46c 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -17,20 +17,17 @@ using Test: @test, @testset path = mktempdir() ITensorPkgSkeleton.generate("NewPkg"; path) @test isdir(joinpath(path, "NewPkg")) + @test isfile(joinpath(path, "NewPkg", "Project.toml")) for dir in pkgdirs @test isdir(joinpath(path, "NewPkg", dir)) end + @test !isfile(joinpath(path, "NewPkg", ".github", "workflows", "Downstream.yml")) end @testset "generate with downstream tests" begin - for templates in (["downstream"], ["default", "downstream"]) - for DOWNSTREAMPKGS in ( - ("DownstreamPkg",), (repo="DownstreamPkg",), (user="ITensor", repo="DownstreamPkg") - ) + for templates in (ITensorPkgSkeleton.default_templates(), []) + for downstreampkgs in (["DownstreamPkg"], [(user="ITensor", repo="DownstreamPkg")]) path = mktempdir() - templates = ["default", "downstream"] - ITensorPkgSkeleton.generate( - "NewPkg"; path, templates, user_replacements=(; DOWNSTREAMPKGS) - ) + ITensorPkgSkeleton.generate("NewPkg"; path, templates, downstreampkgs) @test isdir(joinpath(path, "NewPkg")) @test isdir(joinpath(path, "NewPkg", ".github", "workflows")) @test isfile(joinpath(path, "NewPkg", ".github", "workflows", "Downstream.yml")) @@ -39,8 +36,8 @@ using Test: @test, @testset ) do io return contains(read(io, String), "- {user: ITensor, repo: DownstreamPkg.jl}") end - for dir in pkgdirs - @test isdir(joinpath(path, "NewPkg", dir)) == ("default" in templates) + for dir in setdiff(pkgdirs, [".github", ".github/workflows"]) + @test isdir(joinpath(path, "NewPkg", dir)) == !isempty(templates) end end end