Skip to content

Instantly share code, notes, and snippets.

@sfoster
Created July 8, 2011 12:06
Show Gist options
  • Save sfoster/1071692 to your computer and use it in GitHub Desktop.
Save sfoster/1071692 to your computer and use it in GitHub Desktop.
Wizard-style prompts from node.js
/**
* Module dependencies.
*/
var fs = require('fs'),
path = require('path');
spawn = require('child_process').spawn,
exec = require('child_process').exec;
var isListening = false,
currentFilename = null;
process.stdin.setEncoding('utf8');
var topics = {},
outDir = process.cwd();
// start listening for data events from stdin stream
process.stdin.on('data', function(data){
publish("/stdin/data", data);
});
function sanitize(str){
// not in use, using iconv instead
str = str.replace( /\u2018/g, "'" );
str = str.replace( /\u2019/g, "'" );
str = str.replace( /\u201c/g, '"' );
str = str.replace( /\u201d/g, '"' );
str = str.replace( /\u2013/g, '-' );
str = str.replace( /\u2014/g, '--' );
str = str.replace( /\uFFFD/g, "'" );
return str;
}
function publish(topic) {
var args = Array.prototype.slice.call(arguments, 1);
var subscribers = topics[topic] || [];
subscribers.forEach(function(fn){
fn.apply(null, args);
});
}
function touch(pathname, cb){
if(!path.existsSync(pathname)){
fs.writeFileSync(pathname, "", encoding='utf8');
console.log("created filename: ", pathname);
}
}
function subscribe(topic, fn) {
var subscribers = topics[topic] || (topics[topic] = []);
subscribers.push(fn);
return {
destroy: function(){
var idx = subscribers.indexOf(fn);
if(idx >= 0){
subscribers.splice(idx, 1);
}
}
};
}
var plan = {
title: {
source: 'stdinSingle',
preFn: function(data){
console.log("What's the title?");
},
postFn: function(data){
// console.log("got title: " + data);
var filename = data.toLowerCase().replace(/[^\w]+/g, '_');
filename = filename.replace(/_{2,}/g, '_');
filename = filename.replace(/^_|_$/, '');
filename += ".js";
currentFilename = path.normalize(path.join(outDir, filename));
console.log("creating file: " + currentFilename);
touch(outDir + "/" + filename);
},
next: 'precontent',
},
precontent: {
// prompt for confirmation before we look at the clipboard
// gives user opportunity to get the right stuff on there
source: 'stdinSingle',
preFn: function(data){
console.log("Pulling content from the clipboard, <ENTER> when ready");
},
next: 'content'
},
content: {
// grab clipboard content and slop it into the new file
source: 'clipboard',
postFn: function(data){
// var cleanstr = sanitize(data);
var cleanstr = data;
if(currentFilename){
fs.writeFileSync(currentFilename, cleanstr, encoding='utf8');
// console.log("wrote sanitized content back out to: " + currentFilename);
}
},
}
};
var input = {
// TODO: should be its own module
// get single-line input from stdin
stdinSingle: function (cb){
var dataHdl = subscribe("/stdin/data", function(str){
// console.log("/stdin/data subscriber: " + str);
process.stdin.pause();
dataHdl.destroy();
cb(str.replace(/^\s|\s+$/g, ''));
});
process.stdin.resume();
},
// TODO: had a bitch of a time getting multi-line input from stdin
// wanted to allow ^D to signal end of input, but that kills stdin
// and I can't seem to re-open it
// so.. instead, we'll go direct to the clipboard
clipboard: function(cb){
// source content from the (OSX) clipboard
// OSX-specific - we use the pbpaste utility to pull the content off the clipboard
// optional exec options:
// { encoding: 'utf8',
// timeout: 0,
// maxBuffer: 200*1024,
// killSignal: 'SIGTERM',
// cwd: null,
// env: null }
// pass pbpaste output through iconv to make sure we get plain ascii back
// it fixes curly quotes, ellipses and the kind of junk you often get
// when copy/pasting code from e.g. a blog or slide)
// prolly should be a separate step, but its easier to just pipe it here
// while we are calling exec anyhow
var proc = exec(
'pbpaste | iconv -c -f utf-8 -t ASCII//TRANSLIT',
function(error, stdout, stderr){
if(error){
console.log("Error returned from pbpaste exec call");
throw error;
} else {
// console.log("got data from pbpaste: " + stdout);
cb(stdout);
}
});
}
};
function step(defn) {
// handle each step in the plan, going through
// preFn, source and postFn
if(!defn) {
return;
}
defn.preFn && defn.preFn();
if(defn.source) {
if(!defn.source in input) {
throw new Error("No such input source: " + defn.source);
}
input[defn.source](function(data){
if(defn.postFn){
defn.postFn.apply(defn, arguments);
}
if(defn.next){
step(plan[defn.next]);
}
});
}
}
var app = module.exports = {};
if (!module.parent) {
// execute first step in the plan
step(plan.title);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment