Skip to content

Instantly share code, notes, and snippets.

@gotev
Last active May 5, 2022 11:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gotev/086398eec5c26adf0e7c0b2531f2015c to your computer and use it in GitHub Desktop.
Save gotev/086398eec5c26adf0e7c0b2531f2015c to your computer and use it in GitHub Desktop.
Generate HTML with Swift. Just copy and paste the code below in a Swift Playground and have fun.
import Foundation
extension Sequence where Element: Hashable {
func uniqued() -> [Element] {
var set = Set<Element>()
return filter { set.insert($0).inserted }
}
}
public extension StringProtocol {
var whitespacesFiltered: String {
split(whereSeparator: \.isNewline)
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0 != "" }
.joined(separator: "\n")
}
}
public protocol HTMLComponent {
var importedScripts: [String] { get }
var importedStyles: [String] { get }
var inlineStyle: String? { get }
func render() -> String
}
public struct HTMLPage : CustomStringConvertible {
private let title: String
private let language: String
private let charset: String
private let pageStyles: [String]
private let components: [HTMLComponent]
public init(title: String,
components: [HTMLComponent],
pageStyles: [String] = ["https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css"],
language: String = "en",
charset: String = "utf-8") {
self.title = title
self.pageStyles = pageStyles
self.language = language
self.charset = charset
self.components = components
}
public var description: String {
var scripts = [String]()
var styles = [String]()
components.forEach { component in
component.importedScripts.forEach { script in
if !scripts.contains(script) {
scripts.append(script)
}
}
component.importedStyles.forEach { style in
if !styles.contains(style) {
styles.append(style)
}
}
}
pageStyles.forEach { pageStyle in
if !styles.contains(pageStyle) {
styles.append(pageStyle)
}
}
let htmlScripts = scripts.map { "<script src=\"\($0)\"></script>" }.joined(separator: "\n")
let htmlStyles = styles.map { "<link rel=\"stylesheet\" type=\"text/css\" href=\"\($0)\">" }.joined(separator: "\n")
let inlineStyles = components.compactMap { $0.inlineStyle }.uniqued().joined(separator: "\n")
var htmlInlineStyles = ""
if !inlineStyles.isEmpty {
htmlInlineStyles = "<style>\n\(inlineStyles)\n </style>"
}
return """
<!doctype html>
<html lang="\(language)">
<head>
<meta charset="\(charset)">
<meta name="viewport" content="width=device-width, initial-scale=1">
\(htmlStyles)
\(htmlScripts)
\(htmlInlineStyles)
<title>\(title)</title>
</head>
<body>
<div class="container">
\(components.map { $0.render() }.joined(separator: "\n"))
</div>
</body>
</html>
""".whitespacesFiltered
}
}
public struct HTMLCode : HTMLComponent {
public var importedScripts: [String] = []
public var importedStyles: [String] = []
private let html: String
public let inlineStyle: String?
public init(_ html: String, inlineStyle: String? = nil) {
self.html = html
self.inlineStyle = inlineStyle
}
public func render() -> String {
html
}
}
public struct HTMLTitle : HTMLComponent {
public var importedScripts: [String] = []
public var importedStyles: [String] = []
private let html: String
public let inlineStyle: String? = nil
public init(_ title: String, heading: Int = 1) {
self.html = "<h\(heading)>\(title)</h\(heading)>"
}
public func render() -> String {
html
}
}
public struct HTMLTile: HTMLComponent {
public let importedScripts: [String]
public let importedStyles: [String]
public let inlineStyle: String? = nil
public let components: [HTMLComponent]
public init(_ components: [HTMLComponent] = []) {
self.components = components
self.importedStyles = components.map { $0.importedStyles }.flatMap { $0 }.uniqued()
self.importedScripts = components.map { $0.importedScripts }.flatMap { $0 }.uniqued()
}
public func render() -> String {
"""
<div class="tile is-parent">
<article class="tile is-child box">
\(components.map { $0.render() }.joined())
</article>
</div>
"""
}
}
public struct HTMLTiles: HTMLComponent {
public let importedScripts: [String]
public let importedStyles: [String]
public let inlineStyle: String? = nil
public let tiles: [HTMLTile]
public init(_ tiles: [HTMLTile] = []) {
self.tiles = tiles
self.importedStyles = tiles.map { $0.importedStyles }.flatMap { $0 }.uniqued()
self.importedScripts = tiles.map { $0.importedScripts }.flatMap { $0 }.uniqued()
}
public func render() -> String {
"""
<div class="tile is-ancestor">
\(tiles.map { $0.render() }.joined())
</div>
"""
}
}
// Example usage:
let page = HTMLPage(
title: "My Page",
components: [
HTMLTitle("Hello, world"),
HTMLTiles([
HTMLTile([
HTMLCode("First tile")
]),
HTMLTile([
HTMLCode("Second tile"),
]),
HTMLTile([
HTMLCode("Third tile"),
])
]),
HTMLTiles([
HTMLTile([
HTMLCode("Fourth tile")
]),
HTMLTile([
HTMLCode("Fifth tile"),
]),
HTMLTile([
HTMLCode("Sixth tile"),
])
])
]
)
let destination = FileManager.default
.temporaryDirectory
.appendingPathComponent("test")
.appendingPathExtension("html")
print("Saving page to \(destination.path)")
try page.description.data(using: .utf8)?.write(to: destination)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment