Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/tanelso2/yanyl
YAML library for Nim
https://github.com/tanelso2/yanyl
Last synced: 7 days ago
JSON representation
YAML library for Nim
- Host: GitHub
- URL: https://github.com/tanelso2/yanyl
- Owner: tanelso2
- License: unlicense
- Created: 2023-01-13T09:05:41.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2023-12-28T00:46:48.000Z (11 months ago)
- Last Synced: 2024-10-11T19:18:39.261Z (about 1 month ago)
- Language: Nim
- Size: 135 KB
- Stars: 10
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# YANYL (Yet Another Nim Yaml Library)
A library for working with YAML in Nim. Can generate conversion functions for most types declared in Nim.
[NimDocs can be found here](https://tanelso2.github.io/yanyl/index.html)
# Example
```nim
import
yanyltype
Obj = object
i: int
s: stringderiveYaml Obj
var sample: string = """
i: 99
s: hello world
"""
var o: Obj = ofYamlStr(sample, Obj)
assert o.i == 99
assert o.s == "hello world"
```# Install
Add the following to your `.nimble` file:
```
requires "yanyl"
```# Usage
## `deriveYaml`
Macro that takes the name of a type and will generate `ofYaml` and `toYaml` procs for that type.
```nim
import
yanyltype
Obj = object
i: int
s: stringderiveYaml Obj
var sample: string = """
i: 99
s: hello world
"""
var o: Obj = ofYamlStr(sample, Obj)
assert o.i == 99
assert o.s == "hello world"
```## `loadNode`
Proc that takes a string or Stream and returns a `YNode`, the internal representation of a YAML document.```nim
import
yanyllet s = """
a: 1
b: 3
c:
- a
- b
- c
"""let y: YNode = s.loadNode()
assert y.kind == ynMap
assert y.get("a").kind == ynString
assert y.get("c").kind == ynList
```## `toString`
Proc that returns the YAML string of a `YNode`## `toYaml`
Proc that takes an object of type `T` and returns a `YNode`.Generic proc, should be redefined for every type. Can be autogenerated by `deriveYaml`
## `ofYaml`
Proc that takes a `YNode` and returns an object of type `T`.Generic proc, should be redefined for every type. Can be autogenerated by `deriveYaml`
## `ofYamlStr`
Shortcut proc.`ofYamlStr(s,t)` is equivalent to `s.loadNode().ofYaml(t)`
## `toYamlStr`
Shortcut proc.`toYamlStr(x)` is equivalent to `x.toYaml().toString()`
# Code Generation
Code generation is opt-in per type. You can also write custom `ofYaml`/`toYaml` if the autogenerated ones do not work for your use-case.
Code generation supports most types you can define in Nim: `object`s, `ref object`s, `enum`s, and variant objects are all supported.
Here's an example with multiple types that get generated and parse the YAML as expected:
```nim
import
yanyl,
unittesttype
CatBreed = enum
cbMaineCoon = "MaineCoon"
cbPersian = "Persian"
cbBengal = "Bengal"
DogBreed = enum
dbCorgi = "Corgi"
dbMastiff = "Mastiff"
PetType = enum
ptCat = "cat"
ptDog = "dog"
Nameable = object of RootObj
name: string
Pet = ref object of Nameable
vet: Nameable
case kind: PetType
of ptCat:
catBreed: CatBreed
of ptDog:
dogBreed: DogBreed
Owner = object of Nameable
paid: bool
pets: seq[Pet]deriveYamls:
CatBreed
DogBreed
PetType
Nameable
Pet
Ownervar s: string = """
name: Duncan Indigo
paid: false
pets:
- name: Ginger
vet:
name: Maria Belmont
kind: cat
catBreed: MaineCoon
- name: Buttersnap
vet:
name: Maria Belmont
kind: dog
dogBreed: Corgi
"""let duncan = ofYamlStr(s, Owner)
check duncan.name == "Duncan Indigo"
check duncan.paid == false
check duncan.pets.len() == 2let ginger = duncan.pets[0]
check ginger.name == "Ginger"
check ginger.kind == ptCat
check ginger.catBreed == cbMaineCoon
check ginger.vet.name == "Maria Belmont"let buttersnap = duncan.pets[1]
check buttersnap.name == "Buttersnap"
check buttersnap.kind == ptDog
check buttersnap.dogBreed == dbCorgi
check buttersnap.vet.name == "Maria Belmont"
```## Viewing the generated code
If you would like to view the code generated by yanyl, you can use `macros.expandMacros` from the standard library. This will print the result of expanding the macros at _compile_ time.For example:
```nim
import
yanyl,
macrostype
E = enum
eStr, eInt
V = object
c: string
case kind: E
of eStr:
s: string
of eInt:
i: intexpandMacros:
deriveYamls:
E
V```
will output:
```nim
proc ofYaml(n: YNode; t: typedesc[E]): E =
case n.kind
of ynString:
case n.strVal
of $eStr:
eStr
of $eInt:
eInt
else:
raise newException(ValueError, "unknown kind: " & n.strVal)
else:
raise newException(ValueError, "expected string YNode")proc toYaml(x: E): YNode =
result = newYString($x)proc ofYaml(n: YNode; t: typedesc[V]): V =
case n.kind
of ynMap:
let kind = n.get("kind", typedesc[E])
case kind
of eStr:
result = V(kind: kind, c: n.get("c", typedesc[string]),
s: n.get("s", typedesc[string]))
of eInt:
result = V(kind: kind, c: n.get("c", typedesc[string]),
i: n.get("i", typedesc[int]))
else:
raise newException(ValueError, "expected map YNode")proc toYaml(x: V): YNode =
case x.kind
of eStr:
result = newYMapRemoveNils([("kind", toYaml(x.kind)), ("c", toYaml(x.c)),
("s", toYaml(x.s))])
of eInt:
result = newYMapRemoveNils([("kind", toYaml(x.kind)), ("c", toYaml(x.c)),
("i", toYaml(x.i))])
```# Comparison with [NimYAML](https://github.com/flyx/NimYAML)
If you're using yanyl, you're already using NimYAML. Yanyl uses NimYAML as a parser, and then translates from NimYAML's `YamlNode` to yanyl's `YNode`. Yanyl discards tag information in doing so, and yanyl's `toYamlStr` and related functions do not emit tags either.
Why would you use Yanyl instead of just using NimYAML directly?
## Simplified Output
NimYAML's output contains tags that allow it to reconstruct the object that was deserialized. Yanyl's output is a lot simpler and doesn't include any tag information.For example, the following code
```nim
import
yaml,
yanyltype
Obj = object of RootObj
i*: int
s*: stringderiveYaml Obj
var o = Obj(i: 42, s: "Hello galaxy")
# NimYAML
echo dump(o)
# Yanyl
echo toYamlStr(o)
```will result in the outputs
NimYAML:
```yaml
%YAML 1.2
%TAG !n! tag:nimyaml.org,2016:
--- !n!custom:Obj
i: 42
s: Hello galaxy
```Yanyl:
```yaml
s: Hello galaxy
i: 42
```## Support for more Nim types
I had trouble when using NimYAML where I was structuring my types to better work around what NimYAML could parse. I built yanyl so that I could structure my types how I normally would, and then used macro programming to make it write the `ofYaml`/`toYaml` functions that I would have.NimYAML is more strict about types whereas Yanyl is more akin to duck-typing. If an object has the fields expected, Yanyl will stuff it into the type you specified.
**Note**: some of the things I thought were NimYAML's intended behavior may actually be bugs, so I will work on reporting those properly instead of just spinning off my own library.
## Explicit declarations
As a newbie to Nim macro programming, I wasn't sure how or where NimYAML was creating functions. Yanyl makes it clear with its `derive` macros. It is more boilerplate for the developer, but I think it makes it clear and easier to debug.