Я играл с node.js, чтобы сгенерировать некоторые графики из нашего git-репозитория, что фактически означало объединение в кучу команд оболочки для предоставления мне данных репозитория в нужном мне формате.
Я смог сделать это, используя child_process, который поставляется с базовой библиотекой.
Первая версия выглядела так:
var exec = require('child_process').exec, _ = require("underscore"); ... function parseCommitsFromRepository(fn) { var gitRepository = "/tmp/core"; var gitPlayArea = "/tmp/" + new Date().getTime(); exec('cd ' + gitRepository + ' && git reset HEAD', function() { exec('git clone ' + gitRepository + ' ' + gitPlayArea, function() { exec('cd ' + gitPlayArea + ' && git log --pretty=format:"%H | %ad | %s%d" --date=raw', function(blank, gitEntries) { var commits = _(gitEntries.split("\n")).chain() .filter(function(item) { return item != ""; }) .map(function(item) { return item.split("|") }) .filter(function(theSplit) { return theSplit !== undefined && theSplit[1] !== undefined && theSplit[2] !== undefined; }) .map(function(theSplit) { var date = new Date(theSplit[1].trim().split(" ")[0]*1000); return {message: theSplit[2].trim(), date: date.toDateString(), time : date.toTimeString()}; }) .value(); fn(commits); }); }); }); }
В файле node.js используется модель асинхронного программирования, поэтому большую часть времени мы должны передавать обратные вызовы другим функциям, которые вызываются после завершения асинхронного вычисления.
В этом случае есть более зависимость порядка в parseCommitsFromRepository функции таким образом, что нам нужно , чтобы гнездиться второй вызов EXEC внутри обратного вызова от первого звонка.
то есть мы не хотим получать журнал хранилища до того, как клонируем хранилище в место, из которого мы пытаемся получить этот журнал.
По мере того, как вы создаете все больше и больше зависимостей порядка между асинхронными функциями, вложение становится больше, и код все больше перемещается в правую часть экрана.
Я натолкнулся на библиотеку Step, которая позволяет вам объединять функции и передавать результаты каждой из них следующей.
Я решил попробовать это в своем коде, и в итоге это выглядело так:
function parseCommitsFromRepository(fn) { var gitRepository = "/tmp/core"; var gitPlayArea = "/tmp/" + new Date().getTime(); Step( function getRepositoryUpToDate() { exec('cd ' + gitRepository + ' && git reset HEAD', this); }, function cloneRepository() { exec('git clone ' + gitRepository + ' ' + gitPlayArea, this); }, function getGitEntries() { exec('cd ' + gitPlayArea + ' && git log --pretty=format:"%H | %ad | %s%d" --date=raw', this); }, function handleResponse(blank, gitEntries) { var commits = _(gitEntries.split("\n")).chain() .filter(function(item) { return item != ""; }) .map(function(item) { return item.split("|") }) .filter(function(theSplit) { return theSplit !== undefined && theSplit[1] !== undefined && theSplit[2] !== undefined; }) .map(function(theSplit) { var date = new Date(theSplit[1].trim().split(" ")[0]*1000); return {message: theSplit[2].trim(), date: date.toDateString(), time : date.toTimeString()}; }) .value(); fn(commits); } ); }
Интересным побочным эффектом использования этого подхода является то, что мы можем описать, что делает каждый вызов exec, от имени функции, которая его выполняет.
Еще одна приятная вещь в этой библиотеке — то, что я могу легко обернуть эти функции внутри функции ведения журнала, если я хочу видеть на консоли, где процесс получил:
function log(message, fn) { return function logMe() { console.log(new Date().toString() + ": " + message); fn.apply(this, arguments); } }
function parseCommitsFromRepository(fn) { var gitRepository = "/tmp/core"; var gitPlayArea = "/tmp/" + new Date().getTime(); Step( log("Resetting repository", function getRepositoryUpToDate() { exec('cd ' + gitRepository + ' && git reset HEAD', this); }), log("Cloning repository", function cloneRepository() { exec('git clone ' + gitRepository + ' ' + gitPlayArea, this); }), log("Getting log", function getGitEntries() { exec('cd ' + gitPlayArea + ' && git log --pretty=format:"%H | %ad | %s%d" --date=raw', this); }), log("Processing log", function handleResponse(blank, gitEntries) { var commits = _(gitEntries.split("\n")).chain() .filter(function(item) { return item != ""; }) .map(function(item) { return item.split("|") }) .filter(function(theSplit) { return theSplit !== undefined && theSplit[1] !== undefined && theSplit[2] !== undefined; }) .map(function(theSplit) { var date = new Date(theSplit[1].trim().split(" ")[0]*1000); return {message: theSplit[2].trim(), date: date.toDateString(), time : date.toTimeString()}; }) .value(); fn(commits); }) ); }
Затем я получаю этот вывод при выполнении функции:
Sun Sep 11 2011 23:33:09 GMT+0100 (BST): Resetting repository Sun Sep 11 2011 23:33:11 GMT+0100 (BST): Cloning repository Sun Sep 11 2011 23:33:24 GMT+0100 (BST): Getting log Sun Sep 11 2011 23:33:24 GMT+0100 (BST): Processing log
Есть более крутые способы использования библиотеки Step на странице github — то, что я описал здесь, является лишь очень простым вариантом использования.
С http://www.markhneedham.com/blog/2011/09/11/learning-node-js-step/