Documentation. Removed Foreign Code. No longer support YAML 1.1. Extra quote in YAML output fixed.

This commit is contained in:
Dominick Gendill 2017-05-20 15:32:55 -06:00
parent 75083ae70f
commit a03888dc97
10 changed files with 294 additions and 183 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@
/.psci* /.psci*
/src/.webpack.js /src/.webpack.js
.pulp-cache/ .pulp-cache/
.psc-ide-port

220
README.md
View File

@ -1,129 +1,127 @@
# purescript-yaml # purescript-yaml
## Install
This repo depends on js-yaml, so you'll need to install js-yaml in your
project. It is not compatible with YAML 1.1.
```
npm install js-yaml@^3.4.6
```
Then, if you're using bower, add the this repo as a project dependency, e.g.
```
bower install git://github.com/dgendill/purescript-yaml#1.0.0
```
Or you can manually add the github repo as a project dependency, e.g.
```
"dependencies": {
"purescript-yaml" : git://github.com/dgendill/purescript-yaml#tagOrBranch
}
```
## YAML to Data Type Usage
Assuming we have the following Point data type and yaml...
```purescript ```purescript
data Point = Point Int Int data Point = Point Int Int
data Mobility yamlPoint :: String
= Fix yamlPoint = """
| Flex X: 1
Y: 1
data GeoObject = GeoObject
{ name :: String
, scale :: Number
, points :: Array Point
, mobility :: Mobility
, coverage :: Number
}
```
## Decode YAML
Write functions to read your data from foreign values.
```purescript
readPoint :: Foreign -> F Point
readPoint value = do
x <- readInt =<< readProp "X" value
y <- readInt =<< readProp "Y" value
pure $ Point x y
readMobility :: Foreign -> F Mobility
readMobility value = do
mob <- readString value
case mob of
"Fix" -> pure Fix
"Flex" -> pure Flex
_ -> fail $ JSONError "Mobility must be either Flex or Fix"
readGeoObject :: Foreign -> F GeoObject
readGeoObject value = do
name <- readString =<< readProp "Name" value
scale <- readNumber =<< readProp "Scale" value
points <- traverse readPoint =<< readArray =<< readProp "Points" value
mobility <- readMobility =<< readProp "Mobility" value
coverage <- readNumber =<< readProp "Coverage" value
pure $ GeoObject { name, scale, points, mobility, coverage }
```
Read the YAML into your data structures.
```purescript
yamlInput :: String
yamlInput = """
- Name: House
Scale: 9.5
Points:
- X: 10
Y: 10
- X: 20
Y: 10
- X: 5
Y: 5
Mobility: Fix
Coverage: 10
- Name: Tree
Scale: 1
Points:
- X: 1
Y: 1
- X: 2
Y: 2
- X: 0
Y: 0
Mobility: Fix
Coverage: 10
""" """
let decoded =
(parseYAML yamlInput) >>=
readArray >>=
traverse readGeoObject
``` ```
## Encode YAML We can read a `Point` from the yaml by convertion the YAML into JSON
and then using [purescript-argonaut](https://github.com/purescript-contrib/purescript-argonaut)'s encoding functionality to get the
type we need (specifically, [purescript-argonaut-codecs](https://github.com/purescript-contrib/purescript-argonaut-codecs)
functionality).
```
getPoint :: Either String Point
getPoint = case runExcept $ parseYAMLToJson yamlPoint of
Left err -> Left "Could not parse yaml"
Right json -> decodeJson json
instance pointJson :: DecodeJson Point where
decodeJson s = do
obj <- maybe (Left "Point is not an object.") Right (toObject s)
x <- getField obj "X"
y <- getField obj "Y"
pure $ Point x y
```
## Data Type to YAML Usage
YAML is represented with the following data type.
```
data YValue
= YObject (M.Map String YValue)
| YArray (Array YValue)
| YString String
| YNumber Number
| YInt Int
| YBoolean Boolean
| YNull
```
To convert data into YValue, create instances of the ToYAML class for your
data types.
```purescript ```purescript
class ToYAML a where
toYAML :: a -> YValue
```
For example to take a `Point` to `YValue`
```purescript
import Data.YAML.Foreign.Encode (object, entry, class ToYAML)
instance pointToYAML :: ToYAML Point where instance pointToYAML :: ToYAML Point where
toYAML (Point x y) = toYAML (Point x y) =
object object
[ "X" := x [ "X" `entry` x
, "Y" := y , "Y" `entry` y
]
instance mobilityToYAML :: ToYAML Mobility where
toYAML Fix = toYAML "Fix"
toYAML Flex = toYAML "Flex"
instance archiObjectToYAML :: ToYAML GeoObject where
toYAML (GeoObject o) =
object
[ "Name" := o.name
, "Scale" := o.scale
, "Points" := o.points
, "Mobility" := o.mobility
, "Coverage" := o.coverage
] ]
``` ```
You can find helper functions for converting basic types into `YValue`
in the Data.YAML.Foreign.Encode module.
Finally, if you want to convert YValue into a String, you can use the
`printYAML` function from Data.YAML.Foreign.Encode.
```purescript ```purescript
data :: Array GeoObject printYAML :: forall a. (ToYAML a) => a -> String
data = ```
[ GeoObject
{ coverage: 10.0 ## Summary
, mobility: Fix
, name: "House" Using the previous code and the type classes we defined earlier, we can go
, points: [ Point 10 10, Point 20 10, Point 5 5 ] full circle from a YAML string to a PureScript Data Type and back to a YAML string.
, scale: 9.5
} ```
, GeoObject fullCircle :: String -> Either String String
{ coverage: 10.0 fullCircle yamlString = (readPoint yamlString) >>= pure <<< printYAML
, mobility: Fix ```
, name: "Tree"
, points: [ Point 1 1, Point 2 2, Point 0 0 ] ## Contributing
, scale: 1.0
} Check out the repo and make changes. You can run/add tests by running pulp test.
]
```
encoded = printYAML data bower install
npm install
pulp test
``` ```

View File

@ -8,7 +8,9 @@
"**/.*", "**/.*",
"node_modules", "node_modules",
"bower_components", "bower_components",
"output" "output",
"test",
"docs"
], ],
"dependencies": { "dependencies": {
"js-yaml": "^3.4.6", "js-yaml": "^3.4.6",
@ -20,6 +22,7 @@
}, },
"devDependencies": { "devDependencies": {
"purescript-console": "^3.0.0", "purescript-console": "^3.0.0",
"purescript-spec": "^1.0.0" "purescript-spec": "^1.0.0",
"purescript-argonaut-codecs": "^3.0.1"
} }
} }

