terminal-forms.js
This commit is contained in:
parent
dccaa96dea
commit
7690280f7a
106
README.md
Normal file
106
README.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
Terminal Forms (js)
|
||||||
|
==============
|
||||||
|
|
||||||
|
You give it a TTY, it gives you the best form-handling that it knows how!
|
||||||
|
|
||||||
|
```
|
||||||
|
var form = require('terminal-forms.js').create(process.stdin, process.stdout);
|
||||||
|
```
|
||||||
|
|
||||||
|
Input types presently supported:
|
||||||
|
|
||||||
|
* `form.ask(prompt, handlers)`
|
||||||
|
* i.e. `form.ask("What is your quest? ", form.inputs.text).then(fn);`
|
||||||
|
* `form.setStatus(msg)`
|
||||||
|
* i.e. `form.setStatus("(hint: you seek the Grail!)")`
|
||||||
|
* `form.inputs`
|
||||||
|
* `.text` (no constraints)
|
||||||
|
* `.email` (checks format and looks up MX records)
|
||||||
|
* `.url` (checks format and looks up A/AAAA/CNAME records)
|
||||||
|
|
||||||
|
Handlers
|
||||||
|
========
|
||||||
|
|
||||||
|
A handler may implement any or all of these interfaces:
|
||||||
|
|
||||||
|
* `onReturnAsync(rs, ws, input, ch)`
|
||||||
|
* `onDebounceAsync(rs, ws, input, ch)`
|
||||||
|
|
||||||
|
The follow options may also be specified:
|
||||||
|
|
||||||
|
* `debounceTimeout: ms`
|
||||||
|
|
||||||
|
```
|
||||||
|
{ onReturnAsync: function (rs, ws, input, ch) {
|
||||||
|
// 1. pause the read stream if needed
|
||||||
|
|
||||||
|
// 2. the write stream is given as a convenience for clearing the newline, etc
|
||||||
|
|
||||||
|
// 3. check that input as a whole is valid
|
||||||
|
|
||||||
|
// 4. check the most recent character, if desired
|
||||||
|
|
||||||
|
// 5. normalize the input if desired (i.e. John.Doe@GMail.com -> john.doe@gmail.com)
|
||||||
|
|
||||||
|
// You can error out
|
||||||
|
// return form.PromiseA.reject(new Error("[X] This isn't an email address: no '@'"));
|
||||||
|
|
||||||
|
return input.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
, onDebounceAsync: function (rs, ws, input, ch) {
|
||||||
|
// Do a check on the input after 300ms without waiting for the return character
|
||||||
|
|
||||||
|
// return true if the input is complete
|
||||||
|
|
||||||
|
return false; // otherwise the input is not complete
|
||||||
|
}
|
||||||
|
, debounceTimeout: 300 // default is 300
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
```
|
||||||
|
, onCharAsync: function (rs, ws, input, ch) {
|
||||||
|
// the same as debounceTimeout 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Debugging
|
||||||
|
=========
|
||||||
|
|
||||||
|
### How to detect a pipe
|
||||||
|
|
||||||
|
Your run-of-the-mill bash scripts will not work if you require user input.
|
||||||
|
|
||||||
|
You can check to see if input or output is being handled by a pipe by checking the `isTTY` property.
|
||||||
|
|
||||||
|
* `process.stdin.isTTY`
|
||||||
|
* `process.stdout.isTTY`
|
||||||
|
|
||||||
|
```
|
||||||
|
node example.js
|
||||||
|
stdin.isTTY: true
|
||||||
|
stdout.isTTY: true
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
node bin/oauth3.js | grep ''
|
||||||
|
stdin.isTTY: true
|
||||||
|
stdout.isTTY: undefined
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
echo 'hello' | node bin/oauth3.js
|
||||||
|
stdin.isTTY: undefined
|
||||||
|
stdout.isTTY: true
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
echo 'hello' | node bin/oauth3.js | grep ''
|
||||||
|
stdin.isTTY: undefined
|
||||||
|
stdout.isTTY: undefined
|
||||||
|
```
|
35
index.js
35
index.js
@ -160,7 +160,7 @@ var form = {
|
|||||||
|
|
||||||
var debouncer = {
|
var debouncer = {
|
||||||
set: function () {
|
set: function () {
|
||||||
if (!cbs.onDebounce) {
|
if (!(cbs.onDebounceAsync||cbs.onDebounce)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,8 +172,18 @@ var form = {
|
|||||||
|
|
||||||
debouncer._timeout = setTimeout(function () {
|
debouncer._timeout = setTimeout(function () {
|
||||||
rrs.pause();
|
rrs.pause();
|
||||||
return cbs.onDebounce(ws._input.join(''), ch).then(function () {
|
return (cbs.onDebounceAsync||cbs.onDebounce)(ws._input.join(''), ch).then(function (result) {
|
||||||
rrs.resume();
|
rrs.resume();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.complete) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.input;
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
var errmsg = colors.red(err.message);
|
var errmsg = colors.red(err.message);
|
||||||
form.setStatus(rrs, ws, errmsg);
|
form.setStatus(rrs, ws, errmsg);
|
||||||
@ -182,6 +192,9 @@ var form = {
|
|||||||
});
|
});
|
||||||
}, cbs.debounceTimeout || 300);
|
}, cbs.debounceTimeout || 300);
|
||||||
}
|
}
|
||||||
|
, clear: function () {
|
||||||
|
clearTimeout(debouncer._timeout);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function callback() {
|
function callback() {
|
||||||
@ -190,7 +203,7 @@ var form = {
|
|||||||
|
|
||||||
rrs.pause();
|
rrs.pause();
|
||||||
|
|
||||||
cbs.onReturnAsync(rrs, ws, ws._input.join(''), ch).then(function () {
|
(cbs.onReturnAsync||cbs.onReturn)(rrs, ws, ws._input.join(''), ch).then(function (normalInput) {
|
||||||
ws.write('\n');
|
ws.write('\n');
|
||||||
ws.clearLine(); // person just hit enter, they are on the next line
|
ws.clearLine(); // person just hit enter, they are on the next line
|
||||||
// (and this will clear the status, if any)
|
// (and this will clear the status, if any)
|
||||||
@ -200,7 +213,7 @@ var form = {
|
|||||||
var input = ws._input.join('');
|
var input = ws._input.join('');
|
||||||
ws._input = [];
|
ws._input = [];
|
||||||
ws._inputIndex = 0;
|
ws._inputIndex = 0;
|
||||||
resolve({ input: input });
|
resolve({ input: normalInput || input });
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
rrs.on('data', onData);
|
rrs.on('data', onData);
|
||||||
|
|
||||||
@ -214,7 +227,6 @@ var form = {
|
|||||||
function onData(chunk) {
|
function onData(chunk) {
|
||||||
var ch = chunk.toString('ascii');
|
var ch = chunk.toString('ascii');
|
||||||
var x;
|
var x;
|
||||||
debouncer.set();
|
|
||||||
|
|
||||||
if (CTRL_C === ch) {
|
if (CTRL_C === ch) {
|
||||||
console.log("");
|
console.log("");
|
||||||
@ -229,12 +241,15 @@ var form = {
|
|||||||
case LF:
|
case LF:
|
||||||
case "\n\r":
|
case "\n\r":
|
||||||
case "\r":
|
case "\r":
|
||||||
|
debouncer.clear();
|
||||||
callback();
|
callback();
|
||||||
break;
|
break;
|
||||||
case BKSP:
|
case BKSP:
|
||||||
case WIN_BKSP:
|
case WIN_BKSP:
|
||||||
case ARROW_LEFT:
|
case ARROW_LEFT:
|
||||||
case ARROW_RIGHT:
|
case ARROW_RIGHT:
|
||||||
|
debouncer.clear();
|
||||||
|
// Position-control and delete characters are handled by write wrapper
|
||||||
ws.write(ch);
|
ws.write(ch);
|
||||||
break;
|
break;
|
||||||
case ARROW_UP: // TODO history, show pass
|
case ARROW_UP: // TODO history, show pass
|
||||||
@ -245,6 +260,7 @@ var form = {
|
|||||||
// TODO auto-complete
|
// TODO auto-complete
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
debouncer.set();
|
||||||
form.setStatus(rrs, ws, colors.dim(
|
form.setStatus(rrs, ws, colors.dim(
|
||||||
"inputIndex: " + ws._inputIndex
|
"inputIndex: " + ws._inputIndex
|
||||||
+ " input:" + ws._input.join('')
|
+ " input:" + ws._input.join('')
|
||||||
@ -300,7 +316,12 @@ var form = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var inputs = {
|
var inputs = {
|
||||||
email: {
|
text: {
|
||||||
|
onReturnAsync: function (rrs, ws, str) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, email: {
|
||||||
onReturnAsync: function (rrs, ws, str) {
|
onReturnAsync: function (rrs, ws, str) {
|
||||||
str = str.trim();
|
str = str.trim();
|
||||||
var dns = PromiseA.promisifyAll(require('dns'));
|
var dns = PromiseA.promisifyAll(require('dns'));
|
||||||
@ -347,7 +368,7 @@ var inputs = {
|
|||||||
form.setStatus(rrs, ws, colors.blue("testing `dig A '" + urlObj.hostname + "'` ... "));
|
form.setStatus(rrs, ws, colors.blue("testing `dig A '" + urlObj.hostname + "'` ... "));
|
||||||
|
|
||||||
return dns.resolveAsync(urlObj.hostname).then(function () {
|
return dns.resolveAsync(urlObj.hostname).then(function () {
|
||||||
return;
|
return str;
|
||||||
}, function () {
|
}, function () {
|
||||||
return PromiseA.reject(new Error("[X] '" + urlObj.hostname + "' doesn't look right (dns lookup failed)"));
|
return PromiseA.reject(new Error("[X] '" + urlObj.hostname + "' doesn't look right (dns lookup failed)"));
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user