greskell-core (empty) → 0.1.0.0
raw patch · 11 files changed
+655/−0 lines, 11 filesdep +QuickCheckdep +aesondep +basesetup-changed
Dependencies added: QuickCheck, aeson, base, doctest, doctest-discover, greskell-core, hspec, scientific, text, unordered-containers
Files
- ChangeLog.md +5/−0
- LICENSE +30/−0
- README.md +9/−0
- Setup.hs +2/−0
- greskell-core.cabal +63/−0
- src/Data/Greskell/GraphSON.hs +164/−0
- src/Data/Greskell/Greskell.hs +251/−0
- test/Data/Greskell/GreskellSpec.hs +117/−0
- test/Data/Greskell/Test/QuickCheck.hs +12/−0
- test/DocTest.hs +1/−0
- test/Spec.hs +1/−0
+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for greskell-core++## 0.1.0.0 -- 2018-03-12++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2017, Toshio Ito++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 Toshio Ito 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,9 @@+# greskell-core++Haskell binding for Gremlin query language.++See the package description, or [project README](https://github.com/debug-ito/greskell/blob/master/README.md).++## Author++Toshio Ito <debug.ito@gmail.com>
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ greskell-core.cabal view
@@ -0,0 +1,63 @@+name: greskell-core+version: 0.1.0.0+author: Toshio Ito <debug.ito@gmail.com>+maintainer: Toshio Ito <debug.ito@gmail.com>+license: BSD3+license-file: LICENSE+synopsis: Haskell binding for Gremlin graph query language - core data types and tools+description: Haskell binding for [Gremlin graph query language](http://tinkerpop.apache.org/gremlin.html).+ See [README.md](https://github.com/debug-ito/greskell/blob/master/README.md) for detail.+ .+ This package contains only core data types and tools used commonly by other related packages.+category: Data+cabal-version: >= 1.10+build-type: Simple+extra-source-files: README.md, ChangeLog.md+homepage: https://github.com/debug-ito/greskell/+bug-reports: https://github.com/debug-ito/greskell/issues/++library+ default-language: Haskell2010+ hs-source-dirs: src+ ghc-options: -Wall -fno-warn-unused-imports+ -- default-extensions: + other-extensions: OverloadedStrings, TypeFamilies+ exposed-modules: Data.Greskell.Greskell,+ Data.Greskell.GraphSON+ -- other-modules: + build-depends: base >=4.9.0.0 && <4.11,+ aeson >=0.11.2.1 && <1.4,+ unordered-containers >=0.2.7.1 && <0.3,+ scientific >=0.3.4.9 && <0.4,+ text >=1.2.2.1 && <1.3++test-suite spec+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ hs-source-dirs: test+ ghc-options: -Wall -fno-warn-unused-imports "-with-rtsopts=-M512m"+ main-is: Spec.hs+ -- default-extensions: + other-extensions: OverloadedStrings+ other-modules: Data.Greskell.GreskellSpec,+ Data.Greskell.Test.QuickCheck+ build-depends: base, text, aeson,+ greskell-core,+ hspec >=2.2.3,+ QuickCheck >=2.8.2 && <2.12++test-suite doctest+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ hs-source-dirs: test+ ghc-options: -Wall -fno-warn-unused-imports "-with-rtsopts=-M512m"+ main-is: DocTest.hs+ build-depends: base,+ doctest >=0.11 && <0.15,+ doctest-discover >=0.1.0.7 && <0.2++++source-repository head+ type: git+ location: https://github.com/debug-ito/greskell.git
+ src/Data/Greskell/GraphSON.hs view
@@ -0,0 +1,164 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module: Data.Greskell.GraphSON+-- Description: Encoding and decoding GraphSON+-- Maintainer: Toshio Ito <debug.ito@gmail.com>+--+-- +module Data.Greskell.GraphSON+ ( -- * Type+ GraphSON(..),+ GraphSONTyped(..),+ -- * Constructors+ nonTypedGraphSON,+ typedGraphSON,+ typedGraphSON',+ -- * Parser support+ parseTypedGraphSON+ ) where++import Control.Applicative ((<$>), (<*>))+import Control.Monad (when)+import Data.Aeson (ToJSON(toJSON), FromJSON(parseJSON), object, (.=), Value(Object), (.:?))+import qualified Data.Aeson as Aeson+import Data.Aeson.Types (Parser)+import Data.Foldable (Foldable(foldr))+import qualified Data.HashMap.Lazy as HML+import Data.HashSet (HashSet)+import Data.Int (Int8, Int16, Int32, Int64)+import Data.Scientific (Scientific)+import Data.Text (Text)+import Data.Traversable (Traversable(traverse))++-- $+-- >>> :set -XOverloadedStrings++-- | Wrapper for \"typed JSON object\" introduced in GraphSON version+-- 2. See http://tinkerpop.apache.org/docs/current/dev/io/#graphson+--+-- This data type is useful for encoding/decoding GraphSON text.+-- +-- >>> Aeson.decode "1000" :: Maybe (GraphSON Int32)+-- Just (GraphSON {gsonType = Nothing, gsonValue = 1000})+-- >>> Aeson.decode "{\"@type\": \"g:Int32\", \"@value\": 1000}" :: Maybe (GraphSON Int32)+-- Just (GraphSON {gsonType = Just "g:Int32", gsonValue = 1000})+data GraphSON v =+ GraphSON+ { gsonType :: Maybe Text,+ -- ^ Type ID, corresponding to @\@type@ field.+ gsonValue :: v+ -- ^ Value, correspoding to @\@value@ field.+ }+ deriving (Show,Eq,Ord)++instance Functor GraphSON where+ fmap f gs = gs { gsonValue = f $ gsonValue gs }++instance Foldable GraphSON where+ foldr f start gs = f (gsonValue gs) start++instance Traversable GraphSON where+ traverse f gs = fmap (\v -> gs { gsonValue = v }) $ f $ gsonValue gs++-- | Create a 'GraphSON' without 'gsonType'.+--+-- >>> nonTypedGraphSON (10 :: Int)+-- GraphSON {gsonType = Nothing, gsonValue = 10}+nonTypedGraphSON :: v -> GraphSON v+nonTypedGraphSON = GraphSON Nothing++-- | Create a 'GraphSON' with its type ID.+--+-- >>> typedGraphSON (10 :: Int32)+-- GraphSON {gsonType = Just "g:Int32", gsonValue = 10}+typedGraphSON :: GraphSONTyped v => v -> GraphSON v+typedGraphSON v = GraphSON (Just $ gsonTypeFor v) v++-- | Create a 'GraphSON' with the given type ID.+--+-- >>> typedGraphSON' "g:Int32" (10 :: Int)+-- GraphSON {gsonType = Just "g:Int32", gsonValue = 10}+typedGraphSON' :: Text -> v -> GraphSON v+typedGraphSON' t = GraphSON (Just t)++-- | If 'gsonType' is 'Just', the 'GraphSON' is encoded as a typed+-- JSON object. If 'gsonType' is 'Nothing', the 'gsonValue' is+-- directly encoded.+instance ToJSON v => ToJSON (GraphSON v) where+ toJSON gson = case gsonType gson of+ Nothing -> toJSON $ gsonValue gson+ Just t -> object [ "@type" .= t,+ "@value" .= gsonValue gson+ ]++-- | If the given 'Value' is a typed JSON object, 'gsonType' field of+-- the result is 'Just'. Otherwise, the given 'Value' is directly+-- parsed into 'gsonValue', and 'gsonType' is 'Nothing'.+instance FromJSON v => FromJSON (GraphSON v) where+ parseJSON v@(Object o) = do+ if length o /= 2+ then parseDirect v+ else do+ mtype <- o .:? "@type"+ mvalue <- o .:? "@value"+ maybe (parseDirect v) return $ typedGraphSON' <$> mtype <*> mvalue+ parseJSON v = parseDirect v+ +parseDirect :: FromJSON v => Value -> Parser (GraphSON v)+parseDirect v = GraphSON Nothing <$> parseJSON v+++-- | Types that have an intrinsic type ID for 'gsonType' field.+class GraphSONTyped a where+ gsonTypeFor :: a -> Text+ -- ^ Type ID for 'gsonType'.++instance GraphSONTyped Char where+ gsonTypeFor _ = "gx:Char"++-- | Map to \"gx:Byte\". Note that Java's Byte is signed.+instance GraphSONTyped Int8 where+ gsonTypeFor _ = "gx:Byte"++instance GraphSONTyped Int16 where+ gsonTypeFor _ = "gx:Int16"++instance GraphSONTyped Int32 where+ gsonTypeFor _ = "g:Int32"++instance GraphSONTyped Int64 where+ gsonTypeFor _ = "g:Int64"++instance GraphSONTyped Float where+ gsonTypeFor _ = "g:Float"++instance GraphSONTyped Double where+ gsonTypeFor _ = "g:Double"++instance GraphSONTyped [a] where+ gsonTypeFor _ = "g:List"++-- | Map to \"g:Double\".+instance GraphSONTyped Scientific where+ gsonTypeFor _ = "g:Double"++-- | Note that Lazy HashMap and Strict HashMap are the same data type.+instance GraphSONTyped (HML.HashMap k v) where+ gsonTypeFor _ = "g:Map"++instance GraphSONTyped (HashSet a) where+ gsonTypeFor _ = "g:Set"+++-- | Parse @GraphSON v@, but it checks 'gsonType'. If 'gsonType' is+-- 'Nothing' or it's not equal to 'gsonTypeFor', the 'Parser' fails.+parseTypedGraphSON :: (GraphSONTyped v, FromJSON v) => Value -> Parser (GraphSON v)+parseTypedGraphSON v = checkType =<< parseJSON v+ where+ checkType gson = do+ let exp_type = gsonTypeFor $ gsonValue gson+ mgot_type = gsonType gson+ when (mgot_type /= Just exp_type) $ do+ fail ("Expected @type of " ++ show exp_type ++ ", but got " ++ show mgot_type)+ return gson+
+ src/Data/Greskell/Greskell.hs view
@@ -0,0 +1,251 @@+{-# LANGUAGE OverloadedStrings, TypeFamilies #-}+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}+-- |+-- Module: Data.Greskell.Greskell+-- Description: Low-level Gremlin script data type+-- Maintainer: Toshio Ito <debug.ito@gmail.com>+--+-- +module Data.Greskell.Greskell+ ( -- * Type+ Greskell,+ ToGreskell(..),+ -- * Conversions+ toGremlin,+ toGremlinLazy,+ -- * Literals+ --+ -- $literals+ string,+ true,+ false,+ list,+ single,+ number,+ value,+ -- * Unsafe constructors+ unsafeGreskell,+ unsafeGreskellLazy,+ unsafeFunCall,+ unsafeMethodCall+ ) where++import Data.Aeson (Value)+import qualified Data.Aeson as Aeson+import Data.Bifunctor (bimap)+import Data.Foldable (toList)+import qualified Data.HashMap.Lazy as HM+import Data.Monoid (Monoid(..), (<>))+import Data.Ratio (numerator, denominator, Rational)+import Data.Scientific (Scientific, coefficient, base10Exponent)+import Data.String (IsString(..))+import Data.List (intersperse)+import Data.Text (Text, pack, unpack)+import qualified Data.Text.Lazy as TL++-- $+-- >>> :set -XOverloadedStrings++-- | Gremlin expression of type @a@.+--+-- 'Greskell' is essentially just a piece of Gremlin script with a+-- phantom type. The type @a@ represents the type of data that the+-- script is supposed to evaluate to.+--+-- 'Eq' and 'Ord' instances compare Gremlin scripts, NOT the values+-- they evaluate to.+newtype Greskell a = Greskell { unGreskell :: TL.Text }+ deriving (Show,Eq,Ord)++-- | Same as 'string' except for the input and output type.+instance IsString a => IsString (Greskell a) where+ fromString = Greskell . TL.pack . escapeDQuotes++-- | Unsafely convert the phantom type.+instance Functor Greskell where+ fmap _ = Greskell . unGreskell++-- | Integer literals and numeric operation in Gremlin+instance Num a => Num (Greskell a) where+ (+) = biOp "+"+ (-) = biOp "-"+ (*) = biOp "*"+ negate (Greskell a) = Greskell ("-" <> paren a)+ abs (Greskell a) = Greskell ("java.lang.Math.abs" <> paren a)+ signum (Greskell a) = Greskell ("java.lang.Long.signum" <> paren a)+ fromInteger val = Greskell (TL.pack $ show val)+ +-- | Floating-point number literals and numeric operation in Gremlin+instance Fractional a => Fractional (Greskell a) where+ (/) = biOp "/"+ recip (Greskell a) = Greskell ("1.0/" <> paren a)+ fromRational rat = Greskell $ scriptOf numerator <> ".0/" <> scriptOf denominator+ where+ scriptOf accessor = TL.pack $ show $ accessor rat++-- | Monoidal operations on 'Greskell' assumes @String@ operations in+-- Gremlin. 'mempty' is the empty String, and 'mappend' is String+-- concatenation.+instance IsString a => Monoid (Greskell a) where+ mempty = fromString ""+ mappend = biOp "+"++-- | Something that can convert to 'Greskell'.+class ToGreskell a where+ type GreskellReturn a+ -- ^ type of return value by Greskell.+ toGreskell :: a -> Greskell (GreskellReturn a)++-- | It's just 'id'.+instance ToGreskell (Greskell a) where+ type GreskellReturn (Greskell a) = a+ toGreskell = id+++biOp :: TL.Text -> Greskell a -> Greskell a -> Greskell a+biOp operator (Greskell a) (Greskell b) = Greskell (paren a <> operator <> paren b)++paren :: TL.Text -> TL.Text+paren t = "(" <> t <> ")"++escapeDQuotes :: String -> String+escapeDQuotes orig = ('"' : (esc =<< orig)) ++ "\""+ where+ esc c = case c of+ '\n' -> "\\n"+ '\r' -> "\\r"+ '\t' -> "\\t"+ '\\' -> "\\\\"+ '"' -> "\\\""+ '$' -> "\\$"+ x -> [x]+ -- do we have to espace other characters?+++-- | Unsafely create a 'Greskell' of arbitrary type. The given Gremlin+-- script is printed as-is.+--+-- >>> toGremlin $ unsafeGreskell "x + 100"+-- "x + 100"+unsafeGreskell :: Text -- ^ Gremlin script+ -> Greskell a+unsafeGreskell = Greskell . TL.fromStrict++-- | Same as 'unsafeGreskell', but it takes lazy 'TL.Text'.+unsafeGreskellLazy :: TL.Text -- ^ Gremlin script+ -> Greskell a+unsafeGreskellLazy = Greskell+++-- $literals+--+-- Functions to create literals in Gremlin script. Use 'fromInteger'+-- to create integer literals. Use 'fromRational' or 'number' to+-- create floating-point data literals.++-- | Create a String literal in Gremlin script. The content is+-- automatically escaped.+--+-- >>> toGremlin $ string "foo bar"+-- "\"foo bar\""+-- >>> toGremlin $ string "escape newline\n escape dollar $"+-- "\"escape newline\\n escape dollar \\$\""+string :: Text -> Greskell Text+string = fromString . unpack++-- | Boolean @true@ literal.+--+-- >>> toGremlin true+-- "true"+true :: Greskell Bool+true = unsafeGreskell "true"++-- | Boolean @false@ literal.+--+-- >>> toGremlin false+-- "false"+false :: Greskell Bool+false = unsafeGreskell "false"++-- | List literal.+--+-- >>> toGremlin $ list ([100, 200, 300] :: [Greskell Int])+-- "[100,200,300]"+list :: [Greskell a] -> Greskell [a]+list gs = unsafeGreskellLazy $ ("[" <> TL.intercalate "," gs_txt <> "]")+ where+ gs_txt = map toGremlinLazy gs++-- | Make a list with a single object. Useful to prevent the Gremlin+-- Server from automatically iterating the result object.+--+-- >>> toGremlin $ single ("hoge" :: Greskell String)+-- "[\"hoge\"]"+single :: Greskell a -> Greskell [a]+single g = list [g]++-- | Arbitrary precision number literal, like \"123e8\".+--+-- >>> toGremlin $ number 123e8+-- "1.23e10"+number :: Scientific -> Greskell Scientific+number = unsafeGreskell . pack . show++-- | Aeson 'Value' literal.+--+-- >>> toGremlin $ value Aeson.Null+-- "null"+-- >>> toGremlin $ value $ Aeson.toJSON $ ([10, 20, 30] :: [Int])+-- "[10.0,20.0,30.0]"+-- >>> toGremlin $ value $ Aeson.Object mempty+-- "[:]"+value :: Value -> Greskell Value+value Aeson.Null = unsafeGreskellLazy "null"+value (Aeson.Bool b) = unsafeToValue (if b then true else false)+value (Aeson.Number sci) = unsafeToValue $ number sci+value (Aeson.String s) = unsafeToValue $ string s+value (Aeson.Array v) = unsafeToValue $ list $ map value $ toList v+value (Aeson.Object obj)+ | HM.null obj = unsafeGreskellLazy "[:]"+ | otherwise = unsafeGreskellLazy $ toGroovyMap $ HM.toList obj+ where+ toGroovyMap pairs = "[" <> TL.intercalate "," (map toPairText pairs) <> "]"+ toPairText (key, val) = (toGremlinLazy $ string key) <> ":" <> (toGremlinLazy $ value val)+++unsafeToValue :: Greskell a -> Greskell Value+unsafeToValue = fmap (const Aeson.Null)++-- | Create a readable Gremlin script from 'Greskell'.+toGremlin :: ToGreskell a => a -> Text+toGremlin = TL.toStrict . unGreskell . toGreskell++-- | Same as 'toGremlin' except that this returns lazy 'TL.Text'.+toGremlinLazy :: ToGreskell a => a -> TL.Text+toGremlinLazy = unGreskell . toGreskell++unsafeFunCallText :: Text -> [Text] -> Text+unsafeFunCallText fun_name args = fun_name <> "(" <> args_g <> ")"+ where+ args_g = mconcat $ intersperse "," args++-- | Unsafely create a 'Greskell' that calls the given function with+-- the given arguments.+--+-- >>> toGremlin $ unsafeFunCall "add" ["10", "20"]+-- "add(10,20)"+unsafeFunCall :: Text -- ^ function name+ -> [Text] -- ^ arguments+ -> Greskell a -- ^ return value of the function call+unsafeFunCall fun_name args = unsafeGreskell $ unsafeFunCallText fun_name args++-- | Unsafely create a 'Greskell' that calls the given object method+-- call with the given target and arguments.+--+-- >>> toGremlin $ unsafeMethodCall ("foobar" :: Greskell String) "length" []+-- "(\"foobar\").length()"+unsafeMethodCall :: Greskell a -- ^ target object+ -> Text -- ^ method name+ -> [Text] -- ^ arguments+ -> Greskell b -- ^ return value of the method call+unsafeMethodCall target name args = unsafeGreskell ("(" <> toGremlin target <> ")." <> unsafeFunCallText name args)
+ test/Data/Greskell/GreskellSpec.hs view
@@ -0,0 +1,117 @@+{-# LANGUAGE OverloadedStrings #-}+module Data.Greskell.GreskellSpec (main,spec) where++import qualified Data.Aeson as Aeson+import Data.String (fromString)+import Data.Text (Text, pack)+import Test.Hspec+import Test.QuickCheck (property)++import Data.Greskell.Greskell+ ( unsafeGreskell, toGremlin,+ unsafeFunCall,+ string, list, true, false, number, value,+ Greskell+ )++import Data.Greskell.Test.QuickCheck ()++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ spec_literals+ spec_other++spec_other :: Spec+spec_other = do+ describe "unsafeGreskell" $ it "should be just a raw script text" $ property $ \t ->+ (toGremlin $ unsafeGreskell t) `shouldBe` t+ describe "Num" $ do+ specify "integer" $ do+ let x = 123 :: Greskell Int+ toGremlin x `shouldBe` "123"+ specify "negative integer" $ do+ let x = -56 :: Greskell Int+ toGremlin x `shouldBe` "-(56)"+ specify "operations" $ do+ let x = (30 + 15 * 20 - 10) :: Greskell Int+ toGremlin x `shouldBe` "((30)+((15)*(20)))-(10)"+ specify "abs, signum" $ do+ let x = (signum $ abs (-100)) :: Greskell Int+ toGremlin x `shouldBe` "java.lang.Long.signum(java.lang.Math.abs(-(100)))"+ describe "Fractional" $ do+ specify "floating point literal" $ do+ let x = 92.12 :: Greskell Double+ (toGremlin x) `shouldBe` "2303.0/25"+ specify "operations" $ do+ let x = (100.5 * recip 30.0 / 20.2) :: Greskell Double+ toGremlin x `shouldBe` "((201.0/2)*(1.0/(30.0/1)))/(101.0/5)"+ describe "Monoid" $ do+ specify "mempty" $ do+ let got = mempty :: Greskell Text+ toGremlin got `shouldBe` "\"\""+ specify "mappend" $ do+ let got = (mappend "foo" "bar") :: Greskell Text+ toGremlin got `shouldBe` "(\"foo\")+(\"bar\")"+ describe "unsafeFunCall" $ do+ it "should make function call" $ do+ (toGremlin $ unsafeFunCall "fun" ["foo", "bar"]) `shouldBe` "fun(foo,bar)"++spec_literals :: Spec+spec_literals = do+ describe "string and fromString" $ do+ specify "empty" $ checkStringLiteral "" "\"\""+ specify "words" $ checkStringLiteral "hoge foo bar" "\"hoge foo bar\""+ specify "escaped" $ checkStringLiteral "foo 'aaa \n \t \\ \"bar\"" "\"foo 'aaa \\n \\t \\\\ \\\"bar\\\"\""+ describe "list" $ do+ specify "empty" $ do+ toGremlin (list []) `shouldBe` "[]"+ specify "num" $ do+ toGremlin (list $ [(10 :: Greskell Int), 20, 30]) `shouldBe` "[10,20,30]"+ specify "list of lists" $ do+ toGremlin (list $ map list $ [[("" :: Greskell Text)], ["foo", "bar"], ["buzz"]])+ `shouldBe` "[[\"\"],[\"foo\",\"bar\"],[\"buzz\"]]"+ describe "boolean" $ do+ specify "true" $ do+ toGremlin true `shouldBe` "true"+ specify "false" $ do+ toGremlin false `shouldBe` "false"+ describe "number" $ do+ specify "zero" $ do+ toGremlin (number 0) `shouldBe` "0.0"+ specify "positive integer" $ do+ toGremlin (number 1234) `shouldBe` "1234.0"+ specify "negative integer" $ do+ toGremlin (number (-292)) `shouldBe` "-292.0"+ specify "positive floating" $ do+ toGremlin (number 32.123) `shouldBe` "32.123"+ specify "negative floating" $ do+ toGremlin (number (-0.0943)) `shouldBe` "-9.43e-2"+ specify "big positive integer" $ do+ toGremlin (number 3.23e9) `shouldBe` "3.23e9"+ describe "value" $ do+ specify "null" $ do+ toGremlin (value Aeson.Null) `shouldBe` "null"+ specify "bool" $ do+ toGremlin (value $ Aeson.Bool False) `shouldBe` "false"+ specify "integer" $ do+ toGremlin (value $ Aeson.Number 100) `shouldBe` "100.0"+ specify "floating-point number" $ do+ toGremlin (value $ Aeson.Number 10.23) `shouldBe` "10.23"+ specify "String" $ do+ toGremlin (value $ Aeson.String "foobar") `shouldBe` "\"foobar\""+ specify "empty Array" $ do+ toGremlin (value $ Aeson.toJSON ([] :: [Int])) `shouldBe` "[]"+ specify "non-empty Array" $ do+ toGremlin (value $ Aeson.toJSON [(5 :: Int), 6, 7]) `shouldBe` "[5.0,6.0,7.0]"+ specify "empty Object" $ do+ toGremlin (value $ Aeson.object []) `shouldBe` "[:]"+ ++checkStringLiteral :: String -> Text -> Expectation+checkStringLiteral input expected = do+ let input' = fromString input :: Greskell Text+ (toGremlin $ input') `shouldBe` expected+ (toGremlin $ string $ pack input) `shouldBe` expected
+ test/Data/Greskell/Test/QuickCheck.hs view
@@ -0,0 +1,12 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- | QuickCheck orphan instances and other utility.+module Data.Greskell.Test.QuickCheck+ () where++import Data.Text (Text, pack)+import Test.QuickCheck (Arbitrary(..))++instance Arbitrary Text where+ arbitrary = fmap pack arbitrary++
+ test/DocTest.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF doctest-discover #-}
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}