Improve error handling

- catch errors while loading themes
- use user's actual home directory to look for config files
- restructure main function
This commit is contained in:
Joscha 2018-03-22 09:51:52 +00:00
parent ea83f1aabf
commit bbe6cd830f
3 changed files with 90 additions and 34 deletions

View file

@ -13,6 +13,7 @@ import qualified Brick.Themes as B
import qualified Data.ConfigFile as C import qualified Data.ConfigFile as C
import qualified Database.SQLite.Simple as DB import qualified Database.SQLite.Simple as DB
import qualified Options.Applicative as O import qualified Options.Applicative as O
import qualified System.Posix.User as P
import qualified TaskMachine.Config as TM import qualified TaskMachine.Config as TM
import qualified TaskMachine.Database as TM import qualified TaskMachine.Database as TM
@ -31,9 +32,42 @@ data Options = Options
, oExportDefaultTheme :: [FilePath] , oExportDefaultTheme :: [FilePath]
} deriving (Show) } deriving (Show)
argParser :: O.Parser Options {-
argParser = pure Options - Exit codes
<*> ((++ TM.defaultConfigFilePaths) <$> many configFile) -}
noConfigFile :: ExitCode
noConfigFile = ExitFailure 10
rethrowAs :: (Exception e, Exception f) => (e -> Maybe f) -> IO a -> IO a
rethrowAs f action = do
res <- tryJust f action
case res of
Right v -> return v
Left e -> throwIO e
rethrowAsIf :: (Exception e, Exception f) => (e -> Bool) -> f -> IO a -> IO a
rethrowAsIf check newException action = do
res <- tryJust (guard . check) action
case res of
Right v -> return v
Left _ -> throwIO newException
{-
- Useful functions
-}
-- Log an action (prefixes "-> ")
act :: String -> IO ()
act = putStrLn . ("-> " ++)
{-
- Command line options
-}
argParser :: FilePath -> O.Parser Options
argParser homedir = pure Options
<*> ((++ TM.defaultConfigFilePaths homedir) <$> many configFile)
<*> optional taskDB <*> optional taskDB
<*> many themePaths <*> many themePaths
<*> many exportDefaultConfig <*> many exportDefaultConfig
@ -45,7 +79,7 @@ argParser = pure Options
, O.help $ "Where to look for a config file.\ , O.help $ "Where to look for a config file.\
\ This option can be set multiple times.\ \ This option can be set multiple times.\
\ If not specified, will look in these locations:\n" \ If not specified, will look in these locations:\n"
++ intercalate "," (map show TM.defaultConfigFilePaths) ++ (intercalate "," $ map show $ TM.defaultConfigFilePaths homedir)
, O.metavar "CONFIGFILE" , O.metavar "CONFIGFILE"
] ]
taskDB = O.strOption $ mconcat taskDB = O.strOption $ mconcat
@ -77,20 +111,20 @@ argParser = pure Options
, O.metavar "THEMEFILE" , O.metavar "THEMEFILE"
] ]
argParserInfo :: O.ParserInfo Options argParserInfo :: FilePath -> O.ParserInfo Options
argParserInfo = O.info (O.helper <*> argParser) $ mconcat argParserInfo homedir = O.info (O.helper <*> argParser homedir) $ mconcat
[ O.fullDesc [ O.fullDesc
] ]
-- Log an action (prefixes "-> ") {-
action :: String -> IO () - Loading config and stuff
action = putStrLn . ("-> " ++) -}
-- Kinda ugly... -- Kinda ugly...
loadConfigs :: [FilePath] -> IO (Maybe TM.Config) loadConfigs :: [FilePath] -> IO (Maybe TM.Config)
loadConfigs [] = return Nothing loadConfigs [] = return Nothing
loadConfigs (path:paths) = do loadConfigs (path:paths) = do
action $ "Loading config from " ++ show path ++ "." act $ "Loading config from " ++ show path ++ "."
mConf <- tryLoadConfig path mConf <- tryLoadConfig path
case mConf of case mConf of
Just conf -> return (Just conf) Just conf -> return (Just conf)
@ -102,13 +136,14 @@ loadConfigs (path:paths) = do
tryLoadConfig p = handleOpenFileExceptions tryLoadConfig p = handleOpenFileExceptions
$ handleCPException $ handleCPException
$ Just <$> TM.loadConfig p $ Just <$> TM.loadConfig p
--tryLoadConfig p = Just <$> TM.loadConfig p
handleCPException :: IO (Maybe a) -> IO (Maybe a) handleCPException :: IO (Maybe a) -> IO (Maybe a)
handleCPException f = do handleCPException f = do
res <- try f res <- try f
case res of case res of
Right m -> return m Right m -> return m
Left (TM.CPException (C.ParseError msg)) -> Nothing <$ putStrLn msg Left (TM.CPException (C.ParseError msg)) -> Nothing <$ putStrLn msg
Left (TM.CPException _ ) -> return Nothing Left (TM.CPException _ ) -> putStrLn "Bleep" >> return Nothing
handleOpenFileExceptions :: IO (Maybe a) -> IO (Maybe a) handleOpenFileExceptions :: IO (Maybe a) -> IO (Maybe a)
handleOpenFileExceptions f = do handleOpenFileExceptions f = do
res <- tryJust (guard . isRelevantError) f res <- tryJust (guard . isRelevantError) f
@ -124,53 +159,72 @@ mergeWithOptions :: TM.Config -> Options -> TM.Config
mergeWithOptions = mergeThemePaths <=< mergeTaskDB mergeWithOptions = mergeThemePaths <=< mergeTaskDB
where where
mergeThemePaths conf opt = case oThemePaths opt of mergeThemePaths conf opt = case oThemePaths opt of
[] -> conf [] -> conf
themes -> conf { TM.cThemes = themes } themes -> conf { TM.cThemes = themes }
mergeTaskDB conf opt = case oTaskDB opt of mergeTaskDB conf opt = case oTaskDB opt of
Nothing -> conf Nothing -> conf
Just taskdb -> conf { TM.cTaskDB = taskdb } Just taskdb -> conf { TM.cTaskDB = taskdb }
-- Could probably implement using EitherT, but too lazy :) -- Could probably implement using EitherT, but too lazy :)
loadThemes :: B.Theme -> [FilePath] -> IO B.Theme loadThemes :: B.Theme -> [FilePath] -> IO B.Theme
loadThemes theme [] = return theme loadThemes theme [] = return theme
loadThemes theme (path:paths) = do loadThemes theme (path:paths) = do
action $ "Loading theme " ++ show path ++ "." act $ "Loading theme from " ++ show path ++ "."
eModifiedTheme <- B.loadCustomizations path theme mNewTheme <- tryLoadCustomizations path theme
case eModifiedTheme of case mNewTheme of
Left errMsg -> die errMsg Nothing -> do
Right modifiedTheme -> loadThemes modifiedTheme paths putStrLn $ "Could not load theme from " ++ show path ++ "."
loadThemes theme paths
Just (Right newTheme) -> loadThemes newTheme paths
Just (Left errMsg) -> do
putStrLn $ "Could not load theme from " ++ show path ++ ": " ++ errMsg
loadThemes theme paths
where
tryLoadCustomizations :: FilePath -> B.Theme -> IO (Maybe (Either String B.Theme))
tryLoadCustomizations p t = handleOpenFileExceptions
$ Just <$> B.loadCustomizations p t
handleOpenFileExceptions :: IO (Maybe a) -> IO (Maybe a)
handleOpenFileExceptions f = do
res <- tryJust (guard . isRelevantError) f
case res of
Right m -> return m
Left _ -> return Nothing
isRelevantError :: IOError -> Bool
isRelevantError e = isAlreadyInUseError e
|| isDoesNotExistError e
|| isPermissionError e
main :: IO () main :: IO ()
main = do main = do
options <- O.execParser argParserInfo homedir <- P.homeDirectory <$> (P.getUserEntryForID =<< P.getRealUserID)
options <- O.execParser $ argParserInfo homedir
-- Export default config -- Export default config
forM_ (oExportDefaultConfig options) $ \path -> do forM_ (oExportDefaultConfig options) $ \path -> do
action $ "Exporting default config to " ++ show path ++ "." act $ "Exporting default config to " ++ show path ++ "."
TM.saveConfig path TM.defaultConfig TM.saveConfig path TM.defaultConfig
-- Export default theme -- Export default theme
forM_ (oExportDefaultTheme options) $ \path -> do forM_ (oExportDefaultTheme options) $ \path -> do
action $ "Exporting default theme to " ++ show path ++ "." act $ "Exporting default theme to " ++ show path ++ "."
B.saveTheme path TM.defaultTheme B.saveTheme path TM.defaultTheme
-- Load config -- Load config
mConfig <- loadConfigs $ oConfigFile options mConfig <- loadConfigs $ oConfigFile options
case mConfig of config <- case mConfig of
Nothing -> do Nothing -> do
putStrLn "Could not load any config." putStrLn ""
putStrLn "Could not find any config file."
putStrLn "Use the -C CONFIGFILE flag to generate a default config file." putStrLn "Use the -C CONFIGFILE flag to generate a default config file."
die "No config file" exitWith noConfigFile
Just unmergedConfig -> do Just unmergedConfig -> return $ mergeWithOptions unmergedConfig options
-- Add command line options into config
let config = mergeWithOptions unmergedConfig options
-- According to config, load themes and initialize db -- According to config, load themes and initialize db
theme <- loadThemes TM.defaultTheme $ TM.cThemes config theme <- loadThemes TM.defaultTheme $ TM.cThemes config
DB.withConnection (TM.cTaskDB config) TM.initializeNewDB DB.withConnection (TM.cTaskDB config) TM.initializeNewDB
-- Start the UI -- Start the UI
error "Implement UI" theme config error "Implement UI" theme config
--import qualified Database.SQLite.Simple as DB --import qualified Database.SQLite.Simple as DB
--import qualified TaskMachine.Database as TMD --import qualified TaskMachine.Database as TMD

View file

@ -28,6 +28,7 @@ dependencies:
- vty - vty
- optparse-applicative - optparse-applicative
- ConfigFile - ConfigFile
- unix
#- containers #- containers
#- unordered-containers #- unordered-containers
#- text #- text

View file

@ -25,8 +25,9 @@ defaultConfig = Config
, cTaskDB = "~/.taskmachine/tasks.db" , cTaskDB = "~/.taskmachine/tasks.db"
} }
defaultConfigFilePaths :: [FilePath] defaultConfigFilePaths :: FilePath -> [FilePath]
defaultConfigFilePaths = ["tasks.conf", "~/.taskmachine/tasks.conf"] defaultConfigFilePaths homedir =
[homedir ++ "/.taskmachine/tasks.conf", "tasks.conf"]
newtype CPException = CPException C.CPErrorData newtype CPException = CPException C.CPErrorData
deriving (Show) deriving (Show)