poppy-codegen-1.0.0: src/Poppy/Codegen/Target.hs
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
module Poppy.Codegen.Target
( simpleTarget,
CodegenTarget,
GenOutput (..),
targetOutputs,
targetSchemas,
)
where
import Data.Char (isAlphaNum, isUpper)
import Data.Maybe (isNothing)
import Data.Text (Text)
import qualified Data.Text as T
import Poppy.Codegen.Emit.Client
( emitClientModule,
)
import Poppy.Codegen.Emit.Include (emitIncludeModule)
import Poppy.Codegen.Emit.Schema (emitEnumModule, emitModelModule)
import Poppy.Codegen.IR
( EnumSpec (..),
Model (..),
Schema (..),
enumImport,
enumName,
modelName,
modelRelations,
schemaEnums,
schemaModels,
)
import System.FilePath (dropTrailingPathSeparator, splitDirectories, (</>))
-- | Path is relative to the process working directory.
data GenOutput = GenOutput
{ outputPath :: FilePath,
outputText :: Text
}
data SchemaLayout = SchemaLayout
{ slModulePrefix :: Text,
slOutputDir :: FilePath
}
data ClientLayout = ClientLayout
{ clModulePrefix :: Text,
clOutputDir :: FilePath
}
data CodegenTarget = CodegenTarget
{ ctSchemas :: [Schema],
ctLayout :: SchemaLayout,
ctClientLayout :: ClientLayout
}
-- | Table types at @Prefix.Model@, clients at @Prefix.Client.Model@.
--
-- @Prefix@ is the path with non-module segments dropped (@src/Schema@ →
-- @Schema@).
simpleTarget :: FilePath -> Schema -> CodegenTarget
simpleTarget dir schema =
let prefix = prefixFromDir dir
in CodegenTarget
{ ctSchemas = [schema],
ctLayout = SchemaLayout (prefix <> ".") dir,
ctClientLayout =
ClientLayout
{ clModulePrefix = prefix <> ".Client.",
clOutputDir = dir </> "Client"
}
}
prefixFromDir :: FilePath -> Text
prefixFromDir dir =
case filter isModuleSegment (map T.pack (splitDirectories (dropTrailingPathSeparator dir))) of
[] ->
error $
"simpleTarget: "
<> dir
<> " has no Haskell module segment (e.g. src/Schema)"
parts -> T.intercalate "." parts
isModuleSegment :: Text -> Bool
isModuleSegment name =
case T.uncons name of
Just (c, rest) -> isUpper c && T.all isModuleChar rest
Nothing -> False
isModuleChar :: Char -> Bool
isModuleChar c = isAlphaNum c || c == '_' || c == '\''
targetSchemas :: [CodegenTarget] -> [Schema]
targetSchemas = concatMap (.ctSchemas)
targetOutputs :: CodegenTarget -> [GenOutput]
targetOutputs target =
concatMap (schemaOutputs target) target.ctSchemas
schemaOutputs :: CodegenTarget -> Schema -> [GenOutput]
schemaOutputs target schema =
enumOutputs target schema
++ modelOutputs target schema
++ includeOutputs target schema
++ clientOutputs target schema
enumOutputs :: CodegenTarget -> Schema -> [GenOutput]
enumOutputs target schema =
concat
[ enumOutput target enum
| enum <- schemaEnums schema,
isNothing (enumImport enum)
]
enumOutput :: CodegenTarget -> EnumSpec -> [GenOutput]
enumOutput target enum =
let layout = target.ctLayout
name = enumName enum
moduleName = layout.slModulePrefix <> name
path = layout.slOutputDir </> T.unpack name <> ".hs"
in [gen path (emitEnumModule moduleName enum)]
modelOutputs :: CodegenTarget -> Schema -> [GenOutput]
modelOutputs target schema =
concatMap (modelOutput target schema) (schemaModels schema)
modelOutput :: CodegenTarget -> Schema -> Model -> [GenOutput]
modelOutput target schema model =
let layout = target.ctLayout
name = modelName model
moduleName = layout.slModulePrefix <> name
path = layout.slOutputDir </> T.unpack name <> ".hs"
in [gen path (emitModelModule moduleName schema model)]
includeOutputs :: CodegenTarget -> Schema -> [GenOutput]
includeOutputs target schema =
concatMap (includeOutput target schema) (filter (not . null . modelRelations) (schemaModels schema))
includeOutput :: CodegenTarget -> Schema -> Model -> [GenOutput]
includeOutput target schema model =
let layout = target.ctLayout
name = modelName model
moduleName = layout.slModulePrefix <> "Include." <> name
path = layout.slOutputDir </> "Include" </> T.unpack name <> ".hs"
in [gen path (emitIncludeModule moduleName schema model)]
clientOutputs :: CodegenTarget -> Schema -> [GenOutput]
clientOutputs target schema =
concatMap (clientOutput target.ctClientLayout schema) (schemaModels schema)
clientOutput :: ClientLayout -> Schema -> Model -> [GenOutput]
clientOutput layout schema model =
let name = modelName model
moduleName = layout.clModulePrefix <> name
path = layout.clOutputDir </> T.unpack name <> ".hs"
in [gen path (emitClientModule moduleName schema model)]
gen :: FilePath -> Text -> GenOutput
gen path text = GenOutput {outputPath = path, outputText = text}