Skip to content

Commit

Permalink
Add option to change working directory for evaluation of example code (
Browse files Browse the repository at this point in the history
  • Loading branch information
kescobo authored and mortenpi committed Jun 13, 2019
1 parent 3e56671 commit 3e77205
Show file tree
Hide file tree
Showing 15 changed files with 159 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ test/formats/builds/
test/missingdocs/build/
test/nongit/build/
test/errors/build/
test/workdir/builds/
docs/build/
docs/pdf/build/
docs/site/
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

* ![Enhancement][badge-enhancement] Minor changes to how doctesting errors are printed. ([#1028][github-1028])

* ![Experimental][badge-experimental] ![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`. _The new keyword and its behaviour are experimental and not part of the public API._ ([#1013][github-1013], [#1025][github-1025])

## Version `v0.22.4`

* ![Bugfix][badge-bugfix] Documenter no longer crashes if the build includes doctests from docstrings that are defined in files that do not exist on the file system (e.g. if a Julia Base docstring is included when running a non-source Julia build). ([#1002][github-1002])
Expand Down Expand Up @@ -327,8 +329,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
[github-1027]: https://github.com/JuliaDocs/Documenter.jl/issues/1027
[github-1028]: https://github.com/JuliaDocs/Documenter.jl/pull/1028
[github-1029]: https://github.com/JuliaDocs/Documenter.jl/pull/1029
Expand All @@ -344,6 +348,7 @@
[badge-enhancement]: https://img.shields.io/badge/enhancement-blue.svg
[badge-bugfix]: https://img.shields.io/badge/bugfix-purple.svg
[badge-security]: https://img.shields.io/badge/security-black.svg
[badge-experimental]: https://img.shields.io/badge/experimental-lightgrey.svg

<!--
# Badges
Expand All @@ -354,4 +359,5 @@
![Enhancement][badge-enhancement]
![Bugfix][badge-bugfix]
![Security][badge-security]
![Experimental][badge-experimental]
-->
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
14 changes: 14 additions & 0 deletions src/Documenter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,20 @@ ignored.
**`strict`** -- [`makedocs`](@ref) fails the build right before rendering if it encountered
any errors with the document in the previous build phases.
**`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.
Note that `workdir` does not affect doctests.
## Output formats
**`format`** allows the output format to be specified. The default format is
[`Documenter.HTML`](@ref) which creates a set of HTML files.
Expand Down
38 changes: 21 additions & 17 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 @@ -240,6 +242,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 @@ -272,6 +275,7 @@ function Document(plugins = nothing;
root,
source,
build,
workdir,
format,
clean,
doctest,
Expand Down Expand Up @@ -336,8 +340,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
8 changes: 4 additions & 4 deletions src/Expanders.jl
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,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 @@ -553,7 +553,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 @@ -610,7 +610,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 @@ -647,7 +647,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 @@ -49,6 +49,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

0 comments on commit 3e77205

Please sign in to comment.