packages feed

hocon (empty) → 0.1.0.0

raw patch · 15 files changed

+547/−0 lines, 15 filesdep +MissingHdep +basedep +hoconsetup-changed

Dependencies added: MissingH, base, hocon, hspec, parsec, split

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for hocon++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2020++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Author name here nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,44 @@+# hocon++Small library for [Typesafe's configuration specification](https://github.com/lightbend/config) built with parsec.++```+foo = bar+bar = 123+someObject {+    wow = "this is nice!"+}+```++While there are many configuration notations and formats around, HOCON stands in a comfortable place, albeit a bit underrated. Being a superset to JSON means it inherits its great readability, as well as extending it with some very nice features, such as comments, no need to add `"` for properties and values (unless some special character is required), digging up nested values, among others.++## Sounds nice, how do I use it?++First of all, import the basic stuff:+```haskell+import Data.HOCON+import Text.Parser.HOCON+```++The first module exports the tree-like structure typical of JSON (if you're familiar with `aeson`, then it's really the same structure. It's not using _that_ structure because no way I'm including that just for the structure) and the accessor functions, while the second exports the parser function:+```haskell+parseHOCON :: String -> Either ParseError Config+```++If you've already used Typesafe's config on the JVM, then the functions will make you feel just like at home:+```haskell+getConfig :: String -> Config -> Maybe Config+getNumber :: String -> Config -> Maybe Double+getString :: String -> Config -> Maybe String+getBoolean :: String -> Config -> Maybe Bool+getList :: String -> Config -> Maybe [Config]+hasPath :: String -> Config -> Bool+```++## Missing features / what's to come+* Actually support comments+* Multiline strings+* Include another file and merge them+* Object substitution++Pull requests are always welcome!
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,4 @@+module Main where++main :: IO ()+main = return ()
+ hocon.cabal view
@@ -0,0 +1,81 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 12270ead08a8273a5e886852616f4732e424ebe6ce7641968b4c92725e95f0c8++name:           hocon+version:        0.1.0.0+description:    Please see the README on GitHub at <https://github.com/githubuser/hocon#readme>+homepage:       https://github.com/githubuser/hocon#readme+bug-reports:    https://github.com/githubuser/hocon/issues+author:         Author name here+maintainer:     example@example.com+copyright:      2020 Author name here+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://github.com/githubuser/hocon++library+  exposed-modules:+      Data.Bifunctor.Extra+      Data.HOCON+      Data.Map+      Text.Parser.HOCON+      Text.Parser.HOCON.Internal+  other-modules:+      Paths_hocon+  hs-source-dirs:+      src+  build-depends:+      MissingH <=1.4.3.0+    , base >=4.7 && <5+    , hspec+    , parsec <=3.1.14.0+    , split <=0.2.3.4+  default-language: Haskell2010++executable hocon-exe+  main-is: Main.hs+  other-modules:+      Paths_hocon+  hs-source-dirs:+      app+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      MissingH <=1.4.3.0+    , base >=4.7 && <5+    , hocon+    , hspec+    , parsec <=3.1.14.0+    , split <=0.2.3.4+  default-language: Haskell2010++test-suite hocon-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Data.MapSpec+      Text.Parser.HOCON.InternalSpec+      Text.Parser.HOCONSpec+      Paths_hocon+  hs-source-dirs:+      test+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      MissingH <=1.4.3.0+    , base >=4.7 && <5+    , hocon+    , hspec+    , parsec <=3.1.14.0+    , split <=0.2.3.4+  default-language: Haskell2010
+ src/Data/Bifunctor/Extra.hs view
@@ -0,0 +1,11 @@+module Data.Bifunctor.Extra+  ( module Data.Bifunctor+  , mapValues+  )+where++import Data.Bifunctor (Bifunctor(..))+import Data.Map (Map)++mapValues :: (b -> c) -> Map a b -> Map a c+mapValues f = map (second f)
+ src/Data/HOCON.hs view
@@ -0,0 +1,99 @@+module Data.HOCON+  ( Config(..)+  , getNode+  , hasPath+  , getString+  , getNumber+  , getList+  , getBool+  , isNull+  , mapNode+  , getConfig+  , pretty+  )+where++import Data.List (find)+import Data.List.Split (splitOn)+import Data.Map (Map)+import Data.Maybe (isJust)+import Data.String.Utils (join)++data Config =+  HOCONNode (Map String Config) |+  HOCONString String |+  HOCONNumber Double |+  HOCONList [Config] |+  HOCONBool Bool |+  HOCONNull+  deriving (Show, Eq)++isNull :: Config -> Bool+isNull HOCONNull = True+isNull _         = False++mapNode :: (Map String Config -> Config) -> Config -> Config+mapNode f (HOCONNode nodes) = f nodes+mapNode _ conf              = conf++getNode :: String -> Config -> Maybe (Map String Config)+getNode key conf = do+  conf' <- getConfig key conf+  case conf' of+    HOCONNode nodes -> return nodes+    _               -> Nothing++getConfig :: String -> Config -> Maybe Config+getConfig key = go (splitOn "." key)+ where+  go [k] conf = do+    nodes      <- getNodes conf+    nestedConf <- find ((== k) . fst) nodes+    return $ snd nestedConf+  go (k : ks) conf = do+    nodes      <- getNodes conf+    nestedConf <- find ((== k) . fst) nodes+    go ks (snd nestedConf)++getNumber :: String -> Config -> Maybe Double+getNumber key conf = do+  conf' <- getConfig key conf+  case conf' of+    HOCONNumber n -> return n+    _             -> Nothing++getString :: String -> Config -> Maybe String+getString key conf = do+  conf' <- getConfig key conf+  case conf' of+    HOCONString s -> return s+    _             -> Nothing++getList :: String -> Config -> Maybe [Config]+getList key conf = do+  conf' <- getConfig key conf+  case conf' of+    HOCONList l -> return l+    _           -> Nothing++getBool :: String -> Config -> Maybe Bool+getBool key conf = do+  conf' <- getConfig key conf+  case conf' of+    HOCONBool b -> return b+    _           -> Nothing++hasPath :: String -> Config -> Bool+hasPath key = isJust . getConfig key++getNodes :: Config -> Maybe [(String, Config)]+getNodes (HOCONNode nodes) = Just nodes+getNodes _                 = Nothing++pretty :: Config -> String+pretty HOCONNull           = "null"+pretty (HOCONNumber n    ) = show n+pretty (HOCONString s    ) = "\"" ++ s ++ "\""+pretty (HOCONBool   b    ) = if b then "true" else "false"+pretty (HOCONList   xs   ) = "[" ++ (join "," . map pretty $ xs) ++ "]"+pretty (HOCONNode   nodes) = "{" ++ (join "," . map (\(k, node) -> "\"" ++ k ++ "\":" ++ pretty node) $ nodes) ++ "}"
+ src/Data/Map.hs view
@@ -0,0 +1,24 @@+module Data.Map+  ( groupBy+  , sortByKey+  , Map+  )+where++import Data.List (sortBy)++type Map k v = [(k, v)]++groupBy :: (Ord k, Eq k) => (v -> k) -> [v] -> Map k [v]+groupBy f vs = sortBy (\(k1, _) (k2, _) -> compare k1 k2) $ go f (vs, [])+ where+  go :: Eq k => (v -> k) -> ([v], Map k [v]) -> Map k [v]+  go _ ([], vs) = vs+  go f (v : vs, acc) =+    let+      append key value (k, v) = if key == k then (k, value : v) else (k, v)+      acc' = if any (\(k', _) -> k' == f v) acc then map (append (f v) v) acc else (f v, [v]) : acc+    in go f (vs, acc')++sortByKey :: (Ord k) => Map k v -> Map k v+sortByKey = sortBy (\a b -> compare (fst a) (fst b))
+ src/Text/Parser/HOCON.hs view
@@ -0,0 +1,12 @@+module Text.Parser.HOCON+  ( parseHOCON+  , ParseError(..)+  )+where++import Data.HOCON (Config(..))+import Text.Parser.HOCON.Internal+import Text.ParserCombinators.Parsec (ParseError, parse)++parseHOCON :: String -> Either ParseError Config+parseHOCON = parse hoconParser "hocon" . preProcessing
+ src/Text/Parser/HOCON/Internal.hs view
@@ -0,0 +1,132 @@+-- Based off of [this gist for a JSON parser using Parsec](https://gist.github.com/fero23/51f63a33d733055d53b4)++{-# LANGUAGE ExistentialQuantification #-}++module Text.Parser.HOCON.Internal+  ( objectParser+  , stringParser+  , parseProps+  , parseLabel+  , arrayParser+  , numberParser+  , booleanParser+  , nullParser+  , preProcessing+  , hoconParser+  )+where++import Data.Bifunctor.Extra (mapValues)+import Data.HOCON (Config(..), mapNode)+import Data.List.Split (splitOn)+import Data.Map (groupBy, sortByKey)+import Data.String.Utils (replace, join, strip)+import Text.ParserCombinators.Parsec+  (char, Parser, alphaNum, digit, letter, noneOf, oneOf, space, string, between, sepBy, (<|>), many, many1, skipMany, try)++whitespace :: Parser ()+whitespace = skipMany space++objectParser :: Parser Config+objectParser = do+  whitespace >> char '{' >> whitespace+  props <- sepBy parseProps (whitespace >> (char ',' <|> char '\n') >> whitespace)+  whitespace >> char '}' >> whitespace+  return $ HOCONNode props++parseString :: Parser String+parseString = parseStr <|> parseOpenStr++parseStr :: Parser String+parseStr = between (char '\"') (char '\"') (many $ noneOf "\"" <|> try (string "\"\"" >> return '"'))++parseOpenStr :: Parser String+parseOpenStr = do+  l   <- letter+  str <- many (alphaNum <|> oneOf "._-")+  return $ l : str++parseProps :: Parser (String, Config)+parseProps = do+  label <- parseString+  value <- whitespace >> (parseJsonLikeValue <|> parseObjectValue)+  return $ splitProp label value+ where+  parseJsonLikeValue = do+    (char ':' <|> char '=') >> whitespace+    objectParser <|> arrayParser <|> booleanParser <|> nullParser <|> stringParser <|> numberParser+  parseObjectValue = objectParser++parseLabel :: Parser String+parseLabel = do+  whitespace+  label <- parseString+  whitespace >> (char ':' <|> char '=') >> whitespace+  return label++numberParser :: Parser Config+numberParser = do+  whitespace+  digits <- many1 (digit <|> oneOf ".-")+  whitespace+  return . HOCONNumber $ read digits++stringParser :: Parser Config+stringParser = do+  whitespace+  str <- parseStr <|> parseOpenStr+  whitespace+  return $ HOCONString str++booleanParser :: Parser Config+booleanParser = do+  whitespace+  bool <- string "true" <|> string "false"+  whitespace+  return $ if bool == "true" then HOCONBool True else HOCONBool False++nullParser :: Parser Config+nullParser = do+  whitespace >> string "null" >> whitespace+  return HOCONNull++arrayParser :: Parser Config+arrayParser = do+  whitespace >> char '[' >> whitespace+  array <- sepBy+    (objectParser <|> arrayParser <|> booleanParser <|> nullParser <|> stringParser <|> numberParser)+    (whitespace >> char ',' >> whitespace)+  whitespace >> char ']' >> whitespace+  return $ HOCONList array++splitProp :: String -> Config -> (String, Config)+splitProp label value = case splitOn "." label of+  [l         ] -> (l, value)+  (l : nested) -> (l, join nested value)+ where+  join [l     ] value = HOCONNode [(l, value)]+  join (l : ls) value = HOCONNode [(l, join ls value)]+++preProcessing :: String -> String+preProcessing =+  replace "\n" "," . replace "{\n" "{" . replace "\n}" "}" . replace ",\n" "," . join "\n" . map strip . splitOn "\n"++hoconParser :: Parser Config+hoconParser = objectParser <|> do+  values <- sepBy parseProps (char ',')+  let grouped = groupAndMerge values+  let sorted = mapValues (mapNode (HOCONNode . sortByKey)) . sortByKey $ grouped+  return $ HOCONNode sorted+ where+  groupAndMerge values = do+    (key, mixedConfigsAndKeys) <- groupBy fst values+    let configs = map snd mixedConfigsAndKeys+    return (key, mergeAll configs)++mergeAll :: [Config] -> Config+mergeAll = foldl1 merge++merge :: Config -> Config -> Config+merge (HOCONNode a) (HOCONNode b) = HOCONNode (a ++ b)+
+ test/Data/MapSpec.hs view
@@ -0,0 +1,11 @@+module Data.MapSpec where++import Data.Map+import Test.Hspec++spec :: Spec+spec =+  describe "groupBy"+    $          it "joins all the items whose key is the same"+    $          groupBy length ["abc", "ab", "a", "atlas", "hades", "orpheus"]+    `shouldBe` [(1, ["a"]), (2, ["ab"]), (3, ["abc"]), (5, ["hades", "atlas"]), (7, ["orpheus"])]
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/Text/Parser/HOCON/InternalSpec.hs view
@@ -0,0 +1,74 @@+module Text.Parser.HOCON.InternalSpec+  ( spec+  )+where++import Data.HOCON (Config(..))+import Test.Hspec (describe, it, shouldBe, shouldSatisfy, Spec)+import Text.Parser.HOCON.Internal (arrayParser, numberParser, objectParser, parseProps, stringParser, hoconParser, preProcessing)+import Text.ParserCombinators.Parsec (parse)++isLeft :: Either a b -> Bool+isLeft (Left _) = True+isLeft _        = False++spec :: Spec+spec = do+  describe "stringParser" $ do+    it "succeeds" $ do+      parse stringParser "string" "\"saraza\"" `shouldBe` Right (HOCONString "saraza")+      parse stringParser "string" "saraza" `shouldBe` Right (HOCONString "saraza")+    it "fails" $ parse stringParser "string" "\"saraza" `shouldSatisfy` isLeft++  describe "numberParser" $ it "succeeds" $ do+    parse numberParser "number" "123456" `shouldBe` Right (HOCONNumber 123456)+    parse numberParser "number" "123.456" `shouldBe` Right (HOCONNumber 123.456)++  describe "arrayParser" $ do+    it "succeeds" $ do+      parse arrayParser "array" "[1,2,3]" `shouldBe` Right (HOCONList [HOCONNumber 1, HOCONNumber 2, HOCONNumber 3])+      parse arrayParser "array" "[1,\"a\", a]" `shouldBe` Right (HOCONList [HOCONNumber 1, HOCONString "a", HOCONString "a"])+    it "fails" $ do+      parse arrayParser "array" "[1" `shouldSatisfy` isLeft+      parse arrayParser "array" "[1, a?" `shouldSatisfy` isLeft++  describe "parseProps" $ it "succeeds" $ do+    parse parseProps "props" "foo: 123" `shouldBe` Right ("foo", HOCONNumber 123)+    parse parseProps "props" "foo = \"bar\"" `shouldBe` Right ("foo", HOCONString "bar")+    parse parseProps "props" "\"foo\":\"bar\"" `shouldBe` Right ("foo", HOCONString "bar")+    parse parseProps "props" "foo {\nbar = baz\n}" `shouldBe` Right ("foo", HOCONNode [("bar", HOCONString "baz")])+    parse parseProps "props" "foo.bar = baz" `shouldBe` Right ("foo", HOCONNode [("bar", HOCONString "baz")])++  describe "objectParser" $ do+    it "succeeds" $ do+      parse objectParser "object" "{foo = bar,\"baz\": \"baz\"}"+        `shouldBe` Right (HOCONNode [("foo", HOCONString "bar"), ("baz", HOCONString "baz")])+      parse objectParser "object" "{}" `shouldBe` Right (HOCONNode [])+    it "fails" $ do+      parse objectParser "object" "{\nfoo = bar\n\"baz\": \"baz\"\n" `shouldSatisfy` isLeft+      parse objectParser "object" "{\nfoo = bar\n\"baz\": \"baz\\n}" `shouldSatisfy` isLeft+      parse objectParser "object" "{" `shouldSatisfy` isLeft++  describe "preProcessing" $ do+    it "replaces al commas followed by newline with just the coma" $ do+      preProcessing "foo,\nbar,\nbaz" `shouldBe` "foo,bar,baz"+      preProcessing "foo, bar, baz" `shouldBe` "foo, bar, baz"+    it "replaces all opening brackets followed by newline with just the bracket" $ do+      preProcessing "{\nfoo = bar}" `shouldBe` "{foo = bar}"+      preProcessing "{foo = bar}" `shouldBe` "{foo = bar}"+    it "replaces all newlines followed by closing bracket with just the bracket" $ do+      preProcessing "{foo = bar\n}" `shouldBe` "{foo = bar}"+      preProcessing "{foo = bar}" `shouldBe` "{foo = bar}"+      preProcessing "{foo = bar\n  }" `shouldBe` "{foo = bar}"++  describe "hoconParser" $ do+    it "succeeds" $ do+      parse hoconParser "hocon" "{}" `shouldBe` Right (HOCONNode [])+      parse hoconParser "hocon" "foo = bar" `shouldBe` Right (HOCONNode [("foo", HOCONString "bar")])+      parse hoconParser "hocon" "foo = bar,bar = 123"+        `shouldBe` Right (HOCONNode [("bar", HOCONNumber 123), ("foo", HOCONString "bar")])+      parse hoconParser "hocon" "foo = \"bar?!\",bar {baz = 123}"+        `shouldBe` Right (HOCONNode [("bar", HOCONNode [("baz", HOCONNumber 123)]), ("foo", HOCONString "bar?!")])+    it "fails" $ do+      parse hoconParser "hocon" "{" `shouldSatisfy` isLeft+      parse hoconParser "hocon" "foo =" `shouldSatisfy` isLeft
+ test/Text/Parser/HOCONSpec.hs view
@@ -0,0 +1,19 @@+module Text.Parser.HOCONSpec where++import Data.HOCON+import Test.Hspec+import Text.Parser.HOCON (parseHOCON)++isLeft :: Either a b -> Bool+isLeft (Left _) = True+isLeft _        = False++spec :: Spec+spec = describe "parseHOCON" $ do+  it "works" $ do+    parseHOCON "foo = bar" `shouldBe` Right (HOCONNode [("foo", HOCONString "bar")])+    parseHOCON "foo = bar\nbar = 123\nbaz = \"nani?!\""+      `shouldBe` Right (HOCONNode [("bar", HOCONNumber 123), ("baz", HOCONString "nani?!"), ("foo", HOCONString "bar")])+  it "fails" $ do+    parseHOCON "foo = bar\n{\n  foo = bar\n}" `shouldSatisfy` isLeft+    parseHOCON "{" `shouldSatisfy` isLeft