packages feed

aeson-parsec-picky (empty) → 0.1.0.0

raw patch · 6 files changed

+418/−0 lines, 6 filesdep +aesondep +basedep +parsecsetup-changed

Dependencies added: aeson, base, parsec, scientific, text, unordered-containers, vector

Files

+ CHANGES.md view
@@ -0,0 +1,9 @@+Changelog / Release Notes+=========================++Version 0.1.0.0+---------------++Release date: **2015-11-09**++* First public release.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Matej Kollar++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 Matej Kollar 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,112 @@+Picky JSON Parser+=================++JSON parser with nice error messages and little+more strict syntax (whitespace-wise). Based on+[Aeson](http://hackage.haskell.org/package/aeson) and+[Parsec](http://hackage.haskell.org/package/parsec).++Interacting with user+---------------------++JSON being nice readable text-based format seems good candidate+for occasionally being created by a user. While Aeson provides really+super-optimized parsers, their error messages are not very helpful.+Creating larger JSON object by hand can be frustrating (especially)+when you make even a small mistake.++While this parser is not optimized for speed, it tries to produce+nice and helpful error messages. (This library uses Parsec library.)++Another way to help your user is not allowing him or her to+learn wrong habbits. Just look at the following piece of code (be+warned - there are trailing spaces there):++~~~ .json+{ "name"   :   +   ,   ++"Hal"+}+~~~++That (in my opinion) is something one would not like to see in files+users of his or hers tool produces. So why not forbid that? This+library does not allow such things while still allowing to make+the input more airy.++Composability+-------------++This library was written with re-usability in mind. Parsers it+provides do not consume any spaces before of after corresponding+values and therefore are more easily reusable for your own projects.++Parsing to Aeson data types+---------------------------++Aeson library is nice to work with with large ecosystem of useful+libraries. So why not join them and avoid reinventing the wheel?++Example Use+-----------++### Script++~~~ { .haskell }+{-# LANGUAGE DeriveGeneric #-}+module Main (main) where++import GHC.Generics+import System.Environment (getArgs)++import Data.Aeson hiding (eitherDecode)+import Data.Aeson.Parser.Parsec.Picky (eitherDecode)++import Data.Text.IO as Text (readFile)++data Contact = Contact+    { name :: String+    , address :: String+    } deriving (Generic, Show)++instance FromJSON Contact where++printContacts :: [Contact] -> IO ()+printContacts = mapM_ print++main' :: [String] -> IO ()+main' [filename] = Text.readFile filename+    >>= process . eitherDecode filename+    where+    process = either putStrLn printContacts+main' _ = print "Usage: script [CONTACTS_FILE]"++main :: IO ()+main = getArgs >>= main'+~~~++### Input file++~~~ { .json }+[ { "name": "Alice"+  , "address": "Kansas"+  }+]+~~~++Motivation+----------++Why another JSON parser? Some internal tool for+JSON RPC testing used simple format that re-used JSON parsers.+It was already re-written few times and reasons were:++* Bad error messages for people who were writing testing scripts.+* Those people were able to do horrible stuff (trailing spaces, ...).+* Some parsers that used Parsec (and produced helpful error messages)+  were producing non-aeson data structures and we already use+  aeson on some places so we had option to be more heterogeneous+  or make useless conversions.++No parser I was aware of seemed to solve these issues.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ aeson-parsec-picky.cabal view
@@ -0,0 +1,82 @@+name:                aeson-parsec-picky++-- The package version.  See the Haskell package versioning policy (PVP)+-- for standards guiding when and how versions should be incremented.+-- http://www.haskell.org/haskellwiki/Package_versioning_policy+-- PVP summary:     +-+------- breaking API changes+--                  | | +----- non-breaking API additions+--                  | | | +--- code changes with no API change+version:            0.1.0.0++-- A short (one-line) description of the package.+synopsis:           Alternative JSON parser based on Parsec and Aeson++-- A longer description of the package.+description:        JSON parser with nice error messages and+                    little more strict syntax (whitespace-wise).+homepage:           https://github.com/FPBrno/aeson-parsec-picky++-- The license under which the package is released.+license:            BSD3++-- The file containing the license text.+license-file:       LICENSE++-- The package author(s).+author:             Matej Kollar++-- An email address to which users can send suggestions, bug reports, and+-- patches.+maintainer:         208115@mail.muni.cz++-- A copyright notice.+copyright:          (c) 2015, Matej Kollar++category:           Text, JSON++build-type:         Simple++-- Extra files to be distributed with the package, such as examples or a+-- README.+extra-source-files:   README.md+                    , CHANGES.md++-- Constraint on the version of Cabal needed to build this package.+cabal-version:      >=1.10+++library+  -- Modules exported by the library.+  exposed-modules:  Data.Aeson.Parser.Parsec.Picky++  -- Modules included in this library but not exported.+  -- other-modules:++  -- LANGUAGE extensions used by modules in this package.+  -- other-extensions:++  -- Other library packages from which modules are imported.+  build-depends:      base >=4.7 && <5.0+                    , aeson >= 0.10+                    , parsec >= 3.0+                    , scientific+                    , text+                    , unordered-containers+                    , vector++  -- Directories containing source files.+  hs-source-dirs:   src++  ghc-options:      -Wall++  -- Base language which the package is written in.+  default-language: Haskell2010++source-repository head+  type:             git+  location:         https://github.com/FPBrno/aeson-parsec-picky++source-repository this+  type:             git+  location:         https://github.com/FPBrno/aeson-parsec-picky+  tag:              v0.1.0.0
+ src/Data/Aeson/Parser/Parsec/Picky.hs view
@@ -0,0 +1,183 @@+{-# LANGUAGE NoImplicitPrelude #-}+-- |+-- Module:       $HEADER$+-- Description:  Picky JSON parser based on Parsec and Aeson+-- Copyright:    (c) 2015, Matej Kollar+-- License:      BSD3+--+-- Maintainer:   208115@mail.muni.cz+--+-- JSON parser with nice error messages and+-- little more strict syntax (whitespace-wise).+--+-- In most cases you would want to use either 'value' or 'object'+-- parser.+module Data.Aeson.Parser.Parsec.Picky+    (+    -- * Parsers+      string+    , object+    , array+    , number+    , bool+    , null+    , value+    -- * Convenience functions+    , eitherDecode+    ) where++import Prelude (Enum(toEnum), Int)++import Control.Arrow (left)+import Control.Applicative (pure, (<$>), (<|>), (<*), (<*>), (*>))+import Control.Monad (Monad((>>=)), return, sequence, void)+import Data.Bool (Bool(False, True), (&&))+import Data.Either (Either(Left))+import Data.Eq (Eq((/=)))+import Data.Function (flip, ($), (.))+import Data.List (concat)+import Data.String (String)+import Text.Read (read)+import Text.Show (show)++import qualified Data.HashMap.Strict as HashMap (fromList)+import Data.Text (Text)+import qualified Data.Text as Text (pack)+import qualified Data.Vector as Vector (fromList)+import Data.Scientific (Scientific)+++import Data.Aeson+    ( FromJSON+    , Result(Error, Success)+    , fromJSON+    )+import Data.Aeson.Types+    ( Value+        ( Object+        , Array+        , String+        , Number+        , Bool+        , Null+        )+    )+import Text.Parsec+    ( SourceName+    , between+    , char+    , count+    , digit+    , eof+    , hexDigit+    , many+    , many1+    , newline+    , option+    , optional+    , parse+    , satisfy+    , sepBy+    , try+    , (<?>)+    )+import qualified Text.Parsec as P (string)+import Text.Parsec.Text (Parser)++-- {{{ Helpers ----------------------------------------------------------------+newlines :: Parser ()+newlines = void $ many newline++spaces :: Parser ()+spaces = void $ many (char ' ')++commaSeparated :: Parser a -> Parser [a]+commaSeparated = flip sepBy comma where+    comma = (variant1 <|> try variant2) <* spaces+    variant1 = char ',' <* newlines+    variant2 = pickySpaces *> char ','++pickySpaces :: Parser ()+pickySpaces = newlines *> spaces++pickyBetween :: Parser a -> Parser b -> Parser c -> Parser c+pickyBetween o c = between (o <* pickySpaces) (pickySpaces *> c)+-- }}} Helpers ----------------------------------------------------------------++-- {{{ Underlaying ------------------------------------------------------------+baseString :: Parser Text+baseString = Text.pack <$> p where+    p = between (char '"') (char '"') $ many oneChar+    oneChar = raw <|> char '\\' *> quoted+    raw = satisfy (\ c -> c /= '"' && c /= '\\')+    quoted = tab <|> quot <|> revsolidus <|> solidus <|> backspace <|> formfeed+        <|> nl <|> cr <|> hexUnicode+    tab = char 't' *> pure '\t'+    quot = char '"' *> pure '"'+    revsolidus = char '/' *> pure '/'+    solidus = char '\\' *> pure '\\'+    backspace = char 'b' *> pure '\b'+    formfeed = char 'f' *> pure '\f'+    nl = char 'n' *> pure '\n'+    cr = char 'r' *> pure '\r'+    hexUnicode = char 'u' *> count 4 hexDigit >>= decodeUtf+    decodeUtf x = pure $ toEnum (read ('0':'x':x) :: Int)++baseNumber :: Parser Scientific+baseNumber = read . concat <$> sequence+    [ opt $ P.string "-"+    , P.string "0" <|> many1 digit+    , opt $ (:) <$> char ':' <*> many1 digit+    , opt $ concat <$> sequence+        [ P.string "e" <|> P.string "E"+        , opt $ P.string "+" <|> P.string "-"+        , many1 digit+        ]+    ]+    where+    opt = option ""+-- }}} Underlaying ------------------------------------------------------------++-- {{{ JSON Values ------------------------------------------------------------+-- | Parse just JSON string and nothing more.+string :: Parser Value+string = String <$> baseString <?> "JSON string"++-- | Parse just JSON object and nothing more.+object :: Parser Value+object = Object . HashMap.fromList <$> p <?> "JSON object" where+    p = pickyBetween (char '{') (char '}') $ commaSeparated pair+    pair = (,) <$> (baseString <?> "JSON object key (string)")+        <*> (char ':' *> pickySpaces *> value)++-- | Parse just JSON array and nothing more.+array :: Parser Value+array = Array . Vector.fromList <$> p <?> "JSON array" where+    p = pickyBetween (char '[') (char ']') $ commaSeparated value++-- | Parse just JSON number and nothing more.+number :: Parser Value+number = Number <$> baseNumber <?> "JSON number"++-- | Parse just JSON bool and nothing more.+bool :: Parser Value+bool = Bool <$> (true <|> false) <?> "JSON bool (true|false)" where+    true = P.string "true" *> pure True+    false = P.string "false" *> pure False++-- | Parse just JSON null and nothing more.+null :: Parser Value+null = P.string "null" *> pure Null++-- | Parse any JSON value but nothing more.+value :: Parser Value+value = object <|> array <|> string <|> number <|> bool <|> null+-- }}} JSON Values ------------------------------------------------------------++-- | Convenience function to parse JSON.+eitherDecode :: FromJSON a => SourceName -> Text -> Either String a+eitherDecode s i = left show (parse jsonEof s i) >>= f where+    jsonEof = value <* optional newline <* eof+    f j = case fromJSON j of+        Success v -> return v+        Error e -> Left e