1
1
{-# LANGUAGE CPP, FlexibleContexts, ScopedTypeVariables, OverloadedStrings #-}
2
+ {-# LANGUAGE LambdaCase #-}
3
+ {-# LANGUAGE ViewPatterns #-}
4
+ {-# LANGUAGE TypeApplications #-}
5
+ {-# LANGUAGE MultiWayIf #-}
2
6
{-
3
7
Copyright (C) 2009 John MacFarlane <[email protected] >
4
8
@@ -33,12 +37,23 @@ import Network.Gitit.Util (parsePageType, readFileUTF8)
33
37
import System.Log.Logger (logM , Priority (.. ))
34
38
import System.IO (hPutStrLn , stderr )
35
39
import System.Exit (ExitCode (.. ), exitWith )
40
+ import Data.Either (partitionEithers )
41
+ import Data.Function ((&) )
42
+ import Data.Functor ((<&>) )
36
43
import qualified Data.Map as M
44
+ import qualified Data.Aeson as Json
45
+ import qualified Data.Aeson.Key as Key
46
+ import qualified Data.Aeson.KeyMap as KeyMap
37
47
import Data.List (intercalate , foldl' )
38
48
import Data.Char (toLower , toUpper , isAlphaNum )
49
+ import qualified Data.Scientific as Scientific
39
50
import qualified Data.Text as T
40
51
import Data.Text (Text )
41
52
import Paths_gitit (getDataFileName )
53
+ import qualified Data.Text.Lazy.Builder as T.Builder
54
+ import qualified Data.Text.Lazy as T.Lazy
55
+ import qualified Data.Text.Lazy.Builder.Int as T.Builder.Int
56
+ import qualified Data.Text.Lazy.Builder.Scientific as Scientific
42
57
import System.FilePath ((</>) )
43
58
import Text.Pandoc hiding (ERROR , WARNING , MathJax , MathML , WebTeX , getDataFileName )
44
59
import qualified Control.Exception as E
@@ -50,6 +65,7 @@ import Control.Monad
50
65
import Control.Monad.Trans
51
66
import Text.Parsec
52
67
import Text.Read (readMaybe )
68
+ import Control.Monad.Except (ExceptT (ExceptT ))
53
69
54
70
-- | Get configuration from config file.
55
71
getConfigFromFile :: FilePath -> IO Config
@@ -60,7 +76,7 @@ getConfigFromFiles :: [FilePath] -> IO Config
60
76
getConfigFromFiles fnames = do
61
77
-- we start with default values from the data file
62
78
cp <- getDataFileName " data/default.conf"
63
- cfgmap <- foldM alterConfigMap mempty (cp : fnames)
79
+ cfgmap <- foldM alterConfigMapByNewFile mempty (cp : fnames)
64
80
res <- runExceptT $ extractConfig cfgmap
65
81
case res of
66
82
Right conf -> pure conf
@@ -70,18 +86,67 @@ getConfigFromFiles fnames = do
70
86
71
87
type ConfigMap = M. Map (Text , Text ) Text
72
88
73
- alterConfigMap :: ConfigMap -> FilePath -> IO ConfigMap
74
- alterConfigMap cfmap fname = do
89
+ -- | Parse the config file and return a map from section/field to value.
90
+ -- The config file is first checked to see if it’s a valid json value.
91
+ --
92
+ -- If yes, use the json parser, otherwise use the old style parser for backwards compatibility.
93
+ --
94
+ -- The JSON format mirrors the old style format.
95
+ -- Any key that is in the outer object is put into the [DEFAULT] section.
96
+ -- Any key that is surrounded by `[` and `]` is a section name, and the keys inside are the fields in the section.
97
+ --
98
+ -- Example:
99
+ --
100
+ -- @
101
+ -- {
102
+ -- "repository-type": "git",
103
+ -- "[GitHub]": {
104
+ -- "oauthclientid": "clientid",
105
+ -- "oauthclientsecret": "client
106
+ -- }
107
+ -- }
108
+ -- @
109
+
110
+ alterConfigMapByNewFile :: ConfigMap -> FilePath -> IO ConfigMap
111
+ alterConfigMapByNewFile cfmap fname = do
112
+ eJsonVal <- readJsonValueFromFile fname
113
+ secs <- case eJsonVal of
114
+ Right val ->
115
+ case pSectionJson val of
116
+ Left err -> do
117
+ hPutStrLn stderr (" Error parsing gitit json config " <> fname <> " :\n " <> err)
118
+ exitWith (ExitFailure 1 )
119
+ Right secs -> pure secs
120
+ Left _jsonErr -> do
121
+ eSecs <- readOldStyleConfig fname
122
+ case eSecs of
123
+ Left errOld -> do
124
+ hPutStrLn stderr (" Cannot parse " <> fname <> " as valid json value; tried parsing it as old-style gitit config instead but failed:\n " <> errOld)
125
+ exitWith (ExitFailure 1 )
126
+ Right secs -> pure secs
127
+ pure $ alterConfigMap cfmap secs
128
+
129
+ alterConfigMap :: ConfigMap -> [Section ] -> ConfigMap
130
+ alterConfigMap = foldl' go
131
+ where
132
+ go cfmap' (Section name fields) = foldl' (go' name) cfmap' fields
133
+ go' name cfmap' (k,v) = M. insert (name, k) v cfmap'
134
+
135
+ readOldStyleConfig :: FilePath -> IO (Either String [Section ])
136
+ readOldStyleConfig fname = do
75
137
contents <- readFileUTF8 fname
76
138
let contents' = " [DEFAULT]\n " <> contents
77
139
case parseConfig fname contents' of
78
140
Left msg -> do
79
- hPutStrLn stderr (" Error parsing config " <> fname <> " :\n " <> msg)
80
- exitWith (ExitFailure 1 )
81
- Right secs -> pure $ foldl' go cfmap secs
82
- where
83
- go cfmap' (Section name fields) = foldl' (go' name) cfmap' fields
84
- go' name cfmap' (k,v) = M. insert (name, k) v cfmap'
141
+ pure $ Left msg
142
+ Right secs -> pure $ Right secs
143
+
144
+ readJsonValueFromFile :: FilePath -> IO (Either String Json. Value )
145
+ readJsonValueFromFile fname = do
146
+ mval <- Json. eitherDecodeFileStrict fname
147
+ pure $ case mval of
148
+ Left err -> Left $ " Could not parse file as syntactically valid json value: " <> err
149
+ Right val -> Right val
85
150
86
151
-- | Returns the default gitit configuration.
87
152
getDefaultConfig :: IO Config
@@ -92,6 +157,62 @@ data Section = Section Text [(Text, Text)]
92
157
93
158
parseConfig :: FilePath -> Text -> Either String [Section ]
94
159
parseConfig fname txt = either (Left . show ) Right $ parse (many pSection) fname txt
160
+ data SectionJson =
161
+ DefaultSection Text Text
162
+ | ThisSection Text [(Text , Text )]
163
+
164
+ pSectionJson :: Json. Value -> Either String [Section ]
165
+ pSectionJson (Json. Object obj) = obj & KeyMap. toList
166
+ <&> (\ case
167
+ (asSectionKey -> Just k, v) -> ThisSection k <$> pSectionFields k v
168
+ (k, asJsonScalarText -> Just t) -> Right $ DefaultSection (k & Key. toText) t
169
+ (k, v) -> Left [" The value of field " <> (k & Key. toString) <> " has to be a string, but was: " <> show v]
170
+ )
171
+ & partitionEithers
172
+ & \ case
173
+ ([] , secs) -> secs & foldr go M. empty & M. toList <&> (\ (name, fields) -> Section name fields) & Right
174
+ where
175
+ go (ThisSection name fields) acc = M. insert name fields acc
176
+ go (DefaultSection k v) acc = M. insertWith (++) " DEFAULT" [(k, v)] acc
177
+ (errs, _) -> Left $ intercalate " \n " $ concat errs
178
+ pSectionJson _ = Left " The toplevel json value has to be a json object."
179
+
180
+ -- section key starts with [ and ends with ]
181
+ asSectionKey :: KeyMap. Key -> Maybe Text
182
+ asSectionKey k = case Key. toString k of
183
+ (x : (hasEnd ' ]' -> Just k')) | x == ' [' -> Just $ k' & T. pack & T. toUpper
184
+ _ -> Nothing
185
+ hasEnd :: Eq a => a -> [a ] -> Maybe [a ]
186
+ hasEnd _ [] = Nothing
187
+ hasEnd e xs
188
+ | last xs == e = Just (init xs)
189
+ | otherwise = Nothing
190
+
191
+ asJsonScalarText :: Json. Value -> Maybe Text
192
+ asJsonScalarText (Json. String t) = Just t
193
+ asJsonScalarText (Json. Number n) = Just $ T.Lazy. toStrict $ T.Builder. toLazyText formatNumber
194
+ where
195
+ -- The scientific builder always adds a decimal point, which we don’t want for e.g. port numbers :)
196
+ formatNumber = if
197
+ | Scientific. isInteger n
198
+ , Just i <- Scientific. toBoundedInteger @ Int n -> T.Builder.Int. decimal i
199
+ | otherwise -> n & Scientific. scientificBuilder
200
+ asJsonScalarText (Json. Bool True ) = Just " true"
201
+ asJsonScalarText (Json. Bool False ) = Just " false"
202
+ asJsonScalarText _ = Nothing
203
+
204
+ pSectionFields :: Text -> Json. Value -> Either [String ] [(Text , Text )]
205
+ pSectionFields sec (Json. Object obj) = obj
206
+ & KeyMap. toList
207
+ <&> (\ case
208
+ (k, asJsonScalarText -> Just t) -> Right (k & Key. toText, t)
209
+ (k, v) -> Left $ " In Section " <> (sec & T. unpack) <> " : The value of field " <> (k & Key. toString) <> " has to be a string, but was: " <> show v
210
+ )
211
+ & partitionEithers
212
+ & \ case
213
+ ([] , fields) -> Right fields
214
+ (errs, _) -> Left errs
215
+ pSectionFields sec _ = Left [ " The section " <> (sec & T. unpack) <> " has to be a json object." ]
95
216
96
217
pSection :: Parsec Text () Section
97
218
pSection = do
0 commit comments