Created
June 2, 2025 14:20
-
-
Save tailot/106125cc12d8e720bb16a620e7786ec5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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