aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ChildProcess.purs40
-rw-r--r--src/FS.purs45
-rw-r--r--src/Glob.purs31
-rw-r--r--src/Loader.purs114
-rw-r--r--src/LoaderRef.purs75
-rw-r--r--src/LoaderUtil.purs20
-rw-r--r--src/OS.purs3
-rw-r--r--src/Options.purs72
-rw-r--r--src/Path.purs36
9 files changed, 436 insertions, 0 deletions
diff --git a/src/ChildProcess.purs b/src/ChildProcess.purs
new file mode 100644
index 0000000..c9ff23b
--- /dev/null
+++ b/src/ChildProcess.purs
@@ -0,0 +1,40 @@
1module PursLoader.ChildProcess
2 ( ChildProcess()
3 , spawn
4 ) where
5
6import Control.Monad.Aff (Aff(), makeAff)
7import Control.Monad.Eff (Eff())
8import Control.Monad.Eff.Exception (Error())
9
10import Data.Function
11
12foreign import data ChildProcess :: !
13
14spawn :: forall eff. String -> [String] -> Aff (cp :: ChildProcess | eff) String
15spawn command args = makeAff $ runFn4 spawnFn command args
16
17foreign import spawnFn """
18function spawnFn(command, args, errback, callback) {
19 return function(){
20 var child_process = require('child_process');
21
22 var process = child_process.spawn(command, args);
23
24 var stdout = new Buffer(0);
25
26 process.stdout.on('data', function(data){
27 stdout = Buffer.concat([stdout, new Buffer(data)]);
28 });
29
30 process.on('close', function(code){
31 if (code !== 0) errback(new Error(stdout.toString()))();
32 else callback(stdout.toString())();
33 });
34 };
35}
36""" :: forall eff. Fn4 String
37 [String]
38 (Error -> Eff (cp :: ChildProcess | eff) Unit)
39 (String -> Eff (cp :: ChildProcess | eff) Unit)
40 (Eff (cp :: ChildProcess | eff) Unit)
diff --git a/src/FS.purs b/src/FS.purs
new file mode 100644
index 0000000..68fe2f9
--- /dev/null
+++ b/src/FS.purs
@@ -0,0 +1,45 @@
1module PursLoader.FS
2 ( FS()
3 , readFileUtf8
4 , readFileUtf8Sync
5 ) where
6
7import Control.Monad.Aff (Aff(), makeAff)
8import Control.Monad.Eff (Eff())
9import Control.Monad.Eff.Exception (Error())
10
11import Data.Function
12
13foreign import data FS :: !
14
15readFileUtf8 :: forall eff. String -> Aff (fs :: FS | eff) String
16readFileUtf8 filepath = makeAff $ runFn3 readFileUtf8Fn filepath
17
18readFileUtf8Sync :: forall eff. String -> Eff (fs :: FS | eff) String
19readFileUtf8Sync filepath = readFileUtf8SyncFn filepath
20
21foreign import readFileUtf8Fn """
22function readFileUtf8Fn(filepath, errback, callback) {
23 return function(){
24 var fs = require('fs');
25
26 fs.readFile(filepath, 'utf-8', function(e, data){
27 if (e) errback(e)();
28 else callback(data)();
29 });
30 };
31}
32""" :: forall eff. Fn3 String
33 (Error -> Eff (fs :: FS | eff) Unit)
34 (String -> Eff (fs :: FS | eff) Unit)
35 (Eff (fs :: FS | eff) Unit)
36
37foreign import readFileUtf8SyncFn """
38function readFileUtf8SyncFn(filepath) {
39 return function(){
40 var fs = require('fs');
41
42 return fs.readFileSync(filepath, {encoding: 'utf-8'});
43 };
44}
45""" :: forall eff. String -> (Eff (fs :: FS | eff) String)
diff --git a/src/Glob.purs b/src/Glob.purs
new file mode 100644
index 0000000..7bc9212
--- /dev/null
+++ b/src/Glob.purs
@@ -0,0 +1,31 @@
1module PursLoader.Glob
2 ( Glob()
3 , glob
4 ) where
5
6import Control.Monad.Aff (Aff(), makeAff)
7import Control.Monad.Eff (Eff())
8import Control.Monad.Eff.Exception (Error())
9
10import Data.Function
11
12foreign import data Glob :: !
13
14glob :: forall eff. String -> Aff (glob :: Glob | eff) [String]
15glob pattern = makeAff $ runFn3 globFn pattern
16
17foreign import globFn """
18function globFn(pattern, errback, callback) {
19 return function(){
20 var glob = require('glob');
21
22 glob(pattern, function(e, data){
23 if (e) errback(e)();
24 else callback(data)();
25 });
26 };
27}
28""" :: forall eff. Fn3 String
29 (Error -> Eff (glob :: Glob | eff) Unit)
30 ([String] -> Eff (glob :: Glob | eff) Unit)
31 (Eff (glob :: Glob | eff) Unit)
diff --git a/src/Loader.purs b/src/Loader.purs
new file mode 100644
index 0000000..523aa7a
--- /dev/null
+++ b/src/Loader.purs
@@ -0,0 +1,114 @@
1module PursLoader.Loader
2 ( LoaderEff()
3 , loader
4 , loaderFn
5 ) where
6
7import Control.Monad.Aff (Aff(), runAff)
8import Control.Monad.Eff (Eff())
9import Control.Monad.Eff.Class (liftEff)
10import Control.Monad.Eff.Exception (error)
11
12import Data.Array ((!!), catMaybes, concat, nub, null)
13import Data.Function (Fn2(), mkFn2)
14import Data.Maybe (Maybe(..), fromMaybe, maybe)
15import Data.String (joinWith, split)
16import Data.String.Regex (Regex(), match, noFlags, regex)
17import Data.StrMap (StrMap(), fromList, lookup)
18import Data.Traversable (sequence)
19import Data.Tuple.Nested (tuple2)
20
21import PursLoader.ChildProcess (ChildProcess(), spawn)
22import PursLoader.FS (FS(), readFileUtf8, readFileUtf8Sync)
23import PursLoader.Glob (Glob(), glob)
24import PursLoader.LoaderRef (LoaderRef(), Loader(), async, cacheable, clearDependencies, addDependency, query, resourcePath)
25import PursLoader.LoaderUtil (getRemainingRequest, parseQuery)
26import PursLoader.OS (eol)
27import PursLoader.Options (pscMakeOptions, pscMakeDefaultOutput, pscMakeOutputOption)
28import PursLoader.Path (dirname, join, relative, resolve)
29
30foreign import cwd "var cwd = process.cwd();" :: String
31
32moduleRegex = regex "(?:^|\\n)module\\s+([\\w\\.]+)" noFlags { ignoreCase = true }
33
34importRegex = regex "^\\s*import\\s+(?:qualified\\s+)?([\\w\\.]+)" noFlags { ignoreCase = true }
35
36bowerPattern = join [ "bower_components", "purescript-*", "src" ]
37
38pscMakeCommand = "psc-make"
39
40indexFilename = "index.js"
41
42(!!!) = flip (!!)
43
44pursPattern :: String -> String
45pursPattern root = join [ "{" ++ joinWith "," [ bowerPattern, root ] ++ "}"
46 , "**"
47 , "*.purs"
48 ]
49
50type GraphModule = { file :: String, imports :: [String] }
51
52type Graph = StrMap GraphModule
53
54mkGraph :: forall eff. [String] -> Eff (fs :: FS | eff) Graph
55mkGraph files = (fromList <<< catMaybes) <$> sequence (parse <$> files)
56 where parse file = do source <- readFileUtf8Sync file
57 let key = match moduleRegex source >>= (!!!) 1
58 lines = split eol source
59 imports = catMaybes $ (\a -> match importRegex a >>= (!!!) 1) <$> lines
60 return $ (\a -> tuple2 a { file: file, imports: imports }) <$> key
61
62mkDeps :: forall eff. String -> Graph -> [String]
63mkDeps key graph = nub $ go [] key
64 where go acc key =
65 maybe acc (\a -> if null a.imports
66 then acc
67 else concat $ go (acc <> a.imports) <$> a.imports) (lookup key graph)
68
69addDeps :: forall eff. LoaderRef -> Graph -> [String] -> Eff (loader :: Loader | eff) Unit
70addDeps ref graph deps = const unit <$> (sequence $ add <$> deps)
71 where add dep = let res = lookup dep graph
72 path = (\a -> resolve a.file) <$> res
73 in maybe (pure unit) (addDependency ref) path
74
75type LoaderAff eff a = Aff (loader :: Loader, glob :: Glob, cp :: ChildProcess, fs :: FS | eff) a
76
77loader' :: forall eff. LoaderRef -> String -> LoaderAff eff (Maybe String)
78loader' ref source = do
79 liftEff $ cacheable ref
80
81 let request = getRemainingRequest ref
82 root = dirname $ relative cwd request
83 parsed = parseQuery $ query ref
84 opts = pscMakeOptions parsed
85 pattern = pursPattern root
86 key = match moduleRegex source >>= (!!!) 1
87
88 files <- glob pattern
89 graph <- liftEff $ mkGraph files
90
91 let deps = fromMaybe [] $ flip mkDeps graph <$> key
92 outputPath = fromMaybe pscMakeDefaultOutput $ pscMakeOutputOption parsed
93 indexPath = (\a -> join [ outputPath, a, indexFilename ]) <$> key
94
95 liftEff $ clearDependencies ref
96 liftEff $ addDependency ref (resourcePath ref)
97 liftEff $ addDeps ref graph deps
98
99 spawn pscMakeCommand (opts <> files)
100 indexFile <- sequence $ readFileUtf8 <$> indexPath
101 return indexFile
102
103type LoaderEff eff a = Eff (loader :: Loader, glob :: Glob, cp :: ChildProcess, fs :: FS | eff) a
104
105loader :: forall eff. LoaderRef -> String -> LoaderEff eff Unit
106loader ref source = do
107 callback <- async ref
108 runAff (\e -> callback (Just e) "")
109 (maybe (callback (Just $ error "Loader has failed to run") "")
110 (callback Nothing))
111 (loader' ref source)
112
113loaderFn :: forall eff. Fn2 LoaderRef String (LoaderEff eff Unit)
114loaderFn = mkFn2 loader
diff --git a/src/LoaderRef.purs b/src/LoaderRef.purs
new file mode 100644
index 0000000..2d62754
--- /dev/null
+++ b/src/LoaderRef.purs
@@ -0,0 +1,75 @@
1module PursLoader.LoaderRef
2 ( LoaderRef()
3 , Loader()
4 , async
5 , cacheable
6 , clearDependencies
7 , resourcePath
8 , addDependency
9 , query
10 ) where
11
12import Control.Monad.Eff (Eff())
13import Control.Monad.Eff.Exception (Error())
14
15import Data.Foreign (Foreign())
16import Data.Function (Fn3(), runFn3)
17import Data.Maybe (Maybe(), fromMaybe, isJust)
18
19data LoaderRef
20
21foreign import data Loader :: !
22
23foreign import asyncFn """
24function asyncFn(isJust, fromMaybe, ref){
25 return function(){
26 var callback = ref.async();
27 return function(error){
28 return function(value){
29 return function(){
30 return isJust(error) ? callback(fromMaybe(new Error())(error))
31 : callback(null, value);
32 };
33 };
34 };
35 };
36}""" :: forall eff a. Fn3 (Maybe Error -> Boolean)
37 (Error -> Maybe Error -> Error)
38 LoaderRef
39 (Eff (loader :: Loader | eff) (Maybe Error -> a -> Eff (loader :: Loader | eff) Unit))
40
41async :: forall eff a. LoaderRef -> Eff (loader :: Loader | eff) (Maybe Error -> a -> Eff (loader :: Loader | eff) Unit)
42async ref = runFn3 asyncFn isJust fromMaybe ref
43
44foreign import cacheable """
45function cacheable(ref){
46 return function(){
47 return ref.cacheable && ref.cacheable();
48 };
49}""" :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit
50
51foreign import clearDependencies """
52function clearDependencies(ref){
53 return function(){
54 return ref.clearDependencies();
55 };
56}""" :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit
57
58foreign import resourcePath """
59function resourcePath(ref){
60 return ref.resourcePath;
61}""" :: LoaderRef -> String
62
63foreign import addDependency """
64function addDependency(ref){
65 return function(dep){
66 return function(){
67 return ref.addDependency(dep);
68 };
69 };
70}""" :: forall eff. LoaderRef -> String -> Eff (loader :: Loader | eff) Unit
71
72foreign import query """
73function query(ref){
74 return ref.query;
75}""" :: LoaderRef -> String
diff --git a/src/LoaderUtil.purs b/src/LoaderUtil.purs
new file mode 100644
index 0000000..f22be44
--- /dev/null
+++ b/src/LoaderUtil.purs
@@ -0,0 +1,20 @@
1module PursLoader.LoaderUtil
2 ( getRemainingRequest
3 , parseQuery
4 ) where
5
6import Data.Foreign (Foreign())
7
8import PursLoader.LoaderRef (LoaderRef())
9
10foreign import getRemainingRequest """
11function getRemainingRequest(ref){
12 var loaderUtils = require('loader-utils');
13 return loaderUtils.getRemainingRequest(ref);
14}""" :: LoaderRef -> String
15
16foreign import parseQuery """
17function parseQuery(query){
18 var loaderUtils = require('loader-utils');
19 return loaderUtils.parseQuery(query);
20}""" :: String -> Foreign
diff --git a/src/OS.purs b/src/OS.purs
new file mode 100644
index 0000000..590c3d6
--- /dev/null
+++ b/src/OS.purs
@@ -0,0 +1,3 @@
1module PursLoader.OS (eol) where
2
3foreign import eol "var eol = require('os').EOL;" :: String
diff --git a/src/Options.purs b/src/Options.purs
new file mode 100644
index 0000000..b96cddc
--- /dev/null
+++ b/src/Options.purs
@@ -0,0 +1,72 @@
1module PursLoader.Options
2 ( pscMakeOptions
3 , pscMakeDefaultOutput
4 , pscMakeOutputOption
5 ) where
6
7import Data.Either (either)
8
9import Data.Foreign (Foreign(), F())
10import Data.Foreign.Class (IsForeign, read, readProp)
11import Data.Foreign.NullOrUndefined (NullOrUndefined(), runNullOrUndefined)
12
13import Data.Maybe (Maybe(..), maybe)
14
15noPreludeOpt = "no-prelude"
16
17noOptsOpt = "no-opts"
18
19noMagicDoOpt = "no-magic-do"
20
21noTcoOpt = "no-tco"
22
23verboseErrorsOpt = "verbose-errors"
24
25outputOpt = "output"
26
27pscMakeDefaultOutput = "output"
28
29newtype Options
30 = Options { noPrelude :: NullOrUndefined Boolean
31 , noOpts :: NullOrUndefined Boolean
32 , noMagicDo :: NullOrUndefined Boolean
33 , noTco :: NullOrUndefined Boolean
34 , verboseErrors :: NullOrUndefined Boolean
35 , output :: NullOrUndefined String
36 }
37
38instance isForeignOptions :: IsForeign Options where
39 read obj = (\a b c d e f ->
40 Options { noPrelude: a
41 , noOpts: b
42 , noMagicDo: c
43 , noTco: d
44 , verboseErrors: e
45 , output: f
46 }) <$> readProp noPreludeOpt obj
47 <*> readProp noOptsOpt obj
48 <*> readProp noMagicDoOpt obj
49 <*> readProp noTcoOpt obj
50 <*> readProp verboseErrorsOpt obj
51 <*> readProp outputOpt obj
52
53booleanOpt :: String -> NullOrUndefined Boolean -> [String]
54booleanOpt key opt = maybe [] (\a -> if a then ["--" ++ key] else []) (runNullOrUndefined opt)
55
56stringOpt :: String -> NullOrUndefined String -> [String]
57stringOpt key opt = maybe [] (\a -> ["--" ++ key ++ "=" ++ a]) (runNullOrUndefined opt)
58
59pscMakeOutputOption :: Foreign -> Maybe String
60pscMakeOutputOption query = either (const Nothing)
61 (\(Options a) -> runNullOrUndefined a.output)
62 (read query)
63
64pscMakeOptions :: Foreign -> [String]
65pscMakeOptions query = either (const []) fold parsed
66 where parsed = read query :: F Options
67 fold (Options a) = booleanOpt noPreludeOpt a.noPrelude <>
68 booleanOpt noOptsOpt a.noOpts <>
69 booleanOpt noMagicDoOpt a.noMagicDo <>
70 booleanOpt noTcoOpt a.noTco <>
71 booleanOpt verboseErrorsOpt a.verboseErrors <>
72 stringOpt outputOpt a.output
diff --git a/src/Path.purs b/src/Path.purs
new file mode 100644
index 0000000..e071e35
--- /dev/null
+++ b/src/Path.purs
@@ -0,0 +1,36 @@
1module PursLoader.Path
2 ( dirname
3 , join
4 , relative
5 , resolve
6 ) where
7
8foreign import dirname """
9function dirname(filepath) {
10 var path = require('path');
11 return path.dirname(filepath);
12}
13""" :: String -> String
14
15foreign import join """
16function join(parts) {
17 var path = require('path');
18 return path.join.apply(path, parts);
19}
20""" :: [String] -> String
21
22foreign import relative """
23function relative(from) {
24 return function(to){
25 var path = require('path');
26 return path.relative(from, to);
27 };
28}
29""" :: String -> String -> String
30
31foreign import resolve """
32function resolve(filepath) {
33 var path = require('path');
34 return path.resolve(filepath);
35}
36""" :: String -> String