mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-04-27 14:57:40 +00:00
Migrate projects plugin from Stencil and JSON to Swift
This commit is contained in:
parent
640c76d967
commit
0c17d5c543
21 changed files with 317 additions and 245 deletions
|
|
@ -121,9 +121,9 @@ Execution, trying TDD for the first time:
|
||||||
|
|
||||||
- [x] Replace page template with Swift code
|
- [x] Replace page template with Swift code
|
||||||
|
|
||||||
- [ ] Replace projects.json with Swift code
|
- [x] Replace projects.json with Swift code
|
||||||
|
|
||||||
- [ ] Replace project templates with Swift code
|
- [x] Replace project templates with Swift code
|
||||||
|
|
||||||
- [ ] Replace post templates with Swift code
|
- [ ] Replace post templates with Swift code
|
||||||
|
|
||||||
|
|
|
||||||
8
SiteGenerator/Sources/SiteGenerator/File.swift
Normal file
8
SiteGenerator/Sources/SiteGenerator/File.swift
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
//
|
||||||
|
// File.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
@ -7,8 +7,14 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct Project: Codable {
|
public struct Project {
|
||||||
let title: String
|
public let title: String
|
||||||
let description: String
|
public let description: String
|
||||||
var path: String!
|
public let url: URL
|
||||||
|
|
||||||
|
public init(title: String, description: String, url: URL) {
|
||||||
|
self.title = title
|
||||||
|
self.description = description
|
||||||
|
self.url = url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,42 +7,47 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
private struct Projects: Codable {
|
struct PartialProject {
|
||||||
let projects: [Project]
|
let title: String
|
||||||
|
let description: String
|
||||||
static func decode(from url: URL) throws -> Projects {
|
|
||||||
let json = try Data(contentsOf: url)
|
|
||||||
let projects = try JSONDecoder().decode(Projects.self, from: json)
|
|
||||||
return projects
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ProjectsPlugin: Plugin {
|
public final class ProjectsPlugin: Plugin {
|
||||||
let fileManager: FileManager = .default
|
let fileManager: FileManager = .default
|
||||||
let outputPath: String
|
let outputPath: String
|
||||||
|
let partialProjects: [PartialProject]
|
||||||
let templateRenderer: ProjectsTemplateRenderer
|
let templateRenderer: ProjectsTemplateRenderer
|
||||||
|
let projectAssets: TemplateAssets
|
||||||
|
|
||||||
var projects: [Project] = []
|
var projects: [Project] = []
|
||||||
var sourceURL: URL!
|
var sourceURL: URL!
|
||||||
|
|
||||||
init(templateRenderer: ProjectsTemplateRenderer, outputPath: String? = nil) {
|
init(
|
||||||
|
projects: [PartialProject],
|
||||||
|
templateRenderer: ProjectsTemplateRenderer,
|
||||||
|
projectAssets: TemplateAssets,
|
||||||
|
outputPath: String? = nil
|
||||||
|
) {
|
||||||
|
self.partialProjects = projects
|
||||||
self.templateRenderer = templateRenderer
|
self.templateRenderer = templateRenderer
|
||||||
|
self.projectAssets = projectAssets
|
||||||
self.outputPath = outputPath ?? "projects"
|
self.outputPath = outputPath ?? "projects"
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Plugin methods
|
// MARK: - Plugin methods
|
||||||
|
|
||||||
func setUp(site: Site, sourceURL: URL) throws {
|
public func setUp(site: Site, sourceURL: URL) throws {
|
||||||
self.sourceURL = sourceURL
|
self.sourceURL = sourceURL
|
||||||
let projectsURL = sourceURL.appendingPathComponent("projects.json")
|
projects = partialProjects.map { partial in
|
||||||
if fileManager.fileExists(atPath: projectsURL.path) {
|
Project(
|
||||||
self.projects = try Projects.decode(from: projectsURL).projects.map { project in
|
title: partial.title,
|
||||||
Project(title: project.title, description: project.description, path: "/\(outputPath)/\(project.title)")
|
description: partial.description,
|
||||||
}
|
url: site.url.appendingPathComponent("\(outputPath)/\(partial.title)")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(site: Site, targetURL: URL) throws {
|
public func render(site: Site, targetURL: URL) throws {
|
||||||
guard !projects.isEmpty else {
|
guard !projects.isEmpty else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -50,18 +55,12 @@ final class ProjectsPlugin: Plugin {
|
||||||
let projectsDir = targetURL.appendingPathComponent(outputPath)
|
let projectsDir = targetURL.appendingPathComponent(outputPath)
|
||||||
try fileManager.createDirectory(at: projectsDir, withIntermediateDirectories: true, attributes: nil)
|
try fileManager.createDirectory(at: projectsDir, withIntermediateDirectories: true, attributes: nil)
|
||||||
let projectsURL = projectsDir.appendingPathComponent("index.html")
|
let projectsURL = projectsDir.appendingPathComponent("index.html")
|
||||||
let projectsHTML = try templateRenderer.renderTemplate(.projects, site: site, context: [
|
let projectsHTML = try templateRenderer.renderProjects(projects, site: site, assets: .none())
|
||||||
"title": "Projects",
|
|
||||||
"projects": projects,
|
|
||||||
])
|
|
||||||
try projectsHTML.write(to: projectsURL, atomically: true, encoding: .utf8)
|
try projectsHTML.write(to: projectsURL, atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
for project in projects {
|
for project in projects {
|
||||||
let projectURL = projectsDir.appendingPathComponent("\(project.title)/index.html")
|
let projectURL = projectsDir.appendingPathComponent("\(project.title)/index.html")
|
||||||
let projectHTML = try templateRenderer.renderTemplate(.project, site: site, context: [
|
let projectHTML = try templateRenderer.renderProject(project, site: site, assets: projectAssets)
|
||||||
"title": "\(project.title)",
|
|
||||||
"project": project,
|
|
||||||
])
|
|
||||||
try fileManager.createDirectory(at: projectURL.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil)
|
try fileManager.createDirectory(at: projectURL.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil)
|
||||||
try projectHTML.write(to: projectURL, atomically: true, encoding: .utf8)
|
try projectHTML.write(to: projectURL, atomically: true, encoding: .utf8)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
//
|
||||||
|
// ProjectsPluginBuilder.swift
|
||||||
|
// SiteGenerator
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public final class ProjectsPluginBuilder {
|
||||||
|
let templateRenderer: ProjectsTemplateRenderer
|
||||||
|
private var path: String?
|
||||||
|
private var projects: [PartialProject] = []
|
||||||
|
private var projectAssets: TemplateAssets?
|
||||||
|
|
||||||
|
public init(templateRenderer: ProjectsTemplateRenderer) {
|
||||||
|
self.templateRenderer = templateRenderer
|
||||||
|
}
|
||||||
|
|
||||||
|
public func path(_ path: String) -> ProjectsPluginBuilder {
|
||||||
|
precondition(self.path == nil, "path is already defined")
|
||||||
|
self.path = path
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
public func projectAssets(_ projectAssets: TemplateAssets) -> ProjectsPluginBuilder {
|
||||||
|
precondition(self.projectAssets == nil, "projectAssets are already defined")
|
||||||
|
self.projectAssets = projectAssets
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
public func add(_ title: String, description: String) -> ProjectsPluginBuilder {
|
||||||
|
let project = PartialProject(title: title, description: description)
|
||||||
|
projects.append(project)
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
public func build() -> ProjectsPlugin {
|
||||||
|
if projects.isEmpty {
|
||||||
|
print("WARNING: No projects have been added")
|
||||||
|
}
|
||||||
|
return ProjectsPlugin(
|
||||||
|
projects: projects,
|
||||||
|
templateRenderer: templateRenderer,
|
||||||
|
projectAssets: projectAssets ?? .none(),
|
||||||
|
outputPath: path
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,11 +7,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public enum ProjectTemplate {
|
|
||||||
case project
|
|
||||||
case projects
|
|
||||||
}
|
|
||||||
|
|
||||||
public protocol ProjectsTemplateRenderer {
|
public protocol ProjectsTemplateRenderer {
|
||||||
func renderTemplate(_ template: ProjectTemplate, site: Site, context: [String: Any]) throws -> String
|
func renderProjects(_ projects: [Project], site: Site, assets: TemplateAssets) throws -> String
|
||||||
|
func renderProject(_ project: Project, site: Site, assets: TemplateAssets) throws -> String
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,17 +76,3 @@ public extension SiteBuilder {
|
||||||
renderer(MarkdownRenderer(pageRenderer: pageRenderer))
|
renderer(MarkdownRenderer(pageRenderer: pageRenderer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Projects
|
|
||||||
|
|
||||||
public extension SiteBuilder {
|
|
||||||
func projects(templateRenderer: ProjectsTemplateRenderer, path: String? = nil) -> SiteBuilder {
|
|
||||||
plugin(ProjectsPlugin(templateRenderer: templateRenderer, outputPath: path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Posts
|
|
||||||
|
|
||||||
public extension SiteBuilder {
|
|
||||||
// anything nice we can do there?
|
|
||||||
}
|
|
||||||
|
|
|
||||||
22
SiteGenerator/Sources/SiteGenerator/TemplateAssets.swift
Normal file
22
SiteGenerator/Sources/SiteGenerator/TemplateAssets.swift
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// TemplateAssets.swift
|
||||||
|
// SiteGenerator
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct TemplateAssets {
|
||||||
|
public let scripts: [String]
|
||||||
|
public let styles: [String]
|
||||||
|
|
||||||
|
public init(scripts: [String], styles: [String]) {
|
||||||
|
self.scripts = scripts
|
||||||
|
self.styles = styles
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func none() -> TemplateAssets {
|
||||||
|
TemplateAssets(scripts: [], styles: [])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
{
|
|
||||||
"projects": [
|
|
||||||
{
|
|
||||||
"title": "bin",
|
|
||||||
"description": "my collection of scripts in ~/bin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "config",
|
|
||||||
"description": "important dot files (zsh, emacs, vim, screen)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "compiler",
|
|
||||||
"description": "a compiler targeting x86 in Ruby"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "lake",
|
|
||||||
"description": "a simple implementation of Scheme in C"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "strftime",
|
|
||||||
"description": "strftime for JavaScript"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "format",
|
|
||||||
"description": "printf for JavaScript"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "gitter",
|
|
||||||
"description": "a GitHub client for Node (v3 API)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "mojo.el",
|
|
||||||
"description": "turn emacs into a sweet mojo editor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "ThePusher",
|
|
||||||
"description": "Github post-receive hook router"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "NorthWatcher",
|
|
||||||
"description": "cron for filesystem changes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "repl-edit",
|
|
||||||
"description": "edit Node repl commands with your text editor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "cheat.el",
|
|
||||||
"description": "cheat from emacs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "batteries",
|
|
||||||
"description": "a general purpose node library"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "samhuri.net",
|
|
||||||
"description": "this site"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
14
samhuri.net/Sources/samhuri.net/Date+Sugar.swift
Normal file
14
samhuri.net/Sources/samhuri.net/Date+Sugar.swift
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
//
|
||||||
|
// Date+Sugar.swift
|
||||||
|
// samhuri.net
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Date {
|
||||||
|
var year: Int {
|
||||||
|
Calendar.current.dateComponents([.year], from: self).year!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,11 +6,11 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import SiteGenerator
|
||||||
|
|
||||||
struct Page {
|
struct Page {
|
||||||
let title: String
|
let title: String
|
||||||
let styles: [String]
|
let templateAssets: TemplateAssets
|
||||||
let scripts: [String]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Page {
|
extension Page {
|
||||||
|
|
@ -22,6 +22,6 @@ extension Page {
|
||||||
.split(separator: ",")
|
.split(separator: ",")
|
||||||
.map { $0.trimmingCharacters(in: .whitespaces) }
|
.map { $0.trimmingCharacters(in: .whitespaces) }
|
||||||
let title = metadata["Title", default: ""]
|
let title = metadata["Title", default: ""]
|
||||||
self.init(title: title, styles: styles, scripts: scripts)
|
self.init(title: title, templateAssets: TemplateAssets(scripts: scripts, styles: styles))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,15 +22,17 @@ final class PageRenderer {
|
||||||
let loader = FileSystemLoader(paths: [templatesPath])
|
let loader = FileSystemLoader(paths: [templatesPath])
|
||||||
self.stencil = Environment(loader: loader)
|
self.stencil = Environment(loader: loader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func render(_ body: Node<HTML.BodyContext>, context: TemplateContext) -> String {
|
||||||
|
Template.site(body: body, context: context).render(indentedBy: .spaces(2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PageRenderer: MarkdownPageRenderer {
|
extension PageRenderer: MarkdownPageRenderer {
|
||||||
func renderPage(site: Site, bodyHTML: String, metadata: [String: String]) throws -> String {
|
func renderPage(site: Site, bodyHTML: String, metadata: [String: String]) throws -> String {
|
||||||
let page = Page(metadata: metadata)
|
let pageTitle = metadata["Title", default: ""]
|
||||||
let context = PageContext(site: site, body: bodyHTML, page: page, metadata: metadata)
|
let context = SiteContext(site: site, subtitle: pageTitle, templateAssets: .none())
|
||||||
let body: Node<HTML.BodyContext> = .page(title: page.title, bodyHTML: bodyHTML)
|
return render(.page(title: pageTitle, bodyHTML: bodyHTML), context: context)
|
||||||
return Template.site(body: body, context: context)
|
|
||||||
.render(indentedBy: .spaces(2))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,34 +60,21 @@ extension PostTemplate {
|
||||||
|
|
||||||
extension PageRenderer: PostsTemplateRenderer {
|
extension PageRenderer: PostsTemplateRenderer {
|
||||||
func renderTemplate(_ template: PostTemplate, site: Site, context: [String : Any]) throws -> String {
|
func renderTemplate(_ template: PostTemplate, site: Site, context: [String : Any]) throws -> String {
|
||||||
let siteContext = SiteContext(site: site)
|
let siteContext = SiteContext(site: site, subtitle: nil, templateAssets: .none())
|
||||||
let contextDict = siteContext.dictionary.merging(context, uniquingKeysWith: { _, new in new })
|
let contextDict = siteContext.dictionary.merging(context, uniquingKeysWith: { _, new in new })
|
||||||
return try stencil.renderTemplate(name: template.htmlFilename, context: contextDict)
|
return try stencil.renderTemplate(name: template.htmlFilename, context: contextDict)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProjectTemplate {
|
|
||||||
@available(*, deprecated)
|
|
||||||
var htmlFilename: String {
|
|
||||||
switch self {
|
|
||||||
case .project:
|
|
||||||
return "project.html"
|
|
||||||
case .projects:
|
|
||||||
return "projects.html"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PageRenderer: ProjectsTemplateRenderer {
|
extension PageRenderer: ProjectsTemplateRenderer {
|
||||||
func renderTemplate(_ template: ProjectTemplate, site: Site, context: [String : Any]) throws -> String {
|
func renderProjects(_ projects: [Project], site: Site, assets: TemplateAssets) throws -> String {
|
||||||
let siteContext = SiteContext(site: site)
|
let context = SiteContext(site: site, subtitle: "Projects", templateAssets: assets)
|
||||||
let contextDict = siteContext.dictionary.merging(context, uniquingKeysWith: { _, new in new })
|
return render(.projects(projects), context: context)
|
||||||
return try stencil.renderTemplate(name: template.htmlFilename, context: contextDict)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension Date {
|
func renderProject(_ project: Project, site: Site, assets: TemplateAssets) throws -> String {
|
||||||
var year: Int {
|
let projectContext = ProjectContext(project: project, site: site, templateAssets: assets)
|
||||||
Calendar.current.dateComponents([.year], from: self).year!
|
let context = SiteContext(site: site, subtitle: project.title, templateAssets: assets)
|
||||||
|
return render(.project(projectContext), context: context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
//
|
||||||
|
// ProjectContext.swift
|
||||||
|
// samhuri.net
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SiteGenerator
|
||||||
|
|
||||||
|
struct ProjectContext: TemplateContext {
|
||||||
|
let site: Site
|
||||||
|
let title: String
|
||||||
|
let description: String
|
||||||
|
let githubURL: URL
|
||||||
|
let templateAssets: TemplateAssets
|
||||||
|
|
||||||
|
init(project: Project, site: Site, templateAssets: TemplateAssets) {
|
||||||
|
self.site = site
|
||||||
|
self.title = project.title
|
||||||
|
self.description = project.description
|
||||||
|
self.githubURL = URL(string: "https://github.com/samsonjs/\(title)")!
|
||||||
|
self.templateAssets = templateAssets
|
||||||
|
}
|
||||||
|
|
||||||
|
var stargazersURL: URL {
|
||||||
|
githubURL.appendingPathComponent("stargazers")
|
||||||
|
}
|
||||||
|
|
||||||
|
var networkURL: URL {
|
||||||
|
githubURL.appendingPathComponent("network/members")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
//
|
||||||
|
// ProjectTemplates.swift
|
||||||
|
// samhuri.net
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Plot
|
||||||
|
import SiteGenerator
|
||||||
|
|
||||||
|
extension Node where Context == HTML.BodyContext {
|
||||||
|
static func projects(_ projects: [Project]) -> Node<HTML.BodyContext> {
|
||||||
|
.group([
|
||||||
|
.article(.class("container"),
|
||||||
|
.h1("Projects"),
|
||||||
|
|
||||||
|
.group(projects.map { project in
|
||||||
|
.div(.class("project-listing"),
|
||||||
|
.h4(.a(.href(project.url), .text(project.title))),
|
||||||
|
.p(.class("description"), .text(project.description))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
.div(.class("row clearfix"),
|
||||||
|
.p(.class("fin"), .i(.class("fa fa-code")))
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
static func project(_ context: ProjectContext) -> Node<HTML.BodyContext> {
|
||||||
|
.group([
|
||||||
|
.article(.class("container project"),
|
||||||
|
// projects.js picks up this data-title attribute and uses it to render all the Github stuff
|
||||||
|
.h1(.id("project"), .data(named: "title", value: context.title), .text(context.title)),
|
||||||
|
.h4(.text(context.description)),
|
||||||
|
|
||||||
|
.div(.class("project-stats"),
|
||||||
|
.p(
|
||||||
|
.a(.href(context.githubURL), "GitHub"),
|
||||||
|
"•",
|
||||||
|
.a(.id("nstar"), .href(context.stargazersURL)),
|
||||||
|
"•",
|
||||||
|
.a(.id("nfork"), .href(context.networkURL))
|
||||||
|
),
|
||||||
|
.p("Last updated on ", .span(.id("updated")))
|
||||||
|
),
|
||||||
|
|
||||||
|
.div(.class("project-info row clearfix"),
|
||||||
|
.div(.class("column half"),
|
||||||
|
.h3("Contributors"),
|
||||||
|
.div(.id("contributors"))
|
||||||
|
),
|
||||||
|
.div(.class("column half"),
|
||||||
|
.h3("Languages"),
|
||||||
|
.div(.id("langs"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
.div(.class("row clearfix"),
|
||||||
|
.p(.class("fin"), .i(.class("fa fa-code")))
|
||||||
|
),
|
||||||
|
|
||||||
|
.group(context.scripts.map { url in
|
||||||
|
.script(.attribute(named: "defer"), .src(url))
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,16 +18,8 @@ struct PageContext: TemplateContext {
|
||||||
"\(site.title): \(page.title)"
|
"\(site.title): \(page.title)"
|
||||||
}
|
}
|
||||||
|
|
||||||
var styles: [URL] {
|
var templateAssets: TemplateAssets {
|
||||||
(site.styles + page.styles).map { style in
|
page.templateAssets
|
||||||
style.hasPrefix("http") ? URL(string: style)! : url(for: style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var scripts: [URL] {
|
|
||||||
(site.scripts + page.scripts).map { script in
|
|
||||||
script.hasPrefix("http") ? URL(string: script)! : url(for: script)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,8 +31,8 @@ extension PageContext {
|
||||||
"body": body,
|
"body": body,
|
||||||
"page": page,
|
"page": page,
|
||||||
"metadata": metadata,
|
"metadata": metadata,
|
||||||
"styles": site.styles + page.styles,
|
"styles": site.styles + templateAssets.styles,
|
||||||
"scripts": site.scripts + page.scripts,
|
"scripts": site.scripts + templateAssets.scripts,
|
||||||
"currentYear": Date().year,
|
"currentYear": Date().year,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,7 @@ import SiteGenerator
|
||||||
struct SiteContext: TemplateContext {
|
struct SiteContext: TemplateContext {
|
||||||
let site: Site
|
let site: Site
|
||||||
let subtitle: String?
|
let subtitle: String?
|
||||||
|
let templateAssets: TemplateAssets
|
||||||
init(site: Site, subtitle: String? = nil) {
|
|
||||||
self.site = site
|
|
||||||
self.subtitle = subtitle
|
|
||||||
}
|
|
||||||
|
|
||||||
var title: String {
|
var title: String {
|
||||||
guard let subtitle = subtitle else {
|
guard let subtitle = subtitle else {
|
||||||
|
|
@ -24,18 +20,6 @@ struct SiteContext: TemplateContext {
|
||||||
|
|
||||||
return "\(site.title): \(subtitle)"
|
return "\(site.title): \(subtitle)"
|
||||||
}
|
}
|
||||||
|
|
||||||
var styles: [URL] {
|
|
||||||
site.styles.map { style in
|
|
||||||
style.hasPrefix("http") ? URL(string: style)! : url(for: style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var scripts: [URL] {
|
|
||||||
site.scripts.map { script in
|
|
||||||
script.hasPrefix("http") ? URL(string: script)! : url(for: script)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SiteContext {
|
extension SiteContext {
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,36 @@ protocol TemplateContext {
|
||||||
|
|
||||||
var site: Site { get }
|
var site: Site { get }
|
||||||
var title: String { get }
|
var title: String { get }
|
||||||
var styles: [URL] { get }
|
var templateAssets: TemplateAssets { get }
|
||||||
var scripts: [URL] { get }
|
|
||||||
|
|
||||||
// These all have default implementations
|
// These all have default implementations
|
||||||
|
|
||||||
|
var styles: [URL] { get }
|
||||||
|
var scripts: [URL] { get }
|
||||||
|
|
||||||
var currentYear: Int { get }
|
var currentYear: Int { get }
|
||||||
|
|
||||||
func url(for path: String) -> URL
|
func url(for path: String) -> URL
|
||||||
func imageURL(_ filename: String) -> URL
|
func imageURL(_ filename: String) -> URL
|
||||||
|
func scriptURL(_ filename: String) -> URL
|
||||||
|
func styleURL(_ filename: String) -> URL
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TemplateContext {
|
extension TemplateContext {
|
||||||
|
var styles: [URL] {
|
||||||
|
let allStyles = site.styles + templateAssets.styles
|
||||||
|
return allStyles.map { style in
|
||||||
|
style.hasPrefix("http") ? URL(string: style)! : styleURL(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var scripts: [URL] {
|
||||||
|
let allScripts = site.scripts + templateAssets.scripts
|
||||||
|
return allScripts.map { script in
|
||||||
|
script.hasPrefix("http") ? URL(string: script)! : scriptURL(script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var currentYear: Int {
|
var currentYear: Int {
|
||||||
Date().year
|
Date().year
|
||||||
}
|
}
|
||||||
|
|
@ -38,4 +56,16 @@ extension TemplateContext {
|
||||||
.appendingPathComponent("images")
|
.appendingPathComponent("images")
|
||||||
.appendingPathComponent(filename)
|
.appendingPathComponent(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scriptURL(_ filename: String) -> URL {
|
||||||
|
site.url
|
||||||
|
.appendingPathComponent("js")
|
||||||
|
.appendingPathComponent(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func styleURL(_ filename: String) -> URL {
|
||||||
|
site.url
|
||||||
|
.appendingPathComponent("css")
|
||||||
|
.appendingPathComponent(filename)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,30 @@ public extension samhuri {
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSite(renderer: PageRenderer) -> Site {
|
func buildSite(renderer: PageRenderer) -> Site {
|
||||||
|
let projectsPlugin = ProjectsPluginBuilder(templateRenderer: renderer)
|
||||||
|
.path("projects")
|
||||||
|
.projectAssets(TemplateAssets(scripts: [
|
||||||
|
"https://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js",
|
||||||
|
"gitter.js",
|
||||||
|
"store.js",
|
||||||
|
"projects.js",
|
||||||
|
], styles: []))
|
||||||
|
.add("bin", description: "my collection of scripts in ~/bin")
|
||||||
|
.add("config", description: "important dot files (zsh, emacs, vim, screen)")
|
||||||
|
.add("compiler", description: "a compiler targeting x86 in Ruby")
|
||||||
|
.add("lake", description: "a simple implementation of Scheme in C")
|
||||||
|
.add("strftime", description: "strftime for JavaScript")
|
||||||
|
.add("format", description: "printf for JavaScript")
|
||||||
|
.add("gitter", description: "a GitHub client for Node (v3 API)")
|
||||||
|
.add("mojo.el", description: "turn emacs into a sweet mojo editor")
|
||||||
|
.add("ThePusher", description: "Github post-receive hook router")
|
||||||
|
.add("NorthWatcher", description: "cron for filesystem changes")
|
||||||
|
.add("repl-edit", description: "edit Node repl commands with your text editor")
|
||||||
|
.add("cheat.el", description: "cheat from emacs")
|
||||||
|
.add("batteries", description: "a general purpose node library")
|
||||||
|
.add("samhuri.net", description: "this site")
|
||||||
|
.build()
|
||||||
|
|
||||||
let postsPlugin = PostsPluginBuilder(templateRenderer: renderer)
|
let postsPlugin = PostsPluginBuilder(templateRenderer: renderer)
|
||||||
.path("posts")
|
.path("posts")
|
||||||
.jsonFeed(
|
.jsonFeed(
|
||||||
|
|
@ -29,10 +53,10 @@ public extension samhuri {
|
||||||
email: "sami@samhuri.net",
|
email: "sami@samhuri.net",
|
||||||
url: siteURLOverride ?? URL(string: "https://samhuri.net")!
|
url: siteURLOverride ?? URL(string: "https://samhuri.net")!
|
||||||
)
|
)
|
||||||
.styles("/css/normalize.css", "/css/style.css")
|
.styles("normalize.css", "style.css")
|
||||||
.styles("https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css")
|
.styles("https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css")
|
||||||
.renderMarkdown(pageRenderer: renderer)
|
.renderMarkdown(pageRenderer: renderer)
|
||||||
.projects(templateRenderer: renderer)
|
.plugin(projectsPlugin)
|
||||||
.plugin(postsPlugin)
|
.plugin(postsPlugin)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
{% extends "samhuri.net.html" %}
|
|
||||||
{% block body %}
|
|
||||||
<article class="container project">
|
|
||||||
<!-- projects.js picks up this data-title attribute and uses it to render all the Github stuff -->
|
|
||||||
<h1 id="project" data-title="{{ project.title }}">{{ project.title }}</h1>
|
|
||||||
<h4>{{ project.description }}</h4>
|
|
||||||
|
|
||||||
<div class="project-stats">
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/samsonjs/{{ project.title }}">GitHub</a>
|
|
||||||
•
|
|
||||||
<a id="nstar" href="https://github.com/samsonjs/{{ project.title }}/stargazers"></a>
|
|
||||||
•
|
|
||||||
<a id="nfork" href="https://github.com/samsonjs/{{ project.title }}/network/members"></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Last updated on <span id="updated"></span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="project-info row clearfix">
|
|
||||||
<div class="column half">
|
|
||||||
<h3>Contributors</h3>
|
|
||||||
<div id="contributors"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column half">
|
|
||||||
<h3>Languages</h3>
|
|
||||||
<div id="langs"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<div class="row clearfix">
|
|
||||||
<p class="fin"><i class="fa fa-code"></i></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script defer src="https://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js"></script>
|
|
||||||
<script defer src="/js/gitter.js"></script>
|
|
||||||
<script defer src="/js/store.js"></script>
|
|
||||||
<script defer src="/js/projects.js"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
{% extends "samhuri.net.html" %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
|
|
||||||
<article class="container">
|
|
||||||
<h1>Projects</h1>
|
|
||||||
|
|
||||||
{% for project in projects %}
|
|
||||||
<div class="project-listing">
|
|
||||||
<h4><a href="{{ project.path }}">{{ project.title }}</a></h4>
|
|
||||||
<p class="description">{{ project.description }}</p>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</article>
|
|
||||||
<div class="row clearfix">
|
|
||||||
<p class="fin"><i class="fa fa-code"></i></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
(function() {
|
(function() {
|
||||||
var css = document.createElement('link');
|
var css = document.createElement('link');
|
||||||
css.href = '{{ style }}';
|
css.href = '/css/{{ style }}';
|
||||||
css.rel = 'stylesheet';
|
css.rel = 'stylesheet';
|
||||||
css.type = 'text/css';
|
css.type = 'text/css';
|
||||||
document.getElementsByTagName('head')[0].appendChild(css);
|
document.getElementsByTagName('head')[0].appendChild(css);
|
||||||
|
|
@ -72,16 +72,6 @@
|
||||||
</script>
|
</script>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
(function() {
|
|
||||||
var css = document.createElement('link');
|
|
||||||
css.href = '//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css';
|
|
||||||
css.rel = 'stylesheet';
|
|
||||||
css.type = 'text/css';
|
|
||||||
document.getElementsByTagName('head')[0].appendChild(css);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script src="https://use.typekit.net/tcm1whv.js" crossorigin="anonymous"></script>
|
<script src="https://use.typekit.net/tcm1whv.js" crossorigin="anonymous"></script>
|
||||||
<script>try{Typekit.load({ async: true });}catch(e){}</script>
|
<script>try{Typekit.load({ async: true });}catch(e){}</script>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue