haskell_todo_app/app/Main.hs

280 lines
8.9 KiB
Haskell
Raw Normal View History

2024-05-25 20:07:02 +02:00
{-# LANGUAGE OverloadedStrings #-}
2024-05-25 22:36:59 +02:00
module Main(main, parseTask, serializeTask, getPriority, comparePriority, TaskMap) where
2024-05-25 20:07:02 +02:00
import System.IO
import System.Directory
2024-05-26 17:47:23 +02:00
import Data.Text (Text, pack, unpack, isInfixOf, lines, unlines, strip, splitOn)
2024-05-25 20:07:02 +02:00
import qualified Data.Text.IO as TIO
2024-05-25 22:36:59 +02:00
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BSC
2024-05-25 20:07:02 +02:00
import Control.Monad (when)
import Data.List (sortBy)
import Data.Ord (comparing)
import Data.Char (isAlpha)
2024-05-25 21:09:43 +02:00
import qualified Data.Map as Map
import Data.Function (on)
2024-05-26 17:47:23 +02:00
import Control.Exception (catch, IOException)
2024-05-25 20:07:02 +02:00
todoFile :: FilePath
todoFile = "todo.txt"
2024-05-25 21:09:43 +02:00
type TaskMap = Map.Map Int Text
2024-05-25 20:07:02 +02:00
main :: IO ()
main = do
putStrLn "Welcome to the Haskell Todo App"
putStrLn "1. Display tasks"
putStrLn "2. Add task"
2024-05-26 22:39:58 +02:00
putStrLn "3. Add task (Manual)"
2024-05-26 23:20:11 +02:00
putStrLn "4. Edit task"
putStrLn "5. Filter tasks by tag"
putStrLn "6. Sort tasks by priority"
putStrLn "7. Remove task"
putStrLn "8. Exit"
2024-05-25 20:07:02 +02:00
putStr "Choose an option: "
hFlush stdout
option <- getLine
case option of
"1" -> displayTasks
"2" -> addTask
2024-05-26 22:39:58 +02:00
"3" -> addTaskWithManual
2024-05-26 23:20:11 +02:00
"4" -> editTask
"5" -> filterTasksByTag
"6" -> sortTasksByPriority
"7" -> removeTask
"8" -> putStrLn "Goodbye!"
2024-05-25 20:07:02 +02:00
_ -> putStrLn "Invalid option" >> main
2024-05-26 23:20:11 +02:00
2024-05-25 20:07:02 +02:00
displayTasks :: IO ()
displayTasks = do
exists <- doesFileExist todoFile
if exists
then do
2024-05-26 17:47:23 +02:00
contents <- catch (TIO.readFile todoFile) handleReadError
let taskMap = parseTask contents
2024-05-25 20:07:02 +02:00
putStrLn "Tasks:"
2024-05-25 21:09:43 +02:00
mapM_ (putStrLn . showTask) (Map.toList taskMap)
2024-05-25 20:07:02 +02:00
else putStrLn "No tasks found."
2024-05-26 22:39:58 +02:00
putStrLn " -------------- "
putStrLn "Press Enter"
option <- getLine
case option of
_ -> main
where
handleReadError :: IOException -> IO Text
handleReadError e = do
putStrLn $ "Error reading file: " ++ show e
return ""
2024-05-25 20:07:02 +02:00
addTask :: IO ()
addTask = do
putStr "Enter the task: "
hFlush stdout
task <- getLine
2024-05-25 21:09:43 +02:00
exists <- doesFileExist todoFile
if exists
then do
2024-05-26 17:47:23 +02:00
contents <- catch (TIO.readFile todoFile) handleReadError
let taskMap = parseTask contents
2024-05-25 21:09:43 +02:00
newId = if Map.null taskMap then 1 else fst (Map.findMax taskMap) + 1
newTaskMap = Map.insert newId (pack task) taskMap
2024-05-26 17:47:23 +02:00
catch (TIO.writeFile todoFile (serializeTask newTaskMap)) handleWriteError
else catch (TIO.writeFile todoFile (pack task <> "\n")) handleWriteError
2024-05-25 20:07:02 +02:00
putStrLn "Task added."
2024-05-26 22:39:58 +02:00
putStrLn " -------------- "
putStrLn "Press Enter"
option <- getLine
case option of
_ -> main
2024-05-26 17:47:23 +02:00
where
handleReadError :: IOException -> IO Text
handleReadError e = do
putStrLn $ "Error reading file: " ++ show e
return ""
handleWriteError :: IOException -> IO ()
handleWriteError e = putStrLn $ "Error writing file: " ++ show e
2024-05-25 20:07:02 +02:00
2024-05-26 22:39:58 +02:00
addTaskWithManual :: IO ()
addTaskWithManual = do
putStrLn "First, optionally you can add priority to the task you want to add. To do so, put letter inside '()'"
putStrLn "Tasks without priority will be displayed first."
putStrLn "Second, write your task."
putStrLn "Third, optionally you can add tags to your task. To do so, put '+'and write your tag."
putStrLn "Tasks can have more than one tag."
putStrLn "Example: (C) go shopping +shop +sunday"
putStrLn " -------------- "
putStrLn "Press Enter to add your task or x to go back to menu"
option <- getLine
case option of
"x" -> main
_ -> addTask
2024-05-26 23:20:11 +02:00
editTask :: IO ()
editTask = do
exists <- doesFileExist todoFile
if exists
then do
contents <- catch (TIO.readFile todoFile) handleReadError
let taskMap = parseTask contents
putStrLn "Existing tasks: "
mapM_ (putStrLn . showTaskId) (Map.toList taskMap)
putStr "Enter the ID of the task you wish to edit: "
hFlush stdout
taskIdStr <- getLine
let taskId = read taskIdStr :: Int
putStrLn ""
if Map.member taskId taskMap
then do
putStrLn "Enter the new task description: "
newTask <- getLine
let newTaskMap = Map.insert taskId (pack newTask) taskMap
catch (TIO.writeFile todoFile (serializeTask newTaskMap)) handleWriteError
putStrLn "Task edited."
else do
putStrLn "Task of this ID doesn't exist"
putStrLn "Do you wish to try again? Press Enter if yes or x to go back to menu"
option <- getLine
case option of
"x" -> main
_ -> editTask
else putStrLn "No tasks found."
putStrLn " -------------- "
putStrLn "Press Enter"
option <- getLine
case option of
_ -> main
where
handleReadError :: IOException -> IO Text
handleReadError e = do
putStrLn $ "Error reading file: " ++ show e
return ""
handleWriteError :: IOException -> IO ()
handleWriteError e = putStrLn $ "Error writing file: " ++ show e
2024-05-25 20:07:02 +02:00
filterTasksByTag :: IO ()
filterTasksByTag = do
2024-05-25 21:09:43 +02:00
putStr "Enter the tag to filter by (e.g., +GarageSale): "
2024-05-25 20:07:02 +02:00
hFlush stdout
tag <- getLine
exists <- doesFileExist todoFile
if exists
then do
2024-05-26 17:47:23 +02:00
contents <- catch (TIO.readFile todoFile) handleReadError
2024-05-25 22:26:37 +02:00
let taskMap = parseTask contents
2024-05-25 21:09:43 +02:00
filteredTasks = Map.filter (isInfixOf (pack tag)) taskMap
2024-05-25 20:07:02 +02:00
putStrLn "Filtered tasks:"
2024-05-25 21:09:43 +02:00
mapM_ (putStrLn . showTask) (Map.toList filteredTasks)
2024-05-25 20:07:02 +02:00
else putStrLn "No tasks found."
2024-05-26 22:39:58 +02:00
putStrLn " -------------- "
putStrLn "Press Enter"
option <- getLine
case option of
_ -> main
2024-05-26 17:47:23 +02:00
where
handleReadError :: IOException -> IO Text
handleReadError e = do
putStrLn $ "Error reading file: " ++ show e
return ""
2024-05-25 20:07:02 +02:00
2024-05-26 22:39:58 +02:00
handleWriteError :: IOException -> IO ()
handleWriteError e = putStrLn $ "Error writing file: " ++ show e
2024-05-25 20:07:02 +02:00
sortTasksByPriority :: IO ()
sortTasksByPriority = do
exists <- doesFileExist todoFile
if exists
then do
2024-05-26 17:47:23 +02:00
contents <- catch (TIO.readFile todoFile) handleReadError
2024-05-25 22:26:37 +02:00
let taskMap = parseTask contents
2024-05-25 21:09:43 +02:00
sortedTasks = sortBy (comparePriority `on` snd) (Map.toList taskMap)
2024-05-25 20:07:02 +02:00
putStrLn "Sorted tasks by priority:"
2024-05-25 21:09:43 +02:00
mapM_ (putStrLn . showTask) sortedTasks
2024-05-25 20:07:02 +02:00
else putStrLn "No tasks found."
2024-05-26 22:39:58 +02:00
putStrLn " -------------- "
putStrLn "Press Enter"
option <- getLine
case option of
_ -> main
where
handleReadError :: IOException -> IO Text
handleReadError e = do
putStrLn $ "Error reading file: " ++ show e
return ""
removeTask :: IO ()
removeTask = do
exists <- doesFileExist todoFile
if exists
then do
contents <- catch (TIO.readFile todoFile) handleReadError
let taskMap = parseTask contents
putStrLn "Existing tasks: "
mapM_ (putStrLn . showTaskId) (Map.toList taskMap)
putStr "Enter the ID of the task you wish to remove: "
hFlush stdout
taskIdStr <- getLine
let taskId = read taskIdStr :: Int
putStrLn ""
if Map.member taskId taskMap
then do
let newTaskMap = Map.delete taskId taskMap
catch (TIO.writeFile todoFile (serializeTask newTaskMap)) handleWriteError
putStrLn "Task removed."
else do
putStrLn "Task of this ID doesn't exist"
putStrLn "Do you wish to try again? Press Enter if yes or x to go back to menu"
option <- getLine
case option of
"x" -> main
_ -> removeTask
else putStrLn "No tasks found."
putStrLn " -------------- "
putStrLn "Press Enter"
option <- getLine
case option of
_ -> main
2024-05-26 17:47:23 +02:00
where
handleReadError :: IOException -> IO Text
handleReadError e = do
putStrLn $ "Error reading file: " ++ show e
return ""
2024-05-25 20:07:02 +02:00
2024-05-26 22:39:58 +02:00
handleWriteError :: IOException -> IO ()
handleWriteError e = putStrLn $ "Error writing file: " ++ show e
2024-05-25 20:07:02 +02:00
comparePriority :: Text -> Text -> Ordering
comparePriority t1 t2 = comparing getPriority t1 t2
getPriority :: Text -> Maybe Char
2024-05-25 21:09:43 +02:00
getPriority t = case unpack (strip t) of
2024-05-25 20:07:02 +02:00
('(':p:')':_) | isAlpha p -> Just p
2024-05-25 21:09:43 +02:00
_ -> Nothing
2024-05-25 22:26:37 +02:00
parseTask :: Text -> TaskMap
parseTask = Map.fromList . zip [1..] . Data.Text.lines
2024-05-25 21:09:43 +02:00
2024-05-25 22:26:37 +02:00
serializeTask :: TaskMap -> Text
serializeTask = Data.Text.unlines . map snd . Map.toList
2024-05-25 21:09:43 +02:00
showTask :: (Int, Text) -> String
2024-05-26 22:39:58 +02:00
showTask (_, task) = unpack task
showTaskId :: (Int, Text) -> String
showTaskId (taskId, task) = show taskId ++ ": " ++ unpack task