Make the site generator render index.html with a layout

This commit is contained in:
Sami Samhuri 2019-12-01 13:53:40 -08:00
parent 57bdf5d14a
commit ea2b53d625
21 changed files with 247 additions and 18 deletions

View file

@ -12,4 +12,8 @@ publish_beta: compile
@echo
./bin/publish --beta --delete
.PHONY: compile publish publish_beta
test:
@echo
./bin/test
.PHONY: compile publish publish_beta test

View file

@ -19,9 +19,9 @@ Execution, trying TDD for the first time:
- [x] Write a test harness that renders a site and then checks the output with `diff -r`
- [ ] Port _layout.ejs to Swift code
- [x] Write a site generator that renders www/index.html from site.json
- [ ] Write a site generator that renders www/index.html from site.json
- [ ] Port _layout.ejs to Swift code
- [ ] Add support for CSS files

5
SiteGenerator/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
/.swiftpm

View file

@ -0,0 +1,34 @@
{
"object": {
"pins": [
{
"package": "PathKit",
"repositoryURL": "https://github.com/kylef/PathKit.git",
"state": {
"branch": null,
"revision": "e2f5be30e4c8f531c9c1e8765aa7b71c0a45d7a0",
"version": "0.9.2"
}
},
{
"package": "Spectre",
"repositoryURL": "https://github.com/kylef/Spectre.git",
"state": {
"branch": null,
"revision": "f14ff47f45642aa5703900980b014c2e9394b6e5",
"version": "0.9.0"
}
},
{
"package": "Stencil",
"repositoryURL": "https://github.com/stencilproject/Stencil.git",
"state": {
"branch": null,
"revision": "0e9a78d6584e3812cd9c09494d5c7b483e8f533c",
"version": "0.13.1"
}
}
]
},
"version": 1
}

View file

@ -0,0 +1,21 @@
// swift-tools-version:5.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "SiteGenerator",
dependencies: [
.package(url: "https://github.com/stencilproject/Stencil.git", from: "0.13.0")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "SiteGenerator",
dependencies: ["Stencil"]),
.testTarget(
name: "SiteGeneratorTests",
dependencies: ["SiteGenerator"]),
]
)

5
SiteGenerator/Readme.md Normal file
View file

@ -0,0 +1,5 @@
# SiteGenerator
A static site generator for [samhuri.net](https://samhuri.net).
See https://github.com/samsonjs/samhuri.net for details.

View file

@ -0,0 +1,34 @@
//
// Generator.swift
// SiteGenerator
//
// Created by Sami Samhuri on 2019-12-01.
//
import Foundation
import PathKit
import Stencil
public final class Generator {
private let fileManager: FileManager = .default
public let site: Site
public let sourceURL: URL
private let renderer: Environment
public init(sourceURL: URL) throws {
let publicURL = sourceURL.appendingPathComponent("public")
self.renderer = Environment(loader: FileSystemLoader(paths: [Path(publicURL.path)]))
let siteURL = sourceURL.appendingPathComponent("site.json")
self.site = try Site.decode(from: siteURL)
self.sourceURL = sourceURL
}
public func generate(targetURL: URL) throws {
try fileManager.createDirectory(at: targetURL, withIntermediateDirectories: true, attributes: nil)
let indexHTML = try renderer.renderTemplate(name: "index.html", context: ["site": site])
let indexURL = targetURL.appendingPathComponent("index.html")
try indexHTML.write(to: indexURL, atomically: true, encoding: .utf8)
}
}

View file

@ -0,0 +1,22 @@
//
// Site.swift
// SiteGenerator
//
// Created by Sami Samhuri on 2019-12-01.
//
import Foundation
public struct Site: Codable {
public let author: String
public let email: String
public let title: String
public let url: String
}
public extension Site {
static func decode(from url: URL) throws -> Site {
let json = try Data(contentsOf: url)
return try JSONDecoder().decode(Site.self, from: json)
}
}

View file

@ -0,0 +1,22 @@
//
// main.swift
// SiteGenerator
//
// Created by Sami Samhuri on 2019-12-01.
//
import Foundation
func main(sourcePath: String, targetPath: String) throws {
let sourceURL = URL(fileURLWithPath: sourcePath)
let targetURL = URL(fileURLWithPath: targetPath)
let generator = try Generator(sourceURL: sourceURL)
try generator.generate(targetURL: targetURL)
}
let sourcePath = CommandLine.arguments[1]
let targetPath = CommandLine.arguments[2]
// TODO: validate args
try! main(sourcePath: sourcePath, targetPath: targetPath)

View file

@ -0,0 +1,7 @@
import XCTest
import SiteGeneratorTests
var tests = [XCTestCaseEntry]()
tests += SiteGeneratorTests.allTests()
XCTMain(tests)

View file

@ -0,0 +1,47 @@
import XCTest
import class Foundation.Bundle
final class SiteGeneratorTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
// Some of the APIs that we use below are available in macOS 10.13 and above.
guard #available(macOS 10.13, *) else {
return
}
let fooBinary = productsDirectory.appendingPathComponent("SiteGenerator")
let process = Process()
process.executableURL = fooBinary
let pipe = Pipe()
process.standardOutput = pipe
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
XCTAssertEqual(output, "")
}
/// Returns path to the built products directory.
var productsDirectory: URL {
#if os(macOS)
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
return bundle.bundleURL.deletingLastPathComponent()
}
fatalError("couldn't find the products directory")
#else
return Bundle.main.bundleURL
#endif
}
static var allTests = [
("testExample", testExample),
]
}

