I want to write a command-line program that will show UNICODE characters that will "animate" in the left side of a line. How can I do it using node.js to have the checkmark and UNICODE animation you see here in the following image? What are the characters, and how do I make the instructions to overwrite the first character on the line?
You can build your own spinner in node by writing to stdout like this:
import { stdout } from "process"
function startSpinner() {
const characters = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
const cursorEsc = {
hide: '\u001B[?25l',
show: '\u001B[?25h',
}
stdout.write(cursorEsc.hide)
let i = 0;
const timer = setInterval(function () {
stdout.write("\r" + characters[i++]);
i = i >= characters.length ? 0 : i;
}, 150);
return () => {
clearInterval(timer)
stdout.write("\r")
stdout.write(cursorEsc.show)
}
}
Then invoke the function like this - here's an example that runs for 4 seconds and then finishes
const stopSpinner = startSpinner();
setTimeout(() => {
stopSpinner()
console.log("done")
},4000)
Further Reading
How to make a loading animation in Console Application written in JavaScript or NodeJs?
ANSI Escape Codes
Use this npm library
https://www.npmjs.com/package/cli-spinners
Implement like this:
const cliSpinners = require('cli-spinners');
console.log(cliSpinners.dots);
Related
When I run a little node program and I break the output through a pipe, the bash terminal output remains hidden and I'm forced to run reset (which works every time). How should I restore correctly after a broken pipe to avoid going through reset?
The program:
const { unmarshall } = require("#aws-sdk/util-dynamodb");
const fs = require('fs');
(async () => {
const input = fs.readFileSync(process.argv[2], 'utf-8');
const records = JSON.parse(input);
if (records.Items) {
records.Items = records.Items.map((a) => unmarshall(a));
}
process.stdout.on('error', function( err ) {
if (err.code === 'EPIPE') {
process.exit(0);
}
});
process.stdout.write(JSON.stringify(records, undefined, 2));
})();
And when I run this program like this and exit from less via a q keystroke, subsequent terminal output is hidden (after exiting the JS program and breaking the pipe). Output is restored via reset:
node example.js dynamo_output.json | less
# no terminal output is visible
$ reset
# output is restored
This appears to work:
const fs = require('fs');
const writeStdoutSync = (str) => {
fs.writeSync(process.stdout.fd, str);
}
I'm building a small app in node.js that uses execa to read print statements coming from a compiled Swift application. The idea is similar to Sindre Sorhus' (who else!?) do-not-disturb
Although I'm no Swift-programmer, I put together a pretty straightforward solution. The binary is compiled by running swift build --configuration=release from the CL to be used in a node-app. It also compiles fine (without the CLI-part) in a Swift playground from XCode and I can see the correct print statements coming in.
import Cocoa
var isLocked:Bool = false
DistributedNotificationCenter.default().addObserver(forName: .init("com.apple.isScreenLocked"), object: nil, queue: nil) { notification in
print("Screen is locked")
isLocked = true
}
DistributedNotificationCenter.default().addObserver(forName: .init("com.apple.isScreenUnlocked"), object: nil, queue: nil) { notification in
print("Screen is unlocked")
isLocked = false
}
struct CLI {
static var standardInput = FileHandle.standardInput
static var standardOutput = FileHandle.standardOutput
static var standardError = FileHandle.standardError
static let arguments = Array(CommandLine.arguments.dropFirst(1))
}
switch CLI.arguments.first {
case "status":
print(isLocked)
default:
print("Unsupported command", to: .standardError)
exit(1)
}
// Some other functions omitted for brevity
Now, when I run the code below from Node.js, everything seems to be working fine. However for some reason, the observer doesn't receive the notification.
'use strict';
const execa = require('execa');
const electronUtil = require('electron-util/node');
const binary = path.join(electronUtil.fixPathForAsarUnpack(__dirname), 'IsLockedOrNot');
setInterval(async () => {
const {stdout} = await execa(binary, ['status']);
console.log(stdout) // keeps logging false, also when screen is locked
}, 1000)
Does anyone have any idea WHY the notifications are not being received in this scenario? I tried various things, like explicitly disabling sleep mode shell.exec('sudo pmset -a disablesleep 1')and compiling the app with the --disable-sandbox flag. No luck however until know..
Use spawn from the base child_process library not execa, make sure you are following stdout, and the most important part for nodejs and swift is that you flush the buffer after every line. Otherwise you have to wait for the program to die before you receive any output. Use "import Darwin.C" and "fflush(stdout)" after every "print" where you want a 'newline'
I apologise for the phrasing of the question - it's a bit difficult to sum up as a question - please feel free to edit it if you can clarify. Also, as this quite a complex and long query - thank you to all those who are putting in the time to read through it!
I have 4 files (listed with directory tree from project root) as part of a project I'm building which aims to scrape blockchains and take advantage of multiple cores do get the job done:
./main.js
./scraper.js
./api/api.js
./api/litecoin_api.js
main.js
const { scraper } = require('./scraper.js')
const blockchainCli = process.env.BLOCKSCRAPECLI || 'litecoin-cli'
const client = (args) => {
// create child process which returns a promise which resolves after
// data has finished buffering from locally hosted node using cli
let child = spawn(`${blockchainCli} ${args.join(' ')}`, {
shell: true
})
// ... wrap command in a promise here, etc
}
const main = () => {
// count cores, spawn a worker per core using node cluster, add
// message handlers, then begin scraping blockchain with each core...
scraper(blockHeight)
}
main()
module.exports = {
client,
blockchainCli
}
scraper.js
const api = require('./api/api.js')
const scraper = async (blockHeight) => {
try {
let blockHash = await api.getBlockHashByHeight(blockHeight)
let block = await api.getBlock(blockHash)
// ... etc, scraper tested and working, writes to shared writeStream
}
module.exports = {
scraper
}
api.js
const { client, blockchainCli } = require('../main.js')
const litecoin = require('./litecoin_api')
let blockchain = undefined
if (blockchainCli === 'litecoin-cli' || blockchainCli === 'bitcoin-cli') {
blockchain = litecoin
}
// PROBLEM HERE: blockchainCli (and client) are both undefined if and
// only if running scraper from main.js (but not if running scraper
// from scraper.js)
const decodeRawTransaction = (txHash) => {
return client([blockchain.decodeRawTransaction, txHash])
}
const getBlock = (blockhash) => {
return client([blockchain.getBlock, blockhash])
}
const getBlockHashByHeight = (height) => {
return client([blockchain.getBlockHash, height])
}
const getInfo = () => {
return client([blockchain.getInfo])
}
const getRawTransaction = (txHash, verbose = true) => {
return client([blockchain.getRawTransaction, txHash, verbose])
}
module.exports = {
decodeRawTransaction,
getBlock,
getBlockHashByHeight,
getInfo,
getRawTransaction
}
So, I've taken out most the noise in the files which I don't think is necessary but it's open source so if you need more take a look here.
The problem is that, if I start the scraper from inside scraper.js by doing, say, something like this: scraper(1234567) it works like a charm and outputs the expected data to a csv file.
However if I start the scraper from inside the main.js file, I get this error:
Cannot read property 'getBlockHash' of undefined
at Object.getBlockHashByHeight (/home/grayedfox/github/blockscrape/api/api.js:19:29)
at scraper (/home/grayedfox/github/blockscrape/scraper.js:53:31)
at Worker.messageHandler (/home/grayedfox/github/blockscrape/main.js:81:5)
I don't know why, when launching the scraper from main.js, the blockchain is undefined. I thought it might be from the destructuring, but removing the curly braces from around the first line in the example main.js file doesn't change anything (same error).
Things are a bit messy at the moment (in the middle of developing this branch) - but the essential problem now is that it's not clear to me why the require would fail (cannot see variables inside main.js) if it's used in the following way:
main.js (execute scraper()) > scraper.js > api.js
But not fail (can see variables inside main.js) if it's run like this:
scraper.js (execute scraper()) > api.js
Thank you very much for your time!
You have a circular dependency between main and api, each requiring in the other. main requires api through scraper and api directly requires main. That causes things not to work.
You have to remove the circular dependency by putting common shared code into its own module that can be included by both, but doesn't include others that include it. It just needs better modularity.
I am trying to get the stdout of 7zip when it processes files and get the percentage in nodeJs, but it doesn't behave as expected. 7zip doesn't output anything to stdout until the very end of the execution. Which is not very helpful.. especially when I have large files being compressed and no feedback is shown for a very long time.
The code I am using (simplified):
// 7zip test, place the 7z.exe in the same dir, if it's not on %PATH%
var cp = require('child_process');
var inputFile = process.argv[2]; if(inputFile==null) return;
var regProgress = /(\d{1,3})%\s*$/; //get the last percentage of the string, 3 digits
var proc = cp.spawn("7z.exe",["a","-t7z" ,"-y" ,inputFile + ".7z",inputFile]);
proc.stdout.setEncoding("utf8");
proc.stdout.on("data",function(data){
if(regProgress.test(data))
console.log("Progress = " + regProgress.exec(data)[1] + "%");
});
proc.once("exit",function(exit,sig){ console.log("Complete"); });
I have used the same code to get the percentage with WinRar successfully and I am beginning to think that 7zip might be buggy? Or I am doing it wrong? Can I forcefully read the stdout of a process with a timer perhaps?
The same code above, with the exception of the following line replaced, works as expected with WinRar.
var proc = cp.spawn("Rar.exe",["a","-s","-ma5","-o+",inputFile+".rar",inputFile]);
If anyone knows why this happens and if it is fixable, I would be grateful! :-)
p.s. I have tried 7za.exe, the command line version of 7zip, also the stable, beta and alpha versions, they all have the same issue
It is no longer needed to use a terminal emulator like pty.js, you can pass the -bsp1 to 7z to force to output the progress to stdout.
7-zip only outputs progress when stdout is a terminal.
To trick 7-zip, you need to npm install pty.js (requires Visual Studio or VS Express with Windows SDK) and then use code like:
var pty = require('pty');
var inputFile = process.argv[2],
pathTo7zip = 'c:\\Program Files\\7-Zip\\7z.exe';
if (inputFile == null)
return;
var term = pty.spawn(process.env.ComSpec, [], {
name: 'ansi',
cols: 200,
rows: 30,
cwd: process.env.HOME,
env: process.env
});
var rePrg = /(\d{1,3})%\r\n?/g,
reEsc = /\u001b\[\w{2}/g,
reCwd = new RegExp('^' + process.cwd().replace(/\\/g, '\\\\'), 'm');
prompts = 0,
buffer = '';
term.on('data', function(data) {
var m, idx;
buffer += data;
// remove terminal escape sequences
buffer = buffer.replace(reEsc, '');
// check for multiple progress indicators in the current buffer
while (m = rePrg.exec(buffer)) {
idx = m.index + m[0].length;
console.log(m[1] + ' percent done!');
}
// check for the cmd.exe prompt
if (m = reCwd.exec(buffer)) {
if (++prompts === 2) {
// command is done
return term.kill();
} else {
// first prompt is before we started the actual 7-zip process
if (idx === undefined) {
// we didn't see a progress indicator, so make sure to truncate the
// prompt from our buffer so that we don't accidentally detect the same
// prompt twice
buffer = buffer.substring(m.index + m[0].length);
return;
}
}
}
// truncate the part of our buffer that we're done processing
if (idx !== undefined)
buffer = buffer.substring(idx);
});
term.write('"'
+ pathTo7zip
+ '" a -t7z -y "'
+ inputFile
+ '.7z" "'
+ inputFile
+ '"\r');
It should be noted that 7-zip does not always output 100% at finish. If the file compresses quickly, you may just see only a single 57% for example, so you will have to handle that however you want.
I have a simple readline shell written in Coffeescript:
rl = require 'readline'
cli = rl.createInterface process.stdin, process.stdout, null
cli.setPrompt "hello> "
cli.on 'line', (line) ->
console.log line
cli.prompt()
cli.prompt()
Running this displays a prompt:
$ coffee cli.coffee
hello>
I would like to be able to hit Ctrl-L to clear the screen. Is this possible?
I have also noticed that I cannot hit Ctrl-L in either the node or coffee REPLs either.
I am running on Ubuntu 11.04.
You can watch for the keypress yourself and clear the screen.
process.stdin.on 'keypress', (s, key) ->
if key.ctrl && key.name == 'l'
process.stdout.write '\u001B[2J\u001B[0;0f'
Clearing is done with ASCII control sequences like those written here:
http://ascii-table.com/ansi-escape-sequences-vt-100.php
The first code \u001B[2J instructs the terminal to clear itself, and the second one \u001B[0;0f forces the cursor back to position 0,0.
Note
The keypress event is no longer part of the standard Node API in Node >= 0.10.x but you can use the keypress module instead.
In the MAC terminal, to clear the console in NodeJS, you just hit COMMAND+K just like in Google Developer Tools Console so I'm guessing that on Windows it would be CTRL+K.
This is the only answer that will clear the screen AND scroll history.
function clear() {
// 1. Print empty lines until the screen is blank.
process.stdout.write('\033[2J');
// 2. Clear the scrollback.
process.stdout.write('\u001b[H\u001b[2J\u001b[3J');
}
// Try this example to see it in action!
(function loop() {
let i = -40; // Print 40 lines extra.
(function printLine() {
console.log('line ' + (i + 41));
if (++i < process.stdout.columns) {
setTimeout(printLine, 40);
}
else {
clear();
setTimeout(loop, 3000);
}
})()
})()
The first line ensures the visible lines are always cleared.
The second line ensures the scroll history is cleared.
Try also:
var rl = require('readline');
rl.cursorTo(process.stdout, 0, 0);
rl.clearScreenDown(process.stdout);
On response to #loganfsmyth comment on his answer (thanks for the edit!).
I have been looking here and there and, besides of the wonderfull keypress module, there is a core module that makes possible to create a cli with all standard terminal behavior (all things we give for granted today such as history, options to provide an auto-complete function and input events such as keypress are there).
The module is readline (documentation). The good news is that all the standar behaviour is already done for us so there is no need to attach event handlers (i.e. history, clearing the screen on Ctrl+L, man if you provided the auto complete function it'll be on Tabpress).
Just as an example
var readline = require('readline')
, cli = readline.createInterface({
input : process.stdin,
output : process.stdout
});
var myPrompt = ' > myPropmt '
cli.setPrompt(myPrompt, myPrompt.length);
// prompt length so you can use "color" in your prompt
cli.prompt();
// Display ' > myPrompt ' with all standard features (history also!)
cli.on('line', function(cmd){ // fired each time the input has a new line
cli.prompt();
})
cli.input.on('keypress', function(key){ // self explanatory
// arguments is a "key" object
// with really nice properties such as ctrl : false
process.stdout.write(JSON.stringify(arguments))
});
Really good discovery.
The node version I'm using is v0.10.29. I have been looking at the changelog and it was there since 2010 (commit 10d8ad).
You can clear screen using console.log() and escape sequences.
cli.on 'line', (line) ->
if line == 'cls'
console.log("\033[2J\033[0f")
else
console.log line
cli.prompt()
Vorpal.js makes things like this really easy.
For an interactive CLI with a clear command as well as a REPL within the context of your application, do this:
var vorpal = require('vorpal')();
var repl = require('vorpal-repl');
vorpal
.delimiter('hello>')
.use(repl)
.show();
vorpal
.command('clear', 'Clears the screen.')
.action(function (args, cb) {
var blank = '';
for (var i = 0; i < process.stdout.rows; ++i) {
blank += '\n';
}
vorpal.ui.rewrite(blank);
vorpal.ui.rewrite('');
cb();
});
If you are using readline.createInterface you could do something like:
function clearPrompt() {
rl.setPrompt("");
rl.prompt();
}
Note this won't clear the entire terminal, just the readline prompt. Still useful if you want the user to keep their place in the terminal