UNPKG

pretty-file-tree

Version:

Given a list of file paths, outputs a pretty-printed file tree

119 lines (104 loc) 3.91 kB
class Node { constructor(value) { this.value = value; this.children = new Map(); } addChild(value) { if (!this.children.has(value)) { const newNode = new Node(value); this.children.set(value, newNode); return newNode; } return this.children.get(value); } } class RenderTableEntry { constructor(value, depth, isLastChild) { this.value = value; this.depth = depth; this.isLastChild = isLastChild; } } function parseTree (filePaths, options) { const PATH_SEPARATOR =options.pathSeparator; const roots = new Map(); // Parse into tree let pathElements, rootElement, node; for (const path of filePaths) { pathElements = path.split(PATH_SEPARATOR); rootElement = pathElements.shift(); node = roots.get(rootElement); if (node == null) { node = new Node(rootElement); roots.set(rootElement, node); } for (const pathElement of pathElements) { node = node.addChild(pathElement); } } return builderRenderTable(roots, PATH_SEPARATOR); } function builderRenderTable(roots, pathSeparator) { let renderTable = []; let toVisit = [...roots.values()]; let nodeDepths = new Map(); // Now deep nodes are let lastNodes = new Set([toVisit[toVisit.length - 1]]); // Nodes that are the last child while (toVisit.length > 0) { let currentNode = toVisit.shift(); let nodeDepth = nodeDepths.get(currentNode) || 0; // Compress nodes with one child while (currentNode.children.size === 1) { let childNode = currentNode.children.values().next().value; currentNode.value += `${pathSeparator}${childNode.value}`; currentNode.children = childNode.children; } let children = [...currentNode.children.values()]; if (children.length > 0) { for(const child of children) { nodeDepths.set(child, nodeDepth + 1); } lastNodes.add(children[children.length -1]); toVisit = children.concat(toVisit); } renderTable.push(new RenderTableEntry(currentNode.value, nodeDepth, lastNodes.has(currentNode))); } return renderTable; } function printTree (renderTable, options) { let outputString = ''; let activeColumns = []; // Columns we are currently rendering because they are open for (const tableEntry of renderTable) { // Root node if (tableEntry.depth === 0) { outputString += tableEntry.value; } else { // Indent to the correct column for (let column = 1; column < tableEntry.depth; column++) { if (activeColumns[column]) { outputString += options.sequences.vertical; } else { outputString += options.sequences.emptyColumn; } } outputString += tableEntry.isLastChild ? options.sequences.endTee : options.sequences.throughTee; outputString += ' ' + tableEntry.value; } outputString += (tableEntry === renderTable[renderTable.length - 1]) ? '' : '\n'; activeColumns[tableEntry.depth] = !tableEntry.isLastChild; } return outputString; } const DEFAULT_OPTIONS = { pathSeparator: '/', sequences : { throughTee : '├──', endTee : '└──', vertical : '| ', emptyColumn : ' ' } }; function prettyFileTree(files) { if (!files || typeof files[Symbol.iterator] !== 'function') { return ''; } return printTree(parseTree(files, DEFAULT_OPTIONS), DEFAULT_OPTIONS); } module.exports = prettyFileTree;