book/BookDirectoryStep.ts
import { HtmlRR0SsgContext } from "../RR0SsgContext"
import { DirectoryStep, HtmlLinks, HtmlMeta, OutputFunc, SsgConfig, SsgFile } from "ssg-api"
import { RR0FileUtil } from "../util/file/RR0FileUtil"
import { Book } from "./Book"
import { StringUtil } from "../util/string/StringUtil"
import { HtmlTag } from "../util/HtmlTag"
import fs from "fs"
import path from "path"
import { Chapter } from "./Chapters"
import { PeopleService } from "../people/PeopleService"
/**
* Scan directories for book information, then populates a template with collected data.
*/
export class BookDirectoryStep extends DirectoryStep {
constructor(dirs: string[], template: string, protected outputFunc: OutputFunc,
config: SsgConfig, protected outDir: string, name: string,
protected bookMeta: Map<string, HtmlMeta>, protected bookLinks: Map<string, HtmlLinks>,
protected peopleService: PeopleService) {
super(dirs, [], template, config, name)
}
static async create(outputFunc: OutputFunc, config: SsgConfig, bookMeta: Map<string, HtmlMeta>,
bookLinks: Map<string, HtmlLinks>, peopleService: PeopleService): Promise<BookDirectoryStep> {
const dirs = RR0FileUtil.findDirectoriesContaining("book*.json")
return new BookDirectoryStep(dirs, "book/index.html", outputFunc, config, config.outDir, "all books", bookMeta,
bookLinks, peopleService)
}
protected async processDirs(context: HtmlRR0SsgContext, dirNames: string[]): Promise<void> {
const books = this.scan(context, dirNames)
await this.tocAll(context, books)
const directoriesHtml = this.toList(books)
context.outputFile.contents = context.outputFile.contents.replace(`<!--#echo var="directories" -->`,
directoriesHtml)
await this.outputFunc(context, context.outputFile)
}
protected scan(context: HtmlRR0SsgContext, dirNames: string[]): Book[] {
const books: Book[] = []
for (const dirName of dirNames) {
const dirBook: Book = {
dirName,
authors: [],
publication: {publisher: "", time: undefined},
summary: "",
title: "",
variants: []
}
books.push(dirBook)
try {
const jsonFileInfo = SsgFile.read(context, `${dirName}/book.json`)
Object.assign(dirBook, JSON.parse(jsonFileInfo.contents))
} catch (e) {
context.warn(`${dirName} has no book*.json description`)
}
}
return books
}
/**
* Convert an array of Case[] to an <ul> HTML unordered list.
*
* @param books
*/
protected toList(books: Book[]) {
const listItems = books.map(dirBook => {
if (!dirBook.title) {
const lastSlash = dirBook.dirName.lastIndexOf("/")
const lastDir = dirBook.dirName.substring(lastSlash + 1)
dirBook.title = StringUtil.camelToText(lastDir)
}
return this.toListItem(dirBook)
})
return HtmlTag.toString("ul", listItems.join("\n"), {class: "links"})
}
/**
* Convert a Case object to an HTML list item.
*
* @param dirBook
*/
protected toListItem(dirBook: Book) {
const attrs: { [name: string]: string } = {}
const titles = []
const details: string[] = []
const authors = dirBook.authors
const authorStr = authors ? authors.join(" & ") + ": " : ""
const time = dirBook.publication.time
if (time) {
const timeDetail = time.getYear()
details.push(HtmlTag.toString("time", timeDetail.toString()))
}
const text: (string | string[])[] = [authorStr, dirBook.title]
if (details.length > 0) {
text.push(`(${details.join(", ")})`)
}
const innerHTML = text.join(" ").trim()
const a = fs.existsSync(path.join(dirBook.dirName, "index.html")) ? HtmlTag.toString("a", innerHTML,
{href: "/" + dirBook.dirName + "/"}) : innerHTML
if (titles.length) {
attrs.title = titles.join(", ")
}
return HtmlTag.toString("li", a, attrs)
}
protected async tocAll(context: HtmlRR0SsgContext, books: Book[]) {
for (const book of books) {
await this.toc(context, book)
}
}
protected async toc(context: HtmlRR0SsgContext, book: Book) {
const startFileName = path.join(book.dirName, "index.html")
try {
context.read(startFileName)
const startFileNames = [context.inputFile.name]
const variants = context.inputFile.lang.variants
for (const variant of variants) {
const parsed = path.parse(startFileName)
const variantFileName = path.join(parsed.dir, `${parsed.name}_${variant}${parsed.ext}`)
startFileNames.push(variantFileName)
}
for (const startFileName of startFileNames) {
const chapter = new Chapter(context, startFileName)
await chapter.scan()
const chapterBefore = chapter.toString()
context.logger.debug("toc before:", chapterBefore)
await chapter.update()
const chapterAfter = chapter.toString()
context.logger.debug("toc after:", chapterAfter)
context.logger.log("Updated toc for", chapter.context.inputFile.name)
book.variants.push(chapter)
this.bookMeta.set(startFileName, chapter.context.outputFile.meta)
this.bookLinks.set(startFileName, chapter.context.outputFile.links)
}
} catch (e) {
context.logger.error("Could not check TOC of " + startFileName, e.message)
}
}
}