Skip to content

Commit af706eb

Browse files
authored
Split default template into multiple templates, simplify interface (#20)
1 parent b77a7ac commit af706eb

File tree

37 files changed

+168
-46
lines changed

37 files changed

+168
-46
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@
1313
Manifest.toml
1414
benchmark/*.json
1515
docs/build/
16+
docs/LocalPreferences.toml
17+
examples/LocalPreferences.toml
1618
test/LocalPreferences.toml

Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,23 @@ authors = ["ITensor developers <[email protected]> and contributors"]
44
version = "0.1.0"
55

66
[deps]
7+
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
78
Git = "d7ba0133-e1db-5d97-8f8c-041e4b3a1eb2"
89
Git_jll = "f8c6e375-362e-5223-8a59-34ff63f689eb"
910
LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433"
1011
PkgSkeleton = "d254efa0-af53-535e-b7f1-03c1c9fbcbe7"
1112
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
13+
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
1214

1315
[compat]
1416
Aqua = "0.8.9"
17+
DocStringExtensions = "0.9.3"
1518
Git = "1.3.1"
1619
Git_jll = "2.46.2"
1720
LibGit2 = "1.10"
1821
PkgSkeleton = "1.3.1"
1922
Preferences = "1.4.3"
23+
Suppressor = "0.2.8"
2024
Test = "1.10"
2125
julia = "1.10"
2226

docs/make.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ makedocs(;
1616
edit_link="main",
1717
assets=String[],
1818
),
19-
pages=["Home" => "index.md"],
19+
pages=["Home" => "index.md", "Reference" => "reference.md"],
2020
)
2121

2222
deploydocs(;

docs/src/reference.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## Reference
2+
3+
```@docs
4+
ITensorPkgSkeleton.generate
5+
ITensorPkgSkeleton.default_templates
6+
ITensorPkgSkeleton.all_templates
7+
```

examples/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
[deps]
2+
Git_jll = "f8c6e375-362e-5223-8a59-34ff63f689eb"
23
ITensorPkgSkeleton = "3d388ab1-018a-49f4-ae50-18094d5f71ea"

src/ITensorPkgSkeleton.jl

Lines changed: 145 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
module ITensorPkgSkeleton
22

3+
# Declare `ITensorPkgSkeleton.generate` as public in a
4+
# backwards-compatible way.
5+
# See: https://discourse.julialang.org/t/is-compat-jl-worth-it-for-the-public-keyword/119041
6+
if VERSION >= v"1.11.0-DEV.469"
7+
eval(Meta.parse("public all_templates, default_templates, generate"))
8+
end
9+
10+
using DocStringExtensions: SIGNATURES
311
using Git: git
412
using LibGit2: LibGit2
513
using PkgSkeleton: PkgSkeleton
614
using Preferences: Preferences
15+
using Suppressor: @suppress
716

817
# Configure `Git.jl`/`Git_jll.jl` to
918
# use the local installation of git.
@@ -37,56 +46,81 @@ function default_branch_name()
3746
end
3847

3948
function change_branch_name(path, branch_name)
40-
original_dir = pwd()
41-
cd(path)
42-
original_branch_name = readchomp(`$(git()) branch --show-current`)
43-
run(`$(git()) branch -m $original_branch_name $branch_name`)
44-
cd(original_dir)
49+
cd(path) do
50+
original_branch_name = readchomp(`$(git()) branch --show-current`)
51+
run(`$(git()) branch -m $original_branch_name $branch_name`)
52+
return nothing
53+
end
4554
return nothing
4655
end
4756

48-
function default_path()
49-
# TODO: Use something like `joinpath(first(DEPOT_PATH), "dev", pkg_name)`
50-
# to make it more general.
51-
return joinpath(homedir(), ".julia", "dev")
57+
function set_remote_url(path, pkgname, ghuser)
58+
url = "[email protected]:$ghuser/$pkgname.jl.git"
59+
cd(joinpath(path, pkgname)) do
60+
try
61+
@suppress begin
62+
run(`$(git()) ls-remote -h "$url" HEAD $("&>") /dev/null`)
63+
run(`$(git()) add origin $url`)
64+
end
65+
catch
66+
end
67+
return nothing
68+
end
69+
return nothing
5270
end
5371

54-
default_templates() = ["default"]
72+
# https://pkgdocs.julialang.org/v1/api/#Pkg.develop
73+
function default_path()
74+
return joinpath(DEPOT_PATH[1], "dev")
75+
end
5576

56-
default_user() = "ITensor"
77+
default_ghuser() = "ITensor"
78+
default_username() = "ITensor developers"
79+
default_useremail() = "[email protected]"
5780

5881
function default_user_replacements()
5982
return (
60-
GHUSER=default_user(), USERNAME="ITensor developers", USEREMAIL="[email protected]"
83+
ghuser=default_ghuser(), username=default_username(), useremail=default_useremail()
6184
)
6285
end
6386

87+
# See:
88+
# https://discourse.julialang.org/t/remove-a-field-from-a-namedtuple/34664
89+
# https://github.com/JuliaLang/julia/pull/55270
90+
# https://github.com/JuliaLang/julia/issues/34772
91+
function delete(nt::NamedTuple{names}, key::Symbol) where {names}
92+
return NamedTuple{filter(≠(key), names)}(nt)
93+
end
94+
6495
#=
6596
This processes inputs like:
6697
```julia
67-
ITensorPkgSkeleton.generate("NewPkg"; user_replacements=(DOWNSTREAMPKGS=("ITensors",)))
68-
ITensorPkgSkeleton.generate("NewPkg"; user_replacements=(DOWNSTREAMPKGS=(repo="ITensors",)))
69-
ITensorPkgSkeleton.generate("NewPkg"; user_replacements=(DOWNSTREAMPKGS=(user="ITensor", repo="ITensors",)))
98+
ITensorPkgSkeleton.generate("NewPkg"; downstreampkgs=["ITensors"])
99+
ITensorPkgSkeleton.generate("NewPkg"; downstreampkgs=[(ghuser="ITensor", repo="ITensors")])
70100
```
71101
=#
72-
function format_downstream_pkgs(user_replacements)
73-
if !haskey(user_replacements, :DOWNSTREAMPKGS)
102+
function format_downstreampkgs(user_replacements)
103+
if !haskey(user_replacements, :downstreampkgs)
74104
return user_replacements
75105
end
76-
DOWNSTREAMPKGS = ""
77-
for user_repo in user_replacements.DOWNSTREAMPKGS
78-
user, repo = if user_repo isa AbstractString
106+
if isempty(user_replacements.downstreampkgs)
107+
return delete(user_replacements, :downstreampkgs)
108+
end
109+
downstreampkgs = ""
110+
for ghuser_and_or_repo in user_replacements.downstreampkgs
111+
ghuser, repo = if ghuser_and_or_repo isa AbstractString
79112
# Only the repo was passed as a standalone value.
80-
default_user(), user_repo
113+
default_ghuser(), ghuser_and_or_repo
81114
else
82115
# The user and repo were passed in a NamedTuple,
83116
# or just the repo was passed in a NamedTuple.
84-
get(user_repo, :user, default_user()), user_repo.repo
117+
get(ghuser_and_or_repo, :user, default_ghuser()), ghuser_and_or_repo.repo
85118
end
86-
DOWNSTREAMPKGS *= " - {user: $(user), repo: $(repo).jl}\n"
119+
downstreampkgs *= " - {user: $(ghuser), repo: $(repo).jl}\n"
87120
end
88-
DOWNSTREAMPKGS = chop(DOWNSTREAMPKGS)
89-
return merge(user_replacements, (; DOWNSTREAMPKGS))
121+
# Remove extraneous trailing newline character.
122+
downstreampkgs = chop(downstreampkgs)
123+
return merge(user_replacements, (; downstreampkgs))
90124
end
91125

92126
function set_default_template_path(template)
@@ -103,23 +137,100 @@ function is_git_repo(path)
103137
end
104138
end
105139

140+
"""
141+
$(SIGNATURES)
142+
143+
All available templates when constructing a package. Includes the following templates: `$(all_templates())`
144+
"""
145+
all_templates() = readdir(joinpath(pkgdir(ITensorPkgSkeleton), "templates"))
146+
147+
"""
148+
$(SIGNATURES)
149+
150+
Default templates when constructing a package. Includes the following templates: `$(default_templates())`
151+
"""
152+
default_templates() = all_templates()
153+
154+
function to_pkgskeleton(user_replacements)
155+
return Dict(uppercase.(string.(keys(user_replacements))) .=> values(user_replacements))
156+
end
157+
158+
"""
159+
$(SIGNATURES)
160+
161+
!!! warning
162+
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).
163+
164+
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.
165+
166+
# Examples
167+
168+
```jldoctest
169+
julia> using ITensorPkgSkeleton: ITensorPkgSkeleton;
170+
171+
julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir());
172+
173+
julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir());
174+
175+
julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), templates=ITensorPkgSkeleton.default_templates());
176+
177+
julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), templates=["github"]);
178+
179+
julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), templates=["src", "github"]);
180+
181+
julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), ignore_templates=["src", "github"]);
182+
183+
julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), ghuser="MyOrg");
184+
185+
julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), downstreampkgs=["ITensors", "ITensorMPS"]);
186+
187+
julia> ITensorPkgSkeleton.generate("NewPkg"; path=mktempdir(), downstreampkgs=[(user="ITensor", repo="ITensors")]);
188+
```
189+
190+
# Arguments
191+
192+
- `pkgname::AbstractString`: Name of the package (without the `.jl` extension). Replaces `{PKGNAME}` in the template.
193+
194+
# Keywords
195+
196+
- `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())`.
197+
- `templates`: A list of templates to use. Select a subset of `ITensorPkgSkeleton.all_templates() = $(all_templates())`. Defaults to `ITensorPkgSkeleton.default_templates() = $(default_templates())`.
198+
- `ignore_templates`: A list of templates to ignore. This is the same as setting `templates=setdiff(templates, ignore_templates)`.
199+
- `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.
200+
- `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()`.
201+
- `year`: Replaces `{YEAR}` in the template. Year the package/repository was created. Defaults to the current year.
202+
"""
106203
function generate(
107-
pkg_name; path=default_path(), templates=default_templates(), user_replacements=(;)
204+
pkgname;
205+
path=default_path(),
206+
templates=default_templates(),
207+
ignore_templates=[],
208+
user_replacements...,
108209
)
109-
# Set default values.
210+
pkgpath = joinpath(path, pkgname)
110211
user_replacements = merge(default_user_replacements(), user_replacements)
212+
# Process downstream package information.
213+
user_replacements = format_downstreampkgs(user_replacements)
214+
templates = setdiff(templates, ignore_templates)
215+
# Check if there are downstream tests.
216+
if haskey(user_replacements, :downstreampkgs) &&
217+
!isempty(user_replacements.downstreampkgs)
218+
templates = [templates; ["downstreampkgs"]]
219+
else
220+
templates = setdiff(templates, ["downstreampkgs"])
221+
end
111222
# Fill in default path if missing.
112223
templates = set_default_template_path.(templates)
113-
# Process downstream package information.
114-
user_replacements = format_downstream_pkgs(user_replacements)
115-
pkg_path = joinpath(path, pkg_name)
116-
is_new_repo = !is_git_repo(pkg_path)
224+
is_new_repo = !is_git_repo(pkgpath)
117225
branch_name = default_branch_name()
118-
user_replacements_dict = Dict(keys(user_replacements) .=> values(user_replacements))
119-
PkgSkeleton.generate(pkg_path; templates, user_replacements=user_replacements_dict)
226+
user_replacements_pkgskeleton = to_pkgskeleton(user_replacements)
227+
@suppress PkgSkeleton.generate(
228+
pkgpath; templates, user_replacements=user_replacements_pkgskeleton
229+
)
120230
if is_new_repo
121231
# Change the default branch if this is a new repository.
122-
change_branch_name(pkg_path, branch_name)
232+
change_branch_name(pkgpath, branch_name)
233+
set_remote_url(path, pkgname, user_replacements.ghuser)
123234
end
124235
return nothing
125236
end
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)