Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tp/customizations #21

Merged
merged 5 commits into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ julia:
- 1.1
- 1.2
- 1.3
- 1.4
- nightly
notifications:
email: false
Expand Down
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand Down
73 changes: 0 additions & 73 deletions Manifest.toml

This file was deleted.

6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
112 changes: 73 additions & 39 deletions src/PkgSkeleton.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

####
Expand All @@ -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)
Expand Down Expand Up @@ -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
####
Expand All @@ -140,30 +173,31 @@ 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.

`docs_manifest` completes the `Manifest.toml` in the `docs` subdirectory. You usually want
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
Expand All @@ -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)
Expand All @@ -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

Expand Down
28 changes: 19 additions & 9 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
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

####
#### Command line git should be installed for tests (so that they don't depend in LibGit2).
####

if !success(`git --help`)
@info "Command line git should be installed."
@info "Command line git should be installed for tests."
exit(1)
end

Expand Down Expand Up @@ -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 = "[email protected]", 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
Expand All @@ -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

Expand Down