neat-interpolation 0.4 → 0.5
raw patch · 5 files changed
+133/−134 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- NeatInterpolation: text :: QuasiQuoter
+ NeatInterpolation: trimming :: QuasiQuoter
+ NeatInterpolation: untrimming :: QuasiQuoter
Files
- library/NeatInterpolation.hs +112/−103
- library/NeatInterpolation/Prelude.hs +0/−1
- library/NeatInterpolation/String.hs +14/−23
- neat-interpolation.cabal +3/−3
- test/Main.hs +4/−4
library/NeatInterpolation.hs view
@@ -1,127 +1,136 @@--- |--- NeatInterpolation provides a quasiquoter for producing strings--- with a simple interpolation of input values.--- It removes the excessive indentation from the input and--- accurately manages the indentation of all lines of interpolated variables.--- But enough words, the code shows it better.------ Consider the following declaration:------ > {-# LANGUAGE QuasiQuotes #-}--- >--- > import NeatInterpolation--- > import Data.Text (Text)--- >--- > f :: Text -> Text -> Text--- > f a b =--- > [text|--- > function(){--- > function(){--- > $a--- > }--- > return $b--- > }--- > |]------ Executing the following:------ > main = T.putStrLn $ f "1" "2"------ will produce this (notice the reduced indentation compared to how it was--- declared):------ > function(){--- > function(){--- > 1--- > }--- > return 2--- > }------ Now let's test it with multiline string parameters:------ > main = T.putStrLn $ f--- > "{\n indented line\n indented line\n}"--- > "{\n indented line\n indented line\n}"------ We get------ > function(){--- > function(){--- > {--- > indented line--- > indented line--- > }--- > }--- > return {--- > indented line--- > indented line--- > }--- > }------ See how it neatly preserved the indentation levels of lines the--- variable placeholders were at?------ If you need to separate variable placeholder from the following text to--- prevent treating the rest of line as variable name, use escaped variable:------ > f name = [text|this_could_be_${name}_long_identifier|]------ So------ > f "one" == "this_could_be_one_long_identifier"------ If you want to write something that looks like a variable but should be--- inserted as-is, escape it with another @$@:------ > f word = [text|$$my ${word} $${string}|]------ results in------ > f "funny" == "$my funny ${string}|]-module NeatInterpolation (text) where+{-|+NeatInterpolation provides a quasiquoter for producing strings+with a simple interpolation of input values.+It removes the excessive indentation from the input and+accurately manages the indentation of all lines of interpolated variables.+But enough words, the code shows it better. -import NeatInterpolation.Prelude+Consider the following declaration: +> {-# LANGUAGE QuasiQuotes #-}+>+> import NeatInterpolation+> import Data.Text (Text)+>+> f :: Text -> Text -> Text+> f a b =+> [trimming|+> function(){+> function(){+> $a+> }+> return $b+> }+> |]++Executing the following:++> main = Text.putStrLn $ f "1" "2"++will produce this (notice the reduced indentation compared to how it was+declared):++> function(){+> function(){+> 1+> }+> return 2+> }++Now let's test it with multiline string parameters:++> main = Text.putStrLn $ f+> "{\n indented line\n indented line\n}"+> "{\n indented line\n indented line\n}"++We get++> function(){+> function(){+> {+> indented line+> indented line+> }+> }+> return {+> indented line+> indented line+> }+> }++See how it neatly preserved the indentation levels of lines the+variable placeholders were at?++If you need to separate variable placeholder from the following text to+prevent treating the rest of line as variable name, use escaped variable:++> f name = [trimming|this_could_be_${name}_long_identifier|]++So++> f "one" == "this_could_be_one_long_identifier"++If you want to write something that looks like a variable but should be+inserted as-is, escape it with another @$@:++> f word = [trimming|$$my ${word} $${string}|]++results in++> f "funny" == "$my funny ${string}"+-}+module NeatInterpolation (trimming, untrimming) where++import NeatInterpolation.Prelude import Language.Haskell.TH import Language.Haskell.TH.Quote hiding (quoteExp)+import qualified Data.Text as Text+import qualified NeatInterpolation.String as String+import qualified NeatInterpolation.Parsing as Parsing -import NeatInterpolation.String-import NeatInterpolation.Parsing -import Data.Text (Text)-import qualified Data.Text as T+expQQ quoteExp = QuasiQuoter quoteExp notSupported notSupported notSupported where+ notSupported _ = fail "Quotation in this context is not supported" +{-|+Trimmed quasiquoter variation.+Same as `untrimming`, but also+removes the leading and trailing whitespace.+-}+trimming :: QuasiQuoter+trimming = expQQ (quoteExp . String.trim . String.unindent . String.tabsToSpaces) --- |--- The quasiquoter.-text :: QuasiQuoter-text = QuasiQuoter quoteExp notSupported notSupported notSupported where- notSupported _ = fail "Quotation in this context is not supported"+{-|+Untrimmed quasiquoter variation.+Unindents the quoted template and converts tabs to spaces.+-}+untrimming :: QuasiQuoter+untrimming = expQQ (quoteExp . String.unindent . String.tabsToSpaces) indentQQPlaceholder :: Int -> Text -> Text-indentQQPlaceholder indent text = case T.lines text of- head:tail -> T.intercalate (T.singleton '\n') $- head : map (T.replicate indent (T.singleton ' ') <>) tail+indentQQPlaceholder indent text = case Text.lines text of+ head:tail -> Text.intercalate (Text.singleton '\n') $+ head : map (Text.replicate indent (Text.singleton ' ') <>) tail [] -> text quoteExp :: String -> Q Exp quoteExp input =- case parseLines $ normalizeQQInput input of+ case Parsing.parseLines input of Left e -> fail $ show e- Right lines -> sigE (appE [|T.intercalate "\n"|] $ listE $ map lineExp lines)+ Right lines -> sigE (appE [|Text.intercalate "\n"|] $ listE $ map lineExp lines) [t|Text|] -lineExp :: Line -> Q Exp-lineExp (Line indent contents) =+lineExp :: Parsing.Line -> Q Exp+lineExp (Parsing.Line indent contents) = case contents of- [] -> [| T.empty |]+ [] -> [| Text.empty |] [x] -> toExp x- xs -> appE [|T.concat|] $ listE $ map toExp xs+ xs -> appE [|Text.concat|] $ listE $ map toExp xs where toExp = contentExp (fromIntegral indent) -contentExp :: Integer -> LineContent -> Q Exp-contentExp _ (LineContentText text) = appE [|T.pack|] (stringE text)-contentExp indent (LineContentIdentifier name) = do+contentExp :: Integer -> Parsing.LineContent -> Q Exp+contentExp _ (Parsing.LineContentText text) = appE [|Text.pack|] (stringE text)+contentExp indent (Parsing.LineContentIdentifier name) = do valueName <- lookupValueName name case valueName of Just valueName -> do
library/NeatInterpolation/Prelude.hs view
@@ -29,7 +29,6 @@ import Data.Foldable as Exports hiding (toList) import Data.Function as Exports hiding (id, (.)) import Data.Functor as Exports-import Data.Functor.Contravariant as Exports import Data.Functor.Identity as Exports import Data.Int as Exports import Data.IORef as Exports
library/NeatInterpolation/String.hs view
@@ -3,21 +3,18 @@ import NeatInterpolation.Prelude -normalizeQQInput :: [Char] -> [Char]-normalizeQQInput = trim . unindent' . tabsToSpaces- where- unindent' :: [Char] -> [Char]- unindent' s =- case lines s of- head:tail -> - let - unindentedHead = dropWhile (== ' ') head - minimumTailIndent = minimumIndent . unlines $ tail- unindentedTail = case minimumTailIndent of- Just indent -> map (drop indent) tail- Nothing -> tail- in unlines $ unindentedHead : unindentedTail- [] -> []+unindent :: [Char] -> [Char]+unindent s =+ case lines s of+ head : tail -> + let + unindentedHead = dropWhile (== ' ') head + minimumTailIndent = minimumIndent . unlines $ tail+ unindentedTail = case minimumTailIndent of+ Just indent -> map (drop indent) tail+ Nothing -> tail+ in unlines $ unindentedHead : unindentedTail+ [] -> [] trim :: [Char] -> [Char] trim = dropWhileRev isSpace . dropWhile isSpace@@ -25,15 +22,9 @@ dropWhileRev :: (a -> Bool) -> [a] -> [a] dropWhileRev p = foldr (\x xs -> if p x && null xs then [] else x:xs) [] -unindent :: [Char] -> [Char]-unindent s =- case minimumIndent s of- Just indent -> unlines . map (drop indent) . lines $ s- Nothing -> s- tabsToSpaces :: [Char] -> [Char]-tabsToSpaces ('\t':tail) = " " ++ tabsToSpaces tail-tabsToSpaces (head:tail) = head : tabsToSpaces tail+tabsToSpaces ('\t' : tail) = " " ++ tabsToSpaces tail+tabsToSpaces (head : tail) = head : tabsToSpaces tail tabsToSpaces [] = [] minimumIndent :: [Char] -> Maybe Int
neat-interpolation.cabal view
@@ -1,5 +1,5 @@ name: neat-interpolation-version: 0.4+version: 0.5 synopsis: A quasiquoter for neat and simple multiline text interpolation description: A quasiquoter for producing Text values with support for@@ -24,7 +24,7 @@ library hs-source-dirs: library- default-extensions: BangPatterns, BinaryLiterals, BlockArguments, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, DuplicateRecordFields, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, HexFloatLiterals, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, NumericUnderscores, OverloadedLists, OverloadedStrings, PatternGuards, PatternSynonyms, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, StrictData, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples+ default-extensions: BangPatterns, BinaryLiterals, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, DuplicateRecordFields, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedLists, OverloadedStrings, PatternGuards, PatternSynonyms, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, StrictData, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples default-language: Haskell2010 exposed-modules: NeatInterpolation@@ -41,7 +41,7 @@ test-suite test type: exitcode-stdio-1.0 hs-source-dirs: test- default-extensions: BangPatterns, BinaryLiterals, BlockArguments, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, DuplicateRecordFields, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, HexFloatLiterals, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, NumericUnderscores, OverloadedLists, OverloadedStrings, PatternGuards, PatternSynonyms, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, StrictData, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples+ default-extensions: BangPatterns, BinaryLiterals, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, DuplicateRecordFields, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedLists, OverloadedStrings, PatternGuards, PatternSynonyms, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, StrictData, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedTuples default-language: Haskell2010 main-is: Main.hs build-depends:
test/Main.hs view
@@ -15,7 +15,7 @@ [ testCase "Demo" $ let template a b = - [text|+ [trimming| function(){ function(){ $a@@ -29,14 +29,14 @@ (template a a) , testCase "Isolation" $ let- isolated name = [text|this_could_be_${name}_long_identifier|]+ isolated name = [trimming|this_could_be_${name}_long_identifier|] in assertEqual "" "this_could_be_one_long_identifier" (isolated "one") , testCase "Escaping" $ let template a b = - [text|+ [trimming| function(){ function(){ $a@@ -44,7 +44,7 @@ return "$$b" } |]- escaped name = [text|this_could_be_$$${name}$$_long_identifier|]+ escaped name = [trimming|this_could_be_$$${name}$$_long_identifier|] a = "{\n indented line\n indented line\n}" in do assertEqual ""