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 option to change working directory for evaluation of example code #1025

Merged
merged 22 commits into from
Jun 13, 2019
Merged
Show file tree
Hide file tree
Changes from 15 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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ test/formats/builds/
test/missingdocs/build/
test/nongit/build/
test/errors/build/
test/workdir/builds/
docs/build/
docs/pdf/build/
docs/site/
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

* Documenter v0.23 requires Julia v1.0. ([#1015][github-1015])

* ![Feature][badge-feature] The current working directory when evaluating `@repl` and `@example` blocks can now be set to a fixed directory by passing the `workdir` keyword to `makedocs`. ([#1013][github-1013], [#1025][github-1025])

* ![Enhancement][badge-enhancement] The logo image in the HTML output will now always point to the first page in the navigation menu (as opposed to `index.html`, which may or may not exist). When using pretty URLs, the `index.html` part now omitted from the logo link URL. ([#1005][github-1005])

## Version `v0.22.4`
Expand Down Expand Up @@ -321,8 +323,10 @@
[github-1003]: https://github.com/JuliaDocs/Documenter.jl/issues/1003
[github-1004]: https://github.com/JuliaDocs/Documenter.jl/pull/1004
[github-1009]: https://github.com/JuliaDocs/Documenter.jl/pull/1009
[github-1013]: https://github.com/JuliaDocs/Documenter.jl/issues/1013
[github-1014]: https://github.com/JuliaDocs/Documenter.jl/pull/1014
[github-1015]: https://github.com/JuliaDocs/Documenter.jl/pull/1015
[github-1025]: https://github.com/JuliaDocs/Documenter.jl/pull/1025

[documenterlatex]: https://github.com/JuliaDocs/DocumenterLaTeX.jl
[documentermarkdown]: https://github.com/JuliaDocs/DocumenterMarkdown.jl
Expand Down
6 changes: 4 additions & 2 deletions docs/src/man/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,8 @@ on each line is also removed.

!!! note
The working directory, `pwd`, is set to the directory in `build` where the file
will be written to, and the paths in `include` calls are interpreted to be relative to `pwd`.
will be written to, and the paths in `include` calls are interpreted to be relative to
`pwd`. This can be customized with the `workdir` keyword of [`makedocs`](@ref).

**Hiding Source Code**

Expand Down Expand Up @@ -493,7 +494,8 @@ Named `@repl <name>` blocks behave in the same way as named `@example <name>` bl

!!! note
The working directory, `pwd`, is set to the directory in `build` where the file
will be written to, and the paths in `include` calls are interpreted to be relative to `pwd`.
will be written to, and the paths in `include` calls are interpreted to be relative to
`pwd`. This can be customized with the `workdir` keyword of [`makedocs`](@ref).

## `@setup <name>` block

Expand Down
15 changes: 14 additions & 1 deletion src/Builder.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ function Selectors.runner(::Type{SetupBuildDirectory}, doc::Documents.Document)
# Frequently used fields.
build = doc.user.build
source = doc.user.source
workdir = doc.user.workdir


# The .user.source directory must exist.
isdir(source) || error("source directory '$(abspath(source))' is missing.")
Expand All @@ -105,9 +107,20 @@ function Selectors.runner(::Type{SetupBuildDirectory}, doc::Documents.Document)
for file in files
src = normpath(joinpath(root, file))
dst = normpath(joinpath(build, relpath(root, source), file))

if workdir == :build
# set working directory to be the same as `build`
wd = normpath(joinpath(build, relpath(root, source)))
elseif workdir isa Symbol
# Maybe allow `:src` and `:root` as well?
throw(ArgumentError("Unrecognized working directory option '$workdir'"))
else
wd = normpath(joinpath(doc.user.root, workdir))
end

if endswith(file, ".md")
push!(mdpages, Utilities.srcpath(source, root, file))
Documents.addpage!(doc, src, dst)
Documents.addpage!(doc, src, dst, wd)
else
cp(src, dst; force = true)
end
Expand Down
15 changes: 15 additions & 0 deletions src/Documenter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export Deps, makedocs, deploydocs, hide
repo = "",
highlightsig = true,
sitename = "",
workdir = :build,
)

Combines markdown files and inline docstrings into an interlinked document.
Expand Down Expand Up @@ -107,6 +108,20 @@ written when [`makedocs`](@ref) is run. The name of the build directory is, by c
called `build`, though, like with `source`, users are free to change this to anything else
to better suit their project needs.

**`workdir`** determines the working directory where `@example` and `@repl` code blocks are
executed. It can be either a path or the special value `:build` (default).

If the `workdir` is set to a path, the working directory is reset to that path for each code
block being evaluated. Relative paths are taken to be relative to `root`, but using absolute
paths is recommended (e.g. `workdir = joinpath(@__DIR__, "..")` for executing in the package
root for the usual `docs/make.jl` setup).

With the default `:build` option, the working directory is set to a subdirectory of `build`,
determined from the source file path. E.g. for `src/foo.md` it is set to `build/`, for
`src/foo/bar.md` it is set to `build/foo` etc.
mortenpi marked this conversation as resolved.
Show resolved Hide resolved

Note that `workdir` does not affect doctests.

**`clean`** tells [`makedocs`](@ref) whether to remove all the content from the `build`
folder prior to generating new content from `source`. By default this is set to `true`.

Expand Down
40 changes: 22 additions & 18 deletions src/Documents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ Globals() = Globals(Main, Dict())
Represents a single markdown file.
"""
struct Page
source :: String
build :: String
source :: String
build :: String
workdir :: Union{Symbol,String}
"""
Ordered list of raw toplevel markdown nodes from the parsed page contents. This vector
should be considered immutable.
Expand All @@ -50,9 +51,9 @@ struct Page
mapping :: IdDict{Any,Any}
globals :: Globals
end
function Page(source::AbstractString, build::AbstractString)
function Page(source::AbstractString, build::AbstractString, workdir::AbstractString)
elements = Markdown.parse(read(source, String)).content
Page(source, build, elements, IdDict{Any,Any}(), Globals())
Page(source, build, workdir, elements, IdDict{Any,Any}(), Globals())
end

# Document Nodes.
Expand All @@ -61,12 +62,12 @@ end
## IndexNode.

struct IndexNode
pages :: Vector{String} # Which pages to include in the index? Set by user.
modules :: Vector{Module} # Which modules to include? Set by user.
order :: Vector{Symbol} # What order should docs be listed in? Set by user.
build :: String # Path to the file where this index will appear.
source :: String # Path to the file where this index was written.
elements :: Vector # (object, doc, page, mod, cat)-tuple for constructing links.
pages :: Vector{String} # Which pages to include in the index? Set by user.
modules :: Vector{Module} # Which modules to include? Set by user.
order :: Vector{Symbol} # What order should docs be listed in? Set by user.
build :: String # Path to the file where this index will appear.
source :: String # Path to the file where this index was written.
elements :: Vector # (object, doc, page, mod, cat)-tuple for constructing links.

function IndexNode(;
# TODO: Fix difference between uppercase and lowercase naming of keys.
Expand All @@ -85,11 +86,11 @@ end
## ContentsNode.

struct ContentsNode
pages :: Vector{String} # Which pages should be included in contents? Set by user.
depth :: Int # Down to which level should headers be displayed? Set by user.
build :: String # Same as for `IndexNode`s.
source :: String # Same as for `IndexNode`s.
elements :: Vector # (order, page, anchor)-tuple for constructing links.
pages :: Vector{String} # Which pages should be included in contents? Set by user.
depth :: Int # Down to which level should headers be displayed? Set by user.
build :: String # Same as for `IndexNode`s.
source :: String # Same as for `IndexNode`s.
elements :: Vector # (order, page, anchor)-tuple for constructing links.

function ContentsNode(;
Pages = [],
Expand Down Expand Up @@ -187,6 +188,7 @@ struct User
root :: String # An absolute path to the root directory of the document.
source :: String # Parent directory is `.root`. Where files are read from.
build :: String # Parent directory is also `.root`. Where files are written to.
workdir :: Union{Symbol,String} # Parent directory is also `.root`. Where code is executed from.
format :: Vector{Plugin} # What format to render the final document with?
clean :: Bool # Empty the `build` directory before starting a new build?
doctest :: Union{Bool,Symbol} # Run doctests?
Expand Down Expand Up @@ -239,6 +241,7 @@ function Document(plugins = nothing;
root :: AbstractString = Utilities.currentdir(),
source :: AbstractString = "src",
build :: AbstractString = "build",
workdir :: Union{Symbol, AbstractString} = :build,
format :: Any = Documenter.HTML(),
clean :: Bool = true,
doctest :: Union{Bool,Symbol} = true,
Expand Down Expand Up @@ -270,6 +273,7 @@ function Document(plugins = nothing;
root,
source,
build,
workdir,
format,
clean,
doctest,
Expand Down Expand Up @@ -333,8 +337,8 @@ end

## Methods

function addpage!(doc::Document, src::AbstractString, dst::AbstractString)
page = Page(src, dst)
function addpage!(doc::Document, src::AbstractString, dst::AbstractString, wd::AbstractString)
page = Page(src, dst, wd)
# page's identifier is the path relative to the `doc.user.source` directory
name = normpath(relpath(src, doc.user.source))
doc.internal.pages[name] = page
Expand Down Expand Up @@ -433,7 +437,7 @@ doctest_replace!(block) = true

function buildnode(T::Type, block, doc, page)
mod = get(page.globals.meta, :CurrentModule, Main)
dict = Dict{Symbol, Any}(:source => page.source, :build => page.build)
dict = Dict{Symbol, Any}(:source => page.source, :build => page.build, :workdir => page.workdir)
mortenpi marked this conversation as resolved.
Show resolved Hide resolved
for (ex, str) in Utilities.parseblock(block.code, doc, page)
if Utilities.isassign(ex)
cd(dirname(page.source)) do
Expand Down
8 changes: 4 additions & 4 deletions src/Expanders.jl
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ end
function Selectors.runner(::Type{EvalBlocks}, x, page, doc)
sandbox = Module(:EvalBlockSandbox)
lines = Utilities.find_block_in_file(x.code, page.source)
cd(dirname(page.build)) do
cd(page.workdir) do
result = nothing
for (ex, str) in Utilities.parseblock(x.code, doc, page; keywords = false)
try
Expand Down Expand Up @@ -540,7 +540,7 @@ function Selectors.runner(::Type{ExampleBlocks}, x, page, doc)
end
for (ex, str) in Utilities.parseblock(code, doc, page; keywords = false)
(value, success, backtrace, text) = Utilities.withoutput() do
cd(dirname(page.build)) do
cd(page.workdir) do
Core.eval(mod, ex)
end
end
Expand Down Expand Up @@ -597,7 +597,7 @@ function Selectors.runner(::Type{REPLBlocks}, x, page, doc)
buffer = IOBuffer()
input = droplines(str)
(value, success, backtrace, text) = Utilities.withoutput() do
cd(dirname(page.build)) do
cd(page.workdir) do
Core.eval(mod, ex)
end
end
Expand Down Expand Up @@ -634,7 +634,7 @@ function Selectors.runner(::Type{SetupBlocks}, x, page, doc)
# Evaluate whole @setup block at once instead of piecewise
page.mapping[x] =
try
cd(dirname(page.build)) do
cd(page.workdir) do
include_string(mod, x.code)
end
Markdown.MD([])
Expand Down
3 changes: 3 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ println("="^50)

# A simple build outside of a Git repository
include("nongit/tests.jl")

# A simple build evaluating code outside build directory
include("workdir/tests.jl")
end

# Additional tests
Expand Down
2 changes: 1 addition & 1 deletion test/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ end
end

import Documenter.Documents: Document, Page, Globals
let page = Page("source", "build", [], IdDict{Any,Any}(), Globals()), doc = Document()
let page = Page("source", "build", :build, [], IdDict{Any,Any}(), Globals()), doc = Document()
code = """
x += 3
γγγ_γγγ
Expand Down
19 changes: 19 additions & 0 deletions test/workdir/make.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Documenter

const pages = [
"Home" => "index.md",
"File" => "file.md",
"Subdir" => "subdir/index.md",
"Subfile" => "subdir/file.md",
]

@info "Building builds/default"
makedocs(sitename="Test", pages = pages, build="builds/default")

@info "Building builds/absolute"
mkdir(joinpath(@__DIR__, "builds/absolute-workdir"))
makedocs(sitename="Test", pages = pages, build="builds/absolute", workdir=joinpath(@__DIR__, "builds/absolute-workdir"))

@info "Building builds/relative"
mkdir(joinpath(@__DIR__, "builds/relative-workdir"))
makedocs(sitename="Test", pages = pages, build="builds/relative", workdir="builds/relative-workdir")
9 changes: 9 additions & 0 deletions test/workdir/src/file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Testing

Here's a test

```@repl
pwd()

touch("root_file.txt")
```
9 changes: 9 additions & 0 deletions test/workdir/src/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Testing

Here's a test

```@repl
pwd()

touch("root_index.txt")
```
9 changes: 9 additions & 0 deletions test/workdir/src/subdir/file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Testing

Here's a test

```@repl
pwd()

touch("subdir_file.txt")
```
9 changes: 9 additions & 0 deletions test/workdir/src/subdir/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Testing

Here's a test

```@repl
pwd()

touch("subdir_index.txt")
```
36 changes: 36 additions & 0 deletions test/workdir/tests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Test

# for convenience, when debugging, the build step can be disabled when running the tests with
# julia test/workdir/tests.jl skipbuild
if !("skipbuild" in ARGS)
rm(joinpath(@__DIR__, "builds"), recursive=true, force=true) # cleanup of previous test run
include(joinpath(@__DIR__, "make.jl"))
end

@testset "makedocs workdir" begin
# test for the default build
@test isfile(joinpath(@__DIR__, "builds", "default", "root_index.txt"))
@test isfile(joinpath(@__DIR__, "builds", "default", "root_file.txt"))
@test isfile(joinpath(@__DIR__, "builds", "default", "subdir", "subdir_index.txt"))
@test isfile(joinpath(@__DIR__, "builds", "default", "subdir", "subdir_file.txt"))

# absolute path
@test !isfile(joinpath(@__DIR__, "builds", "absolute", "root_index.txt"))
@test !isfile(joinpath(@__DIR__, "builds", "absolute", "root_file.txt"))
@test !isfile(joinpath(@__DIR__, "builds", "absolute", "subdir", "subdir_index.txt"))
@test !isfile(joinpath(@__DIR__, "builds", "absolute", "subdir", "subdir_file.txt"))
@test isfile(joinpath(@__DIR__, "builds", "absolute-workdir", "root_index.txt"))
@test isfile(joinpath(@__DIR__, "builds", "absolute-workdir", "root_file.txt"))
@test isfile(joinpath(@__DIR__, "builds", "absolute-workdir", "subdir_index.txt"))
@test isfile(joinpath(@__DIR__, "builds", "absolute-workdir", "subdir_file.txt"))

# relative path
@test !isfile(joinpath(@__DIR__, "builds", "relative", "root_index.txt"))
@test !isfile(joinpath(@__DIR__, "builds", "relative", "root_file.txt"))
@test !isfile(joinpath(@__DIR__, "builds", "relative", "subdir", "subdir_index.txt"))
@test !isfile(joinpath(@__DIR__, "builds", "relative", "subdir", "subdir_file.txt"))
@test isfile(joinpath(@__DIR__, "builds", "relative-workdir", "root_index.txt"))
@test isfile(joinpath(@__DIR__, "builds", "relative-workdir", "root_file.txt"))
@test isfile(joinpath(@__DIR__, "builds", "relative-workdir", "subdir_index.txt"))
@test isfile(joinpath(@__DIR__, "builds", "relative-workdir", "subdir_file.txt"))
end