-
Notifications
You must be signed in to change notification settings - Fork 0
/
Conf.hs
184 lines (164 loc) · 5.8 KB
/
Conf.hs
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
177
178
179
180
181
182
183
184
module Conf
( Conf (..)
, Options (..)
, Task (..)
, buildConf
, createDotDir
, parseArgv
) where
import Control.Monad (liftM, unless)
import qualified Data.ConfigFile as CF
import Data.Either.Utils (forceEither)
import Data.Maybe (isNothing)
import Network.Browser
import System.Console.GetOpt
import System.Directory (createDirectoryIfMissing, doesDirectoryExist,
getAppUserDataDirectory)
import System.FilePath (pathSeparator)
data Conf = Conf {
opts :: Options
, proxy :: Proxy
, urls :: [String]
, tasks :: [Task]
}
data Task = Task {
keyword :: String
, threshold :: Int
, action :: String
}
data Options = Options {
optVerbose :: Bool
, optConfigPath :: FilePath
, optFeedsPath :: FilePath
, optPrint :: Bool
, optCount :: Bool
, optKeywords :: [String]
, optTasks :: Bool
}
instance Show Options where
show o =
"-v " ++ show (optVerbose o) ++ "\n" ++
"-C " ++ optConfigPath o ++ "\n" ++
"-f " ++ optFeedsPath o ++ "\n" ++
"-p " ++ show (optPrint o) ++ "\n" ++
"-c " ++ show (optCount o) ++ "\n" ++
"keywords " ++ unwords (optKeywords o) ++ "\n" ++
"-t " ++ show (optTasks o)
-- |Default cli options, used if none given.
defaultOptions :: FilePath -> Options
defaultOptions p = Options {
optVerbose = False
, optConfigPath = p ++ [pathSeparator] ++ "daemon.conf"
, optFeedsPath = p ++ [pathSeparator] ++ "daemon.feeds"
, optPrint = False
, optCount = False
, optKeywords = []
, optTasks = False
}
options :: [OptDescr (Options -> Options)]
options = [
Option "v" ["verbose"]
(NoArg (\optns -> optns { optVerbose = True }))
"verbose output"
, Option "C" ["config"]
(ReqArg (\p optns -> optns {optConfigPath = p }) "PATH")
"filepath to config"
, Option "f" ["feedspath"]
(ReqArg (\p optns -> optns { optFeedsPath = p }) "PATH")
"filepath to feeds"
, Option "p" ["print"]
(NoArg (\optns -> optns { optPrint = True }))
"print feeds"
, Option "c" ["count"]
(ReqArg (\d optns -> optns { optCount = True,
optKeywords = optKeywords optns ++ [d] }) "KEYWORD ...")
"keyword to count"
, Option "t" ["tasks"]
(NoArg (\optns -> optns { optTasks = True }))
"run tasks"
]
-- |Parse the cli argument vector.
parseArgv :: [String] -> IO (Options, [String])
parseArgv argv = do
let opt = getOpt RequireOrder options argv
dDir <- dotDirPath
case opt of
(o,n,[] ) -> return (foldl (flip id) (defaultOptions dDir) o, n)
(_,_,errs) -> ioError
(userError (concat errs ++ usageInfo header options))
where header = "Usage: daemon [-v] [-f feedspath] " ++
"[-p | -c word -c ... | -t]"
-- |Build Conf out of cli options and config file definitions.
buildConf :: Options -> IO Conf
buildConf o = do
items <- getConfItems $ optConfigPath o
feedurls <- getUrls $ optFeedsPath o
let prx = getProxyConf items
let ts = getTasks items
return $ Conf o prx feedurls ts
-- |Read config items out of config file.
getConfItems :: FilePath -> IO [(CF.OptionSpec, String)]
getConfItems path = do
val <- CF.readfile CF.emptyCP path
let cp = forceEither val
return $ forceEither $ CF.items cp "DEFAULT"
getProxyConf :: [(CF.OptionSpec, String)] -> Proxy
getProxyConf items = maybeToProxy $ lookup "proxy" items
maybeToProxy :: Maybe String -> Proxy
maybeToProxy Nothing = NoProxy
maybeToProxy (Just s) = Proxy s Nothing
getTasks :: [(CF.OptionSpec, String)] -> [Task]
getTasks confitems =
getTasks' confitems 1
where
getTasks' :: [(CF.OptionSpec, String)] -> Int -> [Task]
getTasks' items i =
if isNothing (lookup ("keyword" ++ show i) items)
then []
else getTask items i : getTasks' items (i+1)
-- |Construct a Task out of the config file definitions.
getTask :: [(CF.OptionSpec, String)] -> Int -> Task
getTask items n =
let
keywordN = "keyword" ++ show n
thresholdN = keywordN ++ "_threshold"
actionN = keywordN ++ "_action"
kwd = checkConfItem (lookup keywordN items) keywordN
thd = read $ checkConfItem (lookup thresholdN items) thresholdN
act = checkConfItem (lookup actionN items) actionN
in Task kwd thd act
-- |Read the urls of the feeds file.
getUrls :: FilePath -> IO [String]
getUrls =
liftM (filter (\x -> x /= "" && head x /= '#')) .
fmap lines . readFile
checkConfItem :: Maybe String -> String -> String
checkConfItem (Just s) _ = s
checkConfItem Nothing itemName = error $ "failed to parse " ++ itemName
-- |Create dotdir with a default config file.
createDotDir :: IO ()
createDotDir = do
dDir <- dotDirPath
exists <- doesDirectoryExist dDir
createDirectoryIfMissing (not exists) dDir
let cPath = dDir ++ [pathSeparator] ++ "daemon.conf"
let fPath = dDir ++ [pathSeparator] ++ "daemon.feeds"
unless exists $ do
writeFile cPath defaultConf
writeFile fPath defaultFeeds
-- |Default path to application data directory aka the dotdir.
dotDirPath :: IO String
dotDirPath = getAppUserDataDirectory "daemon"
-- |Default configuration with proxy and keyword example.
defaultConf :: String
defaultConf =
"#proxy = 127.0.0.1:8118\n" ++
"#keyword1 = nasa\n" ++
"#keyword1_threshold = 1\n" ++
"#keyword1_action = xeyes\n" ++
"#keyword2 = mars\n" ++
"#keyword2_threshold = 1\n" ++
"#keyword2_action = xeyes"
-- |Default feeds for the default dotdir creation.
defaultFeeds :: String
defaultFeeds = "http://feeds.bbci.co.uk/news/rss.xml"