Ext code files are now nicely represented and copied correctly and lazily. (#68)

Ext code files are now nicely represented and copied correctly and lazily.
This commit is contained in:
Martin Šošić 2020-01-16 18:32:47 +01:00 committed by GitHub
parent a1213677d0
commit 2e409b2854
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 139 additions and 85 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

57
src/ExternalCode.hs Normal file
View File

@ -0,0 +1,57 @@
module ExternalCode
( File
, getFilePathInExtCodeDir
, getFileText
, readFiles
) where
import System.FilePath ((</>))
import qualified Data.Text.Lazy as TextL
import qualified Data.Text.Lazy.IO as TextL.IO
import Data.Text (Text)
import qualified Util.IO
data File = File
{ _pathInExtCodeDir :: !FilePath -- ^ Path relative to external code directory.
, _text :: TextL.Text -- ^ File content. It will throw error when evaluated if file is not textual file.
}
instance Show File where
show = show . _pathInExtCodeDir
instance Eq File where
f1 == f2 = (_pathInExtCodeDir f1) == (_pathInExtCodeDir f2)
-- | Returns path relative to the external code directory.
getFilePathInExtCodeDir :: File -> FilePath
getFilePathInExtCodeDir = _pathInExtCodeDir
-- | Unsafe method: throws error if text could not be read (if file is not a textual file)!
getFileText :: File -> Text
getFileText = TextL.toStrict . _text
-- | Returns all files contained in the specified external code dir, recursively.
-- File paths are relative to the specified external code dir path.
readFiles :: FilePath -> IO [File]
readFiles extCodeDirPath = do
filePaths <- Util.IO.listDirectoryDeep extCodeDirPath
-- NOTE: We read text from all the files, regardless if they are text files or not, because
-- we don't know if they are a text file or not.
-- Since we do lazy reading (Text.Lazy), this is not a problem as long as we don't try to use
-- text of a file that is actually not a text file -> then we will get an error when Haskell
-- actually tries to read that file.
-- TODO: We are doing lazy IO here, and there is an idea of it being a thing to avoid, due to no
-- control over when resources are released and similar.
-- If we do figure out that this is causing us problems, we could do the following refactoring:
-- Don't read files at this point, just list them, and Wasp will contain just list of filepaths.
-- Modify TextFileDraft so that it also takes text transformation function (Text -> Text),
-- or create new file draft that will support that.
-- In generator, when creating TextFileDraft, give it function/logic for text transformation,
-- and it will be taken care of when draft will be written to the disk.
fileTexts <- mapM (TextL.IO.readFile . (extCodeDirPath </>)) filePaths
let files = map (\(path, text) -> File path text) (zip filePaths fileTexts)
return files

View File

@ -85,7 +85,7 @@ generateEntityComponents wasp entity = concat
-- {=/ typedFields =}
generateEntityCreateForm :: Wasp -> EntityForm -> FileDraft
generateEntityCreateForm wasp entityForm =
createTemplateFileDraft dstPath templateSrcPath templateData
createTemplateFileDraft dstPath templateSrcPath (Just templateData)
where
-- NOTE(matija): There should always be an entity in wasp for the given entity form,
-- we want an error to be thrown otherwise.
@ -119,7 +119,7 @@ generateEntityList wasp entity
-- | Helper function that captures common logic for generating entity file draft.
createSimpleEntityFileDraft :: Wasp -> Entity -> FilePath -> FilePath -> FileDraft
createSimpleEntityFileDraft wasp entity dstPathInSrc srcPathInEntityTemplatesDir
= createTemplateFileDraft dstPath srcPath templateData
= createTemplateFileDraft dstPath srcPath (Just templateData)
where
srcPath = entityTemplatesDirPath </> srcPathInEntityTemplatesDir
dstPath = Common.srcDirPath </> dstPathInSrc

View File

@ -0,0 +1,43 @@
module Generator.ExternalCode
( generateExternalCodeDir
, externalCodeDirPathInSrc
) where
import System.FilePath ((</>), takeExtension)
import CompileOptions (CompileOptions)
import qualified CompileOptions
import Wasp (Wasp)
import qualified Wasp
import qualified Generator.FileDraft as FD
import qualified Generator.Common as Common
import qualified ExternalCode
externalCodeDirPathInSrc :: FilePath
externalCodeDirPathInSrc = "ext-src"
generateExternalCodeDir :: CompileOptions -> Wasp -> [FD.FileDraft]
generateExternalCodeDir compileOptions wasp =
map (generateFile compileOptions) (Wasp.getExternalCodeFiles wasp)
getFileDstPath :: ExternalCode.File -> FilePath
getFileDstPath file = Common.srcDirPath </> externalCodeDirPathInSrc </> (ExternalCode.getFilePathInExtCodeDir file)
getFileSrcPath :: CompileOptions -> ExternalCode.File -> FilePath
getFileSrcPath compileOptions file =
(CompileOptions.externalCodeDirPath compileOptions) </> (ExternalCode.getFilePathInExtCodeDir file)
generateFile :: CompileOptions -> ExternalCode.File -> FD.FileDraft
generateFile compileOptions file
| extension `elem` ["js", "jsx"] = generateJsFile file
| otherwise = FD.createCopyFileDraft (getFileDstPath file) (getFileSrcPath compileOptions file)
where
extension = takeExtension (getFileSrcPath compileOptions file)
-- TODO: Now here we do preprocessing!
generateJsFile :: ExternalCode.File -> FD.FileDraft
generateJsFile file = FD.createTextFileDraft (getFileDstPath file) (ExternalCode.getFileText file)

View File

@ -1,28 +0,0 @@
module Generator.ExternalCodeDirGenerator
( generateExternalCodeDir
, externalCodeDirPathInSrc
) where
import System.FilePath ((</>))
import Data.Text (Text)
import CompileOptions (CompileOptions)
import Wasp (Wasp)
import qualified Wasp
import qualified Generator.FileDraft as FD
import qualified Generator.Common as Common
externalCodeDirPathInSrc :: FilePath
externalCodeDirPathInSrc = "ext-src"
generateExternalCodeDir :: CompileOptions -> Wasp -> [FD.FileDraft]
generateExternalCodeDir _ wasp = map generateExternalCodeFile (Wasp.getExternalCodeFiles wasp)
generateExternalCodeFile :: (FilePath, Text) -> FD.FileDraft
generateExternalCodeFile (pathInExtCodeDir, content) = FD.createTextFileDraft dstPath content
where
dstPath = Common.srcDirPath </> externalCodeDirPathInSrc </> pathInExtCodeDir

View File

@ -36,7 +36,7 @@ instance Writeable FileDraft where
write dstDir (FileDraftTextFd draft) = write dstDir draft
createTemplateFileDraft :: FilePath -> FilePath -> Aeson.Value -> FileDraft
createTemplateFileDraft :: FilePath -> FilePath -> Maybe Aeson.Value -> FileDraft
createTemplateFileDraft dstPath templateRelPath templateData =
FileDraftTemplateFd $ TemplateFD.TemplateFileDraft dstPath templateRelPath templateData

View File

@ -11,15 +11,13 @@ import Generator.FileDraft.WriteableMonad
data CopyFileDraft = CopyFileDraft
{ -- | Path of file to be written, relative to some root dir.
copyFileDraftDstFilepath :: !FilePath
-- | Path of source file, relative to some root dir,
-- normally not the same one as root dir for dstFilepath.
-- | Absolute path of source file.
, copyFileDraftSrcFilepath :: !FilePath
}
deriving (Show, Eq)
instance Writeable CopyFileDraft where
write dstDir (CopyFileDraft dstFilepath srcFilepath) = do
let dstAbsFilepath = dstDir </> dstFilepath
srcAbsFilepath <- getTemplateFileAbsPath srcFilepath
createDirectoryIfMissing True (takeDirectory dstAbsFilepath)
copyFile srcAbsFilepath dstAbsFilepath
write dstDir (CopyFileDraft relDstPath absSrcPath) = do
let absDstPath = dstDir </> relDstPath
createDirectoryIfMissing True (takeDirectory absDstPath)
copyFile absSrcPath absDstPath

View File

@ -3,7 +3,6 @@ module Generator.FileDraft.TemplateFileDraft
) where
import System.FilePath (FilePath, (</>), takeDirectory)
import Data.Text (Text)
import qualified Data.Aeson as Aeson
import Generator.FileDraft.Writeable
@ -17,22 +16,23 @@ data TemplateFileDraft = TemplateFileDraft
-- | Path of template source file, relative to templates root dir.
, templateFileDraftTemplateRelFilepath :: !FilePath
-- | Data to be fed to the template while rendering it.
, templateFileDraftTemplateData :: !Aeson.Value
, templateFileDraftTemplateData :: Maybe Aeson.Value
}
deriving (Show, Eq)
instance Writeable TemplateFileDraft where
write dstDir draft =
compileAndRenderTemplate templateRelFilepath templateData >>= writeContentToFile
write dstDir draft = do
createDirectoryIfMissing True (takeDirectory absDstPath)
case templateFileDraftTemplateData draft of
Nothing -> do
absSrcPath <- getTemplateFileAbsPath templateSrcPathInTemplateDir
copyFile absSrcPath absDstPath
Just tmplData -> do
content <- compileAndRenderTemplate templateSrcPathInTemplateDir tmplData
writeFileFromText absDstPath content
where
templateRelFilepath :: FilePath
templateRelFilepath = templateFileDraftTemplateRelFilepath draft
templateSrcPathInTemplateDir :: FilePath
templateSrcPathInTemplateDir = templateFileDraftTemplateRelFilepath draft
templateData :: Aeson.Value
templateData = templateFileDraftTemplateData draft
writeContentToFile :: (WriteableMonad m) => Text -> m ()
writeContentToFile content = do
let absDstFilepath = dstDir </> (templateFileDraftDstFilepath draft)
createDirectoryIfMissing True (takeDirectory absDstFilepath)
writeFileFromText absDstFilepath content
absDstPath :: FilePath
absDstPath = dstDir </> (templateFileDraftDstFilepath draft)

