-
Notifications
You must be signed in to change notification settings - Fork 479
/
anchors.jl
137 lines (114 loc) · 3.71 KB
/
anchors.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# Defines the [`Anchor`](@ref) and [`AnchorMap`](@ref) types.
#
# `Anchor`s and `AnchorMap`s are used to represent links between objects within a document.
# Types.
# ------
"""
Stores an arbitrary object called `.object` and it's location within a document.
**Fields**
- `object` -- the stored object.
- `order` -- ordering of `object` within the entire document.
- `file` -- the destination file, in `build`, where the object will be written to.
- `id` -- the generated "slug" identifying the object.
- `nth` -- integer that unique-ifies anchors with the same `id`.
"""
mutable struct Anchor
object :: Any
order :: Int
file :: String
id :: String
nth :: Int
# Reverse-lookup of .object for MarkdownAST trees. This is intentionally
# uninitialized until set in Documenter.markdownast()
node :: MarkdownAST.Node{Nothing}
Anchor(object) = new(object, 0, "", "", 1)
end
"""
Tree structure representing anchors in a document and their relationships with each other.
**Object Hierarchy**
id -> file -> anchors
Each `id` maps to a `file` which in turn maps to a vector of `Anchor` objects.
"""
mutable struct AnchorMap
map :: Dict{String, Dict{String, Vector{Anchor}}}
count :: Int
AnchorMap() = new(Dict(), 0)
end
# Add anchor.
# -----------
"""
$(SIGNATURES)
Adds a new [`Anchor`](@ref) to the [`AnchorMap`](@ref) for a given `id` and `file`.
Either an actual [`Anchor`](@ref) object may be provided or any other object which is
automatically wrapped in an [`Anchor`](@ref) before being added to the [`AnchorMap`](@ref).
"""
function anchor_add!(m::AnchorMap, anchor::Anchor, id, file)
filemap = get!(m.map, id, Dict{String, Vector{Anchor}}())
anchors = get!(filemap, file, Anchor[])
push!(anchors, anchor)
anchor.order = m.count += 1
anchor.file = file
anchor.id = id
anchor.nth = length(anchors)
anchor
end
anchor_add!(m::AnchorMap, object, id, file) = anchor_add!(m, Anchor(object), id, file)
# Anchor existence.
# -----------------
"""
$(SIGNATURES)
Does the given `id` exist within the [`AnchorMap`](@ref)? A `file` and integer `n` may also
be provided to narrow the search for existence.
"""
anchor_exists(m::AnchorMap, id, file, n) = anchor_exists(m, id, file) && 1 ≤ n ≤ length(m.map[id][file])
anchor_exists(m::AnchorMap, id, file) = anchor_exists(m, id) && haskey(m.map[id], file)
anchor_exists(m::AnchorMap, id) = haskey(m.map, id)
# Anchor uniqueness.
# ------------------
"""
$(SIGNATURES)
Is the `id` unique within the given [`AnchorMap`](@ref)? May also specify the `file`.
"""
function anchor_isunique(m::AnchorMap, id)
anchor_exists(m, id) &&
length(m.map[id]) === 1 &&
anchor_isunique(m, id, first(first(m.map[id])))
end
function anchor_isunique(m::AnchorMap, id, file)
anchor_exists(m, id, file) &&
length(m.map[id][file]) === 1
end
# Get anchor.
# -----------
"""
$(SIGNATURES)
Returns the [`Anchor`](@ref) object matching `id`. `file` and `n` may also be provided. An
`Anchor` is returned, or `nothing` in case of no match.
"""
function anchor(m::AnchorMap, id)
anchor_isunique(m, id) ?
anchor(m, id, first(first(m.map[id])), 1) :
nothing
end
function anchor(m::AnchorMap, id, file)
anchor_isunique(m, id, file) ?
anchor(m, id, file, 1) :
nothing
end
function anchor(m::AnchorMap, id, file, n)
anchor_exists(m, id, file, n) ?
m.map[id][file][n] :
nothing
end
"""
Create a label from an anchor.
"""
anchor_label(a::Anchor) = (a.nth == 1) ? a.id : string(a.id, "-", a.nth)
"""
Create an HTML fragment from an anchor.
"""
function anchor_fragment(a::Anchor)
frag = string("#", anchor_label(a))
# TODO: Sanitize the fragment
return frag
end