]> git.immae.eu Git - github/fretlink/purs-loader.git/commitdiff
Rewrite using purescript for the implementation
authoreric thul <thul.eric@gmail.com>
Wed, 8 Apr 2015 23:49:24 +0000 (19:49 -0400)
committereric thul <thul.eric@gmail.com>
Sun, 12 Apr 2015 15:19:22 +0000 (11:19 -0400)
17 files changed:
.gitignore
MODULE.md [new file with mode: 0644]
bower.json [new file with mode: 0644]
entry.js [new file with mode: 0644]
gulpfile.js [new file with mode: 0644]
index.js [deleted file]
package.json
src/ChildProcess.purs [new file with mode: 0644]
src/FS.purs [new file with mode: 0644]
src/Glob.purs [new file with mode: 0644]
src/Loader.purs [new file with mode: 0644]
src/LoaderRef.purs [new file with mode: 0644]
src/LoaderUtil.purs [new file with mode: 0644]
src/OS.purs [new file with mode: 0644]
src/Options.purs [new file with mode: 0644]
src/Path.purs [new file with mode: 0644]
webpack.config.js [new file with mode: 0644]

index 8cde68436c403ff7b4250b4be5b7f04bd488628d..e58d33a9977c6b86969eb9ce53ece9b301b3aa11 100644 (file)
@@ -1,5 +1,10 @@
+.psci
 npm-debug.log
+index.json
+index.js
 node_modules/
+bower_components/
+build/
 example/node_modules/
 example/bower_components/
 example/dist/
