Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Required Variables in Template #19

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Text/DocTemplates.hs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ Currently the following pipes are predefined:
-}

module Text.DocTemplates ( renderTemplate
, renderTemplateM
, compileTemplate
, compileTemplateFile
, applyTemplate
Expand All @@ -424,7 +425,7 @@ import Data.Text (Text)
import Text.DocTemplates.Parser (compileTemplate)
import Text.DocTemplates.Internal ( TemplateMonad(..), Context(..),
Val(..), ToContext(..), FromContext(..), TemplateTarget,
Template, renderTemplate )
Template, renderTemplate, renderTemplateM )

-- | Compile a template from a file. IO errors will be
-- raised as exceptions; template parsing errors result in
Expand Down
76 changes: 52 additions & 24 deletions src/Text/DocTemplates/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

module Text.DocTemplates.Internal
( renderTemplate
, renderTemplateM
, TemplateMonad(..)
, Context(..)
, Val(..)
Expand All @@ -39,6 +40,7 @@ import Data.Text.Conversions (FromText(..), ToText(..))
import Data.Aeson (Value(..), ToJSON(..), FromJSON(..), Result(..), fromJSON)
import Control.Monad.Identity
import qualified Control.Monad.State.Strict as S
import Control.Monad.Trans
import Data.Char (chr, ord)
import Data.Maybe (fromMaybe)
import qualified Data.Text.Read as T
Expand Down Expand Up @@ -116,14 +118,15 @@ data Variable =
Variable
{ varParts :: [Text]
, varPipes :: [Pipe]
, varRequired :: Bool
}
deriving (Show, Read, Data, Typeable, Generic, Eq, Ord)

instance Semigroup Variable where
Variable xs fs <> Variable ys gs = Variable (xs <> ys) (fs <> gs)
Variable xs fs r <> Variable ys gs r' = Variable (xs <> ys) (fs <> gs) (r || r')

instance Monoid Variable where
mempty = Variable mempty mempty
mempty = Variable mempty mempty False
mappend = (<>)

type TemplateTarget a =
Expand Down Expand Up @@ -373,22 +376,42 @@ instance Monoid (Resolved a) where
mappend = (<>)
mempty = Resolved False []

resolveVariable :: TemplateTarget a
=> Variable -> Context a -> Resolved a
class Monad m => MissingRequired m where
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any other instances that should be added?

missingRequired :: Variable -> m (Resolved a)

instance MissingRequired m => MissingRequired (RenderStateM m) where
missingRequired = lift . missingRequired

instance MissingRequired Identity where
missingRequired = const $ pure $ Resolved False []

instance MissingRequired (Either String) where
missingRequired v = Left $ T.unpack msg
where
msg = "The variable '" <> name <> "' could not be found in the context."
name = T.intercalate "." $ varParts v

resolveVariable :: (TemplateTarget a, MissingRequired m)
=> Variable -> Context a -> m (Resolved a)
resolveVariable v ctx = resolveVariable' v (MapVal ctx)