View file

@ -0,0 +1,9 @@
import XCTest
#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(SiteGeneratorTests.allTests),
]
}
#endif

View file

@ -8,9 +8,8 @@ SOURCE_DIR="$1"
TARGET_DIR="$2"
function main() {
echo "* copy files from $SOURCE_DIR to $TARGET_DIR"
mkdir -p "$TARGET_DIR"
cp -rp "$SOURCE_DIR"/* "$TARGET_DIR"
echo "* generate site from $SOURCE_DIR into $TARGET_DIR"
"$THIS_DIR/sitegen" "$SOURCE_DIR" "$TARGET_DIR"
# echo "* compile rss feed"
# compile_feeds

BIN
bin/sitegen Executable file

Binary file not shown.

View file

@ -2,8 +2,13 @@
set -e
pushd "SiteGenerator" >/dev/null
swift build
cp .build/x86_64-apple-macosx/debug/SiteGenerator ../bin/sitegen
popd >/dev/null
for site in Tests/test-*; do
bin/compile "$site/in" "$site/actual" >/dev/null
diff -r "$site/expected" "$site/actual"
bin/compile "$site/in" "$site/actual" # >/dev/null
diff -ru "$site/expected" "$site/actual"
rm -r "$site/actual"
done

View file

@ -1,3 +1,12 @@
<!doctype html>
<html>
<head>
<title>Valar Morghulis</title>
</head>
<body>
<h1><a href="http://example.net">Valar Morghulis</a></h1>
<p>hello world</p>
</body>
</html>

View file

@ -1,6 +0,0 @@
{
"title": "Valar Morghulis",
"author": "A man has no name",
"email": "jaqen@hotmail.com",
"url": "http://example.net"
}

View file

@ -1,3 +0,0 @@
<!doctype html>
<html>
</html>

View file

@ -0,0 +1,4 @@
{% extends "layout.html" %}
{% block content %}
<p>hello world</p>
{% endblock %}

View file

@ -0,0 +1,11 @@
<!doctype html>
<html>
<head>
<title>{{ site.title }}</title>
</head>
<body>
<h1><a href="{{ site.url }}">{{ site.title }}</a></h1>
{% block content %}
{% endblock %}
</body>
</html>

View file

@ -1,6 +1,6 @@
{
"title": "Valar Morghulis",
"author": "A man has no name",
"email": "jaqen@hotmail.com",
"title": "Valar Morghulis",
"url": "http://example.net"
}