#!/usr/bin/env node /** * Context Generator Module ... * a module for Generating Context ... * * Maintainer * * Hadi Khazaee Asl (hadi_khazaee_asl@yahoo.com) */ // //#region Imports ... const fs = require("fs"); const path = require("path"); //#endregion // //#region Constants ... const outputFilePrefix = "xDashboard"; const allowedPatterns = [ // { // kind: "file", // mode: "allow", // match: "name", // pattern: /^*.mq5$/i // }, // { // kind: "directory", // mode: "deny", // match: "name", // pattern: /^node_modules$/i // }, // { // kind: "file", // mode: "allow", // match: "path", // pattern: /^x-saherelm.*.mq5$/i // }, // // Base Pilot Files ... { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.base.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.session-detector.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-account.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-alert.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-app-dialog.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-backtester.expert.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-bar.analyser.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-chart.helper.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-cobject.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-data.collector.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-expert.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-guard.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-helper.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-http.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-market.pattern.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-md5.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-poi.drawer.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-position.drawer.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-restrictions.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-rm.panel.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-signaller.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-target.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-trade-manager.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-trade.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-volume.class.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.backtester.ea.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-expert.ea.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x121.xcc.helper.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x121.xct.helper.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x121.xcc.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x121.xct.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.common.extensions.lib.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.common.lib.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.common.models.lib.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.draw.lib.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-enums.lib.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-guard.lib.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-ohcl.lib.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-pivot.analysis.lib.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-poi.extensions.lib.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-poi.lib.mq5$/i }, { kind: "file", mode: "allow", match: "name", pattern: /^x-saherelm.x-trade.lib.mq5$/i }, ] //#endregion // // Reading Required Info const inputPath = process.argv[2]; const searchPath = path.resolve(inputPath || process.cwd()); // Check Given Path is Valid or not ... if (!fs.existsSync(searchPath)) { console.error(`Error: path not found: ${searchPath}`); process.exit(1); } // // Retrieve Stats of Given Path ... const stat = fs.statSync(searchPath); if (!stat.isDirectory()) { console.error(`Error: path is not a directory: ${searchPath}`); process.exit(1); } // // Generate Required Data ... const timestamp = getTimestamp(new Date()); const folderName = path.basename(searchPath) || "root"; const safeFolderName = sanitizeFileName(folderName); // Generate Output file Name ... const outputFileName = inputPath ? `${outputFilePrefix}-${safeFolderName}-${timestamp}.txt` : `${outputFilePrefix}-${timestamp}.txt`; // const outputPath = path.join( process.cwd(), "Documents", "GeneratedContext", outputFileName ); // Create Output File ... fs.writeFileSync(outputPath, "", "utf8"); // Navigate to Context Generation ... walk(searchPath); // console.log(`Context generated: ${outputPath}`); // //#region Required Tools ... /** * Validate Timestamp ... * * @param {*} value * @returns */ function pad(value) { return String(value).padStart(2, "0"); } /** * Retrieve Validate ... * * @param {*} date * @returns */ function getTimestamp(date) { // const year = date.getFullYear(); const month = pad(date.getMonth() + 1); const day = pad(date.getDate()); const hours = pad(date.getHours()); const minutes = pad(date.getMinutes()); const seconds = pad(date.getSeconds()); // return `${year}${month}${day}_${hours}${minutes}${seconds}`; } /** * Retrieve Leaf Folder ... * * @param {*} value * @returns */ function sanitizeFileName(value) { return value.replace(/[\\/:*?"<>|]/g, "_"); } /** * Match Value ... * * @param {*} rule * @param {*} targetPath * @returns */ function getMatchValue(rule, targetPath) { // const normalizedPath = targetPath.replace(/\\/g, "/"); if (rule.match === "path") { return normalizedPath; } // return path.basename(targetPath); } /** * Match Patterns ... * * @param {*} targetPath * @param {*} rules * @returns */ function matchesPatterns(targetPath, rules) { // return rules.some(rule => { const value = getMatchValue(rule, targetPath); return rule.pattern.test(value); }); } /** * Check a Directory Path is Valid for Parsing or not ... * * @param {*} fileName * @returns */ function isAllowedFile(filePath) { // // Extract Deny Rules ... const denyRules = allowedPatterns.filter( p => p.kind === "file" && p.mode === "deny" ); // // Extract Allowed Rules ... const allowRules = allowedPatterns.filter( p => p.kind === "file" && p.mode === "allow" ); // Check Deny Rules First ... if (matchesPatterns(filePath, denyRules)) { return false; } // Check Allowed Rules ... return matchesPatterns(filePath, allowRules); } /** * Check a Directory Path is Valid for Parsing or not ... * * @param {*} directoryPath * @returns */ function isAllowedDirectory(directoryPath) { // // Extract Deny Rules ... const denyRules = allowedPatterns.filter( p => p.kind === "directory" && p.mode === "deny" ); // // Extract Allowed Rules ... const allowRules = allowedPatterns.filter( p => p.kind === "directory" && p.mode === "allow" ); // // Check Deny Patterns as First ... if (matchesPatterns(directoryPath, denyRules)) { return false; } // // Check Allow Patterns Exists ... if (allowRules.length === 0) { return true; } // // Chck Allowed Patterns Matched ... return matchesPatterns(directoryPath, allowRules); } /** * Loop through a Directory and Navigate files for including in Context ... * * @param {*} dir * @returns */ function walk(dir) { // // Reading Directory Content ... let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (error) { fs.appendFileSync( outputPath, `\n### ERROR reading directory: ${dir}\n${error.message}\n`, "utf8" ); return; } // // Loop through Entries ... for (const entry of entries) { // const fullPath = path.join(dir, entry.name); // // Ignore if Symlink ... if (entry.isSymbolicLink()) { continue; } // // Handle if requested Path is Directory ... if (entry.isDirectory()) { // // Allowed Folders ... if (!isAllowedDirectory(fullPath)) { continue; } // walk(fullPath); } else // // Handle if requested Path is File ... if (entry.isFile()) { // // Allowed File ... if (!isAllowedFile(fullPath)) { continue; } // // Holds File Content ... let content; try { content = fs.readFileSync(fullPath, "utf8"); } catch (error) { content = `// Error reading file: ${error.message}`; } // // Append File Content for Context ... fs.appendFileSync( outputPath, `\n### FILE: ${fullPath}\n\n${content}\n`, "utf8" ); } } } //#endregion