Advertisement
Guest User

Untitled

a guest
Aug 18th, 2024
1,029
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import fs from 'fs/promises';
  2. import path from 'path';
  3. import { fileURLToPath } from 'url';
  4. import clipboard from 'clipboardy';
  5. import ignore from 'ignore';
  6.  
  7. const __filename = fileURLToPath(import.meta.url);
  8. const __dirname = path.dirname(__filename);
  9.  
  10. async function loadGitignore(dir) {
  11.     const ig = ignore();
  12.     try {
  13.         const gitignorePath = path.join(dir, '.gitignore');
  14.         const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
  15.         ig.add(gitignoreContent);
  16.     } catch (error) {
  17.         if (error.code !== 'ENOENT') {
  18.             console.warn('Error reading .gitignore:', error);
  19.         }
  20.     }
  21.     return ig;
  22. }
  23.  
  24. async function getAllFiles(dir, ig, baseDir) {
  25.     let files = new Set();
  26.     const entries = await fs.readdir(dir, { withFileTypes: true });
  27.     for (const entry of entries) {
  28.         const fullPath = path.join(dir, entry.name);
  29.         const relativePath = path.relative(baseDir, fullPath);
  30.         if (ig.ignores(relativePath)) continue;
  31.  
  32.         if (entry.isDirectory()) {
  33.             const subFiles = await getAllFiles(fullPath, ig, baseDir);
  34.             subFiles.forEach(file => files.add(file));
  35.         } else {
  36.             files.add(fullPath);
  37.         }
  38.     }
  39.     return files;
  40. }
  41.  
  42. async function fileContainsKeywords(filePath, keywords) {
  43.     if (keywords.length === 0) return true;
  44.     try {
  45.         const content = await fs.readFile(filePath, 'utf8');
  46.         return keywords.every(keyword => content.includes(keyword));
  47.     } catch (error) {
  48.         console.warn(`Error reading file ${filePath}:`, error);
  49.         return false;
  50.     }
  51. }
  52.  
  53. async function combineFiles(files, baseDir) {
  54.     let combinedContent = '';
  55.     for (const file of files) {
  56.         const content = await fs.readFile(file, 'utf8');
  57.         const relPath = path.relative(baseDir, file);
  58.         combinedContent += `// ${relPath.replace(/\\/g, '/')}\n${content}\n\n`;
  59.     }
  60.     return combinedContent;
  61. }
  62.  
  63. function parseArguments(args) {
  64.     return args.map(arg => {
  65.         // Remove surrounding quotes if present
  66.         arg = arg.replace(/^(['"])(.*)\1$/, '$2');
  67.        
  68.         const isExclude = arg.startsWith('!');
  69.         const actualArg = isExclude ? arg.slice(1) : arg;
  70.         let type, value;
  71.  
  72.         if (actualArg.startsWith('.')) {
  73.             type = 'extension';
  74.             value = actualArg;
  75.         } else if (actualArg.startsWith('file:')) {
  76.             type = 'file';
  77.             value = actualArg.slice(5);
  78.         } else if (actualArg.startsWith('dir:')) {
  79.             type = 'dir';
  80.             value = actualArg.slice(4);
  81.         } else if (actualArg.startsWith('contains:')) {
  82.             type = 'contains';
  83.             const match = actualArg.match(/contains:"([^"]*)"/);
  84.             value = match ? match[1] : actualArg.slice(9);
  85.         } else {
  86.             type = 'unknown';
  87.             value = actualArg;
  88.         }
  89.  
  90.         return { type, value, isExclude };
  91.     });
  92. }
  93.  
  94. async function applyFilters(files, filters, baseDir) {
  95.     let currentFiles = new Set();
  96.     let hasIncludeFilter = false;
  97.  
  98.     // First pass: apply all include filters
  99.     for (const filter of filters) {
  100.         if (!filter.isExclude) {
  101.             hasIncludeFilter = true;
  102.             const matchingFiles = new Set();
  103.             for (const file of files) {
  104.                 const relativePath = path.relative(baseDir, file);
  105.                 let matches = false;
  106.  
  107.                 switch (filter.type) {
  108.                     case 'extension':
  109.                         matches = relativePath.endsWith(filter.value);
  110.                         break;
  111.                     case 'file':
  112.                         matches = path.basename(file).toLowerCase().includes(filter.value.toLowerCase());
  113.                         break;
  114.                     case 'dir':
  115.                         matches = relativePath.toLowerCase().includes(filter.value.toLowerCase());
  116.                         break;
  117.                     case 'contains':
  118.                         matches = await fileContainsKeywords(file, [filter.value]);
  119.                         break;
  120.                 }
  121.  
  122.                 if (matches) matchingFiles.add(file);
  123.             }
  124.             currentFiles = new Set([...currentFiles, ...matchingFiles]);
  125.         }
  126.     }
  127.  
  128.     // If no include filters were applied, start with all files
  129.     if (!hasIncludeFilter) {
  130.         currentFiles = new Set(files);
  131.     }
  132.  
  133.     // Second pass: apply all exclude filters
  134.     for (const filter of filters) {
  135.         if (filter.isExclude) {
  136.             const filesToRemove = new Set();
  137.             for (const file of currentFiles) {
  138.                 const relativePath = path.relative(baseDir, file);
  139.                 let matches = false;
  140.  
  141.                 switch (filter.type) {
  142.                     case 'extension':
  143.                         matches = relativePath.endsWith(filter.value);
  144.                         break;
  145.                     case 'file':
  146.                         matches = path.basename(file).toLowerCase().includes(filter.value.toLowerCase());
  147.                         break;
  148.                     case 'dir':
  149.                         matches = relativePath.toLowerCase().includes(filter.value.toLowerCase());
  150.                         break;
  151.                     case 'contains':
  152.                         matches = await fileContainsKeywords(file, [filter.value]);
  153.                         break;
  154.                 }
  155.  
  156.                 if (matches) filesToRemove.add(file);
  157.             }
  158.             currentFiles = new Set([...currentFiles].filter(file => !filesToRemove.has(file)));
  159.         }
  160.     }
  161.  
  162.     return currentFiles;
  163. }
  164.  
  165. function displayUsageInstructions() {
  166.     console.log('Usage: node filecollect.js [options]');
  167.     console.log('\nOptions:');
  168.     console.log('  .ext                    Include files with the specified extension (e.g., .js, .ts, .vue)');
  169.     console.log('  !.ext                   Exclude files with the specified extension');
  170.     console.log('  file:part               Include files with names containing the specified part (case-insensitive)');
  171.     console.log('  !file:part              Exclude files with names containing the specified part');
  172.     console.log('  dir:part                Include all files in directories whose path contains the specified part (case-insensitive)');
  173.     console.log('  !dir:part               Exclude files in directories whose path contains the specified part');
  174.     console.log('  contains:"keyword"      Include files containing the specified keyword or phrase');
  175.     console.log('  !contains:"keyword"     Exclude files containing the specified keyword or phrase');
  176.     console.log('\nNotes:');
  177.     console.log('  - Filters are applied in the order they are specified.');
  178.     console.log('  - Directory inclusions add files without removing previously included files.');
  179.     console.log('  - Exclusion filters remove files from the current selection.');
  180.     console.log('  - Non-exclusion filters after directory inclusions only add files, they don\'t remove any.');
  181.     console.log('  - The contains filter is case-sensitive and requires exact matches.');
  182.     console.log('\nImportant:');
  183.     console.log('  When using exclusion filters (starting with !), you may need to escape them or use quotes');
  184.     console.log('  to prevent shell interpretation. For example:');
  185.     console.log('    npm run copyfiles -- dir:pages "!file:Sensor"');
  186.     console.log('    npm run copyfiles -- dir:pages \\!file:Sensor');
  187.     console.log('\nExamples:');
  188.     console.log('  node filecollect.js dir:pages "!file:Sensor"');
  189.     console.log('    # Includes all files in directories containing "pages", then excludes files with "Sensor" in the name');
  190.     console.log('  node filecollect.js dir:src !.json');
  191.     console.log('    # Includes all files in directories containing "src", then excludes .json files');
  192.     console.log('  node filecollect.js .js "!file:test" dir:src "!contains:\\"TODO\\""');
  193.     console.log('    # Includes .js files, excludes files with "test" in the name,');
  194.     console.log('    # adds all files from "src" directories, then excludes files containing "TODO"');
  195.     console.log('\nCombine and chain multiple options to create complex file selection criteria.');
  196. }
  197.  
  198. async function main() {
  199.     const filters = parseArguments(process.argv.slice(2));
  200.     if (filters.length === 0) {
  201.         displayUsageInstructions();
  202.         return;
  203.     }
  204.  
  205.     const projectDir = process.cwd();
  206.     try {
  207.         const ig = await loadGitignore(projectDir);
  208.         const allFiles = await getAllFiles(projectDir, ig, projectDir);
  209.         const matchingFiles = await applyFilters(allFiles, filters, projectDir);
  210.  
  211.         if (matchingFiles.size === 0) {
  212.             console.log(`No files found matching the specified criteria.`);
  213.             return;
  214.         }
  215.  
  216.         const combinedContent = await combineFiles(matchingFiles, projectDir);
  217.         await clipboard.write(combinedContent);
  218.        
  219.         console.log(`Combined ${matchingFiles.size} files matching the specified criteria and copied to clipboard.`);
  220.         console.log('\nList of copied files (relative paths):');
  221.         matchingFiles.forEach(file => {
  222.             const relativePath = path.relative(projectDir, file);
  223.             console.log(relativePath.replace(/\\/g, '/'));
  224.         });
  225.     } catch (error) {
  226.         console.error('An error occurred:', error);
  227.     }
  228. }
  229.  
  230. main();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement