I am an experienced JavaScript programmer, but am just starting to learn node.js.
Using node, I want to read the contents of a directory, and print out files of only a specific extension. Both the directory and file-extension will be given by command-line arguments.
But, I also want to push myself and explore JavaScript programming concepts as I solve these puzzles, so I wanted to create a File object to store information about a file, and use that to solve the problem.
Becaause of this, my approach is overly-complex, and I know that there are simpler ways of doing this, but I just want an answer which solves my current problem:
Why does node.js throw the following error
this.baseName = /.+(?=\.[^.]+)/.exec(file)[0];
^
TypeError: Cannot read property '0' of null
in this code:
function File(file){
if (file instanceof File) return file;
if (!(this instanceof File)) return new File(file);
if (typeof file !== "string") throw new Error("Useful error." + typeof(file));
this.fullName = file;
/*vvvvvvvvvvvv Important Bit vvvvvvvvvvvvvv*/
this.baseName = /.+(?=\.[^.]+)/.exec(file)[0];
this.extension = /[^.]+(?=$)/.exec(file)[0];
}
File.prototype = {
isOfType: function(ext){
return ext === this.extension;
}
}
var fs = require('fs');
var argv = process.argv;
fs.readdir(argv[2], function(err, list){
var res = list.filter(function(element, index, array){
var file = new File(element);
return file.isOfType(argv[3]);
});
console.log(res);
});
but, in a Chrome js console, it runs fine (with simulated process and fs objects of course).
To me (inexperienced me) it looks like node could be making several mistakes:
Not handling the regex properly (I've done tests and this seems likely)
Using square brackets to find key '0' within object, instead of index 0 within array.
Or I could be making several mistakes:
Not understanding fs.readdir and its necessary callback.
Not understanding possible differences between constructors in JavaScript and Node
Please help, I'd like an answer that solves or explains my current problem, not one that works around it.
Thanks.
Node has a built in way to check for valid files
function File(file){
if (file instanceof File) return file;
if (!(this instanceof File)) return new File(file);
if (typeof file !== "string") throw new Error("Useful error." + typeof(file));
var stat = fs.statSync(file);
if ( stat && stat.isFile() ) {
this.fullName = file;
/*vvvvvvvvvvvv Important Bit vvvvvvvvvvvvvv*/
this.baseName = /.+(?=\.[^.]+)/.exec(file)[0];
this.extension = /[^.]+(?=$)/.exec(file)[0];
}
}
Node also has a built in way to get extensions and path names
var path = require('path');
var basename = path.basename(file);
var extension = path.extname(file);
Why does node.js throw the following error
this.baseName = /.+(?=\.[^.]+)/.exec(file)[0];
^
TypeError: Cannot read property '0' of null
Because file doesn't match the regular expression, and so exec returns null; then you're doing null[0], which throws the exception. That would happen for a name like a or a., for instance, or the . and .. pseudo-directories.
Use the NodeJS debugger or just console.log file` immediately prior, an you'll see the value that's causing trouble.
It's because any file names which don't match your regular expression, such as many directory names (which are included in your list variable), will then return null here:
/.+(?=\.[^.]+)/.exec(file)
so you are trying to do
null[0]
which will not work.
I solved this by simply checking for a period within the file name:
var file = (element.indexOf(".") > -1) ? new File(element) : false;
return (file) ? file.isOfType(argv[3]) : false;
within the filter, so final code resembles:
fs.readdir(argv[2], function(err, list){
var res = list.filter(function(element, index, array){
var file = (element.indexOf(".") > -1) ? new File(element) : false;
return (file) ? file.isOfType(argv[3]) : false;
});
for (var i = 0; i < res.length; console.log(res[i++]));
});
Related
I tried searching . How do i do it? I'm create html and i want to read .ini file by javascript on the client Not in the server.
I copy code from javascript parser for a string which contains .ini data
error Uncaught ReferenceError: require is not defined var fs = require('fs')
function parseINIString() {
var fs = require('fs')
var data = fs.readFileSync('C:\\test.ini', 'utf8');
var regex = {
section: /^\s*\[\s*([^\]]*)\s*\]\s*$/,
param: /^\s*([\w\.\-\_]+)\s*=\s*(.*?)\s*$/,
comment: /^\s*;.*$/
};
var value = {};
var lines = data.split(/\r\n|\r|\n/);
var section = null;
alert(lines);
for (x = 0; x < lines.length; x++) {
if (regex.comment.test(lines[x])) {
return;
} else if (regex.param.test(lines[x])) {
var match = lines[x].match(regex.param);
if (section) {
value[section][match[1]] = match[2];
} else {
value[match[1]] = match[2];
}
} else if (regex.section.test(lines[x])) {
var match = lines[x].match(regex.section);
value[match[1]] = {};
section = match[1];
} else if (lines.length == 0 && section) {//changed line to lines to fix bug.
section = null;
};
}
return value;
}
Let's say the javascript running in a browser is so called 'client script'. There are lots of limitation while writing client script, one of them is that it's not allowed to visit the user file on disk. This is to prevent any injected hacker script from reading private data. And the explicit error you see is about the new key word 'require' which is well known as 'commonjs' module which is introduced by Nodejs usually. The 'fs' is one of the internal module of Nodejs as well.
So if you still consist using client script to get the job done, you have to rewrite the script, not 'require' the 'fs' module. And use the file reader to get the content of a file object, which is generated by a file input usually.
A detailed introduction about how to read local files.
I am trying to write a JXA script in Apple Script Editor, that compresses a string using the LZ algorithm and writes it to a text (JSON) file:
var story = "Once upon a time in Silicon Valley..."
var storyC = LZString.compress(story)
var data_to_write = "{\x22test\x22\x20:\x20\x22"+storyC+"\x22}"
app.displayAlert(data_to_write)
var desktopString = app.pathTo("desktop").toString()
var file = `${desktopString}/test.json`
writeTextToFile(data_to_write, file, true)
Everything works, except that the LZ compressed string is just transformed to a set of "?" by the time it reaches the output file, test.json.
It should look like:
{"test" : "㲃냆Њޱᐈ攀렒삶퓲ٔ쀛䳂䨀푖㢈Ӱນꀀ"}
Instead it looks like:
{"test" : "????????????????????"}
I have a feeling the conversion is happening in the app.write command used by the writeTextToFile() function (which I pulled from an example in Apple's Mac Automation Scripting Guide):
var app = Application.currentApplication()
app.includeStandardAdditions = true
function writeTextToFile(text, file, overwriteExistingContent) {
try {
// Convert the file to a string
var fileString = file.toString()
// Open the file for writing
var openedFile = app.openForAccess(Path(fileString), { writePermission: true })
// Clear the file if content should be overwritten
if (overwriteExistingContent) {
app.setEof(openedFile, { to: 0 })
}
// Write the new content to the file
app.write(text, { to: openedFile, startingAt: app.getEof(openedFile) })
// Close the file
app.closeAccess(openedFile)
// Return a boolean indicating that writing was successful
return true
}
catch(error) {
try {
// Close the file
app.closeAccess(file)
}
catch(error) {
// Report the error is closing failed
console.log(`Couldn't close file: ${error}`)
}
// Return a boolean indicating that writing was successful
return false
}
}
Is there a substitute command for app.write that maintains the LZ compressed string / a better way to accomplish what I am trying to do?
In addition, I am using the readFile() function (also from the Scripting Guide) to load the LZ string back into the script:
function readFile(file) {
// Convert the file to a string
var fileString = file.toString()
// Read the file and return its contents
return app.read(Path(fileString))
}
But rather than returning:
{"test" : "㲃냆Њޱᐈ攀렒삶퓲ٔ쀛䳂䨀푖㢈Ӱນꀀ"}
It is returning:
"{\"test\" : \"㲃냆੠Њޱᐈ攀렒삶퓲ٔ쀛䳂䨀푖㢈Ӱນꀀ\"}"
Does anybody know a fix for this too?
I know that it is possible to use Cocoa in JXA scripts, so maybe the solution lies therein?
I am just getting to grips with JavaScript so I'll admit trying to grasp Objective-C or Swift is way beyond me right now.
I look forward to any solutions and/or pointers that you might be able to provide me. Thanks in advance!
After some further Googl'ing, I came across these two posts:
How can I write UTF-8 files using JavaScript for Mac Automation?
read file as class utf8
I have thus altered my script accordingly.
writeTextToFile() now looks like:
function writeTextToFile(text, file) {
// source: https://stackoverflow.com/a/44293869/11616368
var nsStr = $.NSString.alloc.initWithUTF8String(text)
var nsPath = $(file).stringByStandardizingPath
var successBool = nsStr.writeToFileAtomicallyEncodingError(nsPath, false, $.NSUTF8StringEncoding, null)
if (!successBool) {
throw new Error("function writeFile ERROR:\nWrite to File FAILED for:\n" + file)
}
return successBool
};
While readFile() looks like:
ObjC.import('Foundation')
const readFile = function (path, encoding) {
// source: https://github.com/JXA-Cookbook/JXA-Cookbook/issues/25#issuecomment-271204038
pathString = path.toString()
!encoding && (encoding = $.NSUTF8StringEncoding)
const fm = $.NSFileManager.defaultManager
const data = fm.contentsAtPath(pathString)
const str = $.NSString.alloc.initWithDataEncoding(data, encoding)
return ObjC.unwrap(str)
};
Both use Objective-C to overcome app.write and app.read's inability to handle UTF-8.
for several days now I'm learning html, CSS and now javascript. What I need is a way to get the informations of an pdf document into my html webpage.
I tried several things now and couldnt find the correct answer or informations I need. So here come an use case:
get an .pdf document into a folder
get the information of all .pdf documents of the target folder (with the exact same formatting)
convert those information into html context
get this html context to show on the webpage (images and text)
1 is trivial, I can just drag and drop my documents
2 I'm thinking about something like an array, which then calls the folder to get data into it.
For this I found:
'use strict';
function getFiles(dir) {
fileList = [];
var files = fs.readdirSync(dir);
for (var i in files) {
if (!files.hasOwnProperty(i)) continue;
var name = dir + '/' + files[i];
if (!fs.statSync(name).isDirectory()) {
fileList.push(name);
}
}
return fileList;
}
console.log(getFiles('pathtodirectory'));
Here I'm always getting a reference error, no matter what the path, well I can use only a local path on my pc for now. I'm not 100% sure what everything does, but I think I got it good so far. This function just gets me a list of the documents to work with.
3 That's even more tricky for me now, but I think if I get the data to work with, I may be able to work something out.
4 I think I can do it with a little research
I am happy for any tips or solutions, as I said I'm quite new to all of this :)
regards,
Pascal
'use strict';
function getFiles(dir) {
fileList = []; // <- This becomes a global variable
Should be:
'use strict';
function getFiles(dir) {
var fileList = []; // <- Now it's local to this scope
Because creating implicit global variables are not allowed in strict mode.
Also the getDirSync return an array, so you should treat it as such:
function getFiles(dir) {
fileList = [];
var files = fs.readdirSync(dir);
for (var i = 0; i < files.length; i++) {
var name = dir + '/' + files[i];
if (!fs.statSync(name).isDirectory()) {
fileList.push(name);
}
}
return fileList;
}
Or with .reduce:
function getFiles(dir) {
return fs.readdirSync(dir).reduce(function(arr, file) {
var name = dir + '/' + file;
if (!fs.statSync(name).isDirectory()) {
arr.push(name);
}
return arr;
}, []);
}
Reading from their document:
copy(source, destination)
Currently, the way I cope with this is to check fs.exist('myfile') and manual delete it prior to copy:
var fs = require('fs');
var fileName = 'myfile-backup.txt';
if (fs.exists(fileName)) {
fs.remove(fileName);
}
fs.copy('myfile.txt', fileName);
phantom.exit();
I don't know if there is any better way to overwrite the file. Checking for existing file may have a potential problem when I can't remove the file. I will probably need to do more error handling with this approach. It seems to be a common task, so I would like to know what solution people come up with.
I have written a small extension to the fs module. If you try to overwrite a file with fs.copy it will throw an exception which you can catch to do some error handling like removing an existing file.
I also added an optional maxTrials argument if there is a problem that the file is created every time in between the copy trial and the remove.
var fs = require('fs');
fs.overwrite = function(source, destination, maxTrials){
var overwritten = false;
var trials = 0;
maxTrials = parseInt(maxTrials)
maxTrials = !!maxTrials ? maxTrials : null;
while(!overwritten) {
if (maxTrials && trials > maxTrials) {
return -1;
}
try {
this.copy(source, destination);
overwritten = true;
} catch(e) {
if (fs.exists(destination)) {
fs.remove(destination);
} else {
return -2;
}
}
trials++;
}
return trials;
};
How can I tell in JavaScript what path separator is used in the OS where the script is running?
Use path module in node.js returns the platform-specific file separator.
example
path.sep // on *nix evaluates to a string equal to "/"
Edit: As per Sebas's comment below, to use this, you need to add this at the top of your js file:
const path = require('path')
Afair you can always use / as a path separator, even on Windows.
Quote from http://bytes.com/forum/thread23123.html:
So, the situation can be summed up
rather simply:
All DOS services since DOS 2.0 and all Windows APIs accept either forward
slash or backslash. Always have.
None of the standard command shells (CMD or COMMAND) will accept forward
slashes. Even the "cd ./tmp" example
given in a previous post fails.
The Correct Answer
Yes all OS's accept CD ../ or CD ..\ or CD .. regardless of how you pass in separators. But what about reading a path back. How would you know if its say, a 'windows' path, with ' ' and \ allowed.
The Obvious 'Duh!' Question
What happens when you depend on, for example, the installation directory %PROGRAM_FILES% (x86)\Notepad++. Take the following example.
var fs = require('fs'); // file system module
var targetDir = 'C:\Program Files (x86)\Notepad++'; // target installer dir
// read all files in the directory
fs.readdir(targetDir, function(err, files) {
if(!err){
for(var i = 0; i < files.length; ++i){
var currFile = files[i];
console.log(currFile);
// ex output: 'C:\Program Files (x86)\Notepad++\notepad++.exe'
// attempt to print the parent directory of currFile
var fileDir = getDir(currFile);
console.log(fileDir);
// output is empty string, ''...what!?
}
}
});
function getDir(filePath){
if(filePath !== '' && filePath != null){
// this will fail on Windows, and work on Others
return filePath.substring(0, filePath.lastIndexOf('/') + 1);
}
}
What happened!?
targetDir is being set to a substring between the indices 0, and 0 (indexOf('/') is -1 in C:\Program Files\Notepad\Notepad++.exe), resulting in the empty string.
The Solution...
This includes code from the following post: How do I determine the current operating system with Node.js
myGlobals = { isWin: false, isOsX:false, isNix:false };
Server side detection of OS.
// this var could likely a global or available to all parts of your app
if(/^win/.test(process.platform)) { myGlobals.isWin=true; }
else if(process.platform === 'darwin'){ myGlobals.isOsX=true; }
else if(process.platform === 'linux') { myGlobals.isNix=true; }
Browser side detection of OS
var appVer = navigator.appVersion;
if (appVer.indexOf("Win")!=-1) myGlobals.isWin = true;
else if (appVer.indexOf("Mac")!=-1) myGlobals.isOsX = true;
else if (appVer.indexOf("X11")!=-1) myGlobals.isNix = true;
else if (appVer.indexOf("Linux")!=-1) myGlobals.isNix = true;
Helper Function to get the separator
function getPathSeparator(){
if(myGlobals.isWin){
return '\\';
}
else if(myGlobals.isOsx || myGlobals.isNix){
return '/';
}
// default to *nix system.
return '/';
}
// modifying our getDir method from above...
Helper function to get the parent directory (cross platform)
function getDir(filePath){
if(filePath !== '' && filePath != null){
// this will fail on Windows, and work on Others
return filePath.substring(0, filePath.lastIndexOf(getPathSeparator()) + 1);
}
}
getDir() must be intelligent enough to know which its looking for.
You can get even really slick and check for both if the user is inputting a path via command line, etc.
// in the body of getDir() ...
var sepIndex = filePath.lastIndexOf('/');
if(sepIndex == -1){
sepIndex = filePath.lastIndexOf('\\');
}
// include the trailing separator
return filePath.substring(0, sepIndex+1);
You can also use 'path' module and path.sep as stated above, if you want to load a module to do this simple of a task. Personally, i think it sufficient to just check the information from the process that is already available to you.
var path = require('path');
var fileSep = path.sep; // returns '\\' on windows, '/' on *nix
And Thats All Folks!
As already answered here, you can find the OS specific path separator with path.sep to manually construct your path. But you can also let path.join do the job, which is my preferred solution when dealing with path constructions:
Example:
const path = require('path');
const directory = 'logs';
const file = 'data.json';
const path1 = `${directory}${path.sep}${file}`;
const path2 = path.join(directory, file);
console.log(path1); // Shows "logs\data.json" on Windows
console.log(path2); // Also shows "logs\data.json" on Windows
Just use "/", it works on all OS's as far as I know.