packages feed

tuispec-0.1.0.0: app/Main.hs

module Main where

import Data.List (isSuffixOf)
import Options.Applicative
import Text.Read (readMaybe)
import TuiSpec.Render (renderAnsiSnapshotFileWithFont, renderAnsiSnapshotTextFile)
import TuiSpec.Server qualified as Server
import TuiSpec.Types (AmbiguityMode (FailOnAmbiguous, FirstVisibleMatch))

data Command
    = Render RenderOptions
    | RenderText RenderTextOptions
    | Server ServerOptions

data RenderOptions = RenderOptions
    { inputPath :: FilePath
    , outputPath :: Maybe FilePath
    , themeName :: Maybe String
    , fontPath :: Maybe FilePath
    , cols :: Maybe Int
    , rows :: Maybe Int
    }

data RenderTextOptions = RenderTextOptions
    { textInputPath :: FilePath
    , textOutputPath :: Maybe FilePath
    , textCols :: Maybe Int
    , textRows :: Maybe Int
    }

data ServerOptions = ServerOptions
    { artifactDir :: FilePath
    , serverCols :: Int
    , serverRows :: Int
    , timeoutSeconds :: Int
    , ambiguity :: AmbiguityMode
    }

main :: IO ()
main = do
    parsedCommand <- execParser commandParserInfo
    runCommand parsedCommand

runCommand :: Command -> IO ()
runCommand parsedCommand =
    case parsedCommand of
        Render options -> do
            let outPath = maybe (defaultOutputPath (inputPath options)) id (outputPath options)
            renderAnsiSnapshotFileWithFont (fontPath options) (rows options) (cols options) (themeName options) (inputPath options) outPath
            putStrLn ("Rendered " <> outPath)
        RenderText options -> do
            let outPath = maybe (defaultTextOutputPath (textInputPath options)) id (textOutputPath options)
            renderAnsiSnapshotTextFile (textRows options) (textCols options) (textInputPath options) outPath
            putStrLn ("Rendered " <> outPath)
        Server options ->
            Server.runServer
                Server.ServerOptions
                    { Server.serverArtifactsDir = artifactDir options
                    , Server.serverTerminalCols = serverCols options
                    , Server.serverTerminalRows = serverRows options
                    , Server.serverTimeoutSeconds = timeoutSeconds options
                    , Server.serverAmbiguityMode = ambiguity options
                    }

commandParserInfo :: ParserInfo Command
commandParserInfo =
    info
        (parseCommand <**> helper)
        ( fullDesc
            <> progDesc "Render ANSI snapshot artifacts to PNG"
            <> header "tuispec"
        )

parseCommand :: Parser Command
parseCommand =
    hsubparser
        ( command
            "render"
            ( info
                (Render <$> parseRenderOptions)
                (progDesc "Render a .ansi.txt snapshot file")
            )
            <> command
                "render-text"
                ( info
                    (RenderText <$> parseRenderTextOptions)
                    (progDesc "Render visible plain text from a .ansi.txt snapshot file using terminal emulation")
                )
            <> command
                "server"
                ( info
                    (Server <$> parseServerOptions)
                    (progDesc "Run JSON-RPC server on stdin/stdout for interactive TUI orchestration")
                )
        )

parseRenderOptions :: Parser RenderOptions
parseRenderOptions =
    RenderOptions
        <$> argument
            str
            ( metavar "SNAPSHOT_ANSI_TXT"
                <> help "Path to snapshot ANSI file"
            )
        <*> optional
            ( strOption
                ( long "out"
                    <> metavar "OUTPUT_PNG"
                    <> help "Output PNG path"
                )
            )
        <*> optional
            ( strOption
                ( long "theme"
                    <> metavar "THEME"
                    <> help "Theme override: auto|dark|light (default: metadata, then auto)"
                )
            )
        <*> optional
            ( strOption
                ( long "font"
                    <> metavar "FONT_PATH"
                    <> help "TTF/TTC font path override for PNG rendering (default: system fallback fonts)"
                )
            )
        <*> optional
            ( option
                positiveIntReader
                ( long "cols"
                    <> metavar "N"
                    <> help "Viewport columns override (default: metadata, then 134)"
                )
            )
        <*> optional
            ( option
                positiveIntReader
                ( long "rows"
                    <> metavar "N"
                    <> help "Viewport rows override (default: metadata, then 40)"
                )
            )

parseRenderTextOptions :: Parser RenderTextOptions
parseRenderTextOptions =
    RenderTextOptions
        <$> argument
            str
            ( metavar "SNAPSHOT_ANSI_TXT"
                <> help "Path to snapshot ANSI file"
            )
        <*> optional
            ( strOption
                ( long "out"
                    <> metavar "OUTPUT_TXT"
                    <> help "Output plain text path"
                )
            )
        <*> optional
            ( option
                positiveIntReader
                ( long "cols"
                    <> metavar "N"
                    <> help "Viewport columns override (default: metadata, then 134)"
                )
            )
        <*> optional
            ( option
                positiveIntReader
                ( long "rows"
                    <> metavar "N"
                    <> help "Viewport rows override (default: metadata, then 40)"
                )
            )

parseServerOptions :: Parser ServerOptions
parseServerOptions =
    ServerOptions
        <$> strOption
            ( long "artifact-dir"
                <> metavar "PATH"
                <> help "Artifacts directory base path for server sessions"
            )
        <*> option
            positiveIntReader
            ( long "cols"
                <> metavar "N"
                <> help "Default terminal columns"
                <> value 134
                <> showDefault
            )
        <*> option
            positiveIntReader
            ( long "rows"
                <> metavar "N"
                <> help "Default terminal rows"
                <> value 40
                <> showDefault
            )
        <*> option
            positiveIntReader
            ( long "timeout-seconds"
                <> metavar "N"
                <> help "Default timeout seconds"
                <> value 5
                <> showDefault
            )
        <*> option
            ambiguityModeReader
            ( long "ambiguity-mode"
                <> metavar "MODE"
                <> help "Default ambiguity mode: fail|first-visible"
                <> value FailOnAmbiguous
                <> showDefaultWith renderAmbiguityMode
            )

positiveIntReader :: ReadM Int
positiveIntReader =
    eitherReader $ \raw ->
        case readMaybe raw :: Maybe Int of
            Just parsed | parsed > 0 -> Right parsed
            _ -> Left "Expected a positive integer"

ambiguityModeReader :: ReadM AmbiguityMode
ambiguityModeReader =
    eitherReader $ \raw ->
        case raw of
            "fail" -> Right FailOnAmbiguous
            "first-visible" -> Right FirstVisibleMatch
            "first" -> Right FirstVisibleMatch
            _ -> Left "Expected ambiguity mode: fail|first-visible"

renderAmbiguityMode :: AmbiguityMode -> String
renderAmbiguityMode mode =
    case mode of
        FailOnAmbiguous -> "fail"
        FirstVisibleMatch -> "first-visible"

defaultOutputPath :: FilePath -> FilePath
defaultOutputPath input =
    if ".ansi.txt" `isSuffixOf` input
        then take (length input - length (".ansi.txt" :: String)) input <> ".png"
        else input <> ".png"

defaultTextOutputPath :: FilePath -> FilePath
defaultTextOutputPath input =
    if ".ansi.txt" `isSuffixOf` input
        then take (length input - length (".ansi.txt" :: String)) input <> ".txt"
        else input <> ".txt"