packages feed

language-bash 0.8.0 → 0.9.0

raw patch · 10 files changed

+266/−121 lines, 10 filesdep +directorydep +filepathdep +prettyprinterdep −prettyPVP ok

version bump matches the API change (PVP)

Dependencies added: directory, filepath, prettyprinter, tasty-golden

Dependencies removed: pretty

API changes (from Hackage documentation)

- Language.Bash.Cond: instance Language.Bash.Pretty.Pretty Language.Bash.Cond.BinaryOp
- Language.Bash.Cond: instance Language.Bash.Pretty.Pretty Language.Bash.Cond.UnaryOp
- Language.Bash.Cond: instance Language.Bash.Pretty.Pretty a => Language.Bash.Pretty.Pretty (Language.Bash.Cond.CondExpr a)
- Language.Bash.Expand: instance Language.Bash.Pretty.Pretty Language.Bash.Expand.TildePrefix
- Language.Bash.Pretty: class Pretty a
- Language.Bash.Pretty: instance (Language.Bash.Pretty.Pretty a, Language.Bash.Pretty.Pretty b) => Language.Bash.Pretty.Pretty (Data.Either.Either a b)
- Language.Bash.Pretty: instance Language.Bash.Pretty.Pretty GHC.Types.Char
- Language.Bash.Pretty: instance Language.Bash.Pretty.Pretty Text.PrettyPrint.HughesPJ.Doc
- Language.Bash.Pretty: instance Language.Bash.Pretty.Pretty a => Language.Bash.Pretty.Pretty (GHC.Base.Maybe a)
- Language.Bash.Pretty: instance Language.Bash.Pretty.Pretty a => Language.Bash.Pretty.Pretty [a]
- Language.Bash.Pretty: pretty :: Pretty a => a -> Doc
- Language.Bash.Pretty: prettyList :: Pretty a => [a] -> Doc
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.AndOr
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.Assign
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.AssignOp
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.CaseClause
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.CaseTerm
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.Command
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.HeredocOp
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.IODesc
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.List
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.ListTerm
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.Pipeline
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.RValue
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.Redir
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.RedirOp
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.ShellCommand
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.Statement
- Language.Bash.Syntax: instance Language.Bash.Pretty.Pretty Language.Bash.Syntax.WordList
- Language.Bash.Word: instance Language.Bash.Pretty.Pretty Language.Bash.Word.AltOp
- Language.Bash.Word: instance Language.Bash.Pretty.Pretty Language.Bash.Word.Direction
- Language.Bash.Word: instance Language.Bash.Pretty.Pretty Language.Bash.Word.LetterCaseOp
- Language.Bash.Word: instance Language.Bash.Pretty.Pretty Language.Bash.Word.ParamSubst
- Language.Bash.Word: instance Language.Bash.Pretty.Pretty Language.Bash.Word.Parameter
- Language.Bash.Word: instance Language.Bash.Pretty.Pretty Language.Bash.Word.ProcessSubstOp
- Language.Bash.Word: instance Language.Bash.Pretty.Pretty Language.Bash.Word.Span
+ Language.Bash.Cond: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Cond.BinaryOp
+ Language.Bash.Cond: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Cond.UnaryOp
+ Language.Bash.Cond: instance Data.Text.Prettyprint.Doc.Internal.Pretty a => Data.Text.Prettyprint.Doc.Internal.Pretty (Language.Bash.Cond.CondExpr a)
+ Language.Bash.Expand: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Expand.TildePrefix
+ Language.Bash.Pretty: ($+$) :: Doc ann -> Doc ann -> Doc ann
+ Language.Bash.Pretty: ($++$) :: Doc ann -> Doc ann -> Doc ann
+ Language.Bash.Pretty: (<++>) :: Doc ann -> Doc ann -> Doc ann
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.AndOr
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.Assign
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.AssignOp
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.CaseClause
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.CaseTerm
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.Command
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.HeredocOp
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.IODesc
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.List
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.ListTerm
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.Pipeline
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.RValue
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.Redir
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.RedirOp
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.ShellCommand
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.Statement
+ Language.Bash.Syntax: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Syntax.WordList
+ Language.Bash.Syntax: instance GHC.Base.Monoid (Language.Bash.Syntax.BashDoc ann)
+ Language.Bash.Syntax: instance GHC.Base.Semigroup (Language.Bash.Syntax.BashDoc ann)
+ Language.Bash.Syntax: instance Language.Bash.Syntax.ToBashDoc Language.Bash.Syntax.AndOr
+ Language.Bash.Syntax: instance Language.Bash.Syntax.ToBashDoc Language.Bash.Syntax.Command
+ Language.Bash.Syntax: instance Language.Bash.Syntax.ToBashDoc Language.Bash.Syntax.ListTerm
+ Language.Bash.Syntax: instance Language.Bash.Syntax.ToBashDoc Language.Bash.Syntax.Pipeline
+ Language.Bash.Syntax: instance Language.Bash.Syntax.ToBashDoc Language.Bash.Syntax.Statement
+ Language.Bash.Word: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Word.AltOp
+ Language.Bash.Word: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Word.Direction
+ Language.Bash.Word: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Word.LetterCaseOp
+ Language.Bash.Word: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Word.ParamSubst
+ Language.Bash.Word: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Word.Parameter
+ Language.Bash.Word: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Word.ProcessSubstOp
+ Language.Bash.Word: instance Data.Text.Prettyprint.Doc.Internal.Pretty Language.Bash.Word.Span
+ Language.Bash.Word: instance Data.Text.Prettyprint.Doc.Internal.Pretty [Language.Bash.Word.Word]
- Language.Bash.Cond: And :: (CondExpr a) -> (CondExpr a) -> CondExpr a
+ Language.Bash.Cond: And :: CondExpr a -> CondExpr a -> CondExpr a
- Language.Bash.Cond: Not :: (CondExpr a) -> CondExpr a
+ Language.Bash.Cond: Not :: CondExpr a -> CondExpr a
- Language.Bash.Cond: Or :: (CondExpr a) -> (CondExpr a) -> CondExpr a
+ Language.Bash.Cond: Or :: CondExpr a -> CondExpr a -> CondExpr a
- Language.Bash.Syntax: Cond :: (CondExpr Word) -> ShellCommand
+ Language.Bash.Syntax: Cond :: CondExpr Word -> ShellCommand
- Language.Bash.Syntax: If :: List -> List -> (Maybe List) -> ShellCommand
+ Language.Bash.Syntax: If :: List -> List -> Maybe List -> ShellCommand
- Language.Bash.Word: Parameter :: String -> (Maybe Word) -> Parameter
+ Language.Bash.Word: Parameter :: String -> Maybe Word -> Parameter

