packages feed

typst-0.7: src/Typst/Show.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fsimpl-tick-factor=140 #-}

module Typst.Show (applyShowRules) where

import Data.Array ((!))
import qualified Data.Map as M
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
import Data.Text (Text)
import qualified Data.Text as T
import Text.Parsec (getState, updateState)
import qualified Text.Regex.TDFA as TDFA
import Typst.Regex (RE (..), makeLiteralRE)
import Typst.Syntax
import Typst.Types

-- import Debug.Trace

applyShowRules :: Monad m => Seq Content -> MP m (Seq Content)
applyShowRules cs = do
  rules <- evalShowRules <$> getState
  foldMap (tryShowRules rules) cs

-- By experiment, it seems that show rules work this way:
-- the first (i.e. most recently defined) one to match a given element
-- are applied first.
tryShowRules ::
  Monad m =>
  [ShowRule] ->
  Content ->
  MP m (Seq Content)
tryShowRules (r:rest) e@(Elt "text" pos fields) =
  case M.lookup "body" fields of
    Just (VContent cs) -> do
      cs' <- foldMap (tryShowRules (r:rest)) cs
      pure $ Seq.singleton $
        Elt "text" pos (M.insert "body" (VContent cs') fields)
    _ -> applyShowRule r e >>= foldMap (tryShowRules rest)
tryShowRules rs c =
  case rs of
    [] -> pure $ Seq.singleton c
    (r:rest) -> applyShowRule r c >>= foldMap (tryShowRules rest)
-- TODO recursive applyShowRules?

withoutShowRule :: Monad m => ShowRule -> MP m a -> MP m a
withoutShowRule rule pa = do
  oldShowRules <- evalShowRules <$> getState
  updateState $ \st ->
    st { evalShowRules = filter (/= rule) (evalShowRules st) }
  res <- pa
  updateState $ \st -> st {evalShowRules = oldShowRules}
  pure res

applyShowRule :: Monad m => ShowRule -> Content -> MP m (Seq Content)
applyShowRule rule@(ShowRule _ sel f) c = withoutShowRule rule $ do
  case (sel, c) of
    (SelectString s, Txt t) | s `T.isInfixOf` t -> do
      re <- makeLiteralRE s
      replaceRegexContent re t f
    (SelectRegex re@(RE _ re'), Txt t) | not (null (TDFA.matchAll re' t)) -> do
      replaceRegexContent re t f
    (SelectLabel s, elt@(Elt _ _ fields))
      | Just (VLabel s') <- M.lookup "label" fields
      , s' == s
      -> f elt
    (SelectElement name fields, elt@(Elt name' _ fields'))
      | name == name' && fieldsMatch fields fields'
      -> f elt
    (_, cont) -> pure (Seq.singleton cont)
    -- TODO not implemented: SelectOr, SelectAnd, SelectBefore, SelectAfter

fieldsMatch :: [(Identifier, Val)] -> M.Map Identifier Val -> Bool
fieldsMatch [] _ = True
fieldsMatch ((k, v) : rest) m =
  ( case M.lookup k m of
      Just v' -> v == v'
      Nothing -> False
  )
    && fieldsMatch rest m

replaceRegexContent ::
  Monad m =>
  RE ->
  Text ->
  (forall m'. Monad m' => Content -> MP m' (Seq Content)) ->
  MP m (Seq Content)
replaceRegexContent (RE _ re) strIn f =
  let matches = map (! 0) (TDFA.matchAll re strIn)
      go _i str [] = pure $ Seq.singleton (Txt str)
      go i str ((off, len) : rest) =
        let i' = off + len
            before = T.take (off - i) str
            matched = T.take len (T.drop (off - i) str)
            after = T.drop (i' - i) str
         in seq i' $
              (\x y -> Seq.singleton (Txt before) <> x <> y)
                <$> f (Txt matched)
                <*> go i' after rest
   in go 0 strIn matches