linguist-js
Version:
Analyse the programming languages used in a folder or from raw content, using the same rules that GitHub Linguist does.
83 lines (82 loc) • 3.46 kB
JavaScript
import FS from 'node:fs';
import Path from 'node:path';
import parseGitignore from '../parsing/parseGitignore.js';
import { normAbsPath, normPath } from './normalisedPath.js';
let allFiles;
let allFolders;
/** Generate list of files in a directory. */
export default function walkTree(data) {
const { init, commonRoot, folderRoots, folders, ignored } = data;
// Initialise files and folders lists
if (init) {
allFiles = new Set();
allFolders = new Set();
}
// Walk tree of a folder
if (folders.length === 1) {
const folder = folders[0];
const localRoot = folderRoots[0].replace(commonRoot, '').replace(/^\//, '');
// Get list of files and folders inside this folder
const files = FS.readdirSync(folder).map((file) => {
// Create path relative to root
const base = normAbsPath(folder, file).replace(commonRoot, '.');
// Add trailing slash to mark directories
const isDir = FS.lstatSync(Path.resolve(commonRoot, base)).isDirectory();
return isDir ? `${base}/` : base;
});
// Read and apply gitignores
const gitignoreFilename = normPath(folder, '.gitignore');
if (FS.existsSync(gitignoreFilename)) {
const gitignoreContents = FS.readFileSync(gitignoreFilename, 'utf-8');
const ignoredPaths = parseGitignore(gitignoreContents);
const rootRelIgnoredPaths = ignoredPaths.map((ignorePath) =>
// get absolute path of the ignore glob
normPath(folder, ignorePath)
// convert abs ignore glob to be relative to the root folder
.replace(commonRoot + '/', ''));
ignored.add(rootRelIgnoredPaths);
}
// Add gitattributes if present
const gitattributesPath = normPath(folder, '.gitattributes');
if (FS.existsSync(gitattributesPath)) {
allFiles.add(gitattributesPath);
}
// Loop through files and folders
for (const file of files) {
// Create absolute path for disc operations
const path = normAbsPath(commonRoot, file);
const localPath = localRoot ? file.replace(`./${localRoot}/`, '') : file.replace('./', '');
// Skip if nonexistant
const nonExistant = !FS.existsSync(path);
if (nonExistant)
continue;
// Skip if marked in gitignore
const isIgnored = ignored.test(localPath).ignored;
if (isIgnored)
continue;
// Add absolute folder path to list
allFolders.add(normAbsPath(folder));
// Check if this is a folder or file
if (file.endsWith('/')) {
// Recurse into subfolders
allFolders.add(path);
walkTree({ init: false, commonRoot, folderRoots, folders: [path], ignored });
}
else {
// Add file path to list
allFiles.add(path);
}
}
}
// Recurse into all folders
else {
for (const i in folders) {
walkTree({ init: false, commonRoot, folderRoots: [folderRoots[i]], folders: [folders[i]], ignored });
}
}
// Return absolute files and folders lists
return {
files: [...allFiles].map((file) => file.replace(/^\./, commonRoot)),
folders: [...allFolders],
};
}