packages feed

hascard 0.5.0.2 → 0.5.0.3

raw patch · 7 files changed

+121/−36 lines, 7 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Import: ImportOpts :: String -> String -> ImportType -> Bool -> String -> String -> Maybe String -> ImportOpts
+ Import: [_optDefDelimiter] :: ImportOpts -> Maybe String
+ Import: [_optImportReverse] :: ImportOpts -> Bool
+ Import: [_optImportType] :: ImportOpts -> ImportType
+ Import: [_optInput] :: ImportOpts -> String
+ Import: [_optOutput] :: ImportOpts -> String
+ Import: [_optRowDelimiter] :: ImportOpts -> String
+ Import: [_optTermDefDelimiter] :: ImportOpts -> String
+ Import: data ImportOpts
+ Import: defDelimiter :: String
+ Import: optDefDelimiter :: Lens' ImportOpts (Maybe String)
+ Import: optImportReverse :: Lens' ImportOpts Bool
+ Import: optImportType :: Lens' ImportOpts ImportType
+ Import: optInput :: Lens' ImportOpts String
+ Import: optOutput :: Lens' ImportOpts String
+ Import: optRowDelimiter :: Lens' ImportOpts String
+ Import: optTermDefDelimiter :: Lens' ImportOpts String
+ Import: pDef :: ImportOpts -> Parser String
+ Import: pImportInput :: ImportOpts -> Parser [Card]
+ Import: pRow :: ImportOpts -> Parser Card
+ Import: pSpecial :: ImportOpts -> Parser ()
+ Import: rowDelimiter :: String
+ Import: termDefDelimiter :: String
+ Import: type Parser = Parsec Void String
+ UI: ImportOpts :: String -> String -> ImportType -> Bool -> String -> String -> Maybe String -> ImportOpts
+ UI: [_optDefDelimiter] :: ImportOpts -> Maybe String
+ UI: [_optImportReverse] :: ImportOpts -> Bool
+ UI: [_optImportType] :: ImportOpts -> ImportType
+ UI: [_optInput] :: ImportOpts -> String
+ UI: [_optOutput] :: ImportOpts -> String
+ UI: [_optRowDelimiter] :: ImportOpts -> String
+ UI: [_optTermDefDelimiter] :: ImportOpts -> String
+ UI: data ImportOpts
- Import: parseImportInput :: ImportType -> Bool -> String -> Maybe [Card]
+ Import: parseImportInput :: ImportOpts -> String -> Either String [Card]
- States: popupState :: forall s_aeGD. Lens' (Popup s_aeGD) PopupState
+ States: popupState :: forall s_aeJv. Lens' (Popup s_aeJv) PopupState
- UI: parseImportInput :: ImportType -> Bool -> String -> Maybe [Card]
+ UI: parseImportInput :: ImportOpts -> String -> Either String [Card]

Files

