glabrous 2.0.6.4 → 2.1.0.0
raw patch · 5 files changed
+116/−90 lines, 5 filesdep ~directoryPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: directory
API changes (from Hackage documentation)
Files
- CHANGELOG.md +6/−0
- LICENSE +1/−1
- glabrous.cabal +53/−44
- src/Text/Glabrous.hs +33/−45
- tests/hspec.hs +23/−0
+ CHANGELOG.md view
@@ -0,0 +1,6 @@+# Revision history for glabrous++## 2.1.0.0 -- 2026-06-12++* Performance optimizations+* insertManyTemplates has changed (See documentation)
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2016-2025, Michel Boucey+Copyright (c) 2016-2026, Michel Boucey All rights reserved. Redistribution and use in source and binary forms, with or without
glabrous.cabal view
@@ -1,20 +1,22 @@-name: glabrous-version: 2.0.6.4-synopsis: A template DSL library-description: A minimalistic, Mustache-like syntax, truly logic-less,- pure Text template DSL library-homepage: https://github.com/MichelBoucey/glabrous-license: BSD3-license-file: LICENSE-author: Michel Boucey-maintainer: michel.boucey@gmail.com-copyright: (c) 2016-2025 - Michel Boucey-category: Text, Web-build-type: Simple-cabal-version: >=1.10-extra-source-files: README.md+cabal-version: 3.0+name: glabrous+version: 2.1.0.0+synopsis: A template DSL library+description:+ A minimalistic, Mustache-like syntax, truly logic-less,+ pure Text template DSL library -Tested-With:+homepage: https://github.com/MichelBoucey/glabrous+license: BSD-3-Clause+license-file: LICENSE+author: Michel Boucey+maintainer: michel.boucey@gmail.com+copyright: (c) 2016-2026 - Michel Boucey+category: Text, Web+build-type: Simple+extra-source-files: README.md+extra-doc-files: CHANGELOG.md+tested-with: GHC ==8.8.4 || ==8.10.7 || ==9.0.2@@ -22,42 +24,49 @@ || ==9.4.8 || ==9.6.7 || ==9.8.4- || ==9.10.2+ || ==9.10.3 || ==9.12.2+ || ==9.14.1 source-repository head type: git location: https://github.com/MichelBoucey/glabrous.git library- hs-source-dirs: src- exposed-modules: Text.Glabrous- , Text.Glabrous.Types- other-modules: Text.Glabrous.Internal- build-depends: aeson >= 2 && < 2.3- , aeson-pretty >= 0.7.2 && < 0.9- , attoparsec >= 0.12.1.6 && < 0.15- , base >= 4.8.1.0 && < 5- , bytestring >= 0.10.6 && < 0.13- , cereal >= 0.4.1.1 && < 0.6- , cereal-text >= 0.1.0 && < 0.2- , either >= 4.4.1 && < 5.1- , text >= 1.2.1 && < 2.2- , unordered-containers >= 0.2.5.1 && < 0.3+ hs-source-dirs: src+ exposed-modules:+ Text.Glabrous+ Text.Glabrous.Types - default-language: Haskell2010- GHC-options: -Wall+ other-modules: Text.Glabrous.Internal+ build-depends:+ aeson >=2 && <2.4+ , aeson-pretty >=0.7.2 && <0.9+ , attoparsec >=0.12.1.6 && <0.15+ , base >=4.8.1.0 && <5+ , bytestring >=0.10.6 && <0.13+ , cereal >=0.4.1.1 && <0.6+ , cereal-text >=0.1.0 && <0.2+ , text >=1.2.1 && <2.2+ , unordered-containers >=0.2.5.1 && <0.3 + if !impl(ghc >=9.4.8)+ build-depends: either >=4.4.1 && <5.1++ default-language: Haskell2010+ ghc-options: -Wall+ test-suite tests- type: exitcode-stdio-1.0- hs-source-dirs: tests- main-is: hspec.hs- build-depends: base >= 4.8 && < 5- , directory > 1.2 && < 1.4- , either >= 4.4.1 && < 5.1- , glabrous- , hspec >= 2.1.10 && < 3- , text >= 1.2.1 && < 2.2- , unordered-containers == 0.2.*- default-language: Haskell2010+ type: exitcode-stdio-1.0+ hs-source-dirs: tests+ main-is: hspec.hs+ build-depends:+ base >=4.8 && <5+ , directory >=1.2.0 && <1.4+ , either >=4.4.1 && <5.1+ , glabrous+ , hspec >=2.1.10 && <3+ , text >=1.2.1 && <2.2+ , unordered-containers >=0.2 && <0.3 + default-language: Haskell2010
src/Text/Glabrous.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}@@ -68,7 +69,11 @@ import Data.Aeson.Encode.Pretty (encodePretty) import qualified Data.ByteString.Lazy as L import qualified Data.HashMap.Strict as H-import Data.List (intersect, intersperse, uncons)+#if MIN_VERSION_base(4,20,0)+import Data.List (intersperse, isSubsequenceOf, uncons)+#else+import Data.List (foldl', intersperse, isSubsequenceOf, uncons)+#endif import qualified Data.Text as T import qualified Data.Text.IO as I @@ -95,24 +100,11 @@ -- | Optimize a 'Template' content after (many) 'partialProcess'(') rewriting(s). compress :: Template -> Template compress Template{..} =- Template { content = go content [] }+ Template { content = go [] content } where- go ts !ac = do- let (a,b) = span isLiteral ts- u = uncons b- if not (null a)- then case u of- Just (c,d) -> go d (ac ++ [concatLiterals a] ++ [c])- Nothing -> ac ++ [concatLiterals a]- else case u of- Just (e,f) -> go f (ac ++ [e])- Nothing -> ac- where- concatLiterals =- foldr trans (Literal "")- where- trans (Literal a) (Literal b) = Literal (a `T.append` b)- trans _ _ = undefined+ go ac (Literal a : Literal b : rest) = go ac (Literal (T.append a b) : rest)+ go ac (x : rest) = go (x : ac) rest+ go ac [] = reverse ac -- | Build an empty 'Context'. initContext :: Context@@ -224,7 +216,7 @@ -- the given 'Context' are not empty. isSet :: Context -> Bool isSet Context{..} =- H.foldr (\v b -> b && v /= T.empty) True variables+ H.foldl' (\b v -> b && v /= T.empty) True variables -- | Get the list of the given 'Context' variables. variablesOf :: Context -> [T.Text]@@ -249,34 +241,29 @@ insertTemplate _ (Literal _) _ = Nothing insertTemplate te t te' = do guard (t `elem` content te)- return Template { content = foldl trans [] (content te) }+ return Template { content = reverse (foldl' trans [] (content te)) } where trans o t'@(Tag _) = if t' == t- then o ++ content te'- else o ++ [t']- trans o l = o ++ [l]+ then reverse (content te') ++ o+ else t' : o+ trans o l = l : o --- | get 'Just' a new 'Template' by inserting many 'Template's,--- if there is at least one tag correspondence, or 'Nothing'.+-- | get 'Just' a new 'Template' by inserting many 'Template's or 'Nothing'+-- if specified `Tag`s are not present, and not in the exact given order. -- -- >λ>insertManyTemplates t0 [(Tag "template1",t1),(Tag "template2",t2)] insertManyTemplates :: Template -> [(Token,Template)] -> Maybe Template insertManyTemplates te ttps = do- guard (tagsOf te `intersect` (fst <$> ttps) /= mempty)- return Template { content = foldl trans [] (content te) }+ guard (isSubsequenceOf (fst <$> ttps) (tagsOf te))+ pure Template { content = reverse (foldl' trans [] (content te)) } where- trans o li@(Literal _) = o ++ [li]- trans o ta@(Tag _) =- case lookupTemplate ta ttps of- Nothing -> o ++ [ta]- Just te' -> o ++ content te'- lookupTemplate (Literal _) _ = Nothing- lookupTemplate _ [] = Nothing- lookupTemplate t (p:ps) =- if fst p == t- then Just (snd p)- else lookupTemplate t ps+ hm = H.fromList [(k, t) | (Tag k, t) <- ttps]+ trans o li@(Literal _) = li : o+ trans o ta@(Tag k) =+ case H.lookup k hm of+ Nothing -> ta : o+ Just te' -> reverse (content te') ++ o -- | Output the content of the given 'Template' -- as it is, with its 'Tag's, if they exist.@@ -304,7 +291,8 @@ tagsRename ts Template{..} = Template { content = rename <$> content } where- rename t@(Tag n) = maybe t Tag (lookup n ts)+ hm = H.fromList ts+ rename t@(Tag n) = maybe t Tag (H.lookup n hm) rename l@(Literal _) = l -- | 'True' if a 'Template' has no more 'Tag'@@ -346,15 +334,15 @@ -- >Partial {template = Template {content = [Literal "Some ",Tag "tags",Literal " are unused in this ",Tag "text",Literal "."]}, context = Context {variables = fromList [("text",""),("tags","")]}} partialProcess' :: Template -> Context -> Result partialProcess' t c@Context{..} =- case foldl trans (mempty,mempty) (content t) of- (f,[]) -> Final (toTextWithContext (const T.empty) c f)- (p,p') -> Partial Template { content = p } (fromTagsList p')+ case foldl trans ([],[]) (content t) of+ (f,[]) -> Final (toTextWithContext (const T.empty) c (reverse f))+ (p,p') -> Partial Template { content = reverse p } (fromTagsList (reverse p')) where trans (!c',!ts) t' = case t' of Tag k -> case H.lookup k variables of- Just v -> (c' ++ [Literal v],ts)- Nothing -> (c' ++ [t'],ts ++ [k])- Literal _ -> (c' ++ [t'],ts)+ Just v -> (Literal v : c', ts)+ Nothing -> (t' : c', k : ts)+ Literal _ -> (t' : c', ts)
tests/hspec.hs view
@@ -3,6 +3,7 @@ import Data.Either.Combinators (fromRight') import qualified Data.HashMap.Strict as H+import Data.List (isSubsequenceOf) import Data.Maybe import Data.Text import System.Directory (removeFile)@@ -107,6 +108,28 @@ describe "tagsRename" $ it "renames tags for a new template" $ (return $ tagsRename [("consectetur","causatur"),("mattis","generis")] $ compress $ partialProcess template' pcontext) `shouldReturn` Template {content = [Literal "Affirmanti incumbit probatio. Lorem Dolor! sit amet, ",Tag "causatur",Literal " adipiscing elit. Proin bibendum mauris Vitae! dui venenatis pretium. Maecenas vestibulum justo at accumsan ",Tag "generis",Literal ". Nulla id finibus sem. Cras dolor nunc, consectetur in tincidunt id, facilisis in lorem. Nullam sed eros venenatis, tempor felis in, ultrices enim. Donec sit amet ligula ac orci cursus finibus ut eget lectus. Praesent feugiat massa non mi venenatis Faucibus! . Fabricando fit faber."]}++ describe "insertTemplate" $ do+ it "inserts a template into another one at a specified tag" $+ let tt = fromRight' $ fromText templateText+ in return (insertTemplate tt (Tag "condimentum") template') `shouldReturn`+ Just (Template {content = [Literal "Affirmanti incumbit probatio. Lorem ",Tag "ipsum",Literal " dolor sit amet, consectetur adipiscing elit. Proin bibendum mauris vitae dui venenatis pretium. Maecenas vestibulum justo at accumsan mattis. Nulla id finibus sem. Cras dolor nunc, consectetur in tincidunt id, facilisis in lorem. Nullam sed eros venenatis, tempor felis in, ultrices enim. Donec sit amet ligula ac orci cursus finibus ut eget lectus. Praesent feugiat massa non mi venenatis faucibus. Proin sollicitudin ultrices sapien, eu venenatis purus sollicitudin vel. Donec maximus id ligula in ",Literal "{",Literal "congue",Literal "}",Literal ". Ut neque erat, dictum vehicula justo vel, dapibus facilisis est. Proin elementum finibus magna. Maecenas varius ultrices arcu, et auctor lectus egestas nec. Nulla facilisi. Donec eros urna, mattis viverra dapibus et, aliquet vel ",Tag "tortor",Literal ". Etiam rutrum lacinia turpis, vitae faucibus nibh pharetra gravida.\n\nVivamus venenatis nec sapien eu rutrum. Mauris ",Tag "lectus",Literal " tortor, suscipit eget tristique in, finibus a augue. Nulla euismod fermentum ligula, eget viverra eros porttitor eget. Integer lacinia id libero vitae laoreet. Vivamus sit amet molestie mi, in posuere diam. Nulla ornare luctus diam, at sodales neque porta ac. Nunc mi lectus, imperdiet in lorem pretium, volutpat ",Tag "vehicula",Literal " sem. Aliquam quis orci eu nisl consequat bibendum. Fusce efficitur ",Tag "bibendum",Literal " tristique. Integer lacinia turpis sit amet neque imperdiet tempor. Aenean ",Literal "{",Literal "dolor",Literal "}}",Literal " odio, congue sed lorem nec, ",Literal "Affirmanti incumbit probatio. Lorem ",Tag "dolor",Literal " sit amet, ",Tag "consectetur",Literal " adipiscing elit. Proin bibendum mauris ",Tag "vitae",Literal " dui venenatis pretium. Maecenas vestibulum justo at accumsan ",Tag "mattis",Literal ". Nulla id finibus sem. Cras dolor nunc, consectetur in tincidunt id, facilisis in lorem. Nullam sed eros venenatis, tempor felis in, ultrices enim. Donec sit amet ligula ac orci cursus finibus ut eget lectus. Praesent feugiat massa non mi venenatis ",Tag "faucibus",Literal ". Fabricando fit faber.",Literal " tempor lorem. Ut maximus lorem in augue tincidunt, nec dapibus erat luctus. Vivamus varius vitae lacus sed elementum. Nulla feugiat porttitor dictum. Donec posuere nisl at tincidunt gravida. Fusce semper maximus metus sit amet tempus. Quisque tempus neque ligula, efficitur molestie quam convallis vel. Donec porttitor rutrum ",Tag "tellus",Literal ", vel faucibus felis dapibus at. Mauris id tortor ",Tag "faucibus",Literal ", tempus velit sit amet, pretium tellus. Aliquam luctus mauris dui,",Tag "cursus",Literal " volutpat quam egestas vel. In vel neque eu eros efficitur aliquam. Donec efficitur tortor massa. Praesent molestie enim justo, ac gravida enim tristique nec. In non rutrum mi. Morbi viverra, mi vestibulum gravida iaculis, nulla ipsum fringilla nisl, at vestibulum nisl neque at est. Proin viverra varius nisi, quis tincidunt odio suscipit nec. Vestibulum nisi justo, fermentum ",Tag "commodo",Literal " accumsan id, dictum dictum felis. Fusce rutrum suscipit urna in ornare. Duis volutpat arcu et rutrum pulvinar. Vestibulum mattis scelerisque ligula, ut fermentum ligula. Fusce ut semper ipsum, at ",Tag "vestibulum",Literal " felis. Ut semper turpis sit amet diam commodo bibendum. Ut vulputate quam nec massa pretium, vitae sodales quam ultrices. Fusce imperdiet mi sed maximus ",Tag "lobortis",Literal ". Donec nulla quam, luctus a placerat vel, scelerisque et lacus. Nulla sagittis ipsum ac viverra eleifend. Praesent et sem ut erat ornare rutrum. Praesent ullamcorper dolor nunc, nec aliquam massa varius sed.\n\nUt luctus ante ut venenatis blandit. Aenean cursus leo non finibus cursus. Suspendisse sed enim hendrerit neque ",Tag "fermentum",Literal " rutrum eu ut nulla. Curabitur consequat orci quam, et condimentum lacus rutrum interdum. In in ",Tag "maximus",Literal " eros. Duis rhoncus nisi at volutpat aliquam. Quisque vestibulum ",Tag "venenatis",Literal " nisi, sed ",Tag "sagittis",Literal " est ultrices ut. Morbi quis ipsum ",Tag "vitae",Literal " erat mollis egestas nec viverra diam. Aenean imperdiet vestibulum risus vel bibendum. Aenean eget augue luctus, congue erat vel, hendrerit nunc. Ut metus risus, viverra non varius in, ",Tag "rhoncus",Literal " sed. Fabricando fit faber."]})++ describe "insertManyTemplates" $ do+ it "inserts multiple Templates at once in another one" $+ let t = fromRight' $ fromText "Affirmanti incumbit probatio. Lorem {{ipsum}} dolor sit amet, consectetur adipiscing elit. Proin bibendum mauris vitae dui venenatis pretium. Maecenas vestibulum justo at accumsan mattis. Nulla id finibus sem. Cras dolor nunc, consectetur in tincidunt id, facilisis in lorem. Nullam sed eros venenatis, tempor felis in, ultrices enim. Donec sit amet ligula ac orci cursus finibus ut eget lectus. Praesent feugiat massa non mi venenatis faucibus. Pro insollicitudin ultrices sapien, eu venenatis purus sollicitudin vel. Donec maximus id ligula in {{congue}}. Ut neque erat, dictum vehicula justo vel, dapibus facilisis est. Proin elementum finibus magna. Maecenas varius ultrices arcu, et auctor lectus egestas nec. Nulla facilisi. Donec eros urna, mattis viverra dapibus et, aliquet vel {{tortor}}. Etiam rutrum lacinia turpis, vitae faucibus nibh pharetra gravida.\n\nVivamus venenatis nec sapien eu rutrum. Mauris {{lectus}} tortor, suscipit eget tristique in, finibus a augue. Nulla euismod fermentum ligula, eget viverra eros porttitor eget. Integer lacinia id libero vitae laoreet. Vivamus sit amet molestie mi, in posuere diam. Nulla ornare luctus diam, at sodales neque porta ac. Nunc mi lectus, imperdiet in lorem pretium, volutpat {{vehicula}} sem. Aliquam quis orci eu nisl consequat bibendum. Fusce efficitur {{bibendum}} tristique. Integer lacinia turpis sit amet neque imperdiet tempor. Aenean {dolor}} odio, congue sed lorem nec, {{condimentum}} tempor lorem"+ ta = fromRight' $ fromText "Aliquam luctus mauris dui,{{cursus}} volutpat quam egestas vel."+ tb = fromRight' $ fromText "Fusce imperdiet mi sed maximus {{lobortis}}. Donec nulla quam, luctus a placerat vel, scelerisque et lacus."+ tc = fromRight' $ fromText "In in {{maximus}} eros. Duis rhoncus nisi at volutpat aliquam. Quisque vestibulum {{venenatis}} nisi, sed {{sagittis}} est ultrices ut. Morbi quis ipsum {{vitae}} erat mollis egestas nec viverra diam."+ in return (insertManyTemplates t [(Tag "congue", ta), (Tag "lectus", tb), (Tag "vehicula", tc)]) `shouldReturn`+ Just Template { content = [Literal "Affirmanti incumbit probatio. Lorem ", Tag "ipsum", Literal " dolor sit amet, consectetur adipiscing elit. Proin bibendum mauris vitae dui venenatis pretium. Maecenas vestibulum justo at accumsan mattis. Nulla id finibus sem. Cras dolor nunc, consectetur in tincidunt id, facilisis in lorem. Nullam sed eros venenatis, tempor felis in, ultrices enim. Donec sit amet ligula ac orci cursus finibus ut eget lectus. Praesent feugiat massa non mi venenatis faucibus. Pro insollicitudin ultrices sapien, eu venenatis purus sollicitudin vel. Donec maximus id ligula in ", Literal "Aliquam luctus mauris dui,", Tag "cursus", Literal " volutpat quam egestas vel.", Literal ". Ut neque erat, dictum vehicula justo vel, dapibus facilisis est. Proin elementum finibus magna. Maecenas varius ultrices arcu, et auctor lectus egestas nec. Nulla facilisi. Donec eros urna, mattis viverra dapibus et, aliquet vel ", Tag "tortor", Literal ". Etiam rutrum lacinia turpis, vitae faucibus nibh pharetra gravida.\n\nVivamus venenatis nec sapien eu rutrum. Mauris ", Literal "Fusce imperdiet mi sed maximus ", Tag "lobortis", Literal ". Donec nulla quam, luctus a placerat vel, scelerisque et lacus.", Literal " tortor, suscipit eget tristique in, finibus a augue. Nulla euismod fermentum ligula, eget viverra eros porttitor eget. Integer lacinia id libero vitae laoreet. Vivamus sit amet molestie mi, in posuere diam. Nulla ornare luctus diam, at sodales neque porta ac. Nunc mi lectus, imperdiet in lorem pretium, volutpat ", Literal "In in ", Tag "maximus", Literal " eros. Duis rhoncus nisi at volutpat aliquam. Quisque vestibulum ", Tag "venenatis", Literal " nisi, sed ", Tag "sagittis", Literal " est ultrices ut. Morbi quis ipsum ", Tag "vitae", Literal " erat mollis egestas nec viverra diam.", Literal " sem. Aliquam quis orci eu nisl consequat bibendum. Fusce efficitur ", Tag "bibendum", Literal " tristique. Integer lacinia turpis sit amet neque imperdiet tempor. Aenean ", Literal "{", Literal "dolor", Literal "}}", Literal " odio, congue sed lorem nec, ", Tag "condimentum", Literal " tempor lorem"]}++ it "returns Nothing when one or more specified tags is/are missing" $+ let t = fromRight' $ fromText "Affirmanti incumbit probatio. Lorem {{ipsum}} dolor sit amet, consectetur adipiscing elit. Proin bibendum mauris vitae dui venenatis pretium. Maecenas vestibulum justo at accumsan mattis. Nulla id finibus sem. Cras dolor nunc, consectetur in tincidunt id, facilisis in lorem. Nullam sed eros venenatis, tempor felis in, ultrices enim. Donec sit amet ligula ac orci cursus finibus ut eget lectus. Praesent feugiat massa non mi venenatis faucibus. Pro insollicitudin ultrices sapien, eu venenatis purus sollicitudin vel. Donec maximus id ligula in {{congue}}. Ut neque erat, dictum vehicula justo vel, dapibus facilisis est. Proin elementum finibus magna. Maecenas varius ultrices arcu, et auctor lectus egestas nec. Nulla facilisi. Donec eros urna, mattis viverra dapibus et, aliquet vel {{tortor}}. Etiam rutrum lacinia turpis, vitae faucibus nibh pharetra gravida.\n\nVivamus venenatis nec sapien eu rutrum. Mauris {{lectus}} tortor, suscipit eget tristique in, finibus a augue. Nulla euismod fermentum ligula, eget viverra eros porttitor eget. Integer lacinia id libero vitae laoreet. Vivamus sit amet molestie mi, in posuere diam. Nulla ornare luctus diam, at sodales neque porta ac. Nunc mi lectus, imperdiet in lorem pretium, volutpat {{vehicula}} sem. Aliquam quis orci eu nisl consequat bibendum. Fusce efficitur {{bibendum}} tristique. Integer lacinia turpis sit amet neque imperdiet tempor. Aenean {dolor}} odio, congue sed lorem nec, {{condimentum}} tempor lorem"+ ta = fromRight' $ fromText "Aliquam luctus mauris dui,{{cursus}} volutpat quam egestas vel."+ tb = fromRight' $ fromText "Fusce imperdiet mi sed maximus {{lobortis}}. Donec nulla quam, luctus a placerat vel, scelerisque et lacus."+ tc = fromRight' $ fromText "In in {{maximus}} eros. Duis rhoncus nisi at volutpat aliquam. Quisque vestibulum {{venenatis}} nisi, sed {{sagittis}} est ultrices ut. Morbi quis ipsum {{vitae}} erat mollis egestas nec viverra diam."+ in return (insertManyTemplates t [(Tag "congue", ta), (Tag "noMatchum", tb), (Tag "vehicula", tc)]) `shouldReturn` Nothing describe "partialProcess'" $ afterAll_ clean $ it "applies to a template with a context outputs a final text" $