const globby = require('globby')
const Promise = require('bluebird')
const fs = Promise.promisifyAll(require('fs'))
-const spawn = require('child_process').spawn
+const spawn = require('cross-spawn')
const path = require('path')
const retryPromise = require('promise-retry')
+const jsStringEscape = require('js-string-escape')
-const psModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i
+const ffiModuleRegex = /\/\/\s+module\s+([\w\.]+)/i
+const srcModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i
const requireRegex = /require\(['"]\.\.\/([\w\.]+)['"]\)/g
module.exports = function purescriptLoader(source, map) {
})
}
- const psModuleName = match(psModuleRegex, source)
+ const psModuleName = match(srcModuleRegex, source)
const psModule = {
name: psModuleName,
load: js => callback(null, js),
return Promise.props({
js: fs.readFileAsync(jsPath, 'utf8'),
- psModuleMap: psModuleMap(options.src, cache)
+ psModuleMap: psModuleMap(options, cache)
}).then(result => {
let js = ''
if (options.bundle) {
// if bundling, return a reference to the bundle
js = 'module.exports = require("'
- + path.relative(psModule.srcDir, options.bundleOutput)
+ + jsStringEscape(path.relative(psModule.srcDir, options.bundleOutput))
+ '")["' + psModule.name + '"]'
} else {
// replace require paths to output files generated by psc with paths
// to purescript sources, which are then also run through this loader.
- const foreignRequire = 'require("' + path.resolve(
- path.join(psModule.options.output, psModule.name, 'foreign.js')
- ) + '")'
-
js = result.js
.replace(requireRegex, (m, p1) => {
- return 'require("' + result.psModuleMap[p1] + '")'
+ return 'require("' + jsStringEscape(result.psModuleMap[p1].src) + '")'
+ })
+ .replace(/require\(['"]\.\/foreign['"]\)/g, (m, p1) => {
+ return 'require("' + jsStringEscape(result.psModuleMap[psModule.name].ffi) + '")'
})
- .replace(/require\(['"]\.\/foreign['"]\)/g, foreignRequire)
}
return js
const args = dargs(options.pscIdeArgs)
const ideClient = spawn('psc-ide-client', args)
- ideClient.stdout.once('data', data => {
+ var stdout = ''
+ var stderr = ''
+
+ ideClient.stdout.on('data', data => {
+ stdout = stdout + data.toString()
+ })
+
+ ideClient.stderr.on('data', data => {
+ stderr = stderr + data.toString()
+ })
+
+ ideClient.on('close', code => {
+ if (code !== 0) {
+ const error = stderr === '' ? 'Failed to spawn psc-ide-client' : stderr
+ return reject(new Error(error))
+ }
+
let res = null
try {
- res = JSON.parse(data.toString())
+ res = JSON.parse(stdout.toString())
debug(res)
} catch (err) {
return reject(err)
})
})
- ideClient.stderr.once('data', data => reject(data.toString()))
-
ideClient.stdin.write(JSON.stringify(body))
ideClient.stdin.write('\n')
})
}
// map of PS module names to their source path
-function psModuleMap(globs, cache) {
+function psModuleMap(options, cache) {
if (cache.psModuleMap) return Promise.resolve(cache.psModuleMap)
+ const globs = [].concat(options.src).concat(options.ffi)
+
return globby(globs).then(paths => {
return Promise
.props(paths.reduce((map, file) => {
map[file] = fs.readFileAsync(file, 'utf8')
return map
}, {}))
- .then(srcMap => {
- cache.psModuleMap = Object.keys(srcMap).reduce((map, file) => {
- const source = srcMap[file]
- const psModuleName = match(psModuleRegex, source)
- map[psModuleName] = path.resolve(file)
+ .then(fileMap => {
+ cache.psModuleMap = Object.keys(fileMap).reduce((map, file) => {
+ const source = fileMap[file]
+ const ext = path.extname(file)
+ const isPurs = ext.match(/purs$/i)
+ const moduleRegex = isPurs ? srcModuleRegex : ffiModuleRegex
+ const moduleName = match(moduleRegex, source)
+ map[moduleName] = map[moduleName] || {}
+ if (isPurs) {
+ map[moduleName].src = path.resolve(file)
+ } else {
+ map[moduleName].ffi = path.resolve(file)
+ }
return map
}, {})
return cache.psModuleMap
else if (Array.isArray(val)) val.forEach(v => args.push(arg, v))
else args.push(arg, obj[key])
- return args
+ return args.filter(arg => (typeof arg !== 'boolean'))
}, [])
}