module Beautifier
( beautify
, indent
, firstString
, splitAtHead
) where
import Data.Int (Int64)
import Data.Monoid ((<>))
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.Builder as B
type IndentationLevel = Int64
liftToBuilder :: (T.Text -> T.Text) -> (B.Builder -> B.Builder)
liftToBuilder f = B.fromLazyText . f . B.toLazyText
splitAt' :: Int64 -> B.Builder -> (B.Builder, B.Builder)
splitAt' n str = (B.fromLazyText head, B.fromLazyText tail)
where (head, tail) = T.splitAt n (B.toLazyText str)
head' :: B.Builder -> B.Builder
head' = B.singleton . T.head . B.toLazyText
tail' :: B.Builder -> B.Builder
tail' = liftToBuilder T.tail
drop' :: Int64 -> B.Builder -> B.Builder
drop' length = liftToBuilder (T.drop length)
length' :: B.Builder -> Int64
length' = T.length . B.toLazyText
stripStart' :: B.Builder -> B.Builder
stripStart' = liftToBuilder T.stripStart
spaceIndentation :: T.Text
spaceIndentation = " "
indentation :: IndentationLevel -> B.Builder
indentation level = B.fromLazyText (T.replicate level spaceIndentation)
newline :: B.Builder
newline = B.fromLazyText "\n"
indent :: IndentationLevel -> B.Builder -> B.Builder
indent level str = indentation level <> str
trimmedTail :: B.Builder -> B.Builder
trimmedTail = B.fromLazyText . T.stripStart . T.tail . B.toLazyText
splitAtHead :: B.Builder -> (B.Builder, B.Builder)
splitAtHead "" = ("", "")
splitAtHead str@(head' -> "\\") = splitAt' 2 str
splitAtHead str = splitAt' 1 str
string :: Bool -> B.Builder -> B.Builder
string False str@(head' -> "\"") = "\"" <> string True (tail' str)
string True str@(head' -> "\"") = "\""
string True str = let (head, tail) = splitAtHead str in head <> string True tail
firstString :: B.Builder -> B.Builder
firstString str = string False str
isWhitespace x
| x == ' ' = True
| x == '\r' = True
| x == '\n' = True
| x == '\t' = True
| otherwise = False
isEndOfValue x
| isWhitespace x = True
| x == ',' = True
| x == '}' = True
| x == ']' = True
| otherwise = False
extractJsonValue :: B.Builder -> B.Builder
extractJsonValue = liftToBuilder $ T.takeWhile (not . isEndOfValue)
beautifyText :: IndentationLevel -> B.Builder -> B.Builder
beautifyText i str
| str == "" = ""
| head == " " = stripStartThenParse str
| head == "\n" = stripStartThenParse str
| head == "\t" = stripStartThenParse str
| head == "{" = nextLineAfterOpening "{"
| head == "[" = nextLineAfterOpening "["
| head == "}" = nextLineAfterClosing "}"
| head == "]" = nextLineAfterClosing "]"
| head == "," = "," <> newline <> indent i (beautifyText i (trimmedTail str))
| head == "\"" =
let groupedString = firstString str
restOfTheString = drop' (length' groupedString) str
in groupedString <> beautifyText i restOfTheString
| head == ":" = ": " <> beautifyText i (trimmedTail str)
| otherwise =
let value = extractJsonValue str
valueLength = length' value
in value <> beautifyText i (drop' valueLength str)
where
head = head' str
stripStartThenParse = beautifyText i . stripStart'
nextLineAfterOpening token = token
<> newline
<> indent (i + 1) (beautifyText (i + 1) (trimmedTail str))
nextLineAfterClosing token = newline
<> indent (i 1) token
<> beautifyText (i 1) (trimmedTail str)
beautify :: T.Text -> T.Text
beautify = B.toLazyText . beautifyText 0 . B.fromLazyText