View File

@ -0,0 +1,19 @@
## Module Data.YAML.Foreign.Decode
#### `parseYAMLToJson`
``` purescript
parseYAMLToJson :: String -> F Json
```
Attempt to parse a YAML string, returning the result as Json
#### `readYAMLGeneric`
``` purescript
readYAMLGeneric :: forall a rep. Generic a rep => GenericDecode rep => Options -> String -> F a
```
Automatically generate a YAML parser for your data from a generic instance.

View File

@ -0,0 +1,66 @@
## Module Data.YAML.Foreign.Encode
#### `YValue`
``` purescript
data YValue
```
##### Instances
``` purescript
Show YValue
Eq YValue
```
#### `ToYAML`
``` purescript
class ToYAML a where
toYAML :: a -> YValue
```
##### Instances
``` purescript
(ToYAML a) => ToYAML (StrMap a)
(ToYAML a) => ToYAML (Map String a)
ToYAML Boolean
ToYAML Int
ToYAML Number
ToYAML String
(ToYAML a) => ToYAML (Array a)
(ToYAML a) => ToYAML (Maybe a)
```
#### `entry`
``` purescript
entry :: forall a. ToYAML a => String -> a -> Pair
```
Helper function to create a key-value tuple for a YAML object.
`name = "Name" := "This is the name"`
#### `(:=)`
``` purescript
infixl 4 entry as :=
```
#### `object`
``` purescript
object :: Array Pair -> YValue
```
Helper function to create a YAML object.
`obj = object [ "Name" := "This is the name", "Size" := 1.5 ]`
#### `printYAML`
``` purescript
printYAML :: forall a. ToYAML a => a -> String
```

View File

@ -1,4 +1,7 @@
module Data.YAML.Foreign.Decode (parseYAML, readYAMLGeneric, parseYAMLToJson) where module Data.YAML.Foreign.Decode (
readYAMLGeneric,
parseYAMLToJson
) where
import Data.Foreign (F, Foreign, ForeignError(..), fail) import Data.Foreign (F, Foreign, ForeignError(..), fail)
import Data.Foreign.Generic (genericDecode) import Data.Foreign.Generic (genericDecode)

View File

