Skip to content

Commit

Permalink
Several changes.
Browse files Browse the repository at this point in the history
- retry is now true by default
- if retry is true, error messages are automatically printed
- allow empty passwords
- correctly pass input and output to read()
  • Loading branch information
satazor committed Jan 28, 2013
1 parent a292e00 commit af4bfd3
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 72 deletions.
45 changes: 21 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,20 @@ Note that the `options` argument is optional for all the commands.

### .prompt(message, opts, fn) ###

Prompts for a value, printing the `message` and waiting for the input.
Prompts for a value, printing the `message` and waiting for the input.
When done, calls `fn` with `error` and `value`.

Default options:
```js
{
// The default value. If not supplied, the input is mandatory
'default': 'default value',
'default': null,
// Automatically trim the input
'trim': true,
// A validator or an array of validators.
'validator': null,
// Automatically retry if a validator fails
'retry': false,
'retry': true,
// Do not print what the user types
'silent': false,
// Input and output streams to read and write to
Expand Down Expand Up @@ -64,6 +64,7 @@ promptly.prompt('Name: ', function (err, value) {
```

Ask for a name with a constraint (non-empty value and length > 2):

```js
var validator = function (value) {
if (value.length < 2) {
Expand All @@ -73,19 +74,13 @@ var validator = function (value) {
return value;
};

promptly.prompt('Name: ', { validator: validator }, function (err, value) {
if (err) {
console.error('Invalid name');
// Manually call retry
// The passed error has a retry method to easily prompt again.
return err.retry();
}

promptly.prompt('Name: ', { validator: validator , retry: true}, function (err, value) {
// err is always null because promptly will be prompting for a name until it validates
console.log('Name is:', value);
});
```

Same as above but retry automatically:
Same as above but do not retry automatically:

```js
var validator = function (value) {
Expand All @@ -96,21 +91,25 @@ var validator = function (value) {
return value;
};

promptly.prompt('Name: ', { validator: validator , retry: true}, function (err, value) {
// err is always null because promptly will be prompting for a name until it validates
promptly.prompt('Name: ', { validator: validator, retry: false }, function (err, value) {
if (err) {
console.error('Invalid name:', e.message);
// Manually call retry
// The passed error has a retry method to easily prompt again.
return err.retry();
}

console.log('Name is:', value);
});
```


### .confirm(message, opts, fn) ###

Ask the user to confirm something.
Ask the user to confirm something.
Calls `fn` with `error` and `value` (true or false).

The available options are the same, except that `retry` defaults to `true`.
Truthy values are: `y`, `yes` and `1`.
Falsy values are `n`, `no`, and `0`.
Truthy values are: `y`, `yes` and `1`.
Falsy values are `n`, `no`, and `0`.
Comparison is made in a case insensitive way.

