Last active
May 5, 2022 11:40
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/[email protected]/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