packages feed

gotyno-hs-1.0.0: src/Library.hs

module Library where

import qualified CodeGeneration.FSharp as FSharp
import qualified CodeGeneration.Python as Python
import qualified CodeGeneration.TypeScript as TypeScript
import qualified Data.Text.IO as TextIO
import qualified Parsing
import RIO
import qualified RIO.Directory as Directory
import qualified RIO.FilePath as FilePath
import qualified RIO.List as List
import qualified RIO.Time as Time
import qualified System.FSNotify as FSNotify
import Types
import Prelude (print, putStrLn)

data OutputDestination
  = SameAsInput
  | OutputPath !FilePath
  | StandardOut
  deriving (Eq, Show)

data Languages = Languages
  { typescript :: !(Maybe OutputDestination),
    fsharp :: !(Maybe OutputDestination),
    python :: !(Maybe OutputDestination)
  }
  deriving (Eq, Show)

data Options = Options
  { languages :: !Languages,
    watchMode :: !Bool,
    verbose :: !Bool,
    inputs :: ![FilePath]
  }
  deriving (Eq, Show)

runMain :: Options -> IO ()
runMain
  Options
    { languages = languages@Languages {typescript, fsharp, python},
      inputs,
      watchMode,
      verbose
    } = do
    when watchMode $ do
      watchInputs inputs languages verbose
    start <- Time.getCurrentTime
    maybeModules <- Parsing.parseModules inputs
    postParsing <- Time.getCurrentTime
    case maybeModules of
      Right modules -> do
        startTS <- Time.getCurrentTime
        forM_ typescript $ outputLanguage modules TypeScript.outputModule "ts"
        endTS <- Time.getCurrentTime
        startFS <- Time.getCurrentTime
        forM_ fsharp $ outputLanguage modules FSharp.outputModule "fs"
        endFS <- Time.getCurrentTime
        startPython <- Time.getCurrentTime
        forM_ python $ outputLanguage modules Python.outputModule "py"
        endPython <- Time.getCurrentTime
        end <- Time.getCurrentTime
        let diff = Time.diffUTCTime end start
            diffParsing = Time.diffUTCTime postParsing start
            diffTS = Time.diffUTCTime endTS startTS
            diffFS = Time.diffUTCTime endFS startFS
            diffPython = Time.diffUTCTime endPython startPython
        when verbose $ do
          putStrLn $ "Parsing took: " <> show diffParsing
          putStrLn $ "Outputting TypeScript took: " <> show diffTS
          putStrLn $ "Outputting FSharp took: " <> show diffFS
          putStrLn $ "Outputting Python took: " <> show diffPython
          putStrLn $ "Entire compilation took: " <> show diff
      Left errors -> forM_ errors putStrLn

outputLanguage :: [Module] -> (Module -> Text) -> FilePath -> OutputDestination -> IO ()
outputLanguage modules outputFunction extension outputDestination = do
  let outputs = modules & fmap outputFunction & zip modules
  case outputDestination of
    StandardOut -> do
      outputs & reverse & forM_ $ \(_module, output) -> TextIO.putStrLn output
    SameAsInput -> do
      forM_ outputs $ \(Module {sourceFile}, output) -> do
        let pathForOutput = FilePath.replaceExtensions sourceFile extension
        writeFileUtf8 pathForOutput output
    OutputPath outputDirectory -> do
      forM_ outputs $ \(Module {sourceFile}, output) -> do
        let pathForOutput =
              FilePath.replaceDirectory sourceFile outputDirectory
                & flip FilePath.replaceExtensions extension
            basePath = FilePath.takeDirectory pathForOutput
        Directory.createDirectoryIfMissing True basePath
        writeFileUtf8 pathForOutput output

watchInputs :: [FilePath] -> Languages -> Bool -> IO ()
watchInputs relativeInputs Languages {typescript, fsharp, python} verbose = do
  inputs <- traverse Directory.makeAbsolute relativeInputs
  let compileEverything = do
        maybeModules <- Parsing.parseModules relativeInputs
        case maybeModules of
          Right modules -> do
            forM_ typescript $ outputLanguage modules TypeScript.outputModule "ts"
            forM_ fsharp $ outputLanguage modules FSharp.outputModule "fs"
            forM_ python $ outputLanguage modules Python.outputModule "py"
          Left errors ->
            forM_ errors putStrLn
      debounceInterval = 0.01 :: Time.NominalDiffTime
      fsNotifyConfig = FSNotify.defaultConfig {FSNotify.confDebounce = FSNotify.Debounce debounceInterval}
  compileEverything
  FSNotify.withManagerConf fsNotifyConfig $ \watchManager -> do
    let inputDirectories = inputs & fmap FilePath.takeDirectory & List.nub
        eventPredicate (FSNotify.Modified modifiedInput _modificationTime _someBool) =
          modifiedInput `elem` inputs
        eventPredicate _otherEvents = False
    forM_ inputDirectories $ \inputDirectory -> do
      putStrLn $ "Watching directory: '" <> inputDirectory <> "'"
      FSNotify.watchDir
        watchManager
        inputDirectory
        eventPredicate
        ( \event -> do
            when verbose (print event)
            compileEverything
        )
    forever $ threadDelay 1000000