ChangeLog.md view
@@ -1,5 +1,12 @@ # Changelog for hascard-##+## 0.5.0.3+New:+- Improved `hascard import` functionality: delimiters can be specified via CLI options (fixes issue #17).++Fixed bug:+- Removed redundant newline after header++## 0.5.0.2 New: - Setting for changing case sensitivity of open questions. Default behaviour is case sensitive, which was also the functionality before this setting was added. 
README.md view
@@ -1,5 +1,5 @@ # hascard-[![Build Status](https://travis-ci.org/Yvee1/hascard.svg?branch=master)](https://travis-ci.org/Yvee1/hascard) [![GitHub tag](https://img.shields.io/github/tag/Yvee1/hascard.svg)](https://github.com/Yvee1/hascard/releases) [![AUR](https://img.shields.io/aur/version/hascard)](https://aur.archlinux.org/packages/hascard/) [![Hackage](https://img.shields.io/hackage/v/hascard.svg)](https://hackage.haskell.org/package/hascard)+[![Build Status]()](https://github.com/Yvee1/hascard/actions/workflows/build.yml/badge.svg) [![GitHub tag](https://img.shields.io/github/tag/Yvee1/hascard.svg)](https://github.com/Yvee1/hascard/releases) [![AUR](https://img.shields.io/aur/version/hascard)](https://aur.archlinux.org/packages/hascard/) [![Hackage](https://img.shields.io/hackage/v/hascard.svg)](https://hackage.haskell.org/package/hascard)   @@ -64,7 +64,7 @@ As an example, say you have a file `deck.txt` with lots of cards in it and you want to review 5 random ones, you can use `hascard run deck -s -a 5`. Here `-s` shuffles the deck and `-a 5` specifies we only want to look at 5 of them. For more info, see `hascard run --help`.  #### Importing decks-If you have decks in a different format, you might want to convert them into files compatible with hascard. Currently tab-seperated files can be converted to definition or open question cards. For example [Quizlet](https://quizlet.com/) decks can be exported to this format. As an example, the following file+If you have decks in a different format, you might want to convert them into files compatible with hascard. Currently, delimited text files can be converted to definition or open question cards. For example [Quizlet](https://quizlet.com/) decks can be exported to this format. As an example, the following file ``` aussi	ook en outre, de plus	bovendien
app/Main.hs view
@@ -5,14 +5,17 @@ import Control.Exception (displayException, try) import Control.Monad (void, when) import Data.Version (showVersion)+import Data.Void import Lens.Micro.Platform import Paths_hascard (version) import Parser-import Options.Applicative+import Options.Applicative hiding (many) import System.Directory (makeAbsolute) import System.FilePath (takeExtension, takeBaseName, takeDirectory) import System.Process (runCommand) import System.Random.MWC (createSystemRandom)+import Text.Megaparsec (parse, many, errorBundlePretty, ParseErrorBundle)+import Text.Megaparsec.Char.Lexer (charLiteral) import qualified Data.Map.Strict as Map (empty) import qualified System.Directory as D import qualified Stack@@ -34,12 +37,6 @@   , _optBlankMode    :: Bool   } -data ImportOpts = ImportOpts-  { _optInput         :: String -  , _optOutput        :: String-  , _optImportType    :: ImportType-  , _optImportReverse :: Bool }- makeLenses ''Opts makeLenses ''RunOpts makeLenses ''ImportOpts@@ -63,7 +60,10 @@ opts = Opts   <$> optional (hsubparser     (  command "run" (info (Run <$> runOpts) ( progDesc "Run hascard with CLI options"))-    <> command "import" (info (Import <$> importOpts) (progDesc "Convert a TAB delimited file to syntax compatible with hascard. So, terms and definitions should be seperated by tabs, and rows by new lines. When converting to 'open' cards, multiple correct answers can be seperated by semicolons (;), backslashes (/) or commas (,)."))))+    <> command "import" (info (Import <$> importOpts) (progDesc "Convert a delimited text file (e.g. exported from Quizlet) to a file compatible with hascard.\+    \ The delimiters can be specified via CLI options. By default, terms and definitions are assumed to be separated by tabs, and different cards by new lines.\+    \ Either 'Definition' cards are generated (traditional flashcards), or 'Open question' cards (where the answer needs to be typed).\+    \ For 'Open question' cards, a delimiter can be specified which separates multiple correct answers."))))   <*> switch (long "version" <> short 'v' <> help "Show version number")  runOpts :: Parser RunOpts@@ -76,14 +76,18 @@  importOpts :: Parser ImportOpts importOpts = ImportOpts-  <$> argument str (metavar "INPUT" <> help "A TSV file")+  <$> argument str (metavar "INPUT" <> help "A delimited text file")   <*> argument str (metavar "DESINATION" <> help "The filename/path to which the output should be saved")-  <*> option auto (long "type" <> short 't' <> metavar "'open' or 'def'" <> help "The type of card to which the input is transformed, default: open" <> value Open)+  <*> option auto (long "type" <> short 't' <> metavar "'open' or 'def'" <> help "The type of card to which the input is transformed; default: open." <> value Open)   <*> switch (long "reverse" <> short 'r' <> help "Reverse direction of question and answer, i.e. right part becomes the question.")+  <*> strOption (long "row-delimiter" <> metavar "delimiter" <> help "The delimiter used to separate different cards; default: \\n." <> value "\n")+  <*> strOption (long "term-def-delimiter" <> metavar "delimiter" <> help "The delimiter used to separate terms and definitions; default: \\t." <> value "\t")+  <*> optional (strOption (long "def-delimiter" <> metavar "delimiter" <> help "The delimiter used to separate different definitions for the same term; no delimiter is used by default."))  optsWithHelp :: ParserInfo Opts optsWithHelp = info (opts <**> helper) $-              fullDesc <> progDesc "Run the normal application with `hascard`. To run directly on a file, and with CLI options, see `hascard run --help`. For converting TAB seperated files, see `hascard import --help`."+              fullDesc <> progDesc "Run the normal application with `hascard`. To run directly on a file, and with CLI options, see `hascard run --help`.\+              \ For converting delimited text files, see `hascard import --help`."               <> header "Hascard - a TUI for reviewing notes"  nothingIf :: (a -> Bool) -> a -> Maybe a@@ -138,15 +142,20 @@ start (Just (fp, cards)) gs = runBrickFlashcards =<< (gs `goToState`) <$> cardsWithOptionsState gs fp cards  doImport :: ImportOpts -> IO ()-doImport opts = do+doImport opts' = do+  let opts = opts' & optRowDelimiter %~ parseStringLiteral   valOrExc <- try $ readFile (opts ^. optInput) :: IO (Either IOError String)   case valOrExc of     Left exc -> putStrLn (displayException exc)     Right val -> do-      let mCards = parseImportInput (opts ^. optImportType) (opts ^. optImportReverse) val+      let mCards = parseImportInput opts val       case mCards of-        Just cards -> do+        Right cards -> do           writeFile (opts ^. optOutput) . cardsToString $ cards           putStrLn "Successfully converted the file."-        Nothing -> putStrLn "Failed the conversion."+        Left msg -> putStrLn msg          +parseStringLiteral :: String -> String+parseStringLiteral s = case parse (many charLiteral) "" s of+  Left errorBundle -> error (errorBundlePretty (errorBundle :: ParseErrorBundle String Void))+  Right result -> result
hascard.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 37031709ac5e49c9833f7bc7e63a8db75834eda98879221fc1968c2e15fb231d+-- hash: 6236002c595a937b392daea7195014259e4161b14760978c156dbcf4a7dcc106  name:           hascard-version:        0.5.0.2+version:        0.5.0.3 synopsis:       A TUI for reviewing notes using 'flashcards' written with markdown-like syntax. description:    Hascard is a text-based user interface for reviewing notes using flashcards. Cards are written in markdown-like syntax. Please see the README file on GitHub at <https://github.com/Yvee1/hascard#readme> for more information. category:       Application
src/Import.hs view
@@ -1,12 +1,30 @@+{-# LANGUAGE TemplateHaskell #-} module Import where+import Control.Monad (void) import Data.Char (toLower, isSpace) import Data.List-import Data.List.Split+-- import Data.List.Split import qualified Data.List.NonEmpty as NE+import Data.Void+import Lens.Micro.Platform+import Text.Megaparsec+import Text.Megaparsec.Char+import Text.Megaparsec.Debug import Types  data ImportType = Def | Open +data ImportOpts = ImportOpts+  { _optInput            :: String +  , _optOutput           :: String+  , _optImportType       :: ImportType+  , _optImportReverse    :: Bool+  , _optRowDelimiter     :: String+  , _optTermDefDelimiter :: String+  , _optDefDelimiter     :: Maybe String }++makeLenses ''ImportOpts+ instance Read ImportType where   readsPrec _ input =     case map toLower input of@@ -15,14 +33,64 @@          | "definition" `isPrefixOf` xs -> [(Def, drop 10 xs)]          | otherwise -> [] -parseImportInput :: ImportType -> Bool -> String -> Maybe [Card]-parseImportInput iType reverse input = -  let listToTuple [q, a] = Just $ if not reverse then (q, a) else (a, q)-      listToTuple _ = Nothing-      xs = mapM (listToTuple . splitOn "\t") (lines input)-      makeOpen (header, body) = OpenQuestion header Nothing-        (P "" (NE.fromList (map (dropWhile isSpace) (splitOneOf ",/;" body))) (Normal ""))+type Parser = Parsec Void String -  in case iType of-    Def  -> map (\(s1, s2) -> Definition s1 Nothing s2) <$> xs-    Open -> map makeOpen <$> xs +rowDelimiter :: String+rowDelimiter = "\n\n"++termDefDelimiter :: String+termDefDelimiter = "\t"++defDelimiter :: String+defDelimiter = ","++parseImportInput :: ImportOpts -> String -> Either String [Card]+parseImportInput opts s = case parse (pImportInput opts) "failed import parsing" s of+  Left parseErrorBundle -> Left $ errorBundlePretty (parseErrorBundle :: ParseErrorBundle String Void)+  Right cards -> Right cards++pImportInput :: ImportOpts -> Parser [Card]+pImportInput opts = pRow opts `sepEndBy1` (void (try (pRowDelimiter *> eol)) <|> void pRowDelimiter <|> void (many eol) <|> eof)+  where pRowDelimiter = string (opts ^. optRowDelimiter)++pRow :: ImportOpts -> Parser Card+pRow opts =+  let+    pTermDefDelimiter = string (opts ^. optTermDefDelimiter)+    pDefDelimiter = string <$> (opts ^. optDefDelimiter)+    pTerm = manyTill anySingle . lookAhead . try $ pSpecial opts+    pDefs = maybe (fmap (:[]) (pDef opts)) (pDef opts `sepBy`) pDefDelimiter+    defBeforeTerm = opts ^. optImportReverse+  in+    case (defBeforeTerm, opts ^. optImportType) of+      (False, Open) -> do+        term <- pTerm+        pTermDefDelimiter+        defs <- pDefs+        return $ OpenQuestion (filter (/= '\n') term) Nothing (P "" (NE.fromList (map (dropWhile isSpace) defs)) (Normal ""))+      (True, Open) -> do+        defs <- pDefs+        pTermDefDelimiter+        term <- pTerm+        return $ OpenQuestion (filter (/= '\n') term) Nothing (P "" (NE.fromList (map (dropWhile isSpace) defs)) (Normal ""))+      (False, Def) -> do+        term <- pTerm+        pTermDefDelimiter+        def <- pDef opts+        return $ Definition (filter (/= '\n') term) Nothing def+      (True, Def) -> do+        def <- pDef opts+        pTermDefDelimiter+        term <- pTerm+        return $ Definition (filter (/= '\n') term) Nothing def++pDef :: ImportOpts -> Parser String+pDef opts = maybe+  (manyTill anySingle . lookAhead . try $ pSpecial opts)+  (\pDefDelimiter -> manyTill anySingle . lookAhead . try $ void pDefDelimiter <|> pSpecial opts)+  (string <$> (opts ^. optDefDelimiter))++pSpecial :: ImportOpts -> Parser ()+pSpecial opts = void pTermDefDelimiter <|> void pRowDelimiter <|> (eol *> eof) <> eof+  where pTermDefDelimiter = string (opts ^. optTermDefDelimiter)+        pRowDelimiter = string (opts ^. optRowDelimiter)
src/Types.hs view
@@ -36,14 +36,14 @@ instance Show Card where   show card = let showHeader h = "# " <> h <> "\n"     in case card of-      Definition h img descr -> showHeader h <> maybe "" show img <> "\n" <> descr-      OpenQuestion h img p -> showHeader h <> maybe "" show img <> "\n" <> show p+      Definition h img descr -> showHeader h <> maybe "" ((<>"\n") . show) img <> descr+      OpenQuestion h img p -> showHeader h <> maybe "" ((<>"\n") . show) img <> show p       MultipleChoice h img c inc -> -        showHeader h <> maybe "" show img <> "\n" <> showMultipleChoice c inc+        showHeader h <> maybe "" ((<>"\n") . show) img <> showMultipleChoice c inc       MultipleAnswer h img opts ->-        showHeader h <> maybe "" show img <> "\n" <> unlines' (NE.toList (NE.map show opts))+        showHeader h <> maybe "" ((<>"\n") . show) img <> unlines' (NE.toList (NE.map show opts))       Reorder h img elts -> -        showHeader h <>maybe "" show img <> "\n" <> unlines' (NE.toList (NE.map showReorder elts))+        showHeader h <>maybe "" ((<>"\n") . show) img <> unlines' (NE.toList (NE.map showReorder elts))  --              alt   file data External = Image String String
src/UI.hs view
@@ -8,6 +8,7 @@ , Card , External , ImportType(..)+, ImportOpts(..) , Parameters(..)  , goToState