module OrgStat.Parser
( ParsingException (..)
, parseOrg
, runParser
) where
import Control.Exception (Exception)
import qualified Data.Attoparsec.Text as A
import qualified Data.OrgMode.Parse as OP
import qualified Data.Text as T
import Data.Time (LocalTime (..), TimeOfDay (..), fromGregorian)
import Data.Time.Calendar ()
import Universum
import OrgStat.Ast (Clock (..), Org (..))
data ParsingException =
ParsingException Text
deriving (Show, Typeable)
instance Exception ParsingException
parseOrg :: [Text] -> A.Parser Org
parseOrg todoKeywords = convertDocument <$> OP.parseDocument todoKeywords
where
convertDocument :: OP.Document -> Org
convertDocument (OP.Document _ headings) = Org
{ _orgTitle = ""
, _orgTags = []
, _orgClocks = []
, _orgSubtrees = map convertHeading headings
}
convertHeading :: OP.Heading -> Org
convertHeading heading = Org
{ _orgTitle = OP.title heading
, _orgTags = OP.tags heading
, _orgClocks = getClocks $ OP.section heading
, _orgSubtrees = map convertHeading $ OP.subHeadings heading
}
mapEither :: (a -> Either e b) -> ([a] -> [b])
mapEither f xs = rights $ map f xs
getClocks :: OP.Section -> [Clock]
getClocks section =
mapMaybe convertClock $ concat
[ OP.sectionClocks section
, mapEither (A.parseOnly OP.parseClock) $
lines $
OP.sectionParagraph section
]
convertClock :: (Maybe OP.Timestamp, Maybe OP.Duration) -> Maybe Clock
convertClock (Just (OP.Timestamp start _active (Just end)), _duration) =
Clock <$> convertDateTime start <*> convertDateTime end
convertClock _ = Nothing
convertDateTime :: OP.DateTime -> Maybe LocalTime
convertDateTime
OP.DateTime
{ yearMonthDay = OP.YMD' (OP.YearMonthDay year month day)
, hourMinute = Just (hour, minute)
}
= Just $ LocalTime
(fromGregorian (toInteger year) month day)
(TimeOfDay hour minute 0)
convertDateTime _ = Nothing
runParser :: (MonadThrow m) => [Text] -> Text -> m Org
runParser todoKeywords t =
case A.parseOnly (parseOrg todoKeywords) t of
Left err -> throwM $ ParsingException $ T.pack err
Right res -> pure res