Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Do you want a `sscanf` like function to parse format strings?
* [`Directly return`](#directly-return)
* [`Array return`](#array-return)
* [`Json return`](#json-return)
* [`Detect end of input`](#detect-end-of-input)
* [`sscanf`](#sscanf)

## Installation
Expand Down Expand Up @@ -149,6 +150,30 @@ result {
}
```

### Detect end of input

After calling `scanf()`, use `scanf.isEOF()` to check whether the read reached
the end of standard input. This is different from an empty line or input that
does not match the requested format.

```javascript
var scanf = require('scanf');

while (true) {
var values = scanf('%d %d %d');

if (scanf.isEOF()) {
break;
}

console.log(values[0], values[1], values[2]);
}
```

EOF is reported only after a read attempts to go past the available input. In
a terminal, send EOF with `Ctrl+D` on macOS/Linux, or `Ctrl+Z` followed by
Enter on Windows.

## sscanf

REPL
Expand Down
14 changes: 12 additions & 2 deletions lib/gets.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ var bytesRead;

var createGets = function(customFs) {
var localFs = customFs || fs;
var eof = false;

return function() {
var read = function() {
var fd = process.stdin.fd;
bytesRead = 0;
eof = false;

try {
bytesRead = localFs.readSync(fd, buf, 0, BUFSIZE);
Expand All @@ -29,6 +31,7 @@ var createGets = function(customFs) {
} else if (e.code === 'EOF') {
// Happens on Windows 7, but not OS X 10.8.3:
// simply signals the end of *piped* stdin input.
eof = true;
return '';
}
throw e; // unexpected exception
Expand All @@ -40,14 +43,21 @@ var createGets = function(customFs) {
// of input is signaled.
// Windows 7: this is how the end of input is signaled for
// *interactive* stdin input.
eof = true;
return '';
}
// Process the chunk read.

var content = buf.toString(undefined, 0, bytesRead - 1);
var content = buf.toString(undefined, 0, bytesRead).replace(/\r?\n$/, '');

return content;
};

read.isEOF = function() {
return eof;
};

return read;
};

module.exports = createGets();
Expand Down
6 changes: 6 additions & 0 deletions lib/scanf.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ declare namespace __node_scanf {
------------------------------------------------------------ */
namespace scanf {

/**
* Returns whether the most recent scanf operation attempted to read from
* stdin and reached the end of the input stream.
*/
function isEOF(): boolean;

/**
* <p> Reads formatted data from string. </p>
*
Expand Down
7 changes: 7 additions & 0 deletions lib/scanf.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ var scanf = (module.exports = function(format) {

scanf.throw = true;

/**
* Returns whether the most recent scanf read reached the end of stdin.
*/
scanf.isEOF = function() {
return stdin_flag && gets.isEOF();
};

module.exports.sscanf = function(str, format) {
if (typeof str !== 'string' || !str.length) {
return null;
Expand Down
140 changes: 140 additions & 0 deletions test/eof.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
var childProcess = require('child_process');
var path = require('path');
var should = require('should');

describe('scanf', function() {
describe('#isEOF', function() {
var modulePath = path.resolve(__dirname, '..');

function runWithInput(input, statements) {
var script =
'var scanf = require(' + JSON.stringify(modulePath) + ');' + statements;
var result = childProcess.spawnSync(process.execPath, ['-e', script], {
input: input
});

if (result.status !== 0) {
throw new Error(result.stderr.toString());
}

return JSON.parse(result.stdout.toString());
}

it('should be false before stdin is read', function() {
var result = runWithInput(
'',
'process.stdout.write(JSON.stringify(scanf.isEOF()));'
);

should.strictEqual(result, false);
});

it('should become true after reading past complete input', function() {
var result = runWithInput(
'1 2 3\n',
'var values = scanf("%d %d %d");' +
'var afterValues = scanf.isEOF();' +
'var eofValues = scanf("%d %d %d");' +
'process.stdout.write(JSON.stringify({' +
'values: values,' +
'afterValues: afterValues,' +
'eofValues: eofValues,' +
'afterEOF: scanf.isEOF()' +
'}));'
);

should.deepEqual(result, {
values: [1, 2, 3],
afterValues: false,
eofValues: [null, null, null],
afterEOF: true
});
});

it('should preserve the final value when input has no trailing newline', function() {
var result = runWithInput(
'1 2 3',
'var values = scanf("%d %d %d");' +
'process.stdout.write(JSON.stringify({' +
'values: values,' +
'eof: scanf.isEOF()' +
'}));'
);

should.deepEqual(result, {
values: [1, 2, 3],
eof: false
});
});

it('should distinguish a format mismatch from EOF', function() {
var result = runWithInput(
'not-a-record\n',
'var value = scanf("value=%d");' +
'process.stdout.write(JSON.stringify({' +
'value: value,' +
'eof: scanf.isEOF()' +
'}));'
);

should.deepEqual(result, {
value: null,
eof: false
});
});

it('should report EOF reached while reading an incomplete record', function() {
var result = runWithInput(
'1 2\n',
'var values = scanf("%d %d %d");' +
'process.stdout.write(JSON.stringify({' +
'values: values,' +
'eof: scanf.isEOF()' +
'}));'
);

should.deepEqual(result, {
values: [1, 2, null],
eof: true
});
});

it('should remain true across repeated reads after EOF', function() {
var result = runWithInput(
'',
'scanf("%d");' +
'var first = scanf.isEOF();' +
'scanf("%d");' +
'process.stdout.write(JSON.stringify({' +
'first: first,' +
'second: scanf.isEOF()' +
'}));'
);

should.deepEqual(result, {
first: true,
second: true
});
});

it('should not report stdin EOF after sscanf', function() {
var result = runWithInput(
'',
'scanf("%d");' +
'var before = scanf.isEOF();' +
'var value = scanf.sscanf("42", "%d");' +
'process.stdout.write(JSON.stringify({' +
'before: before,' +
'value: value,' +
'after: scanf.isEOF()' +
'}));'
);

should.deepEqual(result, {
before: true,
value: 42,
after: false
});
});
});
});
60 changes: 60 additions & 0 deletions test/gets.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('gets', function() {

var result = gets();
should.strictEqual(result, '');
should.strictEqual(gets.isEOF(), true);
});

it('should return empty string when EOF is reached', function() {
Expand All @@ -44,6 +45,7 @@ describe('gets', function() {

var result = gets();
should.strictEqual(result, '');
should.strictEqual(gets.isEOF(), true);
});

it('should read and trim newline-terminated input', function() {
Expand All @@ -59,6 +61,64 @@ describe('gets', function() {

var result = gets();
should.strictEqual(result, 'hello');
should.strictEqual(gets.isEOF(), false);
});

it('should not treat an empty line as EOF', function() {
var gets = loadGetsWith({
readSync: function(fd, buffer) {
buffer.write('\n');
return 1;
}
});

var result = gets();
should.strictEqual(result, '');
should.strictEqual(gets.isEOF(), false);
});

it('should preserve input that does not end with a newline', function() {
var gets = loadGetsWith({
readSync: function(fd, buffer) {
buffer.write('hello');
return 5;
}
});

var result = gets();
should.strictEqual(result, 'hello');
should.strictEqual(gets.isEOF(), false);
});

it('should trim a trailing CRLF sequence', function() {
var gets = loadGetsWith({
readSync: function(fd, buffer) {
buffer.write('hello\r\n');
return 7;
}
});

var result = gets();
should.strictEqual(result, 'hello');
should.strictEqual(gets.isEOF(), false);
});

it('should reset EOF state after a successful read', function() {
var reads = 0;
var gets = loadGetsWith({
readSync: function(fd, buffer) {
reads++;
if (reads === 1) return 0;

buffer.write('hello\n');
return 6;
}
});

should.strictEqual(gets(), '');
should.strictEqual(gets.isEOF(), true);
should.strictEqual(gets(), 'hello');
should.strictEqual(gets.isEOF(), false);
});

it('should not call openSync (prevents fd leak #46)', function() {
Expand Down
Loading