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

Allow using pretty URLs #485

Merged
merged 4 commits into from
May 21, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Please open an [issue][issues-url] if you encounter any problems. If you have a

[gitter-url]: https://gitter.im/juliadocs/users

[contrib-url]: https://juliadocs.github.io/Documenter.jl/latest/man/contributing.html
[contrib-url]: https://juliadocs.github.io/Documenter.jl/latest/man/contributing/

[docs-latest-img]: https://img.shields.io/badge/docs-latest-blue.svg
[docs-latest-url]: https://juliadocs.github.io/Documenter.jl/latest
Expand Down
3 changes: 2 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ makedocs(
"lib/internals/writers.md",
])
]
]
],
html_prettyurls = true,
)

deploydocs(
Expand Down
2 changes: 0 additions & 2 deletions src/CrossReferences.jl
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ function namedxref(link::Markdown.Link, slug, meta, page, doc)
# Replace the `@ref` url with a path to the referenced header.
anchor = get(Anchors.anchor(headers, slug))
path = relpath(anchor.file, dirname(page.build))
path = Formats.extension(doc.user.format[1], path) # TODO: handle multiple formats.
link.url = string(path, '#', slug, '-', anchor.nth)
else
push!(doc.internal.errors, :cross_references)
Expand Down Expand Up @@ -156,7 +155,6 @@ function docsxref(link::Markdown.Link, code, meta, page, doc)
# Replace the `@ref` url with a path to the referenced docs.
docsnode = doc.internal.objects[object]
path = relpath(docsnode.page.build, dirname(page.build))
path = Formats.extension(doc.user.format[1], path) # TODO: handle multiple formats.
slug = Utilities.slugify(object)
link.url = string(path, '#', slug)
else
Expand Down
3 changes: 3 additions & 0 deletions src/Documents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ immutable User
authors :: Compat.String
analytics::Compat.String
version :: Compat.String # version string used in the version selector by default
html_prettyurls :: Bool # Use pretty URLs in the HTML build?
end

"""
Expand Down Expand Up @@ -248,6 +249,7 @@ function Document(;
authors :: AbstractString = "",
analytics :: AbstractString = "",
version :: AbstractString = "",
html_prettyurls :: Bool = false,
others...
)
Utilities.check_kwargs(others)
Expand Down Expand Up @@ -278,6 +280,7 @@ function Document(;
authors,
analytics,
version,
html_prettyurls,
)
internal = Internal(
Utilities.assetsdir(),
Expand Down
193 changes: 157 additions & 36 deletions src/Writers/HTMLWriter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ A module for rendering `Document` objects to HTML.

[`HTMLWriter`](@ref) uses the following additional keyword arguments that can be passed to
[`Documenter.makedocs`](@ref): `assets`, `sitename`, `analytics`, `authors`, `pages`,
`version`.
`version`, `html_prettyurls`.

**`version`** specifies the version string of the current version which will be the
selected option in the version selector. If this is left empty (default) the version
Expand Down Expand Up @@ -80,9 +80,10 @@ type HTMLContext
search_js :: Compat.String
search_index :: IOBuffer
search_index_js :: Compat.String
search_navnode :: Documents.NavNode
local_assets :: Vector{Compat.String}
end
HTMLContext(doc) = HTMLContext(doc, "", [], "", "", IOBuffer(), "", [])
HTMLContext(doc) = HTMLContext(doc, "", [], "", "", IOBuffer(), "", Documents.NavNode("search", "Search", nothing), [])

"""
Returns a page (as a [`Documents.Page`](@ref) object) using the [`HTMLContext`](@ref).
Expand Down Expand Up @@ -174,14 +175,16 @@ function render_page(ctx, navnode)
body(navmenu, article)
)
)
open(Formats.extension(:html, page.build), "w") do io

open_output(ctx, navnode) do io
print(io, htmldoc)
end
end

function render_head(ctx, navnode)
@tags head meta link script title
src = get(navnode.page)
src = get_url(ctx, navnode)

