{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-orphans #-}
import Data.Aeson (decode)
import Data.ByteString.Lazy (ByteString)
import Data.List (intercalate)
import Data.Maybe (
fromJust,
fromMaybe,
)
import Data.Monoid (Sum (..))
import Data.String (IsString (..))
import Path hiding ((</>))
import Polysemy
import Polysemy.Error
import Polysemy.Operators
import Polysemy.State
import System.Directory (getCurrentDirectory, removeFile)
import System.Environment (setEnv)
import System.FilePath ((</>))
import qualified System.XDG as XDGIO
import System.XDG.Env
import System.XDG.Error
import System.XDG.FileSystem
import System.XDG.Internal
import Test.Hspec
instance IsString (Path Abs Dir) where
fromString = fromMaybe undefined . parseAbsDir
instance IsString (Path Abs File) where
fromString = fromMaybe undefined . parseAbsFile
bareEnv :: EnvList
bareEnv = [("HOME", "/alice")]
fullEnv :: EnvList
fullEnv =
[ ("HOME", "/alice")
, ("XDG_CONFIG_HOME", "/config")
, ("XDG_DATA_HOME", "/data")
, ("XDG_STATE_HOME", "/state")
, ("XDG_CACHE_HOME", "/cache")
, ("XDG_RUNTIME_DIR", "/runtime")
]
badEnv :: EnvList
badEnv =
[ ("HOME", "/alice")
, ("XDG_DATA_HOME", "data/")
, ("XDG_DATA_DIRS", "/foo:./bar:/baz:foo/../bar")
]
userFiles :: FileList Integer
userFiles =
[ ("/data/foo/bar", 1)
, ("/config/foo/bar", 10)
, ("/state/foo/bar", 100)
, ("/cache/foo/bar", 200)
, ("/runtime/foo/bar", 300)
]
sysFiles :: FileList Integer
sysFiles =
[ ("/usr/local/share/foo/bar", 2)
, ("/usr/share/foo/bar", 3)
, ("/etc/xdg/foo/bar", 20)
]
allFiles :: FileList Integer
allFiles = userFiles ++ sysFiles
testXDG ::
EnvList ->
FileList Integer ->
'[Env, ReadFile Integer, WriteFile Integer, State (FileMap Integer), Error XDGError] @> a ->
Either XDGError a
testXDG env files = run . runError . runReadWriteFileList files . runEnvList env
decodeInteger :: ByteString -> Sum Integer
decodeInteger = Sum . fromMaybe 0 . decode
main :: IO ()
main = hspec $ do
describe "XDG" $ do
describe "pure interpreter" $ do
it "finds the data home" $ do
testXDG bareEnv [] getDataHome `shouldBe` Right "/alice/.local/share"
testXDG fullEnv [] getDataHome `shouldBe` Right "/data"
testXDG badEnv [] getDataDirs
`shouldBe` Right ["/alice/.local/share", "/foo", "/baz"]
it "finds the config home" $ do
testXDG bareEnv [] getConfigHome `shouldBe` Right "/alice/.config"
testXDG fullEnv [] getConfigHome `shouldBe` Right "/config"
it "finds the state home" $ do
testXDG bareEnv [] getStateHome `shouldBe` Right "/alice/.local/state"
testXDG fullEnv [] getStateHome `shouldBe` Right "/state"
it "opens a data file" $ do
testXDG fullEnv allFiles (readDataFile "foo/bar") `shouldBe` Right 1
testXDG fullEnv sysFiles (readDataFile "foo/bar") `shouldBe` Right 2
testXDG fullEnv allFiles (readDataFile "foo/baz")
`shouldBe` Left NoReadableFile
testXDG fullEnv [] (maybeRead $ readDataFile "foo/bar")
`shouldBe` Right Nothing
it "merges data files" $ do
testXDG fullEnv allFiles (readData show "foo/bar")
`shouldBe` Right "123"
testXDG fullEnv [] (readData show "foo/bar") `shouldBe` Right ""
it "opens a config file" $ do
testXDG fullEnv userFiles (readConfigFile "foo/bar") `shouldBe` Right 10
testXDG fullEnv sysFiles (readConfigFile "foo/bar") `shouldBe` Right 20
testXDG fullEnv allFiles (readConfigFile "foo/baz")
`shouldBe` Left NoReadableFile
it "merges config files" $ do
testXDG fullEnv allFiles (readConfig show "foo/bar")
`shouldBe` Right "1020"
testXDG fullEnv [] (readConfig show "foo/bar") `shouldBe` Right ""
it "opens a state file" $ do
testXDG fullEnv userFiles (readStateFile "foo/bar") `shouldBe` Right 100
it "opens a cache file" $ do
testXDG fullEnv userFiles (readCacheFile "foo/bar") `shouldBe` Right 200
it "opens a runtime file" $ do
testXDG fullEnv userFiles (readRuntimeFile "foo/bar")
`shouldBe` Right 300
it "writes a config file" $ do
testXDG fullEnv userFiles (writeConfigFile "foo/bar" 1000 >> readConfigFile "foo/bar") `shouldBe` Right 1000
it "writes a data file" $ do
testXDG fullEnv userFiles (writeDataFile "foo/bar" 2000 >> readDataFile "foo/bar") `shouldBe` Right 2000
it "writes a cache file" $ do
testXDG fullEnv userFiles (writeCacheFile "foo/bar" 3000 >> readCacheFile "foo/bar") `shouldBe` Right 3000
it "writes a state file" $ do
testXDG fullEnv userFiles (writeStateFile "foo/bar" 4000 >> readStateFile "foo/bar") `shouldBe` Right 4000
it "writes a runtime file" $ do
testXDG fullEnv userFiles (writeRuntimeFile "foo/bar" 5000 >> readRuntimeFile "foo/bar") `shouldBe` Right 5000
describe "IO interpreter" $ do
it "opens a data file" $ do
cwd <- getCurrentDirectory
setEnv "XDG_DATA_HOME" $ cwd </> "test/dir1"
setEnv "XDG_DATA_DIRS" $
intercalate ":" $
map
(cwd </>)
["test/dir2", "test/dir3"]
(decodeInteger . fromJust)
<$> XDGIO.readDataFile "foo/bar.json"
`shouldReturn` 1
it "merges data files" $ do
XDGIO.readData decodeInteger "foo/bar.json" `shouldReturn` 3
XDGIO.readData decodeInteger "foo/baz.json" `shouldReturn` 30
it "writes a data file" $ do
XDGIO.writeDataFile "foo/quux.json" "4"
(decodeInteger . fromJust) <$> XDGIO.readDataFile "foo/quux.json" `shouldReturn` 4
(</> "test/dir1/foo/quux.json") <$> getCurrentDirectory >>= removeFile