ihp-ide-1.4.0: IHP/IDE/CodeGen/ActionGenerator.hs
module IHP.IDE.CodeGen.ActionGenerator (buildPlan) where
import IHP.Prelude
import qualified Data.Text as Text
import IHP.IDE.CodeGen.Types
import qualified IHP.IDE.SchemaDesigner.Parser as SchemaDesigner
import IHP.IDE.SchemaDesigner.Types
import qualified IHP.IDE.CodeGen.ViewGenerator as ViewGenerator
import Text.Countable (pluralize)
data ActionConfig = ActionConfig
{ controllerName :: Text
, applicationName :: Text
, modelName :: Text
, actionName :: Text
} deriving (Eq, Show)
buildPlan :: Text -> Text -> Text -> Bool -> IO (Either Text [GeneratorAction])
buildPlan actionName applicationName controllerName doGenerateView=
if (null actionName || null controllerName)
then pure $ Left "Neither action name nor controller name can be empty"
else do
schema <- SchemaDesigner.parseSchemaSql >>= \case
Left parserError -> pure []
Right statements -> pure statements
let actionConfig = ActionConfig {controllerName, applicationName, modelName, actionName }
let actionPlan = generateGenericAction schema actionConfig doGenerateView
if doGenerateView
then do
viewPlan <- ViewGenerator.buildPlan viewName applicationName controllerName
case viewPlan of
Right viewPlan' -> pure $ Right $ actionPlan ++ viewPlan'
Left error -> pure $ Left error
else pure $ Right $ actionPlan
where
viewName = ucfirst $ if "Action" `isSuffixOf` actionName
then Text.dropEnd 6 actionName
else actionName
modelName = tableNameToModelName controllerName
-- E.g. qualifiedViewModuleName config "Edit" == "Web.View.Users.Edit"
-- qualifiedViewModuleName :: ActionConfig -> Text -> Text
-- qualifiedViewModuleName config viewName =
-- config.applicationName <> ".View." <> config.controllerName <> "." <> viewName
generateGenericAction :: [Statement] -> ActionConfig -> Bool -> [GeneratorAction]
generateGenericAction schema config doGenerateView =
let
controllerName = config.controllerName
name = ucfirst $ config.actionName
singularName = config.modelName
nameWithSuffix = if "Action" `isSuffixOf` name
then name
else name <> "Action" -- e.g. TestAction
viewName = if "Action" `isSuffixOf` name
then Text.dropEnd 6 name
else name
indexAction = pluralize singularName <> "Action"
specialCases = [
(indexAction, indexContent)
, ("Show" <> singularName <> "Action", showContent)
, ("Edit" <> singularName <> "Action", editContent)
, ("Update" <> singularName <> "Action", updateContent)
, ("Create" <> singularName <> "Action", createContent)
, ("Delete" <> singularName <> "Action", deleteContent)
]
actionContent = if doGenerateView
then
" action " <> nameWithSuffix <> " = " <> "do" <> "\n"
<> " render " <> viewName <> "View { .. }\n"
else
""
<> " action " <> nameWithSuffix <> " = " <> "do" <> "\n"
<> " redirectTo "<> controllerName <> "Action\n"
modelVariablePlural = lcfirst name
modelVariableSingular = lcfirst singularName
idFieldName = lcfirst singularName <> "Id"
idType = "Id " <> singularName
model = ucfirst singularName
indexContent =
""
<> " action " <> name <> "Action = do\n"
<> " " <> modelVariablePlural <> " <- query @" <> model <> " |> fetch\n"
<> " render IndexView { .. }\n"
newContent =
""
<> " action New" <> singularName <> "Action = do\n"
<> " let " <> modelVariableSingular <> " = newRecord\n"
<> " render NewView { .. }\n"
showContent =
""
<> " action Show" <> singularName <> "Action { " <> idFieldName <> " } = do\n"
<> " " <> modelVariableSingular <> " <- fetch " <> idFieldName <> "\n"
<> " render ShowView { .. }\n"
editContent =
""
<> " action Edit" <> singularName <> "Action { " <> idFieldName <> " } = do\n"
<> " " <> modelVariableSingular <> " <- fetch " <> idFieldName <> "\n"
<> " render EditView { .. }\n"
updateContent =
""
<> " action Update" <> singularName <> "Action { " <> idFieldName <> " } = do\n"
<> " " <> modelVariableSingular <> " <- fetch " <> idFieldName <> "\n"
<> " " <> modelVariableSingular <> "\n"
<> " |> build" <> singularName <> "\n"
<> " |> ifValid \\case\n"
<> " Left " <> modelVariableSingular <> " -> render EditView { .. }\n"
<> " Right " <> modelVariableSingular <> " -> do\n"
<> " " <> modelVariableSingular <> " <- " <> modelVariableSingular <> " |> updateRecord\n"
<> " setSuccessMessage \"" <> model <> " updated\"\n"
<> " redirectTo Edit" <> singularName <> "Action { .. }\n"
createContent =
""
<> " action Create" <> singularName <> "Action = do\n"
<> " let " <> modelVariableSingular <> " = newRecord @" <> model <> "\n"
<> " " <> modelVariableSingular <> "\n"
<> " |> build" <> singularName <> "\n"
<> " |> ifValid \\case\n"
<> " Left " <> modelVariableSingular <> " -> render NewView { .. }\n"
<> " Right " <> modelVariableSingular <> " -> do\n"
<> " " <> modelVariableSingular <> " <- " <> modelVariableSingular <> " |> createRecord\n"
<> " setSuccessMessage \"" <> model <> " created\"\n"
<> " redirectTo " <> name <> "Action\n"
deleteContent =
""
<> " action Delete" <> singularName <> "Action { " <> idFieldName <> " } = do\n"
<> " " <> modelVariableSingular <> " <- fetch " <> idFieldName <> "\n"
<> " deleteRecord " <> modelVariableSingular <> "\n"
<> " setSuccessMessage \"" <> model <> " deleted\"\n"
<> " redirectTo " <> name <> "Action\n"
typesContentGeneric =
" | " <> nameWithSuffix
typesContentWithParameter =
" | " <> nameWithSuffix <> " { " <> idFieldName <> " :: !(" <> idType <> ") }\n"
chosenContent = fromMaybe actionContent (lookup nameWithSuffix specialCases)
chosenType = if chosenContent `elem` [actionContent, newContent, createContent, indexContent]
then typesContentGeneric
else typesContentWithParameter
in
[ AddAction { filePath = config.applicationName <> "/Controller/" <> controllerName <> ".hs", fileContent = chosenContent}
, AddToDataConstructor { dataConstructor = "data " <> controllerName, filePath = config.applicationName <> "/Types.hs", fileContent = chosenType }
]