diff --git a/gradle.properties b/gradle.properties index db1f672..87d78c6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=0.1.11 +version=0.1.12 diff --git a/src/main/groovy/KWP.groovy b/src/main/groovy/KWP.groovy index e2b7eb4..3f6635c 100644 --- a/src/main/groovy/KWP.groovy +++ b/src/main/groovy/KWP.groovy @@ -10,7 +10,6 @@ class KWP implements Plugin { void installKwp(File targetDir, boolean force) { def file = new File(targetDir, "kwp.js") if (!file.exists() || force) { - targetDir.mkdirs() writeSafely(file) { buf -> buf.append(loaderText) } @@ -35,8 +34,6 @@ class KWP implements Plugin { def sources = new LinkedHashSet() collectDependencies(project, sources, dependencies) - targetDir.mkdirs() - writeSafely(new File(targetDir, "__modules.txt")) { buf -> dependencies.each {jar -> def m = unzipSafely(jar, targetDir) @@ -79,12 +76,12 @@ class KWP implements Plugin { if (zipEntry.isDirectory()) continue if (zipEntry.name.endsWith(".meta.js") || zipEntry.name.endsWith('.js.map')) { - new File(targetFolder, zipEntry.name).text = zip.getInputStream(zipEntry).text + writeSafely(new File(targetFolder, zipEntry.name), zip.getInputStream(zipEntry).text) targets++ } else if (zipEntry.name.endsWith(".js")) { targetFile = new File(targetFolder, zipEntry.name) - targetFile.text = zip.getInputStream(zipEntry).text + writeSafely(targetFile, zip.getInputStream(zipEntry).text) targets++ } @@ -106,11 +103,17 @@ class KWP implements Plugin { return str } + void writeSafely(File file, String contents) { + if (!file.exists() || file.text != contents) { + file.getParentFile().mkdirs() + file.text = contents + } + } + void writeSafely(File file, Closure builder) { def buffer = new StringBuilder() builder(buffer) - def contents = buffer.toString() - if (!file.exists() || file.text != contents) file.text = contents + writeSafely(file, buffer.toString()) } String normalizedAbsolutePath(File file) { diff --git a/src/main/resources/kwp.js b/src/main/resources/kwp.js index a1914be..a2da965 100644 --- a/src/main/resources/kwp.js +++ b/src/main/resources/kwp.js @@ -2,6 +2,292 @@ var path = require("path"); var fs = require('fs') var execSync = require('child_process').execSync var isWin = /^win/.test(process.platform); +var isMacOs = 'darwin' == process.platform; + +/** + * Node-watch npm package https://github.com/yuanchuan/node-watch + * MIT license http://www.opensource.org/licenses/mit-license.php + * Copyright (c) 2012-2016 Yuan Chuan + */ +var watch = (function () { + /** + * Module dependencies. + */ + var fs = require('fs') + , path = require('path') + , events = require('events'); + + + /** + * Utility functions to synchronously test whether the giving path + * is a file or a directory or a symbolic link. + */ + var is = function(ret) { + var shortcuts = { + 'file': 'File' + , 'dir': 'Directory' + , 'sym': 'SymbolicLink' + }; + Object.keys(shortcuts).forEach(function(method) { + var stat = fs[method === 'sym' ? 'lstatSync' :'statSync']; + ret[method] = function(fpath) { + try { + var yes = stat(fpath)['is' + shortcuts[method]](); + memo.push(fpath, method); + return yes; + } catch(e) {} + } + }); + return ret; + }({}); + + + /** + * Get sub-directories in a directory. + */ + var sub = function(parent, cb) { + if (is.dir(parent)) { + fs.readdir(parent, function(err, all) { + all && all.forEach(function(f) { + var sdir = path.join(parent, f); + if (is.dir(sdir)) { + cb.call(null, sdir) + } + }); + }); + } + }; + + + /** + * Mixing object properties. + */ + var mixin = function() { + var mix = {}; + [].forEach.call(arguments, function(arg) { + for (var name in arg) { + if (arg.hasOwnProperty(name)) { + mix[name] = arg[name]; + } + } + }); + return mix; + }; + + + /** + * A container for memorizing names of files or directories. + */ + var memo = function(memo) { + return { + push: function(name, type) { + memo[name] = type; + }, + has: function(name) { + return {}.hasOwnProperty.call(memo, name); + }, + update: function(name) { + if (!is.file(name) || !is.dir(name)) { + delete memo[name]; + } + return true; + } + }; + }({}); + + + /** + * A Container for storing unique and valid filenames. + */ + var fileNameCache = function(cache) { + return { + push: function(name) { + cache[name] = 1; + return this; + }, + each: function() { + var temp = Object.keys(cache).filter(function(name){ + return is.file(name) || memo.has(name) && memo.update(name); + }); + temp.forEach.apply(temp, arguments); + return this; + }, + clear: function(){ + cache = {}; + return this; + } + }; + }({}); + + + /** + * Abstracting the way of avoiding duplicate function call. + */ + var worker = function() { + var free = true; + return { + busydoing: function(cb) { + if (free) { + free = false; + cb.call(); + } + }, + free: function() { + free = true; + } + } + }(); + + + /** + * Delay function call and ignore invalid filenames. + */ + var normalizeCall = function(fname, options, cb, watcher) { + // Store each name of the modifying or temporary files generated by an editor. + fileNameCache.push(fname); + + worker.busydoing(function() { + // A heuristic delay of the write-to-file process. + setTimeout(function() { + + // When the write-to-file process is done, send all filtered filenames + // to the callback function and call it. + fileNameCache + .each(function(f) { + // Watch new created directory. + if (options.recursive && !memo.has(f) && is.dir(f)) { + watch(f, options, cb, watcher); + } + cb && cb.call(null, f); + watcher.emit('change', f); + }).clear(); + + worker.free(); + + }, 100); + }); + }; + + + /** + * Watcher class to simulate FSWatcher + */ + var Watcher = function Watcher() { + this.watchers = []; + this.closed = false; + this.close = function() { + this.watchers.forEach(function(watcher) { + watcher.close(); + }); + this.watchers = []; + this.closed = true; + }; + this.addWatcher = function(watcher, cb) { + var self = this; + this.watchers.push(watcher); + + watcher.on('error', function(err) { + self.emit('error', err); + }); + }; + }; + + Watcher.prototype.__proto__ = events.EventEmitter.prototype; + + + /** + * Option handler for the `watch` function. + */ + var handleOptions = function(origin, defaultOptions) { + return function() { + var args = [].slice.call(arguments); + args[3] = new Watcher; + if (Object.prototype.toString.call(args[1]) === '[object Function]') { + args[2] = args[1]; + } + if (!Array.isArray(args[0])) { + args[0] = [args[0]]; + } + //overwrite default options + args[1] = mixin(defaultOptions, args[1]); + //handle multiple files. + args[0].forEach(function(path) { + origin.apply(null, [path].concat(args.slice(1))); + }); + return args[3]; + } + }; + + + /** + * Ignore the recursive option on platforms which natively support it, + * or temporarily set it to false for optimization. + */ + var noRecursive = function(option) { + return mixin(option, { recursive: false }); + }; + + + /** + * Watch a file or a directory (recursively by default). + * + * @param {String} fpath + * @options {Object} options + * @param {Function} cb + * + * Options: + * `recursive`: Watch it recursively or not (defaults to true). + * `followSymLinks`: Follow symbolic links or not (defaults to false). + * `maxSymLevel`: The max number of following symbolic links (defaults to 1). + * `filter`: Filter function(fullPath:string) => boolean (defaults to () => true ). + * + * Example: + * + * watch('fpath', { recursive: true }, function(file) { + * console.log(file, ' changed'); + * }); + */ + function watch(fpath, options, cb, watcher) { + var skip = watcher.closed || !options.filter(fpath) || ( + is.sym(fpath) && !(options.followSymLinks && options.maxSymLevel--) + ); + if (skip) return; + + // Due to the unstable fs.watch(), if the `fpath` is a file then + // switch to watch its parent directory instead of watch it directly. + // Once the logged filename matches it then triggers the callback function. + if (is.file(fpath)) { + var parent = path.resolve(fpath, '..'); + watcher.addWatcher(fs.watch(parent, noRecursive(options)).on('change', function(evt, fname) { + if (path.basename(fpath) === fname) { + normalizeCall(fname, options, cb, watcher); + } + }), cb); + } + else if (is.dir(fpath)) { + watcher.addWatcher(fs.watch(fpath, noRecursive(options)).on('change', function(evt, fname) { + normalizeCall(path.join(fpath, fname), options, cb, watcher); + }), cb); + + if (options.recursive) { + // Recursively watch its sub-directories. + sub(fpath, function(dir) { + watch(dir, options, cb, watcher); + }); + } + } + } + + /** + * Set default options and expose. + */ + return handleOptions(watch, { + recursive: true + , followSymLinks: false + , maxSymLevel: 1 + , filter: function(fullPath) { return true; } + }); +})() /* ##### MODULE ALIAS PLUGIN COPY-PASTED SO THIS DOESN"T HAVE ANY DEPENDENCIES @@ -110,12 +396,17 @@ KotlinWebpackPlugin.prototype.apply = function (compiler) { if (running) return running = true - console.log("\nRunning Gradle: " + cmdline) - console.time("Done Gradle") - var stdout = execSync(cmdline) - // console.log(stdout) - console.timeEnd("Done Gradle") - running = false + try { + console.log("\nRunning Gradle: " + cmdline) + console.time("Done Gradle") + var stdout = execSync(cmdline) + // console.log(stdout) + console.timeEnd("Done Gradle") + } catch (err) { + console.error("Gradle task ended with an error", err) + } finally { + running = false + } } execGradle() @@ -124,7 +415,8 @@ KotlinWebpackPlugin.prototype.apply = function (compiler) { for (var i = 0; i < deps.length; i++) { var t = deps[i].trim() if (t.length > 0) { - fs.watch(t, { + var watchFn = isWin || isMacOs ? fs.watch : watch + watchFn(t, { recursive: true, persistent: false }, execGradle)