resolveVariable' :: TemplateTarget a
=> Variable -> Val a -> Resolved a
resolveVariable' :: forall a m. (TemplateTarget a, MissingRequired m)
=> Variable -> Val a -> m (Resolved a)
resolveVariable' v val =
case applyPipes (varPipes v) $ multiLookup (varParts v) val of
ListVal xs -> mconcat $ map (resolveVariable' mempty) xs
ListVal xs -> foldMapA (resolveVariable' mempty) xs
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please double check ListVal line

SimpleVal d
| DL.isEmpty d -> Resolved False []
| otherwise -> Resolved True [removeFinalNl d]
MapVal _ -> Resolved True ["true"]
BoolVal True -> Resolved True ["true"]
BoolVal False -> Resolved False ["false"]
NullVal -> Resolved False []
| DL.isEmpty d -> resolved False []
| otherwise -> resolved True [removeFinalNl d]
MapVal _ -> resolved True ["true"]
BoolVal True -> resolved True ["true"]
BoolVal False -> resolved False ["false"]
NullVal
| varRequired v -> missingRequired v
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does missing Required need to be added to any other?

| otherwise -> resolved False []
where
resolved x y = pure $ Resolved x y
foldMapA f = foldr (\x y -> (<>) <$> f x <*> y) (pure mempty)

removeFinalNl :: Doc a -> Doc a
removeFinalNl DL.NewLine = mempty
Expand All @@ -408,36 +431,41 @@ withVariable var ctx f =
where
setVarVal x =
addToContext var x $ Context $ M.insert "it" x $ unContext ctx
addToContext (Variable [] _) _ (Context ctx') = Context ctx'
addToContext (Variable (v:vs) fs) x (Context ctx') =
addToContext (Variable [] _ _) _ (Context ctx') = Context ctx'
addToContext (Variable (v:vs) fs r) x (Context ctx') =
Context $ M.adjust
(\z -> case z of
_ | null vs -> x
MapVal m ->
MapVal $ addToContext (Variable vs fs) x m
MapVal $ addToContext (Variable vs fs r) x m
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure if I correctly modified addToContext

_ -> z) v ctx'

type RenderState = S.State Int
type RenderStateM m = S.StateT Int m

-- | Render a compiled template in a "context" which provides
-- values for the template's variables.
renderTemplate :: (TemplateTarget a, ToContext a b)
=> Template a -> b -> Doc a
renderTemplate t x = S.evalState (renderTemp t (toContext x)) 0
renderTemplate t x = runIdentity $ renderTemplateM t x
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe renderTemplate should be moved to Text.DocTemplates


renderTemplateM :: forall a b m. (TemplateTarget a, ToContext a b, MissingRequired m)
=> Template a -> b -> m (Doc a)
renderTemplateM t x = S.evalStateT (renderTemp t (toContext x)) 0

updateColumn :: TemplateTarget a => Doc a -> RenderState (Doc a)
updateColumn :: forall a m. (TemplateTarget a, Monad m)
=> Doc a -> RenderStateM m (Doc a)
updateColumn x = do
S.modify $ DL.updateColumn x
return x

renderTemp :: forall a . TemplateTarget a
=> Template a -> Context a -> RenderState (Doc a)
renderTemp :: forall a m. (TemplateTarget a, MissingRequired m)
=> Template a -> Context a -> RenderStateM m (Doc a)
renderTemp (Literal t) _ = updateColumn t
renderTemp (Interpolate v) ctx =
case resolveVariable v ctx of
Resolved _ xs -> updateColumn (mconcat xs)
resolveVariable v ctx >>=
\(Resolved _ xs) -> updateColumn (mconcat xs)
renderTemp (Conditional v ift elset) ctx =
case resolveVariable v ctx of
resolveVariable v ctx >>= \rv -> case rv of
Resolved False _ -> renderTemp elset ctx
Resolved True _ -> renderTemp ift ctx
renderTemp (Iterate v t sep) ctx = do
Expand Down
5 changes: 3 additions & 2 deletions src/Text/DocTemplates/Parser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ pInterpolate = do
P.notFollowedBy (P.char '(') -- bare partial
return (cl, v)
res <- (P.char ':' *> (pPartialName >>= pPartial (Just var)))
<|> Iterate var (Interpolate (Variable ["it"] [])) <$> pSep
<|> Iterate var (Interpolate (Variable ["it"] [] False)) <$> pSep
<|> return (Interpolate var)
P.skipMany pSpaceOrTab
closer
Expand Down Expand Up @@ -377,10 +377,11 @@ pOpen = pOpenDollar <|> pOpenBraces

pVar :: Monad m => Parser m Variable
pVar = do
required <- True <$ P.try (P.string "!") <|> pure False
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not be the syntax you are looking for.

first <- pIdentPart <|> pIt
rest <- P.many (P.char '.' *> pIdentPart)
pipes <- P.many pPipe
return $ Variable (first:rest) pipes
return $ Variable (first:rest) pipes required

pPipe :: Monad m => Parser m Pipe
pPipe = do
Expand Down