/
Anchors.jl
127 lines (103 loc) · 3.2 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
"""
Defines the [`Anchor`](@ref) and [`AnchorMap`](@ref) types.
`Anchor`s and `AnchorMap`s are used to represent links between objects within a document.
"""
module Anchors
using DocStringExtensions
# 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
Anchor(object) = new(object, 0, "", "", 1)
end
"""
Tree structure representating anchors in a document and their relationships with eachother.
**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 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
add!(m::AnchorMap, object, id, file) = add!(m, Anchor(object), id, file)
# Anchor existance.
# -----------------
"""
$(SIGNATURES)
Does the given `id` exist within the [`AnchorMap`](@ref)? A `file` and integer `n` may also
be provided to narrow the search for existance.
"""
exists(m::AnchorMap, id, file, n) = exists(m, id, file) && 1 ≤ n ≤ length(m.map[id][file])
exists(m::AnchorMap, id, file) = exists(m, id) && haskey(m.map[id], file)
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 isunique(m::AnchorMap, id)
exists(m, id) &&
length(m.map[id]) === 1 &&
isunique(m, id, first(first(m.map[id])))
end
function isunique(m::AnchorMap, id, file)
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)
isunique(m, id) ?
anchor(m, id, first(first(m.map[id])), 1) :
nothing
end
function anchor(m::AnchorMap, id, file)
isunique(m, id, file) ?
anchor(m, id, file, 1) :
nothing
end
function anchor(m::AnchorMap, id, file, n)
exists(m, id, file, n) ?
m.map[id][file][n] :
nothing
end
end