import {glob} from 'glob'; import fs from 'node:fs'; import path from "path"; import { Command } from "commander"; import WorkerPool from './worker_pool.js'; import os from 'node:os'; import { execSync } from 'node:child_process'; const VERSION = 8; const generatedFilesPrefix = '-generated'; const resourcesFilesPrefix = '+resources'; const program = new Command(); program .name('QSP TO Sugarcube') .description('CLI to Convert Quest Soft sourcecode to Twine Sugarcube') .version('0.0.8') .option('-in, --input-file-path ','the path where the qsrc-files are') .option('-out, --output-file-path ','the path where the tw-files go') .option('-f, --single-file ','only converts the specified file') .option('-fs, --multiple-files-from-file ','only converts the files specified in the input file') .option('-outff, --failed-files-output-path ','path of the failed files file') .option('-sff, --skip-failedfiles-file','skips creation of the failed files file') .option('-ve, --verbose-errors','prints out complete error messages where possible') ; program.parse(process.argv); const options = program.opts(); console.log('START CONVERSION'); const startTime = (new Date()).getTime(); const inPath = options.inputFilePath ?? './in'; const outPath = options.outputFilePath ?? './out'; const failedFilesPath = options.failedFilesOutputPath ?? "./failedFiles.log"; var filePaths = []; try{ if(options.singleFile) filePaths = [options.singleFile]; else if(options.multipleFilesFromFile){ let multipleFilesFromFilePath = options.multipleFilesFromFile; if(multipleFilesFromFilePath == "ff") multipleFilesFromFilePath = failedFilesPath; const data = fs.readFileSync(multipleFilesFromFilePath, 'utf8'); filePaths = data.split('\n'); } else{ const searchArgument = path.join(inPath,'**/*.qsrc'); filePaths = await glob(searchArgument.replace(/\\/g,'/')); } }catch(e){ console.error("Error retrieving filePaths:",e); } fs.mkdir(path.join(outPath,generatedFilesPrefix), { recursive: true }, (err) => { if (err) throw err; }); const failedFiles = []; const pool = new WorkerPool(os.availableParallelism()); let percentageDisplayed = 0; const convertPromise = new Promise((resolve, reject) => { let finished = 0; if(!filePaths.length){ resolve("DONE"); return; } for (let filePath of filePaths) { pool.runTask({ VERSION: VERSION, filePath: filePath, outPath: outPath, generatedFilesPrefix: generatedFilesPrefix, options:options, }, (err, result) => { const wasSuccessful = (result[0] == 0); const message = result[1]; if(!wasSuccessful){ const displayFilePath = baseFileName(filePath); if(!options.verboseErrors) console.error(`${displayFilePath} failed`); failedFiles.push(filePath); } if (++finished === filePaths.length){ resolve("DONE"); }else{ const percentageFinished = finished / filePaths.length * 100; if(percentageFinished > Math.floor(percentageDisplayed/5 +1)*5){ console.log(`${Math.round(percentageFinished)}% done`); percentageDisplayed = percentageFinished; } } }); } }); await convertPromise; pool.close(); if(!options.skipFailedfilesFile){ const contentsOfFailedFilesFile = failedFiles.sort((a, b) => a.localeCompare(b)).join("\n"); fs.writeFile(failedFilesPath, contentsOfFailedFilesFile , err => { if (err) { console.error(err); } else { // file written successfully } }); } const resourcesPath = path.join(outPath,resourcesFilesPrefix); fs.rmSync(resourcesPath, { recursive: true, force: true }); fs.cpSync("./resources", resourcesPath, {recursive: true}); const executionTime = (new Date()).getTime() - startTime; const executionTimeString = `${Math.floor(executionTime/3600000).toString().padStart(2,'0')}:${Math.floor(executionTime % 3600000 / 60000).toString().padStart(2,'0')}:${Math.floor(executionTime % 60000 /1000).toString().padStart(2,'0')}.${(executionTime % 1000).toString().padStart(4,'0')}`; console.log('ENDED CONVERSION'.padEnd(30,'.')+ ' '+executionTimeString+` (${filePaths.length-failedFiles.length} of ${filePaths.length} successful)`); //#region Versioning File const pathOfVersioningFile = path.join(outPath,generatedFilesPrefix,'version.js'); let version = 'Unknown'; try{ //https://stackoverflow.com/a/35778030/7200161 version = execSync('git rev-parse HEAD') .toString().trim(); } catch(e){ //We didn't get the version. Not too bad. } const versionFileData = `setup.version = '${version}';\n`; fs.writeFileSync(pathOfVersioningFile,versionFileData); //#endregion //https://stackoverflow.com/a/25221100/7200161 function baseFileName(fullpath){ return fullpath.split('\\').pop().split('/').pop(); }