packages feed

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 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 ""