Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit b56a7724fb47f04d004634402a23f301bbbd7b2b 0 parents
Nebojsa Sabovic authored
Showing with 196 additions and 0 deletions.
  1. +1 −0  AUTHORS
  2. +57 −0 index.js
  3. +13 −0 package.json
  4. +125 −0 readme.md
1  AUTHORS
@@ -0,0 +1 @@
+Nebojsa Sabovic <nsabovic@linkedin.com>
57 index.js
@@ -0,0 +1,57 @@
+var child_process = require('child_process'),
+ crypto = require('crypto'),
+ path = require('path');
+
+function subst(cmd, keys) {
+ if (keys) {
+ return cmd.replace(/\{(\w+)\}/g, function(match, item) {
+ return keys[item];
+ });
+ } else {
+ return cmd;
+ }
+}
+
+module.exports.logger = console.log.bind(console);
+
+module.exports.tmpname = function tmpname(pattern) {
+ var tmpbase = process.env['TMPDIR'] || '/tmp';
+ var tmpfile = pattern +
+ crypto.randomBytes(6).toString('base64').replace('/', '-');
+ return path.resolve(tmpbase, tmpfile);
+}
+
+module.exports.run = function run(cmd, keys) {
+ return function (/* ..., callback */) {
+ var callback = arguments[arguments.length - 1];
+ cmd = subst(cmd, keys);
+ module.exports.logger('executing: ' + cmd);
+ child_process.exec(cmd, callback);
+ };
+}
+
+module.exports.sync = function sync(f) {
+ return function (/* ..., callback */) {
+ var callback = arguments[arguments.length - 1];
+ try {
+ var value = f.apply(this, arguments);
+ } catch(e) {
+ return callback(e);
+ }
+ callback(null, value);
+ };
+}
+
+module.exports.cd = function cd(dir, keys) {
+ return module.exports.sync(function() {
+ dir = subst(dir, keys);
+ module.exports.logger('changing directory to: ' + dir);
+ process.chdir(dir);
+ });
+}
+
+module.exports.assign = function assign(key, keys) {
+ return module.exports.sync(function(stdout) {
+ keys[key] = stdout.trim();
+ });
+}
13 package.json
@@ -0,0 +1,13 @@
+{
+ "name": "shutil",
+ "version": "0.0.1",
+ "description": "Utilities for async shell scripting in node.js",
+ "keywords": ["shell", "scripting", "async"],
+ "repository" : {
+ "type": "git",
+ "url": "http://github.com/nsabovic/shutil.git"
+ },
+ "bugs": {"url": "https://github.com/nsabovic/shutil/issues"},
+ "engines": {"node": ">= 0.4.0"},
+ "os": ["!win32"]
+}
125 readme.md
@@ -0,0 +1,125 @@
+# Shutils
+
+## What?
+
+Helper functions for running shell commands from node.js.
+
+## Why?
+
+Obviously writing shell scripts is best done in shell. Except when you need some
+functionality that shell doesn't have, like parsing JSON, or using one of the
+gazillion modules available for node.
+
+## How?
+
+An dumbified version of a script that creates a tarfile for a project:
+
+ #!/usr/bin/env node
+ var fs = require('fs'),
+ async = require('async'),
+ sh = require('./shutil');
+
+ var vars = {
+ REPO: "git@myproject.com/repo.git",
+ VERSION: null,
+ CURDIR: process.cwd(),
+ TMPDIR: sh.tmpname('myproject'),
+ }
+
+ process.on('SIGINT', function() {}); // Just pass SIGINT down.
+
+ async.waterfall([
+ sh.run('mkdir {TMPDIR}', vars),
+ sh.cd('{TMPDIR}', vars),
+ sh.run('git clone --recursive {REPO} .', vars),
+ sh.run('git describe --tags'),
+ sh.assign('VERSION', vars),
+ sh.sync(function() {
+ var package = JSON.parse(fs.readFileSync('package.json'));
+ return package.engines.node.match(/\s*[=v]?\s*(\d+\.\d+\.\d+)/)[1];
+ }),
+ sh.assign('NODE_VERSION', vars),
+ sh.run('echo {NODE_VERSION} >NODE_VERSION', vars),
+ sh.run('find . -name .git\\* -print0 | xargs -0 rm -rf'),
+ sh.run('tar czf {CURDIR}/myproject-{VERSION}.tar.gz .', vars)],
+
+ function(err) {
+ async.series([sh.run('rm -rf {TMPDIR}', vars)], function() {
+ if (err) {
+ console.error(err);
+ process.exit(1);
+ } else {
+ console.log("success");
+ }
+ })
+ });
+
+## Methodology
+
+shutil provides a few functions that, when called, generate function which run
+a command and then call a callback with the result of that command. For example:
+
+ var f = sh.run('mkdir test');
+
+ f(function(err, stdout, stderr) {
+ if (err) {
+ console.error("Failed");
+ } else {
+ console.log("Result: " + stdout);
+ }
+ });
+
+sh.sync() can be used to stick in synchronous JavaScript code which is run
+inside try/catch.
+
+Let's say we want to use the result of one function in another. This won't work:
+
+ var value = false;
+ async.waterfall([
+ sh.run('rm test'),
+ sh.sync(function() { value = true; }),
+ sh.run('echo value is ' + value),
+ ]);
+
+It will always print false -- we need to evaluate value lazily. This is why
+all the functions support simple templating:
+
+ sh.run('echo {a} is {b}', { a : 'templating', b : 'awesome' });
+
+There's no escape characters, so if you need braces, stick them in vars, like:
+
+ sh.run('echo left brace: {L}, right one: {R}', { L : '{', R : '}' });
+
+## Functions
+
+`shutil.run(cmd[, vars])`
+
+Runs the given shell command.
+
+`shutil.cd(dir[, vars])`
+
+Changes the current working directory to dir.
+
+`shutil.sync(f)`
+
+Runs the given function synchronously, and call a callback afterwards. Only
+useful if your async library doesn't catch exceptions itself.
+
+`shutil.assign(keyname, vars)`
+
+Grab the first parameter out passed to it (stdout, if it were called by one of
+the other functions), trim it, and store it under keyname in vars.
+
+## Helper functions
+
+These do not generate other functions, just return values.
+
+`shutil.tmpname(prefix)`
+
+Generate a temporary filename (under $TMPDIR or /tmp) with given prefix.
+
+`shutil.logger`
+
+Set this variable to the logging function that will be called before each run
+and cd. Set it to `function(){}` to turn off logging. It's console.log by
+default.
Please sign in to comment.
Something went wrong with that request. Please try again.