├── .gitignore ├── History.md ├── Makefile ├── Readme.md ├── bin └── pv.js ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.1.2 / 2014-07-16 3 | ================== 4 | 5 | * add missing _flush callback 6 | 7 | 0.1.1 / 2014-07-16 8 | ================== 9 | 10 | * fix newlines in output 11 | 12 | 0.1.0 / 2014-07-16 13 | ================== 14 | 15 | * document js api 16 | * add js api 17 | * docs 18 | 19 | 0.0.1 / 2014-06-04 20 | ================== 21 | 22 | * add .repository and .description 23 | * add history 24 | 25 | 0.0.0 / 2014-06-04 26 | ================== 27 | 28 | * pkg name node-pv 29 | * show naive eta 30 | * docs 31 | * document bin interface 32 | * show percentage 33 | * add -s, --size calculation 34 | * refactor 35 | * add .bin 36 | * use carriage returns 37 | * add -N, --name 38 | * add throughput 39 | * output progress on first chunk 40 | * display time elapsed 41 | * initial readme 42 | * show volume 43 | * passthrough bin 44 | 45 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @node_modules/.bin/tape test.js 4 | 5 | .PHONY: test 6 | 7 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # node-pv 3 | 4 | A node.js implementation of the 5 | [Pipe Viewer](http://www.ivarch.com/programs/pv.shtml) utility, useful for 6 | inspecting a pipe's traffic. 7 | 8 | ## Example 9 | 10 | ```bash 11 | $ cat /dev/random | pv >/dev/null 12 | 18.88MB 00:00:02 [9.59MB/s]^C 13 | ``` 14 | 15 | ## Usage 16 | 17 | There's an excellent description on 18 | [Peteris Krumins](https://github.com/pkrumins)'s blog: 19 | [A Unix Utility You Should Know About: Pipe Viewer](http://www.catonmat.net/blog/unix-utilities-pipe-viewer/) 20 | 21 | ```bash 22 | $ source | pv [OPTIONS] | dest 23 | 24 | $ pv [OPTIONS] | dest 25 | ``` 26 | 27 | ## Options 28 | 29 | - `-s, --size SIZE`: Assume the total amount of data to be transferred is __SIZE__ bytes 30 | - `-N, --name NAME`: Prefix the output information with __NAME__ 31 | 32 | ## Installation 33 | 34 | ```bash 35 | $ npm install -g node-pv 36 | ``` 37 | 38 | ## JS API 39 | 40 | ```js 41 | var PV = require('node-pv'); 42 | var pv = PV({ 43 | size: /* ... */, 44 | name: /* ... */ 45 | }); 46 | 47 | pv.on('info', function(str){ 48 | process.stderr.write(str); 49 | }); 50 | 51 | process.stdin.pipe(pv).pipe(process.stdout); 52 | ``` 53 | 54 | ## License 55 | 56 | MIT 57 | 58 | -------------------------------------------------------------------------------- /bin/pv.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var minimist = require('minimist'); 5 | var PV = require('..'); 6 | 7 | // 8 | // Arguments 9 | // 10 | 11 | var argv = minimist(process.argv.slice(2), { 12 | alias: { 13 | 'name': 'N', 14 | 'size': 's' 15 | } 16 | }); 17 | 18 | // 19 | // Input 20 | // 21 | 22 | var input = argv._[0] 23 | ? fs.createReadStream(argv._[0]) 24 | : process.stdin; 25 | 26 | // 27 | // Size 28 | // 29 | 30 | var size; 31 | 32 | if (argv.size) { 33 | size = argv.size; 34 | } else if (argv._[0]) { 35 | size = fs.statSync(argv._[0]).size; 36 | } 37 | 38 | // 39 | // Go! 40 | // 41 | 42 | var pv = PV({ 43 | name: argv.name, 44 | size: size 45 | }); 46 | 47 | input.pipe(pv).pipe(process.stdout); 48 | 49 | pv.on('info', function(str){ 50 | process.stderr.write(str); 51 | }); 52 | 53 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Transform = require('stream').Transform; 2 | var bytes = require('bytes'); 3 | var inherits = require('util').inherits; 4 | 5 | module.exports = PV; 6 | inherits(PV, Transform); 7 | 8 | function PV(opts){ 9 | if (!(this instanceof PV)) return new PV(opts); 10 | Transform.call(this); 11 | 12 | opts = opts || {}; 13 | this.name = opts.name; 14 | this.size = opts.size; 15 | this.volume = 0; 16 | this.throughput = 0; 17 | this.start = new Date; 18 | this.firstLine = true; 19 | 20 | this.interval = setInterval(this.progress.bind(this), 1000); 21 | } 22 | 23 | PV.prototype._transform = function(buf, _, done){ 24 | this.volume += buf.length; 25 | this.throughput += buf.length; 26 | 27 | if (this.firstLine) { 28 | this.progress(); 29 | this.firstLine = false; 30 | } 31 | 32 | done(null, buf); 33 | }; 34 | 35 | PV.prototype.progress = function(){ 36 | var segs = []; 37 | if (this.name) segs.push(this.name + ':'); 38 | segs.push(bytes(this.volume).toUpperCase()); 39 | segs.push(time(new Date - this.start)); 40 | segs.push('[' + bytes(this.throughput).toUpperCase() + '/s]'); 41 | if (this.size) { 42 | segs.push(Math.round(this.volume / this.size * 100) + '%'); 43 | if (this.throughput) { 44 | segs.push('ETA'); 45 | segs.push(time((this.size - this.volume) / this.throughput * 1000)); 46 | } 47 | } 48 | 49 | this.throughput = 0; 50 | this.emit('info', '\033[1K\r' + segs.join(' ')); 51 | }; 52 | 53 | PV.prototype._flush = 54 | PV.prototype.close = function(cb){ 55 | clearInterval(this.interval); 56 | if (cb) cb(); 57 | }; 58 | 59 | // 60 | // Print time `n` in format HH:MM:SS. 61 | // 62 | 63 | const SECOND = 1000; 64 | const MINUTE = 60 * SECOND; 65 | const HOUR = 60 * MINUTE; 66 | 67 | function time(n){ 68 | var out = ''; 69 | 70 | var hours = Math.floor(n / HOUR); 71 | out += pad(hours) + ':'; 72 | n -= hours * HOUR; 73 | 74 | var minutes = Math.floor(n / MINUTE); 75 | out += pad(minutes) + ':'; 76 | n -= minutes * MINUTE; 77 | 78 | var seconds = Math.floor(n / SECOND); 79 | out += pad(seconds); 80 | 81 | return out; 82 | } 83 | 84 | function pad(n){ 85 | n = String(n); 86 | return n.length == 1 87 | ? '0' + n 88 | : n; 89 | } 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-pv", 3 | "version": "0.1.5", 4 | "repository": "juliangruber/node-pv", 5 | "description": "The PipeView utility in JavaScript", 6 | "dependencies": { 7 | "bytes": "^2.4.0", 8 | "minimist": "^1.2.0" 9 | }, 10 | "devDependencies": { 11 | "tape": "^4.6.3" 12 | }, 13 | "bin": { 14 | "pv": "bin/pv.js" 15 | }, 16 | "scripts": { 17 | "test": "tape test.js" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var spawn = require('child_process').spawn; 3 | var tmpdir = require('os').tmpdir; 4 | var fs = require('fs'); 5 | 6 | test('bin stdin pass through', function(t){ 7 | var ps = spawn(__dirname + '/bin/pv.js'); 8 | ps.on('error', t.error.bind(t)); 9 | ps.stdout.on('data', function(chunk){ 10 | t.equal(chunk.toString(), 'hey'); 11 | t.end(); 12 | }); 13 | ps.stdin.end('hey'); 14 | }); 15 | 16 | test('bin stdarg read', function(t){ 17 | var tmp = tmpdir() + '/pv' + Math.random(); 18 | fs.writeFileSync(tmp, 'hey'); 19 | 20 | var ps = spawn(__dirname + '/bin/pv.js', [tmp]); 21 | ps.on('error', t.error.bind(t)); 22 | ps.stdout.on('data', function(chunk){ 23 | t.equal(chunk.toString(), 'hey'); 24 | t.end(); 25 | }); 26 | }); 27 | 28 | test('bin volume', function(t){ 29 | var ps = spawn(__dirname + '/bin/pv.js'); 30 | ps.on('error', t.error.bind(t)); 31 | ps.stderr.once('data', function(chunk){ 32 | ps.stdin.end(); 33 | t.ok(/3B/.test(chunk.toString())); 34 | t.end(); 35 | }); 36 | ps.stdin.write('hey'); 37 | }); 38 | 39 | test('bin time', function(t){ 40 | var ps = spawn(__dirname + '/bin/pv.js'); 41 | ps.on('error', t.error.bind(t)); 42 | ps.stderr.once('data', function(chunk){ 43 | ps.stdin.end(); 44 | t.ok(/00:00:00/.test(chunk.toString())); 45 | t.end(); 46 | }); 47 | ps.stdin.write('hey'); 48 | }); 49 | 50 | test('bin throughput', function(t){ 51 | var ps = spawn(__dirname + '/bin/pv.js'); 52 | ps.on('error', t.error.bind(t)); 53 | ps.stderr.once('data', function(chunk){ 54 | ps.stdin.end(); 55 | t.ok(/\[3B\/s\]/.test(chunk.toString())); 56 | t.end(); 57 | }); 58 | ps.stdin.write('hey'); 59 | }); 60 | 61 | test('bin name', function(t){ 62 | var ps = spawn(__dirname + '/bin/pv.js', ['-N', 'test']); 63 | ps.on('error', t.error.bind(t)); 64 | ps.stderr.once('data', function(chunk){ 65 | ps.stdin.end(); 66 | t.ok(/test:/.test(chunk.toString())); 67 | t.end(); 68 | }); 69 | ps.stdin.write('hey'); 70 | }); 71 | 72 | test('bin size arg', function(t){ 73 | var ps = spawn(__dirname + '/bin/pv.js', ['-s', '30']); 74 | ps.on('error', t.error.bind(t)); 75 | ps.stderr.once('data', function(chunk){ 76 | ps.stdin.end(); 77 | t.ok(/10%/.test(chunk.toString())); 78 | t.end(); 79 | }); 80 | ps.stdin.write('hey'); 81 | }); 82 | 83 | test('bin argv size', function(t){ 84 | var tmp = tmpdir() + '/pv' + Math.random(); 85 | fs.writeFileSync(tmp, 'hey'); 86 | 87 | var ps = spawn(__dirname + '/bin/pv.js', [tmp]); 88 | ps.on('error', t.error.bind(t)); 89 | ps.stderr.once('data', function(chunk){ 90 | ps.stdin.end(); 91 | t.ok(/100%/.test(chunk.toString())); 92 | t.end(); 93 | }); 94 | ps.stdin.write('hey'); 95 | }); 96 | 97 | test('bin eta', function(t){ 98 | var ps = spawn(__dirname + '/bin/pv.js', ['-s', '30']); 99 | ps.on('error', t.error.bind(t)); 100 | ps.stderr.once('data', function(chunk){ 101 | ps.stdin.end(); 102 | t.ok(/ETA 00:00:09/.test(chunk.toString())); 103 | t.end(); 104 | }); 105 | ps.stdin.write('hey'); 106 | }); 107 | 108 | test('bin no newline', function(t){ 109 | var ps = spawn(__dirname + '/bin/pv.js'); 110 | ps.on('error', t.error.bind(t)); 111 | ps.stderr.once('data', function(chunk){ 112 | ps.stdin.end(); 113 | t.notOk(/\n/.test(chunk.toString())); 114 | t.end(); 115 | }); 116 | ps.stdin.write('hey'); 117 | }); 118 | 119 | --------------------------------------------------------------------------------