overwrite previous lines on copy paste in REPL with ANSI escapes codes - node.js

I have Node.js REPL with syntax highlighting for Scheme language using ANSI escape codes. The problem is that when I copy-paste the code I need to overwrite previous text with new text that is highlighted. I'm using F ANSI escape to move the cursor up clear each line (K code) and write color text on top.
Here is my code:
function run_repl(err, rl) {
const dynamic = options.d || options.dynamic;
var code = '';
var multiline = false;
var resolve;
// we use promise loop to fix issue when copy paste list of S-Expression
var prev_eval = Promise.resolve();
if (process.stdin.isTTY) {
rl.prompt();
}
var prev_line;
boostrap(interp).then(function() {
rl.on('line', function(line) {
code += line + '\n';
const lines = code.split('\n');
const cols = process.stdout.columns;
// fix formatting for previous lines that was echo
// ReadLine will not handle those
if (terminal && lines.length > 0) {
var f = new Formatter(code);
code = f.format();
const stdout = scheme(code).split('\n').map((line, i) => {
var prefix;
if (i === 0) {
prefix = unify_prompt(prompt, continuePrompt);
} else {
prefix = unify_prompt(continuePrompt, prompt);
}
return '\x1b[K' + prefix + line;
}).join('\n');
let num = lines.length - 1;
const format = `\x1b[${num}F${stdout}`;
process.stdout.write(format);
}
try {
if (balanced_parenthesis(code)) {
// we need to clear the prompt because resume
// is adding the prompt that was present when pause was called
// https://github.com/nodejs/node/issues/11699
rl.setPrompt('');
rl.pause();
prev_eval = prev_eval.then(function() {
var result = run(code, interp, dynamic);
code = '';
return result;
}).then(function(result) {
if (process.stdin.isTTY) {
print(result);
if (newline) {
// readline doesn't work with not ended lines
// it ignore those, so we end them ourselves
process.stdout.write("\n");
newline = false;
}
if (multiline) {
multiline = false;
}
}
rl.setPrompt(prompt);
rl.prompt();
rl.resume();
}).catch(function() {
if (process.stdin.isTTY) {
if (multiline) {
multiline = false;
}
}
rl.setPrompt(prompt);
rl.prompt();
rl.resume();
});
} else {
multiline = true;
// write spaces for next line according to indent rules
var ind = indent(code, 2, prompt.length - continuePrompt.length);
rl.setPrompt(continuePrompt);
rl.prompt();
spaces = new Array(ind + 1).join(' ');
if (terminal) {
rl.write(spaces);
}
}
} catch (e) {
console.error(e.message);
console.error(e.stack);
code = '';
rl.setPrompt(prompt);
rl.prompt();
}
});
}).catch(function(e) {
log_error('Internal Error: boostrap filed');
log_error(e.message || e);
console.error('Internal Error: boostrap filed');
});
}
this is my main function, I also have hacked on top of readline that highlight text while the user is typing but it's cleared after you press enter that's why I need to update previous lines on each enter (copy/paste also works the same as enter key because it generates line event).
The only relevant code is this part:
rl.on('line', function(line) {
code += line + '\n';
const lines = code.split('\n');
const cols = process.stdout.columns;
// fix formatting for previous lines that was echo
// ReadLine will not handle those
if (terminal && lines.length > 2) {
const stdout = scheme(code).split('\n').map((line, i) => {
var prefix;
if (i === 0) {
prefix = unify_prompt(prompt, continuePrompt);
} else {
prefix = unify_prompt(continuePrompt, prompt);
}
return '\x1b[K' + prefix + line;
}).join('\n');
let num = lines.length - 1;
const format = `\x1b[${num}F${stdout}`;
process.stdout.write(format);
}
try {
if (balanced_parenthesis(code)) {
// ...
// when Scheme code is not finished else is executed.
} else {
multiline = true;
// write spaces for next line according to indent rules
var ind = indent(code, 2, prompt.length - continuePrompt.length);
rl.setPrompt(continuePrompt);
rl.prompt();
spaces = new Array(ind + 1).join(' ');
if (terminal) {
rl.write(spaces);
}
}
} catch (e) {
}
});
The code works fine unless original lines wrap into multiple lines, and it happens very often because you can't detect copy paste and the code is inserting spaces (auto-indent feature when user type code and press enter) so if the original pasted command already have indentation they are doubled.
I'm testing with this expression:
(define foo (lambda ()
(let ()
(define foo (lambda ()
(let ()
(define foo (lambda ()
(let ()
(define foo (lambda ()
(let ()
(define foo (lambda ()
(let ()
(display "x"))))))))))))))))
The problematic code is on GitHub https://github.com/jcubic/lips/blob/devel/bin/lips.js
The problem is how to clear what was already written in the terminal with ANSI escape codes.
EDIT:
This is my latest code that shows the same error when copy-pasting:
function run_repl(err, rl) {
const dynamic = options.d || options.dynamic;
var code = '';
var multiline = false;
var resolve;
// we use promise loop to fix issue when copy paste list of S-Expression
let prev_eval = Promise.resolve();
if (process.stdin.isTTY) {
rl.prompt();
}
let prev_line;
bootstrap(interp).then(function() {
rl.on('line', function(line) {
code += line;
const lines = code.split('\n');
const cols = process.stdout.columns;
// fix formatting for previous lines that was echo
// ReadLine will not handle those
if (terminal && lines.length > 2) {
var count = 0;
// correction when line wrapps in original line
// that will be overwritten
lines.map(line => {
if (line.length > cols) {
count += Math.ceil(line.length / cols) - 1;
}
});
var f = new Formatter(code);
code = f.format();
const stdout = scheme(code).split('\n').map((line, i) => {
var prefix;
if (i === 0) {
prefix = unify_prompt(prompt, continuePrompt);
} else {
prefix = unify_prompt(continuePrompt, prompt);
}
return '\x1b[K' + prefix + line;
}).join('\n');
let num = lines.length + count;
const format = `\x1b[${num}F${stdout}\n`;
process.stdout.write(format);
}
code += '\n';
try {
if (balanced_parenthesis(code)) {
// we need to clear the prompt because resume
// is adding the prompt that was present when pause was called
// https://github.com/nodejs/node/issues/11699
rl.setPrompt('');
rl.pause();
prev_eval = prev_eval.then(function() {
const result = run(code, interp, dynamic, null, options.t || options.trace);
code = '';
return result;
}).then(function(result) {
if (process.stdin.isTTY) {
print(result);
if (newline) {
// readline doesn't work with not ended lines
// it ignore those, so we end them ourselves
process.stdout.write("\n");
newline = false;
}
if (multiline) {
multiline = false;
}
}
rl.setPrompt(prompt);
rl.prompt();
rl.resume();
}).catch(function() {
if (process.stdin.isTTY) {
if (multiline) {
multiline = false;
}
}
rl.setPrompt(prompt);
rl.prompt();
rl.resume();
});
} else {
multiline = true;
const ind = indent(code, 2, prompt.length - continuePrompt.length);
rl.setPrompt(continuePrompt);
rl.prompt();
const spaces = new Array(ind + 1).join(' ');
if (terminal) {
rl.write(spaces);
}
}
} catch (e) {
console.error(e.message);
console.error(e.stack);
code = '';
rl.setPrompt(prompt);
rl.prompt();
}
});
}).catch(function(e) {
log_error('Internal Error: bootstrap filed');
log_error(e.message || e);
console.error('Internal Error: bootstrap filed');
});
}
if you want to see the working code look at the LIPS git repo.

