diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..84cef4f --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,28 @@ +{ + "parserOptions": { + "ecmaVersion": 5 + }, + "extends": "eslint:recommended", + "env": { + "commonjs": true + }, + "rules": { + "strict": [2, "global"], + "block-scoped-var": 2, + "consistent-return": 2, + "eqeqeq": [2, "smart"], + "guard-for-in": 2, + "no-caller": 2, + "no-extend-native": 2, + "no-loop-func": 2, + "no-new": 2, + "no-param-reassign": 2, + "no-return-assign": 2, + "no-unused-expressions": 2, + "no-use-before-define": 2, + "radix": [2, "always"], + "indent": [2, 2], + "quotes": [2, "double"], + "semi": [2, "always"] + } +} diff --git a/.gitignore b/.gitignore index e306283..7050558 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ /.* !/.gitignore -!/.jscsrc -!/.jshintrc +!/.eslintrc.json !/.travis.yml /bower_components/ /node_modules/ diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index 2561ce9..0000000 --- a/.jscsrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "preset": "grunt", - "disallowSpacesInFunctionExpression": null, - "requireSpacesInFunctionExpression": { - "beforeOpeningRoundBrace": true, - "beforeOpeningCurlyBrace": true - }, - "disallowSpacesInAnonymousFunctionExpression": null, - "requireSpacesInAnonymousFunctionExpression": { - "beforeOpeningRoundBrace": true, - "beforeOpeningCurlyBrace": true - }, - "disallowSpacesInsideObjectBrackets": null, - "requireSpacesInsideObjectBrackets": "all", - "validateQuoteMarks": "\"", - "requireCurlyBraces": null -} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 620d8d7..0000000 --- a/.jshintrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "bitwise": true, - "eqeqeq": true, - "forin": true, - "freeze": true, - "funcscope": true, - "futurehostile": true, - "strict": "global", - "latedef": true, - "maxparams": 1, - "noarg": true, - "nocomma": true, - "nonew": true, - "notypeof": true, - "singleGroups": true, - "undef": true, - "unused": true, - "eqnull": true, - "predef": ["exports"] -} diff --git a/.travis.yml b/.travis.yml index 3455305..4cbd5fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: node_js dist: trusty sudo: required -node_js: 6 +node_js: stable env: - PATH=$HOME/purescript:$PATH install: diff --git a/bower.json b/bower.json index 7fde5b8..4a2a46b 100644 --- a/bower.json +++ b/bower.json @@ -21,16 +21,16 @@ "package.json" ], "dependencies": { - "purescript-arrays": "^3.0.0", - "purescript-either": "^2.0.0", - "purescript-foldable-traversable": "^2.0.0", - "purescript-functions": "^2.0.0", - "purescript-integers": "^2.0.0", - "purescript-lists": "^3.0.0", - "purescript-strings": "^2.0.0", - "purescript-transformers": "^2.0.0" + "purescript-arrays": "^4.0.0", + "purescript-either": "^3.0.0", + "purescript-foldable-traversable": "^3.0.0", + "purescript-functions": "^3.0.0", + "purescript-integers": "^3.0.0", + "purescript-lists": "^4.0.0", + "purescript-strings": "^3.0.0", + "purescript-transformers": "^3.0.0" }, "devDependencies": { - "purescript-console": "^2.0.0" + "purescript-console": "^3.0.0" } } diff --git a/examples/Applicative.purs b/examples/Applicative.purs index 18cbc72..ed082b9 100755 --- a/examples/Applicative.purs +++ b/examples/Applicative.purs @@ -6,19 +6,24 @@ import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, logShow) import Control.Monad.Except (runExcept) -import Data.Foreign (F) -import Data.Foreign.Class (class IsForeign, readJSON, readProp) +import Data.Foreign (F, Foreign, readNumber) +import Data.Foreign.Index ((!)) + +import Example.Util.Value (foreignValue) data Point = Point Number Number Number instance showPoint :: Show Point where show (Point x y z) = "(Point " <> show [x, y, z] <> ")" -instance pointIsForeign :: IsForeign Point where - read value = Point <$> readProp "x" value - <*> readProp "y" value - <*> readProp "z" value +readPoint :: Foreign -> F Point +readPoint value = do + Point + <$> (value ! "x" >>= readNumber) + <*> (value ! "y" >>= readNumber) + <*> (value ! "z" >>= readNumber) main :: Eff (console :: CONSOLE) Unit -main = logShow $ runExcept $ - readJSON """{ "x": 1, "y": 2, "z": 3 }""" :: F Point +main = + logShow $ runExcept $ + readPoint =<< foreignValue """{ "x": 1, "y": 2, "z": 3 }""" diff --git a/examples/Arrays.purs b/examples/Arrays.purs new file mode 100644 index 0000000..25bb108 --- /dev/null +++ b/examples/Arrays.purs @@ -0,0 +1,19 @@ +module Example.Arrays where + +import Prelude + +import Control.Monad.Eff (Eff) +import Control.Monad.Eff.Console (CONSOLE, logShow) +import Control.Monad.Except (runExcept) + +import Data.Foreign (readArray, readNumber, readString) +import Data.Traversable (traverse) + +import Example.Util.Value (foreignValue) + +main :: Eff (console :: CONSOLE) Unit +main = do + logShow $ runExcept $ + traverse readString =<< readArray =<< foreignValue """["hello", "world"]""" + logShow $ runExcept $ + traverse readNumber =<< readArray =<< foreignValue """[1, 2, 3, 4]""" diff --git a/examples/Complex.purs b/examples/Complex.purs index 588f23b..4093181 100755 --- a/examples/Complex.purs +++ b/examples/Complex.purs @@ -6,15 +6,20 @@ import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, logShow) import Control.Monad.Except (runExcept) -import Data.Foreign (F) -import Data.Foreign.Class (class IsForeign, readJSON, readProp) -import Data.Foreign.NullOrUndefined (unNullOrUndefined) +import Data.Foreign (F, Foreign, readArray, readBoolean, readNumber, readString, readNullOrUndefined) +import Data.Foreign.Index ((!)) +import Data.Traversable (traverse) import Data.Maybe (Maybe) -data SomeObject = SomeObject { foo :: String - , bar :: Boolean - , baz :: Number - , list :: Array ListItem } +import Example.Util.Value (foreignValue) + +newtype SomeObject = + SomeObject + { foo :: String + , bar :: Boolean + , baz :: Number + , list :: Array ListItem + } instance showSomeObject :: Show SomeObject where show (SomeObject o) = @@ -22,19 +27,22 @@ instance showSomeObject :: Show SomeObject where ", bar: " <> show o.bar <> ", baz: " <> show o.baz <> ", list: " <> show o.list <> - " })" + "})" -instance objectIsForeign :: IsForeign SomeObject where - read value = do - foo <- readProp "foo" value - bar <- readProp "bar" value - baz <- readProp "baz" value - list <- readProp "list" value - pure $ SomeObject { foo: foo, bar: bar, baz: baz, list: list } +readSomeObject :: Foreign -> F SomeObject +readSomeObject value = do + foo <- value ! "foo" >>= readString + bar <- value ! "bar" >>= readBoolean + baz <- value ! "baz" >>= readNumber + list <- value ! "list" >>= readArray >>= traverse readListItem + pure $ SomeObject { foo, bar, baz, list } -data ListItem = ListItem { x :: Number - , y :: Number - , z :: Maybe Number } +newtype ListItem = + ListItem + { x :: Number + , y :: Number + , z :: Maybe Number + } instance showListItem :: Show ListItem where show (ListItem o) = @@ -43,14 +51,14 @@ instance showListItem :: Show ListItem where ", z: " <> show o.z <> " })" -instance listItemIsForeign :: IsForeign ListItem where - read value = do - x <- readProp "x" value - y <- readProp "y" value - z <- unNullOrUndefined <$> readProp "z" value - pure $ ListItem { x: x, y: y, z: z } +readListItem :: Foreign -> F ListItem +readListItem value = do + x <- value ! "x" >>= readNumber + y <- value ! "y" >>= readNumber + z <- value ! "z" >>= readNullOrUndefined >>= traverse readNumber + pure $ ListItem { x, y, z } main :: Eff (console :: CONSOLE) Unit main = do let json = """{"foo":"hello","bar":true,"baz":1,"list":[{"x":1,"y":2},{"x":3,"y":4,"z":999}]}""" - logShow $ runExcept $ readJSON json :: F SomeObject + logShow $ runExcept $ readSomeObject =<< foreignValue json diff --git a/examples/Either.purs b/examples/Either.purs deleted file mode 100644 index 05ac773..0000000 --- a/examples/Either.purs +++ /dev/null @@ -1,33 +0,0 @@ -module Examples.Either where - -import Prelude - -import Control.Monad.Eff (Eff) -import Control.Monad.Eff.Console (logShow, CONSOLE) -import Control.Monad.Except (runExcept) - -import Data.Either (Either) -import Data.Foreign (F, parseJSON) -import Data.Foreign.Class (class IsForeign, readEitherR, readProp) - -data Point = Point Number Number Number - -instance showPoint :: Show Point where - show (Point x y z) = "Point " <> show [x, y, z] - -instance pointIsForeign :: IsForeign Point where - read value = Point <$> readProp "x" value - <*> readProp "y" value - <*> readProp "z" value - -type Response = Either (Array String) Point - -main :: forall eff. Eff (console :: CONSOLE | eff) Unit -main = do - logShow $ runExcept do - json <- parseJSON """{ "x":1, "y": 2, "z": 3}""" - readEitherR json :: F Response - - logShow $ runExcept do - json <- parseJSON """["Invalid parse", "Not a valid y point"]""" - readEitherR json :: F Response diff --git a/examples/JSONArrays.purs b/examples/JSONArrays.purs deleted file mode 100755 index fbc7645..0000000 --- a/examples/JSONArrays.purs +++ /dev/null @@ -1,15 +0,0 @@ -module Example.JSONArrays where - -import Prelude - -import Control.Monad.Eff (Eff) -import Control.Monad.Eff.Console (CONSOLE, logShow) -import Control.Monad.Except (runExcept) - -import Data.Foreign (F) -import Data.Foreign.Class (readJSON) - -main :: Eff (console :: CONSOLE) Unit -main = do - logShow $ runExcept $ readJSON """["hello", "world"]""" :: F (Array String) - logShow $ runExcept $ readJSON """[1, 2, 3, 4]""" :: F (Array Number) diff --git a/examples/JSONSimpleTypes.purs b/examples/JSONSimpleTypes.purs deleted file mode 100755 index faa2350..0000000 --- a/examples/JSONSimpleTypes.purs +++ /dev/null @@ -1,21 +0,0 @@ -module Example.JSONSimpleTypes where - -import Prelude - -import Control.Monad.Eff (Eff) -import Control.Monad.Eff.Console (CONSOLE, log, logShow) -import Control.Monad.Except (runExcept) - -import Data.Foreign (F, unsafeFromForeign) -import Data.Foreign.Class (readJSON, write) - --- Parsing of the simple JSON String, Number and Boolean types is provided --- out of the box. -main :: Eff (console :: CONSOLE) Unit -main = do - logShow $ runExcept $ readJSON "\"a JSON string\"" :: F String - logShow $ runExcept $ readJSON "42" :: F Number - logShow $ runExcept $ readJSON "true" :: F Boolean - log $ unsafeFromForeign $ write "a JSON string" - log $ unsafeFromForeign $ write 42.0 - log $ unsafeFromForeign $ write true diff --git a/examples/MaybeNullable.purs b/examples/MaybeNullable.purs index 8d53fce..d9bd790 100755 --- a/examples/MaybeNullable.purs +++ b/examples/MaybeNullable.purs @@ -3,19 +3,19 @@ module Example.MaybeNullable where import Prelude import Control.Monad.Eff (Eff) -import Control.Monad.Eff.Console (CONSOLE, log, logShow) +import Control.Monad.Eff.Console (CONSOLE, logShow) import Control.Monad.Except (runExcept) -import Data.Foreign (F, unsafeFromForeign) -import Data.Foreign.Class (readJSON, write) -import Data.Foreign.Null (Null(..), unNull) -import Data.Maybe (Maybe(..)) +import Data.Foreign (readBoolean, readNull) +import Data.Traversable (traverse) + +import Example.Util.Value (foreignValue) -- Parsing values that are allowed to null or undefined is possible by -- using Maybe types. main :: Eff (console :: CONSOLE) Unit main = do - logShow $ unNull <$> runExcept (readJSON "null" :: F (Null Boolean)) - logShow $ unNull <$> runExcept (readJSON "true" :: F (Null Boolean)) - log $ unsafeFromForeign $ write $ Null Nothing :: Null Boolean - log $ unsafeFromForeign $ write $ Null $ Just true + logShow $ runExcept $ + traverse readBoolean =<< readNull =<< foreignValue "null" + logShow $ runExcept $ + traverse readBoolean =<< readNull =<< foreignValue "true" diff --git a/examples/Nested.purs b/examples/Nested.purs index dd1a707..3a04216 100755 --- a/examples/Nested.purs +++ b/examples/Nested.purs @@ -6,9 +6,10 @@ import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, logShow) import Control.Monad.Except (runExcept) -import Data.Foreign (F) -import Data.Foreign.Class (class IsForeign, readJSON, readProp) -import Data.Foreign.Index (prop) +import Data.Foreign (F, Foreign, readNumber, readString) +import Data.Foreign.Index ((!)) + +import Example.Util.Value (foreignValue) data Foo = Foo Bar Baz @@ -25,12 +26,13 @@ instance showBar :: Show Bar where instance showBaz :: Show Baz where show (Baz n) = "(Baz " <> show n <> ")" -instance fooIsForeign :: IsForeign Foo where - read value = do - s <- value # (prop "foo" >=> readProp "bar") - n <- value # (prop "foo" >=> readProp "baz") - pure $ Foo (Bar s) (Baz n) +readFoo :: Foreign -> F Foo +readFoo value = do + s <- value ! "foo" ! "bar" >>= readString + n <- value ! "foo" ! "baz" >>= readNumber + pure $ Foo (Bar s) (Baz n) main :: Eff (console :: CONSOLE) Unit -main = do - logShow $ runExcept $ readJSON """{ "foo": { "bar": "bar", "baz": 1 } }""" :: F Foo +main = + logShow $ runExcept $ + readFoo =<< foreignValue """{ "foo": { "bar": "bar", "baz": 1 } }""" diff --git a/examples/Objects.purs b/examples/Objects.purs index eb5336a..0fa094e 100755 --- a/examples/Objects.purs +++ b/examples/Objects.purs @@ -3,34 +3,27 @@ module Example.Objects where import Prelude import Control.Monad.Eff (Eff) -import Control.Monad.Eff.Console (CONSOLE, log, logShow) +import Control.Monad.Eff.Console (CONSOLE, logShow) import Control.Monad.Except (runExcept) -import Data.Foreign (F, writeObject, unsafeFromForeign) -import Data.Foreign.Class (class AsForeign, class IsForeign, (.=), readJSON, readProp, write) +import Data.Foreign (F, Foreign, readNumber) +import Data.Foreign.Index ((!)) --- | To parse objects of a particular type, we need to define some helper --- data types as making class instances for records is not possible. -data Point = Point { x :: Number, y :: Number } +import Example.Util.Value (foreignValue) + +newtype Point = Point { x :: Number, y :: Number } instance showPoint :: Show Point where - show (Point o) = "(Point { x: " <> show o.x <> ", y: " <> show o.y <> " })" - -instance pointAsForeign :: AsForeign Point where - write (Point o) = writeObject [ "x" .= o.x - , "y" .= o.y - ] - --- | The IsForeign implementations for these types are basically boilerplate, --- type inference takes care of most of the work so we don't have to --- explicitly define the type each of the properties we're parsing. -instance pointIsForeign :: IsForeign Point where - read value = do - x <- readProp "x" value - y <- readProp "y" value - pure $ Point { x: x, y: y } + show (Point { x, y }) = + "(Point { x: " <> show x <> ", y: " <> show y <> " })" + +readPoint :: Foreign -> F Point +readPoint value = do + x <- value ! "x" >>= readNumber + y <- value ! "y" >>= readNumber + pure $ Point { x, y } main :: Eff (console :: CONSOLE) Unit main = do - logShow $ runExcept $ readJSON """{ "x": 1, "y": 2 }""" :: F Point - log $ unsafeFromForeign $ write $ Point { x: 1.0, y: 2.0 } + logShow $ runExcept $ + readPoint =<< foreignValue """{ "x": 1, "y": 2 }""" diff --git a/examples/ParseErrors.purs b/examples/ParseErrors.purs index 4ffbc30..a3418c9 100755 --- a/examples/ParseErrors.purs +++ b/examples/ParseErrors.purs @@ -6,36 +6,41 @@ import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, logShow) import Control.Monad.Except (runExcept) -import Data.Foreign (F) -import Data.Foreign.Class (class IsForeign, readJSON, readProp) +import Data.Foreign (F, Foreign, readArray, readBoolean, readNumber, readString) +import Data.Foreign.Index ((!)) +import Data.Traversable (traverse) --- | See the `Objects` example for an explanation of how to parse objects --- like this. -data Point = Point { x :: Number, y :: Number } +import Example.Util.Value (foreignValue) + +newtype Point = Point { x :: Number, y :: Number } instance showPoint :: Show Point where show (Point o) = "(Point { x: " <> show o.x <> ", y: " <> show o.y <> " })" -instance pointIsForeign :: IsForeign Point where - read value = do - x <- readProp "x" value - y <- readProp "y" value - pure $ Point { x: x, y: y } +readPoint :: Foreign -> F Point +readPoint value = do + x <- value ! "x" >>= readNumber + y <- value ! "y" >>= readNumber + pure $ Point { x: x, y: y } main :: Eff (console :: CONSOLE) Unit main = do -- When trying to parse invalid JSON we catch an exception from -- `JSON.parse` and pass it on. - logShow $ runExcept $ readJSON "not even JSON" :: F String + logShow $ runExcept $ + readString =<< foreignValue "not even JSON" -- When attempting to coerce one type to another we get an error. - logShow $ runExcept $ readJSON "26" :: F Boolean + logShow $ runExcept $ + readBoolean =<< foreignValue "26" -- When parsing fails in an array, we're told at which index the value that -- failed to parse was, along with the reason the value didn't parse. - logShow $ runExcept $ readJSON "[1, true, 3]" :: F (Array Boolean) + logShow $ runExcept $ + traverse readBoolean =<< readArray =<< foreignValue "[1, true, 3]" -- When parsing fails in an object, we're the name of the property which -- failed to parse was, along with the reason the value didn't parse. - logShow $ runExcept $ readJSON """{ "x": 1, "y": false }""" :: F Point + logShow $ runExcept $ + readPoint =<< foreignValue """{ "x": 1, "y": false }""" diff --git a/examples/SimpleTypes.purs b/examples/SimpleTypes.purs new file mode 100644 index 0000000..97233d7 --- /dev/null +++ b/examples/SimpleTypes.purs @@ -0,0 +1,22 @@ +module Example.SimpleTypes where + +import Prelude + +import Control.Monad.Eff (Eff) +import Control.Monad.Eff.Console (CONSOLE, logShow) +import Control.Monad.Except (runExcept) + +import Data.Foreign (readString, readNumber, readBoolean) + +import Example.Util.Value (foreignValue) + +-- Parsing of the simple JSON String, Number and Boolean types is provided +-- out of the box. +main :: Eff (console :: CONSOLE) Unit +main = do + logShow $ runExcept $ + readString =<< foreignValue "\"a JSON string\"" + logShow $ runExcept $ + readNumber =<< foreignValue "42" + logShow $ runExcept $ + readBoolean =<< foreignValue "true" diff --git a/examples/Union.purs b/examples/Union.purs index e65e066..ff9a56b 100755 --- a/examples/Union.purs +++ b/examples/Union.purs @@ -6,8 +6,10 @@ import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, logShow) import Control.Monad.Except (runExcept) -import Data.Foreign (F) -import Data.Foreign.Class (class IsForeign, readJSON, readProp) +import Data.Foreign (F, Foreign, readBoolean, readString) +import Data.Foreign.Index ((!)) + +import Example.Util.Value (foreignValue) data StringList = Nil | Cons String StringList @@ -15,37 +17,44 @@ instance showStringList :: Show StringList where show Nil = "Nil" show (Cons s l) = "(Cons " <> show s <> " " <> show l <> ")" -instance stringListIsForeign :: IsForeign StringList where - read value = do - nil <- readProp "nil" value - if nil +readStringList :: Foreign -> F StringList +readStringList value = + value ! "nil" >>= + readBoolean >>= + if _ then pure Nil - else Cons <$> readProp "head" value - <*> readProp "tail" value + else + Cons + <$> (value ! "head" >>= readString) + <*> (value ! "tail" >>= readStringList) main :: Eff (console :: CONSOLE) Unit main = do - logShow $ runExcept $ readJSON """ - { "nil": false - , "head": "Hello" - , "tail": + logShow $ runExcept $ + readStringList =<< foreignValue + """ { "nil": false - , "head": "World" + , "head": "Hello" , "tail": - { "nil": true } + { "nil": false + , "head": "World" + , "tail": + { "nil": true } + } } - } - """ :: F StringList + """ - logShow $ runExcept $ readJSON """ - { "nil": false - , "head": "Hello" - , "tail": + logShow $ runExcept $ + readStringList =<< foreignValue + """ { "nil": false - , "head": 0 + , "head": "Hello" , "tail": - { "nil": true } + { "nil": false + , "head": 0 + , "tail": + { "nil": true } + } } - } - """ :: F StringList + """ diff --git a/examples/Util/Value.js b/examples/Util/Value.js new file mode 100644 index 0000000..d1a5254 --- /dev/null +++ b/examples/Util/Value.js @@ -0,0 +1,9 @@ +"use strict"; + +exports.foreignValueImpl = function (left, right, str) { + try { + return right(JSON.parse(str)); + } catch (e) { + return left(e.toString()); + } +}; diff --git a/examples/Util/Value.purs b/examples/Util/Value.purs new file mode 100644 index 0000000..bc6516e --- /dev/null +++ b/examples/Util/Value.purs @@ -0,0 +1,12 @@ +module Example.Util.Value where + +import Prelude + +import Data.Function.Uncurried (Fn3, runFn3) + +import Data.Foreign (F, Foreign, ForeignError(..), fail) + +foreign import foreignValueImpl :: forall r. Fn3 (String -> r) (Foreign -> r) String r + +foreignValue :: String -> F Foreign +foreignValue json = runFn3 foreignValueImpl (fail <<< JSONError) pure json diff --git a/package.json b/package.json index 6b429b8..e499ea4 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,13 @@ "private": true, "scripts": { "clean": "rimraf output && rimraf .pulp-cache", - "build": "jshint src && jscs src && pulp build --censor-lib --strict", - "test": "pulp build -I examples --censor-lib --strict" + "build": "eslint src && pulp build -- --censor-lib --strict", + "test": "pulp build -I examples -- --censor-lib --strict" }, "devDependencies": { - "jscs": "^3.0.7", - "jshint": "^2.9.2", - "pulp": "^9.0.1", - "purescript-psa": "^0.3.9", - "rimraf": "^2.5.4" + "eslint": "^3.17.1", + "pulp": "^10.0.4", + "purescript-psa": "^0.5.0-rc.1", + "rimraf": "^2.6.1" } } diff --git a/src/Data/Foreign.js b/src/Data/Foreign.js index 4e0b895..e511096 100644 --- a/src/Data/Foreign.js +++ b/src/Data/Foreign.js @@ -1,16 +1,5 @@ -/* global exports */ "use strict"; -// jshint maxparams: 3 -exports.parseJSONImpl = function (left, right, str) { - try { - return right(JSON.parse(str)); - } catch (e) { - return left(e.toString()); - } -}; -// jshint maxparams: 1 - exports.toForeign = function (value) { return value; }; @@ -38,11 +27,3 @@ exports.isUndefined = function (value) { exports.isArray = Array.isArray || function (value) { return Object.prototype.toString.call(value) === "[object Array]"; }; - -exports.writeObject = function (fields) { - var record = {}; - for (var i = 0; i < fields.length; i++) { - record[fields[i].key] = fields[i].value; - } - return record; -}; diff --git a/src/Data/Foreign.purs b/src/Data/Foreign.purs index ce697ce..ef02f2a 100644 --- a/src/Data/Foreign.purs +++ b/src/Data/Foreign.purs @@ -5,10 +5,8 @@ module Data.Foreign ( Foreign , ForeignError(..) , MultipleErrors(..) - , Prop(..) , F , renderForeignError - , parseJSON , toForeign , unsafeFromForeign , unsafeReadTagged @@ -23,8 +21,10 @@ module Data.Foreign , readNumber , readInt , readArray + , readNull + , readUndefined + , readNullOrUndefined , fail - , writeObject ) where import Prelude @@ -32,11 +32,10 @@ import Prelude import Control.Monad.Except (Except, throwError, mapExcept) import Data.Either (Either(..), either) -import Data.Function.Uncurried (Fn3, runFn3) import Data.Int as Int import Data.List.NonEmpty (NonEmptyList) import Data.List.NonEmpty as NEL -import Data.Maybe (maybe) +import Data.Maybe (Maybe(..), maybe) import Data.String (toChar) -- | A type for _foreign data_. @@ -49,7 +48,7 @@ import Data.String (toChar) -- | -- | - To represent responses from web services -- | - To integrate with external JavaScript libraries. -foreign import data Foreign :: * +foreign import data Foreign :: Type -- | A type for foreign type errors data ForeignError @@ -84,13 +83,7 @@ renderForeignError (TypeMismatch exp act) = "Type mismatch: expected " <> exp <> -- | -- | The `Alt` instance for `Except` allows us to accumulate errors, -- | unlike `Either`, which preserves only the last error. -type F a = Except MultipleErrors a - -foreign import parseJSONImpl :: forall r. Fn3 (String -> r) (Foreign -> r) String r - --- | Attempt to parse a JSON string, returning the result as foreign data. -parseJSON :: String -> F Foreign -parseJSON json = runFn3 parseJSONImpl (fail <<< JSONError) pure json +type F = Except MultipleErrors -- | Coerce any value to the a `Foreign` value. foreign import toForeign :: forall a. a -> Foreign @@ -154,13 +147,21 @@ readArray value | isArray value = pure $ unsafeFromForeign value | otherwise = fail $ TypeMismatch "array" (tagOf value) +readNull :: Foreign -> F (Maybe Foreign) +readNull value + | isNull value = pure Nothing + | otherwise = pure (Just value) + +readUndefined :: Foreign -> F (Maybe Foreign) +readUndefined value + | isUndefined value = pure Nothing + | otherwise = pure (Just value) + +readNullOrUndefined :: Foreign -> F (Maybe Foreign) +readNullOrUndefined value + | isNull value || isUndefined value = pure Nothing + | otherwise = pure (Just value) + -- | Throws a failure error in `F`. fail :: forall a. ForeignError -> F a fail = throwError <<< NEL.singleton - --- | A key/value pair for an object to be written as a `Foreign` value. -newtype Prop = Prop { key :: String, value :: Foreign } - --- | Constructs a JavaScript `Object` value (typed as `Foreign`) from an array --- | of `Prop`s. -foreign import writeObject :: Array Prop -> Foreign diff --git a/src/Data/Foreign/Class.purs b/src/Data/Foreign/Class.purs deleted file mode 100644 index 0393031..0000000 --- a/src/Data/Foreign/Class.purs +++ /dev/null @@ -1,138 +0,0 @@ --- | This module defines a type class for reading foreign values. - -module Data.Foreign.Class - ( class IsForeign - , read - , readJSON - , readWith - , readProp - , class AsForeign - , write - , writeProp, (.=) - , readEitherR - , readEitherL - ) where - -import Prelude - -import Control.Alt ((<|>)) -import Control.Monad.Except (Except, mapExcept) - -import Data.Array (range, zipWith, length) -import Data.Bifunctor (lmap) -import Data.Either (Either(..)) -import Data.Foreign (F, Foreign, MultipleErrors, ForeignError(..), Prop(..), toForeign, parseJSON, readArray, readInt, readNumber, readBoolean, readChar, readString) -import Data.Foreign.Index (class Index, errorAt, (!)) -import Data.Foreign.Null (Null(..), readNull, writeNull) -import Data.Foreign.NullOrUndefined (NullOrUndefined(..), readNullOrUndefined) -import Data.Foreign.Undefined (Undefined(..), readUndefined, writeUndefined) -import Data.Maybe (maybe) -import Data.Traversable (sequence) - --- | A type class instance for this class can be written for a type if it --- | is possible to attempt to _safely_ coerce a `Foreign` value to that --- | type. --- | --- | Instances are provided for standard data structures, and the `F` monad --- | can be used to construct instances for new data structures. -class IsForeign a where - read :: Foreign -> F a - -instance foreignIsForeign :: IsForeign Foreign where - read = pure - -instance stringIsForeign :: IsForeign String where - read = readString - -instance charIsForeign :: IsForeign Char where - read = readChar - -instance booleanIsForeign :: IsForeign Boolean where - read = readBoolean - -instance numberIsForeign :: IsForeign Number where - read = readNumber - -instance intIsForeign :: IsForeign Int where - read = readInt - -instance arrayIsForeign :: IsForeign a => IsForeign (Array a) where - read = readArray >=> readElements - where - readElements :: Array Foreign -> F (Array a) - readElements arr = sequence (zipWith readElement (range zero (length arr)) arr) - - readElement :: Int -> Foreign -> F a - readElement i value = readWith (map (ErrorAtIndex i)) value - -instance nullIsForeign :: IsForeign a => IsForeign (Null a) where - read = readNull read - -instance undefinedIsForeign :: IsForeign a => IsForeign (Undefined a) where - read = readUndefined read - -instance nullOrUndefinedIsForeign :: IsForeign a => IsForeign (NullOrUndefined a) where - read = readNullOrUndefined read - --- | Attempt to read a data structure from a JSON string -readJSON :: forall a. IsForeign a => String -> F a -readJSON json = parseJSON json >>= read - --- | Attempt to read a foreign value, handling errors using the specified function -readWith :: forall a e. IsForeign a => (MultipleErrors -> e) -> Foreign -> Except e a -readWith f = mapExcept (lmap f) <<< read - --- | Attempt to read a property of a foreign value at the specified index -readProp :: forall a i. (IsForeign a, Index i) => i -> Foreign -> F a -readProp prop value = value ! prop >>= readWith (map (errorAt prop)) - --- | A type class to convert to a `Foreign` value. --- | --- | Instances are provided for standard data structures. -class AsForeign a where - write :: a -> Foreign - -instance foreignAsForeign :: AsForeign Foreign where - write = id - -instance stringAsForeign :: AsForeign String where - write = toForeign - -instance charAsForeign :: AsForeign Char where - write = toForeign - -instance booleanAsForeign :: AsForeign Boolean where - write = toForeign - -instance numberAsForeign :: AsForeign Number where - write = toForeign - -instance intAsForeign :: AsForeign Int where - write = toForeign - -instance arrayAsForeign :: AsForeign a => AsForeign (Array a) where - write = toForeign <<< map write - -instance nullAsForeign :: AsForeign a => AsForeign (Null a) where - write (Null a) = maybe writeNull write a - -instance undefinedAsForeign :: AsForeign a => AsForeign (Undefined a) where - write (Undefined a) = maybe writeUndefined write a - -instance nullOrUndefinedAsForeign :: AsForeign a => AsForeign (NullOrUndefined a) where - write (NullOrUndefined a) = write (Null a) - -infixl 8 writeProp as .= - -writeProp :: forall a. AsForeign a => String -> a -> Prop -writeProp k v = Prop { key: k, value: write v } - --- | Attempt to read a value that can be either one thing or another. This --- | implementation is right biased. -readEitherR :: forall l r. (IsForeign l, IsForeign r) => Foreign -> F (Either l r) -readEitherR value = Right <$> read value <|> Left <$> read value - --- | Attempt to read a value that can be either one thing or another. This --- | implementation is left biased. -readEitherL :: forall l r. (IsForeign l, IsForeign r) => Foreign -> F (Either l r) -readEitherL value = Left <$> read value <|> Right <$> read value diff --git a/src/Data/Foreign/Index.js b/src/Data/Foreign/Index.js index 4a472a9..71a8960 100644 --- a/src/Data/Foreign/Index.js +++ b/src/Data/Foreign/Index.js @@ -1,12 +1,9 @@ -/* global exports */ "use strict"; -// jshint maxparams: 4 exports.unsafeReadPropImpl = function (f, s, key, value) { return value == null ? f : s(value[key]); }; -// jshint maxparams: 2 exports.unsafeHasOwnProperty = function (prop, value) { return Object.prototype.hasOwnProperty.call(value, prop); }; diff --git a/src/Data/Foreign/Index.purs b/src/Data/Foreign/Index.purs index 4fa3e67..71afb61 100644 --- a/src/Data/Foreign/Index.purs +++ b/src/Data/Foreign/Index.purs @@ -3,9 +3,11 @@ module Data.Foreign.Index ( class Index - , prop - , index + , class Indexable + , readProp + , readIndex , ix, (!) + , index , hasProperty , hasOwnProperty , errorAt @@ -13,18 +15,25 @@ module Data.Foreign.Index import Prelude +import Control.Monad.Except.Trans (ExceptT) + import Data.Foreign (Foreign, F, ForeignError(..), typeOf, isUndefined, isNull, fail) import Data.Function.Uncurried (Fn2, runFn2, Fn4, runFn4) +import Data.Identity (Identity) +import Data.List.NonEmpty (NonEmptyList) -- | This type class identifies types that act like _property indices_. -- | -- | The canonical instances are for `String`s and `Int`s. class Index i where - ix :: Foreign -> i -> F Foreign + index :: Foreign -> i -> F Foreign hasProperty :: i -> Foreign -> Boolean hasOwnProperty :: i -> Foreign -> Boolean errorAt :: i -> ForeignError -> ForeignError +class Indexable a where + ix :: forall i. Index i => a -> i -> F Foreign + infixl 9 ix as ! foreign import unsafeReadPropImpl :: forall r k. Fn4 r (Foreign -> r) k Foreign r @@ -34,12 +43,12 @@ unsafeReadProp k value = runFn4 unsafeReadPropImpl (fail (TypeMismatch "object" (typeOf value))) pure k value -- | Attempt to read a value from a foreign value property -prop :: String -> Foreign -> F Foreign -prop = unsafeReadProp +readProp :: String -> Foreign -> F Foreign +readProp = unsafeReadProp -- | Attempt to read a value from a foreign value at the specified numeric index -index :: Int -> Foreign -> F Foreign -index = unsafeReadProp +readIndex :: Int -> Foreign -> F Foreign +readIndex = unsafeReadProp foreign import unsafeHasOwnProperty :: forall k. Fn2 k Foreign Boolean @@ -58,13 +67,19 @@ hasPropertyImpl p value | typeOf value == "object" || typeOf value == "function" hasPropertyImpl _ value = false instance indexString :: Index String where - ix = flip prop + index = flip readProp hasProperty = hasPropertyImpl hasOwnProperty = hasOwnPropertyImpl errorAt = ErrorAtProperty instance indexInt :: Index Int where - ix = flip index + index = flip readIndex hasProperty = hasPropertyImpl hasOwnProperty = hasOwnPropertyImpl errorAt = ErrorAtIndex + +instance indexableForeign :: Indexable Foreign where + ix = index + +instance indexableExceptT :: Indexable (ExceptT (NonEmptyList ForeignError) Identity Foreign) where + ix f i = flip index i =<< f diff --git a/src/Data/Foreign/Keys.js b/src/Data/Foreign/Keys.js index e04d5f8..fc6feeb 100644 --- a/src/Data/Foreign/Keys.js +++ b/src/Data/Foreign/Keys.js @@ -1,4 +1,3 @@ -/* global exports */ "use strict"; exports.unsafeKeys = Object.keys || function (value) { diff --git a/src/Data/Foreign/Null.js b/src/Data/Foreign/Null.js deleted file mode 100644 index 1f99458..0000000 --- a/src/Data/Foreign/Null.js +++ /dev/null @@ -1,4 +0,0 @@ -/* global exports */ -"use strict"; - -exports.writeNull = null; diff --git a/src/Data/Foreign/Null.purs b/src/Data/Foreign/Null.purs deleted file mode 100644 index e8dbc54..0000000 --- a/src/Data/Foreign/Null.purs +++ /dev/null @@ -1,32 +0,0 @@ -module Data.Foreign.Null where - -import Prelude - -import Data.Maybe (Maybe(..)) -import Data.Newtype (class Newtype, unwrap) -import Data.Foreign (F, Foreign, isNull) - --- | A `newtype` wrapper whose `IsForeign` instance correctly handles --- | null values. --- | --- | Conceptually, this type represents values which may be `null`, --- | but not `undefined`. -newtype Null a = Null (Maybe a) - -derive instance newtypeNull :: Newtype (Null a) _ -derive instance eqNull :: (Eq a) => Eq (Null a) -derive instance ordNull :: (Ord a) => Ord (Null a) - -instance showNull :: (Show a) => Show (Null a) where - show x = "(Null " <> show (unwrap x) <> ")" - --- | Unwrap a `Null` value -unNull :: forall a. Null a -> Maybe a -unNull (Null m) = m - --- | Read a `Null` value -readNull :: forall a. (Foreign -> F a) -> Foreign -> F (Null a) -readNull _ value | isNull value = pure (Null Nothing) -readNull f value = Null <<< Just <$> f value - -foreign import writeNull :: Foreign diff --git a/src/Data/Foreign/NullOrUndefined.purs b/src/Data/Foreign/NullOrUndefined.purs deleted file mode 100644 index 83d23f9..0000000 --- a/src/Data/Foreign/NullOrUndefined.purs +++ /dev/null @@ -1,30 +0,0 @@ -module Data.Foreign.NullOrUndefined where - -import Prelude - -import Data.Newtype (class Newtype, unwrap) -import Data.Maybe (Maybe(..)) -import Data.Foreign (F, Foreign, isUndefined, isNull) - --- | A `newtype` wrapper whose `IsForeign` instance correctly handles --- | null and undefined values. --- | --- | Conceptually, this type represents values which may be `null` --- | or `undefined`. -newtype NullOrUndefined a = NullOrUndefined (Maybe a) - -derive instance newtypeNullOrUndefined :: Newtype (NullOrUndefined a) _ -derive instance eqNullOrUndefined :: (Eq a) => Eq (NullOrUndefined a) -derive instance ordNullOrUndefined :: (Ord a) => Ord (NullOrUndefined a) - -instance showNullOrUndefined :: (Show a) => Show (NullOrUndefined a) where - show x = "(NullOrUndefined " <> show (unwrap x) <> ")" - --- | Unwrap a `NullOrUndefined` value -unNullOrUndefined :: forall a. NullOrUndefined a -> Maybe a -unNullOrUndefined (NullOrUndefined m) = m - --- | Read a `NullOrUndefined` value -readNullOrUndefined :: forall a. (Foreign -> F a) -> Foreign -> F (NullOrUndefined a) -readNullOrUndefined _ value | isNull value || isUndefined value = pure (NullOrUndefined Nothing) -readNullOrUndefined f value = NullOrUndefined <<< Just <$> f value diff --git a/src/Data/Foreign/Undefined.js b/src/Data/Foreign/Undefined.js deleted file mode 100644 index 21677c7..0000000 --- a/src/Data/Foreign/Undefined.js +++ /dev/null @@ -1,4 +0,0 @@ -/* global exports */ -"use strict"; - -exports.writeUndefined = undefined; diff --git a/src/Data/Foreign/Undefined.purs b/src/Data/Foreign/Undefined.purs deleted file mode 100644 index bccf238..0000000 --- a/src/Data/Foreign/Undefined.purs +++ /dev/null @@ -1,32 +0,0 @@ -module Data.Foreign.Undefined where - -import Prelude - -import Data.Maybe (Maybe(..)) -import Data.Newtype (class Newtype, unwrap) -import Data.Foreign (F, Foreign, isUndefined) - --- | A `newtype` wrapper whose `IsForeign` instance correctly handles --- | undefined values. --- | --- | Conceptually, this type represents values which may be `undefined`, --- | but not `null`. -newtype Undefined a = Undefined (Maybe a) - -derive instance newtypeUndefined :: Newtype (Undefined a) _ -derive instance eqUndefined :: (Eq a) => Eq (Undefined a) -derive instance ordUndefined :: (Ord a) => Ord (Undefined a) - -instance showUndefined :: (Show a) => Show (Undefined a) where - show x = "(Undefined " <> show (unwrap x) <> ")" - --- | Unwrap an `Undefined` value -unUndefined :: forall a. Undefined a -> Maybe a -unUndefined (Undefined m) = m - --- | Read an `Undefined` value -readUndefined :: forall a. (Foreign -> F a) -> Foreign -> F (Undefined a) -readUndefined _ value | isUndefined value = pure (Undefined Nothing) -readUndefined f value = Undefined <<< Just <$> f value - -foreign import writeUndefined :: Foreign