{-# LANGUAGE OverloadedStrings #-} -- | Contains the generation logic and functions of the different possible outputs. module Generation.OutputGenerator(GenerationFunction, generateJSServer, generateJSClient, generatePythonClient) where import Control.Monad import Data.List import qualified Data.Text.Lazy.IO as TL import qualified Generation.ServiceGenerator as SG import qualified Generation.TemplateCompiler as TC import Paths_harmony import System.Directory import System.Exit (ExitCode (..)) import System.Log.Formatter () import System.Log.Handler () import System.Log.Handler.Simple () import System.Log.Handler.Syslog () import System.Log.Logger import System.Process (system) import qualified TypeCheck.ApiSpec as AS -- | A template is a source file and a new extension; e.g. ("template.tpl", "js") will result in -- "template.js" after the template compilation. type TemplateInfo = (FilePath, String) -- | All the info necesaary in order to generate a target: -- * List of regular files (this will be just directly copied) -- * List of templates that will be compiled -- * A map from a Harmony type to the target's type type GenerationInfo = ([FilePath], [TemplateInfo], AS.Type -> String) -- | A function that generates the target. type GenerationFunction = FilePath -- ^ Output path -> AS.ApiSpec -- ^ Information of the defined web service -> IO () -- | Target generation function. generateJSServer, generateJSClient, generatePythonClient :: GenerationFunction generateJSServer = generateOutput (files, templates, fieldMapping) postOpFunc where files = [] templates = [ ("templates/server/js/server.tpl", "js") , ("templates/server/js/package.tpl", "json") ] fieldMapping AS.TString = "String" fieldMapping AS.TLong = "Number" fieldMapping AS.TInt = "Number" fieldMapping AS.TDouble = "Number" -- An enum is an string with constraints. fieldMapping (AS.TEnum _) = "String" fieldMapping (AS.TStruct t) = t fieldMapping (AS.TList t) = fieldMapping t fieldMapping other = error $ "Javascript server generation: Type not recognized -> " ++ show other generateJSClient = error "Javascript client is not implemented yet" generatePythonClient = generateOutput (files, templates, fieldMapping) postOpFunc where files = [] templates = [ ("templates/client/python/client.tpl", "py") , ("templates/client/python/test.tpl", "py") ] -- These are the generators for the different types used by Hypothesis. fieldMapping AS.TString = "strategy([strategy(integers_in_range(65,90)) | strategy(integers_in_range(97, 122))]).map(lambda l: map(chr, l)).map(lambda l: ''.join(l))" fieldMapping AS.TInt = "integers_in_range(-1000,1000)" fieldMapping AS.TLong = "integers_in_range(-1000,1000)" fieldMapping AS.TDouble = "error:PythonNoTypes (Double)" fieldMapping (AS.TEnum _) = "error: no directly translation from enum type to Hypothesis type" fieldMapping (AS.TStruct name) = name ++ "Data" fieldMapping (AS.TList t) = "[" ++ fieldMapping t ++ "]" fieldMapping other = error $ "Python client generation: Type not recognized -> " ++ show other -- | Applies different post processing operations to each file type. postOpFunc :: String -> FilePath -> IO () postOpFunc "js" = applyJsBeautify postOpFunc "py" = applyYapf postOpFunc _ = \_ -> return () applyJsBeautify :: FilePath -> IO () applyJsBeautify path = do infoM "Generation.OutputGenerator" $ "Applying js-beautifier to " ++ path outcome <- system $ "js-beautify " ++ path ++ " > tempfile && cat tempfile > " ++ path ++ " && rm tempfile" case outcome of ExitSuccess -> return () (ExitFailure _) -> warningM "Generation.OutputGenerator" $ "There was a problem applying the python beautifier, " ++ "please check it is installed and in the system's path (if " ++ "you ignore this message the Python generated files will not be properly formatted" applyYapf :: FilePath -> IO () applyYapf path = do infoM "Generation.OutputGenerator" $ "Applying yapf to " ++ path outcome <- system $ "yapf " ++ path ++ " > tempfile && cat tempfile > " ++ path ++ " && rm tempfile" case outcome of ExitSuccess -> return () (ExitFailure _) -> warningM "Generation.OutputGenerator" $ "There was a problem applying the python beautifier, " ++ "please check it is installed and in the system's path (if " ++ "you ignore this message the Python generated files will not be properly formatted" -- | Uses all the information provided by the user (and the input file) and generates -- the output by compiling the templates and copying all the files to the output directory. generateOutput :: GenerationInfo -- ^ The information gathered from the user -> (String -> FilePath -> IO ()) -- ^ A function that performs a different operation per file extension -> FilePath -- ^ Output path -> AS.ApiSpec -- ^ The information gathered from the input file (specification) -> IO () generateOutput (files, templates, fieldMapping) postOpFunc outputPath apiSpec = do updateGlobalLogger "Generation.OutputGenerator" (setLevel INFO) forM_ files (copy outputPath) forM_ templates (generateAndWrite outputPath (SG.generateService apiSpec fieldMapping) postOpFunc) -- | Copies a file. copy :: FilePath -- ^ Origin file -> FilePath -- ^ Destination path -> IO () copy origin dest = do cabalFilePath <- getDataFileName origin copyFile cabalFilePath (dest ++ "/" ++ origin) -- | Generate a template and writes it to the destination path. generateAndWrite :: FilePath -- ^ Destination path -> TC.Service -- ^ Information for the template -> (String -> FilePath -> IO ()) -- ^ A function that performs a different operation per file extension -> TemplateInfo -- ^ Information of the template -> IO () generateAndWrite dest service postOpFunc (templatePath, newExt) = do output <- TC.render templatePath service createDirectoryIfMissing {- create parent dirs too -} True destDir TL.writeFile destFile output postOpFunc newExt destFile where destFileWithoutExt = dest ++ takeWhile (/= '.') (dropWhile (/= '/') templatePath) destDir = let indices = elemIndices '/' destFile in if null indices then "." else take (last indices + 1) destFile destFile = destFileWithoutExt ++ "." ++ newExt