Related

Remove console.log wrapper that Jest adds in tests [duplicate]

Jest has this feature to log the line that outputs to console methods.
In some cases, this can become annoying:
console.log _modules/log.js:37
ℹ login.0 screenshot start
console.time _modules/init.js:409
login.0.screenshot: 0.33ms
console.time _modules/init.js:394
0 | login.0: 0.524ms
console.log _modules/log.js:37
ℹ login.1 screenshot start
Any idea how I can turn it off?
With Jest 24.3.0 or higher, you can do this in pure TypeScript by adding the following to a Jest setup file configured in setupFilesAfterEnv:
import { CustomConsole, LogType, LogMessage } from '#jest/console';
function simpleFormatter(type: LogType, message: LogMessage): string {
const TITLE_INDENT = ' ';
const CONSOLE_INDENT = TITLE_INDENT + ' ';
return message
.split(/\n/)
.map(line => CONSOLE_INDENT + line)
.join('\n');
}
global.console = new CustomConsole(process.stdout, process.stderr, simpleFormatter);
Jest injects custom console implementation that is based on extendable Console class into test global scope. Normally it provides useful debugging information alongside printed message that answers the question where potentially unwanted output comes from.
In case this is undesirable for some reason, a simple way to retrieve default console implementation is to import it from Node built-in module.
Can be done for specific console calls:
let console = require('console');
...
console.log(...)
For many of them that occur inside a range of tests:
const jestConsole = console;
beforeEach(() => {
global.console = require('console');
});
afterEach(() => {
global.console = jestConsole;
});
And so on.
Update: For newer versions of Jest, see Harald Wellmann's answer.
Looking at the source code for Jest, it doesn't seem like there is a neat way to turn those messages off.
However, one possible solution could be to write your own Console. Here I have used Console.js from Jest as a starting ground, and then created SimpleConsole which does what you need (I have removed some terminal coloring features for simplicity, but you could just add them yourself).
Once added to your project, you can overwrite Jest's normal console with your own before running the tests:
const { SimpleConsole } = require('./SimpleConsole');
global.console = new SimpleConsole(process.stdout, process.stderr);
I have made a REPL that shows it in action.
The source code for SimpleConsole:
const path = require('path');
const assert = require('assert');
const {format} = require('util');
const {Console} = require('console');
function simpleFormatter() {
const TITLE_INDENT = ' ';
const CONSOLE_INDENT = TITLE_INDENT + ' ';
return (type, message) => {
message = message
.split(/\n/)
.map(line => CONSOLE_INDENT + line)
.join('\n');
return (
message +
'\n'
);
};
};
class SimpleConsole extends Console {
constructor(stdout, stderr, formatBuffer) {
super(stdout, stderr);
this._formatBuffer = formatBuffer || simpleFormatter();
this._counters = {};
this._timers = {};
this._groupDepth = 0;
}
_logToParentConsole(message) {
super.log(message);
}
_log(type, message) {
if (process.stdout.isTTY) {
this._stdout.write('\x1b[999D\x1b[K');
}
this._logToParentConsole(
this._formatBuffer(type, ' '.repeat(this._groupDepth) + message),
);
}
assert(...args) {
try {
assert(...args);
} catch (error) {
this._log('assert', error.toString());
}
}
count(label = 'default') {
if (!this._counters[label]) {
this._counters[label] = 0;
}
this._log('count', format(`${label}: ${++this._counters[label]}`));
}
countReset(label = 'default') {
this._counters[label] = 0;
}
debug(...args) {
this._log('debug', format(...args));
}
dir(...args) {
this._log('dir', format(...args));
}
dirxml(...args) {
this._log('dirxml', format(...args));
}
error(...args) {
this._log('error', format(...args));
}
group(...args) {
this._groupDepth++;
if (args.length > 0) {
this._log('group', chalk.bold(format(...args)));
}
}
groupCollapsed(...args) {
this._groupDepth++;
if (args.length > 0) {
this._log('groupCollapsed', chalk.bold(format(...args)));
}
}
groupEnd() {
if (this._groupDepth > 0) {
this._groupDepth--;
}
}
info(...args) {
this._log('info', format(...args));
}
log(...args) {
this._log('log', format(...args));
}
time(label = 'default') {
if (this._timers[label]) {
return;
}
this._timers[label] = new Date();
}
timeEnd(label = 'default') {
const startTime = this._timers[label];
if (startTime) {
const endTime = new Date();
const time = endTime - startTime;
this._log('time', format(`${label}: ${time}ms`));
delete this._timers[label];
}
}
warn(...args) {
this._log('warn', format(...args));
}
getBuffer() {
return null;
}
}
module.exports.SimpleConsole = SimpleConsole;
If you want to use Haral Wellmann's answer but avoid typescript, then you can merely do something like:
const JestConsole = require('./node_modules/#jest/console');
global.console = new JestConsole.CustomConsole(process.stdout, process.stderr, (type, message) => {
const TITLE_INDENT = ' ';
const CONSOLE_INDENT = TITLE_INDENT + ' ';
return message.split(/\n/).map(line => CONSOLE_INDENT + line).join('\n');
});
None of the above options worked for me.
The (current) simplest solution is this:
1: Create a file with this code (e.g. config.js)
import console from "console"
global.console = console
2: Add this line to your jest.config.js
setupFilesAfterEnv: ["./config.js"]
Before:
After:
Enjoi!

