'use strict'; var fs = require('fs'); var getFieldAsFn = require('./get-field-as-fn'); /** * Create a decoder for input sources using the given codec hash * @this {object} A loader or compilation * @param {Array.} codecs A list of codecs, each with a `decode` function * @param {boolean} mustDecode Return an error for a source that is not decoded * @returns {function(string):string|Error} A decode function that returns an absolute path or else an Error */ function decodeSourcesWith(codecs, mustDecode) { /* jshint validthis:true */ var context = this; // get a list of valid decoders var candidates = [].concat(codecs) .reduce(reduceValidDecoder.bind(null, codecs), []); /** * Attempt to decode the given source path using the previously supplied codecs * @param {string} inputSource A source path from a source map * @returns {Error|string|undefined} An absolute path if decoded else an error if encountered else undefined */ return function decode(inputSource) { // attempt all candidates until a match for (var i = 0, decoded = null; i < candidates.length && !decoded; i++) { // call the decoder try { decoded = candidates[i].decode.call(context, inputSource); } catch (exception) { return getNamedError(exception); } // match implies a return value if (decoded) { // abstract sources cannot be decoded, only validated if (candidates[i].abstract) { return undefined; } // non-string implies error if (typeof decoded !== 'string') { return getNamedError('Decoder returned a truthy value but it is not a string file path:\n' + decoded); } // non-existant file implies error else if (!fs.existsSync(decoded) || !fs.statSync(decoded).isFile()) { return getNamedError('Cannot find file at absolute path:\n' + decoded); } // otherwise success else { return decoded; } } } // default is undefined or error return mustDecode ? new Error('No viable decoder for source: ' + inputSource) : undefined; function getNamedError(details) { var name = candidates[i].name || '(unnamed)', message = [ 'Decoding with codec: ' + name, 'Incoming source: ' + inputSource, details && (details.stack ? details.stack : details) ] .filter(Boolean) .join('\n'); return new Error(message); } }; } module.exports = decodeSourcesWith; function reduceValidDecoder(reduced, codec) { var decoder = getFieldAsFn('decode')(codec); return decoder ? reduced.concat(codec) : reduced; }