Reputation: 6132
I have a small command line program written in NodeJS to process all text files in a given directory. Since Node is asynch the script will read all the files, process them and output something like this
Converting file: example-file-1.txt
Converting file: example-file-2.txt
Converting file: example-file-3.txt
Converting file: example-file-4.txt
File example-file-3.txt converted!
File example-file-1.txt converted!
File example-file-2.txt converted!
File example-file-4.txt converted!
This is not very pretty, the "converted" messages are not in order as the files are different size and are finished processing at different speeds.
What I'd like to see is something like this
File example-file-1.txt: DONE! // processing of this file has finished
File example-file-2.txt: converting... // this file is still processing
File example-file-3.txt: DONE!
File example-file-4.txt: converting...
The most right part of each line should be dynamically updated with progress.
What I'm really asking here is how to display in console a few lines of messages that I can update as the script progresses?
Upvotes: 6
Views: 3325
Reputation: 6132
I ended up processing all my files in a synchronous way with a simple recursive function in a callback. User Ora library to show progress with each file in terminal window.
var files = [];
// get all "txt" files from a given directory and store them in an array "files"
function getFiles(dir) {
var filesRaw = fs.readdirSync(dir);
for (var i in filesRaw) {
var name = dir === '.' ? filesRaw[i] : dir + '/' + filesRaw[i];
var ext = path.extname(name);
if (!fs.statSync(name).isDirectory()) {
if (ext == '.txt') {
files.push(path.resolve(process.cwd(), name));
}
}
}
// only after the directory is read, run the function that starts processing those files
recode(files);
}
function recode() {
if (files.length > 0) {
var fileName = files.shift();
// this is the terminal spinner library
const spinner = ora(`File ${path.basename(fileName)} processing...`).start();
var fileSingle = fs.readFileSync(fileName);
var data = new Buffer(fileSingle, "ascii");
var translated = encoding.convert(data, "UTF-8", "CP1250");
var converted = iconvlite.encode(translated, "utf8").toString();
fs.writeFile(fileName, converted, function(err) {
if (err) {
spinner.fail(`File ${path.basename(fileName)} failed.`);
console.log(err);
} else {
spinner.succeed(`File ${path.basename(fileName)} DONE!`);
}
// process another file from the files array only when the previous file is finished and saved to disk
recode(files);
});
} else {
console.log('No more files to process or all files done.');
}
}
Upvotes: 0
Reputation: 156
Using standard terminal capabilities, there is only the carriage return \r
that allows to reset the cursor to the start of the current line and overwrite it for updates.
Most terminals support ANSI/VT100 control codes that allow setting colors, cursor positioning and other screen updates. Node modules making use of these are for example:
Windows supports the escape sequences after some tweaks.
The following example solves the task by sending the control commands directly and writing out a cached screen. The commands used are:
\x1b[H
and \x1b[2J
.Writing more lines than available will result in flickering.
var progress = {
'example-file-1.txt': 'converting',
'example-file-2.txt': 'converting',
'example-file-3.txt': 'converting'
}
function renderProgress() {
// reset cursor, clear screen, do not write a new line
process.stdout.write('\x1b[H\x1b[2J')
// render progress
Object.keys(progress).forEach(filename => console.log(`${filename}: ${progress[filename]}`))
}
function updateProgress(filename, status) {
progress[filename] = status
renderProgress()
}
// render initial progress
renderProgress()
// simulare updates
setTimeout(() => updateProgress('example-file-2.txt', 'done'), 1000)
setTimeout(() => updateProgress('example-file-1.txt', 'reconsidering'), 2500)
setTimeout(() => updateProgress('example-file-3.txt', 'done'), 4000)
setTimeout(() => updateProgress('example-file-1.txt', 'done'), 6000)
Upvotes: 3