How to migrate SSE chat node express to node hapi

I was testing a SSE node express chat in localhost.It was working perfectly. I was including a chat_server in a demo with hapijs as modular server...and it complain about the express syntax. How can I migrate the code to the right syntax in hapijs?
I am trying to solve changing writeHead and write methods because it's complaing about and adding stream package after searching answers in internet.
/*
* Request handlers
*/
function handleGetChat(req, res) {
console.log('handleGetChat received.');
// res(chatStream).code(200).type('text/event-stream').header('Connection', 'keep-alive').header('Cache-Control','no-cache');
// chatStream.write('\n');
(function(clientId) {
clients[clientId] = res;
clientNames[clientId] = req.params.name;
console.log('name {$req.params.name}');
req.on("close", () => {
delete clients[clientId];
actUserName = "";
sendText(clientNames[clientId] + " disconnected!", false);
delete clientNames[clientId];
});
})(++clientId);
sendText(req.params.name + " connected!", false);
let allMates = "";
for (cliId in clientNames) {
allMates += `${clientNames[cliId]}`;
if (cliId < clientId) allMates += " ";
}
sendText(`logged in [${allMates}]`, false);
}
let sendText = (text, showUserName = true) => {
for (clientId in clients) {
allMates += `${clientNames[cliId]}`;
if (cliId < clientId) allMates += " ";
}
sendText(logged in [${allMates}], false);
}
let sendText = (text, showUserName = true) => {
for (clientId in clients) {
let data = "";
let date = new Date();
let timestamp = `[${date.getHours()}:${date.getMinutes()}]`;
if (showUserName) {
data = `data: ${timestamp} <${actUserName}> ${text}\n\n`;
} else {
data = `data: ${timestamp} ${text}\n\n`;
}
//chatStream.push('data: ' + "\n\n");
}
};
function handleWriteChat(req, res) {
actUserName = req.body.name;
sendText(req.body.text);
res.json({ success: true });
}
The commented lines in the code above are the lines with syntax error in hapi. I was already changing the originals write and writeHead with chatstream.

