recital / core / src / pipeline / 02-scene-objects / index.ts
index.ts
Raw
/**
 * Copyright (c) 2022 Amorphous
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

import moo from 'moo'
import eol from 'eol'
import { RawSceneObject } from '../../types'
import { exposeMeta } from '../../tools/expose-meta'
import { parseTOMLMeta } from '../../tools/parse-toml-meta'
import { parseInlineMeta } from '../../tools/parse-inline-meta'

export const genSceneObjects = (preprocessedString: string): RawSceneObject[] => {
	const scenes: RawSceneObject[] = []

	const lexer = moo.states({
		main: {
			sceneDefStart: { match: /\#[ \t]*\n/, lineBreaks: true, push: 'sceneDefinition' },
			sceneWithTitleStart: { match: /\#:[ \t]*.*\n/, lineBreaks: true, push: 'sceneDefinition' },
			text: { match: /[^]+?/, lineBreaks: true },
		},
		sceneDefinition: {
			tomlStartDash: { match: '---', push: 'sceneTomlDash' },
			tomlStartPlus: { match: '+++', push: 'sceneTomlPlus' },
			sceneDefEnd: { match: /\n/, lineBreaks: true, push: 'main' },
		},
		sceneTomlDash: {
			tomlEnd: { match: '---', push: 'sceneDefinition' },
			toml: { match: /[^]+?(?=---)/, lineBreaks: true },
		},
		sceneTomlPlus: {
			tomlEnd: { match: '+++', push: 'sceneDefinition' },
			toml: { match: /[^]+?(?=\+\+\+)/, lineBreaks: true },
		}
	})

	// first # can be omitted if we immediately start with toml frontmatter
	let lexerRaw = eol.lf(preprocessedString)
	if (lexerRaw.startsWith('---\n') || lexerRaw.startsWith('+++\n')) {
		lexerRaw = '#\n' + lexerRaw
	}

	lexer.reset(eol.lf(lexerRaw))

	let currentScene: RawSceneObject = {
		type: 'scene',
		raw: '',
	}

	for (let token of Array.from(lexer) as any[]) {
		switch (token.type) {
			case 'sceneDefStart':
				if (!currentScene.raw.length) {
					continue
				}
				scenes.push(currentScene)
				currentScene = {
					type: 'scene',
					raw: '',
				}
				break
			case 'sceneWithTitleStart':
				if (currentScene.raw.length) {
					scenes.push(currentScene)
				}

				const inlineMeta: string = (token.value as string).substring(2).trim()
				const inlineMetaObject = inlineMeta ? parseInlineMeta(inlineMeta.trim()) : undefined

				currentScene = {
					type: 'scene',
					raw: '',
					meta: inlineMetaObject,
				}
				break
			case 'toml':
				const tomlObj = parseTOMLMeta(token.value)
				if (tomlObj === undefined) {
					throw new Error(lexer.formatError(token, 'empty TOML frontmatter in scene.'))
				}
				if (currentScene.meta) {
					currentScene.meta = { ...currentScene.meta, ...tomlObj }
				} else {
					currentScene.meta = tomlObj
				}

				exposeMeta(currentScene)

				break
			case 'text':
				currentScene.raw += token.value
				break
		}
	}

	if (currentScene.raw.length) {
		scenes.push(currentScene)
	}

	return scenes
}