generated from tpl/purs
Documentation. Removed Foreign Code. No longer support YAML 1.1. Extra quote in YAML output fixed.
This commit is contained in:
parent
75083ae70f
commit
a03888dc97
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@
|
||||
/.psci*
|
||||
/src/.webpack.js
|
||||
.pulp-cache/
|
||||
.psc-ide-port
|
||||
|
218
README.md
218
README.md
@ -1,129 +1,127 @@
|
||||
# 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
|
||||
data Point = Point Int Int
|
||||
|
||||
data Mobility
|
||||
= Fix
|
||||
| Flex
|
||||
|
||||
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
|
||||
yamlPoint :: String
|
||||
yamlPoint = """
|
||||
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
|
||||
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
|
||||
toYAML (Point x y) =
|
||||
object
|
||||
[ "X" := x
|
||||
, "Y" := 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
|
||||
[ "X" `entry` x
|
||||
, "Y" `entry` y
|
||||
]
|
||||
```
|
||||
|
||||
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
|
||||
data :: Array GeoObject
|
||||
data =
|
||||
[ GeoObject
|
||||
{ coverage: 10.0
|
||||
, mobility: Fix
|
||||
, name: "House"
|
||||
, points: [ Point 10 10, Point 20 10, Point 5 5 ]
|
||||
, scale: 9.5
|
||||
}
|
||||
, GeoObject
|
||||
{ coverage: 10.0
|
||||
, mobility: Fix
|
||||
, name: "Tree"
|
||||
, points: [ Point 1 1, Point 2 2, Point 0 0 ]
|
||||
, scale: 1.0
|
||||
}
|
||||
]
|
||||
|
||||
encoded = printYAML data
|
||||
printYAML :: forall a. (ToYAML a) => a -> String
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
Using the previous code and the type classes we defined earlier, we can go
|
||||
full circle from a YAML string to a PureScript Data Type and back to a YAML string.
|
||||
|
||||
```
|
||||
fullCircle :: String -> Either String String
|
||||
fullCircle yamlString = (readPoint yamlString) >>= pure <<< printYAML
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Check out the repo and make changes. You can run/add tests by running pulp test.
|
||||
|
||||
```
|
||||
bower install
|
||||
npm install
|
||||
pulp test
|
||||
```
|
||||
|
@ -8,7 +8,9 @@
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"output"
|
||||
"output",
|
||||
"test",
|
||||
"docs"
|
||||
],
|
||||
"dependencies": {
|
||||
"js-yaml": "^3.4.6",
|
||||
@ -20,6 +22,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"purescript-console": "^3.0.0",
|
||||
"purescript-spec": "^1.0.0"
|
||||
"purescript-spec": "^1.0.0",
|
||||
"purescript-argonaut-codecs": "^3.0.1"
|
||||
}
|
||||
}
|
||||
|
19
generated-docs/Data/YAML/Foreign/Decode.md
Normal file
19
generated-docs/Data/YAML/Foreign/Decode.md
Normal 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.
|
||||
|
||||
|
66
generated-docs/Data/YAML/Foreign/Encode.md
Normal file
66
generated-docs/Data/YAML/Foreign/Encode.md
Normal 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
|
||||
```
|
||||
|
||||
|
@ -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.Generic (genericDecode)
|
||||
|
@ -1,14 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
// module Data.YAML.Foreign.Encode
|
||||
|
||||
var yaml = require('js-yaml');
|
||||
|
||||
exports.jsNull = null;
|
||||
|
||||
exports.objToHash =
|
||||
function(valueToYAMLImpl, fst, snd, obj)
|
||||
{
|
||||
exports.objToHash = function(valueToYAMLImpl, fst, snd, obj) {
|
||||
var hash = {};
|
||||
for(var i = 0; i < obj.length; i++) {
|
||||
hash[fst(obj[i])] = valueToYAMLImpl(snd(obj[i]));
|
||||
@ -16,8 +12,7 @@ exports.objToHash =
|
||||
return hash;
|
||||
};
|
||||
|
||||
exports.toYAMLImpl =
|
||||
function(a)
|
||||
{
|
||||
return yaml.safeDump(a);
|
||||
exports.toYAMLImpl = function(a) {
|
||||
// noCompatMode does not support YAML 1.1
|
||||
return yaml.safeDump(a, {noCompatMode : true});
|
||||
}
|
||||
|
@ -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 (Map)
|
||||
|
@ -1,11 +1,13 @@
|
||||
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.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
|
||||
|
||||
@ -33,28 +35,31 @@ derive instance genericMobility :: Generic Mobility
|
||||
instance showMobility :: Show Mobility where show = gShow
|
||||
instance eqMobility :: Eq Mobility where eq = gEq
|
||||
|
||||
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
|
||||
instance geoJson :: DecodeJson GeoObject where
|
||||
decodeJson s = do
|
||||
obj <- maybe (Left "GeoObject is not an object.") Right (toObject s)
|
||||
name <- getField obj "Name"
|
||||
scale <- getField obj "Scale"
|
||||
points <- getField obj "Points"
|
||||
mobility <- getField obj "Mobility"
|
||||
coverage <- getField obj "Coverage"
|
||||
pure $ GeoObject { name, scale, points, mobility, coverage }
|
||||
|
||||
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
|
||||
instance mobilityJson :: DecodeJson Mobility where
|
||||
decodeJson s = do
|
||||
mob <- maybe (Left "Mobility is not a string.") Right (toString s)
|
||||
case mob of
|
||||
"Fix" -> pure Fix
|
||||
"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
|
||||
toYAML (Point x y) =
|
||||
|
@ -1,23 +1,21 @@
|
||||
module Test.Main where
|
||||
|
||||
import Data.Map as Map
|
||||
import Data.StrMap as StrMap
|
||||
import Control.Monad.Eff (Eff)
|
||||
import Control.Monad.Except (runExcept)
|
||||
import Data.Argonaut.Decode (class DecodeJson, decodeJson)
|
||||
import Data.Either (Either(..))
|
||||
import Data.Foreign (F, readArray)
|
||||
import Data.YAML.Foreign.Decode (parseYAML)
|
||||
import Data.Map (Map)
|
||||
import Data.StrMap (StrMap)
|
||||
import Data.YAML.Foreign.Decode (parseYAMLToJson)
|
||||
import Data.YAML.Foreign.Encode (printYAML)
|
||||
import Data.Traversable (traverse)
|
||||
import Prelude (Unit, bind, ($), void, discard, (>>=))
|
||||
import Test.Instances (readGeoObject, readMobility, readPoint, GeoObject(..), Mobility(..), Point(..))
|
||||
import Prelude (Unit, discard, pure, ($), (<<<), (>>=))
|
||||
import Test.Instances (GeoObject(..), Mobility(..), Point(..))
|
||||
import Test.Spec (describe, it)
|
||||
import Test.Spec.Assertions (shouldEqual)
|
||||
import Test.Spec.Reporter.Console (consoleReporter)
|
||||
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 = """
|
||||
@ -50,22 +48,22 @@ yamlOutput :: String
|
||||
yamlOutput = """- Mobility: Fix
|
||||
Points:
|
||||
- X: 10
|
||||
'Y': 10
|
||||
Y: 10
|
||||
- X: 20
|
||||
'Y': 10
|
||||
Y: 10
|
||||
- X: 5
|
||||
'Y': 5
|
||||
Y: 5
|
||||
Coverage: 10
|
||||
Name: House
|
||||
Scale: 9.5
|
||||
- Mobility: Fix
|
||||
Points:
|
||||
- X: 1
|
||||
'Y': 1
|
||||
Y: 1
|
||||
- X: 2
|
||||
'Y': 2
|
||||
Y: 2
|
||||
- X: 0
|
||||
'Y': 0
|
||||
Y: 0
|
||||
Coverage: 10
|
||||
Name: Tree
|
||||
Scale: 1
|
||||
@ -77,27 +75,37 @@ yamlMapOutput = """key:
|
||||
- Mobility: Fix
|
||||
Points:
|
||||
- X: 10
|
||||
'Y': 10
|
||||
Y: 10
|
||||
- X: 20
|
||||
'Y': 10
|
||||
Y: 10
|
||||
- X: 5
|
||||
'Y': 5
|
||||
Y: 5
|
||||
Coverage: 10
|
||||
Name: House
|
||||
Scale: 9.5
|
||||
- Mobility: Fix
|
||||
Points:
|
||||
- X: 1
|
||||
'Y': 1
|
||||
Y: 1
|
||||
- X: 2
|
||||
'Y': 2
|
||||
Y: 2
|
||||
- X: 0
|
||||
'Y': 0
|
||||
Y: 0
|
||||
Coverage: 10
|
||||
Name: Tree
|
||||
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.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 = run [consoleReporter] do
|
||||
describe "purescript-yaml" do
|
||||
describe "decode" do
|
||||
it "Decodes YAML" do
|
||||
let decoded =
|
||||
(parseYAML yamlInput) >>=
|
||||
readArray >>=
|
||||
traverse readGeoObject
|
||||
(runExcept decoded) `shouldEqual` (Right parsedData)
|
||||
let decoded = yamlToData yamlInput
|
||||
decoded `shouldEqual` (Right parsedData)
|
||||
describe "encode" do
|
||||
it "Encodes YAML" $ do
|
||||
let encoded = printYAML parsedData
|
||||
@ -143,3 +154,6 @@ main = run [consoleReporter] do
|
||||
|
||||
let encodedMap = printYAML testMap
|
||||
encodedMap `shouldEqual` yamlMapOutput
|
||||
|
||||
let s = fullCircle pointYaml
|
||||
s `shouldEqual` (Right pointYaml)
|
||||
|
Loading…
Reference in New Issue
Block a user