Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
helje5 committed Jul 5, 2021
2 parents c7091f2 + aa90e60 commit cc8e5a9
Show file tree
Hide file tree
Showing 17 changed files with 615 additions and 297 deletions.
20 changes: 12 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,26 @@ let package = Package(
platforms : [ .macOS(.v10_14), .iOS(.v11) ],

products : [
.library (name: "DocCHTMLExporter", targets: [ "DocCHTMLExporter" ]),
.executable(name: "docc2html", targets: [ "docc2html" ])
.library (name: "DocCHTMLExporter" , targets: [ "DocCHTMLExporter" ]),
.library (name: "DocCStaticExporter" , targets: [ "DocCStaticExporter" ]),
.executable(name: "docc2html" , targets: [ "docc2html" ])
],

dependencies: [
.package(url: "https://github.com/AlwaysRightInstitute/mustache.git",
from: "1.0.1"),
.package(url: "https://github.com/DoccZz/DocCArchive.git", from: "0.1.0"),
.package(url: "https://github.com/apple/swift-log.git",
from: "1.4.0")
.package(url : "https://github.com/AlwaysRightInstitute/mustache.git",
from : "1.0.1"),
.package(url : "https://github.com/DoccZz/DocCArchive.git",
from : "0.1.0"),
.package(url : "https://github.com/apple/swift-log.git",
from : "1.4.0")
],

targets: [
.target(name : "DocCHTMLExporter",
dependencies : [ "DocCArchive", "mustache", "Logging" ]),
.target(name : "DocCStaticExporter",
dependencies : [ "DocCArchive", "DocCHTMLExporter", "Logging" ]),
.target(name : "docc2html",
dependencies : [ "DocCHTMLExporter", "Logging" ])
dependencies : [ "DocCStaticExporter", "Logging" ])
]
)
19 changes: 14 additions & 5 deletions Sources/DocCHTMLExporter/BuildNavigation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,19 @@ fileprivate extension DocCArchive.Document {
let idURL = ref.identifierURL
assert(idURL != nil)

let hierarchy = String(repeating: "../", count: idx + 1)
let relURL = hierarchy
+ (idURL?.appendingPathExtension("html").lastPathComponent
?? "index.html")
let relURL : String = {
let needsStepUp = !context.indexLinks || context.isIndex
let hierarchy =
String(repeating: "../", count: idx + (needsStepUp ? 1 : 0))
if context.indexLinks {
return hierarchy + "index.html"
}
else {
return hierarchy
+ (idURL?.appendingPathExtension("html").lastPathComponent
?? "index.html")
}
}()
items.insert(
NavigationItem(title: tr.title, isCurrent: false, link: relURL),
at: 0
Expand All @@ -84,7 +93,7 @@ fileprivate extension DocCArchive.DocumentFolder {
// FIXME: Here we somehow need to figure out the real title, is this
// NOT in the JSON? Feels weird.
NavigationItem(title: title, isCurrent: false,
link: String(repeating: "../", count: idx + 1)
link: String(repeating: "../", count: idx)
.appending("index.html"))
}.reversed()
)
Expand Down
40 changes: 37 additions & 3 deletions Sources/DocCHTMLExporter/DZRenderingContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import mustache
* The object is used for rendering a single document. Not intended to be
* invoked multiple times.
*/
public final class DZRenderingContext {
open class DZRenderingContext {

public static let defaultStyleSheet = stylesheet

Expand Down Expand Up @@ -87,6 +87,9 @@ public final class DZRenderingContext {

let pathToRoot : String
let references : [ String : DocCArchive.Reference ]
let indexLinks : Bool
let isIndex : Bool
let dataFolderPathes : Set<String>
let traits : Set<DocCArchive.ImageReference.Variant.Trait>
= [ .light, .retina ]

Expand All @@ -110,13 +113,19 @@ public final class DZRenderingContext {
*/
public init(pathToRoot : String,
references : [ String : DocCArchive.Reference ],
isIndex : Bool,
dataFolderPathes : Set<String>,
indexLinks : Bool,
templates : Templates? = nil,
labels : Labels? = nil,
moduleToExternalURL : [ String : URL ]? = nil,
logger : Logger = Logger(label: "docc2html"))
{
self.pathToRoot = pathToRoot
self.references = references
self.isIndex = isIndex
self.dataFolderPathes = dataFolderPathes
self.indexLinks = indexLinks
self.templates = templates ?? Templates()
self.labels = labels ?? Labels()
self.moduleToExternalURL = moduleToExternalURL ?? ModuleToExternalURL
Expand All @@ -126,13 +135,38 @@ public final class DZRenderingContext {

// MARK: - URLs

func makeRelativeToRoot(_ url: String) -> String {

private func makeRelativeToRoot(_ url: String) -> String {
if url.hasPrefix("/") { return pathToRoot + url.dropFirst() }
return pathToRoot + url
}
func makeRelativeToRoot(_ url: URL) -> String {
private func makeRelativeToRoot(_ url: URL) -> String {
return makeRelativeToRoot(url.path)
}

func linkToResource(_ url: String) -> String {
return makeRelativeToRoot(url)
}

func linkToDocument(_ identifierURL: URL) -> String {
// Note: This is not very clever yet, it essentially goes up the chain
// using `../..` and then appends the "absolute" path, like
// `../../documentation/SwiftBlocksUI/index.html`.
// Note: Those can have path extensions, e.g. "color-swift.property"
assert(identifierURL.scheme == "doc")
assert(!dataFolderPathes.isEmpty)

// This is a little hacky, but yeah. The dataFolderPathes are always
// lowercase. Technically we are supposed to use the reference URL,
// but that doesn't come w/ the `doc` scheme and is probably less safe
// to use.
let isIndexed = dataFolderPathes.contains(identifierURL.path.lowercased())

let url = isIndexed
? identifierURL.appendingPathComponent("index.html")
: identifierURL.appendingPathExtension("html")
return makeRelativeToRoot(url)
}

func externalDocumentBaseURL(for module: String) -> URL? {
return moduleToExternalURL[module]
Expand Down
30 changes: 17 additions & 13 deletions Sources/DocCHTMLExporter/HTML/ReferenceHTML.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import DocCArchive

extension DocCArchive.Reference {

func generateHTML(isActive: Bool = true, in ctx: DZRenderingContext) -> String {
var idURL : URL? { return URL(string: identifier) }
func generateHTML(isActive: Bool = true, in ctx: DZRenderingContext)
-> String
{
switch self {

case .topic(let ref):
return ref.generateHTML(isActive: isActive, idURL: idURL, in: ctx)
return ref.generateHTML(isActive: isActive, in: ctx)

case .image(let ref):
return ref.generateHTML(in: ctx)
Expand All @@ -39,12 +39,12 @@ extension DocCArchive.Reference {
switch self {

case .topic(let ref):
guard let url = ref.url ?? idURL else { return "" }
return ctx.makeRelativeToRoot(url) + ".html"

guard let url = idURL ?? ref.url else { return "" }
return ctx.linkToDocument(url)
case .image(let ref):
guard let variant = ref.bestVariant(for: ctx.traits) else { return "" }
return ctx.makeRelativeToRoot(variant.url)
return ctx.linkToResource(variant.url)

case .file(_):
fatalError("unsupported file ref")
Expand Down Expand Up @@ -102,14 +102,18 @@ extension DocCArchive.TopicReference {
/**
* Generate an <a> for the topic reference.
*/
func generateHTML(isActive: Bool = true, idURL: URL?,
func generateHTML(isActive: Bool = true,
in ctx: DZRenderingContext) -> String
{
let idURL = URL(string: identifier)
assert(idURL != nil, "topic refs should always have proper URLs")
assert(idURL?.scheme == "doc", "topic ref w/o a doc:// URL")

let activeClass = isActive ? "" : " class='inactive'"
let title = self.title.htmlEscaped
let url = (self.url ?? idURL).flatMap {
ctx.makeRelativeToRoot($0.appendingPathExtension("html"))
} ?? ""

let url = (idURL ?? self.url).flatMap(ctx.linkToDocument) ?? ""
assert(!url.isEmpty)

var ms = ""
if !url.isEmpty { ms += "<a href='\(url.htmlEscaped)'\(activeClass)>" }
Expand Down Expand Up @@ -151,7 +155,7 @@ extension DocCArchive.ImageReference {
}
}()

let url = ctx.makeRelativeToRoot(variant.url)
let url = ctx.linkToResource(variant.url)
let srcset = url.htmlEscaped
+ (ctx.traits.contains(.retina) ? " 2x" : "")

Expand Down
142 changes: 142 additions & 0 deletions Sources/DocCStaticExporter/DocCFileSystemExportTarget.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//
// DocCFileSystemExportTarget.swift
// docc2html
//
// Created by Helge Heß.
// Copyright © 2021 ZeeZide GmbH. All rights reserved.
//

import Foundation
import Logging

open class DocCFileSystemExportTarget: DocCStaticExportTarget,
CustomStringConvertible
{

public let logger : Logger
public let fileManager : FileManager
public let targetPath : String
open var targetURL : URL { return URL(fileURLWithPath: targetPath) }

public init(targetPath: String, fileManager: FileManager = .default,
logger: Logger = Logger(label: "docc2html"))
{
self.logger = logger
self.targetPath = targetPath
self.fileManager = fileManager
}


open func doesTargetExist() -> Bool {
return fileManager.fileExists(atPath: targetPath)
}


// MARK: - Copying Static Resources

open func copyCSS(_ cssFiles: [ URL ], keepHash: Bool) throws {
guard !cssFiles.isEmpty else { return }

let targetURL = self.targetURL.appendingPathComponent("css")
try ensureTargetDir("css")

for css in cssFiles {
let cssContents : String
do {
cssContents = try String(contentsOf: css)
.stringByRemovingDocCDataReferences()
}
catch {
logger.error("Failed to load CSS:", css.path)
throw DocCStaticExportError
.couldNotLoadStaticResource(css, underlyingError: error)
}

let targetName = keepHash
? css.lastPathComponent
: css.deletingResourceHash().lastPathComponent

let cssTargetURL = targetURL.appendingPathComponent(targetName)

logger.trace("Copying CSS \(css.path) to \(cssTargetURL.path)")

do {
try cssContents.write(to: cssTargetURL, atomically: false,
encoding: .utf8)
}
catch {
logger.error("Failed to save CSS:", cssTargetURL.path)
throw DocCStaticExportError
.couldNotCopyStaticResource(from: css, to: "css")
}
}
}

public func copyRaw(_ files: [ URL ], to directory: String,
keepHash: Bool = true) throws
{
guard !files.isEmpty else { return }

let targetURL = self.targetURL.appendingPathComponent(directory)
try ensureTargetDir(directory)

for file in files {
let targetName = keepHash
? file.lastPathComponent
: file.deletingResourceHash().lastPathComponent

let fileTargetURL = targetURL.appendingPathComponent(targetName)

logger.trace("Copying resource \(file.path) to \(fileTargetURL.path)")

// copyItem fails if the target exists.
try? fileManager.removeItem(at: fileTargetURL)

do {
try fileManager.copyItem(at: file, to: fileTargetURL)
}
catch {
logger.error("Failed to copy resource:", fileTargetURL.path, error)
throw DocCStaticExportError
.couldNotCopyStaticResource(from: file, to: directory)
}
}
}


// MARK: - Files

open func write(_ content: String, to relativePath: String) throws {
let url = targetURL.appendingPathComponent(relativePath)
// TODO: own error
try content.write(to: url, atomically: false, encoding: .utf8)
}


// MARK: - Subdirectories

open func ensureTargetDir(_ relativePath: String) throws {
var url = targetURL
if !relativePath.isEmpty {
url.appendPathComponent(relativePath)
}

do {
try fileManager
.createDirectory(at: url, withIntermediateDirectories: true)
logger.trace("Created output subdir:", url.path)
}
catch {
logger.error("Could not create target directory:", url.path, error)
throw DocCStaticExportError
.couldNotCreateTargetDirectory(relativePath, underlyingError: error)
}
}


// MARK: - Description

open var description: String {
return targetPath
}
}
22 changes: 22 additions & 0 deletions Sources/DocCStaticExporter/DocCStaticExportError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// DocCStaticExportError.swift
// docc2html
//
// Created by Helge Heß.
// Copyright © 2021 ZeeZide GmbH. All rights reserved.
//

import struct Foundation.URL

public enum DocCStaticExportError: Swift.Error {

case targetExists(DocCStaticExportTarget)

case expectedDocCArchive(URL)

case couldNotLoadStaticResource(URL, underlyingError: Swift.Error)

case couldNotCopyStaticResource(from: URL, to: String)

case couldNotCreateTargetDirectory(String, underlyingError: Swift.Error)
}
Loading

0 comments on commit cc8e5a9

Please sign in to comment.