diff --git a/MODULE.md b/MODULE.md
new file mode 100644 (file)
index 0000000..39d9d3a
--- /dev/null
+++ b/MODULE.md
@@ -0,0 +1,226 @@
+# Module Documentation
+
+## Module PursLoader.ChildProcess
+
+#### `ChildProcess`
+
+``` purescript
+data ChildProcess :: !
+```
+
+
+#### `spawn`
+
+``` purescript
+spawn :: forall eff. String -> [String] -> Aff (cp :: ChildProcess | eff) String
+```
+
+
+
+## Module PursLoader.FS
+
+#### `FS`
+
+``` purescript
+data FS :: !
+```
+
+
+#### `readFileUtf8`
+
+``` purescript
+readFileUtf8 :: forall eff. String -> Aff (fs :: FS | eff) String
+```
+
+
+#### `readFileUtf8Sync`
+
+``` purescript
+readFileUtf8Sync :: forall eff. String -> Eff (fs :: FS | eff) String
+```
+
+
+
+## Module PursLoader.Glob
+
+#### `Glob`
+
+``` purescript
+data Glob :: !
+```
+
+
+#### `glob`
+
+``` purescript
+glob :: forall eff. String -> Aff (glob :: Glob | eff) [String]
+```
+
+
+
+## Module PursLoader.Loader
+
+#### `LoaderEff`
+
+``` purescript
+type LoaderEff eff a = Eff (fs :: FS, cp :: ChildProcess, glob :: Glob, loader :: Loader | eff) a
+```
+
+
+#### `loader`
+
+``` purescript
+loader :: forall eff. LoaderRef -> String -> LoaderEff eff Unit
+```
+
+
+#### `loaderFn`
+
+``` purescript
+loaderFn :: forall eff. Fn2 LoaderRef String (LoaderEff eff Unit)
+```
+
+
+
+## Module PursLoader.LoaderRef
+
+#### `LoaderRef`
+
+``` purescript
+data LoaderRef
+```
+
+
+#### `Loader`
+
+``` purescript
+data Loader :: !
+```
+
+
+#### `async`
+
+``` purescript
+async :: forall eff a. LoaderRef -> Eff (loader :: Loader | eff) (Maybe Error -> a -> Eff (loader :: Loader | eff) Unit)
+```
+
+
+#### `cacheable`
+
+``` purescript
+cacheable :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit
+```
+
+
+#### `clearDependencies`
+
+``` purescript
+clearDependencies :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit
+```
+
+
+#### `resourcePath`
+
+``` purescript
+resourcePath :: LoaderRef -> String
+```
+
+
+#### `addDependency`
+
+``` purescript
+addDependency :: forall eff. LoaderRef -> String -> Eff (loader :: Loader | eff) Unit
+```
+
+
+#### `query`
+
+``` purescript
+query :: LoaderRef -> String
+```
+
+
+
+## Module PursLoader.LoaderUtil
+
+#### `getRemainingRequest`
+
+``` purescript
+getRemainingRequest :: LoaderRef -> String
+```
+
+
+#### `parseQuery`
+
+``` purescript
+parseQuery :: String -> Foreign
+```
+
+
+
+## Module PursLoader.OS
+
+#### `eol`
+
+``` purescript
+eol :: String
+```
+
+
+
+## Module PursLoader.Options
+
+#### `isForeignOptions`
+
+``` purescript
+instance isForeignOptions :: IsForeign Options
+```
+
+
+#### `pscMakeOutputOption`
+
+``` purescript
+pscMakeOutputOption :: Foreign -> Maybe String
+```
+
+
+#### `pscMakeOptions`
+
+``` purescript
+pscMakeOptions :: Foreign -> [String]
+```
+
+
+
+## Module PursLoader.Path
+
+#### `dirname`
+
+``` purescript
+dirname :: String -> String
+```
+
+
+#### `join`
+
+``` purescript
+join :: [String] -> String
+```
+
+
+#### `relative`
+
+``` purescript
+relative :: String -> String -> String
+```
+
+
+#### `resolve`
+
+``` purescript
+resolve :: String -> String
+```
+
+
+
+
diff --git a/bower.json b/bower.json
new file mode 100644 (file)
index 0000000..dddddf9
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "name": "purs-loader",
+  "private": true,
+  "devDependencies": {
+    "purescript-aff": "~0.9.1",
+    "purescript-exceptions": "~0.2.3",
+    "purescript-strings": "~0.4.5",
+    "purescript-maybe": "~0.2.2",
+    "purescript-foreign": "~0.4.2",
+    "purescript-foldable-traversable": "~0.3.1",
+    "purescript-tuples": "~0.3.4",
+    "purescript-maps": "~0.3.3",
+    "purescript-arrays": "~0.3.7",
+    "purescript-monad-eff": "~0.1.0"
+  }
+}
diff --git a/entry.js b/entry.js
new file mode 100644 (file)
index 0000000..87f52d3
--- /dev/null
+++ b/entry.js
@@ -0,0 +1,11 @@
+'use strict';
+
+var pursLoader = require('PursLoader.Loader');
+
+function loader(source) {
+  var ref = this;
+  var result = pursLoader.loaderFn(ref, source);
+  return result();
+}
+
+module.exports = loader;
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644 (file)
index 0000000..e217480
--- /dev/null
@@ -0,0 +1,68 @@
+'use strict';
+
+var path = require('path');
+
+var gulp = require('gulp');
+
+var gutil = require('gulp-util');
+
+var plumber = require('gulp-plumber');
+
+var purescript = require('gulp-purescript');
+
+var sequence = require('run-sequence');
+
+var del = require('del');
+
+var config = { del: ['build', 'index.js']
+             , purescript: { src: [ 'bower_components/purescript-*/src/**/*.purs*'
+                                  , 'src/**/*.purs'
+                                  ]
+                           , dest: 'build'
+                           , docs: 'MODULE.md'
+                           }
+             }
+             ;
+
+function error(e) {
+  gutil.log(gutil.colors.magenta('>>>> Error <<<<') + '\n' + e.toString().trim());
+  this.emit('end');
+}
+
+gulp.task('del', function(cb){
+  del(config.del, cb);
+});
+
+gulp.task('make', function(){
+  return gulp.src(config.purescript.src).
+         pipe(plumber()).
+         pipe(purescript.pscMake({output: config.purescript.dest})).
+         on('error', error);
+});
+
+gulp.task('psci', function(){
+  return gulp.src(config.purescript.src).
+         pipe(plumber()).
+         pipe(purescript.dotPsci()).
+         on('error', error);
+});
+
+gulp.task('docs', function(){
+  return gulp.src(config.purescript.src[1]).
+         pipe(plumber()).
+         pipe(purescript.pscDocs()).
+         on('error', error).
+         pipe(gulp.dest(config.purescript.docs));
+});
+
+gulp.task('watch', function(){
+  gulp.watch(config.purescript.src, ['make']);
+});
+
+gulp.task('default', function(callback){
+  sequence('del', 'make', ['psci', 'docs'], callback);
+});
+
+gulp.task('build', function(callback){
+  sequence('del', 'make', callback);
+});
diff --git a/index.js b/index.js
deleted file mode 100644 (file)
index 449c841..0000000
--- a/index.js
+++ /dev/null
@@ -1,164 +0,0 @@
-'use strict';
-
-var os = require('os');
-
-var cp = require('child_process');
-
-var path = require('path')
-
-var fs = require('fs');
-
-var glob = require('glob');
-
-var lodash = require('lodash');
-
-var chalk = require('chalk');
-
-var lu = require('loader-utils');
-
-var cwd = process.cwd();
-
-var MODULE_RE = /(?:^|\n)module\s+([\w\.]+)/i;
-
-var IMPORT_RE = /^\s*import\s+(?:qualified\s+)?([\w\.]+)/i;
-
-var BOWER_PATTERN = path.join('bower_components', 'purescript-*', 'src');
-
-var PSC_MAKE = 'psc-make';
-
-var OUTPUT = 'output';
-
-var OPTIONS = {
-  'no-prelude': '--no-prelude',
-  'no-opts': '--no-opts',
-  'no-magic-do': '--no-magic-do',
-  'no-tco': '--no-tco',
-  'verbose-errors': '--verbose-errors',
-  'output': '--output'
-};
-
-function pattern(root) {
-  var as = [ BOWER_PATTERN, root ];
-  return path.join('{' + as.join(',') + '}', '**', '*.purs');
-}
-
-function mkOptions(query) {
-  return lodash.foldl(lodash.keys(query), function(acc, k){
-    var h = function(v){return acc.concat(query[k] && OPTIONS[k] ? [v] : []);}
-    if (k === OUTPUT) return h(OPTIONS[k] + '=' + query[k]);
-    else return h(OPTIONS[k]);
-  }, []);
-}
-
-function mkGraph(files) {
-  var graph = {};
-
-  files.forEach(function(file){
-    var source = fs.readFileSync(file, {encoding: 'utf-8'});
-
-    var result = MODULE_RE.exec(source);
-
-    var module = result ? result[1] : null;
-
-    var imports =
-      lodash.foldl(source.split(os.EOL), function(b, a){
-        var result = IMPORT_RE.exec(a);
-        if (result) b.push(result[1]);
-        return b;
-      }, [])
-    ;
-
-    if (module) {
-      graph[module] = {
-        file: file,
-        imports: imports || []
-      };
-    }
-  });
-
-  return graph;
-}
-
-function findDeps(graph, module) {
-  function go(acc, module){
-    var node = graph[module];
-
-    var imports = node && node.imports;
-
-    if (lodash.isEmpty(imports)) return acc;
-    else {
-      var deps =
-        lodash.map(imports, function(i){
-          return go(acc.concat(imports), i);
-        })
-      ;
-      return lodash.flatten(deps);
-    }
-  }
-
-  return lodash.unique(go([], module));
-}
-
-function loader(source) {
-  this.cacheable && this.cacheable();
-
-  this.clearDependencies();
-
-  this.addDependency(this.resourcePath);
-
-  var callback = this.async();
-
-  var request = lu.getRemainingRequest(this)
-
-  var root = path.dirname(path.relative(cwd, request));
-
-  var query = lu.parseQuery(this.query);
-
-  var opts = mkOptions(query);
-
-  var that = this;
-
-  glob(pattern(root), function(e, files){
-    if (e !== null) callback(e);
-    else {
-      var cmd = cp.spawn(PSC_MAKE, opts.concat(files));
-
-      var graph = mkGraph(files);
-
-      cmd.on('close', function(e){
-        if (e) callback(e);
-        else {
-          var result = MODULE_RE.exec(source);
-
-          var module = result ? result[1] : '';
-
-          var dependencies = findDeps(graph, module);
-
-          var indexPath = path.join(query[OUTPUT] || OUTPUT, module, 'index.js');
-
-          fs.readFile(indexPath, 'utf-8', function(e, output){
-            if (e) callback(e);
-            else {
-              dependencies.forEach(function(dep){
-                var module = graph[dep];
-                if (module) that.addDependency(path.resolve(module.file));
-              });
-
-              callback(null, output);
-            }
-          });
-        }
-      });
-
-      cmd.stdout.on('data', function(stdout){
-        console.log('Stdout from \'' + chalk.cyan(PSC_MAKE) + '\'\n' + chalk.magenta(stdout));
-      });
-
-      cmd.stderr.on('data', function(stderr){
-        console.log('Stderr from \'' + chalk.cyan(PSC_MAKE) + '\'\n' + chalk.magenta(stderr));
-      });
-    }
-  });
-}
-
-module.exports = loader;
index 4d458b866966c87d725e46efa4d52e5a6f8ca1c4..56c6b39b691cbc44922fb98c9fc6467ecf2d08ce 100644 (file)
@@ -2,24 +2,32 @@
   "name": "purs-loader",
   "version": "0.0.3",
   "description": "PureScript loader for webpack",
