1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
|
{-# LANGUAGE CPP #-}
{-# LANGUAGE TupleSections #-}
module SensibleDir where
import System.Directory
#ifdef VERSION_directory_xdg
import System.DirectoryXdg
#endif
import System.FilePath
import System.IO
import System.Info
import System.Environment
import Control.Arrow
import Control.Applicative
import Data.List
-- | Returns True if "build" occurs as any component of the path of the running executable.
runningInBuildDir :: IO Bool
runningInBuildDir = any (=="build") . take 2 . reverse . splitDirectories . takeDirectory <$> getExecutablePath
-- | Returns True if ".cabal" occurs as any component of the path of the running executable.
runningInCabalDir :: IO Bool
runningInCabalDir = any (==".cabal") . take 2 . reverse . splitDirectories . takeDirectory <$> getExecutablePath
-- | Returns True if running executable is in /bin, /sbin, or /usr/bin, or /usr/sbin.
runningInSysBin = do
exedir <- splitDirectories . takeDirectory <$> getExecutablePath
return $
case take 3 (drop 1 exedir) of
["bin"] -> True
["sbin"] -> True
["usr","bin"] -> True
["usr","sbin"] -> True
_ -> False
-- | Returns True if executable is within /usr/local/bin or /usr/local/sbin.
runningInLocalBin = do
exedir <- splitDirectories . takeDirectory <$> getExecutablePath
return $
case take 3 (drop 1 exedir) of
["usr","local","bin"] -> True
["usr","local","sbin"] -> True
_ -> False
-- scenarios
data InstallScenario = PrefixInstall
-- ^ installed in /opt or some other prefix, or running inside source folder
| SystemInstall
-- ^ installed system wide
| LocalInstall
-- ^ installed in /usr/local/
| UserInstall
-- ^ installed for a given user
| NotInstalled
-- ^ not installed, built by cabal or make, running from dist folder
deriving (Show,Enum,Eq)
-- | careful, assumes unix-style FHS
-- windows aware contributions welcome
-- Detects the likely install scenario based on the path to the exectuable.
detectLikelyInstall :: IO InstallScenario
detectLikelyInstall = do
bBuildDir <- fmap (,NotInstalled) runningInBuildDir
bCabalDir <- fmap (,UserInstall) runningInCabalDir
bLocalDir <- fmap (,LocalInstall) runningInLocalBin
bSysDir <- fmap (,SystemInstall) runningInSysBin
case dropWhile (not . fst) [bBuildDir,bCabalDir,bLocalDir,bSysDir] of
(True,scenario):_ -> return scenario
_ -> return PrefixInstall
isUnix os = not $ "mingw" `isPrefixOf` os
-- | Try to get the cache directory appropriate to the install scenario.
-- On windows it just calls getAppUserDataDirectory and appends "cache".
-- (windows behavior subject to change, contributions welcome)
sensibleCacheDir :: FilePath -> IO FilePath
sensibleCacheDir suffix = do
let ifunix x = if isUnix os then x
else PrefixInstall
install <- ifunix <$> detectLikelyInstall
case install of
PrefixInstall -> do
if isUnix os
then do
dirs <- splitDirectories . takeDirectory <$> getExecutablePath
homedir <- getHomeDirectory
let rdir = reverse dirs
case take 1 rdir of
["bin"] -> do
let dir = foldl1 combine $ reverse (drop 1 rdir)
if homedir == dir then getXdgDirectory XdgCache suffix
else return (dir </> "cache" </> suffix)
_ -> do
let dir = foldl1 combine dirs
if homedir == dir then getXdgDirectory XdgCache suffix
else return (dir </> "cache" </> suffix)
else getAppUserDataDirectory ("cache" </> suffix)
SystemInstall -> return ("/var/cache" </> suffix)
LocalInstall -> return ("/var/local/cache" </> suffix)
UserInstall -> getXdgDirectory XdgCache suffix
NotInstalled -> do
dir <- splitDirectories . takeDirectory <$> getExecutablePath
let rdir = reverse dir
return . foldl1 combine $ reverse ("cache": drop 2 rdir)
-- | like 'sensibleCacheDir', but creates it if needed
sensibleCacheDirCreateIfMissing :: FilePath -> IO FilePath
sensibleCacheDirCreateIfMissing suffix = do
dir <- sensibleCacheDir suffix
createDirectoryIfMissing True dir
return dir
-- | Try to get something approximately equivalent to /var/lib
-- but appropriate to install scenario..
sensibleVarLib :: FilePath -> IO FilePath
sensibleVarLib suffix = do
let ifunix x = if isUnix os then x
else PrefixInstall
install <- ifunix <$> detectLikelyInstall
case install of
PrefixInstall -> do
if isUnix os
then do
dirs <- splitDirectories . takeDirectory <$> getExecutablePath
homedir <- getHomeDirectory
let rdir = reverse dirs
case take 1 rdir of
["bin"] -> do
let dir = foldl1 combine $ reverse (drop 1 rdir)
if homedir == dir then getXdgDirectory XdgData suffix
else return (dir </> "share" </> suffix)
_ -> do
let dir = foldl1 combine dirs
if homedir == dir then getXdgDirectory XdgData suffix
else return (dir </> "share" </> suffix)
else getAppUserDataDirectory ("share" </> suffix)
SystemInstall -> return ("/var/lib" </> suffix)
LocalInstall -> return ("/var/lib" </> suffix) -- not using /var/local/lib because it should be easy to find
UserInstall -> getXdgDirectory XdgCache suffix
NotInstalled -> do
dir <- splitDirectories . takeDirectory <$> getExecutablePath
let rdir = reverse dir
return . foldl1 combine $ reverse ("share": drop 2 rdir)
-- | Try to get something approximately equivalent to /etc
-- but appropriate to install scenario..
sensibleConfig :: FilePath -> IO FilePath
sensibleConfig suffix = do
let ifunix x = if isUnix os then x
else PrefixInstall
install <- ifunix <$> detectLikelyInstall
case install of
PrefixInstall -> do
if isUnix os
then do
dirs <- splitDirectories . takeDirectory <$> getExecutablePath
homedir <- getHomeDirectory
let rdir = reverse dirs
case take 1 rdir of
["bin"] -> do
let dir = foldl1 combine $ reverse (drop 1 rdir)
if homedir == dir then getXdgDirectory XdgData suffix
else return (dir </> "config" </> suffix)
_ -> do
let dir = foldl1 combine dirs
if homedir == dir then getXdgDirectory XdgData suffix
else return (dir </> "config" </> suffix)
else getAppUserDataDirectory ("config" </> suffix)
SystemInstall -> return ("/etc" </> suffix)
LocalInstall -> return ("/etc" </> suffix)
UserInstall -> getXdgDirectory XdgConfig suffix
NotInstalled -> do
dir <- splitDirectories . takeDirectory <$> getExecutablePath
let rdir = reverse dir
return . foldl1 combine $ reverse ("config": drop 2 rdir)
|