Files

.gitignore view
@@ -4,3 +4,4 @@ *.o *.hi .stack-work+tests/pretty/*.out
language-bash.cabal view
@@ -1,5 +1,5 @@ name:               language-bash-version:            0.8.0+version:            0.9.0 category:           Language license:            BSD3 license-file:       LICENSE@@ -42,10 +42,10 @@     Language.Bash.Parse.Internal    build-depends:-    base         >= 4.6 && < 5,-    parsec       >= 3.0 && < 4.0,-    pretty       >= 1.0 && < 2.0,-    transformers >= 0.2 && < 0.6+    base          >= 4.6 && < 5,+    parsec        >= 3.0 && < 4.0,+    prettyprinter >= 1.2 && < 2.0,+    transformers  >= 0.2 && < 0.6    ghc-options: -Wall @@ -56,11 +56,14 @@    build-depends:     base,+    directory,+    filepath,     language-bash,     parsec,     process,     QuickCheck,     tasty,+    tasty-golden,     tasty-quickcheck,     tasty-hunit,     tasty-expected-failure
src/Language/Bash/Cond.hs view
@@ -22,10 +22,9 @@ import GHC.Generics           (Generic) import Text.Parsec            hiding ((<|>), token) import Text.Parsec.Expr       hiding (Operator)-import Text.PrettyPrint       hiding (parens)+import Data.Text.Prettyprint.Doc (Pretty(..), (<+>))  import Language.Bash.Operator-import Language.Bash.Pretty  -- | Bash conditional expressions. data CondExpr a
src/Language/Bash/Expand.hs view
@@ -7,15 +7,16 @@     , splitWord     ) where -import Prelude hiding ((<>), Word)+import Prelude hiding (Word)  import Control.Applicative import Control.Monad import Data.Char+import Data.Monoid            ((<>)) import Text.Parsec.Combinator hiding (optional, manyTill) import Text.Parsec.Prim       hiding ((<|>), many, token) import Text.Parsec.String     ()-import Text.PrettyPrint       hiding (char)+import Data.Text.Prettyprint.Doc (Pretty(..))  import Language.Bash.Pretty import Language.Bash.Word     hiding (prefix)@@ -180,10 +181,10 @@  instance Pretty TildePrefix where     pretty Home         = "~"-    pretty (UserHome s) = "~" <> text s+    pretty (UserHome s) = "~" <> pretty s     pretty PWD          = "~+"     pretty OldPWD       = "~-"-    pretty (Dirs n)     = "~" <> int n+    pretty (Dirs n)     = "~" <> pretty n  -- | Strip the tilde prefix of a word, if any. tildePrefix :: Word -> Maybe (TildePrefix, Word)
src/Language/Bash/Operator.hs view
@@ -8,9 +8,7 @@  import Control.Applicative import Data.Foldable-import Text.PrettyPrint--import Language.Bash.Pretty+import Data.Text.Prettyprint.Doc (Doc, pretty)  -- | String operators. class Eq a => Operator a where@@ -25,5 +23,5 @@ selectOperator p = select p operatorTable  -- | Render an operator.-prettyOperator :: Operator a => a -> Doc+prettyOperator :: Operator a => a -> Doc ann prettyOperator = pretty . flip lookup operatorTable
src/Language/Bash/Parse.hs view
@@ -58,13 +58,13 @@     duck = do         u <- getState         case postHeredoc u of-            Nothing -> () <$ line <* optional (char '\n')+            Nothing -> () <$ line             Just s  -> () <$ setParserState s         h <- unlines <$> heredocLines         s <- getParserState         return (h, s) -    line = many (satisfy (/= '\n'))+    line = many (satisfy (/= '\n')) <* optional (char '\n')      heredocLines = [] <$ eof                <|> nextLine@@ -73,7 +73,7 @@         l <- process <$> line         if l == end             then return []-            else (l :) <$ optional (char '\n') <*> heredocLines+            else (l :) <$> heredocLines  -- | Parse a newline, skipping any here documents. newline :: Parser String@@ -84,6 +84,7 @@         Nothing -> return ()         Just s  -> () <$ setParserState s     setState $ U Nothing+    skipSpace     return "\n"  -- | Parse a list terminator.
src/Language/Bash/Pretty.hs view
@@ -1,38 +1,41 @@ -- | Pretty-printing of Bash scripts. This tries to stay close to the format -- used by the Bash builtin @declare -f@.-module Language.Bash.Pretty-    ( Pretty(..)-    , prettyText-    ) where--import Text.PrettyPrint---- | A class of types which may be pretty-printed.-class Pretty a where-    -- | Pretty-print to a 'Doc'.-    pretty     :: a -> Doc--    -- | Pretty-print a list. By default, this separates each element with-    -- a space using 'hsep'.-    prettyList :: [a] -> Doc-    prettyList = hsep . map pretty--instance Pretty a => Pretty [a] where-    pretty = prettyList+module Language.Bash.Pretty where -instance Pretty Doc where-    pretty = id+import Data.Text.Prettyprint.Doc+import Data.Text.Prettyprint.Doc.Internal+import Data.Text.Prettyprint.Doc.Render.String -instance Pretty Char where-    pretty c   = text [c]-    prettyList = text+-- | @x $+$ y@ concatenates @x@ and @y@ with a 'line' in between+($+$) :: Doc ann -> Doc ann -> Doc ann+x $+$ y = x <> line <> y -instance Pretty a => Pretty (Maybe a) where-    pretty = maybe empty pretty+-- | Behaves like '$+$' except that if one of the documents was empty we do not concatenate at all.+-- 'mempty' is the identity of '$+$':+--+-- prop> x $++$ mempty == x+--+-- and+--+-- prop> mempty $++$ y == y+($++$) :: Doc ann -> Doc ann -> Doc ann+Empty $++$ y     = y+x     $++$ Empty = x+x     $++$ y     = x <> line <> y -instance (Pretty a, Pretty b) => Pretty (Either a b) where-    pretty = either pretty pretty+-- | Behaves like '<+>' except that if one of the documents was empty we do not concatenate at all.+-- 'mempty' is the identity of '<+>':+--+-- prop> x <++> mempty == x+--+-- and+--+-- prop> mempty <++> y == y+(<++>) :: Doc ann -> Doc ann -> Doc ann+Empty <++> y     = y+x     <++> Empty = x+x     <++> y     = x <+> y  -- | Pretty-print to a 'String'. prettyText :: Pretty a => a -> String-prettyText = render . pretty+prettyText = renderString . layoutPretty defaultLayoutOptions . pretty
src/Language/Bash/Syntax.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE DeriveDataTypeable, OverloadedStrings, RecordWildCards, DeriveGeneric #-}+{-# LANGUAGE DeriveDataTypeable, FlexibleInstances, OverloadedStrings, RecordWildCards, DeriveGeneric #-} -- | Shell script types. module Language.Bash.Syntax     (@@ -25,33 +25,109 @@     , RValue(..)     ) where -import Prelude hiding ((<>), Word)+import Prelude hiding (Word)  import Data.Data        (Data)+import Data.List        (intersperse)+import Data.Semigroup   (Semigroup(..)) import Data.Typeable    (Typeable) import GHC.Generics     (Generic)-import Text.PrettyPrint+import Data.Text.Prettyprint.Doc (Doc, Pretty(..), (<+>), hardline, hcat, hsep, indent, nest, nesting, punctuate, vcat)+import Data.Text.Prettyprint.Doc.Internal (Doc(Empty))  import Language.Bash.Cond     (CondExpr) import Language.Bash.Operator import Language.Bash.Pretty import Language.Bash.Word +-- | The BashDoc monoid is used for building Statements, AndOr or Pipelines.+-- Consider the following situation: We have the following command+--+-- > cat <<EOF+-- > some here doc+-- > EOF+--+-- and we want to pipe its output to another arbitrary command @cmd@.+-- We want this pipeline to look like this:+--+-- > cat <<EOF |+-- > some here doc+-- > EOF+-- > cmd+--+-- Note the @|@ at the end of the first line: If we were simply pretty printing the @cat@ command we had no idea where to insert the pipe symbol.+-- And that's the purpose of BashDoc: We store possible suffixes to such lines, commands and the here documents attached to them separately and do the concatenation in the Semigroup instance of BashDoc.+data BashDoc ann = BashDoc+    (Doc ann) -- ^ The head: This is stuff we want to put before the line break and here documents+    (Doc ann) -- ^ The tail: Everthing which follows the here documents+    (Doc ann) -- ^ Collected here documents++instance Semigroup (BashDoc ann) where+    BashDoc Empty Empty Empty <> y = y+    x <> BashDoc Empty Empty Empty = x+    BashDoc h1 t1 Empty <> BashDoc h2 t2 hds2 = BashDoc h1 (t1 <> h2 <++> t2) hds2+    BashDoc h1 t1 hds1  <> BashDoc h2 t2 hds2 = BashDoc h1 (t1 <> noIndent (h2 $++$ hds1) $++$ t2) hds2+        where+            noIndent doc = nesting $ \i -> nest (- i) doc++instance Monoid (BashDoc ann) where+    mempty = BashDoc mempty mempty mempty+    mappend = (<>)++docOp :: Doc ann -> BashDoc ann+docOp xs = BashDoc xs mempty mempty++prettyBashDoc :: BashDoc ann -> Doc ann+prettyBashDoc (BashDoc h t hds) = h <++> t $++$ hds++-- | A utility class for pretty printing without heredocs+class ToBashDoc a where+    toBashDoc :: a -> BashDoc ann++prettyHeredocs :: [Redir] -> Doc ann+prettyHeredocs [] = mempty+prettyHeredocs rs = mconcat $ intersperse hardline $ map prettyHeredoc rs+    where+        prettyHeredoc Heredoc{..} = pretty hereDocument <> pretty heredocDelim+        prettyHeredoc _ = mempty+ -- | Indent by 4 columns.-indent :: Pretty a => a -> Doc-indent = nest 4 . pretty+indent' :: Doc ann -> Doc ann+indent' = indent 4 --- | Render a @do...done@ block.-doDone :: Pretty a => Doc -> a -> Doc-doDone header body = header <+> "do" $+$ indent body $+$ "done"+-- | Render a conditional command with a block.+prettyBlock :: Doc ann -> Doc ann -> Doc ann -> Doc ann -> Doc ann -> Doc ann+prettyBlock pre cond bs block be = pre <+> cond <+> bs $+$ block $+$ be +-- | Render a conditional command with a block whose condition is a list of statements.+prettyBlockList :: Doc ann -> List -> Doc ann -> Doc ann -> Doc ann -> Doc ann+prettyBlockList pre l bs block be+    | hasHeredoc l = pre <+> pretty l $+$ bs $+$ block $+$ be+    | otherwise    = prettyBlock pre (pretty l) bs block be++-- | Does the last statement in a list have a here doc attached?+hasHeredoc :: List -> Bool+hasHeredoc (List []) = False+hasHeredoc (List xs) = let+    Statement l _ = last xs+    BashDoc _ _ hds = toBashDoc l+    in case hds of+        Empty -> False+        _     -> True+ -- | A Bash command with redirections. data Command = Command ShellCommand [Redir]     deriving (Data, Eq, Read, Show, Typeable, Generic)  instance Pretty Command where-    pretty (Command c rs) = pretty c <+> pretty rs+    pretty = prettyBashDoc . toBashDoc +instance ToBashDoc Command where+    toBashDoc (Command c rs) = BashDoc mempty (pretty c <++> pretty rs) (prettyHeredocs $ filter isHeredoc rs)+        where+            isHeredoc Heredoc{..} = True+            isHeredoc _ = False+ -- | A Bash command. data ShellCommand       -- | A simple command consisting of assignments followed by words.@@ -91,36 +167,37 @@     deriving (Data, Eq, Read, Show, Typeable, Generic)  instance Pretty ShellCommand where-    pretty (SimpleCommand as ws)  = pretty as <+> pretty ws-    pretty (AssignBuiltin w args) = pretty w <+> pretty args+    pretty (SimpleCommand as ws)  = pretty as <++> pretty ws+    pretty (AssignBuiltin w args) = pretty w <++> hsep (map (either pretty pretty) args)     pretty (FunctionDef name l) =-        text name <+> "()" $+$ pretty (Group l)+        pretty name <+> "()" $+$ pretty (Group l)     pretty (Coproc name c) =-        "coproc" <+> text name <+> pretty c+        "coproc" <+> pretty name <+> pretty c     pretty (Subshell l) =         "(" <+> pretty l <+> ")"     pretty (Group l) =-        "{" $+$ indent l $+$ "}"+        "{" $+$ indent' (pretty l) $+$ "}"     pretty (Arith s) =-        "((" <> text s <> "))"+        "((" <> pretty s <> "))"     pretty (Cond e) =         "[[" <+> pretty e <+> "]]"     pretty (For w ws l) =-        doDone ("for" <+> pretty w <+> pretty ws <> ";") l+        prettyBlock "for" (pretty w <+> pretty ws <> ";") "do" (indent' $ pretty l) "done"     pretty (ArithFor s l) =-        doDone ("for" <+> "((" <> text s <> "))") l+        prettyBlock "for" ("((" <> pretty s <> "))") "do" (indent' $ pretty l) "done"     pretty (Select w ws l) =-        doDone ("select" <+> pretty w <+> pretty ws <> ";") l+        prettyBlock "select" (pretty w <++> pretty ws <> ";") "do" (indent' $ pretty l) "done"     pretty (Case w cs) =-        "case" <+> pretty w <+> "in" $+$ (vcat $ map indent cs) $+$ "esac"+        prettyBlock "case" (pretty w) "in" (vcat $ map (indent' . pretty) cs) "esac"     pretty (If p t f) =-        "if" <+> pretty p <+> "then" $+$ indent t $+$-        pretty (fmap (\l -> "else" $+$ indent l) f) $+$+        prettyBlockList "if" p "then"+        (indent' (pretty t) $++$ (maybe mempty (\l -> "else" $+$ indent' (pretty l)) f)+        )         "fi"     pretty (Until p l) =-        doDone ("until" <+> pretty p) l+        prettyBlockList "until" p "do" (indent' $ pretty l) "done"     pretty (While p l) =-        doDone ("while" <+> pretty p) l+        prettyBlockList "while" p "do" (indent' $ pretty l) "done"  -- | A word list or @\"$\@\"@. data WordList@@ -129,7 +206,7 @@     deriving (Data, Eq, Read, Show, Typeable, Generic)  instance Pretty WordList where-    pretty Args          = empty+    pretty Args          = mempty     pretty (WordList ws) = "in" <+> pretty ws  -- | A single case clause.@@ -139,8 +216,8 @@ instance Pretty CaseClause where     pretty (CaseClause ps l term) =         hcat (punctuate " | " (map pretty ps)) <> ")" $+$-        indent l $+$-        (indent $ pretty term)+        indent' (pretty l) $+$+        (indent' $ pretty term)  -- | A case clause terminator. data CaseTerm@@ -186,15 +263,11 @@         pretty redirDesc <> pretty redirOp <> pretty redirTarget     pretty Heredoc{..} =         pretty heredocOp <>-        text (if heredocDelimQuoted+        pretty (if heredocDelimQuoted               then "'" ++ heredocDelim ++ "'"-              else heredocDelim) <> "\n" <>-        pretty hereDocument <> text heredocDelim <> "\n"+              else heredocDelim) -    prettyList = foldr f empty-      where-        f a@Redir{}   b = pretty a <+> b-        f a@Heredoc{} b = pretty a <> b+    prettyList = hsep . map pretty  -- | A redirection file descriptor. data IODesc@@ -205,8 +278,8 @@     deriving (Data, Eq, Read, Show, Typeable, Generic)  instance Pretty IODesc where-    pretty (IONumber n) = int n-    pretty (IOVar n)    = "{" <> text n <> "}"+    pretty (IONumber n) = pretty n+    pretty (IOVar n)    = "{" <> pretty n <> "}"  -- | A redirection operator. data RedirOp@@ -253,14 +326,16 @@     deriving (Data, Eq, Read, Show, Typeable, Generic)  instance Pretty Statement where-    pretty (Statement l Sequential)   = pretty l <> ";"-    pretty (Statement l Asynchronous) = pretty l <+> "&"+    pretty = prettyBashDoc . toBashDoc -    prettyList = foldr f empty+    prettyList = foldr f mempty       where-        f a@(Statement _ Sequential)   b = pretty a $+$ b-        f a@(Statement _ Asynchronous) b = pretty a <+> b+        f a@(Statement _ Sequential)   b = pretty a $++$ b+        f a@(Statement _ Asynchronous) b = pretty a <++> b +instance ToBashDoc Statement where+    toBashDoc (Statement l lt) = toBashDoc l <> toBashDoc lt+ -- | A statement terminator. data ListTerm     = Sequential    -- ^ @;@@@ -277,6 +352,10 @@ instance Pretty ListTerm where     pretty = prettyOperator +instance ToBashDoc ListTerm where+    toBashDoc Sequential = docOp ";"+    toBashDoc Asynchronous = docOp "&"+ -- | A right-associative list of pipelines. data AndOr       -- | The last pipeline of a list.@@ -288,10 +367,13 @@     deriving (Data, Eq, Read, Show, Typeable, Generic)  instance Pretty AndOr where-    pretty (Last p)  = pretty p-    pretty (And p a) = pretty p <+> "&&" <+> pretty a-    pretty (Or p a)  = pretty p <+> "||" <+> pretty a+    pretty = prettyBashDoc . toBashDoc +instance ToBashDoc AndOr where+    toBashDoc (Last p)  = toBashDoc p+    toBashDoc (And p a) = toBashDoc p <> docOp " &&" <> toBashDoc a+    toBashDoc (Or p a)  = toBashDoc p <> docOp " ||" <> toBashDoc a+ -- | A (possibly timed or inverted) pipeline, linked with @|@ or @|&@. data Pipeline = Pipeline     { -- | 'True' if the pipeline is timed with @time@.@@ -307,18 +389,25 @@     } deriving (Data, Eq, Read, Show, Typeable, Generic)  instance Pretty Pipeline where-    pretty Pipeline{..} =-        (if timed      then "time" else empty) <+>-        (if timedPosix then "-p"   else empty) <+>-        (if inverted   then "!"    else empty) <+>-        hcat (punctuate " | " (map pretty commands))+    pretty = prettyBashDoc . toBashDoc +instance ToBashDoc Pipeline where+    toBashDoc Pipeline{..} = let+        timed'      = if timed      then "time" else mempty+        timedPosix' = if timedPosix then "-p"   else mempty+        inverted'   = if inverted   then "!"    else mempty+        space       = if timed || timedPosix || inverted then " " else mempty+        prefix = BashDoc mempty (timed' <++> timedPosix' <++> inverted' <> space) mempty+        in prefix <> mconcat (intersperse (docOp " |") (map toBashDoc commands))+ -- | An assignment. data Assign = Assign Parameter AssignOp RValue     deriving (Data, Eq, Read, Show, Typeable, Generic)  instance Pretty Assign where     pretty (Assign lhs op rhs) = pretty lhs <> pretty op <> pretty rhs++    prettyList = hsep . map pretty  -- | An assignment operator. data AssignOp
src/Language/Bash/Word.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE     DeriveDataTypeable   , DeriveGeneric+  , FlexibleInstances   , OverloadedStrings   , RecordWildCards   , TypeSynonymInstances@@ -25,15 +26,16 @@     , unquote     ) where -import Prelude hiding ((<>), Word)+import Prelude hiding (Word)  import           Data.Data        (Data)+import           Data.Monoid      ((<>)) import           Data.Typeable    (Typeable) import           GHC.Generics     (Generic)-import           Text.PrettyPrint+import           Data.Text.Prettyprint.Doc (Doc, Pretty(..), hcat, hsep, layoutCompact)+import           Data.Text.Prettyprint.Doc.Render.String (renderString)  import           Language.Bash.Operator-import           Language.Bash.Pretty  -- | A Bash word, broken up into logical spans. type Word = [Span]@@ -66,28 +68,31 @@     deriving (Data, Eq, Read, Show, Typeable, Generic)  instance Pretty Span where-    pretty (Char c)           = char c-    pretty (Escape c)         = "\\" <> char c+    pretty (Char c)           = pretty c+    pretty (Escape c)         = "\\" <> pretty c     pretty (Single w)         = "\'" <> pretty w <> "\'"     pretty (Double w)         = "\"" <> pretty w <> "\""     pretty (ANSIC w)          = "$\'" <> pretty w <> "\'"     pretty (Locale w)         = "$\"" <> pretty w <> "\""     pretty (Backquote w)      = "`" <> pretty w <> "`"     pretty (ParamSubst s)     = pretty s-    pretty (ArithSubst s)     = "$((" <> text s <> "))"-    pretty (CommandSubst s)   = "$(" <> text s <> ")"-    pretty (ProcessSubst c s) = pretty c <> "(" <> text s <> ")"+    pretty (ArithSubst s)     = "$((" <> pretty s <> "))"+    pretty (CommandSubst s)   = "$(" <> pretty s <> ")"+    pretty (ProcessSubst c s) = pretty c <> "(" <> pretty s <> ")"      prettyList = hcat . map pretty +instance {-# OVERLAPS #-} Pretty [Word] where+    pretty = hsep . map pretty+ -- | A parameter name an optional subscript. data Parameter = Parameter String (Maybe Word)     deriving (Data, Eq, Read, Show, Typeable, Generic)  instance Pretty Parameter where-    pretty (Parameter s sub) = text s <> subscript sub+    pretty (Parameter s sub) = pretty s <> subscript sub       where-        subscript Nothing  = empty+        subscript Nothing  = mempty         subscript (Just w) = "[" <> pretty w <> "]"  -- | A parameter substitution.@@ -163,33 +168,33 @@         }     deriving (Data, Eq, Read, Show, Typeable, Generic) -prettyParameter :: Bool -> Parameter -> Doc -> Doc+prettyParameter :: Bool -> Parameter -> Doc ann -> Doc ann prettyParameter bang param suffix =-    "${" <> (if bang then "!" else empty) <> pretty param <> suffix <> "}"+    "${" <> (if bang then "!" else mempty) <> pretty param <> suffix <> "}" -twiceWhen :: Bool -> Doc -> Doc+twiceWhen :: Bool -> Doc ann -> Doc ann twiceWhen False d = d twiceWhen True  d = d <> d  instance Pretty ParamSubst where     pretty Bare{..}       = "$" <> pretty parameter-    pretty Brace{..}      = prettyParameter indirect parameter empty+    pretty Brace{..}      = prettyParameter indirect parameter mempty     pretty Alt{..}        = prettyParameter indirect parameter $-        (if testNull then ":" else empty) <>+        (if testNull then ":" else mempty) <>         pretty altOp <>         pretty altWord     pretty Substring{..}  = prettyParameter indirect parameter $         ":" <> pretty subOffset <>-        (if null subLength then empty else ":") <> pretty subLength-    pretty Prefix{..}     = "${!" <> text prefix <> char modifier <> "}"-    pretty Indices{..}    = prettyParameter True parameter empty+        (if null subLength then mempty else ":") <> pretty subLength+    pretty Prefix{..}     = "${!" <> pretty prefix <> pretty modifier <> "}"+    pretty Indices{..}    = prettyParameter True parameter mempty     pretty Length{..}     = "${#" <> pretty parameter <> "}"     pretty Delete{..}     = prettyParameter indirect parameter $         twiceWhen longest (pretty deleteDirection) <>         pretty pattern     pretty Replace{..}    = prettyParameter indirect parameter $         "/" <>-        (if replaceAll then "/" else empty) <>+        (if replaceAll then "/" else mempty) <>         pretty replaceDirection <>         pretty pattern <>         "/" <>@@ -260,12 +265,12 @@  -- | Remove all quoting characters from a word. unquote :: Word -> String-unquote = render . unquoteWord+unquote = renderString . layoutCompact . unquoteWord   where     unquoteWord = hcat . map unquoteSpan -    unquoteSpan (Char c)   = char c-    unquoteSpan (Escape c) = char c+    unquoteSpan (Char c)   = pretty c+    unquoteSpan (Escape c) = pretty c     unquoteSpan (Single w) = unquoteWord w     unquoteSpan (Double w) = unquoteWord w     unquoteSpan (ANSIC w)  = unquoteWord w
tests/Tests.hs view
@@ -2,10 +2,13 @@  import           Control.Applicative ((<$>)) import           Control.Monad+import           System.Directory (listDirectory)+import           System.FilePath ((</>), (-<.>), takeExtension) import           System.Process           (readProcess) import           Test.QuickCheck import           Test.QuickCheck.Monadic  as QCM import           Test.Tasty+import           Test.Tasty.Golden import           Test.Tasty.QuickCheck import           Test.Tasty.HUnit import           Test.Tasty.ExpectedFailure (expectFail)@@ -14,6 +17,7 @@   import qualified Language.Bash.Parse      as Parse+import           Language.Bash.Pretty     (prettyText) import           Language.Bash.Syntax import qualified Language.Bash.Cond       as Cond import           Language.Bash.Word@@ -58,6 +62,23 @@ properties :: TestTree properties = testGroup "Properties" [testProperty "brace expansion" prop_expandsLikeBash] +discoverPrettyTests :: FilePath -> IO TestTree+discoverPrettyTests fp = do+    fs <- listDirectory fp+    let shFs = filter (\fp' -> takeExtension fp' == ".sh") fs+    return $ testGroup "Pretty printing" $ map (\shFn -> testPretty shFn (fp </> shFn)) shFs++testPretty :: TestName -> FilePath -> TestTree+testPretty name shFp = do+    let outFp    = shFp -<.> "out"+        goldenFp = shFp -<.> "golden"+    goldenVsFileDiff name (\ref new -> ["diff", "-u", ref, new]) goldenFp outFp $ do+        cnt <- readFile shFp+        parsed <- case Parse.parse shFp cnt of+            Left err -> assertFailure $ show err+            Right parsed -> return parsed+        writeFile outFp $ prettyText parsed ++ "\n"+ testMatches :: (Eq a, Show a) => TestName -> Either Text.Parsec.Error.ParseError a -> a -> TestTree testMatches name parsed expected = testCase name $            case parsed of@@ -93,6 +114,15 @@                   heredocDelimQuoted = False,                   hereDocument = [Char 'a',Char 's',Char 'd',Escape '`',                                   Char '\n']}])+  , tp "cat <<EOF\nfoo\nEOF\n  cat" $ wrapCommands+      [ Command+          (SimpleCommand [] [stringToWord "cat"])+          [Heredoc {heredocOp = Here,+                    heredocDelim = "EOF",+                    heredocDelimQuoted = False,+                    hereDocument = [Char 'f',Char 'o',Char 'o',Char '\n']}]+      , Command (SimpleCommand [] [stringToWord "cat"]) []+      ]   , tp "cat <<\"EOF\"\nasd\\`\nEOF" $ wrapCommand        (Command         (SimpleCommand [] [stringToWord "cat"])@@ -100,6 +130,17 @@                   heredocDelim = "EOF",                   heredocDelimQuoted = True,                   hereDocument = stringToWord "asd\\`\n"}])+  , tp "cat <<EOF1 <<EOF2\nt1\nEOF1\nt2\nEOF2" $ wrapCommand+        (Command+        (SimpleCommand [] [stringToWord "cat"])+        [Heredoc {heredocOp = Here,+                  heredocDelim = "EOF1",+                  heredocDelimQuoted = False,+                  hereDocument = stringToWord "t1\n"}+        ,Heredoc {heredocOp = Here,+                  heredocDelim = "EOF2",+                  heredocDelimQuoted = False,+                  hereDocument = stringToWord "t2\n"}])   , tp "echo $((2 + 2))" $ wrapCommand        (Command         (SimpleCommand [] [stringToWord "echo", [ArithSubst "2 + 2"]])@@ -150,8 +191,12 @@ failingtests = testGroup "Failing tests" (map expectFail   []) -tests :: TestTree-tests = testGroup "Tests" [properties, unittests, failingtests]- main :: IO ()-main = defaultMain tests+main = do+    pptests <- discoverPrettyTests "tests/pretty"+    defaultMain $ testGroup "Tests"+        [ properties+        , unittests+        , failingtests+        , pptests+        ]