Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@davemo
Last active December 27, 2015 03:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davemo/7259064 to your computer and use it in GitHub Desktop.
Save davemo/7259064 to your computer and use it in GitHub Desktop.
wip directory watcher
# Exports an object that defines
# * all of the configuration needed by the projects'
# * depended-on grunt tasks.
# *
# * You can find the original parent object in: node_modules/lineman/config/application.coffee
path = require "path"
grunt = require "grunt"
# additional grunt plugins
npmTasksToLoad = [
"grunt-batman-templates"
"grunt-contrib-sass"
"grunt-contrib-copy"
"grunt-datauri"
"grunt-newer"
"grunt-parallel"
"grunt-concat-sourcemap"
"grunt-contrib-uglify"
"grunt-rails-asset-digest"
"grunt-grunticon"
]
appTasks = {
common: [
"batman_templates"
"datauri"
"grunticon"
"copy:svgicons"
"sass"
"newer:coffee"
"concat_sourcemap"
]
dev: [
"copy"
"facemelting_mindflaying_watcher_3000"
]
dist: [
"parallel"
"rails_asset_digest"
"clean"
]
}
if process.env.LINEMAN_AUTO_WATCH is "true"
appTasks.dev.push "watch"
unless process.env.NODE_ENV is "production"
npmTasksToLoad.push "grunt-contrib-imagemin"
module.exports =
pkg: grunt.file.readJSON("package.json")
# any other grunt tasks / plugins to load
loadNpmTasks: npmTasksToLoad
# sets up the lineman task lifecycle for admin2
appTasks: appTasks
# task configuration targets
grunticon:
svgicons:
options:
svgo: true
src: "<%= files.svgicons.src_path %>"
dest: "<%= files.svgicons.compiled_path %>"
cssprefix: "ico-"
# shopify branding / logo colors should go here as hex values
colors:
slate: "#526066"
green: "#96bf48"
blue: "#469ad1"
white: "#ffffff"
# designery tasks, not part of any lifecycles, available via `lineman grunt <taskname>`
imagemin:
all_images:
files: [
expand: true
src: ["app/assets/batman/admin2/resources/images/**/*.{png,jpg,gif}"]
dest: "."
]
# lifecycle tasks
clean:
admin:
src: ["generated"]
timestamps:
src: ["generated/timestamps"]
newer:
options:
timestamps: "generated/timestamps"
coffee:
admin:
expand: true
cwd: "<%= files.js.admin.src_path %>"
src: "**/*.coffee"
dest: "<%= files.js.admin.compiled_path %>"
ext: ".js"
spec:
expand: true
cwd: "<%= files.spec.src_path %>"
src: "**/*.coffee"
dest: "<%= files.spec.compiled_path %>"
ext: ".js"
spec_legacy:
expand: true
flatten: true
src: "<%= files.spec.coffee.legacy_watch %>"
dest: "<%= files.spec.compiled_path %>"
ext: ".js"
# inlines image resources as base64 encoded strings
# generates SCSS placeholder selectors, and variables
datauri:
options:
checkFilesize: false
variables:
options:
variables: true
classPrefix: "data-icon-"
src: "<%= files.datauri.src %>"
dest: "<%= files.datauri.variables %>"
placeholders:
options:
classPrefix: "data-icon-"
src: "<%= files.datauri.src %>"
dest: "<%= files.datauri.placeholders %>"
# compiles scss
sass:
admin:
options:
bundleExec: true
loadPath: ['app/assets/batman/admin2/resources/css']
files:
"<%= files.sass.app.compiled %>" : "<%= files.sass.app.src %>"
"<%= files.sass.rte.compiled %>" : "<%= files.sass.rte.src %>"
"<%= files.sass.print.compiled %>" : "<%= files.sass.print.src %>"
# batman template precache
batman_templates:
options:
templateFolder: "app/assets/batman/admin2/html"
files:
src: "<%= files.batman_views %>"
dest: "<%= files.batman_viewstore %>"
# concatenated bundles, 1 for vendor, 1 for app+batman template precache
concat_sourcemap:
options:
sourcesContent: true
vendor:
files:
"<%= files.js.vendor.concatenated %>": "<%= files.js.vendor.src %>"
admin:
files:
"<%= files.js.admin.concatenated %>": [
"<%= files.js.admin.compiled_src %>"
"<%= files.batman_viewstore %>"
]
spec:
files:
"<%= files.spec.concatenated %>": "<%= files.spec.src %>"
# minification task for js, only happens in the dist lifecycle
uglify:
options:
sourceMappingURL: (myPath) ->
extension = path.extname(myPath)
path.basename(myPath, ".min#{extension}") + extension + '.map'
admin:
options:
sourceMapIn: "<%= files.js.admin.sourcemap_in %>"
sourceMap: "<%= files.js.admin.sourcemap_out %>"
files:
"<%= files.js.admin.min %>" : "<%= files.js.admin.concatenated %>"
vendor:
options:
sourceMapIn: "<%= files.js.vendor.sourcemap_in %>"
sourceMap: "<%= files.js.vendor.sourcemap_out %>"
files:
"<%= files.js.vendor.min %>" : "<%= files.js.vendor.concatenated %>"
# minification task for css, only happens in the dist lifecycle
cssmin:
admin:
files:
"<%= files.sass.app.min %>" : "<%= files.sass.app.compiled %>"
"<%= files.sass.rte.min %>" : "<%= files.sass.rte.compiled %>"
"<%= files.sass.print.min %>" : "<%= files.sass.print.compiled %>"
parallel:
minification:
tasks: [
{ cmd: './node_modules/lineman/cli.js', args: ['grunt', 'uglify:admin'] }
{ cmd: './node_modules/lineman/cli.js', args: ['grunt', 'uglify:vendor'] }
{ cmd: './node_modules/lineman/cli.js', args: ['grunt', 'cssmin:admin'] }
]
rails_asset_digest:
compile:
files:
"<%= files.js.admin.dest %>" : "<%= files.js.admin.min %>"
"<%= files.js.vendor.dest %>" : "<%= files.js.vendor.min %>"
"<%= files.sass.app.dest %>" : "<%= files.sass.app.min %>"
"<%= files.sass.rte.dest %>" : "<%= files.sass.rte.min %>"
"<%= files.sass.print.dest %>" : "<%= files.sass.print.min %>"
"<%= files.js.admin.sourcemap_out %>" : "<%= files.js.admin.sourcemap_out %>"
"<%= files.js.vendor.sourcemap_out %>" : "<%= files.js.vendor.sourcemap_out %>"
# dev task to copy assets to rails public folder
copy:
svgicons:
files:
"<%= files.svgicons.dest %>" : "<%= files.svgicons.compiled %>"
sourcemaps:
files:
"<%= files.js.admin.sourcemap_out %>" : "<%= files.js.admin.sourcemap_in %>"
"<%= files.js.vendor.sourcemap_out %>" : "<%= files.js.vendor.sourcemap_in %>"
admin:
files:
"<%= files.js.admin.dest %>" : "<%= files.js.admin.concatenated %>"
vendor:
files:
"<%= files.js.vendor.dest %>": "<%= files.js.vendor.concatenated %>"
css:
files:
"<%= files.sass.app.dest %>" : "<%= files.sass.app.compiled %>"
"<%= files.sass.rte.dest %>" : "<%= files.sass.rte.compiled %>"
"<%= files.sass.print.dest %>" : "<%= files.sass.print.compiled %>"
# dev lifecycle, listens for file changes and reruns appropriate tasks
facemelting_mindflaying_watcher_3000:
svgicons:
files: ["app/assets/batman/admin2/resources/icons"]
tasks: ["grunticon", "copy:svgicons", "sass:admin"]
admin:
files: [
"app/assets/batman/admin2/bootstrap.coffee"
"app/assets/batman/admin2/shopify.js.coffee"
"app/assets/batman/admin2/controllers"
"app/assets/batman/admin2/views"
"app/assets/batman/admin2/html"
"app/assets/batman/admin2/models"
"app/assets/batman/admin2/helpers"
"app/assets/batman/admin2/lib"
]
tasks: ["newer:coffee:admin", "concat_sourcemap:admin", "copy:admin", "copy:sourcemaps"]
batman_views:
files: ["app/assets/batman/admin2/html"]
tasks: ["batman_templates", "concat_sourcemap:admin", "copy:admin", "copy:sourcemaps"]
sass:
files: [
"app/assets/batman/admin2/resources/css"
"!app/assets/batman/admin2/resources/css/_datauri_placeholders.scss"
"!app/assets/batman/admin2/resources/css/_datauri_variables.scss"
"!app/assets/batman/admin2/resources/css/_svgicons.scss"
]
tasks: ["sass:admin", "copy:css"]
vendor:
files: ["app/assets/batman/admin2/vendor"]
tasks: ["concat_sourcemap:vendor", "copy:vendor", "copy:sourcemaps"]
spec:
files: ["test/javascripts"]
tasks: ["newer:coffee:spec", "concat_sourcemap:spec"]
spec_legacy:
files: [
"app/assets/javascripts/google_instant_buy.js.coffee"
"app/assets/javascripts/external/app.js.coffee"
]
tasks: ["newer:coffee:spec_legacy", "concat_sourcemap:spec"]
datauri:
files: ["app/assets/batman/admin2/resources/images"]
tasks: ['datauri', 'sass:admin', 'copy:css']
module.exports = function(grunt) {
var _ = grunt.util._;
var fs = require('fs');
grunt.registerTask('facemelting_mindflaying_watcher_3000', 'experimental directory only', function() {
var pathTaskMap = {};
var watched = {};
var folders = {};
var intervalId;
var taskDone = this.async();
var watchcfg = grunt.config('facemelting_mindflaying_watcher_3000');
var targets = Object.keys(watchcfg).filter(function(key) {
return typeof watchcfg[key] !== 'string' && !Array.isArray(watchcfg[key]);
});
targets = targets.map(function(target) {
// Fail if any required config properties have been omitted.
target = ['facemelting_mindflaying_watcher_3000', target];
this.requiresConfig(target.concat('files'), target.concat('tasks'));
return grunt.config(target);
}, this);
var cachePathTaskMappings = function(targets) {
_(targets).each(function(target) {
_(target.files).each(function(file) {
pathTaskMap[file] = target.tasks;
});
});
};
cachePathTaskMappings(targets);
grunt.log.writeln('Waiting to melt faces...');
var nameArgs = this.nameArgs;
var patterns = _.pluck(targets, 'files');
var getFolders = function() {
return grunt.file.expand({filter: 'isDirectory', cwd: process.cwd()}, patterns);
};
var getFiles = function() {
return grunt.file.expand({filter: 'isFile', cwd: process.cwd()}, patterns);
};
function walk(path) {
var results = [];
var list = fs.readdirSync(path);
list.forEach(function(file) {
file = path + '/' + file;
if( !fs.existsSync(file) ) {
return;
}
var stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
results.push( file );
results = results.concat(walk(file));
}
});
return results;
}
function watch(path) {
if(!watched[path]) {
watched[path] = fs.watch(path, function() {
handleChange(path);
});
}
}
function unwatch(path) {
if(watched[path]) {
watched[path].close();
delete watched[path];
}
}
function findClosestPath(path) {
keys = Object.keys(pathTaskMap);
keys = keys.filter(function (x) {
return path.indexOf(x) >= 0;
});
return keys[0];
}
var handleChange = _.debounce(function (fileOrFolderPath) {
grunt.log.ok('changed', fileOrFolderPath);
clearInterval(intervalId);
Object.keys(watched).forEach(unwatch);
if(!pathTaskMap[fileOrFolderPath]) {
fileOrFolderPath = findClosestPath(fileOrFolderPath);
grunt.log.writeln("watch exists on parent:", fileOrFolderPath);
}
grunt.log.ok();
grunt.task.run(pathTaskMap[fileOrFolderPath]).mark();
grunt.task.run(nameArgs);
taskDone();
}, 250);
function addWatches() {
getFiles().forEach(function(path) { watch(path); });
getFolders().forEach(function(path) {
watch(path);
if (!folders[path]) {
folders[path] = walk(path);
}
folders[path].forEach( watch);
});
}
intervalId = setInterval( function() {
addWatches();
}, 200 );
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment