index.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import qsrc2tw from "./src/qsrc2tw.js";
  2. import {glob} from 'glob';
  3. import fs from 'node:fs';
  4. import path from "path";
  5. import { Command } from "commander";
  6. import md5 from "./src/misc/md5.js";
  7. const VERSION = 1;
  8. const generatedFilesPrefix = '-generated';
  9. const resourcesFilesPrefix = '+resources';
  10. const program = new Command();
  11. program
  12. .name('QSP TO Sugarcube')
  13. .description('CLI to Convert Quest Soft sourcecode to Twine Sugarcube')
  14. .version('0.0.1')
  15. .option('-in, --input-file-path <path>','the path where the qsrc-files are')
  16. .option('-out, --output-file-path <path>','the path where the tw-files go')
  17. .option('-f, --single-file <path>','only converts the specified file')
  18. .option('-fs, --multiple-files-from-file <path>','only converts the files specified in the input file')
  19. .option('-outff, --failed-files-output-path <path>','path of the failed files file')
  20. .option('-sff, --skip-failedfiles-file','skips creation of the failed files file')
  21. .option('-ve, --verbose-errors','prints out complete error messages where possible')
  22. ;
  23. program.parse(process.argv);
  24. const options = program.opts();
  25. console.log('START CONVERTION');
  26. const startTime = (new Date()).getTime();
  27. const inPath = options.inputFilePath ?? './in';
  28. const outPath = options.outputFilePath ?? './out';
  29. const failedFilesPath = options.failedFilesOutputPath ?? "./failedFiles.log";
  30. var filePaths = [];
  31. try{
  32. if(options.singleFile)
  33. filePaths = [options.singleFile];
  34. else if(options.multipleFilesFromFile){
  35. let multipleFilesFromFilePath = options.multipleFilesFromFile;
  36. if(multipleFilesFromFilePath == "ff")
  37. multipleFilesFromFilePath = failedFilesPath;
  38. const data = fs.readFileSync(multipleFilesFromFilePath, 'utf8');
  39. filePaths = data.split('\n');
  40. }
  41. else{
  42. const searchArgument = path.join(inPath,'**/*.qsrc');
  43. filePaths = await glob(searchArgument.replace(/\\/g,'/'));
  44. }
  45. }catch(e){
  46. console.error("Error retrieving filePaths:",e);
  47. }
  48. fs.mkdir(path.join(outPath,generatedFilesPrefix), { recursive: true }, (err) => {
  49. if (err) throw err;
  50. });
  51. //https://stackoverflow.com/a/25221100/7200161
  52. function baseFileName(fullpath){
  53. return fullpath.split('\\').pop().split('/').pop();
  54. }
  55. let consoleActive = true;
  56. consoleOverwrite();
  57. function consoleOverwrite(){
  58. //https://stackoverflow.com/a/30197438/7200161
  59. // define a new console
  60. var consoleOverwrite=(function(oldCons){
  61. return {
  62. log: function(text){
  63. if(consoleActive)
  64. oldCons.log(text);
  65. },
  66. info: function (text) {
  67. if(consoleActive)
  68. oldCons.info(text);
  69. },
  70. warn: function (text) {
  71. if(consoleActive)
  72. oldCons.warn(text);
  73. },
  74. error: function (text) {
  75. if(consoleActive)
  76. oldCons.error(text);
  77. }
  78. };
  79. }(console));
  80. //Then redefine the old console
  81. console = consoleOverwrite;
  82. }
  83. async function convertFile(filePath){
  84. return new Promise((resolve, reject) => {
  85. fs.readFile(filePath, 'utf8', (err, data) => {
  86. if (err) {
  87. console.error(err);
  88. reject(err);
  89. return;
  90. }
  91. const startTime = (new Date()).getTime();
  92. const codeHash = md5(data);
  93. const baseFileNameStr = baseFileName(filePath);
  94. const outFilePath = path.join(outPath,generatedFilesPrefix,baseFileNameStr.split('.')[0]+'.tw');
  95. if((data.split('\n')?.[1]).startsWith("!! SKIP_QSRC2TW")){
  96. resolve("SKIP");
  97. return;
  98. }
  99. if (fs.existsSync(outFilePath)) {
  100. try{
  101. const secondLineData = fs.readFileSync(outFilePath, "utf-8").split('\n')[1];
  102. const qsrc2twResultMatch = secondLineData.match(/<!--\s*qsrc2twResult=({.*})\s*-->/);
  103. if(qsrc2twResultMatch){
  104. const qsrc2twResult = JSON.parse(qsrc2twResultMatch[1]);
  105. if((qsrc2twResult.code && codeHash == qsrc2twResult.code) &&
  106. (qsrc2twResult.version && VERSION == qsrc2twResult.version)){
  107. resolve("EXISTS");
  108. return;
  109. }
  110. }
  111. }
  112. catch(e){
  113. }
  114. }
  115. try{
  116. let twineCode = "";
  117. consoleActive = options.verboseErrors;
  118. try{
  119. twineCode = qsrc2tw(data, true)
  120. .split('\n')
  121. .toSpliced(1,0,`<!--qsrc2twResult={"version":${VERSION},"code":"${codeHash}","time":"${(new Date().toISOString())}"}-->`)
  122. .join('\n');
  123. }
  124. catch(e){
  125. throw e;
  126. }
  127. finally{
  128. consoleActive = true;
  129. }
  130. fs.writeFile(outFilePath, twineCode, err => {
  131. if (err) {
  132. console.error(err);
  133. } else {
  134. }
  135. });
  136. const executionTime = (new Date()).getTime() - startTime;
  137. console.log(`${baseFileNameStr.padEnd(30,'.')} ${executionTime} ms`);
  138. resolve("SUCCESS");
  139. }catch(e){
  140. console.error(`Error in "${baseFileNameStr}". No output was generated`, e);
  141. reject(e);
  142. }
  143. });
  144. });
  145. }
  146. const failedFiles = [];
  147. var promises = [];
  148. for(let filePath of filePaths){
  149. const convertPromise = convertFile(filePath);
  150. promises.push(convertPromise);
  151. convertPromise.catch((e)=>{
  152. failedFiles.push(filePath);
  153. });
  154. }
  155. const allPromises = Promise.allSettled(promises);
  156. await allPromises;
  157. if(!options.skipFailedfilesFile){
  158. const contentsOfFailedFilesFile = failedFiles.sort((a, b) => a.localeCompare(b)).join("\n");
  159. fs.writeFile(failedFilesPath, contentsOfFailedFilesFile , err => {
  160. if (err) {
  161. console.error(err);
  162. } else {
  163. // file written successfully
  164. }
  165. });
  166. }
  167. const resourcesPath = path.join(outPath,resourcesFilesPrefix);
  168. fs.rmSync(resourcesPath, { recursive: true, force: true });
  169. fs.cpSync("./resources", resourcesPath, {recursive: true});
  170. const executionTime = (new Date()).getTime() - startTime;
  171. console.log('ENDED CONVERSION'.padEnd(30,'.')+ ' '+executionTime+' ms');