module OCaml.BuckleScript.Internal.Package
(
OCamlPackage
, NoDependency
, PackageOptions (..)
, defaultPackageOptions
, SpecOptions (..)
, defaultSpecOptions
, HasOCamlPackage (..)
, HasOCamlModule (..)
, HasOCamlTypeMetaData (..)
) where
import Data.Monoid ((<>))
import Data.Proxy (Proxy (..))
import Data.Typeable (typeRep, Typeable, typeRepTyCon, tyConName, tyConModule, tyConPackage)
import GHC.TypeLits
import qualified Data.Map.Strict as Map
import System.Directory (createDirectoryIfMissing)
import System.FilePath.Posix ((</>), (<.>))
import OCaml.Internal.Common hiding ((</>))
import OCaml.BuckleScript.Internal.Module
import OCaml.BuckleScript.Types
import Servant.API ((:>), (:<|>))
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import GHC.TypeLits.List
data OCamlPackage (packageName :: Symbol) (packageDependencies :: [*])
deriving Typeable
type NoDependency = '[]
data PackageOptions
= PackageOptions
{ packageRootDir :: FilePath
, packageSrcDir :: FilePath
, packageEmbeddedFiles :: Map.Map String EmbeddedOCamlFiles
, createInterfaceFile :: Bool
, mSpecOptions :: Maybe SpecOptions
}
defaultPackageOptions :: PackageOptions
defaultPackageOptions = PackageOptions "ocaml-generic-package" "src" Map.empty True (Just defaultSpecOptions)
data SpecOptions
= SpecOptions
{ specDir :: FilePath
, goldenDir :: FilePath
, servantURL :: String
}
defaultSpecOptions :: SpecOptions
defaultSpecOptions = SpecOptions "/__tests__" "/__tests__/golden" "localhost:8081"
class HasOCamlPackage a where
mkPackage :: Proxy a -> PackageOptions -> IO ()
instance (HasOCamlTypeMetaData deps, HasOCamlTypeMetaData a, HasOCamlPackage' a) => HasOCamlPackage (OCamlPackage packageName deps :> a) where
mkPackage Proxy packageOptions = mkPackage' (Proxy :: Proxy a) packageOptions (mkOCamlTypeMetaData (Proxy :: Proxy deps) <> mkOCamlTypeMetaData (Proxy :: Proxy a))
instance (HasOCamlTypeMetaData a, HasOCamlPackage' a) => HasOCamlPackage a where
mkPackage Proxy packageOptions = do
mkPackage' (Proxy :: Proxy a) packageOptions (mkOCamlTypeMetaData (Proxy :: Proxy a))
class HasOCamlPackage' a where
mkPackage' :: Proxy a -> PackageOptions -> Map.Map HaskellTypeMetaData OCamlTypeMetaData -> IO ()
instance (HasOCamlPackage' modul, HasOCamlPackage' rest) => HasOCamlPackage' (modul :<|> rest) where
mkPackage' Proxy packageOptions deps = do
mkPackage' (Proxy :: Proxy modul) packageOptions deps
mkPackage' (Proxy :: Proxy rest) packageOptions deps
instance (HasOCamlModule a) => HasOCamlPackage' a where
mkPackage' Proxy packageOptions = mkModule (Proxy :: Proxy a) packageOptions
class HasOCamlModule a where
mkModule :: Proxy a -> PackageOptions -> Map.Map HaskellTypeMetaData OCamlTypeMetaData -> IO ()
instance (KnownSymbols modules, HasOCamlModule' api) => HasOCamlModule ((OCamlModule modules) :> api) where
mkModule Proxy packageOptions deps = mkModule' (Proxy :: Proxy api) (symbolsVal (Proxy :: Proxy modules)) packageOptions deps
class HasOCamlModule' a where
mkModule' :: Proxy a -> [String] -> PackageOptions -> Map.Map HaskellTypeMetaData OCamlTypeMetaData -> IO ()
instance (HasOCamlType api) => HasOCamlModule' api where
mkModule' Proxy modules packageOptions ds = do
if (length modules) == 0
then fail "OCamlModule filePath needs at least one file name"
else do
createDirectoryIfMissing True (rootDir </> packageSrcDir packageOptions)
let typF = (<> "\n") . T.intercalate "\n\n" $ mkType (Proxy :: Proxy api) (defaultOptions {dependencies = ds}) (createInterfaceFile packageOptions) (packageEmbeddedFiles packageOptions)
T.writeFile (fp <.> "ml") typF
if createInterfaceFile packageOptions
then do
let intF = (<> "\n") . T.intercalate "\n\n" $ mkInterface (Proxy :: Proxy api) (defaultOptions {dependencies = ds}) (packageEmbeddedFiles packageOptions)
T.writeFile (fp <.> "mli") intF
else pure ()
case mSpecOptions packageOptions of
Nothing -> pure ()
Just specOptions -> do
let specF = (<> "\n") . T.intercalate "\n\n" $ mkSpec (Proxy :: Proxy api) (defaultOptions {dependencies = ds}) moduls (T.pack $ servantURL specOptions) (T.pack $ goldenDir specOptions) (packageEmbeddedFiles packageOptions)
let specBody = if specF /= "" then ("let () =\n" <> specF) else ""
createDirectoryIfMissing True (rootDir </> (specDir specOptions))
T.writeFile (specFp <> "_spec" <.> "ml") specBody
where
specFp = rootDir </> (specDir specOptions) </> (foldl (</>) "" modules)
moduls = T.pack <$> modules
where
rootDir = packageRootDir packageOptions
fp = rootDir </> (packageSrcDir packageOptions) </> (foldl (</>) "" modules)
class HasOCamlTypeMetaData a where
mkOCamlTypeMetaData :: Proxy a -> Map.Map HaskellTypeMetaData OCamlTypeMetaData
instance (HasOCamlTypeMetaData (OCamlPackage packageName deps), HasOCamlTypeMetaData rest) => HasOCamlTypeMetaData (OCamlPackage packageName deps :<|> rest) where
mkOCamlTypeMetaData Proxy = mkOCamlTypeMetaData (Proxy :: Proxy (OCamlPackage packageName deps)) <> mkOCamlTypeMetaData (Proxy :: Proxy rest)
instance (HasOCamlTypeMetaData deps, HasOCamlTypeMetaData modules) => HasOCamlTypeMetaData (OCamlPackage packageName deps :> modules) where
mkOCamlTypeMetaData Proxy = mkOCamlTypeMetaData (Proxy :: Proxy deps) <> mkOCamlTypeMetaData (Proxy :: Proxy modules)
instance (HasOCamlTypeMetaData modul, HasOCamlTypeMetaData rst) => HasOCamlTypeMetaData (modul ': rst) where
mkOCamlTypeMetaData Proxy = mkOCamlTypeMetaData (Proxy :: Proxy modul) <> mkOCamlTypeMetaData (Proxy :: Proxy rst)
instance (HasOCamlTypeMetaData modul, HasOCamlTypeMetaData rst) => HasOCamlTypeMetaData (modul :<|> rst) where
mkOCamlTypeMetaData Proxy = mkOCamlTypeMetaData (Proxy :: Proxy modul) <> mkOCamlTypeMetaData (Proxy :: Proxy rst)
instance (KnownSymbols modules, HasOCamlTypeMetaData' api) => HasOCamlTypeMetaData ((OCamlModule modules) :> api) where
mkOCamlTypeMetaData Proxy = Map.fromList $ mkOCamlTypeMetaData' (T.pack <$> symbolsVal (Proxy :: Proxy modules)) [] (Proxy :: Proxy api)
instance HasOCamlTypeMetaData '[] where
mkOCamlTypeMetaData Proxy = Map.empty
type family (HasOCamlTypeMetaDataFlag a) :: Nat where
HasOCamlTypeMetaDataFlag (OCamlSubModule a :> b) = 4
HasOCamlTypeMetaDataFlag (a :> b) = 3
HasOCamlTypeMetaDataFlag (OCamlTypeInFile a b) = 2
HasOCamlTypeMetaDataFlag a = 1
class HasOCamlTypeMetaData' a where
mkOCamlTypeMetaData' :: [Text] -> [Text] -> Proxy a -> [(HaskellTypeMetaData,OCamlTypeMetaData)]
instance (HasOCamlTypeMetaDataFlag a ~ flag, HasOCamlTypeMetaData'' flag (a :: *)) => HasOCamlTypeMetaData' a where
mkOCamlTypeMetaData' = mkOCamlTypeMetaData'' (Proxy :: Proxy flag)
class HasOCamlTypeMetaData'' (flag :: Nat) a where
mkOCamlTypeMetaData'' :: Proxy flag -> [Text] -> [Text] -> Proxy a -> [(HaskellTypeMetaData,OCamlTypeMetaData)]
instance (KnownSymbol subModule, HasOCamlTypeMetaData' b) => HasOCamlTypeMetaData'' 4 (OCamlSubModule subModule :> b) where
mkOCamlTypeMetaData'' _ modules subModules Proxy =
(mkOCamlTypeMetaData' modules (subModules ++ [T.pack $ symbolVal (Proxy :: Proxy subModule)]) (Proxy :: Proxy b))
instance (HasOCamlTypeMetaData' a, HasOCamlTypeMetaData' b) => HasOCamlTypeMetaData'' 3 (a :> b) where
mkOCamlTypeMetaData'' _ modul subModul Proxy = (mkOCamlTypeMetaData' modul subModul (Proxy :: Proxy a)) <> (mkOCamlTypeMetaData' modul subModul (Proxy :: Proxy b))
instance (Typeable a) => HasOCamlTypeMetaData'' 2 (OCamlTypeInFile a b) where
mkOCamlTypeMetaData'' _ modul subModul Proxy =
[( HaskellTypeMetaData typeName (T.pack . tyConModule . typeRepTyCon $ aTypeRep) (T.pack . tyConPackage . typeRepTyCon $ aTypeRep)
, OCamlTypeMetaData typeName modul subModul
)]
where
aTypeRep = typeRep (Proxy :: Proxy a)
typeName = T.pack . tyConName . typeRepTyCon $ aTypeRep
instance (Typeable a) => HasOCamlTypeMetaData'' 1 a where
mkOCamlTypeMetaData'' _ modul subModul Proxy =
[( HaskellTypeMetaData typeName (T.pack . tyConModule . typeRepTyCon $ aTypeRep) (T.pack . tyConPackage . typeRepTyCon $ aTypeRep)
, OCamlTypeMetaData typeName modul subModul
)]
where
aTypeRep = typeRep (Proxy :: Proxy a)
typeName = T.pack . tyConName . typeRepTyCon $ aTypeRep