Compare commits
6 Commits
cfb68f5237
...
3d9551117d
Author | SHA1 | Date | |
---|---|---|---|
|
3d9551117d | ||
|
1405fa5ee7 | ||
|
abd42e4909 | ||
|
570cebb5d0 | ||
|
81b373fb5a | ||
|
28623d1209 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
config.json
|
@ -78,6 +78,13 @@ struct Element {
|
|||||||
*/
|
*/
|
||||||
let requiredFiles: Set<String>
|
let requiredFiles: Set<String>
|
||||||
|
|
||||||
|
/**
|
||||||
|
Additional images required by the element.
|
||||||
|
|
||||||
|
These images are specified as: `source_name destination_name width (height)`.
|
||||||
|
*/
|
||||||
|
let images: [ManualImage]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The style of thumbnail to use when generating overviews.
|
The style of thumbnail to use when generating overviews.
|
||||||
|
|
||||||
@ -141,7 +148,7 @@ struct Element {
|
|||||||
- Parameter folder: The root folder of the site content.
|
- Parameter folder: The root folder of the site content.
|
||||||
- Note: Uses global objects.
|
- Note: Uses global objects.
|
||||||
*/
|
*/
|
||||||
init?(atRoot folder: URL) throws {
|
init?(atRoot folder: URL) {
|
||||||
self.inputFolder = folder
|
self.inputFolder = folder
|
||||||
self.path = ""
|
self.path = ""
|
||||||
|
|
||||||
@ -160,6 +167,7 @@ struct Element {
|
|||||||
self.sortIndex = log.unused(metadata.sortIndex, "sortIndex", source: source)
|
self.sortIndex = log.unused(metadata.sortIndex, "sortIndex", source: source)
|
||||||
self.externalFiles = metadata.externalFiles ?? []
|
self.externalFiles = metadata.externalFiles ?? []
|
||||||
self.requiredFiles = metadata.requiredFiles ?? [] // Paths are already relative to root
|
self.requiredFiles = metadata.requiredFiles ?? [] // Paths are already relative to root
|
||||||
|
self.images = metadata.images?.compactMap { ManualImage(input: $0, path: "") } ?? []
|
||||||
self.thumbnailStyle = log.unused(metadata.thumbnailStyle, "thumbnailStyle", source: source) ?? .large
|
self.thumbnailStyle = log.unused(metadata.thumbnailStyle, "thumbnailStyle", source: source) ?? .large
|
||||||
self.useManualSorting = log.unused(metadata.useManualSorting, "useManualSorting", source: source) ?? true
|
self.useManualSorting = log.unused(metadata.useManualSorting, "useManualSorting", source: source) ?? true
|
||||||
self.overviewItemCount = metadata.overviewItemCount ?? Element.overviewItemCountDefault
|
self.overviewItemCount = metadata.overviewItemCount ?? Element.overviewItemCountDefault
|
||||||
@ -175,10 +183,10 @@ struct Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
files.add(page: path, id: id)
|
files.add(page: path, id: id)
|
||||||
try self.readElements(in: folder, source: nil)
|
self.readElements(in: folder, source: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
mutating func readElements(in folder: URL, source: String?) throws {
|
mutating func readElements(in folder: URL, source: String?) {
|
||||||
let subFolders: [URL]
|
let subFolders: [URL]
|
||||||
do {
|
do {
|
||||||
subFolders = try FileManager.default
|
subFolders = try FileManager.default
|
||||||
@ -188,13 +196,13 @@ struct Element {
|
|||||||
log.add(error: "Failed to read subfolders", source: source ?? "root", error: error)
|
log.add(error: "Failed to read subfolders", source: source ?? "root", error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.elements = try subFolders.compactMap { subFolder in
|
self.elements = subFolders.compactMap { subFolder in
|
||||||
let s = (source.unwrapped { $0 + "/" } ?? "") + subFolder.lastPathComponent
|
let s = (source.unwrapped { $0 + "/" } ?? "") + subFolder.lastPathComponent
|
||||||
return try Element(parent: self, folder: subFolder, path: s)
|
return Element(parent: self, folder: subFolder, path: s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init?(parent: Element, folder: URL, path: String) throws {
|
init?(parent: Element, folder: URL, path: String) {
|
||||||
self.inputFolder = folder
|
self.inputFolder = folder
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
@ -228,6 +236,7 @@ struct Element {
|
|||||||
// TODO: Propagate external files from the parent if subpath matches?
|
// TODO: Propagate external files from the parent if subpath matches?
|
||||||
self.externalFiles = Element.rootPaths(for: metadata.externalFiles, path: path)
|
self.externalFiles = Element.rootPaths(for: metadata.externalFiles, path: path)
|
||||||
self.requiredFiles = Element.rootPaths(for: metadata.requiredFiles, path: path)
|
self.requiredFiles = Element.rootPaths(for: metadata.requiredFiles, path: path)
|
||||||
|
self.images = metadata.images?.compactMap { ManualImage(input: $0, path: path) } ?? []
|
||||||
self.thumbnailStyle = log.thumbnailStyle(metadata.thumbnailStyle, source: source)
|
self.thumbnailStyle = log.thumbnailStyle(metadata.thumbnailStyle, source: source)
|
||||||
self.useManualSorting = metadata.useManualSorting ?? false
|
self.useManualSorting = metadata.useManualSorting ?? false
|
||||||
self.overviewItemCount = metadata.overviewItemCount ?? parent.overviewItemCount
|
self.overviewItemCount = metadata.overviewItemCount ?? parent.overviewItemCount
|
||||||
@ -252,7 +261,7 @@ struct Element {
|
|||||||
// All properties initialized
|
// All properties initialized
|
||||||
|
|
||||||
files.add(page: path, id: id)
|
files.add(page: path, id: id)
|
||||||
try self.readElements(in: folder, source: path)
|
self.readElements(in: folder, source: path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,12 +374,20 @@ extension Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func containedFileRelativeToRoot(filePath: String, folder path: String) -> String? {
|
static func containedFileRelativeToRoot(filePath: String, folder path: String) -> String? {
|
||||||
|
if path == "" {
|
||||||
|
return filePath
|
||||||
|
}
|
||||||
if filePath.hasPrefix("/") || filePath.hasPrefix("http") || filePath.hasPrefix("mailto:") {
|
if filePath.hasPrefix("/") || filePath.hasPrefix("http") || filePath.hasPrefix("mailto:") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return "\(path)/\(filePath)"
|
return "\(path)/\(filePath)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert a set of relative paths to paths that are relative to the root element.
|
||||||
|
- Parameter input: The set of paths to convert.
|
||||||
|
- Parameter path: The path to the folder where the paths are currently relative to.
|
||||||
|
*/
|
||||||
static func rootPaths(for input: Set<String>?, path: String) -> Set<String> {
|
static func rootPaths(for input: Set<String>?, path: String) -> Set<String> {
|
||||||
guard let input = input else {
|
guard let input = input else {
|
||||||
return []
|
return []
|
||||||
@ -536,3 +553,44 @@ extension Element {
|
|||||||
elements.forEach { $0.printTree(indentation: indentation + " ") }
|
elements.forEach { $0.printTree(indentation: indentation + " ") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Images
|
||||||
|
|
||||||
|
extension Element {
|
||||||
|
|
||||||
|
struct ManualImage {
|
||||||
|
|
||||||
|
let sourcePath: String
|
||||||
|
|
||||||
|
let destinationPath: String
|
||||||
|
|
||||||
|
let desiredWidth: Int
|
||||||
|
|
||||||
|
let desiredHeight: Int?
|
||||||
|
|
||||||
|
init?(input: String, path: String) {
|
||||||
|
let parts = input.components(separatedBy: " ").filter { !$0.isEmpty }
|
||||||
|
guard parts.count == 3 || parts.count == 4 else {
|
||||||
|
log.add(error: "Invalid image specification, expected 'source dest width (height)", source: path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let width = Int(parts[2]) else {
|
||||||
|
log.add(error: "Invalid width for image \(parts[0])", source: path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sourcePath = Element.relativeToRoot(filePath: parts[0], folder: path)
|
||||||
|
self.destinationPath = Element.relativeToRoot(filePath: parts[1], folder: path)
|
||||||
|
self.desiredWidth = width
|
||||||
|
guard parts.count == 4 else {
|
||||||
|
self.desiredHeight = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let height = Int(parts[3]) else {
|
||||||
|
log.add(error: "Invalid height for image \(parts[0])", source: path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self.desiredHeight = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -77,6 +77,13 @@ struct GenericMetadata {
|
|||||||
*/
|
*/
|
||||||
let requiredFiles: Set<String>?
|
let requiredFiles: Set<String>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
Additional images required by the element.
|
||||||
|
|
||||||
|
These images are specified as: `source_name destination_name width (height)`.
|
||||||
|
*/
|
||||||
|
let images: Set<String>?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The style of thumbnail to use when generating overviews.
|
The style of thumbnail to use when generating overviews.
|
||||||
|
|
||||||
@ -128,6 +135,7 @@ extension GenericMetadata: Codable {
|
|||||||
.sortIndex,
|
.sortIndex,
|
||||||
.externalFiles,
|
.externalFiles,
|
||||||
.requiredFiles,
|
.requiredFiles,
|
||||||
|
.images,
|
||||||
.thumbnailStyle,
|
.thumbnailStyle,
|
||||||
.useManualSorting,
|
.useManualSorting,
|
||||||
.overviewItemCount,
|
.overviewItemCount,
|
||||||
@ -198,6 +206,7 @@ extension GenericMetadata {
|
|||||||
sortIndex: 1,
|
sortIndex: 1,
|
||||||
externalFiles: [],
|
externalFiles: [],
|
||||||
requiredFiles: [],
|
requiredFiles: [],
|
||||||
|
images: [],
|
||||||
thumbnailStyle: "",
|
thumbnailStyle: "",
|
||||||
useManualSorting: false,
|
useManualSorting: false,
|
||||||
overviewItemCount: 6,
|
overviewItemCount: 6,
|
||||||
|
@ -1,8 +1,20 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct Configuration {
|
struct Configuration: Codable {
|
||||||
|
|
||||||
let pageImageWidth: Int
|
let pageImageWidth: Int
|
||||||
|
|
||||||
let minifyCSSandJS: Bool
|
let minifyCSSandJS: Bool
|
||||||
|
|
||||||
|
let contentPath: String
|
||||||
|
|
||||||
|
let outputPath: String
|
||||||
|
|
||||||
|
var contentDirectory: URL {
|
||||||
|
.init(fileURLWithPath: contentPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputDirectory: URL {
|
||||||
|
.init(fileURLWithPath: outputPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,17 +9,15 @@ struct SiteGenerator {
|
|||||||
self.templates = try TemplateFactory(templateFolder: templatesFolder)
|
self.templates = try TemplateFactory(templateFolder: templatesFolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generate(site: Element) throws {
|
func generate(site: Element) {
|
||||||
site.requiredFiles.forEach(files.require)
|
site.languages.forEach {
|
||||||
site.externalFiles.forEach(files.exclude)
|
generate(site: site, metadata: $0)
|
||||||
try site.languages.forEach {
|
|
||||||
try generate(site: site, metadata: $0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generate(site: Element, metadata: Element.LocalizedMetadata) throws {
|
private func generate(site: Element, metadata: Element.LocalizedMetadata) {
|
||||||
let language = metadata.language
|
let language = metadata.language
|
||||||
let template = try LocalizedSiteTemplate(
|
let template = LocalizedSiteTemplate(
|
||||||
factory: templates,
|
factory: templates,
|
||||||
language: language,
|
language: language,
|
||||||
site: site)
|
site: site)
|
||||||
@ -33,8 +31,7 @@ struct SiteGenerator {
|
|||||||
// Move recursively down to all pages
|
// Move recursively down to all pages
|
||||||
elementsToProcess.append(contentsOf: element.elements)
|
elementsToProcess.append(contentsOf: element.elements)
|
||||||
|
|
||||||
element.requiredFiles.forEach(files.require)
|
processAllFiles(for: element)
|
||||||
element.externalFiles.forEach(files.exclude)
|
|
||||||
|
|
||||||
if !element.elements.isEmpty {
|
if !element.elements.isEmpty {
|
||||||
overviewGenerator.generate(section: element, language: language)
|
overviewGenerator.generate(section: element, language: language)
|
||||||
@ -48,4 +45,16 @@ struct SiteGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func processAllFiles(for element: Element) {
|
||||||
|
element.requiredFiles.forEach(files.require)
|
||||||
|
element.externalFiles.forEach(files.exclude)
|
||||||
|
element.images.forEach {
|
||||||
|
files.requireImage(
|
||||||
|
source: $0.sourcePath,
|
||||||
|
destination: $0.destinationPath,
|
||||||
|
width: $0.desiredWidth,
|
||||||
|
desiredHeight: $0.desiredHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ struct LocalizedSiteTemplate {
|
|||||||
factory.page
|
factory.page
|
||||||
}
|
}
|
||||||
|
|
||||||
init(factory: TemplateFactory, language: String, site: Element) throws {
|
init(factory: TemplateFactory, language: String, site: Element) {
|
||||||
self.author = site.author
|
self.author = site.author
|
||||||
self.factory = factory
|
self.factory = factory
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ struct LocalizedSiteTemplate {
|
|||||||
url: $0.path + Element.htmlPagePathAddition(for: language))
|
url: $0.path + Element.htmlPagePathAddition(for: language))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.topBar = try .init(
|
self.topBar = .init(
|
||||||
factory: factory,
|
factory: factory,
|
||||||
language: language,
|
language: language,
|
||||||
sections: sections,
|
sections: sections,
|
||||||
|
@ -10,7 +10,7 @@ struct PrefilledTopBarTemplate {
|
|||||||
|
|
||||||
private let factory: TemplateFactory
|
private let factory: TemplateFactory
|
||||||
|
|
||||||
init(factory: TemplateFactory, language: String, sections: [SectionInfo], topBarWebsiteTitle: String) throws {
|
init(factory: TemplateFactory, language: String, sections: [SectionInfo], topBarWebsiteTitle: String) {
|
||||||
self.factory = factory
|
self.factory = factory
|
||||||
self.language = language
|
self.language = language
|
||||||
self.sections = sections
|
self.sections = sections
|
||||||
|
@ -1,28 +1,33 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
private let contentDirectory = URL(fileURLWithPath: "/Users/ch/Projects/MakerSpace")
|
|
||||||
private let outputDirectory = URL(fileURLWithPath: "/Users/ch/Projects/MakerSpace/Site")
|
|
||||||
|
|
||||||
let configuration = Configuration(
|
let configUrl = URL(fileURLWithPath: "/Users/ch/Projects/MakerSpace/CHGenerator/config.json")
|
||||||
pageImageWidth: 748,
|
|
||||||
minifyCSSandJS: true)
|
let configuration: Configuration
|
||||||
|
do {
|
||||||
|
let data = try Data(contentsOf: configUrl)
|
||||||
|
configuration = try JSONDecoder().decode(from: data)
|
||||||
|
} catch {
|
||||||
|
print("Failed to read configuration: \(error)")
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
let log = ValidationLog()
|
let log = ValidationLog()
|
||||||
let files = FileSystem(in: contentDirectory, to: outputDirectory)
|
let files = FileSystem(
|
||||||
|
in: configuration.contentDirectory,
|
||||||
|
to: configuration.outputDirectory)
|
||||||
|
|
||||||
private let siteData: Element
|
guard let siteData = Element(atRoot: configuration.contentDirectory) else {
|
||||||
do {
|
|
||||||
guard let element = try Element(atRoot: contentDirectory) else {
|
|
||||||
exit(0)
|
|
||||||
}
|
|
||||||
siteData = element
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private let siteGenerator = try SiteGenerator()
|
do {
|
||||||
try siteGenerator.generate(site: siteData)
|
let siteGenerator = try SiteGenerator()
|
||||||
|
siteGenerator.generate(site: siteData)
|
||||||
|
} catch {
|
||||||
|
print("Failed to generate website: \(error)")
|
||||||
|
exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
files.printGeneratedPages()
|
files.printGeneratedPages()
|
||||||
files.printEmptyPages()
|
files.printEmptyPages()
|
||||||
@ -31,4 +36,5 @@ files.printDraftPages()
|
|||||||
files.createImages()
|
files.createImages()
|
||||||
print("Images generated")
|
print("Images generated")
|
||||||
files.copyRequiredFiles()
|
files.copyRequiredFiles()
|
||||||
|
files.printExternalFiles()
|
||||||
files.writeHashes()
|
files.writeHashes()
|
||||||
|
6
config_example.json
Normal file
6
config_example.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"pageImageWidth" : 748,
|
||||||
|
"minifyCSSandJS" : true,
|
||||||
|
"contentPath" : "/path/to/content/folder",
|
||||||
|
"outputPath" : "/path/to/output/folder")
|
||||||
|
}
|
13
install.sh
Executable file
13
install.sh
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Install the required dependencies for CHGenerator
|
||||||
|
|
||||||
|
# Note: The following is only required if `minifyCSSandJS=True`
|
||||||
|
# is set in the generator configuration
|
||||||
|
|
||||||
|
# Install the Javascript minifier
|
||||||
|
# https://github.com/mishoo/UglifyJS
|
||||||
|
npm install uglify-js -g
|
||||||
|
|
||||||
|
# Install the clean-css minifier
|
||||||
|
# https://github.com/clean-css/clean-css-cli
|
||||||
|
npm install clean-css-cli -g
|
Loading…
Reference in New Issue
Block a user