Skip to content

Commit 1065833

Browse files
committed
fix: Fix linux compatibility
Closes #25
1 parent 0dc0339 commit 1065833

File tree

12 files changed

+99
-84
lines changed

12 files changed

+99
-84
lines changed

Example/Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import PackageDescription
55
let package = Package(
66
name: "Example",
77
platforms: [
8-
.macOS(.v12)
8+
.macOS(.v12),
99
],
1010
dependencies: [
1111
.package(path: "../"),
@@ -18,7 +18,7 @@ let package = Package(
1818
dependencies: [
1919
"Saga",
2020
"SagaParsleyMarkdownReader",
21-
"SagaSwimRenderer"
21+
"SagaSwimRenderer",
2222
]
2323
),
2424
]

Example/Sources/Example/String+Extensions.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ extension String {
1010
// This is a sloppy implementation but sadly `NSAttributedString(data:options:documentAttributes:)`
1111
// is not available in CoreFoundation, and as such can't run on Linux (blocking CI builds).
1212
var withoutHtmlTags: String {
13-
return self
14-
.replacingOccurrences(of: "(?m)<pre><span></span><code>[\\s\\S]+?</code></pre>", with: "", options: .regularExpression, range: nil)
13+
return replacingOccurrences(of: "(?m)<pre><span></span><code>[\\s\\S]+?</code></pre>", with: "", options: .regularExpression, range: nil)
1514
.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression, range: nil)
1615
.trimmingCharacters(in: .whitespacesAndNewlines)
1716
}

Example/Sources/Example/run.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
2-
import Saga
32
import PathKit
3+
import Saga
44
import SagaParsleyMarkdownReader
55
import SagaSwimRenderer
66

@@ -48,8 +48,8 @@ struct Run {
4848
.yearWriter(swim(renderPartition)),
4949

5050
// Atom feed for all articles, and a feed per tag
51-
.listWriter(atomFeed(title: SiteMetadata.name, author: SiteMetadata.author, baseURL: SiteMetadata.url, summary: \.self.metadata.summary), output: "feed.xml"),
52-
.tagWriter(atomFeed(title: SiteMetadata.name, author: SiteMetadata.author, baseURL: SiteMetadata.url, summary: \.self.metadata.summary), output: "tag/[key]/feed.xml", tags: \.metadata.tags),
51+
.listWriter(atomFeed(title: SiteMetadata.name, author: SiteMetadata.author, baseURL: SiteMetadata.url, summary: \.metadata.summary), output: "feed.xml"),
52+
.tagWriter(atomFeed(title: SiteMetadata.name, author: SiteMetadata.author, baseURL: SiteMetadata.url, summary: \.metadata.summary), output: "tag/[key]/feed.xml", tags: \.metadata.tags),
5353
]
5454
)
5555

Example/Sources/Example/templates.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1+
import Foundation
12
import HTML
23
import Saga
34
import SagaSwimRenderer
4-
import Foundation
55

66
func baseHtml(title pageTitle: String, @NodeBuilder children: () -> NodeConvertible) -> Node {
77
html(lang: "en-US") {
88
head {
9-
title { SiteMetadata.name+": "+pageTitle }
9+
title { SiteMetadata.name + ": " + pageTitle }
1010
link(href: "/static/style.css", rel: "stylesheet")
1111
link(href: "/static/prism.css", rel: "stylesheet")
1212
}
@@ -40,7 +40,7 @@ func renderArticle(context: ItemRenderingContext<ArticleMetadata>) -> Node {
4040
div(id: "article") {
4141
h1 { context.item.title }
4242
h2 {
43-
context.item.date.formatted("dd MMMM")+", "
43+
context.item.date.formatted("dd MMMM") + ", "
4444
a(href: "/articles/\(context.item.date.formatted("yyyy"))/") { context.item.date.formatted("yyyy") }
4545
}
4646
ul {
@@ -147,7 +147,7 @@ func renderPhotos(context: ItemRenderingContext<EmptyMetadata>) -> Node {
147147
baseHtml(title: "Photos") {
148148
h1 { context.item.title }
149149
Node.raw(context.item.body)
150-
150+
151151
context.resources.map { imagePath in
152152
img(height: "300", src: imagePath.lastComponent)
153153
}

Package.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import PackageDescription
55
let package = Package(
66
name: "Saga",
77
platforms: [
8-
.macOS(.v12)
8+
.macOS(.v12),
99
],
1010
products: [
1111
.library(name: "Saga", targets: ["Saga"]),
12-
.executable(name: "watch", targets: ["SagaCLI"])
12+
.executable(name: "watch", targets: ["SagaCLI"]),
1313
],
1414
dependencies: [
1515
.package(url: "https://github.com/kylef/PathKit", from: "1.0.1"),
16-
//.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"),
16+
// .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"),
1717
],
1818
targets: [
1919
.target(

Sources/Saga/Atom.swift

Lines changed: 64 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -16,63 +16,78 @@ import Foundation
1616
/// - Returns: A function which takes a rendering context, and returns a string.
1717
public func atomFeed<Context: AtomContext, M>(title: String, author: String? = nil, baseURL: URL, summary: ((Item<M>) -> String?)? = nil) -> (_ context: Context) -> String where Context.M == M {
1818
let RFC3339_DF = ISO8601DateFormatter()
19-
19+
2020
return { context in
2121
let feedPath = context.outputPath.string
22-
23-
// Create the root element
24-
let rootElement = XMLElement(name: "feed")
25-
rootElement.setAttributesWith(["xmlns": "http://www.w3.org/2005/Atom"])
26-
27-
// Create the XML document
28-
let XML = XMLDocument(rootElement: rootElement)
29-
30-
let idElement = XMLElement(name: "id")
31-
idElement.stringValue = baseURL.appendingPathComponent(feedPath).absoluteString
32-
rootElement.addChild(idElement)
33-
34-
rootElement.addChild(XMLElement(name: "title", stringValue: title))
35-
22+
let currentDate = RFC3339_DF.string(from: Date())
23+
24+
// Build the feed header
25+
var xml = """
26+
<?xml version="1.0" encoding="UTF-8"?>
27+
<feed xmlns="http://www.w3.org/2005/Atom">
28+
<id>\(baseURL.appendingPathComponent(feedPath).absoluteString)</id>
29+
<title>\(escapeXML(title))</title>
30+
31+
"""
32+
33+
// Add optional author
3634
if let author = author {
37-
let authorElement = XMLElement(name: "author")
38-
authorElement.addChild(XMLElement(name: "name", stringValue: author))
39-
rootElement.addChild(authorElement)
35+
xml += """
36+
<author>
37+
<name>\(escapeXML(author))</name>
38+
</author>
39+
40+
"""
4041
}
41-
42-
let linkElement = XMLElement(name: "link")
43-
linkElement.setAttributesWith(["rel": "self", "href": baseURL.absoluteString])
44-
rootElement.addChild(linkElement)
45-
46-
let updatedElement = XMLElement(name: "updated", stringValue: RFC3339_DF.string(from: Date()))
47-
rootElement.addChild(updatedElement)
48-
49-
// add entries to feed
42+
43+
// Add link and updated date
44+
xml += """
45+
<link rel="self" href="\(baseURL.absoluteString)"/>
46+
<updated>\(currentDate)</updated>
47+
48+
"""
49+
50+
// Add entries
5051
for item in context.items {
51-
// create entry element
52-
let entryElement = XMLElement(name: "entry")
53-
54-
let idElement = XMLElement(name: "id")
55-
idElement.stringValue = baseURL.appendingPathComponent(item.url).absoluteString
56-
57-
entryElement.addChild(idElement)
58-
entryElement.addChild(XMLElement(name: "title", stringValue: item.title))
59-
entryElement.addChild(XMLElement(name: "updated", stringValue: RFC3339_DF.string(from: item.lastModified)))
60-
52+
let itemURL = baseURL.appendingPathComponent(item.url).absoluteString
53+
54+
xml += """
55+
<entry>
56+
<id>\(itemURL)</id>
57+
<title>\(escapeXML(item.title))</title>
58+
<updated>\(RFC3339_DF.string(from: item.lastModified))</updated>
59+
60+
"""
61+
6162
if let summary, let summaryString = summary(item) {
62-
let summaryElement = XMLElement(name: "summary", stringValue: summaryString)
63-
let alternateElement = XMLElement(name: "link")
64-
alternateElement.setAttributesWith(["rel": "alternate", "href": baseURL.appendingPathComponent(item.url).absoluteString])
65-
entryElement.addChild(summaryElement)
66-
entryElement.addChild(alternateElement)
63+
xml += """
64+
<summary>\(escapeXML(summaryString))</summary>
65+
<link rel="alternate" href="\(itemURL)"/>
66+
67+
"""
6768
} else {
68-
let contentElement = XMLElement(name: "content", stringValue: item.body)
69-
contentElement.setAttributesWith(["type": "html"])
70-
entryElement.addChild(contentElement)
69+
xml += """
70+
<content type="html">\(escapeXML(item.body))</content>
71+
72+
"""
7173
}
72-
73-
rootElement.addChild(entryElement)
74+
75+
xml += " </entry>\n"
7476
}
75-
76-
return String(data: XML.xmlData(options: [.nodePrettyPrint]), encoding: .utf8) ?? ""
77+
78+
// Close the feed
79+
xml += "</feed>"
80+
81+
return xml
7782
}
7883
}
84+
85+
// Helper function to escape special XML characters
86+
private func escapeXML(_ string: String) -> String {
87+
return string
88+
.replacingOccurrences(of: "&", with: "&amp;")
89+
.replacingOccurrences(of: "<", with: "&lt;")
90+
.replacingOccurrences(of: ">", with: "&gt;")
91+
.replacingOccurrences(of: "\"", with: "&quot;")
92+
.replacingOccurrences(of: "'", with: "&apos;")
93+
}

Sources/Saga/Item.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class Item<M: Metadata>: AnyItem {
4343
/// The date of the item, defaults to the creation date.
4444
/// Pleaae note that the creation date value can be inconsistent when cloning or pulling from git, see https://github.com/loopwerk/Saga/issues/21.
4545
public var date: Date
46-
46+
4747
/// The last modified date of the item.
4848
/// Pleaae note that this value can be inconsistent when cloning or pulling from git, see https://github.com/loopwerk/Saga/issues/21.
4949
public let lastModified: Date

Sources/Saga/Path+Extensions.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import PathKit
21
import Foundation
2+
import PathKit
33

44
public extension Path {
55
var creationDate: Date? {
6-
return self.attributes[.creationDate] as? Date
6+
return attributes[.creationDate] as? Date
77
}
88

99
var modificationDate: Date? {
10-
return self.attributes[.modificationDate] as? Date
10+
return attributes[.modificationDate] as? Date
1111
}
1212

1313
var url: String {
14-
var url = "/" + self.string
14+
var url = "/" + string
1515
if url.hasSuffix("/index.html") {
1616
url.removeLast(10)
1717
}
@@ -21,22 +21,22 @@ public extension Path {
2121
func makeOutputPath(itemWriteMode: ItemWriteMode) -> Path {
2222
switch itemWriteMode {
2323
case .keepAsFile:
24-
return self.parent() + (self.lastComponentWithoutExtension.slugified + ".html")
24+
return parent() + (lastComponentWithoutExtension.slugified + ".html")
2525
case .moveToSubfolder:
26-
if self.lastComponentWithoutExtension.slugified == "index" {
27-
return self.parent() + (self.lastComponentWithoutExtension.slugified + ".html")
26+
if lastComponentWithoutExtension.slugified == "index" {
27+
return parent() + (lastComponentWithoutExtension.slugified + ".html")
2828
} else {
29-
return self.parent() + self.lastComponentWithoutExtension.slugified + "index.html"
29+
return parent() + lastComponentWithoutExtension.slugified + "index.html"
3030
}
3131
}
3232
}
3333

3434
func relativePath(from: Path) throws -> Path {
35-
guard self.string.hasPrefix(from.string) else {
35+
guard string.hasPrefix(from.string) else {
3636
return self
3737
}
38-
let index = self.string.index(self.string.startIndex, offsetBy: from.string.count)
39-
return Path(String(self.string[index...]).removingPrefix("/"))
38+
let index = string.index(string.startIndex, offsetBy: from.string.count)
39+
return Path(String(string[index...]).removingPrefix("/"))
4040
}
4141
}
4242

@@ -67,8 +67,8 @@ internal extension Path {
6767
return (self + file).isFile
6868
}
6969

70-
var attributes: [FileAttributeKey : Any] {
71-
return (try? FileManager.default.attributesOfItem(atPath: self.string)) ?? [:]
70+
var attributes: [FileAttributeKey: Any] {
71+
return (try? FileManager.default.attributesOfItem(atPath: string)) ?? [:]
7272
}
7373
}
7474

Sources/Saga/ProcessingStep.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ internal class AnyProcessStep {
6161
body: partialItem.body,
6262
date: date ?? unhandledFileContainer.path.creationDate ?? Date(),
6363
lastModified: unhandledFileContainer.path.modificationDate ?? Date(),
64-
metadata: metadata)
64+
metadata: metadata
65+
)
6566

6667
// Process the Item if there's an itemProcessor
6768
if let itemProcessor = step.itemProcessor {

Sources/Saga/Reader.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public struct Reader<M: Metadata> {
4242
/// Which file extensions can be handled by this reader? For example `md` or `rst`.
4343
var supportedExtensions: [String]
4444

45-
public typealias Converter = (_ absoluteSource: Path) async throws -> (title: String?, body: String, frontmatter: [String:String]?)
45+
public typealias Converter = (_ absoluteSource: Path) async throws -> (title: String?, body: String, frontmatter: [String: String]?)
4646

4747
/// The function that will do the actual work of reading and converting a file path into an ``Item``.
4848
var convert: Converter

0 commit comments

Comments
 (0)