| /* | 
|     MIT License http://www.opensource.org/licenses/mit-license.php | 
|     Author Tobias Koppers @sokra | 
| */ | 
| var fs = require("fs"); | 
| var readFile = fs.readFile.bind(fs); | 
| var loadLoader = require("./loadLoader"); | 
|   | 
| function utf8BufferToString(buf) { | 
|     var str = buf.toString("utf-8"); | 
|     if(str.charCodeAt(0) === 0xFEFF) { | 
|         return str.substr(1); | 
|     } else { | 
|         return str; | 
|     } | 
| } | 
|   | 
| function splitQuery(req) { | 
|     var i = req.indexOf("?"); | 
|     if(i < 0) return [req, ""]; | 
|     return [req.substr(0, i), req.substr(i)]; | 
| } | 
|   | 
| function dirname(path) { | 
|     if(path === "/") return "/"; | 
|     var i = path.lastIndexOf("/"); | 
|     var j = path.lastIndexOf("\\"); | 
|     var i2 = path.indexOf("/"); | 
|     var j2 = path.indexOf("\\"); | 
|     var idx = i > j ? i : j; | 
|     var idx2 = i > j ? i2 : j2; | 
|     if(idx < 0) return path; | 
|     if(idx === idx2) return path.substr(0, idx + 1); | 
|     return path.substr(0, idx); | 
| } | 
|   | 
| function createLoaderObject(loader) { | 
|     var obj = { | 
|         path: null, | 
|         query: null, | 
|         options: null, | 
|         ident: null, | 
|         normal: null, | 
|         pitch: null, | 
|         raw: null, | 
|         data: null, | 
|         pitchExecuted: false, | 
|         normalExecuted: false | 
|     }; | 
|     Object.defineProperty(obj, "request", { | 
|         enumerable: true, | 
|         get: function() { | 
|             return obj.path + obj.query; | 
|         }, | 
|         set: function(value) { | 
|             if(typeof value === "string") { | 
|                 var splittedRequest = splitQuery(value); | 
|                 obj.path = splittedRequest[0]; | 
|                 obj.query = splittedRequest[1]; | 
|                 obj.options = undefined; | 
|                 obj.ident = undefined; | 
|             } else { | 
|                 if(!value.loader) | 
|                     throw new Error("request should be a string or object with loader and object (" + JSON.stringify(value) + ")"); | 
|                 obj.path = value.loader; | 
|                 obj.options = value.options; | 
|                 obj.ident = value.ident; | 
|                 if(obj.options === null) | 
|                     obj.query = ""; | 
|                 else if(obj.options === undefined) | 
|                     obj.query = ""; | 
|                 else if(typeof obj.options === "string") | 
|                     obj.query = "?" + obj.options; | 
|                 else if(obj.ident) | 
|                     obj.query = "??" + obj.ident; | 
|                 else if(typeof obj.options === "object" && obj.options.ident) | 
|                     obj.query = "??" + obj.options.ident; | 
|                 else | 
|                     obj.query = "?" + JSON.stringify(obj.options); | 
|             } | 
|         } | 
|     }); | 
|     obj.request = loader; | 
|     if(Object.preventExtensions) { | 
|         Object.preventExtensions(obj); | 
|     } | 
|     return obj; | 
| } | 
|   | 
| function runSyncOrAsync(fn, context, args, callback) { | 
|     var isSync = true; | 
|     var isDone = false; | 
|     var isError = false; // internal error | 
|     var reportedError = false; | 
|     context.async = function async() { | 
|         if(isDone) { | 
|             if(reportedError) return; // ignore | 
|             throw new Error("async(): The callback was already called."); | 
|         } | 
|         isSync = false; | 
|         return innerCallback; | 
|     }; | 
|     var innerCallback = context.callback = function() { | 
|         if(isDone) { | 
|             if(reportedError) return; // ignore | 
|             throw new Error("callback(): The callback was already called."); | 
|         } | 
|         isDone = true; | 
|         isSync = false; | 
|         try { | 
|             callback.apply(null, arguments); | 
|         } catch(e) { | 
|             isError = true; | 
|             throw e; | 
|         } | 
|     }; | 
|     try { | 
|         var result = (function LOADER_EXECUTION() { | 
|             return fn.apply(context, args); | 
|         }()); | 
|         if(isSync) { | 
|             isDone = true; | 
|             if(result === undefined) | 
|                 return callback(); | 
|             if(result && typeof result === "object" && typeof result.then === "function") { | 
|                 return result.then(function(r) { | 
|                     callback(null, r); | 
|                 }, callback); | 
|             } | 
|             return callback(null, result); | 
|         } | 
|     } catch(e) { | 
|         if(isError) throw e; | 
|         if(isDone) { | 
|             // loader is already "done", so we cannot use the callback function | 
|             // for better debugging we print the error on the console | 
|             if(typeof e === "object" && e.stack) console.error(e.stack); | 
|             else console.error(e); | 
|             return; | 
|         } | 
|         isDone = true; | 
|         reportedError = true; | 
|         callback(e); | 
|     } | 
|   | 
| } | 
|   | 
| function convertArgs(args, raw) { | 
|     if(!raw && Buffer.isBuffer(args[0])) | 
|         args[0] = utf8BufferToString(args[0]); | 
|     else if(raw && typeof args[0] === "string") | 
|         args[0] = new Buffer(args[0], "utf-8"); // eslint-disable-line | 
| } | 
|   | 
| function iteratePitchingLoaders(options, loaderContext, callback) { | 
|     // abort after last loader | 
|     if(loaderContext.loaderIndex >= loaderContext.loaders.length) | 
|         return processResource(options, loaderContext, callback); | 
|   | 
|     var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; | 
|   | 
|     // iterate | 
|     if(currentLoaderObject.pitchExecuted) { | 
|         loaderContext.loaderIndex++; | 
|         return iteratePitchingLoaders(options, loaderContext, callback); | 
|     } | 
|   | 
|     // load loader module | 
|     loadLoader(currentLoaderObject, function(err) { | 
|         if(err) { | 
|             loaderContext.cacheable(false); | 
|             return callback(err); | 
|         } | 
|         var fn = currentLoaderObject.pitch; | 
|         currentLoaderObject.pitchExecuted = true; | 
|         if(!fn) return iteratePitchingLoaders(options, loaderContext, callback); | 
|   | 
|         runSyncOrAsync( | 
|             fn, | 
|             loaderContext, [loaderContext.remainingRequest, loaderContext.previousRequest, currentLoaderObject.data = {}], | 
|             function(err) { | 
|                 if(err) return callback(err); | 
|                 var args = Array.prototype.slice.call(arguments, 1); | 
|                 if(args.length > 0) { | 
|                     loaderContext.loaderIndex--; | 
|                     iterateNormalLoaders(options, loaderContext, args, callback); | 
|                 } else { | 
|                     iteratePitchingLoaders(options, loaderContext, callback); | 
|                 } | 
|             } | 
|         ); | 
|     }); | 
| } | 
|   | 
| function processResource(options, loaderContext, callback) { | 
|     // set loader index to last loader | 
|     loaderContext.loaderIndex = loaderContext.loaders.length - 1; | 
|   | 
|     var resourcePath = loaderContext.resourcePath; | 
|     if(resourcePath) { | 
|         loaderContext.addDependency(resourcePath); | 
|         options.readResource(resourcePath, function(err, buffer) { | 
|             if(err) return callback(err); | 
|             options.resourceBuffer = buffer; | 
|             iterateNormalLoaders(options, loaderContext, [buffer], callback); | 
|         }); | 
|     } else { | 
|         iterateNormalLoaders(options, loaderContext, [null], callback); | 
|     } | 
| } | 
|   | 
| function iterateNormalLoaders(options, loaderContext, args, callback) { | 
|     if(loaderContext.loaderIndex < 0) | 
|         return callback(null, args); | 
|   | 
|     var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; | 
|   | 
|     // iterate | 
|     if(currentLoaderObject.normalExecuted) { | 
|         loaderContext.loaderIndex--; | 
|         return iterateNormalLoaders(options, loaderContext, args, callback); | 
|     } | 
|   | 
|     var fn = currentLoaderObject.normal; | 
|     currentLoaderObject.normalExecuted = true; | 
|     if(!fn) { | 
|         return iterateNormalLoaders(options, loaderContext, args, callback); | 
|     } | 
|   | 
|     convertArgs(args, currentLoaderObject.raw); | 
|   | 
|     runSyncOrAsync(fn, loaderContext, args, function(err) { | 
|         if(err) return callback(err); | 
|   | 
|         var args = Array.prototype.slice.call(arguments, 1); | 
|         iterateNormalLoaders(options, loaderContext, args, callback); | 
|     }); | 
| } | 
|   | 
| exports.getContext = function getContext(resource) { | 
|     var splitted = splitQuery(resource); | 
|     return dirname(splitted[0]); | 
| }; | 
|   | 
| exports.runLoaders = function runLoaders(options, callback) { | 
|     // read options | 
|     var resource = options.resource || ""; | 
|     var loaders = options.loaders || []; | 
|     var loaderContext = options.context || {}; | 
|     var readResource = options.readResource || readFile; | 
|   | 
|     // | 
|     var splittedResource = resource && splitQuery(resource); | 
|     var resourcePath = splittedResource ? splittedResource[0] : undefined; | 
|     var resourceQuery = splittedResource ? splittedResource[1] : undefined; | 
|     var contextDirectory = resourcePath ? dirname(resourcePath) : null; | 
|   | 
|     // execution state | 
|     var requestCacheable = true; | 
|     var fileDependencies = []; | 
|     var contextDependencies = []; | 
|   | 
|     // prepare loader objects | 
|     loaders = loaders.map(createLoaderObject); | 
|   | 
|     loaderContext.context = contextDirectory; | 
|     loaderContext.loaderIndex = 0; | 
|     loaderContext.loaders = loaders; | 
|     loaderContext.resourcePath = resourcePath; | 
|     loaderContext.resourceQuery = resourceQuery; | 
|     loaderContext.async = null; | 
|     loaderContext.callback = null; | 
|     loaderContext.cacheable = function cacheable(flag) { | 
|         if(flag === false) { | 
|             requestCacheable = false; | 
|         } | 
|     }; | 
|     loaderContext.dependency = loaderContext.addDependency = function addDependency(file) { | 
|         fileDependencies.push(file); | 
|     }; | 
|     loaderContext.addContextDependency = function addContextDependency(context) { | 
|         contextDependencies.push(context); | 
|     }; | 
|     loaderContext.getDependencies = function getDependencies() { | 
|         return fileDependencies.slice(); | 
|     }; | 
|     loaderContext.getContextDependencies = function getContextDependencies() { | 
|         return contextDependencies.slice(); | 
|     }; | 
|     loaderContext.clearDependencies = function clearDependencies() { | 
|         fileDependencies.length = 0; | 
|         contextDependencies.length = 0; | 
|         requestCacheable = true; | 
|     }; | 
|     Object.defineProperty(loaderContext, "resource", { | 
|         enumerable: true, | 
|         get: function() { | 
|             if(loaderContext.resourcePath === undefined) | 
|                 return undefined; | 
|             return loaderContext.resourcePath + loaderContext.resourceQuery; | 
|         }, | 
|         set: function(value) { | 
|             var splittedResource = value && splitQuery(value); | 
|             loaderContext.resourcePath = splittedResource ? splittedResource[0] : undefined; | 
|             loaderContext.resourceQuery = splittedResource ? splittedResource[1] : undefined; | 
|         } | 
|     }); | 
|     Object.defineProperty(loaderContext, "request", { | 
|         enumerable: true, | 
|         get: function() { | 
|             return loaderContext.loaders.map(function(o) { | 
|                 return o.request; | 
|             }).concat(loaderContext.resource || "").join("!"); | 
|         } | 
|     }); | 
|     Object.defineProperty(loaderContext, "remainingRequest", { | 
|         enumerable: true, | 
|         get: function() { | 
|             if(loaderContext.loaderIndex >= loaderContext.loaders.length - 1 && !loaderContext.resource) | 
|                 return ""; | 
|             return loaderContext.loaders.slice(loaderContext.loaderIndex + 1).map(function(o) { | 
|                 return o.request; | 
|             }).concat(loaderContext.resource || "").join("!"); | 
|         } | 
|     }); | 
|     Object.defineProperty(loaderContext, "currentRequest", { | 
|         enumerable: true, | 
|         get: function() { | 
|             return loaderContext.loaders.slice(loaderContext.loaderIndex).map(function(o) { | 
|                 return o.request; | 
|             }).concat(loaderContext.resource || "").join("!"); | 
|         } | 
|     }); | 
|     Object.defineProperty(loaderContext, "previousRequest", { | 
|         enumerable: true, | 
|         get: function() { | 
|             return loaderContext.loaders.slice(0, loaderContext.loaderIndex).map(function(o) { | 
|                 return o.request; | 
|             }).join("!"); | 
|         } | 
|     }); | 
|     Object.defineProperty(loaderContext, "query", { | 
|         enumerable: true, | 
|         get: function() { | 
|             var entry = loaderContext.loaders[loaderContext.loaderIndex]; | 
|             return entry.options && typeof entry.options === "object" ? entry.options : entry.query; | 
|         } | 
|     }); | 
|     Object.defineProperty(loaderContext, "data", { | 
|         enumerable: true, | 
|         get: function() { | 
|             return loaderContext.loaders[loaderContext.loaderIndex].data; | 
|         } | 
|     }); | 
|   | 
|     // finish loader context | 
|     if(Object.preventExtensions) { | 
|         Object.preventExtensions(loaderContext); | 
|     } | 
|   | 
|     var processOptions = { | 
|         resourceBuffer: null, | 
|         readResource: readResource | 
|     }; | 
|     iteratePitchingLoaders(processOptions, loaderContext, function(err, result) { | 
|         if(err) { | 
|             return callback(err, { | 
|                 cacheable: requestCacheable, | 
|                 fileDependencies: fileDependencies, | 
|                 contextDependencies: contextDependencies | 
|             }); | 
|         } | 
|         callback(null, { | 
|             result: result, | 
|             resourceBuffer: processOptions.resourceBuffer, | 
|             cacheable: requestCacheable, | 
|             fileDependencies: fileDependencies, | 
|             contextDependencies: contextDependencies | 
|         }); | 
|     }); | 
| }; |