diff --git a/Project.toml b/Project.toml index eeff70ca..76089295 100644 --- a/Project.toml +++ b/Project.toml @@ -20,10 +20,17 @@ Documenter = "1" DocumenterCitations = "1" IOCapture = "0.2" NodeJS_20_jll = "20" +Test = "1" julia = "1.6" +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + [weakdeps] DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" [extensions] DocumenterVitepressDocumenterCitationsExt = "DocumenterCitations" + +[targets] +test = ["Test"] diff --git a/src/writer.jl b/src/writer.jl index e15892e4..ad6f95b6 100644 --- a/src/writer.jl +++ b/src/writer.jl @@ -9,8 +9,8 @@ import Markdown """ MarkdownVitepress(; repo, devbranch, devurl, kwargs...) -This is the main entry point for the Vitepress Markdown writer. - +This is the main entry point for the Vitepress Markdown writer. + It is a config which can be passed to the `format` keyword argument in `Documenter.makedocs`, and causes it to emit a Vitepress site. !!! tip "Quick start" @@ -49,7 +49,7 @@ Base.@kwdef struct MarkdownVitepress <: Documenter.Writer """The path to which the Markdown files will be output. Defaults to `\$build/.documenter`.""" md_output_path::String = ".documenter" """ - Determines whether to clean up the Markdown assets after build, i.e., whether to remove the contents of `md_output_path` after the Vitepress site is built. + Determines whether to clean up the Markdown assets after build, i.e., whether to remove the contents of `md_output_path` after the Vitepress site is built. Options are: - `nothing`: **Default**. Only remove the contents of `md_output_path` if the documentation will deploy, to save space. - `true`: Removes the contents of `md_output_path` after the Vitepress site is built. @@ -57,7 +57,14 @@ Base.@kwdef struct MarkdownVitepress <: Documenter.Writer """ clean_md_output::Union{Nothing, Bool} = nothing """ - DeployDecision from Documenter.jl. This is used to determine whether to deploy the documentation or not. + Whether to insert 200 redirects from https://example.com/page/ to https://example.com/page. + + Defaults to `false`. This is useful for transitioning from Documenter.jl which uses + trailing slashes for its canonical urls by default. + """ + redirect_trailing_slash::Bool = false + """ + `DeployDecision` from Documenter.jl. This is used to determine whether to deploy the documentation or not. Options are: - `nothing`: **Default**. Automatically determine whether to deploy the documentation. - `Documenter.DeployDecision`: Override the automatic decision and deploy based on the passed config. @@ -66,8 +73,20 @@ Base.@kwdef struct MarkdownVitepress <: Documenter.Writer `Documenter.deploy_folder(Documenter.auto_detect_deploy_system(); repo, devbranch, devurl, push_preview)`. """ deploy_decision::Union{Nothing, Documenter.DeployDecision} = nothing + "A list of assets, the same as what is provided to Documenter's HTMLWriter." assets = nothing + + # This inner constructor serves only to + function MarkdownVitepress(args...) + args[10] && !args[6] && throw(ArgumentError( + """ + MarkdownVitepress: `redirect_trailing_slash` can only be `true` if `build_vitepress` is also `true`, + because redirects are insterted after the site is built. + """ + )) + new(args...) + end end # return the same file with the extension changed to .md @@ -82,13 +101,13 @@ This function takes the filename `file`, and returns a file path in the `mdfolde function docpath(file, builddir, mdfolder) path = relpath(file, builddir) filename = mdext(path) - return joinpath(builddir, mdfolder, filename) + return joinpath(builddir, mdfolder, filename) end """ render(args...) -This is the main entry point and recursive function to render a Documenter document to +This is the main entry point and recursive function to render a Documenter document to Markdown in the Vitepress flavour. It is called by `Documenter.build` and should not be called directly. @@ -161,7 +180,7 @@ function render(doc::Documenter.Document, settings::MarkdownVitepress=MarkdownVi cp(file, file_destpath; force = true) end end - end + end if any(favicon_files) for file in files[favicon_files] file_relpath = relpath(file, joinpath(builddir, settings.md_output_path, "assets")) @@ -184,6 +203,23 @@ function render(doc::Documenter.Document, settings::MarkdownVitepress=MarkdownVi end mkpath(joinpath(builddir, "final_site")) + + # We manually obtain the Documenter deploy configuration, + # so we can use it to set Vitepress's settings. + if isnothing(settings.deploy_decision) + # TODO: make it so that the user does not have to provide a repo url! + deploy_config = Documenter.auto_detect_deploy_system() + deploy_decision = Documenter.deploy_folder( + deploy_config; + repo = settings.repo, # this must be the full URL! + devbranch = settings.devbranch, + devurl = settings.devurl, + push_preview=true, + ) + else + deploy_decision = settings.deploy_decision + end + if isfile(joinpath(builddir, settings.md_output_path, ".vitepress", "config.mts")) touch(joinpath(builddir, settings.md_output_path, ".vitepress", "config.mts")) end @@ -224,7 +260,7 @@ function render(doc::Documenter.Document, settings::MarkdownVitepress=MarkdownVi rm(joinpath(dirname(builddir), "package-lock.json")) end end - # This is only useful if placed in the root of the `docs` folder, and we don't + # This is only useful if placed in the root of the `docs` folder, and we don't # have any names which conflict with Jekyll (beginning with _ or .) in any case. # touch(joinpath(builddir, "final_site", ".nojekyll")) @@ -244,6 +280,44 @@ function render(doc::Documenter.Document, settings::MarkdownVitepress=MarkdownVi @info "DocumenterVitepress: Markdown output cleaned up. Folder looks like: $(readdir(doc.user.build))" end + if settings.redirect_trailing_slash + @info "DocumenterVitepress: inserting javascript 200 redirects from https://example.com/page/ to https://example.com/page because `redirect_trailing_slash` is true." + for (root, dirs, files) in walkdir(builddir) + for file in files + name, ext = splitext(file) + if ext === ".html" && name ∉ ("404", "index") + dir = joinpath(root, name) + if !isdir(dir) + mkdir(dir) + println(((settings.deploy_url, root, builddir, name))) + url = "https://"*normpath(joinpath(settings.deploy_url, relpath(root, builddir), name)) + println(url) + open(joinpath(dir, "index.html"), "w") do io + write(io, """ + + + Redirecting to ..$name + + """) + # The script is equivalent to + # `` + # but keeps fragments. If Javascript fails for whatever + # reason, the meta http-equiv will proc, dropping fragments + # If that, too fails, there's an ordinary, human readable + # relative link. + # + # This uses a relative canonical link which is bad form, but + # oh well. We don't have access to the full URL until deploy + # time. + end + end + end + end + end + end else @info """ DocumenterVitepress: did not build Vitepress site because `build_vitepress` was set to `false`. @@ -339,7 +413,7 @@ function renderdoc(io::IO, mime::MIME"text/plain", node::Documenter.MarkdownAST. if url !== nothing # This is how Documenter does it: # push!(ret.nodes, a[".docs-sourcelink", :target=>"_blank", :href=>url]("source")) - # so clearly we should be inserting some form of HTML tag here, + # so clearly we should be inserting some form of HTML tag here, # and defining its rendering in CSS? # TODO: switch to Documenter style here println(io, "\n", "[source]($url)", "\n") @@ -407,9 +481,9 @@ function join_multiblock(node::Documenter.MarkdownAST.Node) for thing in code_blocks # reset the buffer and push the old code block if thing.language != current_language - # Remove this if statement if you want to + # Remove this if statement if you want to # include empty code blocks in the output. - if isempty(thing.code) + if isempty(thing.code) current_string *= "\n\n" continue end @@ -768,7 +842,7 @@ function render(io::IO, mime::MIME"text/plain", node::Documenter.MarkdownAST.Nod # Main.@infiltrate print(io, "\$", math.math, "\$") end -# Display math +# Display math function render(io::IO, mime::MIME"text/plain", node::Documenter.MarkdownAST.Node, math::MarkdownAST.DisplayMath, page, doc; kwargs...) # Main.@infiltrate println(io) @@ -813,7 +887,7 @@ function render(io::IO, mime::MIME"text/plain", node::Documenter.MarkdownAST.Nod end end # We create this IOBuffer in order to render to it. - iob = IOBuffer() + iob = IOBuffer() # This will eventually hold the rendered table cells as Strings. cell_strings = Vector{Vector{String}}() current_row_vec = String[] diff --git a/test/runtests.jl b/test/runtests.jl new file mode 100644 index 00000000..2a3ee55f --- /dev/null +++ b/test/runtests.jl @@ -0,0 +1,5 @@ +using Test, DocumenterVitepress + +@test DocumenterVitepress.MarkdownVitepress(; repo = "...", devbranch = "...", devurl = "...") isa DocumenterVitepress.MarkdownVitepress +@test_throws ArgumentError DocumenterVitepress.MarkdownVitepress(; repo = "...", devbranch = "...", devurl = "...", + build_vitepress = false, redirect_trailing_slash = true)