index.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import {glob} from 'glob';
  2. import fs from 'node:fs';
  3. import path from "path";
  4. import { Command } from "commander";
  5. import WorkerPool from './worker_pool.js';
  6. import os from 'node:os';
  7. import { execSync } from 'node:child_process';
  8. const VERSION = 8;
  9. const generatedFilesPrefix = '-generated';
  10. const resourcesFilesPrefix = '+resources';
  11. const program = new Command();
  12. program
  13. .name('QSP TO Sugarcube')
  14. .description('CLI to Convert Quest Soft sourcecode to Twine Sugarcube')
  15. .version('0.0.8')
  16. .option('-in, --input-file-path <path>','the path where the qsrc-files are')
  17. .option('-out, --output-file-path <path>','the path where the tw-files go')
  18. .option('-f, --single-file <path>','only converts the specified file')
  19. .option('-fs, --multiple-files-from-file <path>','only converts the files specified in the input file')
  20. .option('-outff, --failed-files-output-path <path>','path of the failed files file')
  21. .option('-sff, --skip-failedfiles-file','skips creation of the failed files file')
  22. .option('-ve, --verbose-errors','prints out complete error messages where possible')
  23. ;
  24. program.parse(process.argv);
  25. const options = program.opts();
  26. console.log('START CONVERSION');
  27. const startTime = (new Date()).getTime();
  28. const inPath = options.inputFilePath ?? './in';
  29. const outPath = options.outputFilePath ?? './out';
  30. const failedFilesPath = options.failedFilesOutputPath ?? "./failedFiles.log";
  31. var filePaths = [];
  32. try{
  33. if(options.singleFile)
  34. filePaths = [options.singleFile];
  35. else if(options.multipleFilesFromFile){
  36. let multipleFilesFromFilePath = options.multipleFilesFromFile;
  37. if(multipleFilesFromFilePath == "ff")
  38. multipleFilesFromFilePath = failedFilesPath;
  39. const data = fs.readFileSync(multipleFilesFromFilePath, 'utf8');
  40. filePaths = data.split('\n');
  41. }
  42. else{
  43. const searchArgument = path.join(inPath,'**/*.qsrc');
  44. filePaths = await glob(searchArgument.replace(/\\/g,'/'));
  45. }
  46. }catch(e){
  47. console.error("Error retrieving filePaths:",e);
  48. }
  49. fs.mkdir(path.join(outPath,generatedFilesPrefix), { recursive: true }, (err) => {
  50. if (err) throw err;
  51. });
  52. const failedFiles = [];
  53. const pool = new WorkerPool(os.availableParallelism());
  54. let percentageDisplayed = 0;
  55. const convertPromise = new Promise((resolve, reject) => {
  56. let finished = 0;
  57. if(!filePaths.length){
  58. resolve("DONE");
  59. return;
  60. }
  61. for (let filePath of filePaths) {
  62. pool.runTask({
  63. VERSION: VERSION,
  64. filePath: filePath,
  65. outPath: outPath,
  66. generatedFilesPrefix: generatedFilesPrefix,
  67. options:options,
  68. }, (err, result) => {
  69. const wasSuccessful = (result?.[0] === 0);
  70. const message = result?.[1] ?? '';
  71. if(!wasSuccessful){
  72. const displayFilePath = baseFileName(filePath);
  73. if(!options.verboseErrors)
  74. console.error(`${displayFilePath} failed`);
  75. failedFiles.push(filePath);
  76. }
  77. if (++finished === filePaths.length){
  78. resolve("DONE");
  79. }else{
  80. const percentageFinished = finished / filePaths.length * 100;
  81. if(percentageFinished > Math.floor(percentageDisplayed/5 +1)*5){
  82. console.log(`${Math.round(percentageFinished)}% done`);
  83. percentageDisplayed = percentageFinished;
  84. }
  85. }
  86. });
  87. }
  88. });
  89. await convertPromise;
  90. pool.close();
  91. if(!options.skipFailedfilesFile){
  92. const contentsOfFailedFilesFile = failedFiles.sort((a, b) => a.localeCompare(b)).join("\n");
  93. fs.writeFile(failedFilesPath, contentsOfFailedFilesFile , err => {
  94. if (err) {
  95. console.error(err);
  96. } else {
  97. // file written successfully
  98. }
  99. });
  100. }
  101. const resourcesPath = path.join(outPath,resourcesFilesPrefix);
  102. fs.rmSync(resourcesPath, { recursive: true, force: true });
  103. fs.cpSync("./resources", resourcesPath, {recursive: true});
  104. const executionTime = (new Date()).getTime() - startTime;
  105. 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')}`;
  106. console.log('ENDED CONVERSION'.padEnd(30,'.')+ ' '+executionTimeString+` (${filePaths.length-failedFiles.length} of ${filePaths.length} successful)`);
  107. //#region Versioning File
  108. const pathOfVersioningFile = path.join(outPath,generatedFilesPrefix,'version.js');
  109. let version = 'Unknown';
  110. try{
  111. //https://stackoverflow.com/a/35778030/7200161
  112. version = execSync('git rev-parse HEAD')
  113. .toString().trim();
  114. }
  115. catch(e){
  116. //We didn't get the version. Not too bad.
  117. }
  118. const versionFileData = `setup.version = '${version}';\n`;
  119. fs.writeFileSync(pathOfVersioningFile,versionFileData);
  120. //#endregion
  121. //https://stackoverflow.com/a/25221100/7200161
  122. function baseFileName(fullpath){
  123. return fullpath.split('\\').pop().split('/').pop();
  124. }