-  "main": "index.js",
-  "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+  "license": "MIT",
+  "repository": "ethul/purs-loader",
+  "author": {
+    "name": "Eric Thul",
+    "email": "thul.eric@gmail.com"
   },
-  "repository": {
-    "type": "git",
-    "url": "git@github.com:ethul/purs-loader.git"
+  "scripts": {
+    "build": "npm run-script build:compile && npm run-script build:package",
+    "build:compile": "gulp build",
+    "build:package": "./node_modules/.bin/webpack --progress --colors --profile --bail",
+    "build:json": "./node_modules/.bin/webpack --progress --colors --profile --bail --json > index.json"
   },
-  "author": "Eric Thul",
-  "license": "MIT",
-  "bugs": {
-    "url": "https://github.com/ethul/purs-loader/issues"
+  "files": [
+    "index.js"
+  ],
+  "devDependencies": {
+    "del": "^1.1.1",
+    "gulp": "^3.8.11",
+    "gulp-plumber": "^1.0.0",
+    "gulp-purescript": "^0.3.1",
+    "gulp-util": "^3.0.4",
+    "run-sequence": "^1.0.2",
+    "webpack": "^1.8.4"
   },
-  "homepage": "https://github.com/ethul/purs-loader",
   "dependencies": {
-    "chalk": "^0.5.1",
-    "glob": "4.0.6",
-    "loader-utils": "^0.2.3",
-    "lodash": "^2.4.1"
+    "glob": "^5.0.3",
+    "loader-utils": "^0.2.6"
   }
 }
diff --git a/src/ChildProcess.purs b/src/ChildProcess.purs
new file mode 100644 (file)
index 0000000..c9ff23b
--- /dev/null
@@ -0,0 +1,40 @@
+module PursLoader.ChildProcess
+  ( ChildProcess()
+  , spawn
+  ) where
+
+import Control.Monad.Aff (Aff(), makeAff)
+import Control.Monad.Eff (Eff())
+import Control.Monad.Eff.Exception (Error())
+
+import Data.Function
+
+foreign import data ChildProcess :: !
+
+spawn :: forall eff. String -> [String] -> Aff (cp :: ChildProcess | eff) String
+spawn command args = makeAff $ runFn4 spawnFn command args
+
+foreign import spawnFn """
+function spawnFn(command, args, errback, callback) {
+  return function(){
+    var child_process = require('child_process');
+
+    var process = child_process.spawn(command, args);
+
+    var stdout = new Buffer(0);
+
+    process.stdout.on('data', function(data){
+      stdout = Buffer.concat([stdout, new Buffer(data)]);
+    });
+
+    process.on('close', function(code){
+      if (code !== 0) errback(new Error(stdout.toString()))();
+      else callback(stdout.toString())();
+    });
+  };
+}
+""" :: forall eff. Fn4 String
+                       [String]
+                       (Error -> Eff (cp :: ChildProcess | eff) Unit)
+                       (String -> Eff (cp :: ChildProcess | eff) Unit)
+                       (Eff (cp :: ChildProcess | eff) Unit)
diff --git a/src/FS.purs b/src/FS.purs
new file mode 100644 (file)
index 0000000..68fe2f9
--- /dev/null
@@ -0,0 +1,45 @@
+module PursLoader.FS
+  ( FS()
+  , readFileUtf8
+  , readFileUtf8Sync
+  ) where
+
+import Control.Monad.Aff (Aff(), makeAff)
+import Control.Monad.Eff (Eff())
+import Control.Monad.Eff.Exception (Error())
+
+import Data.Function
+
+foreign import data FS :: !
+
+readFileUtf8 :: forall eff. String -> Aff (fs :: FS | eff) String
+readFileUtf8 filepath = makeAff $ runFn3 readFileUtf8Fn filepath
+
+readFileUtf8Sync :: forall eff. String -> Eff (fs :: FS | eff) String
+readFileUtf8Sync filepath = readFileUtf8SyncFn filepath
+
+foreign import readFileUtf8Fn """
+function readFileUtf8Fn(filepath, errback, callback) {
+  return function(){
+    var fs = require('fs');
+
+    fs.readFile(filepath, 'utf-8', function(e, data){
+      if (e) errback(e)();
+      else callback(data)();
+    });
+  };
+}
+""" :: forall eff. Fn3 String
+                       (Error -> Eff (fs :: FS | eff) Unit)
+                       (String -> Eff (fs :: FS | eff) Unit)
+                       (Eff (fs :: FS | eff) Unit)
+
+foreign import readFileUtf8SyncFn """
+function readFileUtf8SyncFn(filepath) {
+  return function(){
+    var fs = require('fs');
+
+    return fs.readFileSync(filepath, {encoding: 'utf-8'});
+  };
+}
+""" :: forall eff. String -> (Eff (fs :: FS | eff) String)
diff --git a/src/Glob.purs b/src/Glob.purs
new file mode 100644 (file)
index 0000000..7bc9212
--- /dev/null
@@ -0,0 +1,31 @@
+module PursLoader.Glob
+  ( Glob()
+  , glob
+  ) where
+
+import Control.Monad.Aff (Aff(), makeAff)
+import Control.Monad.Eff (Eff())
+import Control.Monad.Eff.Exception (Error())
+
+import Data.Function
+
+foreign import data Glob :: !
+
+glob :: forall eff. String -> Aff (glob :: Glob | eff) [String]
+glob pattern = makeAff $ runFn3 globFn pattern
+
+foreign import globFn """
+function globFn(pattern, errback, callback) {
+  return function(){
+    var glob = require('glob');
+
+    glob(pattern, function(e, data){
+      if (e) errback(e)();
+      else callback(data)();
+    });
+  };
+}
+""" :: forall eff. Fn3 String
+                       (Error -> Eff (glob :: Glob | eff) Unit)
+                       ([String] -> Eff (glob :: Glob | eff) Unit)
+                       (Eff (glob :: Glob | eff) Unit)
diff --git a/src/Loader.purs b/src/Loader.purs
new file mode 100644 (file)
index 0000000..523aa7a
--- /dev/null
@@ -0,0 +1,114 @@
+module PursLoader.Loader
+  ( LoaderEff()
+  , loader
+  , loaderFn
+  ) where
+
+import Control.Monad.Aff (Aff(), runAff)
+import Control.Monad.Eff (Eff())
+import Control.Monad.Eff.Class (liftEff)
+import Control.Monad.Eff.Exception (error)
+
+import Data.Array ((!!), catMaybes, concat, nub, null)
+import Data.Function (Fn2(), mkFn2)
+import Data.Maybe (Maybe(..), fromMaybe, maybe)
+import Data.String (joinWith, split)
+import Data.String.Regex (Regex(), match, noFlags, regex)
+import Data.StrMap (StrMap(), fromList, lookup)
+import Data.Traversable (sequence)
+import Data.Tuple.Nested (tuple2)
+
+import PursLoader.ChildProcess (ChildProcess(), spawn)
+import PursLoader.FS (FS(), readFileUtf8, readFileUtf8Sync)
+import PursLoader.Glob (Glob(), glob)
+import PursLoader.LoaderRef (LoaderRef(), Loader(), async, cacheable, clearDependencies, addDependency, query, resourcePath)
+import PursLoader.LoaderUtil (getRemainingRequest, parseQuery)
+import PursLoader.OS (eol)
+import PursLoader.Options (pscMakeOptions, pscMakeDefaultOutput, pscMakeOutputOption)
+import PursLoader.Path (dirname, join, relative, resolve)
+
+foreign import cwd "var cwd = process.cwd();" :: String
+
+moduleRegex = regex "(?:^|\\n)module\\s+([\\w\\.]+)" noFlags { ignoreCase = true }
+
+importRegex = regex "^\\s*import\\s+(?:qualified\\s+)?([\\w\\.]+)" noFlags { ignoreCase = true }
+
+bowerPattern = join [ "bower_components", "purescript-*", "src" ]
+
+pscMakeCommand = "psc-make"
+
+indexFilename = "index.js"
+
+(!!!) = flip (!!)
+
+pursPattern :: String -> String
+pursPattern root = join [ "{" ++ joinWith "," [ bowerPattern, root ] ++ "}"
+                        , "**"
+                        , "*.purs"
+                        ]
+
+type GraphModule = { file :: String, imports :: [String] }
+
+type Graph = StrMap GraphModule
+
+mkGraph :: forall eff. [String] -> Eff (fs :: FS | eff) Graph
+mkGraph files = (fromList <<< catMaybes) <$> sequence (parse <$> files)
+  where parse file = do source <- readFileUtf8Sync file
+                        let key = match moduleRegex source >>= (!!!) 1
+                            lines = split eol source
+                            imports = catMaybes $ (\a -> match importRegex a >>= (!!!) 1) <$> lines
+                        return $ (\a -> tuple2 a { file: file, imports: imports }) <$> key
+
+mkDeps :: forall eff. String -> Graph -> [String]
+mkDeps key graph = nub $ go [] key
+  where go acc key =
+    maybe acc (\a -> if null a.imports
+                       then acc
+                       else concat $ go (acc <> a.imports) <$> a.imports) (lookup key graph)
+
+addDeps :: forall eff. LoaderRef -> Graph -> [String] -> Eff (loader :: Loader | eff) Unit
+addDeps ref graph deps = const unit <$> (sequence $ add <$> deps)
+  where add dep = let res = lookup dep graph
+                      path = (\a -> resolve a.file) <$> res
+                   in maybe (pure unit) (addDependency ref) path
+
+type LoaderAff eff a = Aff (loader :: Loader, glob :: Glob, cp :: ChildProcess, fs :: FS | eff) a
+
+loader' :: forall eff. LoaderRef -> String -> LoaderAff eff (Maybe String)
+loader' ref source = do
+  liftEff $ cacheable ref
+
+  let request = getRemainingRequest ref
+      root = dirname $ relative cwd request
+      parsed = parseQuery $ query ref
+      opts = pscMakeOptions parsed
+      pattern = pursPattern root
+      key = match moduleRegex source >>= (!!!) 1
+
+  files <- glob pattern
+  graph <- liftEff $ mkGraph files
+
+  let deps = fromMaybe [] $ flip mkDeps graph <$> key
+      outputPath = fromMaybe pscMakeDefaultOutput $ pscMakeOutputOption parsed
+      indexPath = (\a -> join [ outputPath, a, indexFilename ]) <$> key
+
+  liftEff $ clearDependencies ref
+  liftEff $ addDependency ref (resourcePath ref)
+  liftEff $ addDeps ref graph deps
+
+  spawn pscMakeCommand (opts <> files)
+  indexFile <- sequence $ readFileUtf8 <$> indexPath
+  return indexFile
+
+type LoaderEff eff a = Eff (loader :: Loader, glob :: Glob, cp :: ChildProcess, fs :: FS | eff) a
+
+loader :: forall eff. LoaderRef -> String -> LoaderEff eff Unit
+loader ref source = do
+  callback <- async ref
+  runAff (\e -> callback (Just e) "")
+         (maybe (callback (Just $ error "Loader has failed to run") "")
+                (callback Nothing))
+         (loader' ref source)
+
+loaderFn :: forall eff. Fn2 LoaderRef String (LoaderEff eff Unit)
+loaderFn = mkFn2 loader
diff --git a/src/LoaderRef.purs b/src/LoaderRef.purs
new file mode 100644 (file)
index 0000000..2d62754
--- /dev/null
@@ -0,0 +1,75 @@
+module PursLoader.LoaderRef
+  ( LoaderRef()
+  , Loader()
+  , async
+  , cacheable
+  , clearDependencies
+  , resourcePath
+  , addDependency
+  , query
+  ) where
+
+import Control.Monad.Eff (Eff())
+import Control.Monad.Eff.Exception (Error())
+
+import Data.Foreign (Foreign())
+import Data.Function (Fn3(), runFn3)
+import Data.Maybe (Maybe(), fromMaybe, isJust)
+
+data LoaderRef
+
+foreign import data Loader :: !
+
+foreign import asyncFn """
+function asyncFn(isJust, fromMaybe, ref){
+  return function(){
+    var callback = ref.async();
+    return function(error){
+      return function(value){
+        return function(){
+          return isJust(error) ? callback(fromMaybe(new Error())(error))
+                               : callback(null, value);
+        };
+      };
+    };
+  };
+}""" :: forall eff a. Fn3 (Maybe Error -> Boolean)
+                          (Error -> Maybe Error -> Error)
+                          LoaderRef
+                          (Eff (loader :: Loader | eff) (Maybe Error -> a -> Eff (loader :: Loader | eff) Unit))
+
+async :: forall eff a. LoaderRef -> Eff (loader :: Loader | eff) (Maybe Error -> a -> Eff (loader :: Loader | eff) Unit)
+async ref = runFn3 asyncFn isJust fromMaybe ref
+
+foreign import cacheable """
+function cacheable(ref){
+  return function(){
+    return ref.cacheable && ref.cacheable();
+  };
+}""" :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit
+
+foreign import clearDependencies """
+function clearDependencies(ref){
+  return function(){
+    return ref.clearDependencies();
+  };
+}""" :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit
+
+foreign import resourcePath """
+function resourcePath(ref){
+  return ref.resourcePath;
+}""" :: LoaderRef -> String
+
+foreign import addDependency """
+function addDependency(ref){
+  return function(dep){
+    return function(){
+      return ref.addDependency(dep);
+    };
+  };
+}""" :: forall eff. LoaderRef -> String -> Eff (loader :: Loader | eff) Unit
+
+foreign import query """
+function query(ref){
+  return ref.query;
+}""" :: LoaderRef -> String
diff --git a/src/LoaderUtil.purs b/src/LoaderUtil.purs
new file mode 100644 (file)
index 0000000..f22be44
--- /dev/null
@@ -0,0 +1,20 @@
+module PursLoader.LoaderUtil
+  ( getRemainingRequest
+  , parseQuery
+  ) where
+
+import Data.Foreign (Foreign())
+
+import PursLoader.LoaderRef (LoaderRef())
+
+foreign import getRemainingRequest """
+function getRemainingRequest(ref){
+  var loaderUtils = require('loader-utils');
+  return loaderUtils.getRemainingRequest(ref);
+}""" :: LoaderRef -> String
+
+foreign import parseQuery """
+function parseQuery(query){
+  var loaderUtils = require('loader-utils');
+  return loaderUtils.parseQuery(query);
+}""" :: String -> Foreign
diff --git a/src/OS.purs b/src/OS.purs
new file mode 100644 (file)
index 0000000..590c3d6
--- /dev/null
@@ -0,0 +1,3 @@
+module PursLoader.OS (eol) where
+
+foreign import eol "var eol = require('os').EOL;" :: String
diff --git a/src/Options.purs b/src/Options.purs
new file mode 100644 (file)
index 0000000..b96cddc
--- /dev/null
@@ -0,0 +1,72 @@
+module PursLoader.Options
+  ( pscMakeOptions
+  , pscMakeDefaultOutput
+  , pscMakeOutputOption
+  ) where
+
+import Data.Either (either)
+
+import Data.Foreign (Foreign(), F())
+import Data.Foreign.Class (IsForeign, read, readProp)
+import Data.Foreign.NullOrUndefined (NullOrUndefined(), runNullOrUndefined)
+
+import Data.Maybe (Maybe(..), maybe)
+
+noPreludeOpt = "no-prelude"
+
+noOptsOpt = "no-opts"
+
+noMagicDoOpt = "no-magic-do"
+
+noTcoOpt = "no-tco"
+
+verboseErrorsOpt = "verbose-errors"
+
+outputOpt = "output"
+
+pscMakeDefaultOutput = "output"
+
+newtype Options
+  = Options { noPrelude :: NullOrUndefined Boolean
+            , noOpts :: NullOrUndefined Boolean
+            , noMagicDo :: NullOrUndefined Boolean
+            , noTco :: NullOrUndefined Boolean
+            , verboseErrors :: NullOrUndefined Boolean
+            , output :: NullOrUndefined String
+            }
+
+instance isForeignOptions :: IsForeign Options where
+  read obj = (\a b c d e f ->
+             Options { noPrelude: a
+                     , noOpts: b
+                     , noMagicDo: c
+                     , noTco: d
+                     , verboseErrors: e
+                     , output: f
+                     }) <$> readProp noPreludeOpt obj
+                        <*> readProp noOptsOpt obj
+                        <*> readProp noMagicDoOpt obj
+                        <*> readProp noTcoOpt obj
+                        <*> readProp verboseErrorsOpt obj
+                        <*> readProp outputOpt obj
+
+booleanOpt :: String -> NullOrUndefined Boolean -> [String]
+booleanOpt key opt = maybe [] (\a -> if a then ["--" ++ key] else []) (runNullOrUndefined opt)
+
+stringOpt :: String -> NullOrUndefined String -> [String]
+stringOpt key opt = maybe [] (\a -> ["--" ++ key ++ "=" ++ a]) (runNullOrUndefined opt)
+
+pscMakeOutputOption :: Foreign -> Maybe String
+pscMakeOutputOption query = either (const Nothing)
+                                   (\(Options a) -> runNullOrUndefined a.output)
+                                   (read query)
+
+pscMakeOptions :: Foreign -> [String]
+pscMakeOptions query = either (const []) fold parsed
+  where parsed = read query :: F Options
+        fold (Options a) = booleanOpt noPreludeOpt a.noPrelude <>
+                           booleanOpt noOptsOpt a.noOpts <>
+                           booleanOpt noMagicDoOpt a.noMagicDo <>
+                           booleanOpt noTcoOpt a.noTco <>
+                           booleanOpt verboseErrorsOpt a.verboseErrors <>
+                           stringOpt outputOpt a.output
diff --git a/src/Path.purs b/src/Path.purs
new file mode 100644 (file)
index 0000000..e071e35
--- /dev/null
@@ -0,0 +1,36 @@
+module PursLoader.Path
+  ( dirname
+  , join
+  , relative
+  , resolve
+  ) where
+
+foreign import dirname """
+function dirname(filepath) {
+  var path = require('path');
+  return path.dirname(filepath);
+}
+""" :: String -> String
+
+foreign import join """
+function join(parts) {
+  var path = require('path');
+  return path.join.apply(path, parts);
+}
+""" :: [String] -> String
+
+foreign import relative """
+function relative(from) {
+  return function(to){
+    var path = require('path');
+    return path.relative(from, to);
+  };
+}
+""" :: String -> String -> String
+
+foreign import resolve """
+function resolve(filepath) {
+  var path = require('path');
+  return path.resolve(filepath);
+}
+""" :: String -> String
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644 (file)
index 0000000..11b9069
--- /dev/null
@@ -0,0 +1,29 @@
+'use strict';
+
+var path = require('path');
+
+var webpack = require('webpack');
+
+var noErrorsPlugin = webpack.NoErrorsPlugin;
+
+var dedupePlugin = webpack.optimize.DedupePlugin;
+
+var config
+  = { cache: true
+    , target: 'node'
+    , entry: { index: './entry' }
+    , externals: { 'glob': 'commonjs glob'
+                 , 'loader-utils': 'commonjs loader-utils'
+                 }
+    , output: { path: __dirname
+              , filename: '[name].js'
+              , libraryTarget: 'commonjs2'
+              }
+    , plugins: [ new noErrorsPlugin()
+               , new dedupePlugin()
+               ]
+    , resolve: { modulesDirectories: [ 'build' ] }
+    }
+    ;
+
+module.exports = config;