@ -1,23 +1,18 @@
"use strict"; "use strict";
// module Data.YAML.Foreign.Encode
var yaml = require('js-yaml'); var yaml = require('js-yaml');
exports.jsNull = null; exports.jsNull = null;
exports.objToHash = exports.objToHash = function(valueToYAMLImpl, fst, snd, obj) {
function(valueToYAMLImpl, fst, snd, obj)
{
var hash = {}; var hash = {};
for(var i = 0; i < obj.length; i++) { for(var i = 0; i < obj.length; i++) {
hash[fst(obj[i])] = valueToYAMLImpl(snd(obj[i])); hash[fst(obj[i])] = valueToYAMLImpl(snd(obj[i]));
} }
return hash; return hash;
}; };
exports.toYAMLImpl = exports.toYAMLImpl = function(a) {
function(a) // noCompatMode does not support YAML 1.1
{ return yaml.safeDump(a, {noCompatMode : true});
return yaml.safeDump(a); }
}

View File

@ -1,4 +1,11 @@
module Data.YAML.Foreign.Encode where module Data.YAML.Foreign.Encode (
YValue,
class ToYAML,
toYAML,
entry, (:=),
object,
printYAML
) where
import Data.Map as M import Data.Map as M
import Data.Map (Map) import Data.Map (Map)

View File

@ -1,11 +1,13 @@
module Test.Instances where module Test.Instances where
import Prelude (class Eq, class Show, bind, pure, ($), (=<<))
import Data.Traversable (traverse)
import Data.Foreign (readArray, readNumber, readString, readInt, F, Foreign, ForeignError(..), fail)
import Data.Foreign.Index (readProp)
import Data.Generic (class Generic, gShow, gEq)
import Data.YAML.Foreign.Encode import Data.YAML.Foreign.Encode
import Data.Argonaut.Core (toObject, toString)
import Data.Argonaut.Decode (getField)
import Data.Argonaut.Decode.Class (class DecodeJson)
import Data.Either (Either(..))
import Data.Generic (class Generic, gShow, gEq)
import Data.Maybe (maybe)
import Prelude (class Eq, class Show, bind, pure, ($))
data Point = Point Int Int data Point = Point Int Int
@ -33,28 +35,31 @@ derive instance genericMobility :: Generic Mobility
instance showMobility :: Show Mobility where show = gShow instance showMobility :: Show Mobility where show = gShow
instance eqMobility :: Eq Mobility where eq = gEq instance eqMobility :: Eq Mobility where eq = gEq
readGeoObject :: Foreign -> F GeoObject instance geoJson :: DecodeJson GeoObject where
readGeoObject value = do decodeJson s = do
name <- readString =<< readProp "Name" value obj <- maybe (Left "GeoObject is not an object.") Right (toObject s)
scale <- readNumber =<< readProp "Scale" value name <- getField obj "Name"
points <- traverse readPoint =<< readArray =<< readProp "Points" value scale <- getField obj "Scale"
mobility <- readMobility =<< readProp "Mobility" value points <- getField obj "Points"
coverage <- readNumber =<< readProp "Coverage" value mobility <- getField obj "Mobility"
coverage <- getField obj "Coverage"
pure $ GeoObject { name, scale, points, mobility, coverage } pure $ GeoObject { name, scale, points, mobility, coverage }
readPoint :: Foreign -> F Point instance mobilityJson :: DecodeJson Mobility where
readPoint value = do decodeJson s = do
x <- readInt =<< readProp "X" value mob <- maybe (Left "Mobility is not a string.") Right (toString s)
y <- readInt =<< readProp "Y" value
pure $ Point x y
readMobility :: Foreign -> F Mobility
readMobility value = do
mob <- readString value
case mob of case mob of
"Fix" -> pure Fix "Fix" -> pure Fix
"Flex" -> pure Flex "Flex" -> pure Flex
_ -> fail $ JSONError "Mobility must be either Flex or Fix" _ -> Left "Mobility must be either Flex or Fix"
instance pointJson :: DecodeJson Point where
decodeJson s = do
obj <- maybe (Left "Point is not an object.") Right (toObject s)
x <- getField obj "X"
y <- getField obj "Y"
pure $ Point x y
instance pointToYAML :: ToYAML Point where instance pointToYAML :: ToYAML Point where
toYAML (Point x y) = toYAML (Point x y) =

View File