How to read from stdin line by line in Node

I'm looking to process a text file with node using a command line call like:
node app.js < input.txt
Each line of the file needs to be processed individually, but once processed the input line can be forgotten.
Using the on-data listener of the stdin, I get the input steam chunked by a byte size so I set this up.
process.stdin.resume();
process.stdin.setEncoding('utf8');
var lingeringLine = "";
process.stdin.on('data', function(chunk) {
lines = chunk.split("\n");
lines[0] = lingeringLine + lines[0];
lingeringLine = lines.pop();
lines.forEach(processLine);
});
process.stdin.on('end', function() {
processLine(lingeringLine);
});
But this seems so sloppy. Having to massage around the first and last items of the lines array. Is there not a more elegant way to do this?
You can use the readline module to read from stdin line by line:
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
rl.on('line', (line) => {
console.log(line);
});
rl.once('close', () => {
// end of input
});
// Work on POSIX and Windows
var fs = require("fs");
var stdinBuffer = fs.readFileSync(0); // STDIN_FILENO = 0
console.log(stdinBuffer.toString());
readline is specifically designed to work with terminal (that is process.stdin.isTTY === true). There are a lot of modules which provide split functionality for generic streams, like split. It makes things super-easy:
process.stdin.pipe(require('split')()).on('data', processLine)
function processLine (line) {
console.log(line + '!')
}
#!/usr/bin/env node
const EventEmitter = require('events');
function stdinLineByLine() {
const stdin = new EventEmitter();
let buff = '';
process.stdin
.on('data', data => {
buff += data;
lines = buff.split(/\r\n|\n/);
buff = lines.pop();
lines.forEach(line => stdin.emit('line', line));
})
.on('end', () => {
if (buff.length > 0) stdin.emit('line', buff);
});
return stdin;
}
const stdin = stdinLineByLine();
stdin.on('line', console.log);
read stream line by line,should be good for large files piped into stdin, my version:
var n=0;
function on_line(line,cb)
{
////one each line
console.log(n++,"line ",line);
return cb();
////end of one each line
}
var fs = require('fs');
var readStream = fs.createReadStream('all_titles.txt');
//var readStream = process.stdin;
readStream.pause();
readStream.setEncoding('utf8');
var buffer=[];
readStream.on('data', (chunk) => {
const newlines=/[\r\n]+/;
var lines=chunk.split(newlines)
if(lines.length==1)
{
buffer.push(lines[0]);
return;
}
buffer.push(lines[0]);
var str=buffer.join('');
buffer.length=0;
readStream.pause();
on_line(str,()=>{
var i=1,l=lines.length-1;
i--;
function while_next()
{
i++;
if(i<l)
{
return on_line(lines[i],while_next);
}
else
{
buffer.push(lines.pop());
lines.length=0;
return readStream.resume();
}
}
while_next();
});
}).on('end', ()=>{
if(buffer.length)
var str=buffer.join('');
buffer.length=0;
on_line(str,()=>{
////after end
console.error('done')
////end after end
});
});
readStream.resume();
Explanation:
to cut it correctly on utf8 letter and not in middle byte set encoding to utf8 it ensures it emits each time full multibyte letter.
When data is received the input is paused. It is used to block the input until all lines are used up. It prevents overflowing the buffet if the lines processing function is slower than input.
If there is every time a line without newlines each time. need to accommulate it for all calls and do nothing, return . once there are more than one line also append it and use the accommulated buffer.
after all the splitted lines were consumed. On the last line push the last line to buffer and resume paused stream.
es6 code
var n=0;
async function on_line(line)
{
////one each line
console.log(n++,"line ",line);
////end of one each line
}
var fs = require('fs');
var readStream = fs.createReadStream('all_titles.txt');
//var readStream = process.stdin;
readStream.pause();
readStream.setEncoding('utf8');
var buffer=[];
readStream.on('data', async (chunk) => {
const newlines=/[\r\n]+/;
var lines=chunk.split(newlines)
if(lines.length==1)
{
buffer.push(lines[0]);
return;
}
readStream.pause();
// let i=0;
buffer.push(lines[0]); // take first line
var str=buffer.join('');
buffer.length=0;//clear array, because consumed
await on_line(str);
for(let i=1;i<lines.length-1;i++)
await on_line(lines[i]);
buffer.push(lines[lines.length-1]);
lines.length=0; //optional, clear array to hint GC.
return readStream.resume();
}).on('end', async ()=>{
if(buffer.length)
var str=buffer.join('');
buffer.length=0;
await on_line(str);
});
readStream.resume();
I did not test the es6 code
In my case the program (elinks) returned lines that looked empty, but in fact had special terminal characters, color control codes and backspace, so grep options presented in other answers did not work for me. So I wrote this small script in Node.js. I called the file tight, but that's just a random name.
#!/usr/bin/env node
function visible(a) {
var R = ''
for (var i = 0; i < a.length; i++) {
if (a[i] == '\b') { R -= 1; continue; }
if (a[i] == '\u001b') {
while (a[i] != 'm' && i < a.length) i++
if (a[i] == undefined) break
}
else R += a[i]
}
return R
}
function empty(a) {
a = visible(a)
for (var i = 0; i < a.length; i++) {
if (a[i] != ' ') return false
}
return true
}
var readline = require('readline')
var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false })
rl.on('line', function(line) {
if (!empty(line)) console.log(line)
})
if you want to ask the user number of lines first:
//array to save line by line
let xInputs = [];
const getInput = async (resolve)=>{
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout,
});
readline.on('line',(line)=>{
readline.close();
xInputs.push(line);
resolve(line);
})
}
const getMultiInput = (numberOfInputLines,callback)=>{
let i = 0;
let p = Promise.resolve();
for (; i < numberOfInputLines; i++) {
p = p.then(_ => new Promise(resolve => getInput(resolve)));
}
p.then(()=>{
callback();
});
}
//get number of lines
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
readline.on('line',(line)=>{
getMultiInput(line,()=>{
//get here the inputs from xinputs array
});
readline.close();
})
process.stdin.pipe(process.stdout);

