/** * 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 { FragmentedSceneObject, RawEmptyObject, RawFragmentObject, RawSceneObject, } from '../../types' import { exposeMeta } from '../../tools/expose-meta' import { parseTOMLMeta } from '../../tools/parse-toml-meta' import { parseInlineMeta } from '../../tools/parse-inline-meta' const FRAGMENT_START_TOKEN = '<>' const FRAGMENT_END_TOKEN = '</>' export const genFragmentsForScenes = (scenes: RawSceneObject[]): FragmentedSceneObject[] => { const newScenes: FragmentedSceneObject[] = [] for (let scene of scenes) { const fragments = genFragmentObjects(scene.raw) const newScene: FragmentedSceneObject = { type: 'scene', meta: scene.meta, id: scene.id, classes: scene.classes, primary: scene.primary, title: scene.title, fragments, } newScenes.push(newScene) } return newScenes } export const genFragmentObjects = (raw: string): (RawFragmentObject | RawEmptyObject)[] => { const fragments: (RawFragmentObject | RawEmptyObject)[] = [] let currentFragment = '' const lines = eol.split(raw) for (let line of lines) { if (line.trim().startsWith(FRAGMENT_START_TOKEN)) { // we start a new fragment let trimmedFragment = currentFragment.trim() if (trimmedFragment.length) { fragments.push(genFragmentObject(currentFragment)) } currentFragment = line + '\n' continue } if (line.trim() === FRAGMENT_END_TOKEN) { // we end the fragment currentFragment += line + '\n' fragments.push(genFragmentObject(currentFragment)) currentFragment = '' continue } // otherwise, we just add the line to the fragment currentFragment += line + '\n' } // make sure we finish off the last fragment let trimmedFragment = currentFragment.trim() if (trimmedFragment.length) { fragments.push(genFragmentObject(currentFragment)) } return fragments } /** * Creates a single fragment out of the given text. * @param text text of the fragment. Should start with the opening <> and end with the </>, if available. If we pass in an object without those tags, * we assume that we are outside of a fragment. */ export const genFragmentObject = (text: string): RawFragmentObject | RawEmptyObject => { // if we if (!text.startsWith(FRAGMENT_START_TOKEN)) { return { type: 'empty', raw: text, } } const fragmentObject: RawFragmentObject = { type: 'fragment', raw: '', } const lexer = moo.states({ start: { fragDefStart: { match: FRAGMENT_START_TOKEN, push: 'fragInlineMeta' }, }, fragInlineMeta: { fragInlineEnd: { match: /\n/, lineBreaks: true, push: 'fragDefinition' }, inlineMeta: { match: /[^]+?/, lineBreaks: true }, }, fragDefinition: { tomlStart: { match: '---', push: 'fragToml' }, tomlStartPlus: { match: '+++', push: 'fragTomlPlus' }, fragDefEnd: { match: /\n/, lineBreaks: true, push: 'main' }, text: { match: /[^]+?/, lineBreaks: true }, }, fragToml: { tomlEnd: { match: '---', push: 'fragDefinition' }, toml: { match: /[^]+?(?=---)/, lineBreaks: true }, }, fragTomlPlus: { tomlEnd: { match: '+++', push: 'fragDefinition' }, toml: { match: /[^]+?(?=\+\+\+)/, lineBreaks: true }, }, main: { fragTagEnd: { match: new RegExp(`^\s*${FRAGMENT_END_TOKEN.replace('/', '\\/')}\s*$`), push: 'endFragTag' }, text: { match: /[^]+?/, lineBreaks: true }, }, endFragTag: { misc: { match: /[^]+?/, lineBreaks: true }, }, }) lexer.reset(eol.lf(text)) let inlineMeta = '' for (let token of Array.from(lexer) as any[]) { switch (token.type) { case 'toml': const tomlObj = parseTOMLMeta(token.value) if (tomlObj === undefined) { throw new Error(lexer.formatError(token, 'empty TOML frontmatter in scene.')) } if (!fragmentObject.meta) { fragmentObject.meta = {} } fragmentObject.meta = { ...fragmentObject.meta, ...tomlObj } exposeMeta(fragmentObject) break case 'text': fragmentObject.raw += token.value break case 'inlineMeta': inlineMeta += token.value } } // Process any inline meta //////////////////////////// inlineMeta = inlineMeta.trim() if (inlineMeta.length) { let inlineMetaObject = parseInlineMeta(inlineMeta) fragmentObject.meta = { ...inlineMetaObject, ...fragmentObject.meta } exposeMeta(fragmentObject) } return fragmentObject }