Skip to content

Commit b9c2709

Browse files
committed
support URL schemes besides https
1 parent 4677aa8 commit b9c2709

19 files changed

+289
-264
lines changed

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

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,11 @@ extension Markdown.InlineHyperlink
77
{
88
/// The link target has already been outlined.
99
case outlined(Int)
10+
1011
/// The link target is a fragment within the current document. The string does not
1112
/// include a leading `#`.
12-
case fragment(Markdown.SourceString)
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 does not include the scheme, nor the
18-
/// delimiting colon.
19-
case external(Markdown.ExternalURL)
13+
case urlFragment(Markdown.SourceString)
14+
case url(Markdown.SourceURL)
2015
}
2116
}
2217
extension Markdown.InlineHyperlink.Target
@@ -26,12 +21,12 @@ extension Markdown.InlineHyperlink.Target
2621
{
2722
switch target[target.startIndex]
2823
{
29-
case "/":
30-
self = .absolute(.init(source: source, string: target))
31-
3224
case "#":
3325
let i:String.Index = target.index(after: target.startIndex)
34-
self = .fragment(.init(source: source, string: String.init(target[i...])))
26+
self = .urlFragment(.init(source: source, string: String.init(target[i...])))
27+
28+
case "/":
29+
self = .url(.init(scheme: nil, suffix: .init(source: source, string: target)))
3530

3631
case ".":
3732
let trimmed:Markdown.SourceString
@@ -51,18 +46,10 @@ extension Markdown.InlineHyperlink.Target
5146
trimmed = .init(source: source, string: target)
5247
}
5348

54-
self = .relative(trimmed)
49+
self = .url(.init(scheme: nil, suffix: trimmed))
5550

5651
default:
57-
let link:Markdown.SourceString = .init(source: source, string: target)
58-
if let url:Markdown.ExternalURL = .init(from: link)
59-
{
60-
self = .external(url)
61-
}
62-
else
63-
{
64-
self = .relative(link)
65-
}
52+
self = .url(.init(from: .init(source: source, string: target)))
6653
}
6754
}
6855
}

Sources/MarkdownAST/InlineElements/Markdown.InlineHyperlink.swift

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,28 @@ extension Markdown.InlineHyperlink:Markdown.TreeElement
6767
{
6868
switch target
6969
{
70-
// These will almost certainly be invalid, so there is no point in encoding them.
71-
case .absolute: break
72-
case .relative: break
73-
case .external(let url): $0[.external] = "\(url)"
74-
case .fragment(let fragment): $0[.href] = "#\(fragment.string)"
75-
case .outlined(let reference): $0[.href] = reference
70+
case .outlined(let reference):
71+
$0[.href] = reference
72+
73+
case .urlFragment(let target):
74+
$0[.href] = "#\(target)"
75+
76+
case .url(let url):
77+
guard
78+
let scheme:String = url.scheme
79+
else
80+
{
81+
// This will almost certainly be invalid, so there is no point encoding it.
82+
break
83+
}
84+
85+
if scheme == "doc"
86+
{
87+
// This will never work, so there is no point encoding it.
88+
break
89+
}
90+
91+
$0[.external] = "\(url)"
7692
}
7793
}
7894
content:
@@ -107,12 +123,10 @@ extension Markdown.InlineHyperlink:Markdown.TextElement
107123
let reference:Markdown.AnyReference
108124
switch self.target
109125
{
110-
case .none: return
111-
case .outlined?: return
112-
case .fragment?: return
113-
case .relative(let link)?: reference = .link(link)
114-
case .absolute(let link)?: reference = .link(link)
115-
case .external(url: let url)?: reference = .external(url: url)
126+
case .none: return
127+
case .outlined?: return
128+
case .urlFragment?: return
129+
case .url(let url)?: reference = .link(url: url)
116130
}
117131

118132
if let reference:Int = try register(reference)

