module Darcs.Util.Printer
(
Doc(Doc,unDoc)
, empty, (<>), (<?>), (<+>), ($$), vcat, vsep, hcat, hsep
, minus, newline, plus, space, backslash, lparen, rparen
, parens
, text
, hiddenText
, invisibleText
, wrapText, quoted
, userchunk, packedString
, prefix
, hiddenPrefix
, insertBeforeLastline
, prefixLines
, invisiblePS, userchunkPS
, RenderMode(..)
, renderString, renderStringWith, renderPS, renderPSWith
, renderPSs, renderPSsWith
, Printers
, Printers'(..)
, Printer
, simplePrinters, invisiblePrinter, simplePrinter
, Printable(..)
, doc
, printable, invisiblePrintable, hiddenPrintable, userchunkPrintable
, Color(..)
, blueText, redText, greenText, magentaText, cyanText
, colorText
, lineColor
, hPutDoc, hPutDocLn, putDoc, putDocLn
, hPutDocWith, hPutDocLnWith, putDocWith, putDocLnWith
, hPutDocCompr
, debugDocLn
, ePutDocLn
, errorDoc
, unsafeText, unsafeBoth, unsafeBothText, unsafeChar
, unsafePackedString
) where
import Prelude ()
import Darcs.Prelude
import Control.Exception ( throwIO, ErrorCall(..) )
import Data.String ( IsString(..) )
import Data.List (intersperse)
import Data.Monoid ( (<>) )
import GHC.Stack ( currentCallStack )
import System.IO (Handle, stdout, stderr, hPutStr)
import System.IO.Unsafe ( unsafePerformIO )
import qualified Data.ByteString as B (ByteString, hPut, concat)
import qualified Data.ByteString.Char8 as BC (unpack, pack, singleton)
import Darcs.Util.ByteString ( linesPS, encodeLocale, gzWriteHandle )
import Darcs.Util.Global ( debugMessage )
data Printable = S !String
| PS !B.ByteString
| Both !String !B.ByteString
spaceP :: Printable
spaceP = Both " " (BC.singleton ' ')
newlineP :: Printable
newlineP = S "\n"
space :: Doc
space = unsafeBoth " " (BC.singleton ' ')
newline :: Doc
newline = unsafeChar '\n'
minus :: Doc
minus = unsafeBoth "-" (BC.singleton '-')
plus :: Doc
plus = unsafeBoth "+" (BC.singleton '+')
backslash :: Doc
backslash = unsafeBoth "\\" (BC.singleton '\\')
lparen :: Doc
lparen = unsafeBoth "(" (BC.singleton '(')
rparen :: Doc
rparen = unsafeBoth ")" (BC.singleton ')')
parens :: Doc -> Doc
parens d = lparen <> d <> rparen
errorDoc :: Doc -> a
errorDoc x = unsafePerformIO $ do
stack <- currentCallStack
throwIO $ ErrorCall $ renderString Encode $ x $$ vcat (map text stack)
putDocWith :: Printers -> Doc -> IO ()
putDocWith prs = hPutDocWith prs Encode stdout
putDocLnWith :: Printers -> Doc -> IO ()
putDocLnWith prs = hPutDocLnWith prs Encode stdout
putDoc :: Doc -> IO ()
putDoc = hPutDoc Encode stdout
putDocLn :: Doc -> IO ()
putDocLn = hPutDocLn Encode stdout
ePutDocLn :: Doc -> IO ()
ePutDocLn = hPutDocLn Encode stderr
hPutDocWith :: Printers -> RenderMode -> Handle -> Doc -> IO ()
hPutDocWith prs target h d = hPrintPrintables target h (renderWith (prs h) d)
hPutDocLnWith :: Printers -> RenderMode -> Handle -> Doc -> IO ()
hPutDocLnWith prs target h d = hPutDocWith prs target h (d <?> newline)
hPutDoc :: RenderMode -> Handle -> Doc -> IO ()
hPutDoc = hPutDocWith simplePrinters
hPutDocLn :: RenderMode -> Handle -> Doc -> IO ()
hPutDocLn = hPutDocLnWith simplePrinters
hPutDocCompr :: RenderMode -> Handle -> Doc -> IO ()
hPutDocCompr target h = gzWriteHandle h . renderPSs target
debugDocLn :: Doc -> IO ()
debugDocLn = debugMessage . renderString Standard
hPrintPrintables :: RenderMode -> Handle -> [Printable] -> IO ()
hPrintPrintables target h = mapM_ (hPrintPrintable target h)
hPrintPrintable :: RenderMode -> Handle -> Printable -> IO ()
hPrintPrintable Standard h (S ps) = hPutStr h ps
hPrintPrintable Encode h (S ps) = B.hPut h (encodeLocale ps)
hPrintPrintable Standard h (PS ps) = B.hPut h ps
hPrintPrintable Encode h (PS ps) = B.hPut h ps
hPrintPrintable Standard h (Both _ ps) = B.hPut h ps
hPrintPrintable Encode h (Both _ ps) = B.hPut h ps
newtype Doc = Doc { unDoc :: St -> Document }
instance IsString Doc where
fromString = text
data RenderMode =
Encode
| Standard
data St = St { printers :: !Printers',
currentPrefix :: !([Printable] -> [Printable]) }
type Printers = Handle -> Printers'
data Printers' = Printers {colorP :: !(Color -> Printer),
invisibleP :: !Printer,
hiddenP :: !Printer,
userchunkP :: !Printer,
defP :: !Printer,
lineColorT :: !(Color -> Doc -> Doc),
lineColorS :: !([Printable] -> [Printable])
}
type Printer = Printable -> St -> Document
data Color = Blue | Red | Green | Cyan | Magenta
data Document = Document ([Printable] -> [Printable])
| Empty
renderString :: RenderMode -> Doc -> String
renderString = renderStringWith simplePrinters'
renderStringWith :: Printers' -> RenderMode -> Doc -> String
renderStringWith prs target d = concatMap (toString target) $ renderWith prs d
where toString Standard (S s) = s
toString Encode (S s) = BC.unpack . encodeLocale $ s
toString Standard (PS ps) = BC.unpack ps
toString Encode (PS ps) = BC.unpack ps
toString Standard (Both s _) = s
toString Encode (Both s _) = BC.unpack . encodeLocale $ s
renderPS :: RenderMode -> Doc -> B.ByteString
renderPS = renderPSWith simplePrinters'
renderPSs :: RenderMode -> Doc -> [B.ByteString]
renderPSs = renderPSsWith simplePrinters'
renderPSWith :: Printers' -> RenderMode -> Doc -> B.ByteString
renderPSWith prs target d = B.concat $ renderPSsWith prs target d
renderPSsWith :: Printers' -> RenderMode -> Doc -> [B.ByteString]
renderPSsWith prs target d = map (toPS target) $ renderWith prs d
where toPS Standard (S s) = BC.pack s
toPS Encode (S s) = encodeLocale s
toPS Standard (PS ps) = ps
toPS Encode (PS ps) = ps
toPS Standard (Both _ ps) = ps
toPS Encode (Both _ ps) = ps
renderWith :: Printers' -> Doc -> [Printable]
renderWith ps (Doc d) = case d (initState ps) of
Empty -> []
Document f -> f []
initState :: Printers' -> St
initState prs = St { printers = prs, currentPrefix = id }
prefix :: String -> Doc -> Doc
prefix s (Doc d) = Doc $ \st ->
let p = S s
st' = st { currentPrefix = currentPrefix st . (p:) } in
case d st' of
Document d'' -> Document $ (p:) . d''
Empty -> Empty
prefixLines :: Doc -> Doc -> Doc
prefixLines prefixer prefixee =
vcat $ map (prefixer <+>) $ map packedString $ linesPS $
renderPS Standard prefixee
insertBeforeLastline :: Doc -> Doc -> Doc
insertBeforeLastline a b =
case reverse $ map packedString $ linesPS $ renderPS Standard a of
(ll:ls) -> vcat (reverse ls) $$ b $$ ll
[] -> error "empty Doc given as first argument of Printer.insert_before_last_line"
lineColor :: Color -> Doc -> Doc
lineColor c d = Doc $ \st -> case lineColorT (printers st) c d of
Doc d' -> d' st
hiddenPrefix :: String -> Doc -> Doc
hiddenPrefix s (Doc d) =
Doc $ \st -> let pr = printers st
p = S (renderStringWith pr Standard $ hiddenText s)
st' = st { currentPrefix = currentPrefix st . (p:) }
in case d st' of
Document d'' -> Document $ (p:) . d''
Empty -> Empty
unsafeBoth :: String -> B.ByteString -> Doc
unsafeBoth s ps = Doc $ simplePrinter (Both s ps)
unsafeBothText :: String -> Doc
unsafeBothText s = Doc $ simplePrinter (Both s (BC.pack s))
packedString :: B.ByteString -> Doc
packedString = printable . PS
unsafePackedString :: B.ByteString -> Doc
unsafePackedString = Doc . simplePrinter . PS
invisiblePS :: B.ByteString -> Doc
invisiblePS = invisiblePrintable . PS
userchunkPS :: B.ByteString -> Doc
userchunkPS = userchunkPrintable . PS
unsafeChar :: Char -> Doc
unsafeChar = unsafeText . (:"")
text :: String -> Doc
text = printable . S
unsafeText :: String -> Doc
unsafeText = Doc . simplePrinter . S
invisibleText :: String -> Doc
invisibleText = invisiblePrintable . S
hiddenText :: String -> Doc
hiddenText = hiddenPrintable . S
userchunk :: String -> Doc
userchunk = userchunkPrintable . S
blueText, redText, greenText, magentaText, cyanText :: String -> Doc
blueText = colorText Blue
redText = colorText Red
greenText = colorText Green
magentaText = colorText Magenta
cyanText = colorText Cyan
colorText :: Color -> String -> Doc
colorText c = mkColorPrintable c . S
wrapText :: Int -> String -> Doc
wrapText n s =
vcat . map text . reverse $ "" : foldl add_to_line [] (words s)
where add_to_line [] a = [a]
add_to_line ("":d) a = a:d
add_to_line (l:ls) new | length l + length new > n = new:l:ls
add_to_line (l:ls) new = (l ++ " " ++ new):ls
printable :: Printable -> Doc
printable x = Doc $ \st -> defP (printers st) x st
mkColorPrintable :: Color -> Printable -> Doc
mkColorPrintable c x = Doc $ \st -> colorP (printers st) c x st
invisiblePrintable :: Printable -> Doc
invisiblePrintable x = Doc $ \st -> invisibleP (printers st) x st
hiddenPrintable :: Printable -> Doc
hiddenPrintable x = Doc $ \st -> hiddenP (printers st) x st
userchunkPrintable :: Printable -> Doc
userchunkPrintable x = Doc $ \st -> userchunkP (printers st) x st
simplePrinters :: Printers
simplePrinters _ = simplePrinters'
simplePrinters' :: Printers'
simplePrinters' = Printers { colorP = const simplePrinter,
invisibleP = simplePrinter,
hiddenP = invisiblePrinter,
userchunkP = simplePrinter,
defP = simplePrinter,
lineColorT = const id,
lineColorS = id
}
simplePrinter :: Printer
simplePrinter x = unDoc $ doc (\s -> x:s)
invisiblePrinter :: Printer
invisiblePrinter _ = unDoc empty
infixr 6 `append`
infixr 6 <+>
infixr 5 $$
empty :: Doc
empty = Doc $ const Empty
doc :: ([Printable] -> [Printable]) -> Doc
doc f = Doc $ const $ Document f
instance Monoid Doc where
mempty = empty
mappend = append
append :: Doc -> Doc -> Doc
Doc a `append` Doc b =
Doc $ \st -> case a st of
Empty -> b st
Document af ->
Document (\s -> af $ case b st of
Empty -> s
Document bf -> bf s)
(<?>) :: Doc -> Doc -> Doc
Doc a <?> Doc b =
Doc $ \st -> case a st of
Empty -> Empty
Document af -> Document (\s -> af $ case b st of
Empty -> s
Document bf -> bf s)
(<+>) :: Doc -> Doc -> Doc
Doc a <+> Doc b =
Doc $ \st -> case a st of
Empty -> b st
Document af -> Document (\s -> af $ case b st of
Empty -> s
Document bf ->
spaceP:bf s)
($$) :: Doc -> Doc -> Doc
Doc a $$ Doc b =
Doc $ \st -> case a st of
Empty -> b st
Document af ->
Document (\s -> af $ case b st of
Empty -> s
Document bf -> sf (newlineP:pf (bf s)))
where pf = currentPrefix st
sf = lineColorS $ printers st
vcat :: [Doc] -> Doc
vcat [] = empty
vcat ds = foldr1 ($$) ds
vsep :: [Doc] -> Doc
vsep [] = empty
vsep ds = foldr1 ($$) $ intersperse (text "") ds
hcat :: [Doc] -> Doc
hcat = mconcat
hsep :: [Doc] -> Doc
hsep = foldr (<+>) empty
quoted :: String -> Doc
quoted s = text "\"" <> text (escape s) <> text "\""
where
escape "" = ""
escape (c:cs) = if c `elem` ['\\', '"']
then '\\' : c : escape cs
else c : escape cs