packages feed

glualint-1.24.6: app/GLuaFixer/Effects/Settings.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}

module GLuaFixer.Effects.Settings where

import Control.Applicative ((<|>))
import qualified Data.Aeson as Aeson
import qualified Data.ByteString.Char8 as BS8
import Effectful (Dispatch (Dynamic), DispatchOf, Eff, Effect, (:>))
import Effectful.Dispatch.Dynamic (interpret, send, interpose)
import qualified Effectful.Error.Static as Err
import GLuaFixer.Cli (OverriddenSettings, SettingsPath (..), overrideSettings)
import GLuaFixer.Effects.Files (Files)
import qualified GLuaFixer.Effects.Files as Files
import GLuaFixer.LintSettings (LintSettings, defaultLintSettings)
import System.FilePath (takeDirectory, (</>))
import GLuaFixer.Effects.Logging (Logging, putStrLnStdError)

data Settings :: Effect where
  -- | Search for settings in various locations, returns default settings if nothing is found
  GetSettings :: FilePath -> Settings m LintSettings
  -- | Read settings from a glualint.json file
  SettingsFromFile :: FilePath -> Settings m LintSettings
  -- | Search for settings in a directory, going up until it reaches the root or finds the file
  SearchSettingsInDirectory :: FilePath -> Settings m (Maybe FilePath)
  -- | Search settings in the home directory
  SearchSettingsInHome :: Settings m (Maybe FilePath)

type instance DispatchOf Settings = Dynamic

searchSettingsInHome :: Settings :> es => Eff es (Maybe FilePath)
searchSettingsInHome = send SearchSettingsInHome

searchSettingsInDirectory :: Settings :> es => FilePath -> Eff es (Maybe FilePath)
searchSettingsInDirectory filepath = send $ SearchSettingsInDirectory filepath

settingsFromFile :: Settings :> es => FilePath -> Eff es LintSettings
settingsFromFile filepath = send $ SettingsFromFile filepath

getSettings :: Settings :> es => FilePath -> Eff es LintSettings
getSettings filepath = send $ GetSettings filepath

newtype SettingsError = CouldNotParseSettings String

-- | The default settings file
settingsFile :: FilePath
settingsFile = "glualint.json"

-- | The default settings file when placed in the home folder
homeSettingsFile :: FilePath
homeSettingsFile = ".glualint.json"

-- | Run settings based on the Files effect.
runSettings :: (Files :> es, Err.Error SettingsError :> es) => Eff (Settings : es) a -> Eff es a
runSettings = interpret $ \_ -> \case
  GetSettings filepath -> do
    absolutePath <-
      if filepath == "stdin"
        then Files.getCurrentDirectory
        else Files.makeAbsolute filepath

    fromSearching <- runSettings $ searchSettingsInDirectory absolutePath
    fromHome <- runSettings searchSettingsInHome

    case fromSearching <|> fromHome of
      Just foundSettings -> runSettings $ settingsFromFile foundSettings
      Nothing -> pure defaultLintSettings
  SettingsFromFile filepath -> do
    contents <- Files.readFile filepath
    let
      eDecoded = Aeson.eitherDecodeStrict @LintSettings $ BS8.pack contents

    case eDecoded of
      Left err -> Err.throwError $ CouldNotParseSettings err
      Right decoded -> pure decoded
  SearchSettingsInDirectory filepath -> do
    isDirectory <- Files.isDirectory filepath
    let
      dir = Files.Directory $ if isDirectory then filepath else takeDirectory filepath
      settingsFiles = Files.FileNames [settingsFile, homeSettingsFile]
    Files.searchUpwardsForFile dir settingsFiles
  SearchSettingsInHome -> do
    homeDir <- Files.getHomeDirectory
    let
      fileWithDot = homeDir </> homeSettingsFile
      fileWithoutDot = homeDir </> settingsFile

    Files.firstExists [fileWithDot, fileWithoutDot]

{- | Combines getting the settings from
- An explicitly passed path to a file
- Settings overridden in the CLI
- Settings that apply to a file
-}
getSettingsForFile ::
  Settings :> es =>
  Maybe SettingsPath ->
  OverriddenSettings ->
  FilePath ->
  Eff es LintSettings
getSettingsForFile mbSettingsPath overridden filepath = do
  readSettings <- case mbSettingsPath of
    Just (SettingsPath path) -> settingsFromFile path
    Nothing -> getSettings filepath
  pure $ overrideSettings overridden readSettings

-- | Trace the steps of a settings effect
traceSettings :: (Settings :> es, Logging :> es) => Eff es a -> Eff es a
traceSettings = interpose $ \_ -> \case
  GetSettings filepath -> do
    putStrLnStdError $ "GetSettings " <> filepath
    send $ GetSettings filepath
  SettingsFromFile filepath -> do
    putStrLnStdError $ "SettingsFromFile " <> filepath
    send $ SettingsFromFile filepath
  SearchSettingsInDirectory filepath -> do
    putStrLnStdError $ "SearchSettingsInDirectory " <> filepath
    send $ SearchSettingsInDirectory filepath
  SearchSettingsInHome -> do
    putStrLnStdError "SearchSettingsInHome"
    send SearchSettingsInHome

traceSettingsIfEnabled :: (Settings :> es, Logging :> es) => Bool -> Eff es a -> Eff es a
traceSettingsIfEnabled enabled = if enabled then traceSettings else id