Sources/MarkdownAST/Markdown.AnyReference.swift

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ extension Markdown
66
@frozen public
77
enum AnyReference
88
{
9-
/// A reference to a code object. These originate from inline code spans.
10-
case code(SourceString)
11-
/// An absolute (begins with slash) or relative URL. Never contains domain or scheme.
12-
case link(SourceString)
9+
/// A symbolic (ABI) reference to a code object.
10+
case symbolic(usr:Symbol.USR)
11+
/// A lexical (API) path reference to a code object. These originate from inline code
12+
/// spans.
13+
case lexical(ucf:SourceString)
14+
/// A URL reference, which may be external or internal.
15+
case link(url:SourceURL)
1316
/// A reference to a file, by its file name. These originate from block directive
1417
/// arguments, such as those in an `@Image`.
1518
case file(SourceString)
@@ -19,28 +22,18 @@ extension Markdown
1922
/// A reference to a source location, which has already been resolved. These originate
2023
/// from the inlining of code snippets.
2124
case location(SourceLocation<Int32>)
22-
case symbolic(Symbol.USR)
23-
/// A fully-qualified URL.
24-
case external(url:ExternalURL)
2525
}
2626
}
2727
extension Markdown.AnyReference
28+
{
29+
@inlinable public
30+
static func link(url:__owned Markdown.SourceString) -> Self { .link(url: .init(from: url)) }
31+
}
32+
extension Markdown.AnyReference
2833
{
2934
@inlinable public
3035
init(_ autolink:Markdown.InlineAutolink)
3136
{
32-
if autolink.code
33-
{
34-
self = .code(autolink.text)
35-
}
36-
else if
37-
let url:Markdown.ExternalURL = .init(from: autolink.text)
38-
{
39-
self = .external(url: url)
40-
}
41-
else
42-
{
43-
self = .link(autolink.text)
44-
}
37+
self = autolink.code ? .lexical(ucf: autolink.text) : .link(url: autolink.text)
4538
}
4639
}

Sources/MarkdownAST/Markdown.ExternalURL.swift renamed to Sources/MarkdownAST/Markdown.SourceURL.swift

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,33 @@ import Sources
33
extension Markdown
44
{
55
@frozen public
6-
struct ExternalURL
6+
struct SourceURL
77
{
88
public
9-
var scheme:String
9+
var scheme:String?
1010
public
1111
var suffix:SourceString
1212

1313
@inlinable public
14-
init(scheme:String, suffix:SourceString)
14+
init(scheme:String?, suffix:SourceString)
1515
{
1616
self.scheme = scheme
1717
self.suffix = suffix
1818
}
1919
}
2020
}
21-
extension Markdown.ExternalURL:CustomStringConvertible
21+
extension Markdown.SourceURL:CustomStringConvertible
2222
{
2323
@inlinable public
24-
var description:String { "\(self.scheme):\(self.suffix)" }
24+
var description:String { self.scheme.map { "\($0):\(self.suffix)" } ?? "\(self.suffix)" }
2525
}
26-
extension Markdown.ExternalURL
26+
extension Markdown.SourceURL
2727
{
2828
@usableFromInline
29-
init?(from url:Markdown.SourceString)
29+
init(from url:Markdown.SourceString)
3030
{
31+
self.init(scheme: nil, suffix: url)
32+
3133
// URL parsing is incredibly tough.
3234
//
3335
// This problem is complicated significantly by the fact that Apple uses a
@@ -37,14 +39,14 @@ extension Markdown.ExternalURL
3739
// We need to do this in order to avoid emitting unresolved relative URLs, as such
3840
// URLs would never work properly for the user.
3941
guard
40-
let colon:String.Index = url.string.firstIndex(of: ":")
42+
let colon:String.Index = self.suffix.string.firstIndex(of: ":")
4143
else
4244
{
43-
return nil
45+
return
4446
}
4547

4648
scheme:
47-
for codepoint:Unicode.Scalar in url.string.unicodeScalars[..<colon]
49+
for codepoint:Unicode.Scalar in self.suffix.string.unicodeScalars[..<colon]
4850
{
4951
switch codepoint
5052
{
@@ -55,14 +57,11 @@ extension Markdown.ExternalURL
5557
case "A" ... "Z": continue
5658
case "a" ... "z": continue
5759
// This isn’t a scheme at all!
58-
default: return nil
60+
default: return
5961
}
6062
}
6163

62-
let scheme:String = .init(url.string[..<colon])
63-
var suffix:Markdown.SourceString = consume url
64-
suffix.string.removeSubrange(...colon)
65-
66-
self.init(scheme: scheme, suffix: suffix)
64+
self.scheme = .init(url.string[..<colon])
65+
self.suffix.string.removeSubrange(...colon)
6766
}
6867
}

