Merge pull request 'Rewrite in-progress' (#1) from rewrite into develop
Reviewed-on: https://chloeherd.codes/walcutt/virtual-star/pulls/1
This commit is contained in:
		
						commit
						325b747bd8
					
				
							
								
								
									
										52
									
								
								build.js
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								build.js
									
									
									
									
									
								
							| @ -1,46 +1,20 @@ | |||||||
| import fs from 'fs'; | import fs from 'fs'; | ||||||
| import Showdown from 'showdown'; | import { BasePaths } from './lib/render/base-paths.js'; | ||||||
| import 'dotenv/config'; | import { Renderer } from './lib/render/renderer.js'; | ||||||
| import { ENV } from './lib/env.js'; | import { FileHelper } from './lib/render/file-helper.js'; | ||||||
|  | import { SettingsReader } from './lib/render/settings-reader.js'; | ||||||
|  | import { Context } from './lib/struct/context.js'; | ||||||
| 
 | 
 | ||||||
| const converter = new Showdown.Converter(); | // Delete prior output, if exists.
 | ||||||
| 
 | if(fs.existsSync(BasePaths.targetRoot())) { | ||||||
| let inputDir = 'content/'; |     fs.rmSync(BasePaths.targetRoot(), { recursive: true, force: true }); | ||||||
| if(ENV.getIsSet('VS_INPUT_DIR')) { |  | ||||||
|     inputDir = ENV.getString('VS_INPUT_DIR'); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| let outputDir = 'build/'; | // fs.mkdirSync(BasePaths.targetRoot());
 | ||||||
| if(ENV.getIsSet('VS_OUTPUT_DIR')) { |  | ||||||
|     outputDir = ENV.getString('VS_OUTPUT_DIR'); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| const fileEntities = fs.readdirSync(inputDir, {recursive: true, withFileTypes: true, encoding: 'utf8'}); | const startPath = ''; | ||||||
|  | const rootContext = SettingsReader.readDirectorySettings(`${BasePaths.contentRoot()}/${startPath}`); | ||||||
| 
 | 
 | ||||||
| fileEntities.forEach((fileEnt) => { | const renderer = new Renderer(startPath, rootContext); | ||||||
|     const filenameTokens = fileEnt.name.split('.'); |  | ||||||
|     const extension = filenameTokens[filenameTokens.length - 1]; |  | ||||||
| 
 | 
 | ||||||
|     if(extension === 'md') { | renderer.renderAll(); | ||||||
|         const fullPath = fileEnt.parentPath + fileEnt.name; |  | ||||||
| 
 |  | ||||||
|         const content = fs.readFileSync(fullPath, {encoding: 'utf8'}); |  | ||||||
| 
 |  | ||||||
|         const converted = converter.makeHtml(content); |  | ||||||
| 
 |  | ||||||
|         let outputFileName = ""; |  | ||||||
|         for(let i = 0; i < filenameTokens.length - 1; i++) { |  | ||||||
|             outputFileName += filenameTokens[i]; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         outputFileName += '.html'; |  | ||||||
| 
 |  | ||||||
|         const trimmedPath = fileEnt.parentPath.replace(inputDir, ""); |  | ||||||
| 
 |  | ||||||
|         const outputPath = outputDir + trimmedPath + outputFileName; |  | ||||||
| 
 |  | ||||||
|         fs.writeFileSync(outputPath, converted); |  | ||||||
| 
 |  | ||||||
|         console.log(`Converted ${fullPath} -> ${outputPath}`); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
							
								
								
									
										1
									
								
								content/fragments/var-test.vs.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								content/fragments/var-test.vs.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | test variable = <* test_var *> | ||||||
							
								
								
									
										4
									
								
								content/site/_settings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								content/site/_settings.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | { | ||||||
|  |     "test_var": "Outer settings", | ||||||
|  |     "template": "base-template" | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								content/site/sub/_settings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								content/site/sub/_settings.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | { | ||||||
|  |     "test_var": "Inner setting", | ||||||
|  |     "template": "" | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								content/site/sub/test.vs.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								content/site/sub/test.vs.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | # Hello world from another directory! | ||||||
|  | 
 | ||||||
|  | <{ var-test }> | ||||||
							
								
								
									
										3
									
								
								content/site/test_root.vs.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								content/site/test_root.vs.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | # Hello world! | ||||||
|  | 
 | ||||||
|  | <{ var-test }> | ||||||
							
								
								
									
										6
									
								
								content/templates/base-template.vs.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								content/templates/base-template.vs.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | <div> | ||||||
|  |     <h3> | ||||||
|  |         Outer wrapper! | ||||||
|  |     </h3> | ||||||
|  |     <{ content }> | ||||||
|  | </div> | ||||||
| @ -1 +0,0 @@ | |||||||
| # Here's a test! |  | ||||||
							
								
								
									
										2
									
								
								lib/constants.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								lib/constants.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | export const DEFAULT_SOURCE_BASE = './content'; | ||||||
|  | export const DEFUALT_TARGET_BASE = './build'; | ||||||
							
								
								
									
										24
									
								
								lib/render/base-paths.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								lib/render/base-paths.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | import { DEFAULT_SOURCE_BASE, DEFUALT_TARGET_BASE } from "../constants.js"; | ||||||
|  | import { ENV } from "../env.js" | ||||||
|  | 
 | ||||||
|  | export const BasePaths = { | ||||||
|  |     sourceRoot() { | ||||||
|  |         const overridePath = ENV.getString(`VS_INPUT_DIR`); | ||||||
|  | 
 | ||||||
|  |         return overridePath ?? DEFAULT_SOURCE_BASE; | ||||||
|  |     }, | ||||||
|  |     targetRoot() { | ||||||
|  |         const overridePath = ENV.getString(`VS_OUTPUT_DIR`); | ||||||
|  | 
 | ||||||
|  |         return overridePath ?? DEFUALT_TARGET_BASE; | ||||||
|  |     }, | ||||||
|  |     templates() { | ||||||
|  |         return `${this.sourceRoot()}/templates`; | ||||||
|  |     }, | ||||||
|  |     fragments() { | ||||||
|  |         return `${this.sourceRoot()}/fragments`; | ||||||
|  |     }, | ||||||
|  |     contentRoot() { | ||||||
|  |         return `${this.sourceRoot()}/site`; | ||||||
|  |     }, | ||||||
|  | } | ||||||
							
								
								
									
										66
									
								
								lib/render/file-helper.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								lib/render/file-helper.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | import { fragmentFormats } from "../struct/fragment.js"; | ||||||
|  | 
 | ||||||
|  | export const FileHelper = { | ||||||
|  |     isContent(fileEnt) { | ||||||
|  |         return this.isFragment(fileEnt); | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     isFragment(fileEnt) { | ||||||
|  |         if(!fileEnt.isFile()) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const contentExtensions = [ | ||||||
|  |             '.vs.md', | ||||||
|  |             '.vs.html', | ||||||
|  |         ]; | ||||||
|  | 
 | ||||||
|  |         const matchesAnyContentExtensions = contentExtensions.some( | ||||||
|  |             (ext) => fileEnt.name.endsWith(ext) | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         return matchesAnyContentExtensions; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     isSettingsFile(fileEnt) { | ||||||
|  |         if(!fileEnt.isFile()) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return fileEnt.name === '_settings.json'; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     getFragmentType(fileEnt) { | ||||||
|  |         const filePath = fileEnt.name; | ||||||
|  |          | ||||||
|  |         if(filePath.endsWith('.vs.md')) { | ||||||
|  |             return fragmentFormats.V_MARKDOWN; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(filePath.endsWith('.vs.html')) { | ||||||
|  |             return fragmentFormats.V_HTML; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return null; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     getOutputFileName(fileEnt) { | ||||||
|  |         if(this.isContent(fileEnt)) { | ||||||
|  |             const stripped = this.getBaseName(fileEnt); | ||||||
|  | 
 | ||||||
|  |             return `${stripped}.html`; | ||||||
|  |         } else { | ||||||
|  |             return fileEnt.name; | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     getBaseName(fileEnt) { | ||||||
|  |         const isMd = (this.getFragmentType(fileEnt) === fragmentFormats.V_MARKDOWN); | ||||||
|  |         const ext = isMd ? '.vs.md' : '.vs.html'; | ||||||
|  | 
 | ||||||
|  |         const idx = fileEnt.name.lastIndexOf(ext); | ||||||
|  |         const stripped = fileEnt.name.substring(0, idx); | ||||||
|  | 
 | ||||||
|  |         return stripped; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										69
									
								
								lib/render/fragment-manager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								lib/render/fragment-manager.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | |||||||
|  | import fs from 'fs'; | ||||||
|  | import { BasePaths } from "./base-paths.js"; | ||||||
|  | import { FileHelper } from './file-helper.js'; | ||||||
|  | import { Fragment } from '../struct/fragment.js'; | ||||||
|  | 
 | ||||||
|  | export const SPECIAL_CONTENT_SYMBOL = `content`; | ||||||
|  | 
 | ||||||
|  | const FragmentSuperManager = { | ||||||
|  |     fragments: null, | ||||||
|  |     initialized: false, | ||||||
|  | 
 | ||||||
|  |     initialize() { | ||||||
|  |         const fragmentDir = BasePaths.fragments(); | ||||||
|  |         const entries = fs.readdirSync(fragmentDir, { encoding: 'utf-8', withFileTypes: true }); | ||||||
|  |         const fragmentEntries = entries.filter( | ||||||
|  |             (e) => FileHelper.isFragment(e) | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         let fragments = []; | ||||||
|  | 
 | ||||||
|  |         for(let i = 0; i < fragmentEntries.length; i++) { | ||||||
|  |             const type = FileHelper.getFragmentType(fragmentEntries[i]); | ||||||
|  |             const path = fragmentEntries[i].parentPath + '/' + fragmentEntries[i].name; | ||||||
|  |             const contents = fs.readFileSync(path, { encoding: 'utf-8' }); | ||||||
|  | 
 | ||||||
|  |             const fragment = new Fragment(type, contents); | ||||||
|  | 
 | ||||||
|  |             const name = FileHelper.getBaseName(fragmentEntries[i]); | ||||||
|  | 
 | ||||||
|  |             fragments.push({ | ||||||
|  |                 name: name, | ||||||
|  |                 fragment: fragment | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.fragments = fragments; | ||||||
|  |         this.initialized = true; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     get(key)  { | ||||||
|  |         const match = this.fragments?.find( | ||||||
|  |             (e) => e.name === key | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         return match?.fragment; | ||||||
|  |     }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export class FragmentManager { | ||||||
|  |     contentFragment; | ||||||
|  |     _fragmentSuperManager; | ||||||
|  | 
 | ||||||
|  |     constructor(contentFragment) { | ||||||
|  |         this.contentFragment = contentFragment; | ||||||
|  |         this._fragmentSuperManager = FragmentSuperManager; | ||||||
|  | 
 | ||||||
|  |         if(!FragmentSuperManager.initialized) { | ||||||
|  |             FragmentSuperManager.initialize(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     get(key) { | ||||||
|  |         if(key === SPECIAL_CONTENT_SYMBOL) { | ||||||
|  |             return this.contentFragment; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return this._fragmentSuperManager.get(key); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										150
									
								
								lib/render/renderer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								lib/render/renderer.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,150 @@ | |||||||
|  | import { Context } from "../struct/context.js"; | ||||||
|  | import { Fragment, fragmentFormats } from "../struct/fragment.js"; | ||||||
|  | import { BasePaths } from "./base-paths.js"; | ||||||
|  | import { FileHelper } from "./file-helper.js"; | ||||||
|  | import { FragmentManager } from "./fragment-manager.js"; | ||||||
|  | import { SettingsReader } from "./settings-reader.js"; | ||||||
|  | import { TemplateManager } from "./template-manager.js"; | ||||||
|  | import { tokenTypes } from "./token.js"; | ||||||
|  | import { Tokenizer } from "./tokenizer.js"; | ||||||
|  | import fs, { write } from 'fs'; | ||||||
|  | 
 | ||||||
|  | export class Renderer { | ||||||
|  |     path; | ||||||
|  |     context; | ||||||
|  | 
 | ||||||
|  |     constructor(path, context) { | ||||||
|  |         this.path = path; | ||||||
|  |         this.context = context; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     readFolder() { | ||||||
|  |         return `${BasePaths.contentRoot()}/${this.path}`; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     writeFolder() { | ||||||
|  |         return `${BasePaths.targetRoot()}/${this.path}`; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     renderAll() { | ||||||
|  |         // Create output folder
 | ||||||
|  |         fs.mkdirSync(this.writeFolder()); | ||||||
|  | 
 | ||||||
|  |         // Get all files
 | ||||||
|  |         const entries = fs.readdirSync(this.readFolder(), { encoding: 'utf-8', withFileTypes: true }); | ||||||
|  |         const files = entries.filter( | ||||||
|  |             (e) => e.isFile() | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         for(let i = 0; i < files.length; i++) { | ||||||
|  |             // Render content files, copy non-content files.
 | ||||||
|  |             if(FileHelper.isContent(files[i])) { | ||||||
|  |                 this.renderPage(files[i]); | ||||||
|  |             } else if(FileHelper.isSettingsFile(files[i])) { | ||||||
|  |                 // pass
 | ||||||
|  |             } else { | ||||||
|  |                 this.copyFile(files[i]); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Get all subdirectories.
 | ||||||
|  |         const subdirs = entries.filter( | ||||||
|  |             (e) => e.isDirectory() | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         for(let i = 0; i < subdirs.length; i++) { | ||||||
|  |             this.renderSubdirectory(subdirs[i]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     copyFile(fileEnt) { | ||||||
|  |         const readPath = `${this.readFolder()}/${fileEnt.name}`; | ||||||
|  |         const writePath = `${this.writeFolder()}/${fileEnt.name}`; | ||||||
|  | 
 | ||||||
|  |         fs.copyFileSync(readPath, writePath); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     renderPage(fileEnt) { | ||||||
|  |         const type = FileHelper.getFragmentType(fileEnt); | ||||||
|  | 
 | ||||||
|  |         const readPath = `${this.readFolder()}/${fileEnt.name}`; | ||||||
|  |         const content = fs.readFileSync(readPath, { encoding: 'utf-8' }); | ||||||
|  | 
 | ||||||
|  |         const vars = SettingsReader.readSettingsFromContent(content); | ||||||
|  |         const strippedContent = SettingsReader.trimSettingsFromContent(content); | ||||||
|  | 
 | ||||||
|  |         const fileContext = new Context(vars); | ||||||
|  |         const fullContext = this.context.mergeFrom(fileContext); | ||||||
|  | 
 | ||||||
|  |         const contentFragment = new Fragment(type, strippedContent); | ||||||
|  |          | ||||||
|  |         let root = contentFragment; | ||||||
|  |         const templateKey = fullContext.get(`template`); | ||||||
|  |         if(templateKey) { | ||||||
|  |             const template = new TemplateManager().get(templateKey); | ||||||
|  |             if(template) { | ||||||
|  |                 root = template; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const pageOutput = this.renderFragment(root, fullContext, new FragmentManager(contentFragment)); | ||||||
|  | 
 | ||||||
|  |         const writePath = `${this.writeFolder()}/${FileHelper.getOutputFileName(fileEnt)}`; | ||||||
|  |         fs.writeFileSync(writePath, pageOutput); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     renderFragment(fragment, localContext, fragmentManager) { | ||||||
|  |         if(!fragment) { | ||||||
|  |             return ''; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const tokenizer = new Tokenizer(); | ||||||
|  |         const fragmentAsHtml = fragment.toHtml().sourceContent; | ||||||
|  | 
 | ||||||
|  |         const tokensByVar = tokenizer.tokensByVariable(fragmentAsHtml); | ||||||
|  |         const replacedVarTokens = tokensByVar.map( | ||||||
|  |             (t) => { | ||||||
|  |                 if(t.type === tokenTypes.TEXT) { | ||||||
|  |                     return t.content; | ||||||
|  |                 } else { | ||||||
|  |                     const key = t.content.trim(); | ||||||
|  |                     const value = localContext.get(key); | ||||||
|  |                     return value; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         const fragmentContentAfterVariableReplace = replacedVarTokens.reduce( | ||||||
|  |             (a, b) => `${a}${b}` | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         const tokensByFragment = tokenizer.tokensByFragment(fragmentContentAfterVariableReplace); | ||||||
|  |         const self = this; | ||||||
|  |         const replacedFragmentTokens = tokensByFragment.map( | ||||||
|  |             (t) => { | ||||||
|  |                 if(t.type === tokenTypes.TEXT) { | ||||||
|  |                     return t.content; | ||||||
|  |                 } else { | ||||||
|  |                     const key = t.content.trim(); | ||||||
|  |                     const value = fragmentManager.get(key); | ||||||
|  |                     return self.renderFragment(value, localContext, fragmentManager); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         const final = replacedFragmentTokens.reduce( | ||||||
|  |             (a, b) => `${a}${b}` | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         return final; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     renderSubdirectory(subDirEnt) { | ||||||
|  |         const subPath = `${this.readFolder()}/${subDirEnt.name}`; | ||||||
|  |         const subdirContext = SettingsReader.readDirectorySettings(subPath); | ||||||
|  |         const nextContext = this.context.copy().mergeFrom(subdirContext); | ||||||
|  | 
 | ||||||
|  |         const subRenderer = new Renderer(`${this.path}/${subDirEnt.name}`, nextContext); | ||||||
|  |         subRenderer.renderAll(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								lib/render/settings-reader.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								lib/render/settings-reader.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | import fs from 'fs'; | ||||||
|  | import { Variable } from '../struct/variable.js'; | ||||||
|  | import { Context } from '../struct/context.js'; | ||||||
|  | 
 | ||||||
|  | export const SettingsReader = { | ||||||
|  |     trimSettingsFromContent(rawContent) { | ||||||
|  |         return rawContent; | ||||||
|  |     }, | ||||||
|  |     readSettingsFromContent(rawContent) { | ||||||
|  |         return []; | ||||||
|  |     }, | ||||||
|  |     readDirectorySettings(directoryPath) { | ||||||
|  |         if(!fs.existsSync(directoryPath)) { | ||||||
|  |             return new Context(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(!directoryPath.endsWith('/')) { | ||||||
|  |             directoryPath += '/'; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const settingsPath = directoryPath + '_settings.json'; | ||||||
|  |         if(!fs.existsSync(settingsPath)) { | ||||||
|  |             return new Context(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const settingsFileContent = fs.readFileSync(settingsPath, { encoding: 'utf-8' }); | ||||||
|  |         const dict = JSON.parse(settingsFileContent); | ||||||
|  | 
 | ||||||
|  |         const vars = Object.keys(dict).map( | ||||||
|  |             (k) => new Variable(k, dict[k]) | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         return new Context(vars); | ||||||
|  |     } | ||||||
|  | }; | ||||||
							
								
								
									
										64
									
								
								lib/render/template-manager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								lib/render/template-manager.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | |||||||
|  | import fs from 'fs'; | ||||||
|  | import { Fragment, fragmentFormats } from "../struct/fragment.js"; | ||||||
|  | import { BasePaths } from "./base-paths.js"; | ||||||
|  | import { FileHelper } from './file-helper.js'; | ||||||
|  | 
 | ||||||
|  | const TemplateSuperManager = { | ||||||
|  |     templates: null, | ||||||
|  |     initalized: false, | ||||||
|  | 
 | ||||||
|  |     initialize() { | ||||||
|  |         const templateDir = BasePaths.templates(); | ||||||
|  |         const entries = fs.readdirSync(templateDir, { encoding: 'utf-8', withFileTypes: true }); | ||||||
|  |         const templateEntries = entries.filter( | ||||||
|  |             (e) => FileHelper.isFragment(e) | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         let templates = []; | ||||||
|  | 
 | ||||||
|  |         for(let i = 0; i < templateEntries.length; i++) { | ||||||
|  |             const type = FileHelper.getFragmentType(templateEntries[i]); | ||||||
|  |             const path = templateEntries[i].parentPath + '/' + templateEntries[i].name; | ||||||
|  |             const contents = fs.readFileSync(path, { encoding: 'utf-8' }); | ||||||
|  | 
 | ||||||
|  |             const template = new Fragment(type, contents); | ||||||
|  | 
 | ||||||
|  |             const name = FileHelper.getBaseName(templateEntries[i]); | ||||||
|  | 
 | ||||||
|  |             templates.push({ | ||||||
|  |                 name: name, | ||||||
|  |                 template: template | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.templates = templates; | ||||||
|  |         this.initialized = true; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     get(key) { | ||||||
|  |         if(!this.initalized) { | ||||||
|  |             this.initialize(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const match = this.templates.find( | ||||||
|  |             (t) => t.name === key | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         return match?.template; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class TemplateManager { | ||||||
|  |     _templateSuperManager; | ||||||
|  | 
 | ||||||
|  |     constructor() { | ||||||
|  |         this._templateSuperManager = TemplateSuperManager; | ||||||
|  |         if(!TemplateSuperManager.initalized) { | ||||||
|  |             TemplateSuperManager.initialize(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     get(key) { | ||||||
|  |         return this._templateSuperManager.get(key); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								lib/render/token.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								lib/render/token.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | export const tokenTypes = { | ||||||
|  |     TEXT: 'text', | ||||||
|  |     VARIABLE: 'variable', | ||||||
|  |     FRAGMENT: 'fragment', | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export class Token { | ||||||
|  |     type; | ||||||
|  |     content; | ||||||
|  | 
 | ||||||
|  |     constructor(type, token) { | ||||||
|  |         this.type = type; | ||||||
|  |         this.content = token; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										61
									
								
								lib/render/tokenizer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								lib/render/tokenizer.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | |||||||
|  | import { Token, tokenTypes } from "./token.js"; | ||||||
|  | 
 | ||||||
|  | const VARIABLE_TOKEN_DEF = { | ||||||
|  |     start: '<*', | ||||||
|  |     end: '*>', | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const FRAGMENT_TOKEN_DEF = { | ||||||
|  |     start: '<{', | ||||||
|  |     end: '}>', | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export class Tokenizer { | ||||||
|  |     constructor() { | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     tokensByVariable(fragmentText) { | ||||||
|  |         const forward_split_tokens = fragmentText.split(VARIABLE_TOKEN_DEF.start); | ||||||
|  | 
 | ||||||
|  |         let tokens = [ | ||||||
|  |             new Token(tokenTypes.TEXT, forward_split_tokens[0]) | ||||||
|  |         ]; | ||||||
|  | 
 | ||||||
|  |         for(let i = 1; i < forward_split_tokens.length; i++) { | ||||||
|  |             const back_split = forward_split_tokens[i].split(VARIABLE_TOKEN_DEF.end); | ||||||
|  | 
 | ||||||
|  |             if(back_split.length !== 2) { | ||||||
|  |                 console.error(`Difficulty parsing token: ${forward_split_tokens[i]}. Keeping as plain-text`); | ||||||
|  |                 tokens.push(new Token(tokenTypes.TEXT, forward_split_tokens[i])); | ||||||
|  |             } else { | ||||||
|  |                 tokens.push(new Token(tokenTypes.VARIABLE, back_split[0])); | ||||||
|  |                 tokens.push(new Token(tokenTypes.TEXT, back_split[1])); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return tokens; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     tokensByFragment(fragmentText) { | ||||||
|  |         const forward_split_tokens = fragmentText.split(FRAGMENT_TOKEN_DEF.start); | ||||||
|  | 
 | ||||||
|  |         let tokens = [ | ||||||
|  |             new Token(tokenTypes.TEXT, forward_split_tokens[0]) | ||||||
|  |         ]; | ||||||
|  | 
 | ||||||
|  |         for(let i = 1; i < forward_split_tokens.length; i++) { | ||||||
|  |             const back_split = forward_split_tokens[i].split(FRAGMENT_TOKEN_DEF.end); | ||||||
|  | 
 | ||||||
|  |             if(back_split.length !== 2) { | ||||||
|  |                 console.error(`Difficulty parsing token: ${forward_split_tokens[i]}. Keeping as plain-text`); | ||||||
|  |                 tokens.push(new Token(tokenTypes.TEXT, forward_split_tokens[i])); | ||||||
|  |             } else { | ||||||
|  |                 tokens.push(new Token(tokenTypes.FRAGMENT, back_split[0])); | ||||||
|  |                 tokens.push(new Token(tokenTypes.TEXT, back_split[1])); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return tokens; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										50
									
								
								lib/struct/context.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								lib/struct/context.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | |||||||
|  | import { Variable } from "./variable.js"; | ||||||
|  | 
 | ||||||
|  | export class Context { | ||||||
|  |     variables; | ||||||
|  | 
 | ||||||
|  |     constructor(vars) { | ||||||
|  |         if(vars) { | ||||||
|  |             this.variables = vars.map(v => new Variable(v.key, v.value)); | ||||||
|  |         } else { | ||||||
|  |             this.variables = []; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     copy() { | ||||||
|  |         return new Context(this.variables); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     hasVariable(key) { | ||||||
|  |         return this.variables.some( | ||||||
|  |             (v) => v.key === key | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     get(key) { | ||||||
|  |         const match = this.variables.find( | ||||||
|  |             (v) => v.key === key | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         return match?.value ?? ""; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     set(key, value) { | ||||||
|  |         const withoutOld = this.variables.filter( | ||||||
|  |             (v) => v.key !== key | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         this.variables = [ | ||||||
|  |             ...withoutOld, | ||||||
|  |             new Variable(key, value) | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     mergeFrom(other) { | ||||||
|  |         for(let i = 0; i < other.variables.length; i++) { | ||||||
|  |             this.set(other.variables[i].key, other.variables[i].value); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								lib/struct/fragment.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								lib/struct/fragment.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | import Showdown from 'showdown'; | ||||||
|  | 
 | ||||||
|  | export const fragmentFormats = { | ||||||
|  |     V_HTML: ".vs.html", | ||||||
|  |     V_MARKDOWN: ".vs.md", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export class Fragment { | ||||||
|  |     format; | ||||||
|  |     sourceContent; | ||||||
|  | 
 | ||||||
|  |     constructor(format, sourceContent) { | ||||||
|  |         this.format = format; | ||||||
|  |         this.sourceContent = sourceContent; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     toHtml() { | ||||||
|  |         if(this.format === fragmentFormats.V_MARKDOWN) { | ||||||
|  |             const converter = new Showdown.Converter(); | ||||||
|  | 
 | ||||||
|  |             const htmlContent = converter.makeHtml(this.sourceContent); | ||||||
|  | 
 | ||||||
|  |             return new Fragment( | ||||||
|  |                 fragmentFormats.V_HTML, | ||||||
|  |                 htmlContent | ||||||
|  |             ); | ||||||
|  |         } else { | ||||||
|  |             return new Fragment( | ||||||
|  |                 this.format, | ||||||
|  |                 this.sourceContent | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								lib/struct/variable.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								lib/struct/variable.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | export class Variable { | ||||||
|  |     key; | ||||||
|  |     value; | ||||||
|  | 
 | ||||||
|  |     constructor(k, v) { | ||||||
|  |         this.key = k; | ||||||
|  |         this.value = v; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								serve.js
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								serve.js
									
									
									
									
									
								
							| @ -3,14 +3,14 @@ import handler from 'serve-handler'; | |||||||
| import http from 'http'; | import http from 'http'; | ||||||
| import 'dotenv/config'; | import 'dotenv/config'; | ||||||
| import { ENV } from './lib/env.js'; | import { ENV } from './lib/env.js'; | ||||||
|  | import { BasePaths } from './lib/render/base-paths.js'; | ||||||
| 
 | 
 | ||||||
| const isDebugEnabled = ENV.getBoolean('VS_DEBUG'); | const isDebugEnabled = ENV.getBoolean('VS_DEBUG'); | ||||||
| const hasOverrideDirectory = ENV.getIsSet('VS_OUTPUT_DIR'); | 
 | ||||||
| const overrideDirectory = ENV.getString('VS_OUTPUT_DIR'); | const directory = BasePaths.targetRoot(); | ||||||
| const defaultDirectory = 'build'; |  | ||||||
| 
 | 
 | ||||||
| const options = { | const options = { | ||||||
|     public: hasOverrideDirectory ? overrideDirectory : defaultDirectory, |     public: directory, | ||||||
|     directoryListing: isDebugEnabled, |     directoryListing: isDebugEnabled, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user