Reputation: 5954
I'm trying to write a function to create a tree from a given starting directory. In other words, I choose a directory, and then the function maps absolutely everything in that particular directory. I've never done anything like this before, but I'm close to the expected output. However, I'm not sure where I'm going wrong.
> Test Root
> 1a
> 1b
> 2a
> 2b
> 2c
> 1c
> 2a
> 1d
> 2a
> 2b
> testfile.txt
The first level is good to go. The second level is almost good to go, but for some reason it's duplicating the parent
property and creating an additional nested object. It's also always inserting the type
property. I've tried removing properties in the constructor
, but that output is just as bewildering.
{
"parent": null,
"path": "C:\\Users\\Anthony\\Desktop\\Test Root",
"name": "Test Root",
"kids": [
{
"parent": {
"parent": "C:\\Users\\Anthony\\Desktop\\Test Root",
"path": "C:\\Users\\Anthony\\Desktop\\Test Root\\1a",
"name": "1a",
"kids": []
},
"type": "fileType"
},
{
"parent": {
"parent": "C:\\Users\\Anthony\\Desktop\\Test Root",
"path": "C:\\Users\\Anthony\\Desktop\\Test Root\\1b",
"name": "1b",
"kids": [
{
"parent": {
"parent": "C:\\Users\\Anthony\\Desktop\\Test Root\\1b",
"path": "C:\\Users\\Anthony\\Desktop\\Test Root\\1b\\2a",
"name": "2a",
"kids": []
},
"type": "fileType"
},
{
"parent": {
"parent": "C:\\Users\\Anthony\\Desktop\\Test Root\\1b",
"path": "C:\\Users\\Anthony\\Desktop\\Test Root\\1b\\2b",
"name": "2b",
"kids": []
},
"type": "fileType"
},
{
"parent": {
"parent": "C:\\Users\\Anthony\\Desktop\\Test Root\\1b",
"path": "C:\\Users\\Anthony\\Desktop\\Test Root\\1b\\2c",
"name": "2c",
"kids": []
},
"type": "fileType"
}
]
},
"type": "fileType"
},
{
"parent": {
"parent": "C:\\Users\\Anthony\\Desktop\\Test Root",
"path": "C:\\Users\\Anthony\\Desktop\\Test Root\\1c",
"name": "1c",
"kids": [
{
"parent": {
"parent": "C:\\Users\\Anthony\\Desktop\\Test Root\\1c",
"path": "C:\\Users\\Anthony\\Desktop\\Test Root\\1c\\2a",
"name": "2a",
"kids": []
},
"type": "fileType"
}
]
},
"type": "fileType"
},
{
"parent": {
"parent": "C:\\Users\\Anthony\\Desktop\\Test Root",
"path": "C:\\Users\\Anthony\\Desktop\\Test Root\\1d",
"name": "1d",
"kids": [
{
"parent": {
"parent": "C:\\Users\\Anthony\\Desktop\\Test Root\\1d",
"path": "C:\\Users\\Anthony\\Desktop\\Test Root\\1d\\2a",
"name": "2a",
"kids": []
},
"type": "fileType"
},
{
"parent": {
"parent": "C:\\Users\\Anthony\\Desktop\\Test Root\\1d",
"path": "C:\\Users\\Anthony\\Desktop\\Test Root\\1d\\2b",
"name": "2b",
"kids": []
},
"type": "fileType"
}
]
},
"type": "fileType"
},
{
"parent": {
"parent": "C:\\Users\\Anthony\\Desktop\\Test Root",
"path": "C:\\Users\\Anthony\\Desktop\\Test Root\\testfile.txt",
"name": "testfile.txt",
"type": "fileType"
},
"type": "fileType"
}
]
}
async function createTree(root){
const
Node = class {
constructor(parent, path, name, fifo){
this.parent = parent
this.path = path
this.name = name
fifo ? this.kids = [] : this.type = 'fileType'
}
addChild(parent, path, name, fifo){
this.kids.push(new Node(parent, path, name, fifo))
}
},
traverse = async function(node, path){
const childPaths = await fsp.readdir(path)
for (const childPath of childPaths){
const
name = childPath,
stats = await fsp.stat(join(path, childPath))
let
fifo
if (stats.isDirectory()) fifo = 1
else if (stats.isFile()) fifo = 0
const
childNode = new Node(path, join(path, childPath), name, fifo)
node.addChild(childNode)
traverse(childNode, join(path, childPath))
}
},
rootName = root.slice(-1) === '\\' ? root.slice(0,1) : root.slice(root.lastIndexOf('\\')+1),
tree = new Node(null, root, rootName, 1)
traverse(tree, root)
setTimeout(function(){
console.log(JSON.stringify(tree, null, 2))
}, 2500)
}
I just realized my asynchronous functions aren't returning anything. I'm going to have to look over all of this tomorrow.
Upvotes: 0
Views: 37
Reputation: 7315
The problem is that you are passing in only the first argument to your addChild
function here.
The addChild
function expects 4 arguments to be provided. However:
node.addChild(childNode)
// is approximately equivalent to:
addChild ( Node {...}, undefined, undefined, undefined)
You should be calling the function like this:
node.addChild(childNode.path, childNode.path, childNode.name, childNode.fifo)
You're on the right path logically, approaching a nicely recursive solution, but I think the class is a bit overkill and far too object-oriented for a functional problem such as this. Here's a quick example of a more functional version which should give you the result you expect:
const nodePath = require('path');
const fs = require('fs');
/**
* Supply an `fs` function and it will turn it into a promisified version
* @param {Function} fn The function to promisify. This means you do not
* need the `fsp` library.
*/
function promisify (fn) {
return (...args) => new Promise((resolve, reject) => fn(...args, (err, data) => err ? reject(err) : resolve(data)));
};
// Create promisified versions of `fs.stat` and `fs.readdir`
const stat = promisify(fs.stat);
const readdir = promisify(fs.readdir);
function createDirectory (parent, path, name, kids) {
return { parent, path, name, kids: kids || [] };
};
function createFile (parent, path, name) {
return { parent, path, name, type: 'fileType' };
}
// The main recursive function.
async function createTree (parent, path) {
let stats = await stat(path);
if (stats.isDirectory()) {
const children = await readdir(path);
// Because each recursive call retruns a promise, we want to continue only when
// all of them have resolved. So we use Promise.all for this.
let child_nodes = await Promise.all(
children.map(child => createTree(path, nodePath.join(path, child)))
);
// Create a directory node
return createDirectory(parent, path, nodePath.basename(path), child_nodes);
}
// Create a file node
return createFile(parent, path, nodePath.basename(path));
};
// Startup code, can use your own stuff here.
async function start () {
let tree = await createTree(null, '/home/james/stackoverflow/58706769/Test Root');
console.log(JSON.stringify(tree, null, 2));
}
start();
Running this file in Node, I get the following output:
{
"parent": null,
"path": "/home/james/stackoverflow/58706769/Test Root",
"name": "Test Root",
"kids": [
{
"parent": "/home/james/stackoverflow/58706769/Test Root",
"path": "/home/james/stackoverflow/58706769/Test Root/1a",
"name": "1a",
"kids": []
},
{
"parent": "/home/james/stackoverflow/58706769/Test Root",
"path": "/home/james/stackoverflow/58706769/Test Root/1b",
"name": "1b",
"kids": [
{
"parent": "/home/james/stackoverflow/58706769/Test Root/1b",
"path": "/home/james/stackoverflow/58706769/Test Root/1b/2a",
"name": "2a",
"kids": []
},
{
"parent": "/home/james/stackoverflow/58706769/Test Root/1b",
"path": "/home/james/stackoverflow/58706769/Test Root/1b/2b",
"name": "2b",
"kids": []
},
{
"parent": "/home/james/stackoverflow/58706769/Test Root/1b",
"path": "/home/james/stackoverflow/58706769/Test Root/1b/2c",
"name": "2c",
"kids": []
}
]
},
{
"parent": "/home/james/stackoverflow/58706769/Test Root",
"path": "/home/james/stackoverflow/58706769/Test Root/1c",
"name": "1c",
"kids": [
{
"parent": "/home/james/stackoverflow/58706769/Test Root/1c",
"path": "/home/james/stackoverflow/58706769/Test Root/1c/2a",
"name": "2a",
"kids": []
}
]
},
{
"parent": "/home/james/stackoverflow/58706769/Test Root",
"path": "/home/james/stackoverflow/58706769/Test Root/1d",
"name": "1d",
"kids": [
{
"parent": "/home/james/stackoverflow/58706769/Test Root/1d",
"path": "/home/james/stackoverflow/58706769/Test Root/1d/2a",
"name": "2a",
"kids": []
},
{
"parent": "/home/james/stackoverflow/58706769/Test Root/1d",
"path": "/home/james/stackoverflow/58706769/Test Root/1d/2b",
"name": "2b",
"kids": []
}
]
},
{
"parent": "/home/james/stackoverflow/58706769/Test Root",
"path": "/home/james/stackoverflow/58706769/Test Root/testfile.txt",
"name": "testfile.txt",
"type": "fileType"
}
]
}
Upvotes: 1