From 297c30d4097d32ed5525fca32e732f72984ef7c1 Mon Sep 17 00:00:00 2001 From: Fredrik Ekre Date: Tue, 21 Aug 2018 09:26:39 +0200 Subject: [PATCH] Add selector argument to deploydocs. The selector argument decide the content, and order, of the version selector. The argument is a vector, defaulting to ["stable" => "v^", "v#.#", devurl => devurl]. Arguments to the selector can be: - "v^" for the latest release. - "v#" expands to latest doc per major release. - "v#.#" expands to latest doc per minor release. - "v#.#.#" expands to latest doc per patch release. - A pair p, which put p.first in the selector, and symlink it to p.second. --- src/Documenter.jl | 60 +++++++++++++++++---------- src/Writers/HTMLWriter.jl | 86 +++++++++++++++++++++++++++++++++------ test/htmlwriter.jl | 71 ++++++++++++++++++++++++-------- 3 files changed, 166 insertions(+), 51 deletions(-) diff --git a/src/Documenter.jl b/src/Documenter.jl index c8717c6ebc0..70e78915169 100644 --- a/src/Documenter.jl +++ b/src/Documenter.jl @@ -249,6 +249,7 @@ hide(root::AbstractString, children) = (true, nothing, root, map(hide, children) make = , devbranch = "master", devurl = "dev", + selector = ["stable" => "v^", "v#.#", devurl => devurl] ) Converts markdown files generated by [`makedocs`](@ref) to HTML and pushes them to `repo`. @@ -316,6 +317,18 @@ documentation. By default this value is set to `"master"`. **`devurl`** the folder that in-development version of the docs will be deployed. Defaults to `"dev"`. +**`selector`** determines content and order of the resulting version selector in +the generated html. The following entries are valied in the `selector` vector: + - `"v#"`: includes links to the latest documentation for each major release cycle + (i.e. `v1.1`, `v2.0`). + - `"v#.#"`: includes links to the latest documentation for each minor release cycle + (i.e. `v1.0`, `v1.1`, `v2.0`). + - `"v#.#.#"`: includes links to all released versions. + - `"v^"`: includes a link to the maximum version. + - A pair, e.g. `"first" => "second"`, which will put `"first"` in the selector, + with the same content as `"second"`. The second argument can be `"v^"`, to + point to the maximum version docs (as in e.g. `"stable" => "v^"`). + # See Also The [Hosting Documentation](@ref) section of the manual provides a step-by-step guide to @@ -339,6 +352,7 @@ function deploydocs(; devbranch = "master", devurl = "dev", + selector = ["stable" => "v^", "v#.#", devurl => devurl] ) # deprecation of latest kwarg (renamed to devbranch) if latest !== nothing @@ -443,7 +457,7 @@ function deploydocs(; root, temp, repo; branch=branch, dirname=dirname, target=target, tag=travis_tag, key=documenter_key, sha=sha, - devurl = devurl, + devurl = devurl, selector = selector, ) end end @@ -462,19 +476,15 @@ end Handles pushing changes to the remote documentation branch. When `tag` is empty the docs are deployed to the `devurl` directory, -and when building docs for a tag they are deployed to a `vX.Y.Z` directory, -and also to the `stable` directory. +and when building docs for a tag they are deployed to a `vX.Y.Z` directory. """ function git_push( root, temp, repo; - branch="gh-pages", dirname="", target="site", tag="", key="", sha="", devurl="dev" + branch="gh-pages", dirname="", target="site", tag="", key="", sha="", devurl="dev", + selector ) dirname = isempty(dirname) ? temp : joinpath(temp, dirname) isdir(dirname) || mkpath(dirname) - # Versioned docs directories. - devurl_dir = joinpath(dirname, devurl) - stable_dir = joinpath(dirname, "stable") - tagged_dir = joinpath(dirname, tag) keyfile = abspath(joinpath(root, ".documenter")) target_dir = abspath(target) @@ -516,6 +526,7 @@ function git_push( # Copy docs to `devurl`, or `stable`, ``, and `` directories. if isempty(tag) + devurl_dir = joinpath(dirname, devurl) gitrm_copy(target_dir, devurl_dir) Writers.HTMLWriter.generate_siteinfo_file(devurl_dir, devurl) # symlink "latest" to devurl to preserve links (remove in some future release) @@ -524,25 +535,25 @@ function git_push( @warn(string("creating symlink from `latest` to `$(devurl)` for backwards ", "compatibility with old links. In future Documenter versions this symlink ", "will not be created. Please update any links that point to `latest`.")) - cd(dirname) do; symlink(devurl, "latest"); end + cd(dirname) do; rm_and_add_symlink(devurl, "latest"); end end else - @assert occursin(Base.VERSION_REGEX, tag) # checked in deploydocs - version = VersionNumber(tag) - # only push to stable if this is the latest stable release - versions = filter!(x -> occursin(Base.VERSION_REGEX, x), readdir(dirname)) - maxver = mapreduce(x -> VersionNumber(x), max, versions; init=v"0.0.0") - if version >= maxver && version.prerelease == () # don't deploy to stable for prereleases - gitrm_copy(target_dir, stable_dir) - Writers.HTMLWriter.generate_siteinfo_file(stable_dir, "stable") - end + tagged_dir = joinpath(dirname, tag) gitrm_copy(target_dir, tagged_dir) Writers.HTMLWriter.generate_siteinfo_file(tagged_dir, tag) end - # Create the versions.js file containing a list of all docs - # versions. This must always happen after the folder copying. - Writers.HTMLWriter.generate_version_file(dirname) + # Expand the users `selector` vector + entries, symlinks = Writers.HTMLWriter.expand_selector(dirname, selector) + + # Create the versions.js file containing a list of `entries`. + # This must always happen after the folder copying. + Writers.HTMLWriter.generate_version_file(joinpath(dir, "versions.js"), entries) + + # generate the symlinks + cd(dirname) do + foreach(kv -> rm_and_add_symlink(kv.second, kv.first), symlinks) + end # Add, commit, and push the docs to the remote. run(`git add -A .`) @@ -560,6 +571,13 @@ function git_push( end end +function rm_and_add_symlink(target, link) + # If `link` is an old symlink, remove it, + # if `link` is something else we let symlink throw. + islink(link) && rm(link) + symlink(target, link) +end + """ gitrm_copy(src, dst) diff --git a/src/Writers/HTMLWriter.jl b/src/Writers/HTMLWriter.jl index cb61afbc2d1..a27283af6eb 100644 --- a/src/Writers/HTMLWriter.jl +++ b/src/Writers/HTMLWriter.jl @@ -477,23 +477,83 @@ function render_topbar(ctx, navnode) return div["#topbar"](span(page_title), a[".fa .fa-bars", :href => "#"]) end -function generate_version_file(dir::AbstractString) - all_folders = readdir(dir) - folders = [] - - for each in all_folders - occursin(Base.VERSION_REGEX, each) && push!(folders, each) +# expand the selector argument from the user +# and return entries and needed symlinks +function expand_selector(dir, selector) + # output: entries and symlinks + selector_entries = String[] + selector_symlinks = Pair{String,String}[] + + # read folders and filter out symlinks + available_folders = readdir(dir) + cd(() -> filter!(!islink, available_folders), dir) + + # filter and sort release folders + vnum(x) = VersionNumber(x) + version_folders = [x for x in available_folders if occursin(Base.VERSION_REGEX, x)] + sort!(version_folders, lt = (x, y) -> vnum(x) < vnum(y), rev = true) + release_folders = filter(x -> (v = vnum(x); v.prerelease == () && v.build == ()), version_folders) + # pre_release_folders = filter(x -> (v = vnum(x); v.prerelease != () || v.build != ()), version_folders) + major_folders = unique(x -> vnum(x).major, release_folders) + minor_folders = unique(x -> (v = vnum(x); (v.major, v.minor)), release_folders) + patch_folders = unique(x -> (v = vnum(x); (v.major, v.minor, v.patch)), release_folders) + + # populate output + for entry in selector + if entry == "v#" # one doc per major release + for x in major_folders + vstr = "v$(vnum(x).major).$(vnum(x).minor)" + push!(selector_entries, vstr) + push!(selector_symlinks, vstr => x) + end + elseif entry == "v#.#" # one doc per minor release + for x in minor_folders + vstr = "v$(vnum(x).major).$(vnum(x).minor)" + push!(selector_entries, vstr) + push!(selector_symlinks, vstr => x) + end + elseif entry == "v#.#.#" # one doc per patch release + for x in patch_folders + vstr = "v$(vnum(x).major).$(vnum(x).minor).$(vnum(x).patch)" + push!(selector_entries, vstr) + push!(selector_symlinks, vstr => x) + end + elseif entry == "v^" || (entry isa Pair && entry.second == "v^") + if !isempty(release_folders) + x = first(release_folders) + vstr = isa(entry, Pair) ? entry.first : "v$(vnum(x).major).$(vnum(x).minor)" + push!(selector_entries, vstr) + push!(selector_symlinks, vstr => x) + end + elseif entry isa Pair + k, v = entry + i = findfirst(==(v), available_folders) + if i === nothing + @info("no match for entry `$(repr(entry))` in selector.") + else + push!(selector_entries, k) + push!(selector_symlinks, k => v) + end + else + @info("no match for entry `$(repr(entry))` in selector.") + end end - # sort tags by version number - sort!(folders, lt = (x, y) -> VersionNumber(x) < VersionNumber(y), rev = true) + unique!(selector_entries) # remove any duplicates - # include stable first, then dev - "dev" in all_folders && pushfirst!(folders, "dev") - "stable" in all_folders && pushfirst!(folders, "stable") + # generate remaining symlinks + foreach(x -> push!(selector_symlinks, "v$(vnum(x).major)" => x), major_folders) + foreach(x -> push!(selector_symlinks, "v$(vnum(x).major).$(vnum(x).minor)" => x), minor_folders) + foreach(x -> push!(selector_symlinks, "v$(vnum(x).major).$(vnum(x).minor).$(vnum(x).patch)" => x), patch_folders) + filter!(x -> x.first != x.second, unique!(selector_symlinks)) + + return selector_entries, selector_symlinks +end - open(joinpath(dir, "versions.js"), "w") do buf +# write version file +function generate_version_file(versionfile::AbstractString, selector_entries) + open(versionfile, "w") do buf println(buf, "var DOC_VERSIONS = [") - for folder in folders + for folder in selector_entries println(buf, " \"", folder, "\",") end println(buf, "];") diff --git a/test/htmlwriter.jl b/test/htmlwriter.jl index 4dea308d3cb..78e0114ac84 100644 --- a/test/htmlwriter.jl +++ b/test/htmlwriter.jl @@ -2,7 +2,18 @@ module HTMLWriterTests using Test -import Documenter.Writers.HTMLWriter: jsescape, generate_version_file +import Documenter.Writers.HTMLWriter: jsescape, generate_version_file, expand_selector + +function verify_version_file(versionfile, entries) + @test isfile(versionfile) + content = read(versionfile, String) + idx = 1 + for entry in entries + i = findnext(entry, content, idx) + @test i !== nothing + idx = last(i) + end +end @testset "HTMLWriter" begin @test jsescape("abc123") == "abc123" @@ -21,28 +32,54 @@ import Documenter.Writers.HTMLWriter: jsescape, generate_version_file @test jsescape("policy to
 delete.") == "policy to\\u2028 delete." mktempdir() do tmpdir - versions = ["stable", "dev", "v0.2.6", "v0.1.1", "v0.1.0"] + versionfile = joinpath(tmpdir, "versions.js") + versions = ["stable", "dev", + "2.1.1", "v2.1.0", "v2.0.1", "v2.0.0", + "1.1.1", "v1.1.0", "v1.0.1", "v1.0.0", + "0.1.1", "v0.1.0"] # note no `v` on first ones cd(tmpdir) do - mkdir("foobar") for version in versions mkdir(version) end end - generate_version_file(tmpdir) - - versions_file = joinpath(tmpdir, "versions.js") - @test isfile(versions_file) - contents = String(read(versions_file)) - @test !occursin("foobar", contents) # only specific directories end up in the versions file - # let's make sure they're in the right order -- they should be sorted in the output file - last = 0:0 - for version in versions - this = findfirst(version, contents) - @test this !== nothing - @test first(last) < first(this) - last = this - end + # expanding selector + selector = ["stable" => "v^", "v#.#", "dev" => "dev"] # default to makedocs + entries, symlinks = expand_selector(tmpdir, selector) + @test entries == ["stable", "v2.1", "v2.0", "v1.1", "v1.0", "v0.1", "dev"] + @test symlinks == ["stable"=>"2.1.1", "v2.1"=>"2.1.1", "v2.0"=>"v2.0.1", + "v1.1"=>"1.1.1", "v1.0"=>"v1.0.1", "v0.1"=>"0.1.1", + "v2"=>"2.1.1", "v1"=>"1.1.1", "v0"=>"0.1.1", "v2.1.1"=>"2.1.1", + "v1.1.1"=>"1.1.1", "v0.1.1"=>"0.1.1"] + generate_version_file(versionfile, entries) + verify_version_file(versionfile, entries) + + selector = ["v#"] + entries, symlinks = expand_selector(tmpdir, selector) + @test entries == ["v2.1", "v1.1", "v0.1"] + @test symlinks == ["v2.1"=>"2.1.1", "v1.1"=>"1.1.1", "v0.1"=>"0.1.1", "v2"=>"2.1.1", + "v1"=>"1.1.1", "v0"=>"0.1.1", "v2.0"=>"v2.0.1", "v1.0"=>"v1.0.1", + "v2.1.1"=>"2.1.1", "v1.1.1"=>"1.1.1", "v0.1.1"=>"0.1.1"] + generate_version_file(versionfile, entries) + verify_version_file(versionfile, entries) + + selector = ["v#.#.#"] + entries, symlinks = expand_selector(tmpdir, selector) + @test entries == ["v2.1.1", "v2.1.0", "v2.0.1", "v2.0.0", "v1.1.1", "v1.1.0", + "v1.0.1", "v1.0.0", "v0.1.1", "v0.1.0"] + @test symlinks == ["v2.1.1"=>"2.1.1", "v1.1.1"=>"1.1.1", "v0.1.1"=>"0.1.1", + "v2"=>"2.1.1", "v1"=>"1.1.1", "v0"=>"0.1.1", "v2.1"=>"2.1.1", + "v2.0"=>"v2.0.1", "v1.1"=>"1.1.1", "v1.0"=>"v1.0.1", "v0.1"=>"0.1.1"] + generate_version_file(versionfile, entries) + verify_version_file(versionfile, entries) + + selector = ["v^", "devel" => "dev", "foobar", "foo" => "bar"] + entries, symlinks = expand_selector(tmpdir, selector) + @test entries == ["v2.1", "devel"] + @test ("v2.1" => "2.1.1") in symlinks + @test ("devel" => "dev") in symlinks + generate_version_file(versionfile, entries) + verify_version_file(versionfile, entries) end end