Sources/MarkdownSemantics/AST/Media/Markdown.BlockCodeLiteral.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ extension Markdown
7878
try
7979
{
8080
if case .inline(let usr)? = $0.usr,
81-
let reference:Int = try register(.symbolic(usr))
81+
let reference:Int = try register(.symbolic(usr: usr))
8282
{
8383
$0.usr = .outlined(reference)
8484
}

Sources/MarkdownSemantics/AST/Tutorials/Markdown.BlockTopicReference.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ extension Markdown
2525
try
2626
{
2727
if case .inline(let expression) = $0,
28-
case let reference? = try register(.external(url: .init(
29-
scheme: "doc",
30-
suffix: expression)))
28+
case let reference? = try register(.link(url: expression))
3129
{
3230
$0 = .outlined(reference)
3331
}

Sources/SymbolGraphLinker/Resolution/SSGC.Outliner.swift

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -97,24 +97,22 @@ extension SSGC.Outliner
9797

9898
switch reference
9999
{
100-
case .code(let link):
101-
return self.outline(ucf: link)
100+
case .symbolic(usr: let usr):
101+
return self.outline(symbol: usr)
102102

103-
case .link(let link):
104-
return self.outline(doc: link)
103+
case .lexical(ucf: let expression):
104+
return self.outline(ucf: expression)
105105

106-
case .external(url: let url):
106+
case .link(url: let url):
107107
switch url.scheme
108108
{
109-
case "doc": return self.outline(doc: url.suffix)
110-
case "http": return self.outline(web: url.suffix)
111-
case "https": return self.outline(web: url.suffix)
112-
default: break
113-
}
109+
case nil, "doc"?:
110+
return self.outline(doc: url.suffix)
114111

115-
self.resolver.diagnostics[url.suffix.source] =
116-
SSGC.AutolinkParsingError<SSGC.Symbolicator>.init(url.suffix)
117-
return nil
112+
case let scheme?:
113+
return self.cache.add(outline: .url("\(scheme):\(url.suffix)",
114+
location: url.suffix.source.start))
115+
}
118116

119117
case .file(let link):
120118
name = link
@@ -133,9 +131,6 @@ extension SSGC.Outliner
133131
source: link.source,
134132
string: String.init(link.string[link.string.index(after: i)...]))
135133

136-
case .symbolic(let usr):
137-
return self.outline(symbol: usr)
138-
139134
case .location(let location):
140135
// This is almost a no-op, except we can optimize away duplicated locations.
141136
return self.cache.add(outline: .location(location))
@@ -152,16 +147,6 @@ extension SSGC.Outliner
152147
return nil
153148
}
154149

155-
private mutating
156-
func outline(web link:__owned Markdown.SourceString) -> Int?
157-
{
158-
// Remove leading slashes
159-
var link:Markdown.SourceString = consume link
160-
link.string.removeFirst(2)
161-
return self.cache.add(outline: .unresolved(web: link.string,
162-
location: link.source.start))
163-
}
164-
165150
private mutating
166151
func outline(ucf link:Markdown.SourceString) -> Int?
167152
{

Sources/SymbolGraphLinker/SSGC.Linker.Tables.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,12 @@ extension SSGC.Linker.Tables
217217

218218
switch target
219219
{
220-
case .external(let url)?:
220+
case .urlFragment(let anchor)?:
221+
spelling = anchor
222+
223+
case .url(url: let url)?:
221224
guard
222-
case "doc" = url.scheme,
225+
case "doc"? = url.scheme,
223226
case "#"? = url.suffix.string.first
224227
else
225228
{
@@ -229,9 +232,6 @@ extension SSGC.Linker.Tables
229232
spelling = url.suffix
230233
spelling.string.removeFirst()
231234

232-
case .fragment(let link)?:
233-
spelling = link
234-
235235
default:
236236
return
237237
}
@@ -240,7 +240,7 @@ extension SSGC.Linker.Tables
240240
{
241241
case .success(let fragment):
242242
spelling.string = fragment
243-
target = .fragment(spelling)
243+
target = .urlFragment(spelling)
244244

245245
case .failure(let error):
246246
self.diagnostics[spelling.source] = error

0 commit comments

Comments
 (0)