packages feed

moonlight-triangulation-1.3.0.0: bench/delaunay-compare/PictureMain.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeApplications #-}

-- | Thin effect boundary for rendering benchmark receipts as SVG pictures.
module Main (main) where

import Control.Exception (IOException, displayException, try)
import Control.Monad.Trans.Except (ExceptT (ExceptT), except, runExceptT)
import Data.Bifunctor (first)
import Data.Foldable (traverse_)
import Moonlight.Triangulation.Bench.DelaunayCompare.Domain (allSizeBands)
import Moonlight.Triangulation.Bench.DelaunayCompare.Picture
import System.Directory (createDirectoryIfMissing)
import System.Environment (getArgs)
import System.Exit (die)
import System.FilePath ((</>))
import System.IO (readFile')

data PictureCommandFailure
  = PictureCommandUsage
  | PictureCommandReadFailed !FilePath !String
  | PictureCommandCreateDirectoryFailed !FilePath !String
  | PictureCommandReceiptObstructed !PictureObstruction
  | PictureCommandWriteFailed !FilePath !String

main :: IO ()
main = do
  arguments <- getArgs
  outcome <- runExceptT (runPictureCommand arguments)
  either (die . renderPictureCommandFailure) pure outcome

runPictureCommand :: [String] -> ExceptT PictureCommandFailure IO ()
runPictureCommand = \case
  [inputCsv, outputDirectory] -> do
    source <-
      ExceptT
        ( first
            (PictureCommandReadFailed inputCsv . displayException)
            <$> try @IOException (readFile' inputCsv)
        )
    receipt <- except (first PictureCommandReceiptObstructed (parseBenchmarkReceipt source))
    let pictures =
          ( \sizeBand ->
              (pictureFileName sizeBand, renderComparisonPicture sizeBand receipt)
          )
            <$> allSizeBands
    ExceptT
      ( first
          (PictureCommandCreateDirectoryFailed outputDirectory . displayException)
          <$> try @IOException (createDirectoryIfMissing True outputDirectory)
      )
    traverse_ (writePicture outputDirectory) pictures
  _ -> except (Left PictureCommandUsage)

writePicture :: FilePath -> (FilePath, String) -> ExceptT PictureCommandFailure IO ()
writePicture outputDirectory (fileName, svgDocument) =
  let outputPath = outputDirectory </> fileName
   in ExceptT
        ( first
            (PictureCommandWriteFailed outputPath . displayException)
            <$> try @IOException (writeFile outputPath svgDocument)
        )

renderPictureCommandFailure :: PictureCommandFailure -> String
renderPictureCommandFailure = \case
  PictureCommandUsage ->
    "usage: moonlight-triangulation-delaunay-pictures INPUT.csv OUTPUT_DIRECTORY"
  PictureCommandReadFailed path details ->
    "could not read benchmark CSV " <> path <> ": " <> details
  PictureCommandCreateDirectoryFailed path details ->
    "could not create picture directory " <> path <> ": " <> details
  PictureCommandReceiptObstructed obstruction ->
    renderPictureObstruction obstruction
  PictureCommandWriteFailed path details ->
    "could not write comparison picture " <> path <> ": " <> details