Projects
"""
struct Project
A `struct` that holds details of a "project", which is defined as
> a configuration file (in TOML format), along with a collection of associated
> source and support files.
"Projects" can be created in two ways, by `Module`, or configuration path.
"""
Base.@kwdef mutable struct Project
path::Union{Nothing,AbstractPath} = nothing
env::Dict{String,Any} = Dict()
tree::FileTree = maketree("." => [])
pages::Vector{AbstractPath} = []
loaded::Dict{AbstractPath,Project} = Dict()
globals::Dict{String,Any} = Dict()
end
Base.show(io::IO, p::Project) = print(io, "$Project($(p.path))")
Project
Constructors
"""
Project(mod)
Create a new [`Project`](#) object from the given module `mod`.
"""
Project(mod::Module; kws...) = Project(findproject(mod); kws...)
"""
Project(path)
Create a new [`Project`](#) object from the given configuration `path`. The
`path` must be a TOML file.
"""
Project(path::AbstractString; kws...) = Project(Path(path); kws...)
function Project(path::AbstractPath; loaded=Dict{AbstractPath,Project}(), globals=Dict{String,Any}())
if haskey(loaded, path)
return loaded[path]
elseif isfile(path)
path = abspath(path)
cd(dirname(path)) do
path = canonicalize(path)
env = loadtoml(path, globals)
env = loadrefs(env)
tree = loadtree(env, path)
tree, env = loadtheme(tree, env)
tree, pages = loadpages(tree, env)
tree, pages = loaddocs(tree, env, pages)
loaded[path] = Project(
path = path,
env = env,
tree = tree,
pages = pages,
loaded = loaded,
globals = globals,
)
return loaded[path]
end
else
# Raise error only when at 'toplevel' of project creation.
return isempty(loaded) ? error("not a file: '$path'.") : Project()
end
end
Project((name, uuid)::Pair; kws...) = Project(findmodule(name, uuid); kws...)
Project(project::Project; kws...) = project
Project(::Nothing; kws...) = nothing
Helper Functions
"""
findproject(mod)
Returns the path to the configuration file for the module `mod`.
When no path can be found, for example `Base` and it's modules, then `nothing`
is returned instead.
"""
function findproject(mod::Module)
root = Base.moduleroot(mod)
meth = first(methods(root.eval))
file = string(meth.file)
isabspath(file) || return nothing
dir = dirname(dirname(realpath(file)))
for toml in ("Project.toml", "JuliaProject.toml")
f = joinpath(dir, toml)
isfile(f) && return f
end
return nothing
end
findproject(id::Base.PkgId) = findproject(findmodule(id))
function findmodule(id::Base.PkgId)
haskey(Base.loaded_modules, id) || Base.require(id)
return Base.loaded_modules[id]
end
findmodule(name::AbstractString, uuid::AbstractString) = findmodule(Base.PkgId(Base.UUID(uuid), name))
findmodule(env::AbstractDict) = findmodule(env["name"], get(env, "uuid", nothing))
findmodule(name::AbstractString, ::Nothing) = nothing
function findmodules(env::AbstractDict)
roots = env["publish"]["modules"]
mods = Set{Module}()
if isempty(roots)
mod = findmodule(env)
mod isa Module && push!(mods, mod)
else
for root in roots
bind = binding(root)
if Docs.defined(bind)
push!(mods, Docs.resolve(bind))
end
end
end
return mods
end
"""
update!(project)
Reload contents of the given `project`.
"""
function update!(p::Project)
# TODO: make this more efficient, only update parts of project that change.
delete!(p.loaded, p.path)
q = Project(string(p.path); loaded=p.loaded, globals=p.globals)
p.path = q.path
p.env = q.env
p.tree = q.tree
p.pages = q.pages
p.loaded = q.loaded
p.globals = q.globals
p.loaded[p.path] = p
return p
end
function loaddeps!(project::Project)
@info "loading project dependencies."
for each in project.env["deps"]
Project(each; loaded=project.loaded, globals=project.globals)
end
end