File: /var/dev/nowruzgan/rest/node_modules/browserify-transform-tools/src/transformTools.coffee
# Framework for building Falafel based transforms for Browserify.
path = require 'path'
fs = require 'fs'
through = require 'through'
falafel = require 'falafel'
loadConfig = require './loadConfig'
skipFile = require './skipFile'
exports.loadTransformConfig = loadConfig.loadTransformConfig
exports.loadTransformConfigSync = loadConfig.loadTransformConfigSync
exports.skipFile = skipFile
# TODO: Does this work on Windows?
isRootDir = (filename) -> filename == path.resolve(filename, '/')
merge = (a={}, b={}) ->
answer = {}
answer[key] = a[key] for key of a
answer[key] = b[key] for key of b
return answer
clone = (a) ->
if !a then return a
answer = {}
answer[key] = a[key] for key of a
return answer
# Create a new Browserify transform which reads and returns a string.
#
# Browserify transforms work on streams. This is all well and good, until you want to call
# a library like "falafel" which doesn't work with streams.
#
# Suppose you are writing a transform called "redify" which replaces all occurances of "blue"
# with "red":
#
# options = {}
# module.exports = makeStringTransform "redify", options, (contents, transformOptions, done) ->
# done null, contents.replace(/blue/g, "red")
#
# Parameters:
# * `transformFn(contents, transformOptions, done)` - Function which is called to
# do the transform. `contents` are the contents of the file. `transformOptions.file` is the
# name of the file (as would be passed to a normal browserify transform.)
# `transformOptions.configData` is the configuration data for the transform (see
# `loadTransformConfig` below for details.) `transformOptions.config` is a copy of
# `transformOptions.configData.config` for convenience. `done(err, transformed)` is a callback
# which must be called, passing the a string with the transformed contents of the file.
# * `options.excludeExtensions` - A list of extensions which will not be processed. e.g.
# "['.coffee', '.jade']"
# * `options.includeExtensions` - A list of extensions to process. If this options is not
# specified, then all extensions will be processed. If this option is specified, then
# any file with an extension not in this list will skipped.
# * `options.jsFilesOnly` - If true (and if includeExtensions is not set) then this transform
# will only operate on .js files, and on files which are commonly compiled to javascript
# (.coffee, .litcoffee, .coffee.md, .jade, etc...)
#
exports.makeStringTransform = (transformName, options={}, transformFn) ->
if !transformFn?
transformFn = options
options = {}
transform = (file, config) ->
configData = if transform.configData?
transform.configData
else
loadConfig.loadTransformConfigSync transformName, file, options
if config?
configData = clone(configData) ? {config:{}}
configData.config = merge configData.config, config
if configData.config.appliesTo
configData.appliesTo = configData.config.appliesTo
delete configData.config.appliesTo
if skipFile file, configData, options then return through()
# Read the file contents into `content`
content = ''
write = (buf) -> content += buf
# Called when we're done reading file contents
end = ->
handleError = (error) =>
suffix = " (while #{transformName} was processing #{file})"
if error instanceof Error and error.message
error.message += suffix
else
error = new Error("#{error}#{suffix}")
@emit 'error', error
try
transformOptions = {
file: file,
configData: configData,
config: configData?.config,
opts: configData?.config
}
transformFn.call @, content, transformOptions, (err, transformed) =>
return handleError err if err
@queue String(transformed)
@queue null
catch err
handleError err
return through write, end
# Called to manually pass configuration data to the transform. Configuration passed in this
# way will override configuration loaded from package.json.
#
# * `config` is the configuration data.
# * `configOptions.configFile` is the file that configuration data was loaded from. If this
# is specified and `configOptions.configDir` is not specified, then `configOptions.configDir`
# will be inferred from the configFile's path.
# * `configOptions.configDir` is the directory the configuration was loaded from. This is used
# by some transforms to resolve relative paths.
#
# Returns a new transform that uses the configuration:
#
# myTransform = require('myTransform').configure(...)
#
transform.configure = (config, configOptions = {}) ->
answer = exports.makeStringTransform transformName, options, transformFn
answer.setConfig config, configOptions
return answer
# Similar to `configure()`, but modifies the transform instance it is called on. This can
# be used to set the default configuration for the transform.
transform.setConfig = (config, configOptions = {}) ->
configFile = configOptions.configFile or null
configDir = configOptions.configDir or if configFile then path.dirname configFile else null
if !config
@configData = null
else
@configData = {
config: config,
configFile: configFile,
configDir: configDir,
cached: false
}
if config.appliesTo
@configData.appliesTo = config.appliesTo
delete config.appliesTo
return this
return transform
# Create a new Browserify transform based on [falafel](https://github.com/substack/node-falafel).
#
# Parameters:
# * `transformFn(node, transformOptions, done)` is called once for each falafel node. transformFn
# is free to update the falafel node directly; any value returned via `done(err)` is ignored.
# * `options.falafelOptions` are options to pass directly to Falafel.
# * `transformName`, `options.excludeExtensions`, `options.includeExtensions`, `options.jsFilesOnly`,
# and `tranformOptions` are the same as for `makeStringTransform()`.
#
exports.makeFalafelTransform = (transformName, options={}, transformFn) ->
if !transformFn?
transformFn = options
options = {}
falafelOptions = options.falafelOptions ? {}
transform = exports.makeStringTransform transformName, options, (content, transformOptions, done) ->
transformErr = null
pending = 1 # We'll decrement this to zero at the end to prevent premature call of `done`.
transformed = null
transformCb = (err) ->
if err and !transformErr
transformErr = err
done err
# Stop further processing if an error has occurred
return if transformErr
pending--
if pending is 0
done null, transformed
transformed = falafel content, falafelOptions, (node) ->
pending++
try
transformFn node, transformOptions, transformCb
catch err
transformCb err
# call transformCb one more time to decrement pending to 0.
transformCb transformErr, transformed
# Called to manually pass configuration data to the transform. Configuration passed in this
# way will override configuration loaded from package.json.
#
# * `config` is the configuration data.
# * `configOptions.configFile` is the file that configuration data was loaded from. If this
# is specified and `configOptions.configDir` is not specified, then `configOptions.configDir`
# will be inferred from the configFile's path.
# * `configOptions.configDir` is the directory the configuration was loaded from. This is used
# by some transforms to resolve relative paths.
#
# Returns a new transform that uses the configuration:
#
# myTransform = require('myTransform').configure(...)
#
transform.configure = (config, configOptions = {}) ->
answer = exports.makeFalafelTransform transformName, options, transformFn
answer.setConfig config, configOptions
return answer
return transform
# Create a new Browserify transform that modifies requires() calls.
#
# The resulting transform will call `transformFn(requireArgs, tranformOptions, cb)` for every
# requires in a file. transformFn should call `cb(null, str)` with a string which will replace the
# entire `require` call.
#
# Exmaple:
#
# makeRequireTransform "xify", (requireArgs, cb) ->
# cb null, "require(x" + requireArgs[0] + ")"
#
# would transform calls like `require("foo")` into `require("xfoo")`.
#
# `transformName`, `options.excludeExtensions`, `options.includeExtensions`, `options.jsFilesOnly`,
# and `tranformOptions` are the same as for `makeStringTransform()`.
#
# By default, makeRequireTransform will attempt to evaluate each "require" parameters.
# makeRequireTransform can handle variabls `__filename`, `__dirname`, `path`, and `join` (where
# `join` is treated as `path.join`) as well as any basic JS expressions. If the argument is
# too complicated to parse, then makeRequireTransform will return the source for the argument.
# You can disable parsing by passing `options.evaluateArguments` as false.
#
exports.makeRequireTransform = (transformName, options={}, transformFn) ->
if !transformFn?
transformFn = options
options = {}
evaluateArguments = options.evaluateArguments ? true
transform = exports.makeFalafelTransform transformName, options, (node, transformOptions, done) ->
if (node.type is 'CallExpression' and node.callee.type is 'Identifier' and
node.callee.name is 'require')
# Parse arguemnts to calls to `require`.
args = evaluateFunctionArgs evaluateArguments, transformOptions, node
transformFn args.values(), transformOptions, (err, transformed) ->
return done err if err
if transformed? then node.update(transformed)
done()
else
done()
# Called to manually pass configuration data to the transform. Configuration passed in this
# way will override configuration loaded from package.json.
#
# * `config` is the configuration data.
# * `configOptions.configFile` is the file that configuration data was loaded from. If this
# is specified and `configOptions.configDir` is not specified, then `configOptions.configDir`
# will be inferred from the configFile's path.
# * `configOptions.configDir` is the directory the configuration was loaded from. This is used
# by some transforms to resolve relative paths.
#
# Returns a new transform that uses the configuration:
#
# myTransform = require('myTransform').configure(...)
#
transform.configure = (config, configOptions = {}) ->
answer = exports.makeRequireTransform transformName, options, transformFn
answer.setConfig config, configOptions
return answer
return transform
# Create a new Browserify transform that modifies arbitrary function calls.
#
# The resulting transform will call `transformFn({functionName: [functionName], args: {value: [arg], type: [type]}}, tranformOptions, cb)` for every
# given function alias in a file. transformFn should call `cb(null, str)` with a string which will replace the
# entire function call.
#
# Exmaple:
#
# makeFunctionTransform "xify", {functionNames: ["baz"]}, (functionParams, cb) ->
# cb null, functionParams.name + "('x" + functionParams.args[0].value + "')"
#
# would transform calls like `baz("foo")` into `baz("xfoo")`.
#
# `transformName`, `options.excludeExtensions`, `options.includeExtensions`, `options.jsFilesOnly`,
# and `tranformOptions` are the same as for `makeStringTransform()`.
#
# `options.functionNames` is an optional parameter which can be a string or an array of strings.
# These strings are taken to identify function occurences in the code. If nothing is passed as a function name
# makeFunctionTransform falls back to `require()` calls.
#
# By default, makeFunctionTransform will attempt to evaluate each "require" parameters.
# makeFunctionTransform can handle variabls `__filename`, `__dirname`, `path`, and `join` (where
# `join` is treated as `path.join`) as well as any basic JS expressions. If the argument is
# too complicated to parse, then makeFunctionTransform will return the source for the argument.
# You can disable parsing by passing `options.evaluateArguments` as false.
#
exports.makeFunctionTransform = (transformName, options={}, transformFn) ->
if !transformFn?
transformFn = options
options = {}
evaluateArguments = options.evaluateArguments ? true
functionNames = []
if options.functionNames?
if Array.isArray(options.functionNames) || {}.toString.call(options.functionNames) is '[object Array]'
functionNames = options.functionNames
else if typeof options.functionNames is 'string'
functionNames = [options.functionNames]
if functionNames.length is 0
functionNames.push 'require'
transform = exports.makeFalafelTransform transformName, options, (node, transformOptions, done) ->
if (node.type is 'CallExpression' and node.callee.type is 'Identifier' and
node.callee.name in functionNames)
# Parse arguments to calls to a given function name.
args = evaluateFunctionArgs evaluateArguments, transformOptions, node
transformFn {name: node.callee.name, args: args}, transformOptions, (err, transformed) ->
return done err if err
if transformed? then node.update(transformed)
done()
else
done()
# Called to manually pass configuration data to the transform. Configuration passed in this
# way will override configuration loaded from package.json.
#
# * `config` is the configuration data.
# * `configOptions.configFile` is the file that configuration data was loaded from. If this
# is specified and `configOptions.configDir` is not specified, then `configOptions.configDir`
# will be inferred from the configFile's path.
# * `configOptions.configDir` is the directory the configuration was loaded from. This is used
# by some transforms to resolve relative paths.
#
# Returns a new transform that uses the configuration:
#
# myTransform = require('myTransform').configure(...)
#
transform.configure = (config, configOptions = {}) ->
answer = exports.makeFunctionTransform transformName, options, transformFn
answer.setConfig config, configOptions
return answer
return transform
# Runs a Browserify-style transform on the given file.
#
# * `transform` is the transform to run (i.e. a `fn(file)` which returns a through stream.)
# * `file` is the name of the file to run the transform on.
# * `options.content` is the content of the file. If this option is not provided, the content
# will be read from disk.
# * `options.config` is configuration to pass along to the transform.
# * `done(err, result)` will be called with the transformed input.
#
exports.runTransform = (transform, file, options={}, done) ->
if !done?
done = options
options = {}
doTransform = (content) ->
data = ""
err = null
throughStream = if options.config?
transform(file, options.config)
else
transform(file)
throughStream.on "data", (d) ->
data += d
throughStream.on "end", ->
if !err then done null, data
throughStream.on "error", (e) ->
err = e
done err
throughStream.write content
throughStream.end()
if options.content
process.nextTick -> doTransform options.content
else
fs.readFile file, "utf-8", (err, content) ->
return done err if err
doTransform content
evaluateFunctionArgs = (evaluateArguments, transformOptions, node) ->
if evaluateArguments
# Based on https://github.com/ForbesLindesay/rfileify.
dirname = path.dirname(transformOptions.file)
varNames = ['__filename', '__dirname', 'path', 'join']
vars = [transformOptions.file, dirname, path, path.join]
args = node.arguments.map (arg) ->
t = "return #{arg.source()}"
try
return {value: Function(varNames, t).apply(null, vars), type: arg.type}
catch err
# Can't evaluate the arguments. Return the raw source.
return {value: arg.source(), type: arg.type}
else
args = ({value: arg.source(), type: arg.type} for arg in node.arguments)
args.values = ->
values = []
for arg in this
if arg.value?
values.push arg.value
values
args