diff --git a/Changelog.md b/Changelog.md index 683f59c1..7592a883 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #140 by @JaapWijnen. - Fixed whitespace of code listings. #144 by @mbrandonw. +- Fixed crash when attempting to generate paths with no base URL specified. + #127 by @mattpolzin, @kareman, and @mattt. ## [1.0.0-beta.3] - 2020-05-19 diff --git a/Sources/SwiftDoc/Helpers.swift b/Sources/SwiftDoc/Helpers.swift new file mode 100644 index 00000000..7a19bb28 --- /dev/null +++ b/Sources/SwiftDoc/Helpers.swift @@ -0,0 +1,22 @@ +import Foundation + +public func route(for symbol: Symbol) -> String { + return route(for: symbol.id) +} + +public func route(for name: CustomStringConvertible) -> String { + return name.description.replacingOccurrences(of: ".", with: "_") +} + +public func path(for symbol: Symbol, with baseURL: String) -> String { + return path(for: route(for: symbol), with: baseURL) +} + +public func path(for identifier: CustomStringConvertible, with baseURL: String) -> String { + let url = URL(string: baseURL)?.appendingPathComponent("\(identifier)") ?? URL(string: "\(identifier)") + guard let string = url?.absoluteString else { + fatalError("Unable to construct path for \(identifier) with baseURL \(baseURL)") + } + + return string +} diff --git a/Sources/swift-doc/Supporting Types/Layout.swift b/Sources/swift-doc/Supporting Types/Layout.swift index a9c940f2..16429c4f 100644 --- a/Sources/swift-doc/Supporting Types/Layout.swift +++ b/Sources/swift-doc/Supporting Types/Layout.swift @@ -1,3 +1,4 @@ +import SwiftDoc import HypertextLiteral import Foundation diff --git a/Sources/swift-doc/Supporting Types/Page.swift b/Sources/swift-doc/Supporting Types/Page.swift index 14b20741..ece11da3 100644 --- a/Sources/swift-doc/Supporting Types/Page.swift +++ b/Sources/swift-doc/Supporting Types/Page.swift @@ -35,28 +35,7 @@ extension Page { } } -func route(for symbol: Symbol) -> String { - return route(for: symbol.id) -} - -func route(for name: CustomStringConvertible) -> String { - return name.description.replacingOccurrences(of: ".", with: "_") -} -func path(for symbol: Symbol, with baseURL: String) -> String { - return path(for: route(for: symbol), with: baseURL) -} - -func path(for identifier: CustomStringConvertible, with baseURL: String) -> String { - var urlComponents = URLComponents(string: baseURL) - urlComponents = urlComponents?.appendingPathComponent("\(identifier)") - guard let string = urlComponents?.string else { - logger.critical("Unable to construct path for \(identifier) with baseURL \(baseURL)") - fatalError() - } - - return string -} func writeFile(_ data: Data, to url: URL) throws { let fileManager = FileManager.default @@ -65,16 +44,3 @@ func writeFile(_ data: Data, to url: URL) throws { try data.write(to: url) try fileManager.setAttributes([.posixPermissions: 0o744], ofItemAtPath: url.path) } - -// MARK: - - -fileprivate extension URLComponents { - func appendingPathComponent(_ component: String) -> URLComponents? { - var urlComponents = self - var pathComponents = urlComponents.path.split(separator: "/").map { "\($0)" } - pathComponents.append(component) - urlComponents.path = "/" + pathComponents.joined(separator: "/") - - return urlComponents - } -} diff --git a/Tests/SwiftDocTests/PathTests.swift b/Tests/SwiftDocTests/PathTests.swift new file mode 100644 index 00000000..a33804d7 --- /dev/null +++ b/Tests/SwiftDocTests/PathTests.swift @@ -0,0 +1,47 @@ +import XCTest + +import SwiftDoc + +final class PathTests: XCTestCase { + func testEmptyBaseURL() { + XCTAssertEqual(path(for: "Class", with: ""), "Class") + + XCTAssertEqual(path(for: "(lhs:rhs:)", with: ""), "(lhs:rhs:)") + } + + func testRootDirectoryBaseURL() { + XCTAssertEqual(path(for: "Class", with: "/"), "/Class") + + XCTAssertEqual(path(for: "(lhs:rhs:)", with: "/"), "/(lhs:rhs:)") + } + + func testCurrentDirectoryBaseURL() { + XCTAssertEqual(path(for: "Class", with: "./"), "./Class") + + XCTAssertEqual(path(for: "(lhs:rhs:)", with: "./"), "./(lhs:rhs:)") + } + + func testNestedSubdirectoryBaseURL() { + XCTAssertEqual(path(for: "Class", with: "/path/to/directory"), "/path/to/directory/Class") + XCTAssertEqual(path(for: "Class", with: "/path/to/directory/"), "/path/to/directory/Class") + + XCTAssertEqual(path(for: "(lhs:rhs:)", with: "/path/to/directory"), "/path/to/directory/(lhs:rhs:)") + XCTAssertEqual(path(for: "(lhs:rhs:)", with: "/path/to/directory/"), "/path/to/directory/(lhs:rhs:)") + } + + func testDomainBaseURL() { + XCTAssertEqual(path(for: "Class", with: "https://example.com"), "https://example.com/Class") + XCTAssertEqual(path(for: "Class", with: "https://example.com/"), "https://example.com/Class") + + XCTAssertEqual(path(for: "(lhs:rhs:)", with: "https://example.com"), "https://example.com/(lhs:rhs:)") + XCTAssertEqual(path(for: "(lhs:rhs:)", with: "https://example.com/"), "https://example.com/(lhs:rhs:)") + } + + func testDomainSubdirectoryBaseURL() { + XCTAssertEqual(path(for: "Class", with: "https://example.com/docs"), "https://example.com/docs/Class") + XCTAssertEqual(path(for: "Class", with: "https://example.com/docs/"), "https://example.com/docs/Class") + + XCTAssertEqual(path(for: "(lhs:rhs:)", with: "https://example.com/docs"), "https://example.com/docs/(lhs:rhs:)") + XCTAssertEqual(path(for: "(lhs:rhs:)", with: "https://example.com/docs/"), "https://example.com/docs/(lhs:rhs:)") + } +} diff --git a/action.yml b/action.yml index e49b486b..c0ad7287 100644 --- a/action.yml +++ b/action.yml @@ -12,6 +12,10 @@ inputs: format: description: "The output format (commonmark or html)" default: "commonmark" + base-url: + description: "The base URL for all relative URLs generated in documents" + required: false + default: "./" output: description: "The path for generated output" required: true @@ -26,6 +30,8 @@ runs: "${{ inputs.inputs }}", --format, "${{ inputs.format }}", + --base-url, + "${{ inputs.base-url }}", --module-name, "${{ inputs.module-name }}", --output,