ctx-gen
Version:
AI-Enhanced Documentation Generator for Code Understanding
228 lines • 7.44 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
/**
* Updates the .gitignore file to include the documentation directory
* @param docsDir - The documentation directory to ignore
*/
export async function updateGitIgnore(docsDir) {
const gitignorePath = '.gitignore';
let content = '';
// Read existing .gitignore if it exists
if (await fs.pathExists(gitignorePath)) {
content = await fs.readFile(gitignorePath, 'utf-8');
}
// Normalize the docs directory path for .gitignore
const normalizedDocsDir = docsDir.replace(/\\/g, '/');
// Check if the directory is already in .gitignore
if (!content.split('\n').some(line => line.trim() === normalizedDocsDir)) {
// Add the docs directory to .gitignore
const updatedContent = content.endsWith('\n')
? `${content}${normalizedDocsDir}\n`
: `${content}\n${normalizedDocsDir}\n`;
// Write updated content back to .gitignore
await fs.writeFile(gitignorePath, updatedContent);
}
}
/**
* Creates a directory if it doesn't exist
* @param dirPath - The directory path to create
*/
export async function ensureDirectory(dirPath) {
await fs.ensureDir(dirPath);
}
/**
* Ensures parent directory exists before writing a file
* @param filePath - The file path
* @param content - The content to write
*/
export async function writeFileWithDir(filePath, content) {
const dir = path.dirname(filePath);
await fs.ensureDir(dir);
await fs.writeFile(filePath, content);
}
/**
* Gets the relative path from the current working directory
* @param absolutePath - The absolute file path
* @returns The relative path from cwd
*/
export function getRelativePath(absolutePath) {
return path.relative(process.cwd(), absolutePath);
}
/**
* Read patterns from .gitignore file
* @returns Array of gitignore patterns
*/
export async function readGitignorePatterns() {
const gitignorePath = '.gitignore';
const patterns = [];
if (await fs.pathExists(gitignorePath)) {
const content = await fs.readFile(gitignorePath, 'utf-8');
const lines = content.split('\n');
for (const line of lines) {
const trimmedLine = line.trim();
// Skip empty lines and comments
if (trimmedLine && !trimmedLine.startsWith('#')) {
patterns.push(trimmedLine);
}
}
}
return patterns;
}
/**
* Simple glob pattern matcher
* @param string - String to test
* @param pattern - Glob pattern
* @returns Whether the string matches the pattern
*/
function simpleGlobMatch(string, pattern) {
// Handle directory patterns (pattern ending with /)
const isDirPattern = pattern.endsWith('/');
let patternToUse = pattern;
if (isDirPattern) {
// If it's a directory pattern, we need to check if the string contains this directory
patternToUse = pattern.slice(0, -1);
// If string starts with the directory pattern, it should match
if (string === patternToUse || string.startsWith(patternToUse + '/')) {
return true;
}
return false;
}
// Handle files and other glob patterns
// Convert glob pattern to regex
const regexPattern = pattern
// Escape regex special characters except * and ?
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
// Replace ** with a special marker for recursive matching
.replace(/\*\*/g, '__RECURSIVE__')
// Replace * with regex equivalent (excluding /)
.replace(/\*/g, '[^/]*')
// Replace ? with regex equivalent (excluding /)
.replace(/\?/g, '[^/]')
// Restore ** as recursive matching pattern
.replace(/__RECURSIVE__/g, '.*');
// Create regex with start and end anchors
const regex = new RegExp(`^${regexPattern}$`);
// Test the string against the pattern
return regex.test(string);
}
/**
* Checks if a file should be excluded based on patterns including gitignore
* @param filePath - The file path to check
* @param excludePatterns - Array of exclude patterns (glob patterns)
* @param gitignorePatterns - Array of gitignore patterns
* @returns Whether the file should be excluded
*/
export function shouldExcludeFile(filePath, excludePatterns, gitignorePatterns = []) {
const relativePath = getRelativePath(filePath);
// Check explicit exclude patterns
for (const pattern of excludePatterns) {
if (relativePath.includes(pattern)) {
return true;
}
}
// Check gitignore patterns
for (const pattern of gitignorePatterns) {
if (simpleGlobMatch(relativePath, pattern)) {
return true;
}
}
return false;
}
/**
* Gets the language of a file based on extension
* @param filePath - The file path
* @returns The file language if supported, undefined otherwise
*/
export function getFileLanguage(filePath) {
const extension = path.extname(filePath).toLowerCase();
const languageMap = {
// JavaScript family
'.ts': 'typescript',
'.tsx': 'typescript',
'.js': 'javascript',
'.jsx': 'javascript',
'.mjs': 'javascript',
'.cjs': 'javascript',
'.json': 'json',
// Web-related
'.html': 'html',
'.css': 'css',
'.scss': 'scss',
'.sass': 'sass',
'.less': 'less',
'.svg': 'svg',
// Backend languages
'.py': 'python',
'.java': 'java',
'.groovy': 'groovy',
'.kt': 'kotlin',
'.scala': 'scala',
'.rb': 'ruby',
'.php': 'php',
'.pl': 'perl',
'.cs': 'csharp',
'.go': 'go',
'.rs': 'rust',
'.swift': 'swift',
'.dart': 'dart',
// C family
'.c': 'c',
'.cpp': 'cpp',
'.cc': 'cpp',
'.h': 'c',
'.hpp': 'cpp',
'.m': 'objective-c',
'.mm': 'objective-cpp',
// Functional languages
'.hs': 'haskell',
'.elm': 'elm',
'.fs': 'fsharp',
'.fsx': 'fsharp',
'.clj': 'clojure',
'.ml': 'ocaml',
// Shell and scripts
'.sh': 'bash',
'.bash': 'bash',
'.zsh': 'zsh',
'.ps1': 'powershell',
'.bat': 'batch',
'.cmd': 'batch',
// Other languages
'.lua': 'lua',
'.r': 'r',
'.sql': 'sql',
'.graphql': 'graphql',
'.yaml': 'yaml',
'.yml': 'yaml',
'.toml': 'toml',
'.md': 'markdown',
'.tex': 'latex'
};
return languageMap[extension];
}
/**
* Detects if a file contains binary content
* @param filePath - The file path
* @returns True if file is binary, false otherwise
*/
export async function isBinaryFile(filePath) {
try {
// Read the first 4KB of the file
const buffer = Buffer.alloc(4096);
const file = await fs.open(filePath, 'r');
const { bytesRead } = await fs.read(file, buffer, 0, 4096, 0);
await fs.close(file);
// Check for NUL bytes which typically indicate binary content
for (let i = 0; i < bytesRead; i++) {
if (buffer[i] === 0) {
return true;
}
}
return false;
}
catch (error) {
console.error(`Error checking if file is binary: ${filePath}`, error);
return false;
}
}
//# sourceMappingURL=fileUtils.js.map