module Language.Bash.PrettyPrinter.State where
import qualified Data.List as List
import Data.Monoid
import Prelude hiding (lines, round, concat, length, replicate)
import Data.Binary.Builder (Builder, toLazyByteString)
import qualified Data.Binary.Builder as Builder
import Data.ByteString.Char8 hiding (null)
import Data.ByteString.Lazy (toChunks)
import Data.Word
import Control.Monad.State.Strict
data PPState = PPState { indents :: [Word]
, curly :: [()]
, round :: [()]
, columns :: Word
, separated :: Bool
, string :: Builder }
instance Show PPState where
show PPState{..} = "PPState { indents=" ++ show indents
++ " curly=" ++ show curly
++ " round=" ++ show round
++ " columns=" ++ show columns
++ " separated=" ++ show separated
++ " string=" ++ "..." ++ " }"
render :: PPState -> State PPState () -> Builder
render init computation = string $ execState computation init
renderBytes :: PPState -> State PPState () -> ByteString
renderBytes = ((concat . toChunks . toLazyByteString) .) . render
nlCol :: Word -> PPState
nlCol w = PPState [w] [()] [()] 0 True Builder.empty
data PPOp = Indent Word
| Outdent
| Bytes ByteString
| Newline
| WordSeparator
| Curly Bool
| Round Bool
op :: PPState -> PPOp -> PPState
op state@PPState{..} x = case x of
Indent w -> state { indents = w:indents }
Outdent -> state { indents = tSafe indents }
Curly f | f -> state { indents = 2:indents, curly = ():curly
, string = curly_s, columns = columns+2
, separated = True }
| otherwise -> state { indents = tSafe indents
, curly = tSafe curly, string = s_curly
, separated = False }
Round f | f -> state { indents = 2:indents, round = ():round
, string = round_s, columns = columns+2
, separated = True }
| otherwise -> state { indents = tSafe indents
, round = tSafe round, string = s_round
, separated = False }
WordSeparator -> state { separated = False }
Newline | columns == 0 -> state { separated = True }
| otherwise -> state { string = sNL, columns = 0
, separated = True }
Bytes b -> state { string = s', columns = c' }
where
c' = columns + cast (length padded + length sSep)
s' = sappend padded
dent = cast (sum indents) `replicate` ' '
padded | columns == 0 = dent `mappend` b
| otherwise = b
where
sappend = mappend string . Builder.fromByteString . mappend sSep
tSafe list = if null list then [] else List.tail list
sNL = sappend "\n"
curly_s = sappend "{"
s_curly = sappend ";}"
round_s = sappend "("
s_round = sappend ")"
sSep | not separated = " "
| otherwise = ""
opM :: [PPOp] -> State PPState ()
opM = mapM_ (modify . flip op)
nl :: State PPState ()
nl = opM [Newline]
hang :: ByteString -> State PPState ()
hang b = opM [Bytes b, Indent (cast (length b))]
hangWord :: ByteString -> State PPState ()
hangWord b = opM [Bytes b, Indent (cast (length b) + 1), WordSeparator]
word :: ByteString -> State PPState ()
word b = opM [Bytes b, WordSeparator]
wordcat :: [ByteString] -> State PPState ()
wordcat = word . concat
outdent :: State PPState ()
outdent = opM [Outdent]
inword :: ByteString -> State PPState ()
inword b = opM [Bytes b, Indent 2, Newline]
outword :: ByteString -> State PPState ()
outword b = opM [Newline, Outdent, Bytes b, WordSeparator]
curlyOpen :: State PPState ()
curlyOpen = opM [Curly True, WordSeparator]
curlyClose :: State PPState ()
curlyClose = opM [Curly False, WordSeparator]
roundOpen :: State PPState ()
roundOpen = opM [Round True, WordSeparator]
roundClose :: State PPState ()
roundClose = opM [Round False, WordSeparator]
indentPadToNextWord :: State PPState ()
indentPadToNextWord = do
PPState{..} <- get
let i = sum indents
columns' | separated = columns
| otherwise = columns + 1
indent | columns' > i = columns' i
| otherwise = 0
opM [Indent indent]
cast = fromIntegral
renderIndents indents = (mconcat . Prelude.reverse)
(Prelude.map prettify_indent indents)
where
prettify_indent 0 = ""
prettify_indent 1 = "|"
prettify_indent 2 = "-|"
prettify_indent n = "-" `mappend` prettify_indent (n1)