Skip to content

Commit 05952cb

Browse files
committed
model external links in the ABI
1 parent 2c463bb commit 05952cb

File tree

7 files changed

+109
-26
lines changed

7 files changed

+109
-26
lines changed

Sources/MarkdownAST/InlineElements/Markdown.InlineHyperlink.Target.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,16 @@ extension Markdown.InlineHyperlink
55
@frozen public
66
enum Target:Sendable
77
{
8+
/// The link target has already been outlined.
89
case outlined (Int)
9-
10-
case safe (Markdown.SourceString)
11-
case unsafe (String)
10+
/// The link target is a fragment within the current document. The string includes a
11+
/// leading `#`.
12+
case fragment (String)
13+
/// The link target is an absolute path. The string includes a leading `/`.
14+
case absolute (Markdown.SourceString)
15+
/// The link target is a relative path. The string does not include a leading `./`.
16+
case relative (Markdown.SourceString)
17+
/// The link target is an external URL. The string includes the scheme.
18+
case external (Markdown.SourceString)
1219
}
1320
}

Sources/MarkdownAST/InlineElements/Markdown.InlineHyperlink.swift

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,52 @@ extension Markdown.InlineHyperlink
2626
target:String?,
2727
elements:[Markdown.InlineSpan])
2828
{
29-
guard let target:String
29+
guard
30+
let target:String,
31+
target.startIndex < target.endIndex
3032
else
3133
{
32-
self.init(target: nil as Target?, elements: elements)
34+
self.init(target: nil, elements: elements)
3335
return
3436
}
3537

36-
if let start:String.Index = target.index(target.startIndex,
37-
offsetBy: 2,
38-
limitedBy: target.endIndex),
39-
target[..<start] == "./"
38+
switch target[target.startIndex]
4039
{
41-
self.init(target: .safe(.init(
40+
case "/":
41+
self.init(target: .absolute(.init(
4242
source: source,
43-
string: String.init(target[start...]))),
43+
string: target)),
44+
elements: elements)
45+
46+
case "#":
47+
self.init(target: .fragment(target), elements: elements)
48+
49+
case ".":
50+
let trimmed:Markdown.SourceString
51+
let i:String.Index = target.index(after: target.startIndex)
52+
if i < target.endIndex, target[i] == "/"
53+
{
54+
let j:String.Index = target.index(after: i)
55+
if j == target.endIndex
56+
{
57+
self.init(target: nil, elements: elements)
58+
return
59+
}
60+
61+
trimmed = .init(source: source, string: String.init(target[j...]))
62+
}
63+
else
64+
{
65+
trimmed = .init(source: source, string: target)
66+
}
67+
68+
self.init(target: .relative(trimmed), elements: elements)
69+
70+
default:
71+
self.init(target: .external(.init(
72+
source: source,
73+
string: target)),
4474
elements: elements)
45-
}
46-
else
47-
{
48-
self.init(target: .unsafe(target), elements: elements)
4975
}
5076
}
5177
}
@@ -79,9 +105,12 @@ extension Markdown.InlineHyperlink:Markdown.TreeElement
79105
{
80106
switch target
81107
{
108+
// These will almost certainly be invalid, so there is no point in encoding them.
109+
case .absolute: break
110+
case .relative: break
111+
case .external(let url): $0[.external] = url.string
112+
case .fragment(let fragment): $0[.href] = fragment
82113
case .outlined(let reference): $0[.href] = reference
83-
case .safe(let link): $0[.href] = link.string
84-
case .unsafe(let url): $0[.external] = url
85114
}
86115
}
87116
content:
@@ -107,10 +136,18 @@ extension Markdown.InlineHyperlink:Markdown.TextElement
107136
@inlinable public mutating
108137
func outline(by register:(Markdown.AnyReference) throws -> Int?) rethrows
109138
{
110-
if case .safe(let link)? = self.target,
111-
let reference:Int = try register(.link(link))
139+
switch self.target
112140
{
113-
self.target = .outlined(reference)
141+
case nil, .outlined?, .fragment?:
142+
return
143+
144+
case .relative(let link)?,
145+
.absolute(let link)?,
146+
.external(let link)?:
147+
if let reference:Int = try register(.link(link))
148+
{
149+
self.target = .outlined(reference)
150+
}
114151
}
115152
}
116153
}

