├── tests ├── nim.cfg ├── example.yaml ├── tyaml_bool.nim ├── tcomparison_output.nim ├── tyaml_publicprocs.nim ├── tyaml_enum.nim ├── tderiveyamls.nim ├── tyaml_distinct.nim ├── tyaml_tuples.nim ├── test_utils │ └── yaml_testing.nim ├── tyaml_seq.nim ├── tyaml_accentquotes.nim ├── tyaml_polymorphism.nim ├── tyaml_table.nim ├── tyaml_inheritance.nim ├── tyaml_typealias.nim ├── tloadnode.nim ├── tyaml_variant.nim ├── tyaml_option.nim ├── tyaml_vetoffice.nim ├── tyaml_object.nim ├── tyaml_mounts.nim ├── tyaml.nim ├── tyaml_extension.nim └── tyaml_codegen.nim ├── .gitignore ├── src ├── yanyl.nim └── yanyl │ ├── reflection.nim │ ├── core.nim │ └── codegen.nim ├── TODO.org ├── yanyl.nimble ├── LICENSE ├── .github └── workflows │ ├── ci.yml │ └── docGen.yml └── README.md /tests/nim.cfg: -------------------------------------------------------------------------------- 1 | --path:"$nim/" 2 | --path:"../src/" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | testresults/ 3 | docs/ 4 | *.swp 5 | -------------------------------------------------------------------------------- /src/yanyl.nim: -------------------------------------------------------------------------------- 1 | {.hint[DuplicateModuleImport]: off.} 2 | 3 | include yanyl/core 4 | include yanyl/codegen -------------------------------------------------------------------------------- /tests/example.yaml: -------------------------------------------------------------------------------- 1 | name: Peter Parker 2 | occupation: Photographer 3 | aliases: 4 | - Tiger 5 | - Pete 6 | -------------------------------------------------------------------------------- /tests/tyaml_bool.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl 3 | 4 | assert ofYaml(newYString("true"), bool) == true 5 | assert ofYaml(newYString("false"), bool) == false -------------------------------------------------------------------------------- /tests/tcomparison_output.nim: -------------------------------------------------------------------------------- 1 | import 2 | yaml, 3 | yanyl 4 | 5 | type 6 | Obj = object of RootObj 7 | i*: int 8 | s*: string 9 | 10 | deriveYaml Obj 11 | 12 | var o = Obj(i: 42, s: "Hello galaxy") 13 | # NimYAML 14 | echo dump(jsonDumper(), o) 15 | # Yanyl 16 | echo toYamlStr(o) 17 | -------------------------------------------------------------------------------- /tests/tyaml_publicprocs.nim: -------------------------------------------------------------------------------- 1 | # Checks to make sure that the ofYaml/toYaml 2 | # procs generated by deriveYaml are exposed publicly 3 | # 4 | import 5 | yanyl, 6 | test_utils/yaml_testing, 7 | unittest 8 | 9 | var sample = """ 10 | s: hello other module 11 | i: 78 12 | """ 13 | 14 | var o: MockObj = ofYamlStr(sample, MockObj) 15 | 16 | check o.s == "hello other module" 17 | check o.i == 78 18 | 19 | checkRoundTrip o -------------------------------------------------------------------------------- /TODO.org: -------------------------------------------------------------------------------- 1 | * TODO 2 | ** Features 3 | - range type 4 | https://nim-lang.org/docs/tut1.html#advanced-types-subranges 5 | - set types 6 | https://nim-lang.org/docs/tut1.html#advanced-types-sets 7 | - array types 8 | https://nim-lang.org/docs/tut1.html#advanced-types-arrays 9 | ** Cleanup 10 | - comment reflection.nim 11 | - comment codegen.nim 12 | ** CI 13 | - Automate creating a github release when new tag is pushed 14 | - Automate bumping the version and tagging locally? `nimble bumpMinorVersion`, `nimble bumpVersion`, etc.? 15 | -------------------------------------------------------------------------------- /tests/tyaml_enum.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl, 3 | test_utils/yaml_testing, 4 | unittest 5 | 6 | type 7 | Example = enum 8 | e1,e2,e3 9 | 10 | deriveYaml Example 11 | 12 | check toYamlStr(e1) == "e1" 13 | check toYamlStr(e2) == "e2" 14 | check toYamlStr(e3) == "e3" 15 | 16 | let e: Example = ofYamlStr("e1", Example) 17 | check e == e1 18 | 19 | type 20 | CustomVal = enum 21 | cv1 = "1" 22 | cv2 23 | 24 | deriveYaml CustomVal 25 | 26 | check ofYamlStr("1", CustomVal) == cv1 27 | check ofYamlStr("cv2", CustomVal) == cv2 28 | -------------------------------------------------------------------------------- /tests/tderiveyamls.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl, 3 | std/macros 4 | 5 | type 6 | Owner = ref object of RootObj 7 | name: string 8 | Pet = ref object of RootObj 9 | name: string 10 | kind: string 11 | owner: Owner 12 | 13 | deriveYamls: 14 | Owner 15 | Pet 16 | 17 | let sample = """ 18 | name: Garfield 19 | kind: cat 20 | owner: 21 | name: J. Arbuckle 22 | """ 23 | let garf = ofYamlStr(sample, Pet) 24 | doAssert garf.name == "Garfield" 25 | doAssert garf.kind == "cat" 26 | doAssert garf.owner.name == "J. Arbuckle" 27 | -------------------------------------------------------------------------------- /tests/tyaml_distinct.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl, 3 | macros, 4 | unittest, 5 | test_utils/yaml_testing 6 | 7 | type 8 | Name = distinct string 9 | 10 | deriveYamls: 11 | Name 12 | 13 | let roger: Name = ofYamlStr("roger", Name) 14 | check roger.string == "roger" 15 | check roger.toYamlStr == "roger" 16 | checkRoundTrip roger 17 | 18 | type 19 | Id = distinct int 20 | Employee = object 21 | name: Name 22 | id: Id 23 | 24 | deriveYamls: 25 | Id 26 | Employee 27 | 28 | var sample = """ 29 | name: Isaac 30 | id: 45 31 | """ 32 | let emp: Employee = ofYamlStr(sample, Employee) 33 | check emp.name.string == "Isaac" 34 | check emp.id.int == 45 -------------------------------------------------------------------------------- /tests/tyaml_tuples.nim: -------------------------------------------------------------------------------- 1 | import 2 | macros, 3 | unittest, 4 | yanyl, 5 | test_utils/yaml_testing 6 | 7 | type 8 | Named = tuple 9 | name: string 10 | id: int 11 | BracketSyntax = tuple[id: int, name: string] 12 | 13 | expandMacros: 14 | deriveYaml Named 15 | deriveYaml BracketSyntax 16 | 17 | var sample = """ 18 | name: Jim 19 | id: 98 20 | """ 21 | let jim1 = ofYamlStr(sample, Named) 22 | check jim1.name == "Jim" 23 | check jim1.id == 98 24 | check jim1[0] == "Jim" 25 | checkRoundTrip jim1 26 | 27 | let jim2 = ofYamlStr(sample, BracketSyntax) 28 | check jim2.name == "Jim" 29 | check jim2.id == 98 30 | check jim2[0] == 98 31 | checkRoundTrip jim2 -------------------------------------------------------------------------------- /tests/test_utils/yaml_testing.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl 3 | 4 | import 5 | tables, 6 | unittest 7 | 8 | const divider* = "\n~~~~~~~~~~~~\n" 9 | template echod*(s) = 10 | echo s 11 | echo divider 12 | 13 | proc roundTrip*(n: YNode): YNode = 14 | n.toString().loadNode() 15 | 16 | proc checkRoundTrip*(n: YNode) = 17 | check n == roundTrip(n) 18 | 19 | proc checkRoundTrip*[T](x: T) = 20 | let n = toYaml(x) 21 | checkRoundTrip n 22 | 23 | proc checkRoundTrip*(s: string) = 24 | let n = s.loadNode() 25 | checkRoundTrip n 26 | 27 | type 28 | MockObj* = object of RootObj 29 | s*: string 30 | i*: int 31 | 32 | deriveYaml MockObj -------------------------------------------------------------------------------- /tests/tyaml_seq.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl, 3 | test_utils/yaml_testing, 4 | std/options, 5 | strutils, 6 | unittest 7 | 8 | type 9 | Person* = object of RootObj 10 | id: int 11 | names*: seq[string] 12 | Team* = object of RootObj 13 | id: int 14 | members*: seq[Person] 15 | 16 | deriveYamls: 17 | Person 18 | Team 19 | 20 | var sample = """ 21 | id: 1 22 | names: 23 | - Bobby 24 | """ 25 | let bobby = ofYamlStr(sample, Person) 26 | check bobby.id == 1 27 | check bobby.names.len == 1 28 | check bobby.names[0] == "Bobby" 29 | 30 | var noNameYaml = """ 31 | id: 0 32 | """ 33 | let noName = ofYamlStr(noNameYaml, Person) 34 | check noName.id == 0 35 | check noName.names.len == 0 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/tyaml_accentquotes.nim: -------------------------------------------------------------------------------- 1 | import 2 | macros, 3 | unittest, 4 | yanyl, 5 | test_utils/yaml_testing 6 | 7 | type 8 | Owner = object of RootObj 9 | name: string 10 | `addr`: string 11 | Pet = ref object of RootObj 12 | name*: string 13 | `type`*: string 14 | owner: Owner 15 | 16 | deriveYamls: 17 | Owner 18 | Pet 19 | 20 | let sample = """ 21 | name: Garfield 22 | type: cat 23 | owner: 24 | name: J. Arbuckle 25 | addr: 711 Maple Street 26 | """ 27 | 28 | let garf = ofYamlStr(sample, Pet) 29 | check garf.name == "Garfield" 30 | check garf.`type` == "cat" 31 | check garf.owner.name == "J. Arbuckle" 32 | check garf.owner.`addr` == "711 Maple Street" 33 | 34 | checkRoundTrip garf 35 | -------------------------------------------------------------------------------- /tests/tyaml_polymorphism.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl, 3 | unittest 4 | 5 | type 6 | Base = ref object of RootObj 7 | name: string 8 | A = ref object of Base 9 | val: int 10 | B = ref object of Base 11 | otherVal: bool 12 | 13 | deriveYamls: 14 | Base 15 | A 16 | B 17 | 18 | var item: Base 19 | var items: seq[Base] 20 | 21 | let sample = """ 22 | name: Bob 23 | val: 10 24 | """ 25 | 26 | item = ofYamlStr(sample, Base) 27 | check item.name == "Bob" 28 | 29 | let samples = """ 30 | - name: Bill 31 | val: 10 32 | - name: Denise 33 | otherVal: true 34 | """ 35 | 36 | items = ofYamlStr(samples, seq[Base]) 37 | check len(items) == 2 38 | check items[0].name == "Bill" 39 | check items[1].name == "Denise" 40 | -------------------------------------------------------------------------------- /tests/tyaml_table.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl, 3 | std/options, 4 | std/tables, 5 | unittest 6 | 7 | type 8 | Dotfile* = object 9 | path*: string 10 | gitPath*: string 11 | 12 | deriveYaml Dotfile 13 | 14 | var sample = """ 15 | all: 16 | - path: ~/.zshrc 17 | gitPath: .zshrc 18 | """ 19 | let t = ofYamlStr(sample, Table[string, seq[Dotfile]]) 20 | check t["all"].len() == 1 21 | check t["all"][0].path == "~/.zshrc" 22 | check "work" in t == false 23 | 24 | let tr = ofYamlStr(sample, TableRef[string, seq[Dotfile]]) 25 | check tr["all"].len() == 1 26 | check tr["all"][0].path == "~/.zshrc" 27 | 28 | sample = """ 29 | zsh: 30 | path: ~/.zshrc 31 | gitPath: .zshrc 32 | """ 33 | let t2 = ofYamlStr(sample, Table[string, Dotfile]) 34 | check t2.len() == 1 35 | check t2["zsh"].path == "~/.zshrc" -------------------------------------------------------------------------------- /tests/tyaml_inheritance.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl, 3 | test_utils/yaml_testing, 4 | unittest 5 | 6 | type 7 | Base = object of RootObj 8 | s1: string 9 | Deriv = object of Base 10 | s2: string 11 | RDeriv = ref object of Base 12 | i1: int 13 | 14 | deriveYamls: 15 | Base 16 | Deriv 17 | RDeriv 18 | 19 | var sample: string = """ 20 | s1: abcdef 21 | """ 22 | 23 | var base: Base = ofYamlStr(sample, Base) 24 | check base.s1 == "abcdef" 25 | 26 | sample = """ 27 | s1: xyz 28 | s2: thomas was here 29 | """ 30 | var deriv: Deriv = ofYamlStr(sample, Deriv) 31 | check deriv.s1 == "xyz" 32 | check deriv.s2 == "thomas was here" 33 | checkRoundTrip deriv 34 | 35 | sample = """ 36 | s1: yep 37 | i1: 73 38 | """ 39 | var rderiv: RDeriv = ofYamlStr(sample, RDeriv) 40 | check rderiv.s1 == "yep" 41 | check rderiv.i1 == 73 42 | checkRoundTrip rderiv -------------------------------------------------------------------------------- /tests/tyaml_typealias.nim: -------------------------------------------------------------------------------- 1 | import 2 | macros, 3 | sequtils, 4 | tables, 5 | unittest, 6 | yanyl, 7 | test_utils/yaml_testing 8 | 9 | 10 | type 11 | IntList = seq[int] 12 | Dict = TableRef[string, string] 13 | MyInt = int 14 | 15 | expandMacros: 16 | deriveYamls: 17 | IntList 18 | Dict 19 | MyInt 20 | 21 | var sample: string = """ 22 | - 7 23 | - 8 24 | - 9 25 | """ 26 | let il: IntList = ofYamlStr(sample, IntList) 27 | check il.len() == 3 28 | check il[0] == 7 29 | check il[1] == 8 30 | check il[2] == 9 31 | checkRoundTrip il 32 | 33 | sample = """ 34 | a: x 35 | b: y 36 | c: z 37 | """ 38 | let dict: Dict = ofYamlStr(sample, Dict) 39 | check dict.len() == 3 40 | check dict["a"] == "x" 41 | check dict["b"] == "y" 42 | check dict["c"] == "z" 43 | checkRoundTrip dict 44 | 45 | check ofYamlStr("3", MyInt) == 3 46 | check ofYamlStr("-78", MyInt) == -78 -------------------------------------------------------------------------------- /yanyl.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | # Yet Another Nim Yaml Library 3 | # YANYL 4 | version = "1.3.0" 5 | author = "Thomas Nelson" 6 | description = "A library for working with YAML in Nim" 7 | license = "Unlicense" 8 | srcDir = "src" 9 | 10 | 11 | # Dependencies 12 | 13 | requires "nim >= 2.0.2" 14 | requires "fusion" 15 | requires "yaml == 2.0.0" 16 | 17 | import 18 | strformat 19 | 20 | task test, "Runs the tests": 21 | exec "testament p 'tests/t*.nim'" 22 | 23 | task genDocs, "Generate the docs": 24 | let gitHash = gorge "git rev-parse --short HEAD" 25 | let url = "https://github.com/tanelso2/yanyl" 26 | exec fmt"nim doc --project --git.url:{url} --git.commit:{gitHash} --git.devel:main --outdir:docs src/yanyl.nim" 27 | exec "cp docs/yanyl.html docs/index.html" 28 | 29 | task release, "Do a release at the current version": 30 | exec fmt"git commit -a -m 'v{version}'" 31 | exec fmt"git tag v{version}" 32 | exec fmt"git push" 33 | exec fmt"git push origin v{version}" 34 | -------------------------------------------------------------------------------- /tests/tloadnode.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl 3 | 4 | let s = """ 5 | a: 1 6 | b: 3 7 | c: 8 | - a 9 | - b 10 | - c 11 | """ 12 | 13 | let y: YNode = s.loadNode() 14 | assert y.kind == ynMap 15 | assert y.get("a").kind == ynString 16 | assert y.get("a").strVal == "1" 17 | assert y.get("b").kind == ynString 18 | assert y.get("b").strVal == "3" 19 | let c = y.get("c") 20 | assert c.kind == ynList 21 | let firstElem = c.elems()[0] 22 | assert firstElem.kind == ynString 23 | assert firstElem.strVal == "a" 24 | 25 | import 26 | streams 27 | 28 | let s2 = newFileStream("tests/example.yaml") 29 | let y2: YNode = s2.loadNode() 30 | assert y2.kind == ynMap 31 | assert y2.get("name").kind == ynString 32 | assert y2.get("name").strVal == "Peter Parker" 33 | assert y2.get("occupation").kind == ynString 34 | assert y2.get("occupation").strVal == "Photographer" 35 | assert y2.get("aliases").kind == ynList 36 | let aliases = y2.get("aliases").elems() 37 | let tiger = aliases[0] 38 | assert tiger.kind == ynString 39 | assert tiger.strVal == "Tiger" 40 | 41 | -------------------------------------------------------------------------------- /tests/tyaml_variant.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl, 3 | test_utils/yaml_testing, 4 | unittest 5 | 6 | type 7 | E = enum 8 | eStr, eInt 9 | V = object 10 | c: string 11 | case kind: E 12 | of eStr: 13 | s: string 14 | of eInt: 15 | i: int 16 | 17 | deriveYamls: 18 | E 19 | V 20 | 21 | var sample: string 22 | sample = """ 23 | kind: eStr 24 | c: c1 25 | s: s1 26 | """ 27 | 28 | var v: V = sample.ofYamlStr(V) 29 | 30 | check v.kind == eStr 31 | check v.c == "c1" 32 | check v.s == "s1" 33 | checkRoundTrip v 34 | 35 | sample = """ 36 | kind: eInt 37 | c: c2 38 | i: 10 39 | """ 40 | 41 | v = sample.ofYamlStr(V) 42 | check v.kind == eInt 43 | check v.c == "c2" 44 | check v.i == 10 45 | checkRoundTrip v 46 | 47 | type 48 | VList = object of RootObj 49 | l: seq[V] 50 | 51 | deriveYaml VList 52 | 53 | sample = """ 54 | l: 55 | - kind: eInt 56 | c: ci 57 | i: 10 58 | - kind: eStr 59 | c: cs 60 | s: hello world 61 | """ 62 | var vlist: VList = ofYamlStr(sample, VList) 63 | check vlist.l.len() == 2 64 | let ci = vlist.l[0] 65 | check ci.kind == eInt 66 | check ci.c == "ci" 67 | check ci.i == 10 68 | let cs = vlist.l[1] 69 | check cs.kind == eStr 70 | check cs.c == "cs" 71 | check cs.s == "hello world" 72 | -------------------------------------------------------------------------------- /tests/tyaml_option.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl, 3 | test_utils/yaml_testing, 4 | std/options, 5 | strutils, 6 | unittest 7 | 8 | type 9 | Job* = object of RootObj 10 | name*: string 11 | weaponOfChoice*: Option[string] 12 | Hero* = object of RootObj 13 | name*: string 14 | primaryJob*: Job 15 | secondaryJob*: Option[Job] 16 | 17 | deriveYamls: 18 | Job 19 | Hero 20 | 21 | var sample = """ 22 | - name: Guy 23 | primaryJob: 24 | name: Samurai 25 | weaponOfChoice: katana 26 | secondaryJob: 27 | name: Monk 28 | weaponOfChoice: fists 29 | - name: Hekate 30 | primaryJob: 31 | name: Witch 32 | """ 33 | 34 | let heroes = ofYamlStr(sample, seq[Hero]) 35 | let guy = heroes[0] 36 | let hekate = heroes[1] 37 | 38 | check guy.name == "Guy" 39 | check guy.primaryJob.name == "Samurai" 40 | check guy.primaryJob.weaponOfChoice.get() == "katana" 41 | check guy.secondaryJob.isSome() == true 42 | check guy.secondaryJob.get().name == "Monk" 43 | 44 | check hekate.name == "Hekate" 45 | check hekate.primaryJob.name == "Witch" 46 | check hekate.primaryJob.weaponOfChoice.isNone() 47 | check hekate.secondaryJob.isNone() 48 | 49 | let hs = hekate.toYamlStr() 50 | # toString shouldn't put nones in the maps 51 | check hs.contains("secondaryJob") == false 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the "main" branch 8 | push: 9 | branches: [ "main" ] 10 | pull_request: 11 | branches: [ "main" ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v3 27 | - name: Install Nim 28 | # You may pin to the exact commit or the version. 29 | # uses: iffy/install-nim@7dd1812db4916d00b984d1c43339346a76e05487 30 | uses: iffy/install-nim@v4.1.3 31 | # with: 32 | # version: # optional, default is stable 33 | - name: Install deps 34 | run: nimble install -y 35 | - name: Nimble test 36 | run: | 37 | nimble test 38 | - name: Nimble genDocs 39 | run: | 40 | nimble genDocs 41 | -------------------------------------------------------------------------------- /tests/tyaml_vetoffice.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl, 3 | unittest 4 | 5 | type 6 | CatBreed = enum 7 | cbMaineCoon = "MaineCoon" 8 | cbPersian = "Persian" 9 | cbBengal = "Bengal" 10 | DogBreed = enum 11 | dbCorgi = "Corgi" 12 | dbMastiff = "Mastiff" 13 | PetType = enum 14 | ptCat = "cat" 15 | ptDog = "dog" 16 | Nameable = object of RootObj 17 | name: string 18 | Pet = ref object of Nameable 19 | vet: Nameable 20 | case kind: PetType 21 | of ptCat: 22 | catBreed: CatBreed 23 | of ptDog: 24 | dogBreed: DogBreed 25 | Owner = object of Nameable 26 | paid: bool 27 | pets: seq[Pet] 28 | 29 | deriveYamls: 30 | CatBreed 31 | DogBreed 32 | PetType 33 | Nameable 34 | Pet 35 | Owner 36 | 37 | var s: string = """ 38 | name: Duncan Indigo 39 | paid: false 40 | pets: 41 | - name: Ginger 42 | vet: 43 | name: Maria Belmont 44 | kind: cat 45 | catBreed: MaineCoon 46 | - name: Buttersnap 47 | vet: 48 | name: Maria Belmont 49 | kind: dog 50 | dogBreed: Corgi 51 | """ 52 | 53 | let duncan = ofYamlStr(s, Owner) 54 | 55 | check duncan.name == "Duncan Indigo" 56 | check duncan.paid == false 57 | check duncan.pets.len() == 2 58 | 59 | let ginger = duncan.pets[0] 60 | check ginger.name == "Ginger" 61 | check ginger.kind == ptCat 62 | check ginger.catBreed == cbMaineCoon 63 | check ginger.vet.name == "Maria Belmont" 64 | 65 | let buttersnap = duncan.pets[1] 66 | check buttersnap.name == "Buttersnap" 67 | check buttersnap.kind == ptDog 68 | check buttersnap.dogBreed == dbCorgi 69 | check buttersnap.vet.name == "Maria Belmont" -------------------------------------------------------------------------------- /tests/tyaml_object.nim: -------------------------------------------------------------------------------- 1 | import 2 | test_utils/yaml_testing, 3 | macros, 4 | unittest 5 | 6 | import 7 | yanyl 8 | 9 | type 10 | Obj = object 11 | i: int 12 | s: string 13 | 14 | deriveYaml Obj 15 | 16 | var sample: string = """ 17 | i: 99 18 | s: hello world 19 | """ 20 | var o: Obj = ofYamlStr(sample, Obj) 21 | check o.i == 99 22 | check o.s == "hello world" 23 | 24 | type 25 | MyType = object of RootObj 26 | pulse: bool 27 | breathing: bool 28 | 29 | deriveYaml MyType 30 | 31 | sample = """ 32 | pulse: true 33 | breathing: true 34 | """ 35 | 36 | var mt: MyType = ofYamlStr(sample, MyType) 37 | check mt.pulse == true 38 | check mt.breathing == true 39 | 40 | sample = """ 41 | pulse: true 42 | breathing: false 43 | """ 44 | mt = ofYamlStr(sample, MyType) 45 | check mt.pulse == true 46 | check mt.breathing == false 47 | 48 | type 49 | Nested = ref object of RootObj 50 | n: string 51 | t: MyType 52 | 53 | deriveYaml Nested 54 | 55 | sample = """ 56 | n: Pinhead Larry 57 | t: 58 | pulse: true 59 | breathing: true 60 | """ 61 | 62 | var nested: Nested = ofYamlStr(sample, Nested) 63 | check nested.n == "Pinhead Larry" 64 | check nested.t.pulse == true 65 | check nested.t.breathing == true 66 | 67 | type 68 | Status = ref object of MyType 69 | bpm: int 70 | Vitals = ref object of RootObj 71 | status: Status 72 | name: string 73 | 74 | deriveYamls: 75 | Status 76 | Vitals 77 | 78 | sample = """ 79 | status: 80 | bpm: 90 81 | pulse: true 82 | breathing: true 83 | name: Leonard Snart 84 | """ 85 | 86 | var v: Vitals = ofYamlStr(sample, Vitals) 87 | check v.status.bpm == 90 88 | check v.status.pulse == true 89 | check v.status.breathing == true 90 | check v.name == "Leonard Snart" -------------------------------------------------------------------------------- /.github/workflows/docGen.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: docGen 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push 8 | push: 9 | branches: [ "main" ] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | concurrency: 15 | group: "pages" 16 | cancel-in-progress: true 17 | 18 | permissions: 19 | contents: read 20 | pages: write 21 | id-token: write 22 | 23 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 24 | jobs: 25 | # This workflow contains a single job called "build" 26 | build: 27 | # The type of runner that the job will run on 28 | runs-on: ubuntu-latest 29 | 30 | # Steps represent a sequence of tasks that will be executed as part of the job 31 | steps: 32 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 33 | - uses: actions/checkout@v3 34 | - name: Install Nim 35 | # You may pin to the exact commit or the version. 36 | # uses: iffy/install-nim@7dd1812db4916d00b984d1c43339346a76e05487 37 | uses: iffy/install-nim@v4.1.3 38 | # with: 39 | # version: # optional, default is stable 40 | - name: Install deps 41 | run: nimble install -y 42 | - name: Nimble genDocs 43 | run: | 44 | nimble genDocs 45 | cp -r ./docs ./_site 46 | - name: Setup Pages 47 | uses: actions/configure-pages@v2 48 | - name: Upload artifact 49 | uses: actions/upload-pages-artifact@v1 50 | deploy: 51 | environment: 52 | name: github-pages 53 | url: ${{ steps.deployment.outputs.page_url }} 54 | runs-on: ubuntu-latest 55 | needs: build 56 | steps: 57 | - name: Deploy to Github Pages 58 | id: deployment 59 | uses: actions/deploy-pages@v1 -------------------------------------------------------------------------------- /tests/tyaml_mounts.nim: -------------------------------------------------------------------------------- 1 | import 2 | yanyl, 3 | test_utils/yaml_testing, 4 | unittest 5 | 6 | type 7 | MountKind* = enum 8 | mkTmpfs = "tmpfs" 9 | mkS3fs = "s3fs" 10 | Mount* = object 11 | mountPoint: string 12 | name: string 13 | case kind: MountKind 14 | of mkTmpfs: 15 | discard 16 | of mkS3fs: 17 | key: string 18 | secret: string 19 | bucket: string 20 | Con* = object of RootObj 21 | name*: string 22 | mounts*: seq[Mount] 23 | 24 | deriveYamls: 25 | MountKind 26 | Mount 27 | Con 28 | 29 | let noMounts = Con(name:"example", mounts: @[]) 30 | 31 | let m = Mount(mountPoint: "/etc/tmpfs", kind: mkTmpfs, name: "tmp") 32 | 33 | var c2 = Con(name: "c2", mounts: @[m]) 34 | 35 | echod m.toYaml().toString() 36 | 37 | echod noMounts.toYaml().toString() 38 | echod c2.toYaml().toString() 39 | 40 | let cy: YNode = c2.toYaml() 41 | 42 | let m2: Mount = ofYaml(m.toYaml(), Mount) 43 | check m2.name == m.name 44 | check m2.mountPoint == m.mountPoint 45 | check m2.kind == m.kind 46 | checkRoundTrip m2 47 | 48 | 49 | let c3: Con = ofYaml(cy, Con) 50 | check c3.name == c2.name 51 | check len(c3.mounts) == 1 52 | check c3.mounts[0].name == m2.name 53 | checkRoundTrip c3 54 | 55 | checkRoundTrip cy 56 | 57 | var sample: string = """ 58 | name: personal-website 59 | mounts: 60 | - name: m1 61 | mountPoint: /mnt/m1 62 | kind: tmpfs 63 | """ 64 | 65 | var c: Con = ofYamlStr(sample, Con) 66 | check c.name == "personal-website" 67 | check c.mounts[0].name == "m1" 68 | check c.mounts[0].mountPoint == "/mnt/m1" 69 | check c.mounts[0].kind == mkTmpfs 70 | 71 | sample = """ 72 | name: nginx 73 | mounts: 74 | - name: cache 75 | mountPoint: /mnt/cache 76 | kind: tmpfs 77 | - name: data 78 | mountPoint: /mnt/data 79 | kind: s3fs 80 | bucket: abc 81 | key: akey 82 | secret: asecret 83 | """ 84 | c = ofYamlStr(sample, Con) 85 | check c.name == "nginx" 86 | check c.mounts.len() == 2 87 | let m0 = c.mounts[0] 88 | check m0.name == "cache" 89 | check m0.kind == mkTmpfs 90 | check m0.mountPoint == "/mnt/cache" 91 | let m1 = c.mounts[1] 92 | check m1.name == "data" 93 | check m1.kind == mkS3fs 94 | check m1.mountPoint == "/mnt/data" 95 | check m1.bucket == "abc" 96 | check m1.key == "akey" 97 | -------------------------------------------------------------------------------- /tests/tyaml.nim: -------------------------------------------------------------------------------- 1 | import 2 | std/tables, 3 | unittest 4 | 5 | import 6 | yanyl 7 | 8 | import 9 | test_utils/yaml_testing 10 | 11 | let sampleNodeStr = """ 12 | { "i": 1, "f": 0.1, "s": "hello"} 13 | """ 14 | checkRoundTrip sampleNodeStr 15 | 16 | var mynode: YNode 17 | mynode = loadNode(sampleNodeStr) 18 | checkRoundTrip mynode 19 | check mynode.kind == ynMap 20 | check mynode.mapVal.len() == 3 21 | check mynode.get("i").toInt() == 1 22 | check mynode.get("f").toFloat() == 0.1 23 | check mynode.get("s").str() == "hello" 24 | 25 | var sampleStr = """ 26 | a: 27 | - 1 28 | - 2 29 | - 3 30 | b: false 31 | """ 32 | checkRoundTrip sampleStr 33 | 34 | mynode = loadNode(sampleStr) 35 | checkRoundTrip mynode 36 | check mynode.kind == ynMap 37 | check mynode.mapVal.len() == 2 38 | let a = mynode.get("a") 39 | check a.kind == ynList 40 | check a.elems().len() == 3 41 | check a.elems()[0].str() == "1" 42 | check mynode.get("b").kind == ynString 43 | 44 | let intList: YNode = newYList(@[ 45 | newYString("1"), 46 | newYString("2"), 47 | newYString("3") 48 | ]) 49 | 50 | checkRoundTrip intList 51 | 52 | let heteroList: YNode = newYList(@[ 53 | newYString("1"), 54 | newYString("2"), 55 | newYList(@[newYString("3"), newYString("4")]), 56 | newYString("5") 57 | ]) 58 | checkRoundTrip heteroList 59 | 60 | 61 | let smallList: YNode = newYList(@["a", "b", "c", "d"]) 62 | checkRoundTrip smallList 63 | 64 | let t = { 65 | "x": smallList, 66 | "y": newYString("yay"), 67 | "z": heteroList, 68 | "z2": heteroList, 69 | }.newTable() 70 | 71 | let mapExample: YNode = newYMap(t) 72 | checkRoundTrip mapExample 73 | 74 | let t2 = { 75 | "apple": newYString("red"), 76 | "orange": heteroList, 77 | "banana": mapExample 78 | }.newTable() 79 | 80 | let map2: YNode = newYMap(t2) 81 | checkRoundTrip map2 82 | 83 | 84 | # Check Maps under lists 85 | let map3 = newYMap({ 86 | "example1": newYList(@[newYString("0.12"), map2]), 87 | "example2": mapExample 88 | }) 89 | 90 | checkRoundTrip map3 91 | 92 | var s = """ 93 | a: 1 94 | b: 2 95 | c: 96 | d: 4 97 | e: 5 98 | f: 99 | - 6 100 | - 7 101 | - 8 102 | """ 103 | checkRoundTrip s 104 | 105 | # Empty list 106 | let emptyNodes: seq[YNode] = @[] 107 | let emptyList = newYList(emptyNodes) 108 | checkRoundTrip emptyList 109 | let emptyList2 = emptyList.toString().loadNode() 110 | assert emptyList2.kind == ynList 111 | assert emptyList2.listVal.len() == 0 112 | 113 | # empty map 114 | let emptyMap = newYMap(newTable[string,YNode]()) 115 | checkRoundTrip emptyMap 116 | let emptyMap2 = emptyMap.toString().loadNode() 117 | checkRoundTrip emptyMap2 118 | assert emptyMap2.kind == ynMap 119 | assert emptyMap2.mapVal.len() == 0 120 | 121 | # empty string 122 | let emptyString = newYString("") 123 | checkRoundTrip emptyString 124 | let emptyStringList = newYList(@[emptyString, emptyString]) 125 | checkRoundTrip emptyStringList 126 | let emptyStringMap = newYMap({"a": emptyString, "b": newYString("1")}) 127 | checkRoundTrip emptyStringMap -------------------------------------------------------------------------------- /tests/tyaml_extension.nim: -------------------------------------------------------------------------------- 1 | import 2 | strformat, 3 | tables, 4 | unittest 5 | 6 | import 7 | yanyl 8 | 9 | import 10 | test_utils/yaml_testing 11 | 12 | type 13 | MountKind* = enum 14 | mkTmpfs = "tmpfs" 15 | mkS3fs = "s3fs" 16 | Mount* = object 17 | mountPoint: string 18 | name: string 19 | case kind: MountKind 20 | of mkTmpfs: 21 | discard 22 | of mkS3fs: 23 | key: string 24 | secret: string 25 | bucket: string 26 | Con* = object of RootObj 27 | name*: string 28 | mounts*: seq[Mount] 29 | 30 | proc ofYaml(n: YNode, t: typedesc[MountKind]): MountKind = 31 | assertYString n 32 | case n.strVal 33 | of $mkTmpfs: 34 | result = mkTmpfs 35 | of $mkS3fs: 36 | result = mkS3fs 37 | else: 38 | raise newException(ValueError, fmt"unknown kind {n.strVal}") 39 | 40 | 41 | proc ofYaml(n: YNode, t: typedesc[Mount]): Mount = 42 | assertYMap n 43 | let kind = ofYaml(n.get("kind"), MountKind) 44 | let mountPoint = n.get("mountPoint").str() 45 | let name = n.getStr("name") 46 | case kind 47 | of mkS3fs: 48 | let key = n.get("key").str() 49 | let secret = n.getStr("secret") 50 | let bucket = n.getStr("bucket") 51 | result = Mount(kind: kind, 52 | mountPoint: mountPoint, 53 | key: key, 54 | secret: secret, 55 | bucket: bucket, 56 | name: name) 57 | of mkTmpfs: 58 | result = Mount(kind: kind, 59 | mountPoint: mountPoint, 60 | name: name) 61 | 62 | proc ofYaml(n: YNode, t: typedesc[Con]): Con = 63 | assertYMap n 64 | let name = n.getStr("name") 65 | let mounts: seq[Mount] = ofYaml(n.get("mounts"), seq[Mount]) 66 | return Con(name: name, mounts: mounts) 67 | 68 | proc toYaml(m: Mount): YNode = 69 | let common = { 70 | "kind": toYaml($m.kind), 71 | "mountPoint": toYaml(m.mountPoint), 72 | "name": toYaml(m.name) 73 | } 74 | var extra: seq[(string,YNode)] 75 | case m.kind 76 | of mkTmpfs: 77 | extra = @[] 78 | of mkS3fs: 79 | extra = @[ 80 | ("key", toYaml(m.key)), 81 | ("secret", toYaml(m.secret)), 82 | ("bucket", toYaml(m.bucket)) 83 | ] 84 | let t = newTable[string,YNode]() 85 | for _,(k,v) in common: 86 | t[k] = v 87 | for _,(k,v) in extra: 88 | t[k] = v 89 | 90 | return newYMap(t) 91 | 92 | 93 | proc toYaml(c: Con): YNode = 94 | { 95 | "name": toYaml(c.name), 96 | "mounts": toYaml(c.mounts) 97 | }.newYMap() 98 | 99 | let noMounts = Con(name:"example", mounts: @[]) 100 | 101 | let m = Mount(mountPoint: "/etc/tmpfs", kind: mkTmpfs, name: "tmp") 102 | 103 | var c2 = Con(name: "c2", mounts: @[m]) 104 | 105 | echod m.toYaml().toString() 106 | 107 | echod noMounts.toYaml().toString() 108 | echod c2.toYaml().toString() 109 | 110 | let cy: YNode = c2.toYaml() 111 | 112 | let m2 = ofYaml(m.toYaml(), Mount) 113 | check m2.name == m.name 114 | check m2.mountPoint == m.mountPoint 115 | check m2.kind == m.kind 116 | checkRoundTrip m2 117 | 118 | 119 | let c3: Con = ofYaml(cy, Con) 120 | check c3.name == c2.name 121 | check len(c3.mounts) == 1 122 | check c3.mounts[0].name == m2.name 123 | checkRoundTrip c3 124 | 125 | checkRoundTrip cy 126 | 127 | # var s = newFileStream("out.yaml", fmWrite) 128 | # dump(noMounts, s) 129 | # s.close() 130 | 131 | # s = newFileStream("out2.yaml", fmWrite) 132 | # dump(c2,s) 133 | # s.close() 134 | 135 | # import std/typeinfo 136 | 137 | # var x: Any 138 | 139 | # x = c2.toAny 140 | 141 | # # echo x.kind 142 | # # for (name,i) in x.fields: 143 | # # echo name 144 | 145 | # import macros 146 | 147 | # macro dumpTypeImpl(x: typed): untyped = 148 | # newLit(x.getTypeImpl.repr) 149 | 150 | # # echo c2.dumpTypeImpl() 151 | -------------------------------------------------------------------------------- /tests/tyaml_codegen.nim: -------------------------------------------------------------------------------- 1 | import 2 | macros, 3 | sequtils, 4 | yanyl, 5 | yanyl/reflection, 6 | test_utils/yaml_testing, 7 | unittest 8 | 9 | type 10 | Simple = object of RootObj 11 | a: string 12 | Simple2 = object of RootObj 13 | a: string 14 | 15 | proc ofYaml(n: YNode, t: typedesc[Simple]): Simple = 16 | assertYMap n 17 | let a = n.get("a").ofYaml( typeof Simple.a ) 18 | result = Simple(a: a) 19 | 20 | proc toYaml(x: Simple): YNode = 21 | { 22 | "a": toYaml(x.a) 23 | }.newYMap() 24 | 25 | # macro dumpDefn(v: untyped) = 26 | # quote do: 27 | # echo treeRepr(getImpl(`v`)) 28 | 29 | # template dumpDef(v: typed) = dumpDefn(v) 30 | 31 | # dumpDef(ofYaml) 32 | # dumpDef(toYaml) 33 | 34 | macro dumpTypeImpl(x: typed) = 35 | echo newLit(x.getTypeImpl.treeRepr) 36 | 37 | # dumpTypeImpl Simple 38 | 39 | macro dumpImpl(x: typed) = 40 | echo newLit(x.getImpl.treeRepr) 41 | 42 | dumpImpl Simple 43 | 44 | macro dumpTypeKind(x: typed) = 45 | echo newLit($x.typeKind) 46 | 47 | # dumpTypeKind Simple 48 | 49 | macro dumpResolvedTypeKind(x: typed) = 50 | echo newLit($x.getType().typeKind) 51 | 52 | # dumpResolvedTypeKind Simple 53 | 54 | macro dumpTypeInst(x: typed) = 55 | echo newLit(x.getTypeInst().treeRepr) 56 | 57 | # dumpTypeInst Simple 58 | 59 | expandMacros: 60 | deriveYaml Simple2 61 | 62 | import 63 | tables 64 | 65 | echo string 66 | echo typeof string 67 | 68 | dumpTree: 69 | typeof typedesc[string] 70 | typeof string 71 | 72 | let s = newYString("hello").ofYaml(string) 73 | echo s 74 | 75 | let simpleStr = """ 76 | a: hello 77 | """ 78 | checkRoundTrip simpleStr 79 | 80 | var a = simpleStr.loadNode().ofYaml(Simple) 81 | checkRoundTrip a 82 | check a.a == "hello" 83 | 84 | var a2 = simpleStr.loadNode().ofYaml(Simple2) 85 | checkRoundTrip a2 86 | check a2.a == "hello" 87 | 88 | type 89 | Example = object of RootObj 90 | i: int 91 | s: string 92 | f: float 93 | 94 | expandMacros: 95 | deriveYaml Example 96 | 97 | let example = """ 98 | i: 3 99 | s: hey 100 | f: 0.2 101 | """ 102 | let e = example.ofYamlStr(Example) 103 | check e.i == 3 104 | check e.s == "hey" 105 | check e.f == 0.2 106 | 107 | type 108 | Example2 = object of Example 109 | i2: int 110 | 111 | deriveYaml Example2 112 | 113 | # echo (typeof Example2.i2) 114 | # echo (typeof Example2.i) 115 | 116 | # expandMacros: 117 | # deriveYaml Example2 118 | 119 | let example2 = """ 120 | i: 3 121 | i2: 4 122 | s: hey 123 | f: 0.2 124 | """ 125 | let e2: Example2 = example2.loadNode().ofYaml(Example2) 126 | check e2.i == 3 127 | check e2.s == "hey" 128 | check e2.f == 0.2 129 | check e2.i2 == 4 130 | 131 | 132 | type 133 | Base = object of RootObj 134 | a: string 135 | RBase = ref object of Base 136 | Deriv = object of Base 137 | b: int 138 | Complex = object of RootObj 139 | c: string 140 | d: Base 141 | VKind = enum 142 | vk1, vk2 143 | Variant = object of RootObj 144 | c: string 145 | case kind: VKind 146 | of vk1: 147 | v1: string 148 | of vk2: 149 | v2: float 150 | 151 | dumpImpl Base 152 | dumpImpl RBase 153 | dumpImpl Deriv 154 | dumpImpl Variant 155 | dumpImpl Complex 156 | 157 | let cs = """ 158 | c: def 159 | d: 160 | a: abc 161 | """ 162 | 163 | deriveYamls: 164 | Base 165 | RBase 166 | Deriv 167 | VKind 168 | Variant 169 | Complex 170 | 171 | let c: Complex = cs.loadNode().ofYaml(Complex) 172 | 173 | check c.c == "def" 174 | check c.d.a == "abc" 175 | checkRoundTrip c 176 | 177 | type 178 | MyEnum = enum 179 | my1, my2, my3 180 | 181 | deriveYaml MyEnum 182 | 183 | type 184 | MyVariant = object 185 | c: string 186 | case kind: MyEnum 187 | of my1: 188 | i: int 189 | of my2: 190 | discard 191 | of my3: 192 | s: string 193 | 194 | # proc ofYaml(n: YNode, t: typedesc[MyVariant]): MyVariant = 195 | # assertYMap n 196 | # let kind = ofYaml(n.get("kind"), MyEnum) 197 | # case kind 198 | # of my1: 199 | # result = MyVariant(kind: my1, 200 | # c: ofYaml(n.get("c"), string), 201 | # i: ofYaml(n.get("i"), int) 202 | # ) 203 | # of my2: 204 | # result = MyVariant(kind: my2, 205 | # c: ofYaml(n.get("c", string))) 206 | # of my3: 207 | # result = MyVariant(kind: my3, 208 | # c: ofYaml(n.get("c", string)), 209 | # s: ofYaml(n.get("s", string)) 210 | # ) 211 | 212 | # dumpTree: 213 | # proc toYaml(x: MyVariant): YNode = 214 | # case x.kind 215 | # of my1: 216 | # result = newYMap({ 217 | # "kind": toYaml(x.kind), 218 | # "c": toYaml(x.c), 219 | # "i": toYaml(x.i) 220 | # }) 221 | # of my2: 222 | # result = newYMap({ 223 | # "kind": toYaml(x.kind), 224 | # "c": toYaml(x.c), 225 | # }) 226 | # of my3: 227 | # result = newYMap({ 228 | # "kind": toYaml(x.kind), 229 | # "c": toYaml(x.c), 230 | # "s": toYaml(x.s) 231 | # }) 232 | 233 | expandMacros: 234 | deriveYaml MyVariant 235 | 236 | # Testing the types from tyaml_extension 237 | 238 | dumpTree: 239 | type 240 | MountKind* = enum 241 | mkTmpfs = "tmpfs" 242 | mkS3fs = "s3fs" 243 | Mount* = object 244 | mountPoint: string 245 | name: string 246 | case kind: MountKind 247 | of mkTmpfs: 248 | discard 249 | of mkS3fs: 250 | key: string 251 | secret: string 252 | bucket: string 253 | Con* = object of RootObj 254 | name*: string 255 | mounts*: seq[Mount] 256 | 257 | type 258 | MountKind* = enum 259 | mkTmpfs = "tmpfs" 260 | mkS3fs = "s3fs" 261 | Mount* = object 262 | mountPoint: string 263 | name: string 264 | case kind: MountKind 265 | of mkTmpfs: 266 | discard 267 | of mkS3fs: 268 | key: string 269 | secret: string 270 | bucket: string 271 | Con* = object of RootObj 272 | name*: string 273 | mounts*: seq[Mount] 274 | 275 | macro dumpFields(x: typed) = 276 | echo newLit($collectObjFieldsForType(x.getImpl())) 277 | 278 | dumpFields Mount 279 | 280 | expandMacros: 281 | deriveYamls: 282 | MountKind 283 | Mount 284 | Con 285 | 286 | 287 | let noMounts = Con(name:"example", mounts: @[]) 288 | 289 | let m = Mount(mountPoint: "/etc/tmpfs", kind: mkTmpfs, name: "tmp") 290 | 291 | var c2 = Con(name: "c2", mounts: @[m]) 292 | 293 | echod m.toYaml().toString() 294 | 295 | echod noMounts.toYaml().toString() 296 | echod c2.toYaml().toString() 297 | 298 | let cy: YNode = c2.toYaml() 299 | 300 | let m2: Mount = ofYaml(m.toYaml(), Mount) 301 | check m2.name == m.name 302 | check m2.mountPoint == m.mountPoint 303 | check m2.kind == m.kind 304 | checkRoundTrip m2 305 | 306 | 307 | let c3: Con = ofYaml(cy, Con) 308 | check c3.name == c2.name 309 | check len(c3.mounts) == 1 310 | check c3.mounts[0].name == m2.name 311 | checkRoundTrip c3 312 | 313 | checkRoundTrip cy 314 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YANYL (Yet Another Nim Yaml Library) 2 | 3 | A library for working with YAML in Nim. Can generate conversion functions for most types declared in Nim. 4 | 5 | [NimDocs can be found here](https://tanelso2.github.io/yanyl/index.html) 6 | 7 | # Example 8 | 9 | ```nim 10 | import 11 | yanyl 12 | 13 | type 14 | Obj = object 15 | i: int 16 | s: string 17 | 18 | deriveYaml Obj 19 | 20 | var sample: string = """ 21 | i: 99 22 | s: hello world 23 | """ 24 | var o: Obj = ofYamlStr(sample, Obj) 25 | assert o.i == 99 26 | assert o.s == "hello world" 27 | ``` 28 | 29 | # Install 30 | 31 | Add the following to your `.nimble` file: 32 | ``` 33 | requires "yanyl" 34 | ``` 35 | 36 | # Usage 37 | 38 | ## `deriveYaml` 39 | 40 | Macro that takes the name of a type and will generate `ofYaml` and `toYaml` procs for that type. 41 | 42 | ```nim 43 | import 44 | yanyl 45 | 46 | type 47 | Obj = object 48 | i: int 49 | s: string 50 | 51 | deriveYaml Obj 52 | 53 | var sample: string = """ 54 | i: 99 55 | s: hello world 56 | """ 57 | var o: Obj = ofYamlStr(sample, Obj) 58 | assert o.i == 99 59 | assert o.s == "hello world" 60 | ``` 61 | 62 | ## `loadNode` 63 | Proc that takes a string or Stream and returns a `YNode`, the internal representation of a YAML document. 64 | 65 | ```nim 66 | import 67 | yanyl 68 | 69 | let s = """ 70 | a: 1 71 | b: 3 72 | c: 73 | - a 74 | - b 75 | - c 76 | """ 77 | 78 | let y: YNode = s.loadNode() 79 | assert y.kind == ynMap 80 | assert y.get("a").kind == ynString 81 | assert y.get("c").kind == ynList 82 | ``` 83 | 84 | ## `toString` 85 | Proc that returns the YAML string of a `YNode` 86 | 87 | 88 | ## `toYaml` 89 | Proc that takes an object of type `T` and returns a `YNode`. 90 | 91 | Generic proc, should be redefined for every type. Can be autogenerated by `deriveYaml` 92 | 93 | 94 | ## `ofYaml` 95 | Proc that takes a `YNode` and returns an object of type `T`. 96 | 97 | Generic proc, should be redefined for every type. Can be autogenerated by `deriveYaml` 98 | 99 | 100 | ## `ofYamlStr` 101 | Shortcut proc. 102 | 103 | `ofYamlStr(s,t)` is equivalent to `s.loadNode().ofYaml(t)` 104 | 105 | ## `toYamlStr` 106 | Shortcut proc. 107 | 108 | `toYamlStr(x)` is equivalent to `x.toYaml().toString()` 109 | 110 | # Code Generation 111 | 112 | 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. 113 | 114 | Code generation supports most types you can define in Nim: `object`s, `ref object`s, `enum`s, and variant objects are all supported. 115 | 116 | Here's an example with multiple types that get generated and parse the YAML as expected: 117 | 118 | ```nim 119 | import 120 | yanyl, 121 | unittest 122 | 123 | type 124 | CatBreed = enum 125 | cbMaineCoon = "MaineCoon" 126 | cbPersian = "Persian" 127 | cbBengal = "Bengal" 128 | DogBreed = enum 129 | dbCorgi = "Corgi" 130 | dbMastiff = "Mastiff" 131 | PetType = enum 132 | ptCat = "cat" 133 | ptDog = "dog" 134 | Nameable = object of RootObj 135 | name: string 136 | Pet = ref object of Nameable 137 | vet: Nameable 138 | case kind: PetType 139 | of ptCat: 140 | catBreed: CatBreed 141 | of ptDog: 142 | dogBreed: DogBreed 143 | Owner = object of Nameable 144 | paid: bool 145 | pets: seq[Pet] 146 | 147 | deriveYamls: 148 | CatBreed 149 | DogBreed 150 | PetType 151 | Nameable 152 | Pet 153 | Owner 154 | 155 | var s: string = """ 156 | name: Duncan Indigo 157 | paid: false 158 | pets: 159 | - name: Ginger 160 | vet: 161 | name: Maria Belmont 162 | kind: cat 163 | catBreed: MaineCoon 164 | - name: Buttersnap 165 | vet: 166 | name: Maria Belmont 167 | kind: dog 168 | dogBreed: Corgi 169 | """ 170 | 171 | let duncan = ofYamlStr(s, Owner) 172 | 173 | check duncan.name == "Duncan Indigo" 174 | check duncan.paid == false 175 | check duncan.pets.len() == 2 176 | 177 | let ginger = duncan.pets[0] 178 | check ginger.name == "Ginger" 179 | check ginger.kind == ptCat 180 | check ginger.catBreed == cbMaineCoon 181 | check ginger.vet.name == "Maria Belmont" 182 | 183 | let buttersnap = duncan.pets[1] 184 | check buttersnap.name == "Buttersnap" 185 | check buttersnap.kind == ptDog 186 | check buttersnap.dogBreed == dbCorgi 187 | check buttersnap.vet.name == "Maria Belmont" 188 | ``` 189 | 190 | ## Viewing the generated code 191 | 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. 192 | 193 | For example: 194 | 195 | ```nim 196 | import 197 | yanyl, 198 | macros 199 | 200 | type 201 | E = enum 202 | eStr, eInt 203 | V = object 204 | c: string 205 | case kind: E 206 | of eStr: 207 | s: string 208 | of eInt: 209 | i: int 210 | 211 | expandMacros: 212 | deriveYamls: 213 | E 214 | V 215 | 216 | ``` 217 | 218 | will output: 219 | 220 | ```nim 221 | proc ofYaml(n: YNode; t: typedesc[E]): E = 222 | assertYString n 223 | case n.strVal 224 | of $eStr: 225 | eStr 226 | of $eInt: 227 | eInt 228 | else: 229 | raise newException(ValueError, "unknown kind: " & n.strVal) 230 | 231 | proc toYaml(x: E): YNode = 232 | result = newYString($x) 233 | 234 | proc ofYaml(n: YNode; t: typedesc[V]): V = 235 | assertYMap n 236 | let kind = n.get("kind", typedesc[E]) 237 | case kind 238 | of eStr: 239 | V(kind: kind, c: n.get("c", typedesc[string]), 240 | s: n.get("s", typedesc[string])) 241 | of eInt: 242 | V(kind: kind, c: n.get("c", typedesc[string]), 243 | i: n.get("i", typedesc[int])) 244 | 245 | proc toYaml(x: V): YNode = 246 | result = case x.kind 247 | of eStr: 248 | newYMapRemoveNils([("kind", toYaml(x.kind)), ("c", toYaml(x.c)), 249 | ("s", toYaml(x.s))]) 250 | of eInt: 251 | newYMapRemoveNils([("kind", toYaml(x.kind)), ("c", toYaml(x.c)), 252 | ("i", toYaml(x.i))]) 253 | ``` 254 | 255 | # Comparison with [NimYAML](https://github.com/flyx/NimYAML) 256 | 257 | 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. 258 | 259 | Why would you use Yanyl instead of just using NimYAML directly? 260 | 261 | ## Simplified Output 262 | 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. 263 | 264 | For example, the following code 265 | 266 | ```nim 267 | import 268 | yaml, 269 | yanyl 270 | 271 | type 272 | Obj = object of RootObj 273 | i*: int 274 | s*: string 275 | 276 | deriveYaml Obj 277 | 278 | var o = Obj(i: 42, s: "Hello galaxy") 279 | # NimYAML 280 | echo dump(o) 281 | # Yanyl 282 | echo toYamlStr(o) 283 | ``` 284 | 285 | will result in the outputs 286 | 287 | NimYAML: 288 | 289 | ```yaml 290 | %YAML 1.2 291 | %TAG !n! tag:nimyaml.org,2016: 292 | --- !n!custom:Obj 293 | i: 42 294 | s: Hello galaxy 295 | ``` 296 | 297 | Yanyl: 298 | 299 | ```yaml 300 | s: Hello galaxy 301 | i: 42 302 | ``` 303 | 304 | 305 | ## Support for more Nim types 306 | 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. 307 | 308 | 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. 309 | 310 | **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. 311 | **Note 2024-11-18:** some of these things may have been fixed in more recent versions of NimYAML. It has been a while since I checked in on its behavior with types and generating code. 312 | 313 | ## Explicit declarations 314 | 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. 315 | 316 | -------------------------------------------------------------------------------- /src/yanyl/reflection.nim: -------------------------------------------------------------------------------- 1 | import 2 | macros, 3 | sequtils, 4 | strformat, 5 | sugar, 6 | fusion/matching 7 | 8 | {.experimental: "caseStmtMacros".} 9 | 10 | type 11 | Field* = object of RootObj 12 | name*: string 13 | t*: NimNode 14 | ObjType* = enum 15 | otObj, 16 | otVariant, 17 | otEnum, 18 | otEmpty, 19 | otTypeAlias, 20 | otDistinct, 21 | otTuple 22 | NimVariant* = object of RootObj 23 | name*: string 24 | fields*: seq[Field] 25 | EnumVal* = object of RootObj 26 | name*: string 27 | val*: NimNode 28 | ObjFields* = object of RootObj 29 | case kind*: ObjType 30 | of otEmpty: 31 | discard 32 | of otObj: 33 | ## e.g. Type = object of RootObj 34 | ## Object types have a list of fields, 35 | ## including fields from their parents. 36 | fields*: seq[Field] 37 | of otVariant: 38 | ## Variant types have a list of fields in common, 39 | ## but also have variants that can hold additional fields. 40 | common*: seq[Field] 41 | ## Discriminator decides between the variants, 42 | ## and is not included in the common fields 43 | discriminator*: Field 44 | variants*: seq[NimVariant] 45 | of otEnum: 46 | ## e.g. Type = enum 47 | ## Enums have a list of names and their values 48 | vals*: seq[EnumVal] 49 | of otTypeAlias: 50 | ## e.g. Type = seq[Foo] 51 | ## A type alias can hold anything 52 | t*: NimNode 53 | of otDistinct: 54 | ## e.g. Type = distinct int 55 | ## A distinct type can be made from anything 56 | base*: NimNode 57 | of otTuple: 58 | ## e.g. Type = tuple 59 | tupleFields*: seq[Field] 60 | 61 | proc newTupleFields(fields: seq[Field]): ObjFields = 62 | ObjFields(kind: otTuple, tupleFields: fields) 63 | 64 | proc newDistinct(base: NimNode): ObjFields = 65 | ObjFields(kind: otDistinct, base: base) 66 | 67 | proc newTypeAlias(t: NimNode): ObjFields = 68 | ObjFields(kind: otTypeAlias, t: t) 69 | 70 | proc discrim*(o: ObjFields): Field = o.discriminator 71 | 72 | proc newEnumFields(vals: seq[EnumVal]): ObjFields = 73 | ObjFields(kind: otEnum, vals: vals) 74 | 75 | proc newVariantFields(common: seq[Field], discrim: Field, variants: seq[NimVariant]): ObjFields = 76 | ObjFields(kind: otVariant, 77 | common: common, 78 | discriminator: discrim, 79 | variants: variants) 80 | 81 | proc newObjFields(fields: seq[Field]): ObjFields = 82 | ObjFields(kind: otObj, fields: fields) 83 | 84 | proc empty(): ObjFields = 85 | ObjFields(kind: otEmpty) 86 | 87 | proc getName*(f: Field): string = 88 | f.name 89 | 90 | proc getT*(f: Field): NimNode = 91 | f.t 92 | 93 | proc getTypeDefName*(n: NimNode): string = 94 | expectKind(n, nnkTypeDef) 95 | n[0].strVal 96 | 97 | proc combine(a,b: ObjFields): ObjFields = 98 | case (a.kind, b.kind) 99 | of (otEmpty, _): 100 | result = b 101 | of (_, otEmpty): 102 | result = a 103 | of (otObj, otObj): 104 | result = newObjFields(concat(a.fields, b.fields)) 105 | of (otObj, otVariant): 106 | result = newVariantFields( 107 | common=concat(a.fields, b.common), 108 | discrim=b.discrim, 109 | variants=b.variants) 110 | of (otVariant, otObj): 111 | result = newVariantFields( 112 | common=concat(a.common, b.fields), 113 | discrim=a.discrim, 114 | variants=a.variants 115 | ) 116 | of (otVariant, otVariant): 117 | if a.discriminator != b.discriminator: 118 | raise newException(ValueError, "Cannot combine variants of different discriminators") 119 | result = newVariantFields( 120 | common=concat(a.common, b.common), 121 | discrim=a.discrim, 122 | variants=concat(a.variants, b.variants) 123 | ) 124 | of (otEnum, otEnum): 125 | result = newEnumFields(concat(a.vals, b.vals)) 126 | else: 127 | raise newException(ValueError, fmt"No implementation for comparing {a.kind} and {b.kind}") 128 | 129 | proc combineAll(x: seq[ObjFields]): ObjFields = 130 | foldl(x, combine(a,b)) 131 | 132 | proc collectEnumVals(x: NimNode): seq[EnumVal] = 133 | case x.kind 134 | of nnkSym: 135 | result = @[EnumVal(name: x.strVal, val: newStrLitNode(x.strVal))] 136 | of nnkEmpty: 137 | result = @[] 138 | of nnkEnumFieldDef: 139 | result = @[EnumVal(name: x[0].strVal, val: x[1])] 140 | else: 141 | error("Cannot collect enum vals from node ", x) 142 | 143 | proc collectEnumFields(x: NimNode): ObjFields = 144 | expectKind(x, nnkEnumTy) 145 | let vs = collect: 146 | for c in x.children: 147 | collectEnumVals c 148 | let vals = concat(vs) 149 | return ObjFields(kind: otEnum, vals: vals) 150 | 151 | proc getUnadornedName(x: NimNode): string = 152 | ## Removes stars and accent quoting 153 | case x.kind 154 | of nnkIdent, nnkSym: 155 | return x.strVal 156 | of nnkAccQuoted: 157 | return $x 158 | of nnkPostfix: 159 | let op = x[0].strVal 160 | if op == "*": 161 | return getUnadornedName(x[1]) 162 | else: 163 | raise newException(ValueError, fmt"do not know how to handle postfix op {op}") 164 | else: 165 | raise newException(ValueError, fmt"do not know how to get name of {x.kind}") 166 | 167 | # Forward declaration 168 | proc collectObjFieldsForType*(t: NimNode): ObjFields 169 | 170 | proc fieldOfIdentDef(x: NimNode): Field = 171 | expectKind(x, nnkIdentDefs) 172 | # echo x[0] 173 | Field(name: getUnadornedName(x[0]), 174 | t: x[1]) 175 | 176 | # Forward declaration for mutual recursion 177 | proc collectObjFields(x: NimNode): ObjFields 178 | 179 | 180 | proc collectVariantFields(x: NimNode): ObjFields = 181 | expectKind(x, nnkRecCase) 182 | result = ObjFields(kind: otVariant, common: @[], variants: @[]) 183 | for c in x.children: 184 | case c.kind 185 | of nnkIdentDefs: 186 | let discrim = fieldOfIdentDef(c) 187 | result.discriminator = discrim 188 | of nnkOfBranch: 189 | let name = c[0].strVal 190 | let branchFields = collectObjFields(c[1]) 191 | case branchFields.kind 192 | of otObj: 193 | let newVariant = NimVariant(name: name, fields: branchFields.fields) 194 | result.variants.add(newVariant) 195 | of otEmpty: 196 | let newVariant = NimVariant(name: name, fields: @[]) 197 | result.variants.add(newVariant) 198 | else: 199 | error("branch of variant parsed as an enum type or variant type", x) 200 | of nnkEmpty: 201 | discard 202 | else: 203 | error("Don't know how to collect variant fields from ", x) 204 | 205 | proc collectObjFields(x: NimNode): ObjFields = 206 | case x.kind 207 | of nnkIdent, nnkEmpty, nnkNilLit, nnkSym: 208 | return empty() 209 | of nnkRefTy: 210 | return collectObjFields(x[0]) 211 | of nnkIdentDefs: 212 | return ObjFields( 213 | kind: otObj, 214 | fields: @[fieldOfIdentDef(x)] 215 | ) 216 | of nnkRecList: 217 | let r = collect: 218 | for c in x.children: 219 | collectObjFields(c) 220 | return combineAll(r) 221 | of nnkRecCase: 222 | return collectVariantFields(x) 223 | else: 224 | error(fmt"Cannot collect object fields from a NimNode of this kind {x.kind}", x) 225 | 226 | proc getParentFieldsFromInherit(t: NimNode): ObjFields = 227 | case t.kind 228 | of nnkEmpty: 229 | return empty() 230 | of nnkOfInherit: 231 | let parentClassSym = t[0] 232 | if parentClassSym.strVal == "RootObj": 233 | return empty() 234 | else: 235 | return collectObjFieldsForType(parentClassSym.getImpl()) 236 | else: 237 | error("cannot get parent fields from this NimNode", t) 238 | 239 | 240 | 241 | proc collectFieldsFromDefinition(t: NimNode): ObjFields = 242 | case t.kind 243 | of nnkRefTy: 244 | return collectFieldsFromDefinition(t[0]) 245 | of nnkEnumTy: 246 | return collectEnumFields(t) 247 | of nnkObjectTy: 248 | let parent = getParentFieldsFromInherit(t[1]) 249 | let base = collectObjFields(t[2]) 250 | return combine(parent, base) 251 | of nnkDistinctTy: 252 | return newDistinct(t[0]) 253 | of nnkTupleTy: 254 | let objFields = t 255 | .children 256 | .toSeq() 257 | .map(collectObjFields) 258 | .combineAll() 259 | assert objFields.kind == otObj 260 | return newTupleFields(objFields.fields) 261 | else: 262 | return newTypeAlias(t) 263 | 264 | 265 | proc collectObjFieldsForType*(t: NimNode): ObjFields = 266 | ## Takes a Nim type and returns an `ObjFields` object 267 | ## representing the fields of that object 268 | expectKind(t, nnkTypeDef) 269 | let definition = t[2] 270 | collectFieldsFromDefinition(definition) 271 | 272 | macro dumpFields*(x: typed) = 273 | ## Dumps the result of running `collectObjFieldsForType` 274 | ## for the given type 275 | ## 276 | ## Used to debug that proc 277 | ## 278 | ## NOTE: Runs at compile time 279 | echo newLit($collectObjFieldsForType(x.getImpl())) 280 | 281 | macro dumpImpl*(x: typed) = 282 | ## Used to dump the AST definition of x 283 | ## 284 | ## Useful for debugging 285 | ## 286 | ## NOTE: Runs at compile time 287 | echo newLit(fmt"Impl for {x}" & "\n") 288 | echo newLit(x.getImpl.treeRepr) 289 | echo newLit("\n~~~~~~~~~~~~~~\n") 290 | 291 | macro dumpTypeImpl*(x: typed) = 292 | ## Macro to dump the AST tree of the 293 | ## type of x 294 | ## 295 | ## Useful for debugging/writing reflection code 296 | ## 297 | ## NOTE: Runs at compile time 298 | echo newLit(x.getTypeImpl.treeRepr) 299 | -------------------------------------------------------------------------------- /src/yanyl/core.nim: -------------------------------------------------------------------------------- 1 | import 2 | options, 3 | sequtils, 4 | streams, 5 | strformat, 6 | strutils, 7 | sugar, 8 | tables, 9 | yaml, 10 | yaml/data, 11 | yaml/dom 12 | 13 | export data, dom 14 | 15 | type 16 | YNodeKind* = enum 17 | ynString, ynList, ynMap, ynNil 18 | YNode* = object 19 | case kind*: YNodeKind 20 | of ynNil: 21 | discard 22 | of ynString: 23 | strVal*: string 24 | of ynList: 25 | listVal*: seq[YNode] 26 | of ynMap: 27 | mapVal*: TableRef[string, YNode] 28 | 29 | proc newYList*(elems: seq[YNode]): YNode = 30 | runnableExamples: 31 | let list = @[newYNil(), newYNil()] 32 | let node = newYList(list) 33 | doAssert node.kind == ynList 34 | doAssert node.listVal.len == 2 35 | 36 | YNode(kind:ynList, listVal: elems) 37 | 38 | proc newYMap*(t: TableRef[string,YNode]): YNode = 39 | YNode(kind: ynMap, mapVal: t) 40 | 41 | proc newYMap*(a: openArray[(string,YNode)]): YNode = 42 | a.newTable().newYMap() 43 | 44 | proc newYMapRemoveNils*(a: openArray[(string, YNode)]): YNode = 45 | runnableExamples: 46 | import std/tables 47 | let node = newYMapRemoveNils( 48 | [("a", newYString("astring")), 49 | ("b", newYNil())]) 50 | doAssert node.kind == ynMap 51 | doAssert node.mapVal.len == 1 52 | 53 | let t = collect: 54 | for (k,v) in a.items: 55 | if v.kind != ynNil: 56 | (k, v) 57 | return t.newTable().newYMap() 58 | 59 | proc newYString*(s: string): YNode = 60 | YNode(kind: ynString, strVal: s) 61 | 62 | proc newYList*(elems: seq[string]): YNode = 63 | YNode(kind:ynList, listVal: elems.map(newYString)) 64 | 65 | proc newYNil*(): YNode = 66 | YNode(kind: ynNil) 67 | 68 | template expectYString*(n: YNode, body: untyped) {.deprecated.} = 69 | case n.kind 70 | of ynString: 71 | body 72 | else: 73 | raise newException(ValueError, "expected string YNode") 74 | 75 | template expectYList*(n: YNode, body: untyped) {.deprecated.} = 76 | case n.kind 77 | of ynList: 78 | body 79 | else: 80 | raise newException(ValueError, "expected list YNode") 81 | 82 | template expectYMap*(n: YNode, body: untyped) {.deprecated.} = 83 | case n.kind 84 | of ynMap: 85 | body 86 | else: 87 | raise newException(ValueError, "expected map YNode") 88 | 89 | template assertYString*(n: YNode) = 90 | doAssert n.kind == ynString 91 | 92 | template assertYMap*(n: YNode) = 93 | doAssert n.kind == ynMap 94 | 95 | template assertYList*(n: YNode) = 96 | doAssert n.kind == ynList 97 | 98 | proc toYaml*(s: string): YNode = 99 | newYString(s) 100 | 101 | proc toYaml*(i: int): YNode = 102 | newYString($i) 103 | 104 | proc toYaml*(f: float): YNode = 105 | newYString($f) 106 | 107 | proc toYaml*(b: bool): YNode = 108 | newYString($b) 109 | 110 | proc toYaml*(c: char): YNode = 111 | newYString($c) 112 | 113 | proc toYaml*[T](l: seq[T]): YNode = 114 | var elems = newSeq[YNode]() 115 | for x in l: 116 | elems.add(x.toYaml()) 117 | return elems.newYList() 118 | 119 | proc toYaml*[T](o: Option[T]): YNode = 120 | if o.isSome(): 121 | toYaml(o.get()) 122 | else: 123 | newYNil() 124 | 125 | proc toYaml*[T](t: Table[string, T]): YNode = 126 | let m = collect: 127 | for k,v in t.pairs: 128 | (k, toYaml(v)) 129 | newYMap(m.newTable()) 130 | 131 | proc toYaml*[T](t: TableRef[string, T]): YNode = 132 | let m = collect: 133 | for k,v in t.pairs: 134 | (k, toYaml(v)) 135 | newYMap(m.newTable()) 136 | 137 | proc get*(n: YNode, k: string): YNode = 138 | ## Get the map value associated with `k` 139 | ## 140 | ## Throws if `n` is not a map 141 | runnableExamples: 142 | let m = newYMap({ 143 | "a": newYString("astring") 144 | }) 145 | let a = m.get("a") 146 | doAssert a.kind == ynString 147 | doAssert a.strVal == "astring" 148 | 149 | assertYMap n 150 | n.mapVal[k] 151 | 152 | proc elems*(n: YNode): seq[YNode] = 153 | ## Get the list value of the node 154 | ## 155 | ## Throws if `n` is not a list 156 | runnableExamples: 157 | let l = newYList(@[newYNil(), 158 | newYString("abc")]) 159 | let items = l.elems() 160 | doAssert len(items) == 2 161 | doAssert items[0].kind == ynNil 162 | doAssert items[1].strVal == "abc" 163 | 164 | assertYList n 165 | n.listVal 166 | 167 | proc str*(n: YNode): string = 168 | ## Get the string value of the node 169 | ## 170 | ## Throws if `n` is not a string 171 | runnableExamples: 172 | let s = newYString("abc") 173 | doAssert s.str() == "abc" 174 | 175 | assertYString n 176 | n.strVal 177 | 178 | proc getStr*(n: YNode, k: string): string = 179 | assertYMap n 180 | n.get(k).str() 181 | 182 | proc toInt*(n: YNode): int = 183 | ## Get the int value of the node 184 | ## 185 | ## Throws if `n` is not a string 186 | runnableExamples: 187 | let n = newYString("123") 188 | doAssert n.toInt() == 123 189 | 190 | assertYString n 191 | parseInt(n.strVal) 192 | 193 | proc toFloat*(n: YNode): float = 194 | ## Get the float value of the node 195 | ## 196 | ## Throws if `n` is not a string 197 | runnableExamples: 198 | let n = newYString("3.14") 199 | doAssert n.toFloat() == 3.14 200 | 201 | assertYString n 202 | parseFloat(n.strVal) 203 | 204 | proc toChar*(n: YNode): char = 205 | ## Get the char value of the node 206 | ## 207 | ## Throws if `n` is not a string 208 | runnableExamples: 209 | let n = newYString("8") 210 | doAssert n.toChar() == '8' 211 | 212 | assertYString n 213 | let s = n.strVal 214 | if len(s) == 1: 215 | s[0] 216 | else: 217 | raise newException(ValueError, "Cannot make a char out of a string than isn't length 1") 218 | 219 | proc ofYaml*[T](n: YNode, t: typedesc[seq[T]]): seq[T] = 220 | runnableExamples: 221 | let l = newYList(@[ 222 | newYString("1"), 223 | newYString("2"), 224 | newYString("3") 225 | ]) 226 | let res = ofYaml(l, seq[int]) 227 | doAssert res.len == 3 228 | doAssert res[0] == 1 229 | doAssert res[1] == 2 230 | doAssert res[2] == 3 231 | 232 | mixin ofYaml 233 | assertYList n 234 | result = newSeq[T]() 235 | for x in n.elems(): 236 | result.add(ofYaml(x, T)) 237 | 238 | proc ofYaml*[T](n: YNode, t: typedesc[Option[T]]): Option[T] = 239 | runnableExamples: 240 | import std/options 241 | 242 | let n1 = newYNil() 243 | let o1 = ofYaml(n1, Option[string]) 244 | doAssert o1.isNone 245 | let n2 = newYString("heyo") 246 | let o2 = ofYaml(n2, Option[string]) 247 | doAssert o2.isSome 248 | doAssert o2.get() == "heyo" 249 | 250 | case n.kind 251 | of ynNil: 252 | return none(T) 253 | else: 254 | return some(ofYaml(n, T)) 255 | 256 | proc ofYaml*(n: YNode, t: typedesc[int]): int = 257 | runnableExamples: 258 | let n = newYString("8675309") 259 | doAssert ofYaml(n, int) == 8675309 260 | 261 | n.toInt() 262 | 263 | proc ofYaml*(n: YNode, t: typedesc[float]): float = 264 | runnableExamples: 265 | let n = newYString("3.14") 266 | doAssert ofYaml(n, float) == 3.14 267 | 268 | n.toFloat() 269 | 270 | proc ofYaml*(n: YNode, t: typedesc[string]): string = 271 | runnableExamples: 272 | let n = newYString("yep") 273 | doAssert ofYaml(n, string) == "yep" 274 | 275 | n.str() 276 | 277 | proc ofYaml*(n: YNode, t: typedesc[char]): char = 278 | n.toChar() 279 | 280 | proc ofYaml*(n: YNode, t: typedesc[bool]): bool = 281 | runnableExamples: 282 | doAssert ofYaml(newYString("true"), bool) == true 283 | doAssert ofYaml(newYString("false"), bool) == false 284 | 285 | parseBool(n.str()) 286 | 287 | proc ofYaml*[T](n: YNode, t: typedesc[ref T]): ref T = 288 | ofYaml(n, T) 289 | 290 | proc toYaml*[T](x: ref T): YNode = 291 | toYaml() 292 | 293 | proc ofYaml*[T](n: YNode, t: typedesc[Table[string, T]]): Table[string, T] = 294 | assertYMap n 295 | let m = collect: 296 | for k,v in n.mapVal.pairs: 297 | {k: ofYaml(v, T)} 298 | return m 299 | 300 | proc ofYaml*[T](n: YNode, t: typedesc[TableRef[string, T]]): TableRef[string, T] = 301 | assertYMap n 302 | let m = collect: 303 | for k,v in n.mapVal.pairs: 304 | (k, ofYaml(v, T)) 305 | return m.newTable() 306 | 307 | proc get*[T](n: YNode, k: string, t: typedesc[T]): T = 308 | assertYMap n 309 | n.get(k).ofYaml(t) 310 | 311 | proc get*[T](n: YNode, k: string, t: typedesc[Option[T]]): Option[T] = 312 | assertYMap n 313 | let m = n.mapVal 314 | if k in m: 315 | some(ofYaml(m[k], T)) 316 | else: 317 | none(T) 318 | 319 | proc get*[T](n: YNode, k: string, t: typedesc[seq[T]]): seq[T] = 320 | assertYMap n 321 | let m = n.mapVal 322 | if k in m: 323 | ofYaml(m[k], seq[T]) 324 | else: 325 | @[] 326 | 327 | proc simplifyName(k: YamlNode): string = 328 | case k.kind 329 | of yScalar: 330 | return k.content 331 | else: 332 | raise newException(ValueError, "Cannot simplify the name of a non-scalar") 333 | 334 | proc translate(n: YamlNode): YNode = 335 | case n.kind 336 | of yMapping: 337 | let t = newTable[string,YNode](n.fields.len) 338 | for k,v in n.fields.pairs: 339 | let name = simplifyName(k) 340 | t[name] = translate(v) 341 | newYMap(t) 342 | of ySequence: 343 | let elems = n.elems.mapIt(translate(it)) 344 | newYList(elems) 345 | else: 346 | let content = n.content 347 | if content == "null": 348 | newYNil() 349 | else: 350 | newYString(n.content) 351 | 352 | proc loadNode*(s: string | Stream): YNode = 353 | ## Load a YNode from a YAML string or stream 354 | runnableExamples: 355 | let sample = """ 356 | s: x 357 | i: 3 358 | f: 0.32 359 | """ 360 | let n = sample.loadNode() 361 | doAssert n.kind == ynMap 362 | doAssert n.get("s", string) == "x" 363 | doAssert n.get("i", int) == 3 364 | doAssert n.get("f", float) == 0.32 365 | 366 | var node: YamlNode = loadAs[YamlNode](s) 367 | return translate(node) 368 | 369 | proc newline(i: int): string = 370 | "\n" & repeat(' ', i) 371 | 372 | proc needsMultipleLines(n: YNode): bool = 373 | case n.kind 374 | of ynNil, ynString: 375 | false 376 | of ynList: 377 | len(n.listVal) > 0 378 | of ynMap: 379 | len(n.mapVal) > 0 380 | 381 | proc toString*(n: YNode, indentLevel=0): string = 382 | ## Convert a YNode to a YAML string 383 | 384 | proc newline(): string = 385 | newline(indentLevel) 386 | 387 | case n.kind 388 | of ynString: 389 | let s = n.strVal 390 | if len(s) > 0: 391 | return s 392 | else: 393 | return "\"\"" 394 | of ynMap: 395 | let fields = n.mapVal 396 | let s = collect: 397 | for k,v in fields.pairs: 398 | if needsMultipleLines v: 399 | let newIndent = indentLevel+2 400 | let vstr = v.toString(indentLevel=newIndent) 401 | fmt"{k}:{newline(newIndent)}{vstr}" 402 | else: 403 | let newIndent = indentLevel + len(k) + 2 404 | let vstr = v.toString(indentLevel=newIndent) 405 | fmt"{k}: {vstr}" 406 | case len(s) 407 | of 0: 408 | return "{}" 409 | else: 410 | return s.join(newline()) 411 | of ynNil: 412 | return "null" 413 | of ynList: 414 | let elems = n.listVal 415 | case len(elems) 416 | of 0: 417 | return "[]" 418 | else: 419 | return elems 420 | .mapIt(toString(it,indentLevel=indentLevel+2)) 421 | .mapIt("- $1" % it) 422 | .join(newline()) 423 | 424 | proc `==`*(a: YNode, b: YNode): bool {.noSideEffect.} = 425 | ## Compare two yaml documents for equality 426 | if a.kind != b.kind: 427 | return false 428 | else: 429 | case a.kind 430 | of ynNil: 431 | # Two nil vals equal each other 432 | return true 433 | of ynString: 434 | return a.strVal == b.strVal 435 | of ynList: 436 | let la = a.elems() 437 | let lb = b.elems() 438 | return la == lb 439 | of ynMap: 440 | let ma = a.mapVal 441 | let mb = b.mapVal 442 | return ma == mb 443 | 444 | proc ofYamlStr*[T](s: string, t:typedesc[T]): T = 445 | result = s.loadNode().ofYaml(t) 446 | 447 | proc toYamlStr*[T](x: T): string = 448 | x.toYaml().toString() 449 | -------------------------------------------------------------------------------- /src/yanyl/codegen.nim: -------------------------------------------------------------------------------- 1 | import 2 | macros, 3 | sequtils, 4 | strformat, 5 | sugar, 6 | ./reflection 7 | 8 | proc mkYNodeGetCall(n: NimNode, k: string): NimNode = 9 | newCall(newDotExpr(n, ident("get")), 10 | newStrLitNode(k)) 11 | 12 | proc mkYNodeGetCall(n: NimNode, k: string, t: NimNode): NimNode = 13 | newCall(newDotExpr(n, ident("get")), 14 | newLit(k), 15 | t) 16 | 17 | proc mkTypedesc(t: NimNode): NimNode = 18 | nnkBracketExpr.newTree( 19 | ident("typedesc"), t 20 | ) 21 | 22 | proc getValueForField(f: Field, obj: NimNode): NimNode = 23 | mkYNodeGetCall( 24 | obj, 25 | f.getName(), 26 | mkTypedesc(f.getT())) 27 | 28 | proc pubIdent(s: string): NimNode = 29 | nnkPostfix.newTree( 30 | ident("*"), 31 | ident(s) 32 | ) 33 | 34 | proc mkObjTypeConsFieldParam(f: Field, obj: NimNode): NimNode = 35 | newColonExpr( 36 | ident(f.name), 37 | getValueForField(f, obj) 38 | ) 39 | 40 | 41 | 42 | proc mkOfYamlForObjType(t: NimNode, fields: seq[Field]): NimNode = 43 | let retType = t 44 | let nodeParam = newIdentDefs(ident("n"), ident("YNode")) 45 | let typeParam = newIdentDefs(ident("t"), 46 | nnkBracketExpr.newTree( 47 | ident "typedesc", 48 | retType 49 | )) 50 | let n = ident("n") 51 | newProc( 52 | name=pubIdent("ofYaml"), 53 | params=[retType, nodeParam, typeParam], 54 | body=nnkStmtList.newTree( 55 | nnkCommand.newTree( 56 | ident("assertYMap"), 57 | n 58 | ), 59 | nnkObjConstr.newTree( 60 | concat( 61 | @[retType], 62 | fields.mapIt(mkObjTypeConsFieldParam(it, n)))))) 63 | 64 | proc mkOfYamlForTupleType(t: NimNode, fields: seq[Field]): NimNode = 65 | let retType = t 66 | let nodeParam = newIdentDefs(ident("n"), ident("YNode")) 67 | let typeParam = newIdentDefs(ident("t"), 68 | nnkBracketExpr.newTree( 69 | ident "typedesc", 70 | retType 71 | )) 72 | let n = ident("n") 73 | newProc( 74 | name=pubIdent("ofYaml"), 75 | params=[retType, nodeParam, typeParam], 76 | body=nnkStmtList.newTree( 77 | nnkCommand.newTree( 78 | ident "assertYMap", 79 | n 80 | ), 81 | nnkTupleConstr.newTree( 82 | fields.mapIt(mkObjTypeConsFieldParam(it, n))))) 83 | 84 | proc mkObjTypeTableField(f: Field, obj: NimNode): NimNode = 85 | nnkExprColonExpr.newTree( 86 | newStrLitNode(f.name), 87 | newCall( 88 | ident("toYaml"), 89 | newDotExpr( 90 | obj, 91 | ident(f.name) 92 | ) 93 | ) 94 | ) 95 | 96 | proc mkToYamlForObjType(t: NimNode, fields: seq[Field]): NimNode = 97 | let retType = ident("YNode") 98 | let obj = ident("x") 99 | newProc( 100 | name=pubIdent("toYaml"), 101 | params=[retType, newIdentDefs(obj, t)], 102 | body=nnkStmtList.newTree( 103 | newCall( 104 | ident("newYMapRemoveNils"), 105 | nnkTableConstr.newTree( 106 | fields.mapIt(mkObjTypeTableField(it, obj)))))) 107 | 108 | proc mkToYamlForTupleType(t: NimNode, fields: seq[Field]): NimNode = 109 | let retType = ident("YNode") 110 | let obj = ident("x") 111 | newProc( 112 | name=pubIdent("toYaml"), 113 | params=[retType, newIdentDefs(obj, t)], 114 | body=nnkStmtList.newTree( 115 | newCall( 116 | ident("newYMap"), 117 | nnkTableConstr.newTree( 118 | fields.mapIt(mkObjTypeTableField(it, obj)))))) 119 | 120 | proc mkToYamlForEnumType(t: NimNode, vals: seq[EnumVal]): NimNode = 121 | let retType = ident("YNode") 122 | let obj = ident("x") 123 | newProc( 124 | name=pubIdent("toYaml"), 125 | params=[retType, newIdentDefs(obj, t)], 126 | body=nnkStmtList.newTree( 127 | newCall( 128 | ident("newYString"), 129 | nnkPrefix.newTree( 130 | ident("$"), 131 | obj 132 | ) 133 | ) 134 | ) 135 | ) 136 | 137 | proc mkEnumOfBranch(val: EnumVal): NimNode = 138 | nnkOfBranch.newTree( 139 | nnkPrefix.newTree( 140 | ident("$"), 141 | ident(val.name) 142 | ), 143 | newStmtList( 144 | ident(val.name) 145 | ) 146 | ) 147 | 148 | proc mkOfYamlForEnumType(t: NimNode, vals: seq[EnumVal]): NimNode = 149 | let retType = t 150 | let n = ident("n") 151 | let nodeParam = newIdentDefs(n, ident("YNode")) 152 | let typeParam = newIdentDefs(ident("t"), 153 | nnkBracketExpr.newTree( 154 | ident "typedesc", 155 | retType 156 | )) 157 | let elseBranch = 158 | nnkElse.newTree( 159 | newStmtList( 160 | nnkRaiseStmt.newTree( 161 | newCall( 162 | ident("newException"), 163 | ident("ValueError"), 164 | nnkInfix.newTree( 165 | ident("&"), 166 | #TODO: Add type name here 167 | newStrLitNode("unknown kind: "), 168 | nnkDotExpr.newTree( 169 | n, ident("strVal"))))))) 170 | newProc( 171 | name=pubIdent("ofYaml"), 172 | params=[retType, nodeParam, typeParam], 173 | body=nnkStmtList.newTree( 174 | nnkCommand.newTree( 175 | ident "assertYString", 176 | n 177 | ), 178 | nnkCaseStmt.newTree( 179 | concat(@[ 180 | nnkDotExpr.newTree( 181 | n, ident("strVal") 182 | ) 183 | ], 184 | vals.mapIt(mkEnumOfBranch(it)), 185 | @[elseBranch]) 186 | ))) 187 | 188 | proc mkOfYamlForVariantType(t: NimNode, 189 | common: seq[Field], 190 | discrim: Field, 191 | variants: seq[NimVariant]): NimNode = 192 | let retType = t 193 | let n = ident("n") 194 | let kind = ident(discrim.name) 195 | 196 | proc mkVariantOfBranch(v: NimVariant): NimNode = 197 | let neededFields = common & v.fields 198 | nnkOfBranch.newTree( 199 | ident(v.name), 200 | newStmtList( 201 | nnkObjConstr.newTree( 202 | concat( 203 | @[t, 204 | newColonExpr(kind, kind)], 205 | neededFields.mapIt(mkObjTypeConsFieldParam(it, n)))))) 206 | 207 | let nodeParam = newIdentDefs(n, ident("YNode")) 208 | let typedescType = nnkBracketExpr.newTree( 209 | ident "typedesc", retType 210 | ) 211 | let typeParam = newIdentDefs(ident("t"), typedescType) 212 | let ofBranches = collect: 213 | for v in variants: 214 | mkVariantOfBranch(v) 215 | newProc( 216 | name=pubIdent("ofYaml"), 217 | params=[retType, nodeParam, typeParam], 218 | body=newStmtList( 219 | nnkCommand.newTree( 220 | ident "assertYMap", 221 | n 222 | ), 223 | newLetStmt(kind, 224 | getValueForField(discrim, n)), 225 | nnkCaseStmt.newTree( 226 | concat(@[kind], ofBranches)))) 227 | 228 | proc mkToYamlForVariantType(t: NimNode, 229 | common: seq[Field], 230 | discrim: Field, 231 | variants: seq[NimVariant]): NimNode = 232 | let retType = ident("YNode") 233 | let obj = ident("x") 234 | proc mkVariantOfBranch(v: NimVariant): NimNode = 235 | let neededFields = @[discrim] & common & v.fields 236 | nnkOfBranch.newTree( 237 | ident(v.name), 238 | newCall( 239 | ident("newYMapRemoveNils"), 240 | nnkTableConstr.newTree( 241 | neededFields.mapIt(mkObjTypeTableField(it, obj)) 242 | ) 243 | ) 244 | ) 245 | let ofBranches = variants.map(mkVariantOfBranch) 246 | newProc( 247 | name=pubIdent("toYaml"), 248 | params=[retType, newIdentDefs(obj, t)], 249 | body=newStmtList( 250 | nnkCaseStmt.newTree( 251 | concat( 252 | @[newDotExpr(obj, ident(discrim.name))], 253 | ofBranches 254 | ) 255 | ) 256 | ) 257 | ) 258 | 259 | proc mkToYamlForTypeAlias(t, alias: NimNode): NimNode = 260 | newCommentStmtNode(fmt"Not generating toYaml() for {$t.repr}, compiler will use implementation for {$alias.repr}") 261 | 262 | proc mkOfYamlForTypeAlias(t, alias: NimNode): NimNode = 263 | newCommentStmtNode(fmt"Not generating ofYaml() for {$t.repr}, compiler will use implementation for {$alias.repr}") 264 | 265 | proc mkToYamlForDistinctType(t, base: NimNode): NimNode = 266 | let retType = ident("YNode") 267 | let obj = ident("x") 268 | newProc( 269 | name=pubIdent("toYaml"), 270 | params=[retType, newIdentDefs(obj, t)], 271 | body=newStmtList( 272 | newCall( 273 | ident("toYaml"), 274 | nnkCommand.newTree( 275 | newPar(base), 276 | obj 277 | ) 278 | ) 279 | ), 280 | ) 281 | 282 | proc mkOfYamlForDistinctType(t, base: NimNode): NimNode = 283 | let retType = t 284 | let n = ident("n") 285 | let nodeParam = newIdentDefs(n, ident("YNode")) 286 | let typeParam = newIdentDefs(ident("t"), mkTypedesc(retType)) 287 | newProc( 288 | name = pubIdent("ofYaml"), 289 | params = [retType, nodeParam, typeParam], 290 | body = nnkStmtList.newTree( 291 | newCall( 292 | ident(t.strVal), 293 | newCall( 294 | ident("ofYaml"), 295 | n, 296 | mkTypeDesc(base) 297 | ) 298 | ) 299 | ) 300 | ) 301 | 302 | proc mkToYamlForType(t: NimNode): NimNode = 303 | let fields = collectObjFieldsForType(t.getImpl()) 304 | case fields.kind 305 | of otObj: 306 | return mkToYamlForObjType(t, fields.fields) 307 | of otVariant: 308 | return mkToYamlForVariantType(t, fields.common, fields.discrim, fields.variants) 309 | of otEnum: 310 | return mkToYamlForEnumType(t, fields.vals) 311 | of otTypeAlias: 312 | return mkToYamlForTypeAlias(t, fields.t) 313 | of otDistinct: 314 | return mkToYamlForDistinctType(t, fields.base) 315 | of otTuple: 316 | # return newEmptyNode() 317 | return mkToYamlForTupleType(t, fields.tupleFields) 318 | of otEmpty: 319 | error("NOIMPL for empty types", t) 320 | 321 | proc mkOfYamlForType(t: NimNode): NimNode = 322 | let fields = collectObjFieldsForType(t.getImpl()) 323 | case fields.kind 324 | of otObj: 325 | return mkOfYamlForObjType(t, fields.fields) 326 | of otVariant: 327 | return mkOfYamlForVariantType(t, fields.common, fields.discrim, fields.variants) 328 | of otEnum: 329 | return mkOfYamlForEnumType(t, fields.vals) 330 | of otTypeAlias: 331 | return mkOfYamlForTypeAlias(t, fields.t) 332 | of otDistinct: 333 | return mkOfYamlForDistinctType(t, fields.base) 334 | of otTuple: 335 | return mkOfYamlForTupleType(t, fields.tupleFields) 336 | # return newEmptyNode() 337 | of otEmpty: 338 | error("NOIMPL for empty types", t) 339 | 340 | macro deriveYaml*(v: typed) = 341 | ## Generate `ofYaml` and `toYaml` procs for a type 342 | runnableExamples: 343 | type 344 | Obj = object 345 | i: int 346 | s: string 347 | 348 | deriveYaml Obj 349 | 350 | var sample: string = """ 351 | i: 99 352 | s: hello world 353 | """ 354 | var o: Obj = ofYamlStr(sample, Obj) 355 | doAssert o.i == 99 356 | doAssert o.s == "hello world" 357 | 358 | if v.kind == nnkSym and v.symKind == nskType: 359 | let ofYamlDef = mkOfYamlForType v 360 | let toYamlDef = mkToYamlForType v 361 | result = newStmtList( 362 | ofYamlDef, 363 | toYamlDef 364 | ) 365 | else: 366 | error("deriveYaml only works on types", v) 367 | 368 | macro deriveYamls*(body: untyped) = 369 | ## Derive yamls for multiple types 370 | runnableExamples: 371 | type 372 | Owner = ref object of RootObj 373 | name: string 374 | Pet = ref object of RootObj 375 | name: string 376 | kind: string 377 | owner: Owner 378 | 379 | deriveYamls: 380 | Owner 381 | Pet 382 | 383 | let sample = """ 384 | name: Garfield 385 | kind: cat 386 | owner: 387 | name: J. Arbuckle 388 | """ 389 | let garf = ofYamlStr(sample, Pet) 390 | doAssert garf.name == "Garfield" 391 | doAssert garf.kind == "cat" 392 | doAssert garf.owner.name == "J. Arbuckle" 393 | 394 | expectKind(body, nnkStmtList) 395 | result = newStmtList() 396 | for x in body.children: 397 | result.add(nnkCommand.newTree( 398 | ident("deriveYaml"), 399 | x 400 | )) 401 | --------------------------------------------------------------------------------