page_title = "$(mdflatten(pagetitle(ctx, navnode))) · $(ctx.doc.user.sitename)"
css_links = [
normalize_css,
Expand Down Expand Up @@ -246,10 +249,11 @@ analytics_script(tracking_id::AbstractString) =

function render_search(ctx)
@tags article body h1 header hr html li nav p span ul script
navnode = Documents.NavNode("search", "Search", nothing)

head = render_head(ctx, navnode)
navmenu = render_navmenu(ctx, navnode)
src = get_url(ctx, ctx.search_navnode)

head = render_head(ctx, ctx.search_navnode)
navmenu = render_navmenu(ctx, ctx.search_navnode)
article = article(
header(
nav(ul(li("Search"))),
Expand All @@ -264,11 +268,11 @@ function render_search(ctx)
html[:lang=>"en"](
head,
body(navmenu, article),
script[:src => ctx.search_index_js],
script[:src => ctx.search_js],
script[:src => relhref(src, ctx.search_index_js)],
script[:src => relhref(src, ctx.search_js)],
)
)
open(Formats.extension(:html, joinpath(ctx.doc.user.build, "search")), "w") do io
open_output(ctx, ctx.search_navnode) do io
print(io, htmldoc)
end
end
Expand All @@ -278,7 +282,9 @@ end

function render_navmenu(ctx, navnode)
@tags a form h1 img input nav div select option
src = get(navnode.page)

src = get_url(ctx, navnode)

navmenu = nav[".toc"]
if !isempty(ctx.logo)
push!(navmenu.nodes,
Expand Down Expand Up @@ -306,7 +312,7 @@ function render_navmenu(ctx, navnode)
push!(navmenu.nodes, version_selector)
end
push!(navmenu.nodes,
form[".search", :action => relhref(src, "search.html")](
form[".search", :action => navhref(ctx, ctx.search_navnode, navnode)](
input[
"#search-query",
:name => "q",
Expand Down Expand Up @@ -348,7 +354,7 @@ function navitem(ctx, current, nn::Documents.NavNode)
link = if isnull(nn.page)
span[".toctext"](title)
else
a[".toctext", :href => navhref(nn, current)](title)
a[".toctext", :href => navhref(ctx, nn, current)](title)
end
item = (nn === current) ? li[".current"](link) : li(link)

Expand Down Expand Up @@ -378,7 +384,7 @@ function render_article(ctx, navnode)

header_links = map(Documents.navpath(navnode)) do nn
title = mdconvert(pagetitle(ctx, nn))
isnull(nn.page) ? li(title) : li(a[:href => navhref(nn, navnode)](title))
isnull(nn.page) ? li(title) : li(a[:href => navhref(ctx, nn, navnode)](title))
end

topnav = nav(ul(header_links))
Expand All @@ -405,14 +411,14 @@ function render_article(ctx, navnode)
Utilities.unwrap(navnode.prev) do nn
direction = span[".direction"]("Previous")
title = span[".title"](mdconvert(pagetitle(ctx, nn)))
link = a[".previous", :href => navhref(nn, navnode)](direction, title)
link = a[".previous", :href => navhref(ctx, nn, navnode)](direction, title)
push!(art_footer.nodes, link)
end

Utilities.unwrap(navnode.next) do nn
direction = span[".direction"]("Next")
title = span[".title"](mdconvert(pagetitle(ctx, nn)))
link = a[".next", :href => navhref(nn, navnode)](direction, title)
link = a[".next", :href => navhref(ctx, nn, navnode)](direction, title)
push!(art_footer.nodes, link)
end

Expand Down Expand Up @@ -520,7 +526,10 @@ function jsonescape(s)
replace(s, '"', "\\\"")
end

domify(ctx, navnode, node) = mdconvert(node, Base.Markdown.MD())
function domify(ctx, navnode, node)
fixlinks!(ctx, navnode, node)
mdconvert(node, Base.Markdown.MD())
end

function domify(ctx, navnode, anchor::Anchors.Anchor)
@tags a
Expand Down Expand Up @@ -561,9 +570,12 @@ end

function domify(ctx, navnode, contents::Documents.ContentsNode)
@tags a
navnode_dir = dirname(get(navnode.page))
navnode_url = get_url(ctx, navnode)
lb = ListBuilder()
for (count, path, anchor) in contents.elements
path = Formats.extension(:html, path)
path = joinpath(navnode_dir, path) # links in ContentsNodes are relative to current page
path = pretty_url(ctx, relhref(navnode_url, get_url(ctx, path)))
header = anchor.object
url = string(path, '#', anchor.id, '-', anchor.nth)
node = a[:href=>url](mdconvert(header.text))
Expand All @@ -575,10 +587,13 @@ end

function domify(ctx, navnode, index::Documents.IndexNode)
@tags a code li ul
navnode_dir = dirname(get(navnode.page))
navnode_url = get_url(ctx, navnode)
lis = map(index.elements) do el
object, doc, page, mod, cat = el
page = Formats.extension(:html, page)
url = string(page, "#", Utilities.slugify(object))
object, doc, path, mod, cat = el
path = joinpath(navnode_dir, path) # links in IndexNodes are relative to current page
path = pretty_url(ctx, relhref(navnode_url, get_url(ctx, path)))
url = string(path, "#", Utilities.slugify(object))
li(a[:href=>url](code("$(object.binding)")))
end
ul(lis)
Expand Down Expand Up @@ -648,11 +663,21 @@ end
# Utilities
# ------------------------------------------------------------------------------

"""
Opens the output file of the `navnode` in write node. If necessary, the path to the output
file is created before opening the file.
"""
function open_output(f, ctx, navnode)
path = joinpath(ctx.doc.user.build, get_url(ctx, navnode))
isdir(dirname(path)) || mkpath(dirname(path))
open(f, path, "w")
end

"""
Get the relative hyperlink between two [`Documents.NavNode`](@ref)s. Assumes that both
[`Documents.NavNode`](@ref)s have an associated [`Documents.Page`](@ref) (i.e. `.page` is not null).
"""
navhref(to, from) = Formats.extension(:html, relhref(get(from.page), get(to.page)))
navhref(ctx, to, from) = pretty_url(ctx, relhref(get_url(ctx, from), get_url(ctx, to)))

"""
Calculates a relative HTML link from one path to another.
Expand All @@ -664,6 +689,42 @@ function relhref(from, to)
replace(relpath(to, isempty(pagedir) ? "." : pagedir), r"[/\\]+", "/")
end

"""
Returns the full path corresponding to a path of a `.md` page file. The the input and output
paths are assumed to be relative to `src/`.
"""
function get_url(ctx, path)
if ctx.doc.user.html_prettyurls
d = if basename(path) == "index.md"
dirname(path)
else
first(splitext(path))
end
isempty(d) ? "index.html" : "$d/index.html"
else
Formats.extension(:html, path)
end
end

"""
Returns the full path of a [`Documents.NavNode`](@ref) relative to `src/`.
"""
get_url(ctx, navnode::Documents.NavNode) = get_url(ctx, get(navnode.page))

"""
If `html_prettyurls` is enabled, returns a "pretty" version of the `path` which can then be
used in links in the resulting HTML file.
"""
function pretty_url(ctx, path)
if ctx.doc.user.html_prettyurls
dir, file = splitdir(path)
if file == "index.html"
return length(dir) == 0 ? "" : "$(dir)/"
end
end
return path
end

"""
Tries to guess the page title by looking at the `<h1>` headers and returns the
header contents of the first `<h1>` on a page as a `Nullable` (nulled if the algorithm
Expand Down Expand Up @@ -777,20 +838,7 @@ mdconvert(m::Markdown.LaTeX, parent) = Tag(:span)(string('$', m.formula, '$'))

mdconvert(::Markdown.LineBreak, parent) = Tag(:br)()

function mdconvert(link::Markdown.Link, parent)
# TODO: fixing the extension should probably be moved to an earlier step
if Utilities.isabsurl(link.url)
Tag(:a)[:href => link.url](mdconvert(link.text, link))
else
s = split(link.url, "#", limit = 2)
path = first(s)
path = endswith(path, ".md") ? Formats.extension(:html, path) : path
# Replace any backslashes in links, if building the docs on Windows
path = replace(path, '\\', '/')
url = (length(s) > 1) ? "$path#$(last(s))" : Compat.String(path)
Tag(:a)[:href => url](mdconvert(link.text, link))
end
end
mdconvert(link::Markdown.Link, parent) = Tag(:a)[:href => link.url](mdconvert(link.text, link))

mdconvert(list::Markdown.List, parent) = (isordered(list) ? Tag(:ol) : Tag(:ul))(map(Tag(:li), mdconvert(list.items, list)))

Expand Down Expand Up @@ -833,4 +881,77 @@ end

mdconvert(html::Documents.RawHTML, parent) = Tag(Symbol("#RAW#"))(html.code)


# fixlinks!
# ------------------------------------------------------------------------------

"""
Replaces URLs in `Markdown.Link` elements (if they point to a local `.md` page) with the
actual URLs.
"""
function fixlinks!(ctx, navnode, link::Markdown.Link)
fixlinks!(ctx, navnode, link.text)
Utilities.isabsurl(link.url) && return

s = split(link.url, "#", limit = 2)
if is_windows() && ':' in first(s)
Utilities.warn("Invalid local link: colons not allowed in paths on Windows\n '$(link.url)' in $(get(navnode.page))")
return
end
path = normpath(joinpath(dirname(get(navnode.page)), first(s)))

if endswith(path, ".md") && path in keys(ctx.doc.internal.pages)
# make sure that links to different valid pages are correct
path = pretty_url(ctx, relhref(get_url(ctx, navnode), get_url(ctx, path)))
elseif isfile(joinpath(ctx.doc.user.build, path))
# update links to other files that are present in build/ (e.g. either user
# provided files or generated by code examples)
path = relhref(get_url(ctx, navnode), path)
else
Utilities.warn("Invalid local link: unresolved path\n '$(link.url)' in $(get(navnode.page))")
end

# Replace any backslashes in links, if building the docs on Windows
path = replace(path, '\\', '/')
link.url = (length(s) > 1) ? "$path#$(last(s))" : String(path)
end

function fixlinks!(ctx, navnode, img::Markdown.Image)
Utilities.isabsurl(img.url) && return

if is_windows() && ':' in img.url
Utilities.warn("Invalid local image: colons not allowed in paths on Windows\n '$(img.url)' in $(get(navnode.page))")
return
end

path = joinpath(dirname(get(navnode.page)), img.url)
if isfile(joinpath(ctx.doc.user.build, path))
path = relhref(get_url(ctx, navnode), path)
# Replace any backslashes in links, if building the docs on Windows
img.url = replace(path, '\\', '/')
else
Utilities.warn("Invalid local image: unresolved path\n '$(img.url)' in `$(get(navnode.page))`")
end
end

fixlinks!(ctx, navnode, md::Markdown.MD) = fixlinks!(ctx, navnode, md.content)
function fixlinks!(ctx, navnode, a::Markdown.Admonition)
fixlinks!(ctx, navnode, a.title)
fixlinks!(ctx, navnode, a.content)
end
fixlinks!(ctx, navnode, b::Markdown.BlockQuote) = fixlinks!(ctx, navnode, b.content)
fixlinks!(ctx, navnode, b::Markdown.Bold) = fixlinks!(ctx, navnode, b.text)
fixlinks!(ctx, navnode, f::Markdown.Footnote) = fixlinks!(ctx, navnode, f.text)
fixlinks!(ctx, navnode, h::Markdown.Header) = fixlinks!(ctx, navnode, h.text)
fixlinks!(ctx, navnode, i::Markdown.Italic) = fixlinks!(ctx, navnode, i.text)
fixlinks!(ctx, navnode, list::Markdown.List) = fixlinks!(ctx, navnode, list.items)
fixlinks!(ctx, navnode, p::Markdown.Paragraph) = fixlinks!(ctx, navnode, p.content)
fixlinks!(ctx, navnode, t::Markdown.Table) = fixlinks!(ctx, navnode, t.rows)

fixlinks!(ctx, navnode, mds::Vector) = map(md -> fixlinks!(ctx, navnode, md), mds)
fixlinks!(ctx, navnode, md) = nothing

# TODO: do some regex-magic in raw HTML blocks? Currently ignored.
#fixlinks!(ctx, navnode, md::Documents.RawHTML) = ...

end
Loading