{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE TemplateHaskell #-} module Main where import Control.Monad import qualified Data.Bifunctor as BF import qualified Data.Text as T import Embed import Language.Haskell.TH import OpenAPI.Generate import qualified OpenAPI.Generate.Doc as Doc import qualified OpenAPI.Generate.Flags as OAF import OpenAPI.Generate.Internal.Util import qualified OpenAPI.Generate.Monad as OAM import qualified OpenAPI.Generate.Reference as Ref import Options import System.Directory import System.Exit import System.FilePath import System.IO.Error srcDirectory :: FilePath srcDirectory = "src" -- | Creates files from mostly static data stackProjectFiles :: -- | Name of the cabal project String -> -- | Name of the module String -> -- | Modules to export [String] -> [(FilePath, String)] stackProjectFiles packageName moduleName modulesToExport = [ ( packageName ++ ".cabal", unlines $ [ "cabal-version: 1.12", "", "name: " ++ packageName, "version: 0.1.0.0", "build-type: Simple", "", "library", " exposed-modules:", " " ++ moduleName ] <> fmap (" " <>) modulesToExport <> [ " hs-source-dirs:", " src", " build-depends:", " base >=4.7 && <5", " , text", " , ghc-prim", " , http-conduit", " , http-client", " , http-types", " , bytestring", " , aeson", " , unordered-containers", " , vector", " , scientific", " , time", " , mtl", " , transformers", " default-language: Haskell2010" ] ), ( "stack.yaml", unlines [ "resolver: lts-15.3", "packages:", "- ." ] ) ] replaceOpenAPI :: String -> String -> String replaceOpenAPI moduleName contents = T.unpack $ T.replace (T.pack "OpenAPI.Common") (T.pack $ moduleName ++ ".Common") $ T.replace (T.pack "OpenAPI.Configuration") (T.pack $ moduleName ++ ".Configuration") $ T.pack contents permitProceed :: FilePath -> Bool -> IO Bool permitProceed outputDirectory force = do outputDirectoryExists <- doesPathExist outputDirectory if outputDirectoryExists then if force then do putStrLn "output directory already exists and will be overwritten" pure True else do putStrLn "The output directory " putStrLn "" putStrLn $ " " ++ show outputDirectory putStrLn "" putStrLn "already exists. overwrite? Y/N" putStrLn "" answer <- getLine pure $ (answer == "Y") || (answer == "y") else do putStrLn "output directory will be created" pure True main :: IO () main = runCommand $ \opts args -> case args of [path] -> do spec <- decodeOpenApi path let env = OAM.createEnvironment opts $ Ref.buildReferenceMap spec logMessages = mapM_ (putStrLn . T.unpack) . OAM.transformGeneratorLogs moduleName = OAF.optModuleName opts (operationsQ, logs) = OAM.runGenerator env $ defineOperations (OAF.optModuleName opts) spec logMessages logs operationModules <- runQ operationsQ configurationInfo <- runQ $ defineConfigurationInformation moduleName spec let (modelsQ, logsModels) = OAM.runGenerator env $ defineModels moduleName spec logMessages logsModels modelModules <- runQ modelsQ let (securitySchemesQ, logs') = OAM.runGenerator env $ defineSecuritySchemes moduleName spec logMessages logs' securitySchemes' <- runQ securitySchemesQ let outputDirectory = OAF.optOutputDir opts showAndReplace = replaceOpenAPI moduleName . show modules = fmap (BF.bimap ("Operations" :) showAndReplace) operationModules <> fmap (BF.second showAndReplace) modelModules <> [ (["Configuration"], showAndReplace configurationInfo), (["SecuritySchemes"], showAndReplace securitySchemes'), (["Common"], replaceOpenAPI moduleName $(embedFile "src/OpenAPI/Common.hs")) ] modulesToExport = fmap ( (moduleName <>) . ("." <>) . joinWithPoint . fst ) modules mainFile = outputDirectory srcDirectory (moduleName ++ ".hs") mainModuleContent = show $ Doc.createModuleHeaderWithReexports moduleName modulesToExport "The main module which exports all functionality." filesToCreate = (mainFile, mainModuleContent) : (BF.first ((outputDirectory ) . (srcDirectory ) . (moduleName ) . (<> ".hs") . foldr1 ()) <$> modules) if OAF.optDryRun opts then mapM_ ( \(file, content) -> do putStrLn $ "File: " <> file putStrLn "---" putStrLn content putStrLn "---\n\n" ) filesToCreate else do proceed <- permitProceed outputDirectory (OAF.optForce opts) if proceed then do _ <- tryIOError (removeDirectoryRecursive outputDirectory) createDirectoryIfMissing True (outputDirectory srcDirectory) createDirectoryIfMissing True (outputDirectory srcDirectory moduleName) createDirectoryIfMissing True (outputDirectory srcDirectory moduleName "Operations") createDirectoryIfMissing True (outputDirectory srcDirectory moduleName "Types") mapM_ (uncurry writeFile) filesToCreate when (OAF.optGenerateStackProject opts) $ mapM_ ( uncurry writeFile . BF.first (outputDirectory ) ) (stackProjectFiles (OAF.optPackageName opts) moduleName modulesToExport) putStrLn "finished" else putStrLn "aborted" _ -> die "Failed to generate code because no OpenAPI specification was provided. Pass the location of the OpenAPI specification as CLI argument. Run the CLI with --help to see further options."