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 +9/−0
- LICENSE +30/−0
- README.md +112/−0
- Setup.hs +2/−0
- aeson-parsec-picky.cabal +82/−0
- src/Data/Aeson/Parser/Parsec/Picky.hs +183/−0
+ 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