diff --git a/.gitignore b/.gitignore index f356293..b785247 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,3 @@ -lib-cov -*.seed -*.log -*.csv -*.dat -*.out -*.pid -*.gz - -pids -logs -results - +node_modules npm-debug.log +tmp diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..6b4c1a9 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,14 @@ +{ + "curly": true, + "eqeqeq": true, + "immed": true, + "latedef": true, + "newcap": true, + "noarg": true, + "sub": true, + "undef": true, + "boss": true, + "eqnull": true, + "node": true, + "es5": true +} diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..e7bcac3 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,191 @@ +/* + * grunt-chmod + * https://github.com/JamesMGreene/grunt-chmod + * + * Copyright (c) 2013 James M. Greene + * Licensed under the MIT license. + */ + +'use strict'; + +module.exports = function(grunt) { + + // Project configuration. + grunt.initConfig({ + jshint: { + options: { + jshintrc: '.jshintrc', + }, + all: [ + 'Gruntfile.js', + 'tasks/**/*.js', + 'test/**/*.js', + ] + }, + + // Before generating any new files, remove any previously-created files. + clean: { + tests: ['tmp'], + }, + + // Configuration to be run (and then tested). + chmod: { + default_options: { + options: { + emit: true + }, + files: ['tmp/default_options.json'] + }, + custom_options_without_string_mode: { + options: { + mode: {}, + emit: true + }, + files: ['tmp/custom_options_without_string_mode.json'] + }, + custom_options_with_empty_mode: { + options: { + mode: '', + emit: true + }, + files: ['tmp/custom_options_with_empty_mode.json'] + }, + custom_options_with_invalid_mode: { + options: { + mode: '', + emit: true + }, + files: ['tmp/custom_options_with_invalid_mode.json'] + }, + custom_options_file: { + options: { + mode: 'go-rwx', + emit: true + }, + files: ['tmp/custom_options_file.json'] + }, + custom_options_dir: { + options: { + mode: 'go-rwx', + emit: true + }, + files: ['tmp/custom_options_dir/'] + } + }, + + // Unit tests. + nodeunit: { + all: ['test/*_test.js'] + }, + + + + }); + + // Actually load this plugin's task(s). + grunt.loadTasks('tasks'); + + // These plugins provide necessary tasks. + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-nodeunit'); + + var forceValue = (function(val) { + return (typeof val === 'boolean' ? val : !!val); + })(grunt.option('force')); + + // Enable force for tests to avoid the build halting on warnings + grunt.registerTask('force-enable', function() { + grunt.option('force', true); + }); + grunt.registerTask('force-revert', function() { + grunt.option('force', forceValue); + }); + + // Test setup + grunt.registerTask('test-setup', function() { + grunt.file.mkdir('tmp'); + }); + + // Test emission listeners + var args = (function() { + var slicer = Array.prototype.slice; + return function(argsObj) { + return slicer.call(argsObj, 0); + }; + })(); + var emissions = [], + taskTargetName; + grunt.registerTask('listeners-on', function() { + grunt.event.on('chmod.*', function(msg) { + if (this.event === 'chmod.taskTargetName') { + taskTargetName = msg; + return; + } + var emission = { + name: this.event, + args: args(arguments) + }; + emissions.push(emission); + }); + }); + grunt.registerTask('listeners-off', function() { + grunt.file.write('tmp/' + taskTargetName + '.json', JSON.stringify(emissions)); + + emissions.length = 0; + grunt.event.removeAllListeners('chmod.*'); + }); + + var badConfigOptions = [ + 'default_options', + 'custom_options_without_string_mode', + 'custom_options_with_empty_mode', + 'custom_options_with_invalid_mode' + ]; + badConfigOptions.forEach(function(e, i) { + grunt.registerTask( + 'test-prep-' + (i + 1), + [ + 'force-enable', + 'listeners-on', + 'chmod:' + e, + 'listeners-off', + 'force-revert' + ] + ); + }); + var goodConfigOptions = [ + 'custom_options_file', + 'custom_options_dir' + ]; + goodConfigOptions.forEach(function(e, i) { + grunt.registerTask( + 'test-prep-' + (badConfigOptions.length + i + 1), + [ + 'listeners-on', + 'chmod:' + e, + 'listeners-off' + ] + ); + }); + + // Whenever the "test" task is run, first clean the "tmp" dir, then run this plugin's + // task(s), then test the result(s). Do NOT clean the "tmp" dir at the end! + grunt.registerTask( + 'test', + (function() { + var taskList = []; + taskList.push('clean'); + taskList.push('test-setup'); + for (var i = 1, len = (badConfigOptions.length + goodConfigOptions.length + 1); i < len; i++) { + taskList.push('test-prep-' + i); + } + taskList.push('nodeunit'); + return taskList; + })() + ); + + // By default: lint, run all tests, and clean it up. + grunt.registerTask('default', ['jshint', /*'test',*/ 'clean']); + +}; diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..c2ccb02 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,22 @@ +Copyright (c) 2013 James M. Greene + +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 index 49c2ccf..9095a1c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,89 @@ -grunt-chmod -=========== +# grunt-chmod -A Grunt plugin to modify file permissions, i.e. like using `chmod`. \ No newline at end of file +> A Grunt task plugin to modify file permissions, a la `chmod`. + +## Getting Started +This plugin requires Grunt `~0.4.0` + +If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: + +```shell +npm install grunt-chmod --save-dev +``` + +One the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: + +```js +grunt.loadNpmTasks('grunt-chmod'); +``` + +## The "chmod" task + +### Overview +In your project's Gruntfile, add a section named `chmod` to the data object passed into `grunt.initConfig()`. + +```js +grunt.initConfig({ + chmod: { + options: { + // Task-specific options go here. + }, + your_target: { + // Target-specific file lists and/or options go here. + }, + }, +}) +``` + +### Options + +#### options.separator +Type: `String` +Default value: `', '` + +A string value that is used to do something with whatever. + +#### options.punctuation +Type: `String` +Default value: `'.'` + +A string value that is used to do something else with whatever else. + +### Usage Examples + +#### Default Options +In this example, the default options are used to do something with whatever. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result would be `Testing, 1 2 3.` + +```js +grunt.initConfig({ + chmod: { + options: {}, + files: { + 'dest/default_options': ['src/testing', 'src/123'], + }, + }, +}) +``` + +#### Custom Options +In this example, custom options are used to do something else with whatever else. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result in this case would be `Testing: 1 2 3 !!!` + +```js +grunt.initConfig({ + chmod: { + options: { + separator: ': ', + punctuation: ' !!!', + }, + files: { + 'dest/default_options': ['src/testing', 'src/123'], + }, + }, +}) +``` + +## Contributing +In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). + +## Release History +_(Nothing yet)_ diff --git a/package.json b/package.json new file mode 100644 index 0000000..138c0cd --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "grunt-chmod", + "description": "A Grunt task plugin to modify file permissions, a la `chmod`.", + "version": "0.1.0", + "homepage": "https://github.com/JamesMGreene/grunt-chmod", + "author": { + "name": "James M. Greene", + "email": "james.m.greene@gmail.com", + "url": "http://jamesgreene.net/" + }, + "repository": { + "type": "git", + "url": "git://github.com/JamesMGreene/grunt-chmod.git" + }, + "bugs": { + "url": "https://github.com/JamesMGreene/grunt-chmod/issues" + }, + "licenses": [ + { + "type": "MIT", + "url": "https://github.com/JamesMGreene/grunt-chmod/blob/master/LICENSE-MIT" + } + ], + "main": "Gruntfile.js", + "engines": { + "node": ">= 0.8.0" + }, + "scripts": { + "test": "grunt test" + }, + "devDependencies": { + "grunt-contrib-jshint": "~0.1.1", + "grunt-contrib-clean": "~0.4.0", + "grunt-contrib-nodeunit": "~0.1.2", + "grunt": "~0.4.0" + }, + "keywords": [ + "gruntplugin", + "chmod" + ] +} \ No newline at end of file diff --git a/tasks/chmod.js b/tasks/chmod.js new file mode 100644 index 0000000..725e850 --- /dev/null +++ b/tasks/chmod.js @@ -0,0 +1,109 @@ +/* + * grunt-chmod + * https://github.com/JamesMGreene/grunt-chmod + * + * Copyright (c) 2013 James M. Greene + * Licensed under the MIT license. + */ + +'use strict'; + +module.exports = function(grunt) { + + // Please see the Grunt documentation for more information regarding task + // creation: http://gruntjs.com/creating-tasks + + grunt.registerMultiTask('chmod', 'Modify file permissions, a la `chmod`.', function() { + // Merge task-specific and/or target-specific options with these defaults. + var options = this.options({ + mode: '', + emit: false + }); + + var shouldEmit = options.emit === true; + if (shouldEmit) { + grunt.event.emit('chmod.taskTargetName', this.target); + } + var logError = createLogErrorFunc(shouldEmit); + var taskFailure = createTaskFailureFunc(shouldEmit); + var taskSuccess = createTaskSuccessFunc(shouldEmit); + + var mode = options.mode; + + // If there isn't any mode to set, then bail out + if (!mode) { + logError('No `mode` was specified in the task `options`. Task failed!'); + return taskFailure(); + } + // If the mode set wasn't a string, then bail out + if (typeof mode !== 'string') { + logError('The `mode` specified in the task `options` was not a string. Task failed!'); + return taskFailure(); + } + + var fs = require('fs'); + var files = this.filesSrc; + + // Iterate over all specified file groups. + files.forEach(function(path) { + // Warn on and remove invalid source files (if nonull was set). + if (!grunt.file.exists(path)) { + logError('Source dir/file "' + path + '" not found.'); + } + + // Write the destination file. + try { + fs.chmodSync(path, mode); + } + catch (e) { + logError('Failed to set `chmod` mode "' + mode + '" on dir/file: ' + path); + } + }); + + // Fail task if errors were logged. + if (this.errorCount) { + return taskFailure(); + } + + // Otherwise, print a success message. + grunt.log.ok(files.length + ' file' + (files.length === 1 ? '' : 's') + ' had their `chmod` mode set to "' + mode + '".'); + return taskSuccess(); + }); + + var createLogErrorFunc = function(shouldEmit) { + if (shouldEmit) { + return function(errorMsg) { + grunt.event.emit('chmod.error', errorMsg); + grunt.log.error(errorMsg); + }; + } + return function(errorMsg) { + grunt.log.error(errorMsg); + }; + }; + + var createTaskFailureFunc = function(shouldEmit) { + if (shouldEmit) { + return function() { + grunt.event.emit('chmod.fail'); + return false; + }; + } + return function() { + return false; + }; + }; + + var createTaskSuccessFunc = function(shouldEmit) { + if (shouldEmit) { + return function() { + grunt.event.emit('chmod.success'); + return true; + }; + } + return function() { + return true; + }; + }; + +}; diff --git a/test/chmod_test.js b/test/chmod_test.js new file mode 100644 index 0000000..72103d5 --- /dev/null +++ b/test/chmod_test.js @@ -0,0 +1,80 @@ +'use strict'; + +var grunt = require('grunt'); + +/* + ======== A Handy Little Nodeunit Reference ======== + https://github.com/caolan/nodeunit + + Test methods: + test.expect(numAssertions) + test.done() + Test assertions: + test.ok(value, [message]) + test.equal(actual, expected, [message]) + test.notEqual(actual, expected, [message]) + test.deepEqual(actual, expected, [message]) + test.notDeepEqual(actual, expected, [message]) + test.strictEqual(actual, expected, [message]) + test.notStrictEqual(actual, expected, [message]) + test.throws(block, [error], [message]) + test.doesNotThrow(block, [error], [message]) + test.ifError(value) +*/ + +exports.chmod = { + setUp: function(done) { + // setup here if necessary + done(); + }, + + default_options: function(test) { + test.expect(1); + + var actual = grunt.file.readJSON('tmp/actual/default_options.json'); + var expected = grunt.file.readJSON('test/expected/default_options.json'); + test.deepEqual(actual, expected, 'Task should have failed when relying on the default options.'); + + test.done(); + }, + + custom_options_without_string_mode: function(test) { + test.expect(1); + + var actual = grunt.file.readJSON('tmp/actual/custom_options_without_string_mode.json'); + var expected = grunt.file.readJSON('test/expected/custom_options_without_string_mode.json'); + test.deepEqual(actual, expected, 'should describe what the custom option(s) behavior is.'); + + test.done(); + }, + + custom_options_with_empty_mode: function(test) { + test.expect(1); + + var actual = grunt.file.readJSON('tmp/actual/custom_options_with_empty_mode.json'); + var expected = grunt.file.readJSON('test/expected/custom_options_with_empty_mode.json'); + test.deepEqual(actual, expected, 'should describe what the custom option(s) behavior is.'); + + test.done(); + }, + + custom_options_with_invalid_mode: function(test) { + test.expect(1); + + var actual = grunt.file.readJSON('tmp/actual/custom_options_with_invalid_mode.json'); + var expected = grunt.file.readJSON('test/expected/custom_options_with_invalid_mode.json'); + test.deepEqual(actual, expected, 'should describe what the custom option(s) behavior is.'); + + test.done(); + }, + + custom_options: function(test) { + test.expect(1); + + var actual = grunt.file.readJSON('tmp/actual/custom_options.json'); + var expected = grunt.file.readJSON('test/expected/custom_options.json'); + test.deepEqual(actual, expected, 'should describe what the custom option(s) behavior is.'); + + test.done(); + } +}; diff --git a/test/expected/custom_options b/test/expected/custom_options new file mode 100644 index 0000000..e597128 --- /dev/null +++ b/test/expected/custom_options @@ -0,0 +1 @@ +Testing: 1 2 3 !!! \ No newline at end of file diff --git a/test/expected/default_options b/test/expected/default_options new file mode 100644 index 0000000..5f8b72f --- /dev/null +++ b/test/expected/default_options @@ -0,0 +1 @@ +Testing, 1 2 3. \ No newline at end of file