Sources/SymbolGraphLinker/Resolution/SSGC.Outliner.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,21 @@ extension SSGC.Outliner
106106
return self.outline(link: link, code: true)
107107

108108
case .link(let link):
109+
if let colon:String.Index = link.string.firstIndex(of: ":"),
110+
let start:String.Index = link.string.index(colon,
111+
offsetBy: 3,
112+
limitedBy: link.string.endIndex)
113+
{
114+
var url:Substring { link.string[start...] }
115+
116+
switch link.string[..<start]
117+
{
118+
case "https://": return self.outline(translating: link, to: url)
119+
case "http://": return self.outline(translating: link, to: url)
120+
default: break
121+
}
122+
}
123+
109124
return self.outline(link: link, code: false)
110125

111126
case .file(let link):
@@ -135,6 +150,15 @@ extension SSGC.Outliner
135150
return nil
136151
}
137152

153+
private mutating
154+
func outline(translating link:Markdown.SourceString, to url:Substring) -> Int?
155+
{
156+
self.cache.append(outline: .unresolved(.init(
157+
link: String.init(url),
158+
type: .web,
159+
location: link.source.start)))
160+
}
161+
138162
private mutating
139163
func outline(link:Markdown.SourceString, code:Bool) -> Int?
140164
{

Sources/SymbolGraphs/Articles/SymbolGraph.Outline.Unresolved.LinkType.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ extension SymbolGraph.Outline.Unresolved
33
@frozen public
44
enum LinkType:Equatable, Hashable, Sendable
55
{
6-
/// The associated text is a doclink.
6+
/// The associated text is an unresolved doclink.
77
case doc
8-
/// The associated text is a UCF expression.
8+
/// The associated text is an untranslated web URL. The string does **not** include a
9+
/// scheme.
10+
case web
11+
/// The associated text is an unresolved UCF expression.
912
case ucf
1013
/// The associated text is a legacy Unidoc codelink expression.
1114
case unidocV3

Sources/SymbolGraphs/Articles/SymbolGraph.Outline.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ extension SymbolGraph.Outline
1818
enum CodingKey:String, Sendable
1919
{
2020
case unresolved_doc = "D"
21+
case unresolved_web = "W"
2122
case unresolved_ucf = "U"
2223
case unresolved_unidocV3 = "C"
2324

@@ -49,6 +50,7 @@ extension SymbolGraph.Outline:BSONDocumentEncodable
4950
switch self.type
5051
{
5152
case .doc: bson[.unresolved_doc] = self.link
53+
case .web: bson[.unresolved_web] = self.link
5254
case .ucf: bson[.unresolved_ucf] = self.link
5355
case .unidocV3: bson[.unresolved_unidocV3] = self.link
5456
}
@@ -79,15 +81,22 @@ extension SymbolGraph.Outline:BSONDocumentDecodable
7981
let type:Unresolved.LinkType
8082
let link:String
8183

82-
if let text:String = try bson[.unresolved_doc]?.decode()
84+
// These are unscientifically ordered by likelihood.
85+
if let text:String = try bson[.unresolved_ucf]?.decode()
86+
{
87+
type = .ucf
88+
link = text
89+
}
90+
else if
91+
let text:String = try bson[.unresolved_doc]?.decode()
8392
{
8493
type = .doc
8594
link = text
8695
}
8796
else if
88-
let text:String = try bson[.unresolved_ucf]?.decode()
97+
let text:String = try bson[.unresolved_web]?.decode()
8998
{
90-
type = .ucf
99+
type = .web
91100
link = text
92101
}
93102
else

Sources/SymbolGraphs/SymbolGraphABI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import SemanticVersions
33
@frozen public
44
enum SymbolGraphABI
55
{
6-
@inlinable public static var version:PatchVersion { .v(0, 8, 19) }
6+
@inlinable public static var version:PatchVersion { .v(0, 8, 20) }
77
}

Sources/UnidocLinker/Resolver/Codelink (ext).swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ extension Codelink
1818

1919
self.init(doclink.path.joined(separator: "/"))
2020

21+
case .web:
22+
return nil
23+
2124
case .ucf:
2225
self.init(unresolved.link)
2326

0 commit comments

Comments
 (0)