module Main (main) where import qualified Backend.Python as Python import qualified Backend.Rust as Rust import qualified Config import qualified Convex.Parser as P import Data.List (intercalate) import Dev (runDevMode) import qualified Dev import qualified Init import Options.Applicative import System.Directory (doesFileExist) import System.Exit (exitFailure) data Command = Generate GenerateOptions | Dev DevCliOptions | Init InitOptions data GenerateOptions = GenerateOptions { schemaPath :: FilePath, declarationsDir :: FilePath, target :: Config.GenTarget, outputFile :: Maybe FilePath } data DevCliOptions = DevCliOptions { devCliConfigPath :: FilePath } data InitOptions = InitOptions { initConfigPath :: FilePath } -- We can reuse the GenTarget Read instance from Config.hs if it were exported, -- but redefining it here keeps Main self-contained for parsing. instance Read Config.GenTarget where readsPrec _ "Python" = [(Config.Python, "")] readsPrec _ "Rust" = [(Config.Rust, "")] readsPrec _ _ = [] availableTargets :: String availableTargets = "Available: " ++ intercalate ", " (map show [(minBound :: Config.GenTarget) ..]) generateOptionsParser :: Parser GenerateOptions generateOptionsParser = GenerateOptions <$> strOption ( long "schema" <> metavar "SCHEMA_PATH" <> help "Path to the source schema.ts file" ) <*> strOption ( long "declarations" <> metavar "DECL_DIR" <> help "Path to the root of the tsc-generated declarations directory" ) <*> option auto ( long "target" <> metavar "TARGET" <> value Config.Python <> showDefault <> help ("Target language for code generation. " ++ availableTargets) ) <*> optional ( strOption ( long "output" <> short 'o' <> metavar "OUTPUT_FILE" <> help "File to write the generated code to (prints to stdout if omitted)" ) ) devCliOptionsParser :: Parser DevCliOptions devCliOptionsParser = DevCliOptions <$> strOption ( long "config" <> metavar "CONFIG_FILE" <> value "convex-parser.yaml" <> showDefault <> help "Path to the YAML config file" ) initOptionsParser :: Parser InitOptions initOptionsParser = InitOptions <$> strOption ( long "config" <> metavar "CONFIG_FILE" <> value "convex-parser.yaml" <> showDefault <> help "Path where the default YAML config file will be created" ) commandParser :: Parser Command commandParser = subparser ( command "generate" (info (Generate <$> generateOptionsParser <**> helper) (progDesc "Generate a client once and exit")) <> command "dev" (info (Dev <$> devCliOptionsParser <**> helper) (progDesc "Watch for changes and regenerate clients from a config file")) <> command "init" (info (Init <$> initOptionsParser <**> helper) (progDesc "Initialize a descriptive default config")) ) main :: IO () main = do cmd <- customExecParser p optsParserInfo case cmd of Generate opts -> runGenerate opts Dev opts -> runDev opts Init opts -> Init.runInit (initConfigPath opts) where optsParserInfo = info (commandParser <**> helper) ( fullDesc <> progDesc "Generate typed clients from a Convex schema and function definitions." <> header "convex-parser - A code generator for Convex" ) p = prefs showHelpOnError runGenerate :: GenerateOptions -> IO () runGenerate opts = do projectResult <- P.parseProject (schemaPath opts) (declarationsDir opts) case projectResult of Left err -> do putStrLn $ "Error parsing project: " ++ err exitFailure Right project -> let generatedCode = case target opts of Config.Python -> Python.generatePythonCode project Config.Rust -> Rust.generateRustCode project in case outputFile opts of Just path -> writeFile path generatedCode Nothing -> putStrLn generatedCode runDev :: DevCliOptions -> IO () runDev opts = do configExists <- doesFileExist (devCliConfigPath opts) if not configExists then do putStrLn $ "Error: Config file not found at " ++ devCliConfigPath opts exitFailure else do configResult <- Config.loadConfig (devCliConfigPath opts) case configResult of Left err -> do putStrLn "Error parsing config file:" print err exitFailure Right config -> let declarationsDir = Config.declarationsDir config devOpts = Dev.DevOptions (Config.projectPath config) declarationsDir config in runDevMode config