Node.js How to delete first line in file

I'm making simple Node.js app and I need to delete first line in file. Please is any way how to do it? I think that It will be possible with fs.write, but how?
Here is streamed version of removing first line from file.
As it uses streams, means you don't need to load whole file in memory, so it is way more efficient and fast, as well can work on very large files without filling memory on your hardware.
var Transform = require('stream').Transform;
var util = require('util');
// Transform sctreamer to remove first line
function RemoveFirstLine(args) {
if (! (this instanceof RemoveFirstLine)) {
return new RemoveFirstLine(args);
}
Transform.call(this, args);
this._buff = '';
this._removed = false;
}
util.inherits(RemoveFirstLine, Transform);
RemoveFirstLine.prototype._transform = function(chunk, encoding, done) {
if (this._removed) { // if already removed
this.push(chunk); // just push through buffer
} else {
// collect string into buffer
this._buff += chunk.toString();
// check if string has newline symbol
if (this._buff.indexOf('\n') !== -1) {
// push to stream skipping first line
this.push(this._buff.slice(this._buff.indexOf('\n') + 2));
// clear string buffer
this._buff = null;
// mark as removed
this._removed = true;
}
}
done();
};
And use it like so:
var fs = require('fs');
var input = fs.createReadStream('test.txt'); // read file
var output = fs.createWriteStream('test_.txt'); // write file
input // take input
.pipe(RemoveFirstLine()) // pipe through line remover
.pipe(output); // save to file
Another way, which is not recommended.
If your files are not large, and you don't mind loading them into memory, load file, remove line, save file, but it is slower and wont work well on large files.
var fs = require('fs');
var filePath = './test.txt'; // path to file
fs.readFile(filePath, function(err, data) { // read file to memory
if (!err) {
data = data.toString(); // stringify buffer
var position = data.toString().indexOf('\n'); // find position of new line element
if (position != -1) { // if new line element found
data = data.substr(position + 1); // subtract string based on first line length
fs.writeFile(filePath, data, function(err) { // write file
if (err) { // if error, report
console.log (err);
}
});
} else {
console.log('no lines found');
}
} else {
console.log(err);
}
});
Here is another way:
const fs = require('fs');
const filePath = './table.csv';
let csvContent = fs.readFileSync(filePath).toString().split('\n'); // read file and convert to array by line break
csvContent.shift(); // remove the the first element from array
csvContent = csvContent.join('\n'); // convert array back to string
fs.writeFileSync(filePath, csvContent);
Thanks to #Lilleman 's comment, I've made an amendment to the original solution, which requires a 3rd-party module "line-by-line" and can prevent memory overflow and racing condition while processing very large file.
const fs = require('fs');
const LineReader = require('line-by-line');
const removeLines = function(srcPath, destPath, count, cb) {
if(count <= 0) {
return cb();
}
var reader = new LineReader(srcPath);
var output = fs.createWriteStream(destPath);
var linesRemoved = 0;
var isFirstLine = true;
reader.on('line', (line) => {
if(linesRemoved < count) {
linesRemoved++;
return;
}
reader.pause();
var newLine;
if(isFirstLine) {
newLine = line;
isFirstLine = false;
} else {
newLine = '\n' + line;
}
output.write(newLine, () => {
reader.resume();
});
})
.on('error', (err) => {
reader.pause();
return cb(err);
})
.on('close', () => {
return cb();
})
}
---------------- original solution below---------------
Inspired by another answer, here is a revised stream version:
const fs = require('fs');
const readline = require('readline');
const removeFirstLine = function(srcPath, destPath, done) {
var rl = readline.createInterface({
input: fs.createReadStream(srcPath)
});
var output = fs.createWriteStream(destPath);
var firstRemoved = false;
rl.on('line', (line) => {
if(!firstRemoved) {
firstRemoved = true;
return;
}
output.write(line + '\n');
}).on('close', () => {
return done();
})
}
and it can be easily modified to remove certain amount of lines, by changing the 'firstRemoved' into a counter:
var linesRemoved = 0;
...
if(linesRemoved < LINES_TO_BE_REMOVED) {
linesRemoved++;
return;
}
...
Here is a naive solution using the Promise-based file system APIs.
const fs = require('node:fs/promises')
const os = require('node:os')
async function removeLines(path, numLinesToRemove) {
const data = await fs.readFile(path, { encoding: 'utf-8' })
const newData = data
.split(os.EOL) // split data into array of strings
.slice(numLinesToRemove) // remove first N lines of array
.join(os.EOL) // join array into a single string
// overwrite original file with new data
return fs.writeFile(path, newData)
}

