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

Add the @has_preference and @delete_preferences convenience functions and macros #6

Merged
merged 1 commit into from
Jan 7, 2021
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- windows-latest
arch:
- x64
- x86
# - x86 # TODO: uncomment this line
exclude:
- os: macOS-latest
arch: x86
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Preferences"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
authors = ["Elliot Saba <elliot.saba@juliacomputing.com>"]
version = "1.0.0"
version = "1.1.0"

[deps]
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
Expand Down
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Preferences

[![Continuous Integration][ci-img]][ci-url]
[![Code Coverage][codecov-img]][codecov-url]

[ci-url]: https://github.com/JuliaPackaging/Preferences.jl/actions?query=workflow%3ACI
[codecov-url]: https://codecov.io/gh/JuliaPackaging/Preferences.jl

[ci-img]: https://github.com/JuliaPackaging/Preferences.jl/workflows/CI/badge.svg "Continuous Integration"
[codecov-img]: https://codecov.io/gh/JuliaPackaging/Preferences.jl/branch/master/graph/badge.svg "Code Coverage"

The `Preferences` package provides a convenient, integrated way for packages to store configuration switches to persistent TOML files, and use those pieces of information at both run time and compile time.
This enables the user to modify the behavior of a package, and have that choice reflected in everything from run time algorithm choice to code generation at compile time.
Preferences are stored as TOML dictionaries and are, by default, stored within a `(Julia)LocalPreferences.toml` file next to the currently-active project.
Expand All @@ -17,12 +26,16 @@ When your package sets a compile-time preference, it is usually best to suggest

## API

Preferences use is very simple; it is all based around two functions (which each have convenience macros): `@set_preferences!()` and `@load_preference()`.
Preferences use is very simple; it is all based around four functions (which each have convenience macros): `@set_preferences!()`, `@load_preference()`, `@has_preference()`, and `@delete_preferences!()`.

* `@load_preference(key, default = nothing)`: This loads a preference named `key` for the current package. If no such preference is found, it returns `default`.

* `@set_preferences!(pairs...)`: This allows setting multiple preferences at once as pairs.

* `@has_preference(key)`: Returns true if the preference named `key` is found, and `false` otherwise.

* `@delete_preferences!(keys...)`: Delete one or more preferences.

To illustrate the usage, we show a toy module, taken directly from this package's tests:

