bunz (empty) → 0.0.1
raw patch · 10 files changed
+292/−0 lines, 10 filesdep +basedep +bunzdep +cmdargssetup-changed
Dependencies added: base, bunz, cmdargs, doctest, hspec, text, unix
Files
- LICENSE +21/−0
- README.md +2/−0
- Setup.hs +2/−0
- app/Main.hs +32/−0
- bunz.cabal +49/−0
- src/Beautifier.hs +121/−0
- src/Character.hs +9/−0
- test/BeautifierSpec.hs +37/−0
- test/CharacterSpec.hs +18/−0
- test/MainSpec.hs +1/−0
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2017 Sendy Halim <sendyhalim93@gmail.com>++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,2 @@+# Bunz+JSON beautifier cli tool written in Haskell
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Main where++import qualified Beautifier as B+import System.Console.CmdArgs ((&=))+import qualified System.Console.CmdArgs as CA+import qualified System.Posix.IO as IO+import qualified System.Posix.Terminal as T++data Args = Args { jsonString :: String }+ deriving (Show, CA.Data, CA.Typeable)+++args = Args { jsonString = CA.def &= CA.typ "JSON String" &= CA.argPos 0 }+ &= CA.summary "JSON beautifier tool version 0.0.1"++main :: IO ()+main = T.queryTerminal IO.stdInput >>= run++printBeautified :: String -> IO ()+printBeautified = putStrLn . B.beautify++runFromStdInput :: IO ()+runFromStdInput = getContents >>= printBeautified++runFromArgs :: Args -> IO ()+runFromArgs (Args { jsonString = str }) = printBeautified str++run :: Bool -> IO ()+run False = runFromStdInput+run True = CA.cmdArgs args >>= runFromArgs
+ bunz.cabal view
@@ -0,0 +1,49 @@+name: bunz+version: 0.0.1+synopsis: CLI tool to beautify JSON string.+description: CLI tool to beautify JSON string.+homepage: https://github.com/sendyhalim/bunz+license: MIT+license-file: LICENSE+author: Sendy Halim <sendyhalim93@gmail.com>+maintainer: Sendy Halim <sendyhalim93@gmail.com>+copyright: (c) 2017 Sendy Halim+category: CLI, JSON+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Character+ , Beautifier+ build-depends: base >= 4.7 && < 5+ , text == 1.2.2.2+ default-language: Haskell2010++executable bunz-exe+ hs-source-dirs: app+ main-is: Main.hs+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends: base+ , bunz+ , unix == 2.7.2.1+ , cmdargs >= 0.10.18+ default-language: Haskell2010++test-suite bunz-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: MainSpec.hs+ other-modules: CharacterSpec+ , BeautifierSpec+ build-depends: base+ , bunz+ , doctest >= 0.11+ , hspec == 2.4.4+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/githubuser/bunz
+ src/Beautifier.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ViewPatterns #-}++module Beautifier+ ( beautify+ , indent+ , firstString+ , splitAtHead+ ) where++import Data.Monoid ((<>))+import qualified Data.String as S+import qualified Data.Text as T++-- Doctest setup+-- $setup+-- >>> :set -XOverloadedStrings++type IndentationLevel = Int++indentation :: T.Text+indentation = " "++newline :: T.Text+newline = "\n"++indent :: IndentationLevel -> T.Text -> T.Text+indent level str = T.replicate level indentation <> str++trimmedTail :: T.Text -> T.Text+trimmedTail = T.stripStart . T.tail++-- | Split the given string at its head while honouring escaped string.+-- Examples:+--+-- >>> splitAtHead "whatsup"+-- ("w","hatsup")+--+-- >>> splitAtHead "\nyeay"+-- ("\n","yeay")+splitAtHead :: T.Text -> (T.Text, T.Text)+splitAtHead "" = ("", "")+splitAtHead str@(T.head -> '\\') = T.splitAt 2 str+splitAtHead str = T.splitAt 1 str++-- | Extract string within double quotes `"` including the double quotes ignoring+-- the suffix after closing quote `"`+--+-- Examples:+--+-- >>> string False "\"test\"whatuppp"+-- "\"test\""+string :: Bool -> T.Text -> T.Text+string False str@(T.head -> '"') = "\"" <> string True (T.tail str)+string True str@(T.head -> '"') = "\""+string True str = let (head, tail) = splitAtHead str in head <> string True tail++-- | Extract the first string value starting from the front.+firstString :: T.Text -> T.Text+firstString str = string False str+++-- | Check if the given character is a whitespace+isWhitespace x+ | x == ' ' = True+ | x == '\r' = True+ | x == '\n' = True+ | x == '\t' = True+ | otherwise = False++-- | Check if the given character is end of json value+isEndOfValue x+ | isWhitespace x = True+ | x == ',' = True+ | x == '}' = True+ | x == ']' = True+ | otherwise = False++-- | Extract json value from the given text+--+-- Examples+--+-- >>> extractJsonValue "123131}"+-- "123131"+--+-- >>> extractJsonValue "false,true,false]"+-- "false"+extractJsonValue :: T.Text -> T.Text+extractJsonValue = T.takeWhile (not . isEndOfValue)++-- |Beautify the given JSON text based on the current indentation level.+beautifyText :: IndentationLevel -> T.Text -> T.Text+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 = T.drop (T.length groupedString) str+ in groupedString <> beautifyText i restOfTheString+ | head == ':' = ": " <> beautifyText i (trimmedTail str)+ | otherwise = let value = extractJsonValue str+ in value <> beautifyText i (T.drop (T.length value) str)+ where+ head = T.head str+ stripStartThenParse = beautifyText i . T.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 :: String -> String+beautify = T.unpack . beautifyText 0 . T.pack
+ src/Character.hs view
@@ -0,0 +1,9 @@+module Character+ ( isEscaped+ ) where++import Control.Monad++isEscaped :: String -> Bool+isEscaped ('\\':x) = True+isEscaped _ = False
+ test/BeautifierSpec.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE OverloadedStrings #-}++module BeautifierSpec (main, spec) where++import qualified Beautifier as B+import Test.Hspec++main :: IO()+main = hspec spec++spec :: Spec+spec = do+ describe "Beautifier.indent" $ do+ context "When given negative indent level" $ do+ it "should not indent" $ do+ B.indent (-1) "{" `shouldBe` "{"++ context "When given 0 indent level" $ do+ it "should not indent" $ do+ B.indent 0 "{" `shouldBe` "{"++ context "When given 2 indent level" $ do+ it "should indent by 2 levels" $ do+ B.indent 2 "{" `shouldBe` " {"++ describe "Beautifier.splitAtHead" $ do+ context "when text first character is escaped" $ do+ it "should split escaped character as the head" $ do+ B.splitAtHead "\\\"hithere" `shouldBe` ("\\\"", "hithere")++ context "when the first character is not escaped" $ do+ it "should split correctly" $ do+ B.splitAtHead "testtest" `shouldBe` ("t", "esttest")++ context "when given empty text" $ do+ it "should split into empty strings" $ do+ B.splitAtHead "" `shouldBe` ("", "")
+ test/CharacterSpec.hs view
@@ -0,0 +1,18 @@+module CharacterSpec (main, spec) where++import qualified Character as C+import Test.Hspec++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "Character.isEscaped" $ do+ context "Given character \\t" $ do+ it "should return True" $ do+ C.isEscaped "\\t" `shouldBe` True++ context "Given character \t" $ do+ it "should return False" $ do+ C.isEscaped "\t" `shouldBe` False
+ test/MainSpec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}