Example usage:
Expand All @@ -124,11 +123,9 @@ promptly.confirm('Are you sure? ', function (err, value) {

### .choose(message, choices, opts, fn) ###

Ask the user to choose between multiple `choices` (array of choices).
Ask the user to choose between multiple `choices` (array of choices).
Calls `fn` with `error` and `value` (true or false).

The available options are the same, except that `retry` defaults to `true`.

Example usage:

```js
Expand All @@ -140,10 +137,10 @@ promptly.choose('Do you want an apple or an orange? ', ['apple', 'orange'], func

### .password(message, opts, fn) ###

Prompts for a password, printing the `message` and waiting for the input.
Prompts for a password, printing the `message` and waiting for the input.
When available, calls `fn` with `error` and `value`.

The available options are the same, except that `trim` and `silent` default to `false`.
The available options are the same, except that `trim` and `silent` default to `false` and `default` is an empty string (to allow empty passwords).

Example usage:

Expand Down
33 changes: 19 additions & 14 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@ promptly.prompt = function (message, opts, fn) {
if (opts.trim === undefined) {
opts.trim = true;
}
if (opts.retry === undefined) {
opts.retry = true;
}

// Setup read's options
var readOpts = {
prompt: message,
stdin: opts.input || process.stdin,
stdout: opts.output || process.stdout,
input: opts.input || process.stdin,
output: opts.output || process.stdout,
silent: opts.silent
};

// Use readline question
read(readOpts, function (err, response) {
read(readOpts, function (err, data) {
// Ignore the error attribute
// It is set on SIGINT or if timeout reached (we are not using timeout)
if (err) {
Expand All @@ -34,14 +37,14 @@ promptly.prompt = function (message, opts, fn) {

// Trim?
if (opts.trim) {
response = response.trim();
data = data.trim();
}

// Mandatory?
if (opts['default'] === undefined && !response) {
if (opts['default'] == null && !data) {
return promptly.prompt(message, opts, fn);
} else {
response = response || opts['default'];
data = data || opts['default'];
}

// Validator verification
Expand All @@ -55,10 +58,14 @@ promptly.prompt = function (message, opts, fn) {

for (x = 0; x < length; x += 1) {
try {
response = opts.validator[x](response);
data = opts.validator[x](data);
} catch (e) {
// Retry automatically if the retry option is enabled
if (opts.retry) {
if (e.message) {
readOpts.output.write(e.message + '\n');
}

return promptly.prompt(message, opts, fn);
}

Expand All @@ -70,7 +77,7 @@ promptly.prompt = function (message, opts, fn) {
}

// Everything ok
fn(null, response);
fn(null, data);
});
};

Expand All @@ -90,6 +97,9 @@ promptly.password = function (message, opts, fn) {
if (opts.trim === undefined) {
opts.trim = false;
}
if (opts['default'] === undefined) {
opts['default'] = '';
}

// Use prompt()
promptly.prompt(message, opts, fn);
Expand Down Expand Up @@ -128,7 +138,7 @@ promptly.confirm = function (message, opts, fn) {
return false;
}

return value;
throw new Error();
};
opts.validator.push(validator);

Expand All @@ -150,11 +160,6 @@ promptly.choose = function (message, choices, opts, fn) {
opts.validator = [opts.validator];
}

// Set the default options
if (opts.retry === undefined) {
opts.retry = true;
}

// Push the choice validator
var validator = function (value) {
if (choices.indexOf(value) === -1) {
Expand Down
84 changes: 50 additions & 34 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('prompt()', function () {
return value;
};

promptly.prompt('something: ', { validator: validator }, function (err, value) {
promptly.prompt('something: ', { validator: validator, retry: false }, function (err, value) {
expect(err).to.be(null);
expect(value).to.be('yeaa');
expect(stdout).to.contain('something: ');
Expand All @@ -87,29 +87,52 @@ describe('prompt()', function () {
process.stdin.emit('data', ' yeaa \n');
});

it('should give error if the validator fails', function (next) {
it('should assume values from the validator', function (next) {
stdout = '';

var validator = function () { throw new Error('bla'); };
var validator = function () { return 'bla'; };

promptly.prompt('something: ', { validator: validator }, function (err) {
expect(err).to.be.an(Error);
expect(err.message).to.be('bla');
promptly.prompt('something: ', { validator: validator }, function (err, value) {
expect(err).to.be(null);
expect(value).to.be('bla');
expect(stdout).to.contain('something: ');
next();
});

process.stdin.emit('data', ' yeaa \n');
});

it('should assume values from the validator', function (next) {
it('should automatically retry if a validator fails by default', function (next) {
stdout = '';

var validator = function () { return 'bla'; };
var validator = function (value) {
if (value !== 'yeaa') {
throw new Error('bla');
}

promptly.prompt('something: ', { validator: validator }, function (err, value) {
expect(err).to.be(null);
expect(value).to.be('bla');
return value;
};

promptly.prompt('something: ', { validator: validator, retry: true }, function (err, value) {
expect(stdout).to.contain('something: ');
expect(stdout.indexOf('something')).to.not.be(stdout.lastIndexOf('something'));
expect(stdout).to.contain('bla');
expect(value).to.equal('yeaa');
next();
});

process.stdin.emit('data', 'wtf\n');
process.stdin.emit('data', 'yeaa\n');
});

it('should give error if the validator fails and retry is false', function (next) {
stdout = '';

var validator = function () { throw new Error('bla'); };

promptly.prompt('something: ', { validator: validator, retry: false }, function (err) {
expect(err).to.be.an(Error);
expect(err.message).to.be('bla');
expect(stdout).to.contain('something: ');
next();
});
Expand All @@ -129,7 +152,7 @@ describe('prompt()', function () {
},
times = 0;

promptly.prompt('something: ', { validator: validator }, function (err, value) {
promptly.prompt('something: ', { validator: validator, retry: false }, function (err, value) {
times++;

if (times === 1) {
Expand All @@ -147,27 +170,6 @@ describe('prompt()', function () {
process.stdin.emit('data', 'wtf\n');
});

it('should automatically retry if a validator fails and retry is enabled', function (next) {
stdout = '';

var validator = function (value) {
if (value !== 'yeaa') {
throw new Error('bla');
}

return value;
};

promptly.prompt('something: ', { validator: validator, retry: true }, function (err, value) {
expect(stdout).to.contain('something: ');
expect(stdout.indexOf('something')).to.not.be(stdout.lastIndexOf('something'));
expect(value).to.equal('yeaa');
next();
});

process.stdin.emit('data', 'wtf\n');
process.stdin.emit('data', 'yeaa\n');
});
});

describe('choose()', function () {
Expand All @@ -179,6 +181,7 @@ describe('choose()', function () {
expect(value).to.be('orange');
expect(stdout).to.contain('apple or orange: ');
expect(stdout.indexOf('apple or orange')).to.not.be(stdout.lastIndexOf('apple or orange'));
expect(stdout).to.contain('Invalid choice');
next();
});

Expand Down Expand Up @@ -260,7 +263,7 @@ describe('confirm()', function () {

promptly.confirm('yes or no: ', { retry: false }, function (err) {
expect(err).to.be.an(Error);
expect(err.message).to.contain('choice');
expect(err.message).to.not.contain('Invalid choice');
expect(stdout).to.contain('yes or no: ');
next();
});
Expand Down Expand Up @@ -297,4 +300,17 @@ describe('password()', function () {

process.stdin.emit('data', ' yeaa \n');
});

it('show allow empty passwords by default', function (next) {
stdout = '';

promptly.password('something: ', function (err, value) {
expect(value).to.be('');
expect(stdout).to.contain('something: ');

next();
});

process.stdin.emit('data', '\n');
});
});

0 comments on commit af4bfd3

Please sign in to comment.