add and Test Context Generator ...
This commit is contained in:
@@ -0,0 +1,557 @@
|
||||
#!/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
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user