diff options
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | MODULE.md | 226 | ||||
-rw-r--r-- | bower.json | 16 | ||||
-rw-r--r-- | entry.js | 11 | ||||
-rw-r--r-- | gulpfile.js | 68 | ||||
-rw-r--r-- | index.js | 164 | ||||
-rw-r--r-- | package.json | 38 | ||||
-rw-r--r-- | src/ChildProcess.purs | 40 | ||||
-rw-r--r-- | src/FS.purs | 45 | ||||
-rw-r--r-- | src/Glob.purs | 31 | ||||
-rw-r--r-- | src/Loader.purs | 114 | ||||
-rw-r--r-- | src/LoaderRef.purs | 75 | ||||
-rw-r--r-- | src/LoaderUtil.purs | 20 | ||||
-rw-r--r-- | src/OS.purs | 3 | ||||
-rw-r--r-- | src/Options.purs | 72 | ||||
-rw-r--r-- | src/Path.purs | 36 | ||||
-rw-r--r-- | webpack.config.js | 29 |
17 files changed, 814 insertions, 179 deletions
@@ -1,5 +1,10 @@ | |||
1 | .psci | ||
1 | npm-debug.log | 2 | npm-debug.log |
3 | index.json | ||
4 | index.js | ||
2 | node_modules/ | 5 | node_modules/ |
6 | bower_components/ | ||
7 | build/ | ||
3 | example/node_modules/ | 8 | example/node_modules/ |
4 | example/bower_components/ | 9 | example/bower_components/ |
5 | example/dist/ | 10 | example/dist/ |
diff --git a/MODULE.md b/MODULE.md new file mode 100644 index 0000000..39d9d3a --- /dev/null +++ b/MODULE.md | |||
@@ -0,0 +1,226 @@ | |||
1 | # Module Documentation | ||
2 | |||
3 | ## Module PursLoader.ChildProcess | ||
4 | |||
5 | #### `ChildProcess` | ||
6 | |||
7 | ``` purescript | ||
8 | data ChildProcess :: ! | ||
9 | ``` | ||
10 | |||
11 | |||
12 | #### `spawn` | ||
13 | |||
14 | ``` purescript | ||
15 | spawn :: forall eff. String -> [String] -> Aff (cp :: ChildProcess | eff) String | ||
16 | ``` | ||
17 | |||
18 | |||
19 | |||
20 | ## Module PursLoader.FS | ||
21 | |||
22 | #### `FS` | ||
23 | |||
24 | ``` purescript | ||
25 | data FS :: ! | ||
26 | ``` | ||
27 | |||
28 | |||
29 | #### `readFileUtf8` | ||
30 | |||
31 | ``` purescript | ||
32 | readFileUtf8 :: forall eff. String -> Aff (fs :: FS | eff) String | ||
33 | ``` | ||
34 | |||
35 | |||
36 | #### `readFileUtf8Sync` | ||
37 | |||
38 | ``` purescript | ||
39 | readFileUtf8Sync :: forall eff. String -> Eff (fs :: FS | eff) String | ||
40 | ``` | ||
41 | |||
42 | |||
43 | |||
44 | ## Module PursLoader.Glob | ||
45 | |||
46 | #### `Glob` | ||
47 | |||
48 | ``` purescript | ||
49 | data Glob :: ! | ||
50 | ``` | ||
51 | |||
52 | |||
53 | #### `glob` | ||
54 | |||
55 | ``` purescript | ||
56 | glob :: forall eff. String -> Aff (glob :: Glob | eff) [String] | ||
57 | ``` | ||
58 | |||
59 | |||
60 | |||
61 | ## Module PursLoader.Loader | ||
62 | |||
63 | #### `LoaderEff` | ||
64 | |||
65 | ``` purescript | ||
66 | type LoaderEff eff a = Eff (fs :: FS, cp :: ChildProcess, glob :: Glob, loader :: Loader | eff) a | ||
67 | ``` | ||
68 | |||
69 | |||
70 | #### `loader` | ||
71 | |||
72 | ``` purescript | ||
73 | loader :: forall eff. LoaderRef -> String -> LoaderEff eff Unit | ||
74 | ``` | ||
75 | |||
76 | |||
77 | #### `loaderFn` | ||
78 | |||
79 | ``` purescript | ||
80 | loaderFn :: forall eff. Fn2 LoaderRef String (LoaderEff eff Unit) | ||
81 | ``` | ||
82 | |||
83 | |||
84 | |||
85 | ## Module PursLoader.LoaderRef | ||
86 | |||
87 | #### `LoaderRef` | ||
88 | |||
89 | ``` purescript | ||
90 | data LoaderRef | ||
91 | ``` | ||
92 | |||
93 | |||
94 | #### `Loader` | ||
95 | |||
96 | ``` purescript | ||
97 | data Loader :: ! | ||
98 | ``` | ||
99 | |||
100 | |||
101 | #### `async` | ||
102 | |||
103 | ``` purescript | ||
104 | async :: forall eff a. LoaderRef -> Eff (loader :: Loader | eff) (Maybe Error -> a -> Eff (loader :: Loader | eff) Unit) | ||
105 | ``` | ||
106 | |||
107 | |||
108 | #### `cacheable` | ||
109 | |||
110 | ``` purescript | ||
111 | cacheable :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit | ||
112 | ``` | ||
113 | |||
114 | |||
115 | #### `clearDependencies` | ||
116 | |||
117 | ``` purescript | ||
118 | clearDependencies :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit | ||
119 | ``` | ||
120 | |||
121 | |||
122 | #### `resourcePath` | ||
123 | |||
124 | ``` purescript | ||
125 | resourcePath :: LoaderRef -> String | ||
126 | ``` | ||
127 | |||
128 | |||
129 | #### `addDependency` | ||
130 | |||
131 | ``` purescript | ||
132 | addDependency :: forall eff. LoaderRef -> String -> Eff (loader :: Loader | eff) Unit | ||
133 | ``` | ||
134 | |||
135 | |||
136 | #### `query` | ||
137 | |||
138 | ``` purescript | ||
139 | query :: LoaderRef -> String | ||
140 | ``` | ||
141 | |||
142 | |||
143 | |||
144 | ## Module PursLoader.LoaderUtil | ||
145 | |||
146 | #### `getRemainingRequest` | ||
147 | |||
148 | ``` purescript | ||
149 | getRemainingRequest :: LoaderRef -> String | ||
150 | ``` | ||
151 | |||
152 | |||
153 | #### `parseQuery` | ||
154 | |||
155 | ``` purescript | ||
156 | parseQuery :: String -> Foreign | ||
157 | ``` | ||
158 | |||
159 | |||
160 | |||
161 | ## Module PursLoader.OS | ||
162 | |||
163 | #### `eol` | ||
164 | |||
165 | ``` purescript | ||
166 | eol :: String | ||
167 | ``` | ||
168 | |||
169 | |||
170 | |||
171 | ## Module PursLoader.Options | ||
172 | |||
173 | #### `isForeignOptions` | ||
174 | |||
175 | ``` purescript | ||
176 | instance isForeignOptions :: IsForeign Options | ||
177 | ``` | ||
178 | |||
179 | |||
180 | #### `pscMakeOutputOption` | ||
181 | |||
182 | ``` purescript | ||
183 | pscMakeOutputOption :: Foreign -> Maybe String | ||
184 | ``` | ||
185 | |||
186 | |||
187 | #### `pscMakeOptions` | ||
188 | |||
189 | ``` purescript | ||
190 | pscMakeOptions :: Foreign -> [String] | ||
191 | ``` | ||
192 | |||
193 | |||
194 | |||
195 | ## Module PursLoader.Path | ||
196 | |||
197 | #### `dirname` | ||
198 | |||
199 | ``` purescript | ||
200 | dirname :: String -> String | ||
201 | ``` | ||
202 | |||
203 | |||
204 | #### `join` | ||
205 | |||
206 | ``` purescript | ||
207 | join :: [String] -> String | ||
208 | ``` | ||
209 | |||
210 | |||
211 | #### `relative` | ||
212 | |||
213 | ``` purescript | ||
214 | relative :: String -> String -> String | ||
215 | ``` | ||
216 | |||
217 | |||
218 | #### `resolve` | ||
219 | |||
220 | ``` purescript | ||
221 | resolve :: String -> String | ||
222 | ``` | ||
223 | |||
224 | |||
225 | |||
226 | |||
diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..dddddf9 --- /dev/null +++ b/bower.json | |||
@@ -0,0 +1,16 @@ | |||
1 | { | ||
2 | "name": "purs-loader", | ||
3 | "private": true, | ||
4 | "devDependencies": { | ||
5 | "purescript-aff": "~0.9.1", | ||
6 | "purescript-exceptions": "~0.2.3", | ||
7 | "purescript-strings": "~0.4.5", | ||
8 | "purescript-maybe": "~0.2.2", | ||
9 | "purescript-foreign": "~0.4.2", | ||
10 | "purescript-foldable-traversable": "~0.3.1", | ||
11 | "purescript-tuples": "~0.3.4", | ||
12 | "purescript-maps": "~0.3.3", | ||
13 | "purescript-arrays": "~0.3.7", | ||
14 | "purescript-monad-eff": "~0.1.0" | ||
15 | } | ||
16 | } | ||
diff --git a/entry.js b/entry.js new file mode 100644 index 0000000..87f52d3 --- /dev/null +++ b/entry.js | |||
@@ -0,0 +1,11 @@ | |||
1 | 'use strict'; | ||
2 | |||
3 | var pursLoader = require('PursLoader.Loader'); | ||
4 | |||
5 | function loader(source) { | ||
6 | var ref = this; | ||
7 | var result = pursLoader.loaderFn(ref, source); | ||
8 | return result(); | ||
9 | } | ||
10 | |||
11 | module.exports = loader; | ||
diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..e217480 --- /dev/null +++ b/gulpfile.js | |||
@@ -0,0 +1,68 @@ | |||
1 | 'use strict'; | ||
2 | |||
3 | var path = require('path'); | ||
4 | |||
5 | var gulp = require('gulp'); | ||
6 | |||
7 | var gutil = require('gulp-util'); | ||
8 | |||
9 | var plumber = require('gulp-plumber'); | ||
10 | |||
11 | var purescript = require('gulp-purescript'); | ||
12 | |||
13 | var sequence = require('run-sequence'); | ||
14 | |||
15 | var del = require('del'); | ||
16 | |||
17 | var config = { del: ['build', 'index.js'] | ||
18 | , purescript: { src: [ 'bower_components/purescript-*/src/**/*.purs*' | ||
19 | , 'src/**/*.purs' | ||
20 | ] | ||
21 | , dest: 'build' | ||
22 | , docs: 'MODULE.md' | ||
23 | } | ||
24 | } | ||
25 | ; | ||
26 | |||
27 | function error(e) { | ||
28 | gutil.log(gutil.colors.magenta('>>>> Error <<<<') + '\n' + e.toString().trim()); | ||
29 | this.emit('end'); | ||
30 | } | ||
31 | |||
32 | gulp.task('del', function(cb){ | ||
33 | del(config.del, cb); | ||
34 | }); | ||
35 | |||
36 | gulp.task('make', function(){ | ||
37 | return gulp.src(config.purescript.src). | ||
38 | pipe(plumber()). | ||
39 | pipe(purescript.pscMake({output: config.purescript.dest})). | ||
40 | on('error', error); | ||
41 | }); | ||
42 | |||
43 | gulp.task('psci', function(){ | ||
44 | return gulp.src(config.purescript.src). | ||
45 | pipe(plumber()). | ||
46 | pipe(purescript.dotPsci()). | ||
47 | on('error', error); | ||
48 | }); | ||
49 | |||
50 | gulp.task('docs', function(){ | ||
51 | return gulp.src(config.purescript.src[1]). | ||
52 | pipe(plumber()). | ||
53 | pipe(purescript.pscDocs()). | ||
54 | on('error', error). | ||
55 | pipe(gulp.dest(config.purescript.docs)); | ||
56 | }); | ||
57 | |||
58 | gulp.task('watch', function(){ | ||
59 | gulp.watch(config.purescript.src, ['make']); | ||
60 | }); | ||
61 | |||
62 | gulp.task('default', function(callback){ | ||
63 | sequence('del', 'make', ['psci', 'docs'], callback); | ||
64 | }); | ||
65 | |||
66 | gulp.task('build', function(callback){ | ||
67 | sequence('del', 'make', callback); | ||
68 | }); | ||
diff --git a/index.js b/index.js deleted file mode 100644 index 449c841..0000000 --- a/index.js +++ /dev/null | |||
@@ -1,164 +0,0 @@ | |||
1 | 'use strict'; | ||
2 | |||
3 | var os = require('os'); | ||
4 | |||
5 | var cp = require('child_process'); | ||
6 | |||
7 | var path = require('path') | ||
8 | |||
9 | var fs = require('fs'); | ||
10 | |||
11 | var glob = require('glob'); | ||
12 | |||
13 | var lodash = require('lodash'); | ||
14 | |||
15 | var chalk = require('chalk'); | ||
16 | |||
17 | var lu = require('loader-utils'); | ||
18 | |||
19 | var cwd = process.cwd(); | ||
20 | |||
21 | var MODULE_RE = /(?:^|\n)module\s+([\w\.]+)/i; | ||
22 | |||
23 | var IMPORT_RE = /^\s*import\s+(?:qualified\s+)?([\w\.]+)/i; | ||
24 | |||
25 | var BOWER_PATTERN = path.join('bower_components', 'purescript-*', 'src'); | ||
26 | |||
27 | var PSC_MAKE = 'psc-make'; | ||
28 | |||
29 | var OUTPUT = 'output'; | ||
30 | |||
31 | var OPTIONS = { | ||
32 | 'no-prelude': '--no-prelude', | ||
33 | 'no-opts': '--no-opts', | ||
34 | 'no-magic-do': '--no-magic-do', | ||
35 | 'no-tco': '--no-tco', | ||
36 | 'verbose-errors': '--verbose-errors', | ||
37 | 'output': '--output' | ||
38 | }; | ||
39 | |||
40 | function pattern(root) { | ||
41 | var as = [ BOWER_PATTERN, root ]; | ||
42 | return path.join('{' + as.join(',') + '}', '**', '*.purs'); | ||
43 | } | ||
44 | |||
45 | function mkOptions(query) { | ||
46 | return lodash.foldl(lodash.keys(query), function(acc, k){ | ||
47 | var h = function(v){return acc.concat(query[k] && OPTIONS[k] ? [v] : []);} | ||
48 | if (k === OUTPUT) return h(OPTIONS[k] + '=' + query[k]); | ||
49 | else return h(OPTIONS[k]); | ||
50 | }, []); | ||
51 | } | ||
52 | |||
53 | function mkGraph(files) { | ||
54 | var graph = {}; | ||
55 | |||
56 | files.forEach(function(file){ | ||
57 | var source = fs.readFileSync(file, {encoding: 'utf-8'}); | ||
58 | |||
59 | var result = MODULE_RE.exec(source); | ||
60 | |||
61 | var module = result ? result[1] : null; | ||
62 | |||
63 | var imports = | ||
64 | lodash.foldl(source.split(os.EOL), function(b, a){ | ||
65 | var result = IMPORT_RE.exec(a); | ||
66 | if (result) b.push(result[1]); | ||
67 | return b; | ||
68 | }, []) | ||
69 | ; | ||
70 | |||
71 | if (module) { | ||
72 | graph[module] = { | ||
73 | file: file, | ||
74 | imports: imports || [] | ||
75 | }; | ||
76 | } | ||
77 | }); | ||
78 | |||
79 | return graph; | ||
80 | } | ||
81 | |||
82 | function findDeps(graph, module) { | ||
83 | function go(acc, module){ | ||
84 | var node = graph[module]; | ||
85 | |||
86 | var imports = node && node.imports; | ||
87 | |||
88 | if (lodash.isEmpty(imports)) return acc; | ||
89 | else { | ||
90 | var deps = | ||
91 | lodash.map(imports, function(i){ | ||
92 | return go(acc.concat(imports), i); | ||
93 | }) | ||
94 | ; | ||
95 | return lodash.flatten(deps); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | return lodash.unique(go([], module)); | ||
100 | } | ||
101 | |||
102 | function loader(source) { | ||
103 | this.cacheable && this.cacheable(); | ||
104 | |||
105 | this.clearDependencies(); | ||
106 | |||
107 | this.addDependency(this.resourcePath); | ||
108 | |||
109 | var callback = this.async(); | ||
110 | |||
111 | var request = lu.getRemainingRequest(this) | ||
112 | |||
113 | var root = path.dirname(path.relative(cwd, request)); | ||
114 | |||
115 | var query = lu.parseQuery(this.query); | ||
116 | |||
117 | var opts = mkOptions(query); | ||
118 | |||
119 | var that = this; | ||
120 | |||
121 | glob(pattern(root), function(e, files){ | ||
122 | if (e !== null) callback(e); | ||
123 | else { | ||
124 | var cmd = cp.spawn(PSC_MAKE, opts.concat(files)); | ||
125 | |||
126 | var graph = mkGraph(files); | ||
127 | |||
128 | cmd.on('close', function(e){ | ||
129 | if (e) callback(e); | ||
130 | else { | ||
131 | var result = MODULE_RE.exec(source); | ||
132 | |||
133 | var module = result ? result[1] : ''; | ||
134 | |||
135 | var dependencies = findDeps(graph, module); | ||
136 | |||
137 | var indexPath = path.join(query[OUTPUT] || OUTPUT, module, 'index.js'); | ||
138 | |||
139 | fs.readFile(indexPath, 'utf-8', function(e, output){ | ||
140 | if (e) callback(e); | ||
141 | else { | ||
142 | dependencies.forEach(function(dep){ | ||
143 | var module = graph[dep]; | ||
144 | if (module) that.addDependency(path.resolve(module.file)); | ||
145 | }); | ||
146 | |||
147 | callback(null, output); | ||
148 | } | ||
149 | }); | ||
150 | } | ||
151 | }); | ||
152 | |||
153 | cmd.stdout.on('data', function(stdout){ | ||
154 | console.log('Stdout from \'' + chalk.cyan(PSC_MAKE) + '\'\n' + chalk.magenta(stdout)); | ||
155 | }); | ||
156 | |||
157 | cmd.stderr.on('data', function(stderr){ | ||
158 | console.log('Stderr from \'' + chalk.cyan(PSC_MAKE) + '\'\n' + chalk.magenta(stderr)); | ||
159 | }); | ||
160 | } | ||
161 | }); | ||
162 | } | ||
163 | |||
164 | module.exports = loader; | ||
diff --git a/package.json b/package.json index 4d458b8..56c6b39 100644 --- a/package.json +++ b/package.json | |||
@@ -2,24 +2,32 @@ | |||
2 | "name": "purs-loader", | 2 | "name": "purs-loader", |
3 | "version": "0.0.3", | 3 | "version": "0.0.3", |
4 | "description": "PureScript loader for webpack", | 4 | "description": "PureScript loader for webpack", |
5 | "main": "index.js", | 5 | "license": "MIT", |
6 | "scripts": { | 6 | "repository": "ethul/purs-loader", |
7 | "test": "echo \"Error: no test specified\" && exit 1" | 7 | "author": { |
8 | "name": "Eric Thul", | ||
9 | "email": "thul.eric@gmail.com" | ||
8 | }, | 10 | }, |
9 | "repository": { | 11 | "scripts": { |
10 | "type": "git", | 12 | "build": "npm run-script build:compile && npm run-script build:package", |
11 | "url": "git@github.com:ethul/purs-loader.git" | 13 | "build:compile": "gulp build", |
14 | "build:package": "./node_modules/.bin/webpack --progress --colors --profile --bail", | ||
15 | "build:json": "./node_modules/.bin/webpack --progress --colors --profile --bail --json > index.json" | ||
12 | }, | 16 | }, |
13 | "author": "Eric Thul", | 17 | "files": [ |
14 | "license": "MIT", | 18 | "index.js" |
15 | "bugs": { | 19 | ], |
16 | "url": "https://github.com/ethul/purs-loader/issues" | 20 | "devDependencies": { |
21 | "del": "^1.1.1", | ||
22 | "gulp": "^3.8.11", | ||
23 | "gulp-plumber": "^1.0.0", | ||
24 | "gulp-purescript": "^0.3.1", | ||
25 | "gulp-util": "^3.0.4", | ||
26 | "run-sequence": "^1.0.2", | ||
27 | "webpack": "^1.8.4" | ||
17 | }, | 28 | }, |
18 | "homepage": "https://github.com/ethul/purs-loader", | ||
19 | "dependencies": { | 29 | "dependencies": { |
20 | "chalk": "^0.5.1", | 30 | "glob": "^5.0.3", |
21 | "glob": "4.0.6", | 31 | "loader-utils": "^0.2.6" |
22 | "loader-utils": "^0.2.3", | ||
23 | "lodash": "^2.4.1" | ||
24 | } | 32 | } |
25 | } | 33 | } |
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 @@ | |||
1 | module PursLoader.ChildProcess | ||
2 | ( ChildProcess() | ||
3 | , spawn | ||
4 | ) where | ||
5 | |||
6 | import Control.Monad.Aff (Aff(), makeAff) | ||
7 | import Control.Monad.Eff (Eff()) | ||
8 | import Control.Monad.Eff.Exception (Error()) | ||
9 | |||
10 | import Data.Function | ||
11 | |||
12 | foreign import data ChildProcess :: ! | ||
13 | |||
14 | spawn :: forall eff. String -> [String] -> Aff (cp :: ChildProcess | eff) String | ||
15 | spawn command args = makeAff $ runFn4 spawnFn command args | ||
16 | |||
17 | foreign import spawnFn """ | ||
18 | function 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 @@ | |||
1 | module PursLoader.FS | ||
2 | ( FS() | ||
3 | , readFileUtf8 | ||
4 | , readFileUtf8Sync | ||
5 | ) where | ||
6 | |||
7 | import Control.Monad.Aff (Aff(), makeAff) | ||
8 | import Control.Monad.Eff (Eff()) | ||
9 | import Control.Monad.Eff.Exception (Error()) | ||
10 | |||
11 | import Data.Function | ||
12 | |||
13 | foreign import data FS :: ! | ||
14 | |||
15 | readFileUtf8 :: forall eff. String -> Aff (fs :: FS | eff) String | ||
16 | readFileUtf8 filepath = makeAff $ runFn3 readFileUtf8Fn filepath | ||
17 | |||
18 | readFileUtf8Sync :: forall eff. String -> Eff (fs :: FS | eff) String | ||
19 | readFileUtf8Sync filepath = readFileUtf8SyncFn filepath | ||
20 | |||
21 | foreign import readFileUtf8Fn """ | ||
22 | function 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 | |||
37 | foreign import readFileUtf8SyncFn """ | ||
38 | function 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 @@ | |||
1 | module PursLoader.Glob | ||
2 | ( Glob() | ||
3 | , glob | ||
4 | ) where | ||
5 | |||
6 | import Control.Monad.Aff (Aff(), makeAff) | ||
7 | import Control.Monad.Eff (Eff()) | ||
8 | import Control.Monad.Eff.Exception (Error()) | ||
9 | |||
10 | import Data.Function | ||
11 | |||
12 | foreign import data Glob :: ! | ||
13 | |||
14 | glob :: forall eff. String -> Aff (glob :: Glob | eff) [String] | ||
15 | glob pattern = makeAff $ runFn3 globFn pattern | ||
16 | |||
17 | foreign import globFn """ | ||
18 | function 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 @@ | |||
1 | module PursLoader.Loader | ||
2 | ( LoaderEff() | ||
3 | , loader | ||
4 | , loaderFn | ||
5 | ) where | ||
6 | |||
7 | import Control.Monad.Aff (Aff(), runAff) | ||
8 | import Control.Monad.Eff (Eff()) | ||
9 | import Control.Monad.Eff.Class (liftEff) | ||
10 | import Control.Monad.Eff.Exception (error) | ||
11 | |||
12 | import Data.Array ((!!), catMaybes, concat, nub, null) | ||
13 | import Data.Function (Fn2(), mkFn2) | ||
14 | import Data.Maybe (Maybe(..), fromMaybe, maybe) | ||
15 | import Data.String (joinWith, split) | ||
16 | import Data.String.Regex (Regex(), match, noFlags, regex) | ||
17 | import Data.StrMap (StrMap(), fromList, lookup) | ||
18 | import Data.Traversable (sequence) | ||
19 | import Data.Tuple.Nested (tuple2) | ||
20 | |||
21 | import PursLoader.ChildProcess (ChildProcess(), spawn) | ||
22 | import PursLoader.FS (FS(), readFileUtf8, readFileUtf8Sync) | ||
23 | import PursLoader.Glob (Glob(), glob) | ||
24 | import PursLoader.LoaderRef (LoaderRef(), Loader(), async, cacheable, clearDependencies, addDependency, query, resourcePath) | ||
25 | import PursLoader.LoaderUtil (getRemainingRequest, parseQuery) | ||
26 | import PursLoader.OS (eol) | ||
27 | import PursLoader.Options (pscMakeOptions, pscMakeDefaultOutput, pscMakeOutputOption) | ||
28 | import PursLoader.Path (dirname, join, relative, resolve) | ||
29 | |||
30 | foreign import cwd "var cwd = process.cwd();" :: String | ||
31 | |||
32 | moduleRegex = regex "(?:^|\\n)module\\s+([\\w\\.]+)" noFlags { ignoreCase = true } | ||
33 | |||
34 | importRegex = regex "^\\s*import\\s+(?:qualified\\s+)?([\\w\\.]+)" noFlags { ignoreCase = true } | ||
35 | |||
36 | bowerPattern = join [ "bower_components", "purescript-*", "src" ] | ||
37 | |||
38 | pscMakeCommand = "psc-make" | ||
39 | |||
40 | indexFilename = "index.js" | ||
41 | |||
42 | (!!!) = flip (!!) | ||
43 | |||
44 | pursPattern :: String -> String | ||
45 | pursPattern root = join [ "{" ++ joinWith "," [ bowerPattern, root ] ++ "}" | ||
46 | , "**" | ||
47 | , "*.purs" | ||
48 | ] | ||
49 | |||
50 | type GraphModule = { file :: String, imports :: [String] } | ||
51 | |||
52 | type Graph = StrMap GraphModule | ||
53 | |||
54 | mkGraph :: forall eff. [String] -> Eff (fs :: FS | eff) Graph | ||
55 | mkGraph 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 | |||
62 | mkDeps :: forall eff. String -> Graph -> [String] | ||
63 | mkDeps 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 | |||
69 | addDeps :: forall eff. LoaderRef -> Graph -> [String] -> Eff (loader :: Loader | eff) Unit | ||
70 | addDeps 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 | |||
75 | type LoaderAff eff a = Aff (loader :: Loader, glob :: Glob, cp :: ChildProcess, fs :: FS | eff) a | ||
76 | |||
77 | loader' :: forall eff. LoaderRef -> String -> LoaderAff eff (Maybe String) | ||
78 | loader' 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 | |||
103 | type LoaderEff eff a = Eff (loader :: Loader, glob :: Glob, cp :: ChildProcess, fs :: FS | eff) a | ||
104 | |||
105 | loader :: forall eff. LoaderRef -> String -> LoaderEff eff Unit | ||
106 | loader 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 | |||
113 | loaderFn :: forall eff. Fn2 LoaderRef String (LoaderEff eff Unit) | ||
114 | loaderFn = 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 @@ | |||
1 | module PursLoader.LoaderRef | ||
2 | ( LoaderRef() | ||
3 | , Loader() | ||
4 | , async | ||
5 | , cacheable | ||
6 | , clearDependencies | ||
7 | , resourcePath | ||
8 | , addDependency | ||
9 | , query | ||
10 | ) where | ||
11 | |||
12 | import Control.Monad.Eff (Eff()) | ||
13 | import Control.Monad.Eff.Exception (Error()) | ||
14 | |||
15 | import Data.Foreign (Foreign()) | ||
16 | import Data.Function (Fn3(), runFn3) | ||
17 | import Data.Maybe (Maybe(), fromMaybe, isJust) | ||
18 | |||
19 | data LoaderRef | ||
20 | |||
21 | foreign import data Loader :: ! | ||
22 | |||
23 | foreign import asyncFn """ | ||
24 | function 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 | |||
41 | async :: forall eff a. LoaderRef -> Eff (loader :: Loader | eff) (Maybe Error -> a -> Eff (loader :: Loader | eff) Unit) | ||
42 | async ref = runFn3 asyncFn isJust fromMaybe ref | ||
43 | |||
44 | foreign import cacheable """ | ||
45 | function cacheable(ref){ | ||
46 | return function(){ | ||
47 | return ref.cacheable && ref.cacheable(); | ||
48 | }; | ||
49 | }""" :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit | ||
50 | |||
51 | foreign import clearDependencies """ | ||
52 | function clearDependencies(ref){ | ||
53 | return function(){ | ||
54 | return ref.clearDependencies(); | ||
55 | }; | ||
56 | }""" :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit | ||
57 | |||
58 | foreign import resourcePath """ | ||
59 | function resourcePath(ref){ | ||
60 | return ref.resourcePath; | ||
61 | }""" :: LoaderRef -> String | ||
62 | |||
63 | foreign import addDependency """ | ||
64 | function 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 | |||
72 | foreign import query """ | ||
73 | function 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 @@ | |||
1 | module PursLoader.LoaderUtil | ||
2 | ( getRemainingRequest | ||
3 | , parseQuery | ||
4 | ) where | ||
5 | |||
6 | import Data.Foreign (Foreign()) | ||
7 | |||
8 | import PursLoader.LoaderRef (LoaderRef()) | ||
9 | |||
10 | foreign import getRemainingRequest """ | ||
11 | function getRemainingRequest(ref){ | ||
12 | var loaderUtils = require('loader-utils'); | ||
13 | return loaderUtils.getRemainingRequest(ref); | ||
14 | }""" :: LoaderRef -> String | ||
15 | |||
16 | foreign import parseQuery """ | ||
17 | function 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 @@ | |||
1 | module PursLoader.OS (eol) where | ||
2 | |||
3 | foreign 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 @@ | |||
1 | module PursLoader.Options | ||
2 | ( pscMakeOptions | ||
3 | , pscMakeDefaultOutput | ||
4 | , pscMakeOutputOption | ||
5 | ) where | ||
6 | |||
7 | import Data.Either (either) | ||
8 | |||
9 | import Data.Foreign (Foreign(), F()) | ||
10 | import Data.Foreign.Class (IsForeign, read, readProp) | ||
11 | import Data.Foreign.NullOrUndefined (NullOrUndefined(), runNullOrUndefined) | ||
12 | |||
13 | import Data.Maybe (Maybe(..), maybe) | ||
14 | |||
15 | noPreludeOpt = "no-prelude" | ||
16 | |||
17 | noOptsOpt = "no-opts" | ||
18 | |||
19 | noMagicDoOpt = "no-magic-do" | ||
20 | |||
21 | noTcoOpt = "no-tco" | ||
22 | |||
23 | verboseErrorsOpt = "verbose-errors" | ||
24 | |||
25 | outputOpt = "output" | ||
26 | |||
27 | pscMakeDefaultOutput = "output" | ||
28 | |||
29 | newtype 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 | |||
38 | instance 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 | |||
53 | booleanOpt :: String -> NullOrUndefined Boolean -> [String] | ||
54 | booleanOpt key opt = maybe [] (\a -> if a then ["--" ++ key] else []) (runNullOrUndefined opt) | ||
55 | |||
56 | stringOpt :: String -> NullOrUndefined String -> [String] | ||
57 | stringOpt key opt = maybe [] (\a -> ["--" ++ key ++ "=" ++ a]) (runNullOrUndefined opt) | ||
58 | |||
59 | pscMakeOutputOption :: Foreign -> Maybe String | ||
60 | pscMakeOutputOption query = either (const Nothing) | ||
61 | (\(Options a) -> runNullOrUndefined a.output) | ||
62 | (read query) | ||
63 | |||
64 | pscMakeOptions :: Foreign -> [String] | ||
65 | pscMakeOptions 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 @@ | |||
1 | module PursLoader.Path | ||
2 | ( dirname | ||
3 | , join | ||
4 | , relative | ||
5 | , resolve | ||
6 | ) where | ||
7 | |||
8 | foreign import dirname """ | ||
9 | function dirname(filepath) { | ||
10 | var path = require('path'); | ||
11 | return path.dirname(filepath); | ||
12 | } | ||
13 | """ :: String -> String | ||
14 | |||
15 | foreign import join """ | ||
16 | function join(parts) { | ||
17 | var path = require('path'); | ||
18 | return path.join.apply(path, parts); | ||
19 | } | ||
20 | """ :: [String] -> String | ||
21 | |||
22 | foreign import relative """ | ||
23 | function relative(from) { | ||
24 | return function(to){ | ||
25 | var path = require('path'); | ||
26 | return path.relative(from, to); | ||
27 | }; | ||
28 | } | ||
29 | """ :: String -> String -> String | ||
30 | |||
31 | foreign import resolve """ | ||
32 | function resolve(filepath) { | ||
33 | var path = require('path'); | ||
34 | return path.resolve(filepath); | ||
35 | } | ||
36 | """ :: String -> String | ||
diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..11b9069 --- /dev/null +++ b/webpack.config.js | |||
@@ -0,0 +1,29 @@ | |||
1 | 'use strict'; | ||
2 | |||
3 | var path = require('path'); | ||
4 | |||
5 | var webpack = require('webpack'); | ||
6 | |||
7 | var noErrorsPlugin = webpack.NoErrorsPlugin; | ||
8 | |||
9 | var dedupePlugin = webpack.optimize.DedupePlugin; | ||
10 | |||
11 | var config | ||
12 | = { cache: true | ||
13 | , target: 'node' | ||
14 | , entry: { index: './entry' } | ||
15 | , externals: { 'glob': 'commonjs glob' | ||
16 | , 'loader-utils': 'commonjs loader-utils' | ||
17 | } | ||
18 | , output: { path: __dirname | ||
19 | , filename: '[name].js' | ||
20 | , libraryTarget: 'commonjs2' | ||
21 | } | ||
22 | , plugins: [ new noErrorsPlugin() | ||
23 | , new dedupePlugin() | ||
24 | ] | ||
25 | , resolve: { modulesDirectories: [ 'build' ] } | ||
26 | } | ||
27 | ; | ||
28 | |||
29 | module.exports = config; | ||