558 lines
12 KiB
JavaScript
558 lines
12 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Context Generator Module ...
|
|
* a module for Generating Context ...
|
|
*
|
|
* Maintainer
|
|
*
|
|
* Hadi Khazaee Asl <https://saherelm.ir> (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
|