packages feed

axel-0.0.13: src/Axel/Haskell/Project.hs

{-# LANGUAGE OverloadedStrings #-}

module Axel.Haskell.Project where

import Axel.Prelude

import Axel.Eff ((:>>))
import qualified Axel.Eff.Console as Effs (Console)
import Axel.Eff.Error (Error)
import Axel.Eff.FileSystem
  ( copyFile
  , getCurrentDirectory
  , getDirectoryContentsRec
  , removeFile
  )
import qualified Axel.Eff.FileSystem as Effs (FileSystem)
import qualified Axel.Eff.Ghci as Effs (Ghci)
import qualified Axel.Eff.Ghci as Ghci
import qualified Axel.Eff.Log as Effs (Log)
import qualified Axel.Eff.Process as Effs (Process)
import Axel.Eff.Process (passthroughProcess)
import Axel.Eff.Resource (getResourcePath, newProjectTemplate)
import qualified Axel.Eff.Resource as Effs (Resource)
import qualified Axel.Haskell.Cabal as Cabal
  ( addDependency
  , axelPackageId
  , buildProject
  , createProject
  , runProject
  )
import Axel.Haskell.File
  ( convertFileInPlace
  , formatFileInPlace
  , readModuleInfo
  , transpileFileInPlace
  )
import Axel.Sourcemap (ModuleInfo)
import Axel.Utils.FilePath ((<.>), (</>))

import Control.Lens (op)
import Control.Monad (void)
import Control.Monad.Extra (concatMapM)

import qualified Data.Text as T

import Effectful ((:>))
import qualified Effectful as Eff
import qualified Effectful.Error.Static as Eff
import qualified Effectful.State.Static.Local as Eff

type ProjectPath = FilePath

newProject ::
     ('[ Effs.FileSystem, Effs.Process, Effs.Resource] :>> effs)
  => Text
  -> Eff.Eff effs ()
newProject projectName = do
  Cabal.createProject projectName
  let projectPath = FilePath projectName
  Cabal.addDependency Cabal.axelPackageId projectPath
  templatePath <- getResourcePath newProjectTemplate
  let copyAxel filePath = do
        copyFile
          (templatePath </> filePath <.> "axel")
          (projectPath </> filePath <.> "axel")
        removeFile (projectPath </> filePath <.> "hs")
  mapM_
    copyAxel
    [ FilePath "Setup"
    , FilePath "app" </> FilePath "Main"
    , FilePath "src" </> FilePath "Lib"
    , FilePath "test" </> FilePath "Spec"
    ]

data ProjectFileType
  = Axel
  | Backend

getProjectFiles ::
     (Effs.FileSystem :> effs) => ProjectFileType -> Eff.Eff effs [FilePath]
getProjectFiles fileType = do
  files <-
    concatMapM
      getDirectoryContentsRec
      [FilePath "app", FilePath "src", FilePath "test"]
  let ext =
        case fileType of
          Axel -> ".axel"
          Backend -> ".hs"
  pure $ filter (\filePath -> ext `T.isSuffixOf` op FilePath filePath) files

transpileProject ::
     ('[ Effs.Console, Eff.Error Error, Effs.FileSystem, Effs.Ghci, Effs.Log, Effs.Process, Effs.Resource] :>> effs)
  => Eff.Eff effs ModuleInfo
transpileProject =
  Ghci.withGhci $ do
    axelFiles <- getProjectFiles Axel
    initialModuleInfo <- readModuleInfo axelFiles
    Eff.execState initialModuleInfo $ mapM transpileFileInPlace axelFiles

buildProject ::
     ('[ Effs.Console, Eff.Error Error, Effs.FileSystem, Effs.Ghci, Effs.Log, Effs.Process, Effs.Resource] :>> effs)
  => Eff.Eff effs ()
buildProject = do
  void $ passthroughProcess "hpack"
  projectPath <- getCurrentDirectory
  transpiledFiles <- transpileProject
  Cabal.buildProject transpiledFiles projectPath

convertProject ::
     ('[ Effs.Console, Effs.FileSystem, Eff.Error Error, Effs.FileSystem, Effs.Process] :>> effs)
  => Eff.Eff effs ()
convertProject = getProjectFiles Backend >>= void . traverse convertFileInPlace

runProject ::
     ('[ Effs.Console, Eff.Error Error, Effs.FileSystem, Effs.Process] :>> effs)
  => Eff.Eff effs ()
runProject = getCurrentDirectory >>= Cabal.runProject

formatProject ::
     ('[ Effs.Console, Effs.FileSystem, Eff.Error Error] :>> effs)
  => Eff.Eff effs ()
formatProject = getProjectFiles Axel >>= void . traverse formatFileInPlace