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