```julia
Expand Down
66 changes: 63 additions & 3 deletions src/Preferences.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ using TOML
using Base: UUID, TOMLCache

export load_preference, @load_preference,
set_preferences!, @set_preferences!
has_preference, @has_preference,
set_preferences!, @set_preferences!,
delete_preferences!, @delete_preferences!

include("utils.jl")

Expand Down Expand Up @@ -53,6 +55,31 @@ macro load_preference(key, default = nothing)
end
end

"""
has_preference(uuid_or_module, key, default = nothing)
Return `true` if the particular preference is found, and `false` otherwise.
See the `has_preference` docstring for more details.
"""
function has_preference(uuid::UUID, key::String, default = nothing)
value = load_preference(uuid, key, nothing)
return !(value isa Nothing)
end
function has_preference(m::Module, key::String, default = nothing)
return has_preference(get_uuid(m), key, default)
end

"""
@has_preference(key)
Convenience macro to call `has_preference()` for the current package.
"""
macro has_preference(key, default = nothing)
return quote
has_preference($(esc(get_uuid(__module__))), $(esc(key)), $(esc(default)))
end
end

"""
process_sentinel_values!(prefs::Dict)
Expand Down Expand Up @@ -169,7 +196,7 @@ will be exactly what was saved here. If we wanted to re-enable inheritance from
up in the chain, we could do the same but passing `missing` first.
The `export_prefs` option determines whether the preferences being set should be stored
within `LocalPreferences.toml` or `Project.toml`.
within `LocalPreferences.toml` or `Project.toml`.
"""
function set_preferences!(u::UUID, prefs::Pair{String,<:Any}...; export_prefs=false, kwargs...)
# Find the first `Project.toml` that has this UUID as a direct dependency
Expand Down Expand Up @@ -203,7 +230,7 @@ function set_preferences!(u::UUID, prefs::Pair{String,<:Any}...; export_prefs=fa
target_toml = project_toml
if !export_prefs
target_toml = joinpath(dirname(project_toml), "LocalPreferences.toml")
end
end
return set_preferences!(target_toml, pkg_name, prefs...; kwargs...)
end
function set_preferences!(m::Module, prefs::Pair{String,<:Any}...; kwargs...)
Expand All @@ -223,4 +250,37 @@ macro set_preferences!(prefs...)
end
end

"""
delete_preferences!(uuid_or_module, prefs::String...; block_inheritance::Bool = false, export_prefs=false, force=false)
Deletes a series of preferences for the given UUID/Module, identified by the
keys passed in as `prefs`.
See the docstring for `set_preferences!`for more details.
"""
function delete_preferences!(u::UUID, pref_keys::String...; block_inheritance::Bool = false, kwargs...)
if block_inheritance
return set_preferences!(u::UUID, [k => nothing for k in pref_keys]...; kwargs...)
else
return set_preferences!(u::UUID, [k => missing for k in pref_keys]...; kwargs...)
end
end
function delete_preferences!(m::Module, pref_keys::String...; kwargs...)
return delete_preferences!(get_uuid(m), prefs...; kwargs...)
end

"""
@delete_preferences!(prefs...)
Convenience macro to call `delete_preferences!()` for the current package. Defaults to
setting `force=true`, since a package should have full control over itself, but not
so for deleting the preferences in other packages, pending private dependencies.
"""
macro delete_preferences!(prefs...)
return quote
delete_preferences!($(esc(get_uuid(__module__))), $(esc(prefs...)), force=true)
end
end

end # module Preferences
6 changes: 6 additions & 0 deletions test/UsesPreferences/src/UsesPreferences.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,11 @@ end
function get_username()
return @load_preference("username")
end
function has_username()
return @has_preference("username")
end
function delete_username()
@delete_preferences!("username")
end

end # module UsesPreferences
41 changes: 39 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ up_path = joinpath(@__DIR__, "UsesPreferences")
prefs = TOML.parsefile(local_prefs_toml)
@test haskey(prefs, "UsesPreferences")
@test prefs["UsesPreferences"]["backend"] == "CUDA"

# Now show that it forces recompilation
did_precompile(output) = occursin("Precompiling UsesPreferences [$(string(up_uuid))]", output)
cuda_test = """
Expand All @@ -92,18 +92,55 @@ up_path = joinpath(@__DIR__, "UsesPreferences")
using UsesPreferences, Test, Preferences
using Base: UUID
@test load_preference($(repr(up_uuid)), "username") === nothing
@test !UsesPreferences.has_username()
@test UsesPreferences.get_username() === nothing
UsesPreferences.set_username("giordano")
@test UsesPreferences.get_username() == "giordano"
""")

# This does not cause a recompilation, and we can also get the username back again:
username_test = """
using UsesPreferences, Test
using UsesPreferences, Test, Preferences
@test UsesPreferences.get_username() == "giordano"
"""
output = activate_and_run(up_path, username_test; env=Dict("JULIA_DEBUG" => "loading"))
@test !did_precompile(output)

_prefs_delete_username = "delete_preferences!($(repr(up_uuid)), \"username\"; block_inheritance = false)"
_delete_username = "UsesPreferences.delete_username()"
_has_username = "@test UsesPreferences.has_username()"
_doesnt_have_username = "!@test UsesPreferences.has_username()"
_set_username = "UsesPreferences.set_username(\"giordano\")"
_get_username = "@test UsesPreferences.get_username() == \"giordano\""
_doesnt_have_set_get_username = """
$(_doesnt_have_username)
$(_set_username)
$(_get_username)
"""
snippets = [
_prefs_delete_username,
_doesnt_have_set_get_username,
_has_username,
_delete_username,
_doesnt_have_set_get_username,
_has_username,
_prefs_delete_username,
_doesnt_have_set_get_username,
_has_username,
_prefs_delete_username,
_doesnt_have_set_get_username,
_has_username,
]
for snippet in snippets
code = """
using UsesPreferences, Test, Preferences
using Base: UUID
$(snippet)
"""
output = activate_and_run(up_path, username_test; env=Dict("JULIA_DEBUG" => "loading"))
@test !did_precompile(output)
end
end
end

Expand Down