Knockout-2.2.0, subscribe get value before change AND new value

jsfiddle link: http://jsfiddle.net/T8ee7/
When I call Knockout's subscribe method is there a way I can get both the previous and new value? Right now, I can only call get these values separately.
I want to trigger some code if the old and new value are different.
I suppose I could do the following, but it can get messy...
(http://jsfiddle.net/MV3fN/)
var sv = sv || {};
sv.PagedRequest = function (pageNumber, pageSize) {
this.pageNumber = ko.observable(pageNumber || 1);
this.numberOfPages = ko.observable(1);
this.pageSize = ko.observable(pageSize || sv.DefaultPageSize);
};
var _pagedRequest = new sv.PagedRequest();
var oldValue;
_pagedRequest.pageNumber.subscribe(function (previousValue) {
console.log("old: " + previousValue);
oldValue = previousValue;
}, _pagedRequest, "beforeChange");
_pagedRequest.pageNumber.subscribe(function (newValue) {
console.log("new: " + newValue);
if (oldValue != newValue) {
console.log("value changed!");
}
});
_pagedRequest.pageNumber(10);
_pagedRequest.pageNumber(20);
​
I prefer using an observable extender.
http://jsfiddle.net/neonms92/xybGG/
Extender:
ko.extenders.withPrevious = function (target) {
// Define new properties for previous value and whether it's changed
target.previous = ko.observable();
target.changed = ko.computed(function () { return target() !== target.previous(); });
// Subscribe to observable to update previous, before change.
target.subscribe(function (v) {
target.previous(v);
}, null, 'beforeChange');
// Return modified observable
return target;
}
Example Usage:
// Define observable using 'withPrevious' extension
self.hours = ko.observable().extend({ withPrevious: 1 });
// Subscribe to observable like normal
self.hours.subscribe(function () {
if (!self.hours.changed()) return; // Cancel if value hasn't changed
print('Hours changed from ' + self.hours.previous() + ' to ' + self.hours());
});
This seems to work for me
ko.observable.fn.beforeAndAfterSubscribe = function (callback, target) {
var _oldValue;
this.subscribe(function (oldValue) {
_oldValue = oldValue;
}, null, 'beforeChange');
this.subscribe(function (newValue) {
callback.call(target, _oldValue, newValue);
});
};
See more at: http://ideone.com/NPpNcB#sthash.wJn57567.dpuf
http://jsfiddle.net/MV3fN/3/
var sv = sv || {};
sv.PagedRequest = function (pageNumber, pageSize) {
var self = this;
self.pageNumber = ko.observable(pageNumber || 1);
self.numberOfPages = ko.observable(1);
self.pageSize = ko.observable(pageSize || sv.DefaultPageSize);
self.pageNumber.subscribe(function (previousValue) {
console.log(previousValue);
console.log(self.pageNumber.arguments[0]);
if (previousValue != _pagedRequest.pageNumber.arguments[0]) {
console.log('value changed');
}
else {
//This won't get executed because KO doesn't
//call the function if the value doesn't change
console.log('not changed');
}
}, _pagedRequest, "beforeChange");
};
var _pagedRequest = new sv.PagedRequest();
_pagedRequest.pageNumber(10);
_pagedRequest.pageNumber(20);
_pagedRequest.pageNumber(20);
_pagedRequest.pageNumber(5);
I don't know if you're really supposed to use arguments[0], but it seems to work.
You could also set up your own method to accomplish this in a much cleaner way:
http://jsfiddle.net/PXKgr/2/
...
self.setPageNumber = function(page) {
console.log(self.pageNumber());
console.log(page);
if (self.pageNumber() != page) {
console.log('value changed');
}
else {
console.log('not changed');
}
self.pageNumber(page);
};
...
_pagedRequest.setPageNumber(10);
_pagedRequest.setPageNumber(20);
_pagedRequest.setPageNumber(20);
_pagedRequest.setPageNumber(5);

Resources