diff --git a/.travis.yml b/.travis.yml index 320b018..23e84de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ julia: - 1.1 - 1.2 - 1.3 + - 1.4 - nightly notifications: email: false diff --git a/CHANGELOG.md b/CHANGELOG.md index ceed8c0..923147e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Unreleased +- allow more customizations ([#21](https://github.com/tpapp/PkgSkeleton.jl/pull/21)) +- add TagBot and CompatHelper actions to template ([#20](https://github.com/tpapp/PkgSkeleton.jl/pull/20)) + # 0.3.2 - dev the parent package in the docs project upon generation @@ -14,9 +17,9 @@ # 0.3.0 -- get rid of Pkg.METADATA_compatible_uuid (#7, thanks @ffevotte) +- get rid of `Pkg.METADATA_compatible_uuid` ([#7](https://github.com/tpapp/PkgSkeleton.jl/pull/7)), thanks @ffevotte) -- use Documenter 0.23.4 in the template, minor cleanup (#8) +- use Documenter 0.23.4 in the template, minor cleanup ([#8](https://github.com/tpapp/PkgSkeleton.jl/pull/8)) # 0.2.0 diff --git a/Manifest.toml b/Manifest.toml deleted file mode 100644 index 96cbcb2..0000000 --- a/Manifest.toml +++ /dev/null @@ -1,73 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - -[[ArgCheck]] -git-tree-sha1 = "59c256cf71c3982484ae4486ee86a3d7da891dea" -uuid = "dce04be8-c92d-5529-be00-80e4d2c0e197" -version = "2.0.0" - -[[Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" - -[[Dates]] -deps = ["Printf"] -uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" - -[[Distributed]] -deps = ["Random", "Serialization", "Sockets"] -uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" - -[[DocStringExtensions]] -deps = ["LibGit2", "Markdown", "Pkg", "Test"] -git-tree-sha1 = "88bb0edb352b16608036faadcc071adda068582a" -uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.8.1" - -[[InteractiveUtils]] -deps = ["Markdown"] -uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" - -[[LibGit2]] -uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" - -[[Logging]] -uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" - -[[Markdown]] -deps = ["Base64"] -uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" - -[[Pkg]] -deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"] -uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" - -[[Printf]] -deps = ["Unicode"] -uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" - -[[REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets"] -uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" - -[[Random]] -deps = ["Serialization"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" - -[[SHA]] -uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" - -[[Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" - -[[Sockets]] -uuid = "6462fe0b-24de-5631-8697-dd941f90decc" - -[[Test]] -deps = ["Distributed", "InteractiveUtils", "Logging", "Random"] -uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[[UUIDs]] -deps = ["Random", "SHA"] -uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" - -[[Unicode]] -uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" diff --git a/Project.toml b/Project.toml index e8be9c1..9c8ece6 100644 --- a/Project.toml +++ b/Project.toml @@ -11,9 +11,9 @@ LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433" UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] -ArgCheck = "^1, 2.0" -DocStringExtensions = "^0.8" -julia = "^1" +ArgCheck = "1, 2" +DocStringExtensions = "0.8" +julia = "1" [extras] Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" diff --git a/src/PkgSkeleton.jl b/src/PkgSkeleton.jl index de980fa..f6fb71a 100644 --- a/src/PkgSkeleton.jl +++ b/src/PkgSkeleton.jl @@ -17,22 +17,68 @@ import UUIDs #### Template values #### +""" +$(SIGNATURES) + +Extract a package name using the last component of a path. + +The following all result in `"Foo"`: + +```julia +pkg_name_from_path("/tmp/Foo") +pkg_name_from_path("/tmp/Foo/") +pkg_name_from_path("/tmp/Foo.jl") +pkg_name_from_path("/tmp/Foo.jl/") +``` +""" +function pkg_name_from_path(path::AbstractString) + base, ext = splitext(basename(isdirpath(path) ? dirname(path) : path)) + @argcheck(isempty(ext) || ext == ".jl", + "Invalid extension $(ext), specify package as a replacement value.") + base +end + +"Docstring for replacement values." +const REPLACEMENTS_DOCSTRING = """ +- `UUID`: the package UUID; default: random +- `PKGNAME`: the package name; default: taken from the destination directory +- `GHUSER`: the github user; default: taken from Git options +- `USERNAME`: the user name; default: taken from Git options +- `USEREMAIL`: the user e-mail; default: taken from Git options +- `YEAR`: the calendar year; default: from system time +""" """ $(SIGNATURES) Populate a vector of replacement pairs, either from arguments or by querying global settings and state. + +The following replacement values are used: + +$(REPLACEMENTS_DOCSTRING) """ -function get_replacement_values(; pkg_name) +function fill_replacements(replacements; dest_dir) c = LibGit2.GitConfig() # global configuration _getgitopt(opt, type = AbstractString) = LibGit2.get(type, c, opt) - ["{UUID}" => UUIDs.uuid4(), - "{PKGNAME}" => pkg_name, - "{GHUSER}" => _getgitopt("github.user"), - "{USERNAME}" => _getgitopt("user.name"), - "{USEREMAIL}" => _getgitopt("user.email"), - "{YEAR}" => Dates.year(Dates.now())] + _provided_values = propertynames(replacements) + function _ensure_value(key, f) + if key ∈ _provided_values + # VERSION ≥ 1.2 could use hasproperty, but we support earlier versions too + getproperty(replacements, key) + else + # we are lazy here so that the user can user an override when obtaining the + # value from the environment would error + f() + end + end + defaults = (UUID = () -> UUIDs.uuid4(), + PKGNAME = () -> pkg_name_from_path(dest_dir), + GHUSER = () -> _getgitopt("github.user"), + USERNAME = () -> _getgitopt("user.name"), + USEREMAIL = () -> _getgitopt("user.email"), + YEAR = () -> Dates.year(Dates.now())) + NamedTuple{keys(defaults)}(map(_ensure_value, keys(defaults), values(defaults))) end #### @@ -43,8 +89,15 @@ end $(SIGNATURES) Replace multiple pairs in `str`, using `replace` iteratively. + +`replacements` should be an associative collection that supports `pairs` (eg `Dict`, +`NamedTuple`, …). They are wrapped in `{}`s for replacement in the templates. """ -replace_multiple(str, replacements) = foldl(replace, replacements; init = str) +function replace_multiple(str, replacements) + delimited_replacements = Dict(["{$(string(key))}" => string(value) + for (key, value) in pairs(replacements)]) + foldl(replace, delimited_replacements; init = str) +end """ $(SIGNATURES) @@ -104,26 +157,6 @@ function resolve_template_dir(dir::AbstractString) dir end -""" -$(SIGNATURES) - -Extract a package name using the last component of a path. - -The following all result in `"Foo"`: - -```julia -pkg_name_from_path("/tmp/Foo") -pkg_name_from_path("/tmp/Foo/") -pkg_name_from_path("/tmp/Foo.jl") -pkg_name_from_path("/tmp/Foo.jl/") -``` -""" -function pkg_name_from_path(path::AbstractString) - base, ext = splitext(basename(isdirpath(path) ? dirname(path) : path)) - @argcheck isempty(ext) || ext == ".jl" "Invalid extension $(ext), specify package name manually." - base -end - #### #### exposed API #### @@ -140,20 +173,21 @@ The directory is transformed with `expanduser`, replacing `~` in paths. `template` specifies the template to use. Symbols (eg `:default`, which is the default) refer to *built-in* templates delivered with this package. Strings are considered paths. -`skip_existing_dir = true` (the default) aborts package generation for existing directories. +`replacements`: a `NamedTuple` that can be used to manually specify the replacements: -`skip_existing_files = true` (the default) prevents existing files from being overwritten. +$(REPLACEMENTS_DOCSTRING) -`pkg_name` can be used to specify a package name. Note that it is derived from `dest_dir`: -the package name is `"Foo"` for all of +Specifically, `PKGNAME` can be used to specify a package name, derived from `dest_dir` by +default: the package name is `"Foo"` for all of -1. `"/tmp/Foo"`, -2. `"/tmp/Foo/"`, -3. `"/tmp/Foo.jl"`, -4. `"/tmp/Foo.jl/"`, +1. `"/tmp/Foo"`, 2. `"/tmp/Foo/"`, 3. `"/tmp/Foo.jl"`, 4. `"/tmp/Foo.jl/"`. Use a different name only when you know what you are doing. +`skip_existing_dir = true` (the default) aborts package generation for existing directories. + +`skip_existing_files = true` (the default) prevents existing files from being overwritten. + `git_init = true` (the default) ensures that an *empty* repository is generated in `dest_dir`. You still have to commit files yourself. @@ -161,9 +195,9 @@ Use a different name only when you know what you are doing. this. """ function generate(dest_dir; template = :default, + replacements::NamedTuple = NamedTuple(), skip_existing_dir::Bool = true, skip_existing_files::Bool = true, - pkg_name = pkg_name_from_path(dest_dir), git_init::Bool = true, docs_manifest::Bool = true) dest_dir = expanduser(dest_dir) # preliminary checks @@ -175,7 +209,7 @@ function generate(dest_dir; template = :default, # copy and substitute @info "getting template values" - replacements = get_replacement_values(; pkg_name = pkg_name) + replacements = fill_replacements(replacements; dest_dir = dest_dir) @info "copy and substitute" results = copy_and_substitute(resolve_template_dir(template), dest_dir, replacements; skip_existing_files = skip_existing_files) @@ -199,7 +233,7 @@ function generate(dest_dir; template = :default, end # done - @info "successfully generated $(pkg_name)" dest_dir + @info "successfully generated $(replacements.PKGNAME)" dest_dir true end diff --git a/test/runtests.jl b/test/runtests.jl index e0b41ab..9e5c278 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,7 @@ using PkgSkeleton, Test, Dates, UUIDs # import internals for testing -using PkgSkeleton: get_replacement_values, resolve_template_dir, pkg_name_from_path, +using PkgSkeleton: fill_replacements, resolve_template_dir, pkg_name_from_path, replace_multiple #### @@ -9,7 +9,7 @@ using PkgSkeleton: get_replacement_values, resolve_template_dir, pkg_name_from_p #### if !success(`git --help`) - @info "Command line git should be installed." + @info "Command line git should be installed for tests." exit(1) end @@ -47,12 +47,22 @@ end #### @testset "replacement values" begin - d = Dict(get_replacement_values(; pkg_name = "FOO")) - @test d["{UUID}"] isa UUID - @test d["{GHUSER}"] == GHUSER - @test d["{USERNAME}"] == USERNAME - @test d["{USEREMAIL}"] == USEREMAIL - @test d["{YEAR}"] == year(now()) + @testset "using environment" begin + d = fill_replacements(NamedTuple(); dest_dir = "/tmp/FOO.jl") + @test d.PKGNAME == "FOO" + @test d.UUID isa UUID + @test d.GHUSER == GHUSER + @test d.USERNAME == USERNAME + @test d.USEREMAIL == USEREMAIL + @test d.YEAR == year(now()) + end + + @testset "using explicit replacements" begin + r = (PKGNAME = "bar", UUID = "1234", GHUSER = "someone", USERNAME = "Some O. N.", + USEREMAIL = "foo@bar.baz", YEAR = 1643) + r′ = fill_replacements(r; dest_dir = "irrelevant") + @test sort(collect(pairs(r)), by = first) == sort(collect(pairs(r′)), by = first) + end end @testset "template directories" begin @@ -72,7 +82,7 @@ end end @testset "multiple replaces" begin - @test replace_multiple("{COLOR} {DISH}", ["{COLOR}" => "green", "{DISH}" => "curry"]) == + @test replace_multiple("{COLOR} {DISH}", (COLOR = "green", DISH = "curry")) == "green curry" end