Created
July 8, 2011 12:06
-
-
Save sfoster/1071692 to your computer and use it in GitHub Desktop.
Wizard-style prompts from node.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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