greybel-js
Version:
Transpiler/Interpreter for GreyScript. (GreyHack)
211 lines • 8.24 kB
JavaScript
import fs from 'fs/promises';
import path from 'path';
import { generateAutoCompileCode } from '../helper/auto-compile-helper.js';
import { createBasePath } from '../helper/create-base-path.js';
import { escapeMSString } from '../helper/escape-ms-string.js';
class InstallerFile {
maxChars;
rootDirectory;
items;
buffer;
previous;
constructor(options) {
this.rootDirectory = options.rootDirectory;
this.maxChars = options.maxChars;
this.buffer = options.contentHeader;
this.items = [];
this.previous = options.previous ?? null;
}
insert(item) {
const isNew = !this.previous?.items.includes(item);
const remaining = this.getRemainingSpace();
const filePath = `${this.rootDirectory}${item.ingameFilepath}`;
let line = `m("${filePath}","${item.content}",${isNew ? '1' : '0'});d`;
if (remaining > line.length) {
this.buffer += line.slice(0, -1);
this.items.push(item);
item.content = '';
return true;
}
let diff = item.content.length + (remaining - line.length);
if (diff <= 0) {
return false;
}
let content = item.content.slice(0, diff);
const endingQuotes = content.match(/"+$/)?.[0];
if (endingQuotes && endingQuotes.length % 2 === 1) {
content = item.content.slice(0, --diff);
}
line = `m("${filePath}","${content}",${isNew ? '1' : '0'});d`;
this.buffer += line;
this.items.push(item);
item.content = item.content.slice(diff);
return false;
}
appendCode(content) {
const remaining = this.getRemainingSpace();
if (remaining > content.length) {
this.buffer += content;
return true;
}
return false;
}
getCode() {
return this.buffer;
}
getRemainingSpace() {
return this.maxChars - this.buffer.length;
}
}
class Installer {
importList;
rootDir;
rootPaths;
ingameDirectory;
buildPath;
maxChars;
files;
createdFiles;
autoCompile;
constructor(options) {
this.rootDir = options.rootDir;
this.buildPath = options.buildPath;
this.rootPaths = options.rootPaths;
this.ingameDirectory = options.ingameDirectory.trim().replace(/\/$/i, '');
this.maxChars = options.maxChars;
this.autoCompile = options.autoCompile;
this.files = [];
this.importList = this.createImportList(options.rootDir, options.result);
this.createdFiles = [];
this.autoCompile = options.autoCompile;
}
getCreatedFiles() {
return this.createdFiles;
}
async createInstallerFiles() {
await Promise.all(this.files.map(async (file, index) => {
const target = path.resolve(this.buildPath, 'installer' + index + '.src');
this.createdFiles.push(target);
await fs.writeFile(target, file.getCode(), { encoding: 'utf-8' });
}));
}
createImportList(rootDir, parseResult) {
const imports = Object.entries(parseResult).map(([target, code]) => {
const ingameFilepath = createBasePath(rootDir, target, '');
return {
filepath: target,
ingameFilepath,
content: escapeMSString(code).replace(/import_code\(/gi, 'import"+"_"+"code(')
};
});
return imports;
}
createContentHeader() {
return [
's = get_shell',
'c = s.host_computer',
'm = function(filePath, content, isNew)',
' segments = filePath.split("/")[1 : ]',
' fileName = segments.pop',
' for segment in segments',
' parentPath = "/" + segments[ : __segment_idx].join("/")',
' folderName = segment',
' if parentPath == "/" then',
' folderPath = "/" + folderName',
' else',
' folderPath = parentPath + "/" + folderName',
' end if',
' folderHandle = c.File(folderPath)',
' if folderHandle == null then',
' result = c.create_folder(parentPath, folderName) == 1',
' if result != 1 then exit("Could not create folder in """ + folderPath + """ due to: " + result)',
' print("New folder """ + folderPath + """ got created.")',
' folderHandle = c.File(folderPath)',
' end if',
' if not folderHandle.is_folder then exit("Entity at """ + folderPath + """ is not a folder. Installation got aborted.")',
' end for',
' parentPath = "/" + segments.join("/")',
' fileEntity = c.File(filePath)',
' if fileEntity == null then',
' result = c.touch(parentPath, fileName)',
' if result != 1 then exit("Could not create file in """ + filePath + """ due to: " + result)',
' fileEntity = c.File(filePath)',
' end if',
' if fileEntity == null then exit("Unable to get file at """ + filePath + """. Installation got aborted.")',
' if fileEntity.is_folder then exit("File at """ + filePath + """ is a folder but should be a source file. Installation got aborted.")',
' if fileEntity.is_binary then exit("File at """ + filePath + """ is a binary but should be a source file. Installation got aborted.")',
' if isNew then',
' fileEntity.set_content(content)',
' print("New file """ + filePath + """ got created.")',
' else',
' fileEntity.set_content(fileEntity.get_content + content)',
' print("Content got appended to """ + filePath + """.")',
' end if',
'end function',
'd = function',
' c.File(program_path).delete',
'end function',
''
]
.map((line) => line.trim())
.join(';');
}
createContentFooterAutoCompile() {
if (this.autoCompile.enabled) {
const rootImports = this.rootPaths.map((it) => {
return this.importList.find((item) => item.filepath === it);
});
return generateAutoCompileCode({
rootDirectory: this.ingameDirectory,
rootFilePaths: rootImports.map((it) => it.ingameFilepath),
importPaths: this.importList.map((it) => it.ingameFilepath),
purge: this.autoCompile.purge,
allowImport: this.autoCompile.allowImport
}).split(';');
}
return [];
}
createContentFooter() {
return ['d', ...this.createContentFooterAutoCompile(), ''].join(';');
}
async build() {
let file = new InstallerFile({
rootDirectory: this.ingameDirectory,
contentHeader: this.createContentHeader(),
maxChars: this.maxChars
});
this.files.push(file);
for (const item of this.importList) {
let done = false;
while (!done) {
done = file.insert(item);
if (!done) {
file = new InstallerFile({
rootDirectory: this.ingameDirectory,
contentHeader: this.createContentHeader(),
maxChars: this.maxChars,
previous: file
});
this.files.push(file);
}
}
}
const contentFooter = this.createContentFooter();
if (!file.appendCode(contentFooter)) {
file = new InstallerFile({
rootDirectory: this.ingameDirectory,
contentHeader: contentFooter,
maxChars: this.maxChars,
previous: file
});
this.files.push(file);
}
await this.createInstallerFiles();
}
}
export const createInstaller = async (options) => {
const installer = new Installer(options);
await installer.build();
return installer.getCreatedFiles();
};
//# sourceMappingURL=installer.js.map