From 59a50f697100a8044f6a9a85c6a985efcce7fff3 Mon Sep 17 00:00:00 2001 From: Phillip Gates-Idem Date: Wed, 11 Feb 2015 16:33:58 -0500 Subject: [PATCH] Initial commit --- .gitignore | 1 + .jshintrc | 39 ++++++++ LICENSE | 21 +++++ README.md | 113 +++++++++++++++++++++++ index.js | 242 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 29 ++++++ test/.jshintrc | 36 ++++++++ test/test.js | 176 +++++++++++++++++++++++++++++++++++ 8 files changed, 657 insertions(+) create mode 100644 .gitignore create mode 100644 .jshintrc create mode 100644 LICENSE create mode 100644 README.md create mode 100644 index.js create mode 100644 package.json create mode 100644 test/.jshintrc create mode 100644 test/test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..7ea1bff --- /dev/null +++ b/.jshintrc @@ -0,0 +1,39 @@ +{ + "browser": false, + "esnext": true, + "globals": { + "require": true, + "module": true, + "process": true, + "__dirname": true, + "Buffer": true, + "setTimeout": true, + "exports": true + }, + "strict": false, + "globalstrict": true, + "quotmark": true, + "smarttabs": true, + "trailing": true, + "unused": "vars", + "curly": true, + "debug": false, + "devel": false, + "eqeqeq": true, + "eqnull": true, + "evil": true, + "forin": false, + "immed": true, + "laxbreak": false, + "newcap": true, + "noarg": true, + "noempty": false, + "nonew": true, + "nomen": false, + "onevar": false, + "plusplus": false, + "undef": true, + "sub": true, + "white": false, + "latedef": "nofunc" +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6739a1b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Phillip Gates-Idem + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a8ec8fa --- /dev/null +++ b/README.md @@ -0,0 +1,113 @@ +task-list +=========== +Simple utility library for managing a list of tasks and their lifecycle. + +## Installation +```bash +npm install task-list --save +``` + +## Overview +A task list is created from an array of objects. Each object has a `start` +function and an optional `stop` function. Both of these functions should expect +a callback argument that should be invoked when the operation completes. +Operations can be called on the task list and if a logger was provided +in the config then the current activity will be logged via the given logger. + +## Usage +Simple example with minimal configuration: +```javascript +var serviceList = taskList.create([ + { + name: 'service1', + + start: function(callback) { + task1Started = true; + callback(); + }, + + stop: function(callback) { + task1Started = false; + callback(); + } + }, + + { + name: 'service2', + + start: function(callback) { + task2Started = true; + callback(); + }, + + stop: function(callback) { + task2Started = false; + callback(); + } + } +]); + +serviceList.startAll(function(err) { + if (err) { + // some error occurred + } else { + // services are running + } + + // now stop the services + serviceList.stopAll(function(err) { + if (err) { + // one or more services failed to stop + } else { + // all services stopped successfully + } + }); +}); +``` + +An example that provides a `logger`: +```javascript +var serviceList = taskList.create({ + logger: { + info: function(message) { + console.log('INFO: ' + message); + }, + + success: function(message) { + console.log('SUCCESS: ' + message); + }, + + error: function(message) { + console.error('ERROR: ' + message); + } + }, + + tasks: [ + { + name: 'task1', + + start: function(callback) { + // do something + callback(); + }, + + stop: function(callback) { + callback(); + } + }, + + { + name: 'task2', + + start: function(callback) { + // do something + callback(); + }, + + stop: function(callback) { + callback(); + } + } + ] +}); +``` diff --git a/index.js b/index.js new file mode 100644 index 0000000..3737fcf --- /dev/null +++ b/index.js @@ -0,0 +1,242 @@ +var series = require('raptor-async/series'); + +var NOOP = function() {}; + +var TaskState = exports.TaskState = { + STARTED: {}, + STOPPED: {}, + ERROR: {} +}; + +var TaskType = exports.TaskType = { + SERVICE: { + disabledMessage: function(task) { + return 'Service "' + task.name + '" is disabled.'; + }, + + startingMessage: function(task) { + return 'Starting service "' + task.name + '"...'; + }, + + startedMessage: function(task) { + return 'Service "' + task.name + '" started.'; + }, + + stoppingMessage: function(task) { + return 'Stopping service "' + task.name + '"...'; + }, + + stoppedMessage: function(task) { + return 'Service "' + task.name + '" stopped.'; + }, + + startErrorMessage: function(task) { + return 'Error starting service "' + task.name + '".'; + }, + + stopErrorMessage: function(task) { + return 'Error stopping service "' + task.name + '".'; + } + }, + TASK: { + disabledMessage: function(task) { + return 'Task "' + task.name + '" is disabled.'; + }, + + startingMessage: function(task) { + return 'Starting task "' + task.name + '"...'; + }, + + startedMessage: function(task) { + return 'Task "' + task.name + '" completed.'; + }, + + startErrorMessage: function(task) { + return 'Error running task "' + task.name + '".'; + }, + + stoppingMessage: function(task) { + return 'Stopping task "' + task.name + '"...'; + }, + + stoppedMessage: function(task) { + return 'Task "' + task.name + '" stopped.'; + }, + + stopErrorMessage: function(task) { + return 'Task "' + task.name + '" stopped.'; + } + } +}; + +function TaskList(options) { + /* jshint devel:true */ + + var tasks; + var logger; + if (Array.isArray(options)) { + tasks = options; + } else { + tasks = options.tasks; + logger = options.logger; + } + + this.tasks = tasks; + this.taskByNameMap = {}; + + if (logger) { + if (logger === true) { + logger = {}; + } + + logger.info = logger.info || console.log.bind(console); + logger.success = logger.success || console.log.bind(console); + logger.error = logger.error || console.error.bind(console); + } else { + logger = { + info: NOOP, + success: NOOP, + error: NOOP + }; + } + + this.logger = logger; + + for (var i = 0; i < tasks.length; i++) { + var task = tasks[i]; + + if (task.name) { + this.taskByNameMap[task.name] = task; + } else { + // Use index as name for display purposes + task.name = '#' + i; + } + + var typeName = task.type; + if (typeName) { + task.type = TaskType[typeName.toUpperCase()]; + if (!task.type) { + throw new Error('Invalid task type: "' + typeName + '". Should be one of: ' + Object.keys(TaskType).map(function(typeName) { + return typeName.toLowerCase(); + }).join(', ')); + } + } else { + task.type = TaskType.TASK; + } + + /*jshint loopfunc: true */ + ['start', 'stop'].forEach(function(property) { + var func = task[property]; + if (func && (func.length !== 1)) { + throw new Error('Task "' + task.name + '" has invalid "' + property + '" function. This function should accept one argument which is callback.'); + } + }); + } +} + +var TaskList_prototype = TaskList.prototype; + +TaskList_prototype.getTaskByName = function(name) { + return this.taskByNameMap[name]; +}; + +TaskList_prototype.startAll = function(callback) { + var logger = this.logger; + var work = []; + + var failures = []; + + this.tasks.forEach(function(task, index) { + if (task.disabled) { + logger.info(task.type.disabledMessage(task)); + return; + } + + work.push(function(callback) { + logger.info(task.type.startingMessage(task)); + task.start(function(startErr) { + if (startErr) { + task.state = TaskState.ERROR; + + var err = new Error(task.type.startErrorMessage(task)); + err.cause = startErr; + + failures.push({ + task: task, + err: startErr + }); + + callback(err); + } else { + task.state = TaskState.STARTED; + logger.success(task.type.startedMessage(task)); + callback(); + } + }); + }); + }); + + series(work, function(err) { + if (err) { + err.failures = failures; + callback(err); + } else { + callback(); + } + }); +}; + +TaskList_prototype.stopAll = function(callback) { + var logger = this.logger; + var work = []; + + var failures = []; + + this.tasks.forEach(function(task, index) { + + if (!task.stop || (task.state !== TaskState.STARTED)) { + return; + } + + work.push(function(callback) { + logger.info(task.type.stoppingMessage(task)); + task.stop(function(stopErr) { + if (stopErr) { + task.state = TaskState.ERROR; + + logger.error('Failed to stop "' + task.name + '". Error: ' + (stopErr.stack || stopErr)); + failures.push({ + task: task, + err: stopErr + }); + } else { + task.state = TaskState.STOPPED; + logger.success(task.type.stoppedMessage(task)); + callback(); + } + }); + }); + }); + + series(work, function(err) { + if (err) { + logger.error('Errors occurrred while stopping tasks.'); + return callback(err); + } + + if (failures.length > 0) { + err = new Error('Following tasks failed to stop: ' + failures.map(function(failure) { + return failure.task.name; + }).join(', ')); + + err.failures = failures; + return callback(err); + } + + callback(); + }); +}; + +exports.create = function(options) { + return new TaskList(options); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..91b9601 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "task-list", + "version": "1.0.0", + "description": "Simple utility library for managing a list of tasks and their lifecycle.", + "keywords": [ + "data", + "models", + "types", + "data-modeling", + "modeling" + ], + "homepage": "https://github.com/philidem/task-list", + "repository": { + "type": "git", + "url": "https://github.com/philidem/task-list.git" + }, + "author": "Phil Gates-Idem