@ -1,23 +1,21 @@
module Test.Main where module Test.Main where
import Data.Map as Map
import Data.StrMap as StrMap
import Control.Monad.Eff (Eff) import Control.Monad.Eff (Eff)
import Control.Monad.Except (runExcept) import Control.Monad.Except (runExcept)
import Data.Argonaut.Decode (class DecodeJson, decodeJson)
import Data.Either (Either(..)) import Data.Either (Either(..))
import Data.Foreign (F, readArray) import Data.Map (Map)
import Data.YAML.Foreign.Decode (parseYAML) import Data.StrMap (StrMap)
import Data.YAML.Foreign.Decode (parseYAMLToJson)
import Data.YAML.Foreign.Encode (printYAML) import Data.YAML.Foreign.Encode (printYAML)
import Data.Traversable (traverse) import Prelude (Unit, discard, pure, ($), (<<<), (>>=))
import Prelude (Unit, bind, ($), void, discard, (>>=)) import Test.Instances (GeoObject(..), Mobility(..), Point(..))
import Test.Instances (readGeoObject, readMobility, readPoint, GeoObject(..), Mobility(..), Point(..))
import Test.Spec (describe, it) import Test.Spec (describe, it)
import Test.Spec.Assertions (shouldEqual) import Test.Spec.Assertions (shouldEqual)
import Test.Spec.Reporter.Console (consoleReporter) import Test.Spec.Reporter.Console (consoleReporter)
import Test.Spec.Runner (RunnerEffects, run) import Test.Spec.Runner (RunnerEffects, run)
import Control.Monad.Eff.Console (log, CONSOLE)
import Data.Map as Map
import Data.Map (Map)
import Data.StrMap as StrMap
import Data.StrMap (StrMap)
yamlInput :: String yamlInput :: String
yamlInput = """ yamlInput = """
@ -50,22 +48,22 @@ yamlOutput :: String
yamlOutput = """- Mobility: Fix yamlOutput = """- Mobility: Fix
Points: Points:
- X: 10 - X: 10
'Y': 10 Y: 10
- X: 20 - X: 20
'Y': 10 Y: 10
- X: 5 - X: 5
'Y': 5 Y: 5
Coverage: 10 Coverage: 10
Name: House Name: House
Scale: 9.5 Scale: 9.5
- Mobility: Fix - Mobility: Fix
Points: Points:
- X: 1 - X: 1
'Y': 1 Y: 1
- X: 2 - X: 2
'Y': 2 Y: 2
- X: 0 - X: 0
'Y': 0 Y: 0
Coverage: 10 Coverage: 10
Name: Tree Name: Tree
Scale: 1 Scale: 1
@ -77,27 +75,37 @@ yamlMapOutput = """key:
- Mobility: Fix - Mobility: Fix
Points: Points:
- X: 10 - X: 10
'Y': 10 Y: 10
- X: 20 - X: 20
'Y': 10 Y: 10
- X: 5 - X: 5
'Y': 5 Y: 5
Coverage: 10 Coverage: 10
Name: House Name: House
Scale: 9.5 Scale: 9.5
- Mobility: Fix - Mobility: Fix
Points: Points:
- X: 1 - X: 1
'Y': 1 Y: 1
- X: 2 - X: 2
'Y': 2 Y: 2
- X: 0 - X: 0
'Y': 0 Y: 0
Coverage: 10 Coverage: 10
Name: Tree Name: Tree
Scale: 1 Scale: 1
""" """
pointYaml :: String
pointYaml = """X: 1
Y: 1
"""
yamlToData :: forall a. (DecodeJson a) => String -> Either String a
yamlToData s = case runExcept $ parseYAMLToJson s of
Left err -> Left "Could not parse yaml"
Right json -> decodeJson json
testStrMap :: StrMap (Array GeoObject) testStrMap :: StrMap (Array GeoObject)
testStrMap = StrMap.singleton "key" parsedData testStrMap = StrMap.singleton "key" parsedData
@ -123,16 +131,19 @@ parsedData =
} }
] ]
readPoint :: String -> Either String Point
readPoint = yamlToData
fullCircle :: String -> Either String String
fullCircle yamlString = (readPoint yamlString) >>= pure <<< printYAML
main :: Eff (RunnerEffects ()) Unit main :: Eff (RunnerEffects ()) Unit
main = run [consoleReporter] do main = run [consoleReporter] do
describe "purescript-yaml" do describe "purescript-yaml" do
describe "decode" do describe "decode" do
it "Decodes YAML" do it "Decodes YAML" do
let decoded = let decoded = yamlToData yamlInput
(parseYAML yamlInput) >>= decoded `shouldEqual` (Right parsedData)
readArray >>=
traverse readGeoObject
(runExcept decoded) `shouldEqual` (Right parsedData)
describe "encode" do describe "encode" do
it "Encodes YAML" $ do it "Encodes YAML" $ do
let encoded = printYAML parsedData let encoded = printYAML parsedData
@ -143,3 +154,6 @@ main = run [consoleReporter] do
let encodedMap = printYAML testMap let encodedMap = printYAML testMap
encodedMap `shouldEqual` yamlMapOutput encodedMap `shouldEqual` yamlMapOutput
let s = fullCircle pointYaml
s `shouldEqual` (Right pointYaml)