mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-11-24 03:35:17 +03:00
Implement Entity generator (code needs cleanup).
This commit is contained in:
parent
8396974686
commit
8fafa01504
@ -1,11 +1,30 @@
|
|||||||
{{={= =}=}}
|
{{={= =}=}}
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
|
{=# entities =}
|
||||||
|
import * as {= entityLowerName =}State from '{= entityStatePath =}'
|
||||||
|
import * as {= entityLowerName =}Actions from '{= entityActionsPath =}'
|
||||||
|
import {= entity.name =} from '{= entityClassPath =}'
|
||||||
|
{=/ entities =}
|
||||||
|
|
||||||
|
|
||||||
export default class {= page.name =} extends Component {
|
export class {= page.name =} extends Component {
|
||||||
|
// TODO: Add propTypes.
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
{=& page.content =}
|
{=& page.content =}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(state => ({
|
||||||
|
{=# entities =}
|
||||||
|
{= entityLowerName =}List: {= entityLowerName =}State.selectors.all(state)
|
||||||
|
{=/ entities =}
|
||||||
|
}), {
|
||||||
|
{=# entities =}
|
||||||
|
add{= entityUpperName =}: {= entityLowerName =}Actions.add
|
||||||
|
{=/ entities =}
|
||||||
|
})({= page.name =})
|
||||||
|
22
stic/data/Generator/templates/react-app/src/entities/_entity/_Entity.js
vendored
Normal file
22
stic/data/Generator/templates/react-app/src/entities/_entity/_Entity.js
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{{={= =}=}}
|
||||||
|
export default class {= entity.name =} {
|
||||||
|
_data = {}
|
||||||
|
|
||||||
|
constructor (data = {}) {
|
||||||
|
this._data = {
|
||||||
|
{=# entity.fields =}
|
||||||
|
{= name =}: data.{= name =},
|
||||||
|
{=/ entity.fields =}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{=# entity.fields =}
|
||||||
|
get {= name =} () {
|
||||||
|
return this._data.{= name =}
|
||||||
|
}
|
||||||
|
{=/ entity.fields =}
|
||||||
|
|
||||||
|
toData () {
|
||||||
|
return this._data
|
||||||
|
}
|
||||||
|
}
|
2
stic/data/Generator/templates/react-app/src/entities/_entity/actionTypes.js
vendored
Normal file
2
stic/data/Generator/templates/react-app/src/entities/_entity/actionTypes.js
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
{{={= =}=}}
|
||||||
|
export const ADD = 'entities/{= entityLowerName =}/ADD'
|
11
stic/data/Generator/templates/react-app/src/entities/_entity/actions.js
vendored
Normal file
11
stic/data/Generator/templates/react-app/src/entities/_entity/actions.js
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{{={= =}=}}
|
||||||
|
import * as types from './actionTypes'
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{= entity.name =}} {= entityLowerName =}
|
||||||
|
*/
|
||||||
|
export const add = ({= entityLowerName =}) => ({
|
||||||
|
type: types.ADD,
|
||||||
|
data: {= entityLowerName =}.toData()
|
||||||
|
})
|
41
stic/data/Generator/templates/react-app/src/entities/_entity/state.js
vendored
Normal file
41
stic/data/Generator/templates/react-app/src/entities/_entity/state.js
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{{={= =}=}}
|
||||||
|
import { createSelector } from 'reselect'
|
||||||
|
|
||||||
|
import {= entity.name =} from './{= entity.name =}'
|
||||||
|
import * as types from './actionTypes'
|
||||||
|
|
||||||
|
|
||||||
|
// We assume that root reducer of the app will put this reducer under
|
||||||
|
// key ROOT_REDUCER_KEY.
|
||||||
|
const ROOT_REDUCER_KEY = 'entities/{= entity.name =}'
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
all: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const reducer = (state = initialState, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case types.ADD:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
all: [ ...state.all, action.data ]
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let selectors = {}
|
||||||
|
selectors.root = (state) => state[ROOT_REDUCER_KEY]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {{= entity.name =}[]}
|
||||||
|
*/
|
||||||
|
selectors.all = createSelector(selectors.root, (state) => {
|
||||||
|
return state.all.map(data => new {= entity.name =}(data))
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export { reducer, initialState, selectors, ROOT_REDUCER_KEY }
|
@ -1,12 +1,15 @@
|
|||||||
{{={= =}=}}
|
{{={= =}=}}
|
||||||
import { combineReducers } from 'redux'
|
import { combineReducers } from 'redux'
|
||||||
|
|
||||||
// import * as dataState from './modules/data/state'
|
{=# entities =}
|
||||||
|
import * as {= entityLowerName =}State from '{= entityStatePath =}'
|
||||||
|
{=/ entities =}
|
||||||
|
|
||||||
|
|
||||||
const states = [
|
const states = [
|
||||||
// dataState,
|
{=# entities =}
|
||||||
// Add reducer here to add it to the app.
|
{= entityLowerName =}State,
|
||||||
|
{=/ entities =}
|
||||||
]
|
]
|
||||||
|
|
||||||
const keyToReducer = states.reduce((acc, state) => {
|
const keyToReducer = states.reduce((acc, state) => {
|
||||||
|
@ -42,6 +42,7 @@ library:
|
|||||||
- text
|
- text
|
||||||
- aeson
|
- aeson
|
||||||
- directory
|
- directory
|
||||||
|
- split
|
||||||
|
|
||||||
executables:
|
executables:
|
||||||
stic-exe:
|
stic-exe:
|
||||||
|
@ -7,10 +7,12 @@ module Generator.Generators
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Aeson ((.=), object, ToJSON(..))
|
import Data.Aeson ((.=), object, ToJSON(..))
|
||||||
|
import Data.Char (toLower, toUpper)
|
||||||
import System.FilePath (FilePath, (</>), (<.>))
|
import System.FilePath (FilePath, (</>), (<.>))
|
||||||
|
|
||||||
import Generator.FileDraft
|
import Generator.FileDraft
|
||||||
import Wasp
|
import Wasp
|
||||||
|
import qualified Util
|
||||||
|
|
||||||
|
|
||||||
generateWebApp :: Wasp -> [FileDraft]
|
generateWebApp :: Wasp -> [FileDraft]
|
||||||
@ -39,6 +41,8 @@ generatePublicDir wasp
|
|||||||
, "manifest.json"
|
, "manifest.json"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
-- * Src dir
|
||||||
|
|
||||||
generateSrcDir :: Wasp -> [FileDraft]
|
generateSrcDir :: Wasp -> [FileDraft]
|
||||||
generateSrcDir wasp
|
generateSrcDir wasp
|
||||||
= (createCopyFileDraft ("src" </> "logo.png") ("src" </> "logo.png"))
|
= (createCopyFileDraft ("src" </> "logo.png") ("src" </> "logo.png"))
|
||||||
@ -48,13 +52,31 @@ generateSrcDir wasp
|
|||||||
, "App.css"
|
, "App.css"
|
||||||
, "index.js"
|
, "index.js"
|
||||||
, "index.css"
|
, "index.css"
|
||||||
, "reducers.js"
|
|
||||||
, "router.js"
|
, "router.js"
|
||||||
, "serviceWorker.js"
|
, "serviceWorker.js"
|
||||||
, "store/index.js"
|
, "store/index.js"
|
||||||
, "store/middleware/logger.js"
|
, "store/middleware/logger.js"
|
||||||
]
|
]
|
||||||
++ generatePages wasp
|
++ generatePages wasp
|
||||||
|
++ generateEntities wasp
|
||||||
|
++ [generateReducersJs wasp]
|
||||||
|
|
||||||
|
generateReducersJs :: Wasp -> FileDraft
|
||||||
|
generateReducersJs wasp = createTemplateFileDraft dstPath srcPath templateData
|
||||||
|
where
|
||||||
|
srcPath = "src" </> "reducers.js"
|
||||||
|
dstPath = srcPath
|
||||||
|
templateData = object
|
||||||
|
[ "wasp" .= wasp
|
||||||
|
, "entities" .= map toEntityData (getEntities wasp)
|
||||||
|
]
|
||||||
|
toEntityData entity = object
|
||||||
|
[ "entity" .= entity
|
||||||
|
, "entityLowerName" .= entityLowerName entity
|
||||||
|
, "entityStatePath" .= ("./" ++ (entityStatePath entity))
|
||||||
|
]
|
||||||
|
|
||||||
|
-- * Pages
|
||||||
|
|
||||||
generatePages :: Wasp -> [FileDraft]
|
generatePages :: Wasp -> [FileDraft]
|
||||||
generatePages wasp = generatePage wasp <$> getPages wasp
|
generatePages wasp = generatePage wasp <$> getPages wasp
|
||||||
@ -64,8 +86,106 @@ generatePage wasp page = createTemplateFileDraft dstPath srcPath templateData
|
|||||||
where
|
where
|
||||||
srcPath = "src" </> "_Page.js"
|
srcPath = "src" </> "_Page.js"
|
||||||
dstPath = "src" </> (pageName page) <.> "js"
|
dstPath = "src" </> (pageName page) <.> "js"
|
||||||
templateData = object ["wasp" .= wasp, "page" .= page]
|
templateData = object
|
||||||
|
[ "wasp" .= wasp
|
||||||
|
, "page" .= page
|
||||||
|
, "entities" .= map toEntityData (getEntities wasp)
|
||||||
|
]
|
||||||
|
toEntityData entity = object
|
||||||
|
[ "entity" .= entity
|
||||||
|
, "entityLowerName" .= entityLowerName entity
|
||||||
|
, "entityUpperName" .= entityUpperName entity
|
||||||
|
, "entityStatePath" .= ("./" ++ (entityStatePath entity))
|
||||||
|
, "entityActionsPath" .= ("./" ++ (entityActionsPath entity))
|
||||||
|
, "entityClassPath" .= ("./" ++ (entityClassPath entity))
|
||||||
|
]
|
||||||
|
|
||||||
|
-- * Entities
|
||||||
|
|
||||||
|
generateEntities :: Wasp -> [FileDraft]
|
||||||
|
generateEntities wasp = concat $ generateEntity wasp <$> getEntities wasp
|
||||||
|
|
||||||
|
-- TODO(martin): Create EntityData object that will contain more stuff,
|
||||||
|
-- like small camel case name and similar, that will be representation used in the
|
||||||
|
-- template files, instead of Entity directly.
|
||||||
|
-- Then build that from Entity and pass that to templates.
|
||||||
|
-- I could even have one data type per template.
|
||||||
|
-- Or, have function that builds json?
|
||||||
|
|
||||||
|
-- TODO(martin): Also, I should extract entity stuff into separate module,
|
||||||
|
-- there is too much logic here already.
|
||||||
|
|
||||||
|
-- TODO(martin): This file has lot of duplication + is missing tests, work on that.
|
||||||
|
|
||||||
|
generateEntity :: Wasp -> Entity -> [FileDraft]
|
||||||
|
generateEntity wasp entity =
|
||||||
|
[ generateEntityClass wasp entity
|
||||||
|
, generateEntityState wasp entity
|
||||||
|
, generateEntityActionTypes wasp entity
|
||||||
|
, generateEntityActions wasp entity
|
||||||
|
]
|
||||||
|
|
||||||
|
generateEntityClass :: Wasp -> Entity -> FileDraft
|
||||||
|
generateEntityClass wasp entity = createTemplateFileDraft dstPath srcPath templateData
|
||||||
|
where
|
||||||
|
srcPath = "src" </> "entities" </> "_entity" </> "_Entity.js"
|
||||||
|
dstPath = "src" </> entityClassPath entity
|
||||||
|
templateData = object
|
||||||
|
[ "wasp" .= wasp
|
||||||
|
, "entity" .= entity
|
||||||
|
]
|
||||||
|
|
||||||
|
generateEntityState :: Wasp -> Entity -> FileDraft
|
||||||
|
generateEntityState wasp entity = createTemplateFileDraft dstPath srcPath templateData
|
||||||
|
where
|
||||||
|
srcPath = "src" </> "entities" </> "_entity" </> "state.js"
|
||||||
|
dstPath = "src" </> entityStatePath entity
|
||||||
|
templateData = object
|
||||||
|
[ "wasp" .= wasp
|
||||||
|
, "entity" .= entity
|
||||||
|
]
|
||||||
|
|
||||||
|
generateEntityActionTypes :: Wasp -> Entity -> FileDraft
|
||||||
|
generateEntityActionTypes wasp entity = createTemplateFileDraft dstPath srcPath templateData
|
||||||
|
where
|
||||||
|
srcPath = "src" </> "entities" </> "_entity" </> "actionTypes.js"
|
||||||
|
dstPath = "src" </> "entities" </> (entityDirName entity) </> "actionTypes.js"
|
||||||
|
templateData = object
|
||||||
|
[ "wasp" .= wasp
|
||||||
|
, "entity" .= entity
|
||||||
|
, "entityLowerName" .= entityLowerName entity
|
||||||
|
]
|
||||||
|
|
||||||
|
generateEntityActions :: Wasp -> Entity -> FileDraft
|
||||||
|
generateEntityActions wasp entity = createTemplateFileDraft dstPath srcPath templateData
|
||||||
|
where
|
||||||
|
srcPath = "src" </> "entities" </> "_entity" </> "actions.js"
|
||||||
|
dstPath = "src" </> entityActionsPath entity
|
||||||
|
templateData = object
|
||||||
|
[ "wasp" .= wasp
|
||||||
|
, "entity" .= entity
|
||||||
|
, "entityLowerName" .= entityLowerName entity
|
||||||
|
]
|
||||||
|
|
||||||
|
entityDirName :: Entity -> String
|
||||||
|
entityDirName entity = Util.camelToKebabCase (entityName entity)
|
||||||
|
|
||||||
|
entityLowerName :: Entity -> String
|
||||||
|
entityLowerName Entity{entityName=name} = (toLower $ head name) : (tail name)
|
||||||
|
|
||||||
|
entityUpperName :: Entity -> String
|
||||||
|
entityUpperName Entity{entityName=name} = (toUpper $ head name) : (tail name)
|
||||||
|
|
||||||
|
entityStatePath :: Entity -> String
|
||||||
|
entityStatePath entity = "entities" </> (entityDirName entity) </> "state.js"
|
||||||
|
|
||||||
|
entityActionsPath :: Entity -> String
|
||||||
|
entityActionsPath entity = "entities" </> (entityDirName entity) </> "actions.js"
|
||||||
|
|
||||||
|
entityClassPath :: Entity -> String
|
||||||
|
entityClassPath entity = "entities" </> (entityDirName entity) </> (entityName entity) <.> "js"
|
||||||
|
|
||||||
|
-- * Helpers
|
||||||
|
|
||||||
-- | Creates template file draft that uses given path as both src and dst path
|
-- | Creates template file draft that uses given path as both src and dst path
|
||||||
-- and wasp as template data.
|
-- and wasp as template data.
|
||||||
|
16
stic/src/Util.hs
Normal file
16
stic/src/Util.hs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module Util
|
||||||
|
( camelToKebabCase
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Data.Char (isUpper, toLower)
|
||||||
|
|
||||||
|
|
||||||
|
camelToKebabCase :: String -> String
|
||||||
|
camelToKebabCase "" = ""
|
||||||
|
camelToKebabCase camel@(camelHead:camelTail) = kebabHead:kebabTail
|
||||||
|
where
|
||||||
|
kebabHead = toLower camelHead
|
||||||
|
kebabTail = concat $ map
|
||||||
|
(\(a, b) -> (if (isCamelHump (a, b)) then ['-'] else []) ++ [toLower b])
|
||||||
|
(zip camel camelTail)
|
||||||
|
isCamelHump (a, b) = (not . isUpper) a && isUpper b
|
@ -16,6 +16,8 @@ module Wasp
|
|||||||
, Entity (..)
|
, Entity (..)
|
||||||
, EntityField (..)
|
, EntityField (..)
|
||||||
, EntityFieldType (..)
|
, EntityFieldType (..)
|
||||||
|
, getEntities
|
||||||
|
, addEntity
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Aeson ((.=), object, ToJSON(..))
|
import Data.Aeson ((.=), object, ToJSON(..))
|
||||||
@ -89,6 +91,12 @@ data EntityField = EntityField
|
|||||||
|
|
||||||
data EntityFieldType = EftString | EftBoolean deriving (Show, Eq)
|
data EntityFieldType = EftString | EftBoolean deriving (Show, Eq)
|
||||||
|
|
||||||
|
getEntities :: Wasp -> [Entity]
|
||||||
|
getEntities (Wasp elems) = [entity | (WaspElementEntity entity) <- elems]
|
||||||
|
|
||||||
|
addEntity :: Wasp -> Entity -> Wasp
|
||||||
|
addEntity (Wasp elems) entity = Wasp $ (WaspElementEntity entity):elems
|
||||||
|
|
||||||
-- * ToJSON instances.
|
-- * ToJSON instances.
|
||||||
|
|
||||||
-- NOTE(martin): Here I define general transformation of App into JSON that I can then easily use
|
-- NOTE(martin): Here I define general transformation of App into JSON that I can then easily use
|
||||||
@ -107,9 +115,25 @@ instance ToJSON Page where
|
|||||||
, "route" .= pageRoute page
|
, "route" .= pageRoute page
|
||||||
, "content" .= pageContent page
|
, "content" .= pageContent page
|
||||||
]
|
]
|
||||||
|
|
||||||
|
instance ToJSON Entity where
|
||||||
|
toJSON entity = object
|
||||||
|
[ "name" .= entityName entity
|
||||||
|
, "fields" .= entityFields entity
|
||||||
|
]
|
||||||
|
|
||||||
|
instance ToJSON EntityField where
|
||||||
|
toJSON entityField = object
|
||||||
|
[ "name" .= entityFieldName entityField
|
||||||
|
, "type" .= entityFieldType entityField
|
||||||
|
]
|
||||||
|
|
||||||
|
instance ToJSON EntityFieldType where
|
||||||
|
toJSON EftString = "string"
|
||||||
|
toJSON EftBoolean = "boolean"
|
||||||
|
|
||||||
instance ToJSON Wasp where
|
instance ToJSON Wasp where
|
||||||
toJSON wasp = object
|
toJSON wasp = object
|
||||||
[ "app" .= getApp wasp
|
[ "app" .= getApp wasp
|
||||||
, "pages" .= getPages wasp
|
, "pages" .= getPages wasp
|
||||||
]
|
]
|
||||||
|
|
||||||
|
17
stic/test/UtilTest.hs
Normal file
17
stic/test/UtilTest.hs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
module UtilTest where
|
||||||
|
|
||||||
|
import Test.Tasty.Hspec
|
||||||
|
|
||||||
|
import Util
|
||||||
|
|
||||||
|
|
||||||
|
spec_camelToKebabCase :: Spec
|
||||||
|
spec_camelToKebabCase = do
|
||||||
|
"foobar" ~> "foobar"
|
||||||
|
"s3" ~> "s3"
|
||||||
|
"fooBarBar" ~> "foo-bar-bar"
|
||||||
|
"s3Folder" ~> "s3-folder"
|
||||||
|
"S3Folder" ~> "s3-folder"
|
||||||
|
where
|
||||||
|
camel ~> kebab = it (camel ++ " -> " ++ kebab) $ do
|
||||||
|
camelToKebabCase camel `shouldBe` kebab
|
Loading…
Reference in New Issue
Block a user