View File

@ -11,7 +11,7 @@ import Wasp
import Generator.FileDraft
import qualified Generator.EntityGenerator as EntityGenerator
import qualified Generator.PageGenerator as PageGenerator
import qualified Generator.ExternalCodeDirGenerator as ExternalCodeDirGenerator
import qualified Generator.ExternalCode as ExternalCodeGenerator
import qualified Generator.Common as Common
@ -22,7 +22,7 @@ generateWebApp wasp options = concatMap ($ wasp)
, generateGitignore
, generatePublicDir
, generateSrcDir
, ExternalCodeDirGenerator.generateExternalCodeDir options
, ExternalCodeGenerator.generateExternalCodeDir options
]
generateReadme :: Wasp -> [FileDraft]
@ -32,11 +32,11 @@ generatePackageJson :: Wasp -> [FileDraft]
generatePackageJson wasp = [simpleTemplateFileDraft "package.json" wasp]
generateGitignore :: Wasp -> [FileDraft]
generateGitignore wasp = [createTemplateFileDraft ".gitignore" "gitignore" (toJSON wasp)]
generateGitignore wasp = [createTemplateFileDraft ".gitignore" "gitignore" (Just $ toJSON wasp)]
generatePublicDir :: Wasp -> [FileDraft]
generatePublicDir wasp
= createCopyFileDraft ("public" </> "favicon.ico") ("public" </> "favicon.ico")
= createTemplateFileDraft ("public" </> "favicon.ico") ("public" </> "favicon.ico") Nothing
: map (\path -> simpleTemplateFileDraft ("public/" </> path) wasp)
[ "index.html"
, "manifest.json"
@ -46,7 +46,7 @@ generatePublicDir wasp
generateSrcDir :: Wasp -> [FileDraft]
generateSrcDir wasp
= (createCopyFileDraft (Common.srcDirPath </> "logo.png") ("src" </> "logo.png"))
= (createTemplateFileDraft (Common.srcDirPath </> "logo.png") ("src" </> "logo.png") Nothing)
: map (\path -> simpleTemplateFileDraft ("src/" </> path) wasp)
[ "index.js"
, "index.css"
@ -60,7 +60,7 @@ generateSrcDir wasp
++ [generateReducersJs wasp]
generateReducersJs :: Wasp -> FileDraft
generateReducersJs wasp = createTemplateFileDraft dstPath srcPath templateData
generateReducersJs wasp = createTemplateFileDraft dstPath srcPath (Just templateData)
where
srcPath = "src" </> "reducers.js"
dstPath = Common.srcDirPath </> "reducers.js"
@ -80,4 +80,4 @@ generateReducersJs wasp = createTemplateFileDraft dstPath srcPath templateData
-- | Creates template file draft that uses given path as both src and dst path
-- and wasp as template data.
simpleTemplateFileDraft :: FilePath -> Wasp -> FileDraft
simpleTemplateFileDraft path wasp = createTemplateFileDraft path path (toJSON wasp)
simpleTemplateFileDraft path wasp = createTemplateFileDraft path path (Just $ toJSON wasp)

View File

@ -16,7 +16,7 @@ import qualified Util
import Wasp
import Generator.FileDraft
import qualified Generator.EntityGenerator as EntityGenerator
import Generator.ExternalCodeDirGenerator (externalCodeDirPathInSrc)
import Generator.ExternalCode (externalCodeDirPathInSrc)
import qualified Generator.Common as Common
@ -30,7 +30,7 @@ generatePage wasp page =
++ generatePageStyle wasp page
generatePageComponent :: Wasp -> Page -> FileDraft
generatePageComponent wasp page = createTemplateFileDraft dstPath srcPath templateData
generatePageComponent wasp page = createTemplateFileDraft dstPath srcPath (Just templateData)
where
srcPath = "src" </> "_Page.js"
dstPath = FilePath.normalise $ Common.srcDirPath </> pageDirPathInSrc </> (pageName page) <.> "js"

View File

@ -2,13 +2,9 @@ module Lib
( compile
) where
import qualified Data.Text.IO as TextIO
import Data.Text (Text)
import System.FilePath ((</>))
import qualified Util.IO
import CompileOptions (CompileOptions)
import qualified CompileOptions
import qualified ExternalCode
import Parser
import Generator
import Wasp (setExternalCodeFiles)
@ -23,15 +19,7 @@ compile waspFile outDir options = do
case parseWasp waspStr of
Left err -> return $ Left (show err)
Right wasp -> do
externalCodeFiles <- readExternalCodeFiles $ CompileOptions.externalCodeDirPath options
externalCodeFiles <- ExternalCode.readFiles (CompileOptions.externalCodeDirPath options)
generateCode $ wasp `setExternalCodeFiles` externalCodeFiles
where
generateCode wasp = writeWebAppCode wasp outDir options >> return (Right ())
-- | Returns paths and contents of external code files.
-- Paths are relative to the given external code dir path.
readExternalCodeFiles :: FilePath -> IO [(FilePath, Text)]
readExternalCodeFiles externalCodeDirPath = do
externalCodeFilePaths <- Util.IO.listDirectoryDeep externalCodeDirPath
externalCodeFileContents <- mapM (TextIO.readFile . (externalCodeDirPath </>)) externalCodeFilePaths
return $ zip externalCodeFilePaths externalCodeFileContents

View File

@ -28,9 +28,9 @@ module Wasp
, getExternalCodeFiles
) where
import Data.Text (Text)
import Data.Aeson ((.=), object, ToJSON(..))
import qualified ExternalCode
import Wasp.App
import Wasp.Entity
import Wasp.EntityForm
@ -44,10 +44,7 @@ import qualified Util as U
data Wasp = Wasp
{ waspElements :: [WaspElement]
, waspJsImports :: [JsImport]
, externalCodeFiles ::
[( FilePath -- ^ Path relative to external code directory.
, Text -- ^ Text of that file.
)]
, externalCodeFiles :: [ExternalCode.File]
} deriving (Show, Eq)
data WaspElement
@ -66,10 +63,10 @@ fromWaspElems elems = Wasp
-- * External code files
getExternalCodeFiles :: Wasp -> [(FilePath, Text)]
getExternalCodeFiles :: Wasp -> [ExternalCode.File]
getExternalCodeFiles = externalCodeFiles
setExternalCodeFiles :: Wasp -> [(FilePath, Text)] -> Wasp
setExternalCodeFiles :: Wasp -> [ExternalCode.File] -> Wasp
setExternalCodeFiles wasp files = wasp { externalCodeFiles = files }
-- * Js imports

View File

@ -22,6 +22,5 @@ spec_CopyFileDraft = do
where
(dstDir, dstPath, srcPath) = ("a/b", "c/d/dst.txt", "e/src.txt")
fileDraft = createCopyFileDraft dstPath srcPath
expectedSrcPath = mockTemplatesDirAbsPath </> srcPath
expectedSrcPath = srcPath
expectedDstPath = dstDir </> dstPath
mockTemplatesDirAbsPath = Mock.getTemplatesDirAbsPath_impl Mock.defaultMockConfig

View File

@ -26,7 +26,7 @@ spec_TemplateFileDraft = do
where
(dstDir, dstPath, templatePath) = ("a/b", "c/d/dst.txt", "e/tmpl.txt")
templateData = object [ "foo" .= ("bar" :: String) ]
fileDraft = createTemplateFileDraft dstPath templatePath templateData
fileDraft = createTemplateFileDraft dstPath templatePath (Just templateData)
expectedDstPath = dstDir </> dstPath
mockTemplatesDirAbsPath = "mock/templates/dir"
mockTemplateContent = "Mock template content" :: Text