-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpush.hs
More file actions
145 lines (123 loc) · 6.63 KB
/
push.hs
File metadata and controls
145 lines (123 loc) · 6.63 KB
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
import Data.Char
import Data.List
import System.Directory
import System.FilePath.Posix
-- Locate the configuration directory starting from the current directory.
locateConf :: IO (Maybe FilePath)
locateConf = do d <- getCurrentDirectory
locateConf' d
-- Locate the configuration directory starting from the given directory (d).
locateConf' :: FilePath -> IO (Maybe FilePath)
locateConf' d = do d' <- canonicalizePath d
found <- doesDirectoryExist $ d' </> ".push"
if found -- Hit!
then return (Just (d' </> ".push"))
else if d' == "/" -- Miss! Cannot dig deeper, we hit the filesystem root.
then return Nothing
else locateConf' $ d' </> ".." -- Miss! Dig deeper.
-- Locate the root directory bound to the given configuration directory (conf).
locateRoot :: FilePath -> IO FilePath
locateRoot conf = canonicalizePath $ conf </> ".."
-- Return the value of the required configuration parameter with the given name
-- (name), looking at the given configuration directory (conf).
param' :: FilePath -> String -> IO String
param' conf name = do v <- option name
case v of Just v' -> return v'
Nothing -> error $ "Missing parameter: " ++ name
where option = option' conf
-- Return the value of the optional configuration parameter with the given name
-- (name), looking at the given configuration directory (conf).
option' :: FilePath -> String -> IO (Maybe String)
option' conf name = do found <- doesFileExist f
if found
then do v <- readFile f
return $ Just (dropWhileEnd isControl v)
else return Nothing
where f = optionPath' conf name
optionPath' :: FilePath -> String -> FilePath
optionPath' conf name = conf </> name
haveOption' :: FilePath -> String -> IO Bool
haveOption' conf name = do doesFileExist f
where f = optionPath' conf name
-- Explode the configuration into a list of configuration.
explodeConf :: FilePath -> IO [FilePath]
explodeConf conf = do wantToDelegate <- haveOption "delegate"
if wantToDelegate
then explodeDelegateFile $ optionPath "delegate"
else return [conf]
where haveOption = haveOption' conf
optionPath = optionPath' conf
explodeDelegateFile :: FilePath -> IO [FilePath]
explodeDelegateFile df = do content <- readFile df
return $ lines content
rsyncBaseOptions :: FilePath -> IO [String]
rsyncBaseOptions conf = do options <- option "rsync-options"
case options of Nothing -> return defaultOptions
Just o -> return $ words o
where option = option' conf
defaultOptions = ["-vv", "-a", "--no-group", "--no-perms", "-T /tmp"]
rsyncDeleteOption :: FilePath -> IO [String]
rsyncDeleteOption conf = do noDelete <- haveOption "no-delete"
if noDelete
then return []
else return ["--delete"]
where haveOption = haveOption' conf
rsyncAdditionalOptions :: FilePath -> IO [String]
rsyncAdditionalOptions conf = do options <- option "additional-rsync-options"
case options of Nothing -> return []
Just o -> return $ words o
where option = option' conf
rsyncIncludes :: FilePath -> IO [String]
rsyncIncludes conf = do haveIncludes <- haveOption "includes"
if haveIncludes
then return ["--include-from=" ++ (optionPath "includes")]
else return []
where haveOption = haveOption' conf
optionPath = optionPath' conf
rsyncExcludes :: FilePath -> IO [String]
rsyncExcludes conf = do haveIncludes <- haveOption "excludes"
if haveIncludes
then return ["--exclude-from=" ++ (optionPath "excludes"), "--exclude=*"]
else return ["--exclude=*"]
where haveOption = haveOption' conf
optionPath = optionPath' conf
accumulateOptions :: String -> [String] -> String
accumulateOptions acc [] = acc
accumulateOptions "" (o:os) = accumulateOptions o os
accumulateOptions acc (o:os) = accumulateOptions (concat [acc, " ", o]) os
rsyncOptionsBuilders :: FilePath -> [IO [String]]
rsyncOptionsBuilders conf = do builders <- [ rsyncBaseOptions,
rsyncDeleteOption,
rsyncAdditionalOptions,
rsyncIncludes,
rsyncExcludes ]
return $ builders conf
rsyncOptions :: FilePath -> IO String
rsyncOptions conf = do options <- sequence $ rsyncOptionsBuilders conf
return $ foldl accumulateOptions "" options
remotePath :: FilePath -> IO String
remotePath conf = do haveRemotePath <- haveOption "remote-path"
if haveRemotePath
then param "remote-path"
else locateRoot conf
where haveOption = haveOption' conf
param = param' conf
buildRsyncCommandLine :: FilePath -> IO String
buildRsyncCommandLine conf = do options <- rsyncOptions conf
host <- param "target-host"
path <- remotePath conf
root <- locateRoot conf
return $ intercalate " " ["cd", root, "&&", "rsync", options, "./", concat [host, ":", path ++ "/"]]
where param = param' conf
-- Trigger a push from the given root directory (root).
push :: FilePath -> IO ()
push conf = do putStrLn $ "Conf: " ++ conf
cmdline <- buildRsyncCommandLine conf
putStrLn cmdline
sequencePush :: IO [FilePath] -> IO ()
sequencePush cs = do confs <- cs
sequence_ $ map push confs
main :: IO ()
main = do bc <- locateConf
case bc of Nothing -> error "Missing configuration."
Just baseConf -> sequencePush $ explodeConf baseConf