aboutsummaryrefslogblamecommitdiffhomepage
path: root/src/Loader.purs
blob: 523aa7a693b6720b4de5300e6e4bb456650d4d38 (plain) (tree)

















































































































                                                                                                                            
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