packages feed

clanki 1.0.2 → 1.0.3

raw patch · 9 files changed

+339/−2 lines, 9 files

Files

clanki.cabal view
@@ -2,7 +2,7 @@ --  see http://haskell.org/cabal/users-guide/  name:                clanki-version:             1.0.2+version:             1.0.3 synopsis:            Command-line spaced-repetition software description:         Command-line spaced-repetition learning software. CL (command line) + Anki (popular spaced-repetition software) = Clanki. Usage is fairly simple, just follow the instructions after running the program. Add a deck, add cards to the deck, then quiz whenever possible. The program will determine what cards need to be reviewed, using the Super Memo 2 algorithm. license:             MIT@@ -17,7 +17,7 @@  executable clanki   main-is:             Main.hs-  --other-modules:       src+  other-modules:       Add, Quiz, Remove, Display, Card, Decks, Input, Tracker   -- other-extensions:       build-depends:       base >=4.7 && <4.8, time >= 1.4.2, directory >= 1.2.1.0, safe   hs-source-dirs:      src
+ src/Add.hs view
@@ -0,0 +1,76 @@+module Add where+import Card+import Decks+import Display+import Text.Printf(printf)+import qualified Input+data AddAction = NewDeck | ToDeck deriving (Show, Eq)++instance Display AddAction where+    display addAct = show addAct++addLoop :: [Deck] -> IO [Deck]+addLoop decks = do+    addAction <- getAddAction decks+    runAddAction addAction decks++runAddAction :: Maybe AddAction -> [Deck] -> IO [Deck]+runAddAction addAction decks+    | addAction == Just NewDeck = newDeck decks+    | addAction == Just ToDeck  = toDeck decks+    | addAction == Nothing      = return decks++newDeck :: [Deck] -> IO [Deck]+newDeck decks = do+    printf $ "Please input the name of the new deck" ++ "\n"+    deckName <- getLine+    case deckName of +        "" -> do+                return decks+        _  -> if any (\deck -> dName deck == deckName) decks+                then do+                  printf $ "Invalid input, already a deck with that name" ++ "\n"+                  newDeck decks+                else +                  return $ addDeckWithName deckName decks++addDeckWithName :: String -> [Deck] -> [Deck]+addDeckWithName name decks = decks ++ [Deck {dCards = [], dName = name}]++toDeck :: [Deck] -> IO [Deck]+toDeck decks = do+    chosenDeck <- Input.getUserChoice $ decks+    case chosenDeck of+        Just deck -> do+                        deckWithNewItems <- toDeckLoop deck+                        let newDecks = replaceDeckNamed (dName deck) deckWithNewItems decks+                        return $ newDecks+        Nothing -> return decks+++toDeckLoop :: Deck -> IO Deck+toDeckLoop deck = do+    printf $ "Please input the question, enter to stop adding" ++ "\n"+    question <- getLine+    case question of +        "" -> do+                printf $ "You wish to stop adding" ++ "\n"+                return deck+        _  -> do+                printf $ "Please input the answer" ++ "\n"+                answer <- getLine+                case answer of+                    "" -> do+                        printf $ "You wish to stop adding" ++ "\n"+                        return deck+                    _  -> do+                        let card = newCard question answer+                        toDeckLoop (addCardToDeck card deck)++getAddAction :: [Deck] -> IO (Maybe AddAction)+getAddAction decks+    | null decks = return $ Just NewDeck+    | otherwise = Input.getUserChoice allAddActions++allAddActions :: [AddAction]+allAddActions = [NewDeck, ToDeck]
+ src/Card.hs view
@@ -0,0 +1,19 @@+module Card where +import Display+import Text.Printf(printf)+data CardTracker = CardTracker {ctEF :: Float, ctN :: Int, ctTimeQuizzed :: Integer, ctLastResponseQuality :: Maybe Integer} deriving (Show, Read, Eq)++data Card = Card {cardQuestion :: String, cardAnswer :: String, cardTracker :: CardTracker} deriving (Show, Read, Eq)++newCard :: String -> String -> Card+newCard question answer = Card {cardQuestion = question, cardAnswer = answer, cardTracker = newCardTracker} ++newCardTracker :: CardTracker+newCardTracker = CardTracker {ctEF = 2.5, ctN = 0, ctTimeQuizzed = 0, ctLastResponseQuality = Nothing}++instance Display Card where+    display card = cardQuestion card ++ "\n" ++ cardAnswer card++printCard :: Card -> IO ()+printCard card = printf $ cardQuestion card ++ "\n" ++ cardAnswer card+
+ src/Decks.hs view
@@ -0,0 +1,21 @@+module Decks where+import Card+import Display+import Data.List(delete)+data Deck = Deck {dCards :: [Card], dName :: String} deriving (Show, Eq, Read)++instance Display Deck where+    display = dName -- ++ "\n" ++ concat [display card ++ "\n" | card <- dCards deck] ++ "--------"++addCardToDeck :: Card -> Deck -> Deck+addCardToDeck card deck = deck {dCards = dCards deck ++ [card]}++removeCardFromDeck :: Card -> Deck -> Deck+removeCardFromDeck card deck = deck {dCards = delete card (dCards deck)}++allCards :: [Deck] -> [Card]+allCards decks = concat [dCards deck | deck <- decks]++replaceDeckNamed :: String -> Deck -> [Deck] -> [Deck]+replaceDeckNamed deckName deck decks = filter (\d -> dName d /= deckName) decks ++ [deck]+
+ src/Display.hs view
@@ -0,0 +1,5 @@+module Display where++class (Show a) => Display a where+    display :: a -> String+
+ src/Input.hs view
@@ -0,0 +1,36 @@+module Input(getUserChoice) where+import Text.Printf(printf)+import Data.Maybe(isJust)+import System.IO(hSetBuffering, BufferMode(NoBuffering, LineBuffering), stdin, stdout)+import Display+import Control.Monad(when)++keys :: [String]+keys = map (:[]) ['a' .. 'z']++repKeysAndValues :: (Display a) => [(String, a)] -> String+repKeysAndValues choices = unlines [key ++ ")" ++ " " ++ display a | (key, a) <- choices]++getUserChoice :: (Display a) => [a] -> IO (Maybe a)+getUserChoice = getChoiceWithKeys . zip keys++getChoiceWithKeys :: (Display a) => [(String, a)] -> IO (Maybe a)+getChoiceWithKeys choices = do+    hSetBuffering stdin NoBuffering+    hSetBuffering stdout NoBuffering+    printf $ repKeysAndValues choices+    input <- userInputLetter+    let action = lookup input choices+    hSetBuffering stdin LineBuffering+    hSetBuffering stdout LineBuffering+    when (isJust action) $ printf "\n"++    return action++userInputLetter :: IO String+userInputLetter = do+    input <- fmap (:[]) getChar+    putStrLn ""+    return input++
+ src/Quiz.hs view
@@ -0,0 +1,93 @@+module Quiz where+import Card+import Decks+import Tracker+import Data.List(lookup)+import Data.Char(isSpace)+import Display+import Text.Printf(printf)+import Safe(readMay)+import Data.Time.Clock.POSIX+import qualified Input+data QuizAction = AllCards | FromDeck deriving (Show, Eq)++allQuizActions :: [QuizAction]+allQuizActions = [AllCards, FromDeck]++instance Display QuizAction where+    display quizAct +        | quizAct == AllCards = "Quiz from all cards"+        | quizAct == FromDeck = "Quiz from a deck"++quizLoop :: [Deck] -> IO [Deck]+quizLoop decks = do+    quizAction <- getQuizAction decks+    runQuizAction quizAction decks++anyCardsToQuiz :: [Deck] -> IO Bool+anyCardsToQuiz decks = do+    let cards = allCards decks+    sequence [shouldQuizCard card | card <- cards] >>= return . (True `elem`)++runQuizAction :: Maybe QuizAction -> [Deck] -> IO [Deck]+runQuizAction quizAct decks+    | quizAct == Just AllCards = quizDecks decks decks+    | quizAct == Just FromDeck = do+        input <- Input.getUserChoice decks+        case input of+            Just deck -> return decks+            Nothing   -> return decks+    | otherwise = return decks++quizDecks :: [Deck] -> [Deck] -> IO [Deck]+quizDecks decksToQuiz allDecks = sequence $ map (\deck -> if deck `elem` decksToQuiz then quizDeck deck else return deck) allDecks++quizDeck :: Deck -> IO Deck+quizDeck deck = do+    newCards <- sequence [maybeQuiz card | card <- dCards deck]+    return deck {dCards = newCards}+++maybeQuiz :: Card -> IO Card+maybeQuiz card = shouldQuizCard card >>= (\shouldQuiz -> if shouldQuiz then quizCard card else return card)+    ++{-quizCards :: [Card] -> [Deck] -> IO [Deck]-}+{-quizCards cards decks = return decks-}+    {-newTrackers <- sequence [quizCard card tracker | card <- cards, let tracker = trackerOfCard card (cardTrackers decks)]-}+    {--- TODO: replace trackers, but not all -}+    {-return $ decks {cardTrackers = newTrackers}-}++quizCard :: Card -> IO Card+quizCard card = do+    printf $ "Question : " ++ cardQuestion card ++ "\n"+    printf "Input your answer, then press enter to continue\n"+    _ <- getLine+    --printf $ "Answer : " ++ cardAnswer card ++ "\n"+    printf "Did you get it right?\n"+    confidence <- getAnswerConfidence+    let newEF = adjustEF (ctEF $ cardTracker card) confidence+    let oldTracker = cardTracker card+    let n = if confidence < 3 then 1 else ctN oldTracker + 1+    currentTime <- fmap round getPOSIXTime+    printf "\n"+    return $ card {cardTracker = oldTracker {ctEF = newEF, ctN = n, ctLastResponseQuality = Just confidence, ctTimeQuizzed = currentTime}}++getAnswerConfidence :: IO Integer+getAnswerConfidence = do+    input <- getLine +    let readInput = readMay input :: Maybe Integer+    case readInput of+        Just confidence -> if confidence `elem` [1 .. 5] then return confidence else getAnswerConfidence+        Nothing         -> getAnswerConfidence+++++{-quizFromDeck :: Deck -> [Deck] -> IO [Deck]-}+{-quizFromDeck deck decks = return decks-}++getQuizAction :: [Deck] -> IO (Maybe QuizAction)+getQuizAction decks = Input.getUserChoice allQuizActions+    +
+ src/Remove.hs view
@@ -0,0 +1,60 @@+module Remove where+import Decks+import Display+import Text.Printf(printf)+import qualified Input+data RemoveAction = RemoveDeck | RemoveFromDeck deriving (Show, Eq)++instance Display RemoveAction where+    display removeAct+        | removeAct == RemoveDeck = "Remove a deck"+        | removeAct == RemoveFromDeck = "Remove cards from a deck"++removeLoop :: [Deck] -> IO [Deck]+removeLoop decks = do+    removeAction <- getRemoveAction decks+    updatedDecks <- runRemoveAction removeAction decks+    return $ updatedDecks++runRemoveAction :: Maybe RemoveAction -> [Deck] -> IO [Deck]+runRemoveAction removeAction decks+    | removeAction == Just RemoveDeck     = removeDeck decks+    | removeAction == Just RemoveFromDeck = removeFromDeck decks+    | removeAction == Nothing             = return decks++removeDeck :: [Deck] -> IO [Deck]+removeDeck decks = do+    printf $ "Choose what deck to remove" ++ "\n"+    input <- Input.getUserChoice decks+    case input of +        Just deck -> return $ filter (/= deck) decks+        Nothing   -> return decks+++removeFromDeck :: [Deck] -> IO [Deck]+removeFromDeck decks = do+    chosenDeck <- Input.getUserChoice $ filter (\deck -> length (dCards deck) > 0) decks+    case chosenDeck of+        Just deck -> do+                        updatedDeck <- removeFromDeckLoop deck+                        let newDecks = replaceDeckNamed (dName updatedDeck) updatedDeck decks +                        return newDecks +        Nothing -> return decks++removeFromDeckLoop :: Deck -> IO Deck+removeFromDeckLoop deck = do+    input <- Input.getUserChoice $ dCards deck+    case input of +        Just card  -> if length (dCards deck) > 0 +                        then removeFromDeckLoop $ removeCardFromDeck card deck+                        else return deck+        Nothing    -> return deck++getRemoveAction :: [Deck] -> IO (Maybe RemoveAction)+getRemoveAction decks+    | null decks = return Nothing+    | all (\deck -> length (dCards deck) == 0) decks = return $ Just RemoveDeck+    | otherwise  = Input.getUserChoice allRemoveActions++allRemoveActions :: [RemoveAction]+allRemoveActions = [RemoveDeck, RemoveFromDeck]
+ src/Tracker.hs view
@@ -0,0 +1,27 @@+module Tracker where+import Card+import Data.Time.Clock.POSIX+import Data.Maybe(fromJust, fromMaybe, isNothing)++sm2 :: Int -> Float -> Float+sm2 n ef+    | n == 0 = 1+    | n == 1 = 1+    | n == 2 = 6+    | otherwise = sm2 (n-1) ef++shouldQuizCard :: Card -> IO Bool+shouldQuizCard card = do+    currentTime <- fmap round getPOSIXTime+    let timeDifference = currentTime - (ctTimeQuizzed $ cardTracker card)+        daysSinceQuiz = floor $ (fromIntegral $ timeDifference :: Float) / 86400+        sm2Time = (ceiling $ sm2 (ctN tracker) (ctEF tracker)) :: Integer+        tracker = cardTracker card+        lastResponse = ctLastResponseQuality tracker+        noLastResponse = isNothing lastResponse+        justLastResponse = fromJust lastResponse+    return (daysSinceQuiz >= sm2Time ||  noLastResponse || justLastResponse < 4)++adjustEF :: Float -> Integer -> Float+adjustEF ef confidence = ef-0.8+0.28*q-0.02*q*q+    where q = fromIntegral confidence :: Float