packages feed

clippings-0.1.3: src/Text/Kindle/Clippings/Reader.hs

module Text.Kindle.Clippings.Reader where

import Control.Applicative ((<$>), (<*>), (*>), (<*), (<|>))
import Data.Bifunctor (bimap)
import Data.Functor.Extras ((<$$>),for)
import Data.List (find)
import Data.Maybe (fromJust, isJust)
import Data.String.Extras (pad, chomp)
import Data.Time.LocalTime (LocalTime)
import Data.Time.Parse (strptime)
import Text.Kindle.Clippings.Types (Clipping(..),Location(..),Document(..),Position(..),Content(..))
import Text.Parsec (many1, digit, alphaNum, string, skipMany, oneOf, try, char, manyTill, anyToken)
import Text.Parsec.String (Parser)
import Text.Parsec.Combinator.Extras (but, tryBut1, tryMaybe, tryString)

eol :: Parser ()
eol = skipMany $ oneOf "\n\r"

eor :: Parser String
eor = string "=========="

readTitle :: Parser String
readTitle = chomp . concat <$> textAndBrackets
  where brackets = (\a b c -> a:(concat b)++c) <$> char '(' <*> textAndBrackets <*> string ") "
        textAndBrackets = many1 (tryBut1 "(\r\n)" <|> try brackets)

readAuthor :: Parser (Maybe String)
readAuthor = tryMaybe $ init 
         <$> (char '(' *> but "\n\r")

readContentType :: Parser String
readContentType = (tryString "- Your " <|> string "- ")
               *> but " "
               <* (tryString " on " <|> many1 (char ' '))

readPageNumber :: Parser (Maybe Int)
readPageNumber = tryMaybe $ read 
             <$> (string "Page " *> many1 alphaNum <* string " | ")

readLocation :: Parser (Maybe Location)
readLocation = tryMaybe 
             $ (tryString "Loc. " <|> string "Location ")
            *> (try readLocationRegion <|> readLocationInt)
            <* but "|" <* char '|' <* many1 (char ' ')

readLocationInt :: Parser Location
readLocationInt = Location . read <$> many1 digit

readLocationRegion :: Parser Location
readLocationRegion = parseRegion 
                <$$> (,) 
                 <$> many1 digit 
                 <*> (char '-' *> many1 digit)

parseRegion :: (String, String) -> Location
parseRegion = Region . bimap read read . pad

parseDate :: String -> LocalTime
parseDate = fst . fromJust . fromJust {-[^1]-} . find isJust . for formats . flip strptime
  where formats =
          [ "%A, %d %B %y %X"
          , "%A, %B %d, %Y %r"
          , ""
          ]
-- [^1]: This is safe: `strptime x ""` is `Just` for all `x`.

readDate :: Parser LocalTime
readDate = parseDate <$> (string "Added on " *> but "\n\r")

readContent :: Parser String
readContent = chomp <$> manyTill anyToken (try eor)

readClipping :: Parser (Maybe Clipping)
readClipping = clipping
           <$> (Document <$> readTitle <*> readAuthor <* eol)
           <*> readContentType
           <*> (Position <$> readPageNumber <*> readLocation)
           <*> readDate <* eol
           <*> readContent <* eol

clipping :: Document -> String -> Position -> LocalTime -> String -> Maybe Clipping
clipping d t p l c
  |(==) t "Highlight" = Just $ Clipping d p l $ Highlight c
  |(==) t "Note"      = Just $ Clipping d p l $ Annotation c
  |(==) t "Bookmark"  = Just $ Clipping d p l Bookmark
  |otherwise = Nothing

readClippings :: Parser [Maybe Clipping]
readClippings = many1 readClipping