i need help writing a node.js application that searches for all sub directories under the current directory which their names contain the specified string.
for example the user want to search all directories that have the string 'test' in it.
what is the js code i need to use?
i try using this:
var walk = function(dir) {
var results = []
var list = fs.readdirSync(dir)
list.forEach(function(file) {
file = dir + '/' + file
var stat = fs.statSync(file)
if (stat && stat.isDirectory()) results = results.concat(walk(file))
else results.push(file)
})
return results
}
Take a look at node-glob
In your case you could use it like this. This pattern will give you all files in the folder that contain at least once test in the name.
var glob = require("glob")
glob("+(test).js", options, function (er, files) {
// files is an array of filenames.
// If the `nonull` option is set, and nothing
// was found, then files is ["**/*.js"]
// er is an error object or null.
if (er) {
// omg something went wrong
throw new Exception(er);
}
var requiredFiles = files.map(function(filename) {
return require(filename);
});
// do something with the required files
});
Related
I am trying to develop an extension for VSCode and I need to search for the exact file path where my extension is saved. I've tried this code:
var text;
const directoryPath = path.join(Os.homedir(), '.vscode/extensions');
fs.readdir(directoryPath, function (err, files) {
if (err) {
y2.appendLine('Unable to scan directory: ' + err);
}
for (let i=0;i<files.length;i++){
if (files[i].startsWith("MY_EXTENSION")){
text = files[i];
y2.appendLine(text);
break;
}
}
});
y2.appendLine(text);
y2 is only an output channel I added.
The outcome of this code is:
undefined
MY_EXTENSION
If I take out the second y2.appendLine(text) I receive only MY_EXTENSION but I don't get how to make text global or how to modify the value of the global variable text inside readdir.
I also don't understand why it first executes y2.appendLine(text) and then readdir.
It is really important and I would be grateful to have your help!
I'm not sure what exactly you are trying to do there, but looking at your code it seems like you are trying to save some data for your extension in particular.
For this purpose, you probably want to use the globalStorage object instead.
You can learn more about it in the Data storage section and the ExtensionContext documentation.
For the path to your extension, use the context object and its properties:
extensionPath
The absolute file path of the directory containing the extension.
Shorthand notation for ExtensionContext.extensionUri.fsPath
(independent of the uri scheme).
extensionUri
The uri of the directory containing the extension.
From ExtensionContext. If it is just for the purpose of storage, see #mausworks's answer about globalStorage or storagePath, ets.
You could put it in a function like this:
function searchExt() {
const directoryPath = path.join(Os.homedir(), '.vscode/extensions');
fs.readdir(directoryPath, (err, files) => {
let text = null;
if (err) {
y2.appendLine(`Unable to scan directory: ${err}`);
} else {
for (let i = 0; i < files.length; i++) {
if (files[i].startsWith('MY_EXTENSION')) {
text = files[i];
break;
}
}
}
return text;
});
}
const searchResult = searchExt();
Goal: Get a List of Absolute Paths for all files in a directory recursively leveraging NodeJs.
Info: As a python dev, I normally use the python packages which handle this in a platform independent fashion. My boss wanted some javascript code which would handle this goal... and as a JS dev previously, I was like "oh this is easy. Let's look up the node as I never got a chance to get my hands dirty with it." but I seem to be mistaken.
I don't see anything in node relating to Dir Walking, or a way I could hack together to create such a thing.
I was looking in "Child Process", "Console", "File System", "OS", "Path", and "Process". I didn't see anything which would do anything akin to:
pushd .
cd $dir
for folder in $(ls);
do
pushd .
cd $folder
//call again
ls $(pwd)$flag >> $dir/files_matching.txt
popd
done;
// or any platform independent means of recursively getting
// all files and their abs path which meet flag criterion,
// such as "*.txt" || "_*found*"
I could use child process to carry out Command Line items, but then I need to create a bunch of conditionals based on the OS consuming the app, and figured this would be something which already exists.
I don't want to reinvent the wheel, but figured this has already been done; I just don't see it in the base modules.
Is there a node module I would need which accomplishes this, which is outside of the base modules?
I am trying not to have to hand roll a conditional os based system to get an exhaustive list of abs paths for all files under a directory (or subset due to extensions, etc.)
I'd do it like this:
synchronous:
const fs = require("fs");
const { resolve } = require("path");
const getFiles = dir => {
const stack = [resolve(dir)];
const files = [];
while (stack.length) {
dir = stack.pop();
fs.readdirSync(dir).forEach(item => {
const path = resolve(dir, item);
(fs.statSync(path).isDirectory() ? stack : files).push(path);
});
}
return files;
};
console.log(getFiles("."));
asynchronous:
const fs = require("fs");
const { resolve } = require("path");
const pify = require("util").promisify;
const readdir = pify(fs.readdir);
const stat = pify(fs.stat);
const getFiles = async dir => {
const files = await readdir(resolve(dir));
const filesP = files.map(async file => {
const path = resolve(dir, file);
return (await stat(path)).isDirectory() ? getFiles(path) : path;
});
// return (await Promise.all(filesP)).flat(); // flat supported in node ~11
return [].concat(...(await Promise.all(filesP)));
};
getFiles(".").then(console.log);
async demo https://repl.it/#marzelin/getFiles
So, I was looking at the filesystem module and noticed the function readDir
https://nodejs.org/dist/latest-v8.x/docs/api/fs.html#fs_fs_readdir_path_options_callback
which does the trick in part. I guess it wasnt named a method i would have looking for. I was looking for things involving LIST and DIR, but not READ.
Anyways, here is a way to read Dir.
var fs = require('fs');
if (process.argv.length <= 2) {
console.log("Usage: " + __filename + " path/to/directory");
process.exit(-1);
}
var path = process.argv[2];
fs.readdir(path, function(err, items) {
console.log(items);
for (var i=0; i<items.length; i++) {
console.log(items[i]);
}
});
You notice that this one above is Async, but there is a Sync variant, just add "Sync" to the signature. Now you need to determine if something is a directory:
let file = fs.statSync("path/to/directory")
let isDir = file.isDirectory()
So you can couple this all together.
var fs = require('fs')
function recurse_file_system(path, contains) {
let files = fs.readdirSync(path);
let dArr = [];
let fArr = [];
for (let i in files){
let newPath = path + "/" + files[i]
if (fs.statSync(newPath).isDirectory()){
dArr.push(newPath)
}else{
if (filter(files[i], ".txt")){
fArr.push(newPath)
}
}
}
if (arr.length == 0){
return fArr;
}else{
for (let d in dArr){
let rslt = recurse_file_system(dArr[d]);
for (let i in rslt){
fArr.push(rslt[i])
}
}
return fArr;
}
}
console.log("Files:")
console.log(recurse_file_system("/"))
Now if you want to extend this, all you need to do is add a filter to say, limit the size of returns based on particular criterion, such as file name limitation.
function filter(filename, contains){
let reg = new RegEx(contains)
return reg.test(filename)
}
and you can add it to the base case, where you see filter... OR you can just return the WHOLE set and filter it there with the List method, filter.
I have a function that writes a file to a directory:
response.pipe(fs.createWriteStream(fullPath))
But before that I want to check if the path already exist, and if so, add a suffix, e.g. file_1.txt (and if that exist, create file_2.txt instead...etc):
// Check if the path already exist
let fullPath = "C:/test/file.txt"
let dir = "C:/test/"
let fileName = "file"
let fileExt = ".txt"
if (fs.existsSync(fullPath)) {
// I tried using a while loop but I end up making it too complicated
...
}
// Write file to disk
response.pipe(fs.createWriteStream(destinationPath))
Question
How do I properly / efficiently do that?
The while loop is the correct way.
// Check if the path already exist
let fullPath = "C:/test/file.txt"
let dir = "C:/test/"
let fileName = "file"
let fileExt = ".txt"
let num = 0;
while (fs.existsSync(fullPath)) {
fullPath = `${dir}${fileName}_${num++}${fileExt}`;
}
After this, fullPath contains the first nonexistent file.
Note that there's a potential race condition. Some other process could create the file after your loop finishes.
If all of your file names are named the same thing + "_#".txt, I think the most (one of the most) efficient ways to check that would be something along the lines of:
Get all files from the directory
var files = [];
fs.readdir(dir, (err, files) => {
files.forEach(file => {
files.push(file);
});
})
You would then sort the array (could be expensive if a lot of files)... then last record would be the highest number which you can easily extract.
Another thing you could do is find the file which has the latest creation date using similar approach using the Stats class from FS.
Get all files from the directory and sort them:
var files = fs.readdirSync(pathName)
.map(function(v) {
return { name:v};
})
.sort(function(a, b) { return a.nam > b.name; })
.map(function(v) { return v.name; });
And by latest creation date.
var files = fs.readdirSync(dir)
.map(function(v) {
return { name:v,
time:fs.statSync(dir + v).mtime.getTime()
};
})
.sort(function(a, b) { return a.time - b.time; })
.map(function(v) { return v.name; });
I am using the below code to split up a user provided path, create all intermediate dirs in the path and attach a timestamp to the ending file name. I am splitting the path with / first and then using forEach over the resulting array. Is there any better way/direct lib function to do this in javascript / nodejs.
function test(usrPath) {
var locMatches = usrPath.split("/")
locMatches.forEach (function (item) {
location = pathUtils.join(location,item)
if (!fs.existsSync(location)) {
fs.mkdirSync(location)
}
})
return pathUtils.join (location,usrPath + (new Date).toISOString().replace(/(^\d\d\d\d)|-|:|(\..*)/g,"").replace("T","_")+".log")
}
Ok, so there are path utils that allow to make the implementation better across platform.
Also, it gives a better managed access for working with path elements like root, dir, filename and extension. pathUtils.sep allows working on the dir elements more cross platform.
var pathUtils = require('path')
function test(usrPath) {
var pathElements = pathUtils.parse(usrPath)
pathElements.dir.split(pathUtils.sep).forEach (function (item) {
location = pathUtils.join(location,item)
if (!fs.existsSync(location)) {
fs.mkdirSync(location)
}
})
return pathUtils.join (location,pathElements.name + (new Date).toISOString().replace(/(^\d\d\d\d)|-|:|(\..*)/g,"").replace("T","_")+pathElements.ext.replace(/^$/i,".log"))
}
I am trying to change ownership of files in Google Drive, where my service account isn't owner of the file.
function getDriveFiles(folder, path) {
var folder = DriveApp.getFolderById("0B23heXhtbThYaWdxzMc");
var path = "";
var files = [];
var fileIt = folder.getFiles();
while ( fileIt.hasNext() ) {
var f = fileIt.next();
if (f.getOwner().getEmail() != "service#domain.com")
files.push({owner: f.getOwner().getEmail(), id: f.getId()});
}
return files;
}
So my array looks like this:
var files = [
{owner=jens#domain.com, id=CjOqUeno3Yjd4VEFrYzg},
{owner=jens#domain.com, id=CjOqUYWxWaVpTQ2tKc3c},
{owner=jens#domain.com, id=CjOqUNTltdHo2NllkcWs},
{owner=jens#domain.com, id=CjOqUVTRRMnU2Y0ZJYms},
{owner=jack#domain.com, id=CjOqUXzBmeE1CT0VLNkE},
{owner=aurora#domain.com, id=CjfKj4ur7YcttORkXTn8D2rvGE},
{owner=aurora#domain.com, id=CjOqUY3RFUFlScDBlclk}
]
Next function that i need to pass this array to is batchPermissionChange which will batch change the ownership to my service account. However i would like it to run batchPermissionChange per user. So if e.g jens#domain.com have 4 files, i don't want the batchPermissionChange function to be triggered 4 times, i would like it to trigger it one time with jens#domain.com, and include his four fileID's.
function batchPermissionChange(ownerEmail, filesArray){
Do batch job Google... https://www.googleapis.com/batch
}
Question
How do i run the function batchPermissionChange(ownerEmail, filesArray) with for e.g jens#domain.com with his 4 fileId's? I could loop through the array, like, 'for each item in array run batchPermissionChange', but that will trigger the batch-function 4 times for the user jens#domain.com.
When you retrieve the list of files, instead of pushing all the files into a single array, you can create a map of arrays, with the keys in the map being the owners, and the arrays being the list of files for that owner.
function getDriveFiles(folder, path) {
var folder = DriveApp.getFolderById("0B23heXhtbThYaWdxzMc");
var path = "";
var files = {};
var fileIt = folder.getFiles();
while (fileIt.hasNext()) {
var f = fileIt.next();
var owner = f.getOwner().getEmail();
var id = f.getId();
if (owner != "service#domain.com") {
// if the owner doesn't exist yet, add an empty array
if (!files[owner]) {
files[owner] = [];
}
// push the file to the owner's array
files[owner].push(id);
}
}
return files;
}
The files object will end up looking something like this:
{
'jens#domain.com': ['CjOqUeno3Yjd4VEFrYzg', 'CjOqUYWxWaVpTQ2tKc3c', 'CjOqUNTltdHo2NllkcWs', 'CjOqUVTRRMnU2Y0ZJYms'],
'jack#domain.com': ['CjOqUXzBmeE1CT0VLNkE'],
'aurora#domain.com': ['CjfKj4ur7YcttORkXTn8D2rvGE', 'CjOqUY3RFUFlScDBlclk']
}
Now, in the area of your code where you want to call batchPermissionChange, do it like this:
for(var ownerEmail in files) {
if(files.hasOwnProperty(ownerEmail)) {
// NOTE: I'm not sure what the first parameter should be for this, but
// this shows how to send the array of files for just one user at a
// time, so change the first parameter if I got it wrong.
batchPermissionChange(ownerEmail, files[ownerEmail]);
}
}