Skip to content

Instantly share code, notes, and snippets.

@tailot
Created June 2, 2025 14:20
Show Gist options
  • Save tailot/106125cc12d8e720bb16a620e7786ec5 to your computer and use it in GitHub Desktop.
Save tailot/106125cc12d8e720bb16a620e7786ec5 to your computer and use it in GitHub Desktop.
/*
USAGE: node <script_name.js> <file_or_directory_path> [start_marker] [end_marker] [extensions]
------------------------------------------------------------------------------------
<file_or_directory_path>: Path to the file or directory to analyze (required).
[start_marker]: Start marker string (optional).
If omitted, uses: "/*<taylored" (Default)
Provide "" for a literally empty marker.
[end_marker]: End marker string (optional).
If omitted, uses: "/*taylored>" (Default)
Provide "" for a literally empty marker.
[extensions]: Comma-separated list of extensions (e.g., .js,.ts,.txt)
(optional). If omitted or provided as an empty string (""),
searches all files (if analyzing a directory) or
the specified file (if its extension is not filtered).
Extensions are case-insensitive.
------------------------------------------------------------------------------------
Examples:
node search_blocks.js ./my_file.txt
node search_blocks.js ./project_directory
node search_blocks.js ./docs/report.md "" "" .md
node search_blocks.js ./src "/*<taylored" "/*taylored>" .js,.java
------------------------------------------------------------------------------------
*/
// Required modules
const fs = require('fs').promises;
const path = require('path');
// Default marker values
const DEFAULT_START_MARKER = "/*<taylored";
const DEFAULT_END_MARKER = "/*taylored>";
/**
* Reads a single file and searches for blocks delimited by a specific regex.
* @param {string} filePath - The path to the file to analyze.
* @param {RegExp} regexInstance - The RegExp instance to use for searching.
* @returns {Promise<Array>} A promise that resolves with an array of found blocks.
*/
async function findTayloredBlocksInFile(filePath, regexInstance) {
const foundBlocks = [];
let content;
try {
content = await fs.readFile(filePath, 'utf-8');
} catch (error) {
// console.warn(`Warning: Could not read file ${filePath}: ${error.message}`);
return [];
}
let match;
while ((match = regexInstance.exec(content)) !== null) {
const matchStartIndex = match.index;
const matchEndIndex = match.index + match[0].length;
const startLine = content.substring(0, matchStartIndex).split('\n').length;
const endLine = content.substring(0, matchEndIndex).split('\n').length;
foundBlocks.push({
file: filePath,
startMarkerLine: startLine,
endMarkerLine: endLine,
});
}
return foundBlocks;
}
/**
* Recursively scans a directory for blocks, optionally filtering by extension.
* @param {string} dirPath - The path to the directory to scan.
* @param {RegExp} regexInstance - The RegExp instance to use for searching.
* @param {Array<string>} targetExtensions - Array of extensions to include (e.g., ['.js', '.txt']). If empty, processes all files.
* @param {Array} allFoundBlocks - Array to accumulate all found blocks.
* @returns {Promise<Array>} A promise that resolves with the updated array of found blocks.
*/
async function scanDirectory(dirPath, regexInstance, targetExtensions, allFoundBlocks = []) {
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
await scanDirectory(fullPath, regexInstance, targetExtensions, allFoundBlocks);
} else if (entry.isFile()) {
let processThisFile = false;
if (targetExtensions.length === 0) {
processThisFile = true;
} else {
const fileExt = path.extname(entry.name).toLowerCase();
if (targetExtensions.includes(fileExt)) {
processThisFile = true;
}
}
if (processThisFile) {
const blocksInFile = await findTayloredBlocksInFile(fullPath, regexInstance);
allFoundBlocks.push(...blocksInFile);
}
}
}
} catch (error) {
console.error(`Error reading directory ${dirPath}: ${error.message}`);
}
return allFoundBlocks;
}
// --- Main function to run the script ---
async function main() {
const args = process.argv.slice(2);
if (args.length < 1) {
// This console output is for runtime help if arguments are incorrect.
// The comment block at the top of the file serves as static documentation.
console.log("------------------------------------------------------------------------------------");
console.log("USAGE: node <script_name.js> <file_or_directory_path> [start_marker] [end_marker] [extensions]");
console.log("------------------------------------------------------------------------------------");
console.log(" <file_or_directory_path>: Path to the file or directory to analyze (required).");
console.log(" [start_marker]: Start marker string (optional).");
console.log(" If omitted, uses: \"" + DEFAULT_START_MARKER + "\"");
console.log(" Provide \"\" for a literally empty marker.");
console.log(" [end_marker]: End marker string (optional).");
console.log(" If omitted, uses: \"" + DEFAULT_END_MARKER + "\"");
console.log(" Provide \"\" for a literally empty marker.");
console.log(" [extensions]: Comma-separated list of extensions (e.g., .js,.ts,.txt)");
console.log(" (optional). If omitted or provided as an empty string (\"\"),");
console.log(" searches all files (if analyzing a directory) or");
console.log(" the specified file (if its extension is not filtered).");
console.log(" Extensions are case-insensitive.");
console.log("------------------------------------------------------------------------------------");
console.log("Examples:");
console.log(" node search_blocks.js ./my_file.txt"); // Using a generic name for the script in examples
console.log(" node search_blocks.js ./project_directory");
console.log(` node search_blocks.js ./docs/report.md "" "" .md`);
console.log(` node search_blocks.js ./src "${DEFAULT_START_MARKER}" "${DEFAULT_END_MARKER}" .js,.java`);
console.log("------------------------------------------------------------------------------------");
return;
}
const pathToSearch = args[0];
// Use provided argument (even if empty string), otherwise use default.
const startMarkerInput = (args[1] !== undefined) ? args[1] : DEFAULT_START_MARKER;
const endMarkerInput = (args[2] !== undefined) ? args[2] : DEFAULT_END_MARKER;
let targetExtensions = []; // Default: empty array, means all extensions
if (args[3] !== undefined && args[3].trim() !== "") { // If extensions argument is provided and not just whitespace
targetExtensions = args[3].split(',')
.map(ext => ext.trim().toLowerCase()) // Clean up, lowercase
.filter(ext => ext.length > 0) // Remove empty strings (e.g., from ",,")
.map(ext => ext.startsWith('.') ? ext : '.' + ext); // Ensure it starts with '.'
}
// Construct the regex dynamically
const tayloredRegex = new RegExp(
startMarkerInput.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + // Escape for start marker
'(.*?)' + // Capture content
endMarkerInput.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), // Escape for end marker
'gs' // Flags: g (global), s (dotall)
);
let results = [];
let searchActuallyPerformed = false;
try {
const stats = await fs.stat(pathToSearch);
if (stats.isFile()) {
let processThisFile = false;
if (targetExtensions.length === 0) { // No extension filter, process
processThisFile = true;
} else {
const fileExt = path.extname(pathToSearch).toLowerCase();
if (targetExtensions.includes(fileExt)) { // File extension is in the specified list
processThisFile = true;
}
}
if (processThisFile) {
results = await findTayloredBlocksInFile(pathToSearch, tayloredRegex);
searchActuallyPerformed = true;
} else {
console.log(`\nThe specified file '${pathToSearch}' does not have an extension matching the requested ones (${targetExtensions.join(', ')}). No search performed on this file.`);
}
} else if (stats.isDirectory()) {
results = await scanDirectory(pathToSearch, tayloredRegex, targetExtensions);
searchActuallyPerformed = true; // Scanning a directory counts as a search attempt
} else {
console.error(`\nPath '${pathToSearch}' is not a processable standard file or directory.`);
return;
}
// Print results
if (results.length > 0) {
console.log(`\n--- Search Results (${results.length} blocks found) ---`);
results.forEach(block => {
console.log(`File: ${block.file}`);
console.log(` Found from line ${block.startMarkerLine} to line ${block.endMarkerLine}`);
});
console.log(`--- End of results ---`);
} else {
if (searchActuallyPerformed) {
console.log("\nNo blocks matching the criteria were found.");
}
}
} catch (error) {
if (error.code === 'ENOENT') {
console.error(`\nError: The specified path '${pathToSearch}' does not exist.`);
} else {
console.error(`\nError accessing path '${pathToSearch}': ${error.message}`);
}
return;
}
}
// Run the main function
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment