I'm using Readline for my interpreter, and I want to insert newline character on enter instead into going into next line, so if I use history I will have full command instead of one line of my multi line command separated.
My code look like this:
var prompt = 'lips> ';
var continuePrompt = '... ';
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: prompt,
terminal: !!process.stdin.isTTY && !(process.env.EMACS || process.env.INSIDE_EMACS)
});
if (process.stdin.isTTY) {
rl.prompt();
}
boostrap(interp.env).then(function() {
rl.on('line', function(line) {
code += line + '\n';
if (balanced_parenthesis(code)) {
rl.pause();
run(code, interp.env).then(function(result) {
if (process.stdin.isTTY) {
print(result);
if (multiline) {
rl.setPrompt(prompt);
}
rl.prompt();
}
rl.resume();
}).catch(function() {
if (process.stdin.isTTY) {
if (multiline) {
rl.setPrompt(prompt);
}
rl.prompt();
}
});
code = '';
} else {
multiline = true;
var ind = indent(code, 2, prompt.length - continuePrompt.length);
rl.setPrompt(continuePrompt);
rl.prompt();
rl.write(new Array(ind + 1).join(' '));
}
});
});
So basically what I need is some continue or preventDefault (like in browser). Is something like this possible?
One if the issue I have is that I have auto indent and when I copy paste the code I have double indent if code already have indentation, because each line from clipboard is single line in Readline. I want to insert single text. Or at least rewrite history to add multi line command all at once at the end, maybe disable history completely and add items to history in my own code.
Is something like this possible with Readline in Node.js or do I need recreate same readline API without readline. Do you know of any example that use this technique or maybe open source project that use this?
EDIT:
I've found this question and answer Is there a nice way of handling multi-line input with GNU readline?
rl_bind_key('\r', return_func) ===> int return_func(int cnt, int key) { ... }
but it seems that Node don't support this API. Any way to have same functionality in Node?
Related
I have some data in a txt file in this format:
byr:1985
eyr:2021 iyr:2011 hgt:175cm pid:163069444 hcl:#18171d
eyr:2023
hcl:#cfa07d ecl:blu hgt:169cm pid:494407412 byr:1936
ecl:zzz
eyr:2036 hgt:109 hcl:#623a2f iyr:1997 byr:2029
cid:169 pid:170290956
hcl:#18171d ecl:oth
pid:266824158 hgt:168cm byr:1992 eyr:2021
I already have a function that parses the txt file line by line:
function parse_file_by_line(folder, file_name) {
// Read the input file line by line, creating an array of inputs.
const input_file = path.join(__dirname, folder, file_name);
return (input_array = fs.readFileSync(input_file).toString().split("\r\n"));
}
However, i want to parse on the blank lines. ( the empty spaces in the text file ). Someone suggested to split on "\n\n" Which i have tried but it ends up putting all the data into one big array element." I want to split it on the empty lines and the data between into one array element. For example, the first index would be "byr:1985 eyr:2021 iyr:2011 hgt:175cm pid:163069444 hcl:#18171d".
I want to provide a way to check file string. You could use JSON.stringify to check what kind of string your file have. By this way, you will find out what is your line ending.
const fs = require('fs')
const data = fs.readFileSync("./file.txt")
console.log(JSON.stringify(data.toString()));
In my mac, this is output.
"byr:1985\neyr:2021 iyr:2011 hgt:175cm pid:163069444 hcl:#18171d\n\neyr:2023\nhcl:#cfa07d ecl:blu hgt:169cm pid:494407412 byr:1936\n\necl:zzz\neyr:2036 hgt:109 hcl:#623a2f iyr:1997 byr:2029\ncid:169 pid:170290956\n\nhcl:#18171d ecl:oth\npid:266824158 hgt:168cm byr:1992 eyr:2021"
Use split("\r\n\r\n"). Windows has \r\n on each line ending and linux uses just \n.
You can use a stream approach which mean not reading all the file into memory. This is useful when working with large files. Following handles CRLF too:
const fs = require('fs');
const readline = require('readline');
async function processLineByLine() {
const fileStream = fs.createReadStream('input.txt');
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
// Note: we use the crlfDelay option to recognize all instances of CR LF
// ('\r\n') in input.txt as a single line break.
for await (const line of rl) {
// Each line in input.txt will be successively available here as `line`.
console.log(`Line from file: ${line}`);
}
}
processLineByLine();
For more info see this.
Now, you can customize the for-loop to detect empty line like this:
var lines = []
for await (const line of rl) {
// Each line in input.txt will be successively available here as `line`.
if (line === ''){
if (lines.length > 0)
process_chunk(lines);
lines = [] //empty the array and make it ready for next iteration
}
else
lines.push(line)
}
function process_chunk(lines) {
//now, you have an array of consequtive non empty lines here
}
I'm working on my final project of the Winter 2017 quarter to demonstrate how to use Regular Expressions in both C# and JavaScript code behind pages. I've got the C# version of my demonstration program done, but the JavaScript version is making me pull what little hair I have left on my head out (no small achievement since I got a fresh buzz cut this morning!). The problem involves not getting any output after applying a Regular Expression in a While loop to get each instance of the expression and printing it out.
On my HTML page I have an input textarea, seven radio buttons, an output textarea, and two buttons underneath (one button is to move the output text to the input area to perform multiple iterations of applying expressions, and the other button to clear all textareas for starting from scratch). Each radio button links to a function that applies a regular expression to the text in the input area. Five of my seven functions work; the sixth is the one I can't figure out, and the seventh is essentially the same but with a slightly different RegEx pattern, so if I fix the sixth function, the seventh function will be a snap.
(I tried to insert/upload a JPG of the front end, but the photo upload doesn't seem to be working. Hopefully you get the drift of what I've set up.)
Here are my problem children from my JS code behind:
// RegEx_Demo_JS.js - code behind for RegEx_Demo_JS
var inputString; // Global variable for the input from the input text box.
var pattern; // Global variable for the regular expression.
var result; // Global variable for the result of applying the regular expression to the user input.
// Initializes a new instance of the StringBuilder class
// and appends the given value if supplied
function StringBuilder()
{
var strings = [];
this.append = function (string)
{
string = verify(string);
if (string.length > 0) strings[strings.length] = string;
}
this.appendLine = function (string)
{
string = verify(string);
if (this.isEmpty())
{
if (string.length > 0) strings[strings.length] = string;
else return;
}
else strings[strings.length] = string.length > 0 ? "\r\n" + string : "\r\n";
}
this.clear = function () { strings = []; };
this.isEmpty = function () { return strings.length == 0; };
this.toString = function () { return strings.join(""); };
var verify = function (string)
{
if (!defined(string)) return "";
if (getType(string) != getType(new String())) return String(string);
return string;
}
var defined = function (el)
{
// Changed per Ryan O'Hara's comment:
return el != null && typeof(el) != "undefined";
}
var getType = function (instance)
{
if (!defined(instance.constructor)) throw Error("Unexpected object type");
var type = String(instance.constructor).match(/function\s+(\w+)/);
return defined(type) ? type[1] : "undefined";
}
}
Within the code of the second radio button (which will be the seventh and last function to complete), I tested the ScriptBuilder with data in a local variable, and it ran successfully and produced output into the output textarea. But I get no output from this next function that invokes a While loop:
function RegEx_Match_TheOnly_AllInstances()
{
inputString = document.getElementById("txtUserInput").value;
pattern = /(\s+the\s+)/ig; // Using an Flag (/i) to select either lowercase or uppercase version. Finds first occurrence either as a standalone word or inside a word.
//result = pattern.exec(inputString); // Finds the first index location
var arrResult; // Array for the results of the search.
var sb = getStringBuilder(); // Variable to hold iterations of the result and the text
while ((arrResult = pattern.exec(inputString)) !==null)
{
sb.appendLine = "Match: " + arrResult[0] ;
}
document.getElementById("txtRegExOutput").value = sb.toString();
/* Original code from C# version:
// string pattern = #"\s+(?i)the\s+"; // Same as above, but using Option construct for case insensitive search.
string pattern = #"(^|\s+)(?i)the(\W|\s+)";
MatchCollection matches = Regex.Matches(userTextInput, pattern);
StringBuilder outputString = new StringBuilder();
foreach (Match match in matches)
{
string outputRegExs = "Match: " + "\"" + match.Value + "\"" + " at index [" + match.Index + ","
+ (match.Index + match.Length) + "]" + "\n";
outputString.Append(outputRegExs);
}
txtRegExOutput.Text = outputString.ToString();
*/
} // End RegEx_Match_The_AllInstances
I left the commented code in to show what I had used in the C# code behind version to illustrate what I'm trying to accomplish.
The test input/string I used for this function is:
Don’t go there. If you want to be the Man, you have to beat The Man.
That should return two hits. Ideally, I want it to show the word that it found and the index where it found the word, but at this point I'd be happy to just get some output showing every instance it found, and then build on that with the index and possibly the lastIndex.
So, is my problem in my While loop, the way I'm applying the StringBuilder, or a combination of the two? I know the StringBuilder code works, at least when not being used in a loop and using some test data from the site I found that code. And the code for simply finding the first instance of "the" as a standalone or inside another word does work and returns output, but that doesn't use a loop.
I've looked through Stack Overflow and several other JavaScript websites for inspiration, but nothing I've tried so far has worked. I appreciate any help anyone can provide! (If you need me to post any other code, please advise and I'll be happy to oblige.)
Using node.js, I am trying to build an array of objects and write them to a file. To do this, I'm using the built in fs library.
After calling
var file = fs.createWriteStream('arrayOfObjects.json'); and file.write('[') I run several asynchronous functions to eventually append objects like this:
file.write(JSON.stringify(objectToAppend) + ',\n')
I can determine when all of the objects have stopped appending, and this is where I run file.write(']') and file.end(). My problem is that adding the last comma to the end of the last object causes the JSON to be invalid.
It is very difficult to determine where and when the last object is being created due to the asynchronous nature of the script, so I was wondering if there is a way to strip or remove characters from a file-stream. If so, I could do this before adding the last ']' character.
I could do this manually, but I was hoping to pipe this to another application. The only solution I've thought about is using the fs.truncate() function, however this doesn't seem to work for file streams, and neither file.length or file.length() will give me the length of the contents because it is not a string so it's difficult to determine how or where to truncate the file.
For now I have just been adding '{}]' to the end of the array to make it valid JSON, but this empty object may cause some problems later.
Also note: the array of objects I am writing in this stream is VERY large, so I would rather not end the stream and re-open the file.
I'd recommend to prepend the separator instead, so that you dynamically can adjust it after the first call:
file.write('[\n')
var sep = "";
forEach(function(objectToAppen) {
file.write(sep + JSON.stringify(objectToAppend))
if (!sep)
sep = ",\n";
});
Example using JSONStream:
var JSONStream = require('JSONStream');
var fs = require('fs');
var jsonwriter = JSONStream.stringify();
var file = fs.createWriteStream('arrayOfObjects.json');
// Pipe the JSON data to the file.
jsonwriter.pipe(file);
// Write your objects to the JSON stream.
jsonwriter.write({ foo : 'bar#1' });
jsonwriter.write({ foo : 'bar#2' });
jsonwriter.write({ foo : 'bar#3' });
jsonwriter.write({ foo : 'bar#4' });
// When you're done, end it.
jsonwriter.end();
Here's a snippet incorporating robertklep's answer. This converts from a pipe-separated file to json:
var fs = require('fs');
var readline = require('readline');
var JSONStream = require('JSONStream');
// Make sure we got a filename on the command line.
if (process.argv.length < 3) {
console.log('Usage: node ' + process.argv[1] + ' FILENAME');
process.exit(1);
}
var filename = process.argv[2];
var outputFilename = filename + '.json';
console.log("Converting psv to json. Please wait.");
var jsonwriter = JSONStream.stringify();
var outputFile = fs.createWriteStream(outputFilename);
jsonwriter.pipe(outputFile);
var rl = readline.createInterface({
input: fs.createReadStream(filename),
terminal: false
}).on('line', function(line) {
console.log('Line: ' + line);
if(!/ADDRESS_DETAIL_PID/.test(line))
{
var split = line.split('|');
var line_as_json = { "address_detail_pid": split[0], "flat_type": split[1], "flat_number": split[2], "level_type": split[3], "level_number": split[4], "number_first": split[5], "street_name": split[6], "street_type_code": split[7], "locality_name": split[8], "state_abbreviation": split[9], "postcode": split[10], "longitude": split[11], "latitude": split[12] };
jsonwriter.write(line_as_json);
}
}).on('close', () => {
jsonwriter.end();
});;
console.log('psv2json complete.');
The accepted answer is interesting (prepending the separator) but in my case I have found it easier to append the separator and remove the last character of the file, just as suggested in the question.
This is how you remove the last character of a file with Node.js :
import fs from 'fs'
async function removeLastCharacter(filename) {
const stat = await fs.promises.stat(filename)
const fileSize = stat.size
await fs.promises.truncate(filename, fileSize - 1)
}
explanation :
fs.promises.stat gives us some information about the file, we will use its size.
fs.promises.truncate remove from the file what is after a certain position
We use the position fileSize - 1 which is the last character.
Note :
Yes I know that we need to wait until the stream is closed, but this is ok because truncate and stat functions are very fast and doesn't depend on the file size, it doesn't have to read its content.
In case you are not familiar with CoffeeScript, here's the JavaScript version of p1.coffee and p2.coffee mentioned below.
Piping a node.js script's stdout to another's stdin doesn't seem to work. I have p1.coffee which outputs numbers to stdout as fast as it can:
i = 0
(->
i += 1
process.stdout.write "#{i} "
)() while true
I now have p2.coffee which does exactly what's like a cat:
process.stdin.on 'end', -> console.log "EOF"
process.stdin.pipe(process.stdout)
Now if I pipeline them together, it displays only the first number and "blocks" there:
> coffee p1.coffee | coffee p2.coffee
1
I'm using node v0.10.31 on Windows if that matters.
That might be a Windows specific issue. I tried the following with Node.js v0.10.31 on OS X and it worked fine:
// cat.js
process.stdin.once('end', function () {
console.log('EOF');
});
process.stdin.pipe(process.stdout);
// count.js
var i = 0;
while (true) {
process.stdout.write(i++ + ' ');
}
and piped the output of count.js to cat.js:
node count.js | node cat.js
Also note that your CoffeeScript compiles to:
var i;
i = 0;
while (true) {
(function() {
i += 1;
return process.stdout.write("" + i + " ");
})();
}
Creating functions within loops makes your code slower. You could do the following instead:
i = 0
loop process.stdout.write("#{i += 1} ")
I'm working on a simple cli script and wanted to add some color to the following code:
rl.question('Enter destination path: ', function(answer) {
// ...
});
rl.write('/home/' + user + '/bin');
Which displays in the terminal:
Enter destination path: /home/jmcateer/bin_
But I wanted to add some color to the prompt I did the following:
rl.question('\u001b[1;36mEnter destination path:\u001b[0m ', function(answer) {
});
rl.write('/home/' + user + '/bin');
And the command line prompt ended up displaying:
Enter destination path: /home/jmcateer/bin_
It works but there's a huge amount of white space I'd prefer weren't there. Does anyone have any ideas on how to deal with this?
Edit:
I can't delete the white space by backspacing through it... when I try to use the backspace key the white space jumps to the other end like so
Enter destination path: /home/jmcateer/bin_
Enter destination path: /home/jmcateer/bi _
Enter destination path: /home/jmcateer/b _
...
Enter destination path: _
At that point backspace has no effect.
When you call rl.setPrompt(prompt, length) without its second argument, the internal _promptLength variable is set to the length of the prompt string; ANSI X3.64 escape sequences are not interpreted. The internal _getCursorPos method computes the cursor position from _promptLength][3]; as such, the inclusion of the escape sequences in the length results in the cursor being positioned further away than it should be.
To resolve this problem fully, Node's readline library should parse the ANSI escape sequences when setting _promptLength. To work around this problem, you can manually calculate the length of the prompt string without the escape sequences and pass that as the second argument to rl.setPrompt(prompt, length).
I also ran into a similar issue. Basil Crow is correct in his excellent answer on the cause of the problem in that it is indeed the ANSI escape sequences that are causing the length glitch; however, Interface.setPrompt() is not just the problem—it is the solution!
It seems that this misreading of the length (something I artfully avoided while playing around in bash) affects the entire Interface object's writeout process, i.e. anything that calls Interface.setPrompt() in any capacity will be afflicted when the length parameter is left not specified.
In order to overcome this problem, you can do one of two things:
Redefine Interface.setPrompt() to always specify a length for prompt output so that methods like Interface.question() function properly again:
// I'm using the 'color' npm library for the sake of convenience. It is not required
// and you can use normal regex to strip color codes and what not.
var colors = require('colors'),
readline = require('readline');
var rl = readline.createInterface(process.stdin, process.stdout);
/* Overcome some bugs in the Nodejs readline implementation */
rl._setPrompt = rl.setPrompt;
rl.setPrompt = function(prompt, length)
{
rl._setPrompt(prompt, length ? length : prompt.split(/[\r\n]/).pop().stripColors.length);
};
var str = '[' + '?'.green + '] Blackbeard walks under the black flag with a ____? ';
rl.question(str, function(answer)
{
var answ = 'scallywag swagger';
console.log(
'You answered "' + ((answer == answ) ? answer.green : answer.red)
+ '". The correct answer is', '"' + answ.green + '".');
});
Redefine Interface.write() to use Interface.setPrompt() passing both the writeout string and the true length of the string to the setPrompt method:
var colors = require('colors'),
readline = require('readline');
var rl = readline.createInterface(process.stdin, process.stdout);
/* Overcome some bugs in the Nodejs readline implementation */
rl._write = rl.write;
rl.write = function(d, key)
{
// "key" functionality is lost, but if you care enough you can add it back
rl.setPrompt(d, d.split(/[\r\n]/).pop().stripColors.length);
rl.prompt(true);
};
var str = '[' + '?'.green + '] Blackbeard walks under the black flag with a ____? ';
rl.write(str);
rl.on('line', function(answer)
{
var answ = 'scallywag swagger';
console.log(
'You answered "' + ((answer == answ) ? answer.green : answer.red)
+ '". The correct answer is', '"' + answ.green + '".');
rl.prompt(true);
});
The results for both are the same:
Or you can do both. Or you can modify the readline.Interface.prototype directly (and have your fix applied globally) instead of the object instances themselves. Lots of options here.
Hope this helps someone!
EDIT—See also: https://github.com/joyent/node/issues/3860
Sure, you'll want to modify your rl.write string with the CSI sequence n D where n is the number of characters to move your cursor back.
Here is a snippet to experiment with:
var rl = require('readline').createInterface({input: process.stdin, output: process.stdout});
rl.question('\u001b[1;36mEnter destination path: \u001b[0m', function(answer) {
});
rl.write('\u001b[11 D/home/jp/bin');
Notice the 11 and the D in the last line? The D stands for the number of characters to move back. 11 is obviously then the number of characters.
See this for all of the fun terminal codes: http://en.wikipedia.org/wiki/ANSI_escape_code