├── .github ├── dependabot.ncl ├── dependabot.yml ├── settings.ncl ├── settings.yml └── workflows │ ├── ci.ncl │ ├── ci.yml │ ├── release.ncl │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── aws │ ├── flake.nix │ ├── main.ncl │ ├── modules │ │ ├── aws-availability-zones.ncl │ │ ├── aws-provider.ncl │ │ ├── aws-s3-backend.ncl │ │ ├── aws-simple-ec2.ncl │ │ ├── aws-simple-vpc.ncl │ │ ├── aws.ncl │ │ └── types.ncl │ ├── ssh-pubkey.sh │ └── utils.ncl ├── github-simple │ ├── flake.nix │ └── main.ncl ├── github-users │ ├── flake.nix │ ├── lib.ncl │ ├── main.ncl │ ├── user-contract.ncl │ ├── users.ncl │ └── validation.ncl └── hello-tf │ ├── flake.nix │ └── main.ncl ├── flake.lock ├── flake.nix ├── ncl ├── github │ ├── matrix.ncl │ └── setup-steps.ncl └── lib.ncl ├── nix ├── devshell.nix ├── nickel_schema.nix └── terraform_schema.nix ├── schema-merge ├── go.mod ├── go.sum ├── intermediate.go ├── main.go └── schema_store.go └── tf-ncl ├── Cargo.toml └── src ├── intermediate.rs ├── lib.rs ├── main.rs └── nickel.rs /.github/dependabot.ncl: -------------------------------------------------------------------------------- 1 | { 2 | updates = [{ 3 | commit-message = { include = "scope", prefix = "chore", }, 4 | directory = "/", 5 | open-pull-requests-limit = 10, 6 | package-ecosystem = "github-actions", 7 | schedule = { interval = "daily", time = "00:00", timezone = "UTC", }, 8 | }], 9 | version = 2, 10 | } 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT! Generated by eval-github-yaml from .github/dependabot.ncl 2 | { 3 | "updates": [ 4 | { 5 | "commit-message": { 6 | "include": "scope", 7 | "prefix": "chore" 8 | }, 9 | "directory": "/", 10 | "open-pull-requests-limit": 10, 11 | "package-ecosystem": "github-actions", 12 | "schedule": { 13 | "interval": "daily", 14 | "time": "00:00", 15 | "timezone": "UTC" 16 | } 17 | } 18 | ], 19 | "version": 2 20 | } 21 | -------------------------------------------------------------------------------- /.github/settings.ncl: -------------------------------------------------------------------------------- 1 | { 2 | labels = [ 3 | { color = "cfd3d7", name = "duplicate", }, 4 | { color = "7057ff", name = "good first issue", }, 5 | { color = "cfd3d7", name = "invalid", }, 6 | { color = "bfdadc", name = "more data needed", }, 7 | { 8 | color = "b60205", 9 | description = "blocker: fix immediately!", 10 | name = "P0", 11 | }, 12 | { 13 | color = "d93f0b", 14 | description = "critical: next release", 15 | name = "P1", 16 | }, 17 | { 18 | color = "e99695", 19 | description = "major: an upcoming release", 20 | name = "P2", 21 | }, 22 | { 23 | color = "fbca04", 24 | description = "minor: not priorized", 25 | name = "P3", 26 | }, 27 | { 28 | color = "fef2c0", 29 | description = "unimportant: consider wontfix or other priority", 30 | name = "P4", 31 | }, 32 | { color = "d876e3", name = "question", }, 33 | { color = "0052cc", name = "type: bug", }, 34 | { color = "0052cc", name = "type: documentation", }, 35 | { color = "0052cc", name = "type: feature request", }, 36 | { color = "ffffff", name = "wontfix", }, 37 | { 38 | color = "0e8a16", 39 | description = "merge on green CI", 40 | name = "merge-queue", 41 | } 42 | ], 43 | repository = { has_wiki = false, }, 44 | } 45 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT! Generated by eval-github-yaml from .github/settings.ncl 2 | { 3 | "labels": [ 4 | { 5 | "color": "cfd3d7", 6 | "name": "duplicate" 7 | }, 8 | { 9 | "color": "7057ff", 10 | "name": "good first issue" 11 | }, 12 | { 13 | "color": "cfd3d7", 14 | "name": "invalid" 15 | }, 16 | { 17 | "color": "bfdadc", 18 | "name": "more data needed" 19 | }, 20 | { 21 | "color": "b60205", 22 | "description": "blocker: fix immediately!", 23 | "name": "P0" 24 | }, 25 | { 26 | "color": "d93f0b", 27 | "description": "critical: next release", 28 | "name": "P1" 29 | }, 30 | { 31 | "color": "e99695", 32 | "description": "major: an upcoming release", 33 | "name": "P2" 34 | }, 35 | { 36 | "color": "fbca04", 37 | "description": "minor: not priorized", 38 | "name": "P3" 39 | }, 40 | { 41 | "color": "fef2c0", 42 | "description": "unimportant: consider wontfix or other priority", 43 | "name": "P4" 44 | }, 45 | { 46 | "color": "d876e3", 47 | "name": "question" 48 | }, 49 | { 50 | "color": "0052cc", 51 | "name": "type: bug" 52 | }, 53 | { 54 | "color": "0052cc", 55 | "name": "type: documentation" 56 | }, 57 | { 58 | "color": "0052cc", 59 | "name": "type: feature request" 60 | }, 61 | { 62 | "color": "ffffff", 63 | "name": "wontfix" 64 | }, 65 | { 66 | "color": "0e8a16", 67 | "description": "merge on green CI", 68 | "name": "merge-queue" 69 | } 70 | ], 71 | "repository": { 72 | "has_wiki": false 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.github/workflows/ci.ncl: -------------------------------------------------------------------------------- 1 | { 2 | configs = [ 3 | { os = "ubuntu-latest" }, 4 | { os = "macos-latest" } 5 | ], 6 | 7 | concurrency = { 8 | group = "ci-${{ github.ref }}", 9 | cancel-in-progress = true 10 | }, 11 | name = "CI", 12 | on = { 13 | push = { 14 | branches = ["main"] 15 | }, 16 | pull_request = { 17 | branches = ["main"] 18 | } 19 | }, 20 | 21 | matrix = { 22 | config | default = { os = "" }, 23 | 24 | name = "Build and Test", 25 | 26 | job = { 27 | steps = 28 | (import "../../ncl/github/setup-steps.ncl") 29 | @ [ 30 | { 31 | run = m%" 32 | nix flake check --print-build-logs 33 | nix run .#test-examples 34 | "%, 35 | name = "Run flake check" 36 | }, 37 | ], 38 | runs-on = config.os, 39 | } 40 | }, 41 | } 42 | & (import "../../ncl/github/matrix.ncl") 43 | 44 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT! Generated by eval-github-yaml from .github/workflows/ci.ncl 2 | { 3 | "concurrency": { 4 | "cancel-in-progress": true, 5 | "group": "ci-${{ github.ref }}" 6 | }, 7 | "jobs": { 8 | "build-and-test-macos-latest": { 9 | "name": "Build and Test / (macos-latest)", 10 | "runs-on": "macos-latest", 11 | "steps": [ 12 | { 13 | "uses": "actions/checkout@v4" 14 | }, 15 | { 16 | "name": "Installing Nix", 17 | "uses": "cachix/install-nix-action@v23", 18 | "with": { 19 | "extra_nix_config": "experimental-features = nix-command flakes", 20 | "nix_path": "nixpkgs=channel:nixos-unstable" 21 | } 22 | }, 23 | { 24 | "name": "Setup Cachix", 25 | "uses": "cachix/cachix-action@v12", 26 | "with": { 27 | "authToken": "${{ secrets.CACHIX_TWEAG_NICKEL_AUTH_TOKEN }}", 28 | "name": "tweag-nickel" 29 | } 30 | }, 31 | { 32 | "name": "Run flake check", 33 | "run": "nix flake check --print-build-logs\nnix run .#test-examples" 34 | } 35 | ] 36 | }, 37 | "build-and-test-ubuntu-latest": { 38 | "name": "Build and Test / (ubuntu-latest)", 39 | "runs-on": "ubuntu-latest", 40 | "steps": [ 41 | { 42 | "uses": "actions/checkout@v4" 43 | }, 44 | { 45 | "name": "Installing Nix", 46 | "uses": "cachix/install-nix-action@v23", 47 | "with": { 48 | "extra_nix_config": "experimental-features = nix-command flakes", 49 | "nix_path": "nixpkgs=channel:nixos-unstable" 50 | } 51 | }, 52 | { 53 | "name": "Setup Cachix", 54 | "uses": "cachix/cachix-action@v12", 55 | "with": { 56 | "authToken": "${{ secrets.CACHIX_TWEAG_NICKEL_AUTH_TOKEN }}", 57 | "name": "tweag-nickel" 58 | } 59 | }, 60 | { 61 | "name": "Run flake check", 62 | "run": "nix flake check --print-build-logs\nnix run .#test-examples" 63 | } 64 | ] 65 | } 66 | }, 67 | "name": "CI", 68 | "on": { 69 | "pull_request": { 70 | "branches": [ 71 | "main" 72 | ] 73 | }, 74 | "push": { 75 | "branches": [ 76 | "main" 77 | ] 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /.github/workflows/release.ncl: -------------------------------------------------------------------------------- 1 | { 2 | name = "Release", 3 | on.release.types = ["created"], 4 | on.workflow_dispatch = { 5 | inputs.release_tag.description = "The release tag to target" 6 | }, 7 | jobs = { 8 | "tarball" = { 9 | name = "Upload Release Tarball", 10 | steps = 11 | (import "../../ncl/github/setup-steps.ncl") 12 | @ [ 13 | { 14 | name = "Create Release Tarball", 15 | run = "nix build .#release --print-build-logs", 16 | }, 17 | { 18 | name = "Upload Release Tarball", 19 | run = m%" 20 | gh release upload --clobber $RELEASE_TAG ./result/tf-ncl.tar.xz#"Generated Tf-Ncl Schemas" 21 | "%, 22 | env = { 23 | RELEASE_TAG = "${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.release_tag }}", 24 | GH_TOKEN = "${{ github.token }}", 25 | }, 26 | } 27 | ], 28 | runs-on = "ubuntu-latest", 29 | } 30 | }, 31 | } 32 | 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT! Generated by eval-github-yaml from .github/workflows/release.ncl 2 | { 3 | "jobs": { 4 | "tarball": { 5 | "name": "Upload Release Tarball", 6 | "runs-on": "ubuntu-latest", 7 | "steps": [ 8 | { 9 | "uses": "actions/checkout@v4" 10 | }, 11 | { 12 | "name": "Installing Nix", 13 | "uses": "cachix/install-nix-action@v23", 14 | "with": { 15 | "extra_nix_config": "experimental-features = nix-command flakes", 16 | "nix_path": "nixpkgs=channel:nixos-unstable" 17 | } 18 | }, 19 | { 20 | "name": "Setup Cachix", 21 | "uses": "cachix/cachix-action@v12", 22 | "with": { 23 | "authToken": "${{ secrets.CACHIX_TWEAG_NICKEL_AUTH_TOKEN }}", 24 | "name": "tweag-nickel" 25 | } 26 | }, 27 | { 28 | "name": "Create Release Tarball", 29 | "run": "nix build .#release --print-build-logs" 30 | }, 31 | { 32 | "env": { 33 | "GH_TOKEN": "${{ github.token }}", 34 | "RELEASE_TAG": "${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.release_tag }}" 35 | }, 36 | "name": "Upload Release Tarball", 37 | "run": "gh release upload --clobber $RELEASE_TAG ./result/tf-ncl.tar.xz#\"Generated Tf-Ncl Schemas\"" 38 | } 39 | ] 40 | } 41 | }, 42 | "name": "Release", 43 | "on": { 44 | "release": { 45 | "types": [ 46 | "created" 47 | ] 48 | }, 49 | "workflow_dispatch": { 50 | "inputs": { 51 | "release_tag": { 52 | "description": "The release tag to target" 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .terraform* 3 | .pre-commit-config.yaml 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.8.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 25 | dependencies = [ 26 | "cfg-if", 27 | "once_cell", 28 | "version_check", 29 | "zerocopy", 30 | ] 31 | 32 | [[package]] 33 | name = "aho-corasick" 34 | version = "1.1.3" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 37 | dependencies = [ 38 | "memchr", 39 | ] 40 | 41 | [[package]] 42 | name = "ansi_term" 43 | version = "0.12.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 46 | dependencies = [ 47 | "winapi", 48 | ] 49 | 50 | [[package]] 51 | name = "anstream" 52 | version = "0.6.18" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 55 | dependencies = [ 56 | "anstyle", 57 | "anstyle-parse", 58 | "anstyle-query", 59 | "anstyle-wincon", 60 | "colorchoice", 61 | "is_terminal_polyfill", 62 | "utf8parse", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle" 67 | version = "1.0.10" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 70 | 71 | [[package]] 72 | name = "anstyle-parse" 73 | version = "0.2.6" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 76 | dependencies = [ 77 | "utf8parse", 78 | ] 79 | 80 | [[package]] 81 | name = "anstyle-query" 82 | version = "1.1.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 85 | dependencies = [ 86 | "windows-sys 0.59.0", 87 | ] 88 | 89 | [[package]] 90 | name = "anstyle-wincon" 91 | version = "3.0.6" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 94 | dependencies = [ 95 | "anstyle", 96 | "windows-sys 0.59.0", 97 | ] 98 | 99 | [[package]] 100 | name = "anyhow" 101 | version = "1.0.95" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" 104 | 105 | [[package]] 106 | name = "arrayvec" 107 | version = "0.5.2" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 110 | 111 | [[package]] 112 | name = "ascii-canvas" 113 | version = "3.0.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" 116 | dependencies = [ 117 | "term", 118 | ] 119 | 120 | [[package]] 121 | name = "autocfg" 122 | version = "1.4.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 125 | 126 | [[package]] 127 | name = "backtrace" 128 | version = "0.3.74" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 131 | dependencies = [ 132 | "addr2line", 133 | "cfg-if", 134 | "libc", 135 | "miniz_oxide", 136 | "object", 137 | "rustc-demangle", 138 | "windows-targets 0.52.6", 139 | ] 140 | 141 | [[package]] 142 | name = "base64" 143 | version = "0.22.1" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 146 | 147 | [[package]] 148 | name = "beef" 149 | version = "0.5.2" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" 152 | 153 | [[package]] 154 | name = "bincode" 155 | version = "1.3.3" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 158 | dependencies = [ 159 | "serde", 160 | ] 161 | 162 | [[package]] 163 | name = "bit-set" 164 | version = "0.5.3" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 167 | dependencies = [ 168 | "bit-vec", 169 | ] 170 | 171 | [[package]] 172 | name = "bit-vec" 173 | version = "0.6.3" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 176 | 177 | [[package]] 178 | name = "bitflags" 179 | version = "1.3.2" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 182 | 183 | [[package]] 184 | name = "bitflags" 185 | version = "2.7.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" 188 | 189 | [[package]] 190 | name = "bitmaps" 191 | version = "3.2.1" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" 194 | 195 | [[package]] 196 | name = "block-buffer" 197 | version = "0.10.4" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 200 | dependencies = [ 201 | "generic-array", 202 | ] 203 | 204 | [[package]] 205 | name = "bumpalo" 206 | version = "3.16.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 209 | 210 | [[package]] 211 | name = "cc" 212 | version = "1.2.7" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" 215 | dependencies = [ 216 | "shlex", 217 | ] 218 | 219 | [[package]] 220 | name = "cfg-if" 221 | version = "1.0.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 224 | 225 | [[package]] 226 | name = "clap" 227 | version = "4.5.26" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" 230 | dependencies = [ 231 | "clap_builder", 232 | "clap_derive", 233 | ] 234 | 235 | [[package]] 236 | name = "clap_builder" 237 | version = "4.5.26" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" 240 | dependencies = [ 241 | "anstream", 242 | "anstyle", 243 | "clap_lex", 244 | "strsim 0.11.1", 245 | "terminal_size", 246 | ] 247 | 248 | [[package]] 249 | name = "clap_derive" 250 | version = "4.5.24" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" 253 | dependencies = [ 254 | "heck", 255 | "proc-macro2", 256 | "quote", 257 | "syn 2.0.96", 258 | ] 259 | 260 | [[package]] 261 | name = "clap_lex" 262 | version = "0.7.4" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 265 | 266 | [[package]] 267 | name = "clipboard-win" 268 | version = "4.5.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" 271 | dependencies = [ 272 | "error-code", 273 | "str-buf", 274 | "winapi", 275 | ] 276 | 277 | [[package]] 278 | name = "codespan" 279 | version = "0.11.1" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "3362992a0d9f1dd7c3d0e89e0ab2bb540b7a95fea8cd798090e758fda2899b5e" 282 | dependencies = [ 283 | "codespan-reporting", 284 | "serde", 285 | ] 286 | 287 | [[package]] 288 | name = "codespan-reporting" 289 | version = "0.11.1" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 292 | dependencies = [ 293 | "serde", 294 | "termcolor", 295 | "unicode-width", 296 | ] 297 | 298 | [[package]] 299 | name = "colorchoice" 300 | version = "1.0.3" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 303 | 304 | [[package]] 305 | name = "comrak" 306 | version = "0.24.1" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "5a972c8ec1be8065f7b597b5f7f5b3be535db780280644aebdcd1966decf58dc" 309 | dependencies = [ 310 | "clap", 311 | "derive_builder", 312 | "entities", 313 | "memchr", 314 | "once_cell", 315 | "regex", 316 | "shell-words", 317 | "slug", 318 | "syntect", 319 | "typed-arena", 320 | "unicode_categories", 321 | "xdg", 322 | ] 323 | 324 | [[package]] 325 | name = "coolor" 326 | version = "0.5.0" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "af4d7a805ca0d92f8c61a31c809d4323fdaa939b0b440e544d21db7797c5aaad" 329 | dependencies = [ 330 | "crossterm", 331 | ] 332 | 333 | [[package]] 334 | name = "cpufeatures" 335 | version = "0.2.16" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" 338 | dependencies = [ 339 | "libc", 340 | ] 341 | 342 | [[package]] 343 | name = "crc32fast" 344 | version = "1.4.2" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 347 | dependencies = [ 348 | "cfg-if", 349 | ] 350 | 351 | [[package]] 352 | name = "crossbeam" 353 | version = "0.8.4" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" 356 | dependencies = [ 357 | "crossbeam-channel", 358 | "crossbeam-deque", 359 | "crossbeam-epoch", 360 | "crossbeam-queue", 361 | "crossbeam-utils", 362 | ] 363 | 364 | [[package]] 365 | name = "crossbeam-channel" 366 | version = "0.5.14" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" 369 | dependencies = [ 370 | "crossbeam-utils", 371 | ] 372 | 373 | [[package]] 374 | name = "crossbeam-deque" 375 | version = "0.8.6" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 378 | dependencies = [ 379 | "crossbeam-epoch", 380 | "crossbeam-utils", 381 | ] 382 | 383 | [[package]] 384 | name = "crossbeam-epoch" 385 | version = "0.9.18" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 388 | dependencies = [ 389 | "crossbeam-utils", 390 | ] 391 | 392 | [[package]] 393 | name = "crossbeam-queue" 394 | version = "0.3.12" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" 397 | dependencies = [ 398 | "crossbeam-utils", 399 | ] 400 | 401 | [[package]] 402 | name = "crossbeam-utils" 403 | version = "0.8.21" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 406 | 407 | [[package]] 408 | name = "crossterm" 409 | version = "0.23.2" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17" 412 | dependencies = [ 413 | "bitflags 1.3.2", 414 | "crossterm_winapi", 415 | "libc", 416 | "mio", 417 | "parking_lot", 418 | "signal-hook", 419 | "signal-hook-mio", 420 | "winapi", 421 | ] 422 | 423 | [[package]] 424 | name = "crossterm_winapi" 425 | version = "0.9.1" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 428 | dependencies = [ 429 | "winapi", 430 | ] 431 | 432 | [[package]] 433 | name = "crunchy" 434 | version = "0.2.2" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 437 | 438 | [[package]] 439 | name = "crypto-common" 440 | version = "0.1.6" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 443 | dependencies = [ 444 | "generic-array", 445 | "typenum", 446 | ] 447 | 448 | [[package]] 449 | name = "darling" 450 | version = "0.20.10" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 453 | dependencies = [ 454 | "darling_core", 455 | "darling_macro", 456 | ] 457 | 458 | [[package]] 459 | name = "darling_core" 460 | version = "0.20.10" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 463 | dependencies = [ 464 | "fnv", 465 | "ident_case", 466 | "proc-macro2", 467 | "quote", 468 | "strsim 0.11.1", 469 | "syn 2.0.96", 470 | ] 471 | 472 | [[package]] 473 | name = "darling_macro" 474 | version = "0.20.10" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 477 | dependencies = [ 478 | "darling_core", 479 | "quote", 480 | "syn 2.0.96", 481 | ] 482 | 483 | [[package]] 484 | name = "deranged" 485 | version = "0.3.11" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 488 | dependencies = [ 489 | "powerfmt", 490 | ] 491 | 492 | [[package]] 493 | name = "derive_builder" 494 | version = "0.20.2" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" 497 | dependencies = [ 498 | "derive_builder_macro", 499 | ] 500 | 501 | [[package]] 502 | name = "derive_builder_core" 503 | version = "0.20.2" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" 506 | dependencies = [ 507 | "darling", 508 | "proc-macro2", 509 | "quote", 510 | "syn 2.0.96", 511 | ] 512 | 513 | [[package]] 514 | name = "derive_builder_macro" 515 | version = "0.20.2" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" 518 | dependencies = [ 519 | "derive_builder_core", 520 | "syn 2.0.96", 521 | ] 522 | 523 | [[package]] 524 | name = "deunicode" 525 | version = "1.6.0" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" 528 | 529 | [[package]] 530 | name = "diff" 531 | version = "0.1.13" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 534 | 535 | [[package]] 536 | name = "digest" 537 | version = "0.10.7" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 540 | dependencies = [ 541 | "block-buffer", 542 | "crypto-common", 543 | ] 544 | 545 | [[package]] 546 | name = "dirs-next" 547 | version = "2.0.0" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 550 | dependencies = [ 551 | "cfg-if", 552 | "dirs-sys-next", 553 | ] 554 | 555 | [[package]] 556 | name = "dirs-sys-next" 557 | version = "0.1.2" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 560 | dependencies = [ 561 | "libc", 562 | "redox_users", 563 | "winapi", 564 | ] 565 | 566 | [[package]] 567 | name = "either" 568 | version = "1.13.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 571 | 572 | [[package]] 573 | name = "ena" 574 | version = "0.14.3" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" 577 | dependencies = [ 578 | "log", 579 | ] 580 | 581 | [[package]] 582 | name = "endian-type" 583 | version = "0.1.2" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" 586 | 587 | [[package]] 588 | name = "entities" 589 | version = "1.0.1" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" 592 | 593 | [[package]] 594 | name = "equivalent" 595 | version = "1.0.1" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 598 | 599 | [[package]] 600 | name = "errno" 601 | version = "0.3.10" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 604 | dependencies = [ 605 | "libc", 606 | "windows-sys 0.59.0", 607 | ] 608 | 609 | [[package]] 610 | name = "error-code" 611 | version = "2.3.1" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" 614 | dependencies = [ 615 | "libc", 616 | "str-buf", 617 | ] 618 | 619 | [[package]] 620 | name = "fancy-regex" 621 | version = "0.11.0" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" 624 | dependencies = [ 625 | "bit-set", 626 | "regex", 627 | ] 628 | 629 | [[package]] 630 | name = "fd-lock" 631 | version = "3.0.13" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" 634 | dependencies = [ 635 | "cfg-if", 636 | "rustix", 637 | "windows-sys 0.48.0", 638 | ] 639 | 640 | [[package]] 641 | name = "fixedbitset" 642 | version = "0.4.2" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 645 | 646 | [[package]] 647 | name = "flate2" 648 | version = "1.0.35" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" 651 | dependencies = [ 652 | "crc32fast", 653 | "miniz_oxide", 654 | ] 655 | 656 | [[package]] 657 | name = "fnv" 658 | version = "1.0.7" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 661 | 662 | [[package]] 663 | name = "futures" 664 | version = "0.3.31" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 667 | dependencies = [ 668 | "futures-channel", 669 | "futures-core", 670 | "futures-executor", 671 | "futures-io", 672 | "futures-sink", 673 | "futures-task", 674 | "futures-util", 675 | ] 676 | 677 | [[package]] 678 | name = "futures-channel" 679 | version = "0.3.31" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 682 | dependencies = [ 683 | "futures-core", 684 | "futures-sink", 685 | ] 686 | 687 | [[package]] 688 | name = "futures-core" 689 | version = "0.3.31" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 692 | 693 | [[package]] 694 | name = "futures-executor" 695 | version = "0.3.31" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 698 | dependencies = [ 699 | "futures-core", 700 | "futures-task", 701 | "futures-util", 702 | ] 703 | 704 | [[package]] 705 | name = "futures-io" 706 | version = "0.3.31" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 709 | 710 | [[package]] 711 | name = "futures-macro" 712 | version = "0.3.31" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 715 | dependencies = [ 716 | "proc-macro2", 717 | "quote", 718 | "syn 2.0.96", 719 | ] 720 | 721 | [[package]] 722 | name = "futures-sink" 723 | version = "0.3.31" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 726 | 727 | [[package]] 728 | name = "futures-task" 729 | version = "0.3.31" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 732 | 733 | [[package]] 734 | name = "futures-util" 735 | version = "0.3.31" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 738 | dependencies = [ 739 | "futures-channel", 740 | "futures-core", 741 | "futures-io", 742 | "futures-macro", 743 | "futures-sink", 744 | "futures-task", 745 | "memchr", 746 | "pin-project-lite", 747 | "pin-utils", 748 | "slab", 749 | ] 750 | 751 | [[package]] 752 | name = "generic-array" 753 | version = "0.14.7" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 756 | dependencies = [ 757 | "typenum", 758 | "version_check", 759 | ] 760 | 761 | [[package]] 762 | name = "getrandom" 763 | version = "0.2.15" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 766 | dependencies = [ 767 | "cfg-if", 768 | "libc", 769 | "wasi", 770 | ] 771 | 772 | [[package]] 773 | name = "gimli" 774 | version = "0.31.1" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 777 | 778 | [[package]] 779 | name = "hashbrown" 780 | version = "0.12.3" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 783 | 784 | [[package]] 785 | name = "hashbrown" 786 | version = "0.14.5" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 789 | dependencies = [ 790 | "ahash", 791 | ] 792 | 793 | [[package]] 794 | name = "hashbrown" 795 | version = "0.15.2" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 798 | 799 | [[package]] 800 | name = "heck" 801 | version = "0.5.0" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 804 | 805 | [[package]] 806 | name = "ident_case" 807 | version = "1.0.1" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 810 | 811 | [[package]] 812 | name = "imbl-sized-chunks" 813 | version = "0.1.3" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" 816 | dependencies = [ 817 | "bitmaps", 818 | ] 819 | 820 | [[package]] 821 | name = "indexmap" 822 | version = "1.9.3" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 825 | dependencies = [ 826 | "autocfg", 827 | "hashbrown 0.12.3", 828 | "serde", 829 | ] 830 | 831 | [[package]] 832 | name = "indexmap" 833 | version = "2.7.0" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 836 | dependencies = [ 837 | "equivalent", 838 | "hashbrown 0.15.2", 839 | ] 840 | 841 | [[package]] 842 | name = "indoc" 843 | version = "2.0.5" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 846 | 847 | [[package]] 848 | name = "is_terminal_polyfill" 849 | version = "1.70.1" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 852 | 853 | [[package]] 854 | name = "itertools" 855 | version = "0.11.0" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" 858 | dependencies = [ 859 | "either", 860 | ] 861 | 862 | [[package]] 863 | name = "itoa" 864 | version = "1.0.14" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 867 | 868 | [[package]] 869 | name = "js-sys" 870 | version = "0.3.68" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" 873 | dependencies = [ 874 | "wasm-bindgen", 875 | ] 876 | 877 | [[package]] 878 | name = "lalrpop" 879 | version = "0.20.2" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" 882 | dependencies = [ 883 | "ascii-canvas", 884 | "bit-set", 885 | "ena", 886 | "itertools", 887 | "lalrpop-util", 888 | "petgraph", 889 | "pico-args", 890 | "regex", 891 | "regex-syntax 0.8.5", 892 | "string_cache", 893 | "term", 894 | "tiny-keccak", 895 | "unicode-xid", 896 | "walkdir", 897 | ] 898 | 899 | [[package]] 900 | name = "lalrpop-util" 901 | version = "0.20.2" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" 904 | dependencies = [ 905 | "regex-automata", 906 | ] 907 | 908 | [[package]] 909 | name = "libc" 910 | version = "0.2.169" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 913 | 914 | [[package]] 915 | name = "libm" 916 | version = "0.2.11" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" 919 | 920 | [[package]] 921 | name = "libredox" 922 | version = "0.1.3" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 925 | dependencies = [ 926 | "bitflags 2.7.0", 927 | "libc", 928 | ] 929 | 930 | [[package]] 931 | name = "linked-hash-map" 932 | version = "0.5.6" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 935 | 936 | [[package]] 937 | name = "linux-raw-sys" 938 | version = "0.4.15" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 941 | 942 | [[package]] 943 | name = "lock_api" 944 | version = "0.4.12" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 947 | dependencies = [ 948 | "autocfg", 949 | "scopeguard", 950 | ] 951 | 952 | [[package]] 953 | name = "log" 954 | version = "0.4.22" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 957 | 958 | [[package]] 959 | name = "logos" 960 | version = "0.12.1" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" 963 | dependencies = [ 964 | "logos-derive", 965 | ] 966 | 967 | [[package]] 968 | name = "logos-derive" 969 | version = "0.12.1" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" 972 | dependencies = [ 973 | "beef", 974 | "fnv", 975 | "proc-macro2", 976 | "quote", 977 | "regex-syntax 0.6.29", 978 | "syn 1.0.109", 979 | ] 980 | 981 | [[package]] 982 | name = "malachite" 983 | version = "0.4.16" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "5616515d632967cd329b6f6db96be9a03ea0b3a49cdbc45b0016803dad8a77b7" 986 | dependencies = [ 987 | "malachite-base", 988 | "malachite-float", 989 | "malachite-nz", 990 | "malachite-q", 991 | ] 992 | 993 | [[package]] 994 | name = "malachite-base" 995 | version = "0.4.16" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "46059721011b0458b7bd6d9179be5d0b60294281c23320c207adceaecc54d13b" 998 | dependencies = [ 999 | "hashbrown 0.14.5", 1000 | "itertools", 1001 | "libm", 1002 | "ryu", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "malachite-float" 1007 | version = "0.4.16" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "a9bfda4e3628f09bf7c42242d590b5d0cc04ff0a29c820d81e9afa25db7b089e" 1010 | dependencies = [ 1011 | "itertools", 1012 | "malachite-base", 1013 | "malachite-nz", 1014 | "malachite-q", 1015 | "serde", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "malachite-nz" 1020 | version = "0.4.16" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "1503b27e825cabd1c3d0ff1e95a39fb2ec9eab6fd3da6cfa41aec7091d273e78" 1023 | dependencies = [ 1024 | "itertools", 1025 | "libm", 1026 | "malachite-base", 1027 | "serde", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "malachite-q" 1032 | version = "0.4.16" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "a475503a70a3679dbe3b9b230a23622516742528ba614a7b2490f180ea9cb514" 1035 | dependencies = [ 1036 | "itertools", 1037 | "malachite-base", 1038 | "malachite-nz", 1039 | "serde", 1040 | ] 1041 | 1042 | [[package]] 1043 | name = "md-5" 1044 | version = "0.10.6" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 1047 | dependencies = [ 1048 | "cfg-if", 1049 | "digest", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "memchr" 1054 | version = "2.7.4" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1057 | 1058 | [[package]] 1059 | name = "minimad" 1060 | version = "0.12.0" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "38b136454924e4d020e55c4992e07c105b40d5c41b84662862f0e15bc0a2efef" 1063 | dependencies = [ 1064 | "once_cell", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "miniz_oxide" 1069 | version = "0.8.2" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" 1072 | dependencies = [ 1073 | "adler2", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "mio" 1078 | version = "0.8.11" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 1081 | dependencies = [ 1082 | "libc", 1083 | "log", 1084 | "wasi", 1085 | "windows-sys 0.48.0", 1086 | ] 1087 | 1088 | [[package]] 1089 | name = "new_debug_unreachable" 1090 | version = "1.0.6" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 1093 | 1094 | [[package]] 1095 | name = "nibble_vec" 1096 | version = "0.1.0" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" 1099 | dependencies = [ 1100 | "smallvec", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "nickel-lang-core" 1105 | version = "0.10.0" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "2d134185a2c519c10e80b64f44c6ef75c34ef3f91499befaa1cb2eaf608cf599" 1108 | dependencies = [ 1109 | "ansi_term", 1110 | "codespan", 1111 | "codespan-reporting", 1112 | "colorchoice", 1113 | "comrak", 1114 | "indexmap 1.9.3", 1115 | "indoc", 1116 | "lalrpop", 1117 | "lalrpop-util", 1118 | "logos", 1119 | "malachite", 1120 | "malachite-q", 1121 | "md-5", 1122 | "nickel-lang-vector", 1123 | "once_cell", 1124 | "pretty", 1125 | "regex", 1126 | "rustyline", 1127 | "rustyline-derive", 1128 | "serde", 1129 | "serde_json", 1130 | "serde_yaml", 1131 | "sha-1", 1132 | "sha2", 1133 | "simple-counter", 1134 | "smallvec", 1135 | "strip-ansi-escapes", 1136 | "strsim 0.10.0", 1137 | "termimad", 1138 | "toml", 1139 | "toml_edit", 1140 | "topiary-core", 1141 | "topiary-queries", 1142 | "tree-sitter-nickel", 1143 | "typed-arena", 1144 | "unicode-segmentation", 1145 | "void", 1146 | ] 1147 | 1148 | [[package]] 1149 | name = "nickel-lang-vector" 1150 | version = "0.1.0" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "870c323d81061fc47db4aa7346f6f3492f3e4bb8bd5dd8b7c85ac9d0c9709f0c" 1153 | dependencies = [ 1154 | "imbl-sized-chunks", 1155 | "serde", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "nix" 1160 | version = "0.26.4" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" 1163 | dependencies = [ 1164 | "bitflags 1.3.2", 1165 | "cfg-if", 1166 | "libc", 1167 | ] 1168 | 1169 | [[package]] 1170 | name = "num-conv" 1171 | version = "0.1.0" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1174 | 1175 | [[package]] 1176 | name = "object" 1177 | version = "0.36.7" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1180 | dependencies = [ 1181 | "memchr", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "once_cell" 1186 | version = "1.20.2" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 1189 | 1190 | [[package]] 1191 | name = "onig" 1192 | version = "6.4.0" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" 1195 | dependencies = [ 1196 | "bitflags 1.3.2", 1197 | "libc", 1198 | "once_cell", 1199 | "onig_sys", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "onig_sys" 1204 | version = "69.8.1" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" 1207 | dependencies = [ 1208 | "cc", 1209 | "pkg-config", 1210 | ] 1211 | 1212 | [[package]] 1213 | name = "pad" 1214 | version = "0.1.6" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" 1217 | dependencies = [ 1218 | "unicode-width", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "parking_lot" 1223 | version = "0.12.3" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1226 | dependencies = [ 1227 | "lock_api", 1228 | "parking_lot_core", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "parking_lot_core" 1233 | version = "0.9.10" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1236 | dependencies = [ 1237 | "cfg-if", 1238 | "libc", 1239 | "redox_syscall", 1240 | "smallvec", 1241 | "windows-targets 0.52.6", 1242 | ] 1243 | 1244 | [[package]] 1245 | name = "petgraph" 1246 | version = "0.6.5" 1247 | source = "registry+https://github.com/rust-lang/crates.io-index" 1248 | checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" 1249 | dependencies = [ 1250 | "fixedbitset", 1251 | "indexmap 2.7.0", 1252 | ] 1253 | 1254 | [[package]] 1255 | name = "phf_shared" 1256 | version = "0.10.0" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 1259 | dependencies = [ 1260 | "siphasher", 1261 | ] 1262 | 1263 | [[package]] 1264 | name = "pico-args" 1265 | version = "0.5.0" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" 1268 | 1269 | [[package]] 1270 | name = "pin-project-lite" 1271 | version = "0.2.16" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1274 | 1275 | [[package]] 1276 | name = "pin-utils" 1277 | version = "0.1.0" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1280 | 1281 | [[package]] 1282 | name = "pkg-config" 1283 | version = "0.3.31" 1284 | source = "registry+https://github.com/rust-lang/crates.io-index" 1285 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 1286 | 1287 | [[package]] 1288 | name = "plist" 1289 | version = "1.7.0" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" 1292 | dependencies = [ 1293 | "base64", 1294 | "indexmap 2.7.0", 1295 | "quick-xml", 1296 | "serde", 1297 | "time", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "powerfmt" 1302 | version = "0.2.0" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1305 | 1306 | [[package]] 1307 | name = "precomputed-hash" 1308 | version = "0.1.1" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 1311 | 1312 | [[package]] 1313 | name = "pretty" 1314 | version = "0.11.3" 1315 | source = "registry+https://github.com/rust-lang/crates.io-index" 1316 | checksum = "83f3aa1e3ca87d3b124db7461265ac176b40c277f37e503eaa29c9c75c037846" 1317 | dependencies = [ 1318 | "arrayvec", 1319 | "log", 1320 | "typed-arena", 1321 | "unicode-segmentation", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "pretty_assertions" 1326 | version = "1.4.1" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" 1329 | dependencies = [ 1330 | "diff", 1331 | "yansi", 1332 | ] 1333 | 1334 | [[package]] 1335 | name = "prettydiff" 1336 | version = "0.6.4" 1337 | source = "registry+https://github.com/rust-lang/crates.io-index" 1338 | checksum = "8ff1fec61082821f8236cf6c0c14e8172b62ce8a72a0eedc30d3b247bb68dc11" 1339 | dependencies = [ 1340 | "ansi_term", 1341 | "pad", 1342 | ] 1343 | 1344 | [[package]] 1345 | name = "proc-macro2" 1346 | version = "1.0.92" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 1349 | dependencies = [ 1350 | "unicode-ident", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "quick-xml" 1355 | version = "0.32.0" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" 1358 | dependencies = [ 1359 | "memchr", 1360 | ] 1361 | 1362 | [[package]] 1363 | name = "quote" 1364 | version = "1.0.38" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 1367 | dependencies = [ 1368 | "proc-macro2", 1369 | ] 1370 | 1371 | [[package]] 1372 | name = "radix_trie" 1373 | version = "0.2.1" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" 1376 | dependencies = [ 1377 | "endian-type", 1378 | "nibble_vec", 1379 | ] 1380 | 1381 | [[package]] 1382 | name = "redox_syscall" 1383 | version = "0.5.8" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" 1386 | dependencies = [ 1387 | "bitflags 2.7.0", 1388 | ] 1389 | 1390 | [[package]] 1391 | name = "redox_users" 1392 | version = "0.4.6" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 1395 | dependencies = [ 1396 | "getrandom", 1397 | "libredox", 1398 | "thiserror", 1399 | ] 1400 | 1401 | [[package]] 1402 | name = "regex" 1403 | version = "1.11.1" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1406 | dependencies = [ 1407 | "aho-corasick", 1408 | "memchr", 1409 | "regex-automata", 1410 | "regex-syntax 0.8.5", 1411 | ] 1412 | 1413 | [[package]] 1414 | name = "regex-automata" 1415 | version = "0.4.9" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1418 | dependencies = [ 1419 | "aho-corasick", 1420 | "memchr", 1421 | "regex-syntax 0.8.5", 1422 | ] 1423 | 1424 | [[package]] 1425 | name = "regex-syntax" 1426 | version = "0.6.29" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 1429 | 1430 | [[package]] 1431 | name = "regex-syntax" 1432 | version = "0.8.5" 1433 | source = "registry+https://github.com/rust-lang/crates.io-index" 1434 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1435 | 1436 | [[package]] 1437 | name = "rustc-demangle" 1438 | version = "0.1.24" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1441 | 1442 | [[package]] 1443 | name = "rustix" 1444 | version = "0.38.43" 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" 1446 | checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" 1447 | dependencies = [ 1448 | "bitflags 2.7.0", 1449 | "errno", 1450 | "libc", 1451 | "linux-raw-sys", 1452 | "windows-sys 0.59.0", 1453 | ] 1454 | 1455 | [[package]] 1456 | name = "rustversion" 1457 | version = "1.0.19" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 1460 | 1461 | [[package]] 1462 | name = "rustyline" 1463 | version = "11.0.0" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "5dfc8644681285d1fb67a467fb3021bfea306b99b4146b166a1fe3ada965eece" 1466 | dependencies = [ 1467 | "bitflags 1.3.2", 1468 | "cfg-if", 1469 | "clipboard-win", 1470 | "dirs-next", 1471 | "fd-lock", 1472 | "libc", 1473 | "log", 1474 | "memchr", 1475 | "nix", 1476 | "radix_trie", 1477 | "scopeguard", 1478 | "unicode-segmentation", 1479 | "unicode-width", 1480 | "utf8parse", 1481 | "winapi", 1482 | ] 1483 | 1484 | [[package]] 1485 | name = "rustyline-derive" 1486 | version = "0.8.0" 1487 | source = "registry+https://github.com/rust-lang/crates.io-index" 1488 | checksum = "8218eaf5d960e3c478a1b0f129fa888dd3d8d22eb3de097e9af14c1ab4438024" 1489 | dependencies = [ 1490 | "proc-macro2", 1491 | "quote", 1492 | "syn 1.0.109", 1493 | ] 1494 | 1495 | [[package]] 1496 | name = "ryu" 1497 | version = "1.0.18" 1498 | source = "registry+https://github.com/rust-lang/crates.io-index" 1499 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1500 | 1501 | [[package]] 1502 | name = "same-file" 1503 | version = "1.0.6" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1506 | dependencies = [ 1507 | "winapi-util", 1508 | ] 1509 | 1510 | [[package]] 1511 | name = "scopeguard" 1512 | version = "1.2.0" 1513 | source = "registry+https://github.com/rust-lang/crates.io-index" 1514 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1515 | 1516 | [[package]] 1517 | name = "serde" 1518 | version = "1.0.217" 1519 | source = "registry+https://github.com/rust-lang/crates.io-index" 1520 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 1521 | dependencies = [ 1522 | "serde_derive", 1523 | ] 1524 | 1525 | [[package]] 1526 | name = "serde_derive" 1527 | version = "1.0.217" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 1530 | dependencies = [ 1531 | "proc-macro2", 1532 | "quote", 1533 | "syn 2.0.96", 1534 | ] 1535 | 1536 | [[package]] 1537 | name = "serde_json" 1538 | version = "1.0.135" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" 1541 | dependencies = [ 1542 | "itoa", 1543 | "memchr", 1544 | "ryu", 1545 | "serde", 1546 | ] 1547 | 1548 | [[package]] 1549 | name = "serde_spanned" 1550 | version = "0.6.8" 1551 | source = "registry+https://github.com/rust-lang/crates.io-index" 1552 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1553 | dependencies = [ 1554 | "serde", 1555 | ] 1556 | 1557 | [[package]] 1558 | name = "serde_yaml" 1559 | version = "0.9.34+deprecated" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 1562 | dependencies = [ 1563 | "indexmap 2.7.0", 1564 | "itoa", 1565 | "ryu", 1566 | "serde", 1567 | "unsafe-libyaml", 1568 | ] 1569 | 1570 | [[package]] 1571 | name = "sha-1" 1572 | version = "0.10.1" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" 1575 | dependencies = [ 1576 | "cfg-if", 1577 | "cpufeatures", 1578 | "digest", 1579 | ] 1580 | 1581 | [[package]] 1582 | name = "sha2" 1583 | version = "0.10.8" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1586 | dependencies = [ 1587 | "cfg-if", 1588 | "cpufeatures", 1589 | "digest", 1590 | ] 1591 | 1592 | [[package]] 1593 | name = "shell-words" 1594 | version = "1.1.0" 1595 | source = "registry+https://github.com/rust-lang/crates.io-index" 1596 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 1597 | 1598 | [[package]] 1599 | name = "shlex" 1600 | version = "1.3.0" 1601 | source = "registry+https://github.com/rust-lang/crates.io-index" 1602 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1603 | 1604 | [[package]] 1605 | name = "signal-hook" 1606 | version = "0.3.17" 1607 | source = "registry+https://github.com/rust-lang/crates.io-index" 1608 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1609 | dependencies = [ 1610 | "libc", 1611 | "signal-hook-registry", 1612 | ] 1613 | 1614 | [[package]] 1615 | name = "signal-hook-mio" 1616 | version = "0.2.4" 1617 | source = "registry+https://github.com/rust-lang/crates.io-index" 1618 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 1619 | dependencies = [ 1620 | "libc", 1621 | "mio", 1622 | "signal-hook", 1623 | ] 1624 | 1625 | [[package]] 1626 | name = "signal-hook-registry" 1627 | version = "1.4.2" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1630 | dependencies = [ 1631 | "libc", 1632 | ] 1633 | 1634 | [[package]] 1635 | name = "simple-counter" 1636 | version = "0.1.0" 1637 | source = "registry+https://github.com/rust-lang/crates.io-index" 1638 | checksum = "4bb57743b52ea059937169c0061d70298fe2df1d2c988b44caae79dd979d9b49" 1639 | 1640 | [[package]] 1641 | name = "siphasher" 1642 | version = "0.3.11" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 1645 | 1646 | [[package]] 1647 | name = "slab" 1648 | version = "0.4.9" 1649 | source = "registry+https://github.com/rust-lang/crates.io-index" 1650 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1651 | dependencies = [ 1652 | "autocfg", 1653 | ] 1654 | 1655 | [[package]] 1656 | name = "slug" 1657 | version = "0.1.6" 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" 1659 | checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" 1660 | dependencies = [ 1661 | "deunicode", 1662 | "wasm-bindgen", 1663 | ] 1664 | 1665 | [[package]] 1666 | name = "smallvec" 1667 | version = "1.13.2" 1668 | source = "registry+https://github.com/rust-lang/crates.io-index" 1669 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1670 | 1671 | [[package]] 1672 | name = "str-buf" 1673 | version = "1.0.6" 1674 | source = "registry+https://github.com/rust-lang/crates.io-index" 1675 | checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" 1676 | 1677 | [[package]] 1678 | name = "string_cache" 1679 | version = "0.8.7" 1680 | source = "registry+https://github.com/rust-lang/crates.io-index" 1681 | checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" 1682 | dependencies = [ 1683 | "new_debug_unreachable", 1684 | "once_cell", 1685 | "parking_lot", 1686 | "phf_shared", 1687 | "precomputed-hash", 1688 | ] 1689 | 1690 | [[package]] 1691 | name = "strip-ansi-escapes" 1692 | version = "0.2.0" 1693 | source = "registry+https://github.com/rust-lang/crates.io-index" 1694 | checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" 1695 | dependencies = [ 1696 | "vte", 1697 | ] 1698 | 1699 | [[package]] 1700 | name = "strsim" 1701 | version = "0.10.0" 1702 | source = "registry+https://github.com/rust-lang/crates.io-index" 1703 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1704 | 1705 | [[package]] 1706 | name = "strsim" 1707 | version = "0.11.1" 1708 | source = "registry+https://github.com/rust-lang/crates.io-index" 1709 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1710 | 1711 | [[package]] 1712 | name = "syn" 1713 | version = "1.0.109" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1716 | dependencies = [ 1717 | "proc-macro2", 1718 | "quote", 1719 | "unicode-ident", 1720 | ] 1721 | 1722 | [[package]] 1723 | name = "syn" 1724 | version = "2.0.96" 1725 | source = "registry+https://github.com/rust-lang/crates.io-index" 1726 | checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" 1727 | dependencies = [ 1728 | "proc-macro2", 1729 | "quote", 1730 | "unicode-ident", 1731 | ] 1732 | 1733 | [[package]] 1734 | name = "syntect" 1735 | version = "5.2.0" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" 1738 | dependencies = [ 1739 | "bincode", 1740 | "bitflags 1.3.2", 1741 | "fancy-regex", 1742 | "flate2", 1743 | "fnv", 1744 | "once_cell", 1745 | "onig", 1746 | "plist", 1747 | "regex-syntax 0.8.5", 1748 | "serde", 1749 | "serde_derive", 1750 | "serde_json", 1751 | "thiserror", 1752 | "walkdir", 1753 | "yaml-rust", 1754 | ] 1755 | 1756 | [[package]] 1757 | name = "term" 1758 | version = "0.7.0" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 1761 | dependencies = [ 1762 | "dirs-next", 1763 | "rustversion", 1764 | "winapi", 1765 | ] 1766 | 1767 | [[package]] 1768 | name = "termcolor" 1769 | version = "1.4.1" 1770 | source = "registry+https://github.com/rust-lang/crates.io-index" 1771 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1772 | dependencies = [ 1773 | "winapi-util", 1774 | ] 1775 | 1776 | [[package]] 1777 | name = "termimad" 1778 | version = "0.23.2" 1779 | source = "registry+https://github.com/rust-lang/crates.io-index" 1780 | checksum = "2e32883199fc52cda7e431958dee8bc3ec6898afabc152b76959b9e0e74e2202" 1781 | dependencies = [ 1782 | "coolor", 1783 | "crossbeam", 1784 | "crossterm", 1785 | "minimad", 1786 | "thiserror", 1787 | "unicode-width", 1788 | ] 1789 | 1790 | [[package]] 1791 | name = "terminal_size" 1792 | version = "0.4.1" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" 1795 | dependencies = [ 1796 | "rustix", 1797 | "windows-sys 0.59.0", 1798 | ] 1799 | 1800 | [[package]] 1801 | name = "tf-ncl" 1802 | version = "0.1.0" 1803 | dependencies = [ 1804 | "anyhow", 1805 | "clap", 1806 | "codespan", 1807 | "nickel-lang-core", 1808 | "pretty", 1809 | "pretty_assertions", 1810 | "serde", 1811 | "serde_json", 1812 | ] 1813 | 1814 | [[package]] 1815 | name = "thiserror" 1816 | version = "1.0.69" 1817 | source = "registry+https://github.com/rust-lang/crates.io-index" 1818 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1819 | dependencies = [ 1820 | "thiserror-impl", 1821 | ] 1822 | 1823 | [[package]] 1824 | name = "thiserror-impl" 1825 | version = "1.0.69" 1826 | source = "registry+https://github.com/rust-lang/crates.io-index" 1827 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1828 | dependencies = [ 1829 | "proc-macro2", 1830 | "quote", 1831 | "syn 2.0.96", 1832 | ] 1833 | 1834 | [[package]] 1835 | name = "time" 1836 | version = "0.3.37" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" 1839 | dependencies = [ 1840 | "deranged", 1841 | "itoa", 1842 | "num-conv", 1843 | "powerfmt", 1844 | "serde", 1845 | "time-core", 1846 | "time-macros", 1847 | ] 1848 | 1849 | [[package]] 1850 | name = "time-core" 1851 | version = "0.1.2" 1852 | source = "registry+https://github.com/rust-lang/crates.io-index" 1853 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1854 | 1855 | [[package]] 1856 | name = "time-macros" 1857 | version = "0.2.19" 1858 | source = "registry+https://github.com/rust-lang/crates.io-index" 1859 | checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" 1860 | dependencies = [ 1861 | "num-conv", 1862 | "time-core", 1863 | ] 1864 | 1865 | [[package]] 1866 | name = "tiny-keccak" 1867 | version = "2.0.2" 1868 | source = "registry+https://github.com/rust-lang/crates.io-index" 1869 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 1870 | dependencies = [ 1871 | "crunchy", 1872 | ] 1873 | 1874 | [[package]] 1875 | name = "tokio" 1876 | version = "1.43.0" 1877 | source = "registry+https://github.com/rust-lang/crates.io-index" 1878 | checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" 1879 | dependencies = [ 1880 | "backtrace", 1881 | "pin-project-lite", 1882 | "tokio-macros", 1883 | ] 1884 | 1885 | [[package]] 1886 | name = "tokio-macros" 1887 | version = "2.5.0" 1888 | source = "registry+https://github.com/rust-lang/crates.io-index" 1889 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 1890 | dependencies = [ 1891 | "proc-macro2", 1892 | "quote", 1893 | "syn 2.0.96", 1894 | ] 1895 | 1896 | [[package]] 1897 | name = "toml" 1898 | version = "0.8.19" 1899 | source = "registry+https://github.com/rust-lang/crates.io-index" 1900 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 1901 | dependencies = [ 1902 | "serde", 1903 | "serde_spanned", 1904 | "toml_datetime", 1905 | "toml_edit", 1906 | ] 1907 | 1908 | [[package]] 1909 | name = "toml_datetime" 1910 | version = "0.6.8" 1911 | source = "registry+https://github.com/rust-lang/crates.io-index" 1912 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 1913 | dependencies = [ 1914 | "serde", 1915 | ] 1916 | 1917 | [[package]] 1918 | name = "toml_edit" 1919 | version = "0.22.22" 1920 | source = "registry+https://github.com/rust-lang/crates.io-index" 1921 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 1922 | dependencies = [ 1923 | "indexmap 2.7.0", 1924 | "serde", 1925 | "serde_spanned", 1926 | "toml_datetime", 1927 | "winnow", 1928 | ] 1929 | 1930 | [[package]] 1931 | name = "topiary-core" 1932 | version = "0.5.1" 1933 | source = "registry+https://github.com/rust-lang/crates.io-index" 1934 | checksum = "0236570d34c4e60d88e6bce8446f6d6fd51f5e1e4bdea68cdcf0d1ff5c89776d" 1935 | dependencies = [ 1936 | "futures", 1937 | "itertools", 1938 | "log", 1939 | "pretty_assertions", 1940 | "prettydiff", 1941 | "serde", 1942 | "serde_json", 1943 | "tokio", 1944 | "topiary-tree-sitter-facade", 1945 | "topiary-web-tree-sitter-sys", 1946 | ] 1947 | 1948 | [[package]] 1949 | name = "topiary-queries" 1950 | version = "0.5.1" 1951 | source = "registry+https://github.com/rust-lang/crates.io-index" 1952 | checksum = "dea6b8d21ab29eeb34fca55e384ddb6507261019e075d78812de1a1465ac210c" 1953 | 1954 | [[package]] 1955 | name = "topiary-tree-sitter-facade" 1956 | version = "0.5.1" 1957 | source = "registry+https://github.com/rust-lang/crates.io-index" 1958 | checksum = "2eb978cfd5d4686e9193eed02c6c3abe8aac98e9d4942aaaf83ea472fc9b32d6" 1959 | dependencies = [ 1960 | "js-sys", 1961 | "topiary-web-tree-sitter-sys", 1962 | "tree-sitter", 1963 | "wasm-bindgen", 1964 | "web-sys", 1965 | ] 1966 | 1967 | [[package]] 1968 | name = "topiary-web-tree-sitter-sys" 1969 | version = "0.5.1" 1970 | source = "registry+https://github.com/rust-lang/crates.io-index" 1971 | checksum = "7e6ec4ec3a2426af1ff74688cd19c841a1e852403665d96b455946cb491cb0b6" 1972 | dependencies = [ 1973 | "js-sys", 1974 | "wasm-bindgen", 1975 | "wasm-bindgen-futures", 1976 | "web-sys", 1977 | ] 1978 | 1979 | [[package]] 1980 | name = "tree-sitter" 1981 | version = "0.22.6" 1982 | source = "registry+https://github.com/rust-lang/crates.io-index" 1983 | checksum = "df7cc499ceadd4dcdf7ec6d4cbc34ece92c3fa07821e287aedecd4416c516dca" 1984 | dependencies = [ 1985 | "cc", 1986 | "regex", 1987 | ] 1988 | 1989 | [[package]] 1990 | name = "tree-sitter-nickel" 1991 | version = "0.3.0" 1992 | source = "registry+https://github.com/rust-lang/crates.io-index" 1993 | checksum = "5e8b3a80d772b47cef3145983c0f431f7af2c93088695c516d7a89e68216f854" 1994 | dependencies = [ 1995 | "cc", 1996 | "tree-sitter", 1997 | ] 1998 | 1999 | [[package]] 2000 | name = "typed-arena" 2001 | version = "2.0.2" 2002 | source = "registry+https://github.com/rust-lang/crates.io-index" 2003 | checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" 2004 | 2005 | [[package]] 2006 | name = "typenum" 2007 | version = "1.17.0" 2008 | source = "registry+https://github.com/rust-lang/crates.io-index" 2009 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 2010 | 2011 | [[package]] 2012 | name = "unicode-ident" 2013 | version = "1.0.14" 2014 | source = "registry+https://github.com/rust-lang/crates.io-index" 2015 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 2016 | 2017 | [[package]] 2018 | name = "unicode-segmentation" 2019 | version = "1.12.0" 2020 | source = "registry+https://github.com/rust-lang/crates.io-index" 2021 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 2022 | 2023 | [[package]] 2024 | name = "unicode-width" 2025 | version = "0.1.14" 2026 | source = "registry+https://github.com/rust-lang/crates.io-index" 2027 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 2028 | 2029 | [[package]] 2030 | name = "unicode-xid" 2031 | version = "0.2.6" 2032 | source = "registry+https://github.com/rust-lang/crates.io-index" 2033 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 2034 | 2035 | [[package]] 2036 | name = "unicode_categories" 2037 | version = "0.1.1" 2038 | source = "registry+https://github.com/rust-lang/crates.io-index" 2039 | checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 2040 | 2041 | [[package]] 2042 | name = "unsafe-libyaml" 2043 | version = "0.2.11" 2044 | source = "registry+https://github.com/rust-lang/crates.io-index" 2045 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 2046 | 2047 | [[package]] 2048 | name = "utf8parse" 2049 | version = "0.2.2" 2050 | source = "registry+https://github.com/rust-lang/crates.io-index" 2051 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2052 | 2053 | [[package]] 2054 | name = "version_check" 2055 | version = "0.9.5" 2056 | source = "registry+https://github.com/rust-lang/crates.io-index" 2057 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2058 | 2059 | [[package]] 2060 | name = "void" 2061 | version = "1.0.2" 2062 | source = "registry+https://github.com/rust-lang/crates.io-index" 2063 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 2064 | 2065 | [[package]] 2066 | name = "vte" 2067 | version = "0.11.1" 2068 | source = "registry+https://github.com/rust-lang/crates.io-index" 2069 | checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" 2070 | dependencies = [ 2071 | "utf8parse", 2072 | "vte_generate_state_changes", 2073 | ] 2074 | 2075 | [[package]] 2076 | name = "vte_generate_state_changes" 2077 | version = "0.1.2" 2078 | source = "registry+https://github.com/rust-lang/crates.io-index" 2079 | checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" 2080 | dependencies = [ 2081 | "proc-macro2", 2082 | "quote", 2083 | ] 2084 | 2085 | [[package]] 2086 | name = "walkdir" 2087 | version = "2.5.0" 2088 | source = "registry+https://github.com/rust-lang/crates.io-index" 2089 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 2090 | dependencies = [ 2091 | "same-file", 2092 | "winapi-util", 2093 | ] 2094 | 2095 | [[package]] 2096 | name = "wasi" 2097 | version = "0.11.0+wasi-snapshot-preview1" 2098 | source = "registry+https://github.com/rust-lang/crates.io-index" 2099 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2100 | 2101 | [[package]] 2102 | name = "wasm-bindgen" 2103 | version = "0.2.91" 2104 | source = "registry+https://github.com/rust-lang/crates.io-index" 2105 | checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" 2106 | dependencies = [ 2107 | "cfg-if", 2108 | "wasm-bindgen-macro", 2109 | ] 2110 | 2111 | [[package]] 2112 | name = "wasm-bindgen-backend" 2113 | version = "0.2.91" 2114 | source = "registry+https://github.com/rust-lang/crates.io-index" 2115 | checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" 2116 | dependencies = [ 2117 | "bumpalo", 2118 | "log", 2119 | "once_cell", 2120 | "proc-macro2", 2121 | "quote", 2122 | "syn 2.0.96", 2123 | "wasm-bindgen-shared", 2124 | ] 2125 | 2126 | [[package]] 2127 | name = "wasm-bindgen-futures" 2128 | version = "0.4.41" 2129 | source = "registry+https://github.com/rust-lang/crates.io-index" 2130 | checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" 2131 | dependencies = [ 2132 | "cfg-if", 2133 | "js-sys", 2134 | "wasm-bindgen", 2135 | "web-sys", 2136 | ] 2137 | 2138 | [[package]] 2139 | name = "wasm-bindgen-macro" 2140 | version = "0.2.91" 2141 | source = "registry+https://github.com/rust-lang/crates.io-index" 2142 | checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" 2143 | dependencies = [ 2144 | "quote", 2145 | "wasm-bindgen-macro-support", 2146 | ] 2147 | 2148 | [[package]] 2149 | name = "wasm-bindgen-macro-support" 2150 | version = "0.2.91" 2151 | source = "registry+https://github.com/rust-lang/crates.io-index" 2152 | checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" 2153 | dependencies = [ 2154 | "proc-macro2", 2155 | "quote", 2156 | "syn 2.0.96", 2157 | "wasm-bindgen-backend", 2158 | "wasm-bindgen-shared", 2159 | ] 2160 | 2161 | [[package]] 2162 | name = "wasm-bindgen-shared" 2163 | version = "0.2.91" 2164 | source = "registry+https://github.com/rust-lang/crates.io-index" 2165 | checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" 2166 | 2167 | [[package]] 2168 | name = "web-sys" 2169 | version = "0.3.68" 2170 | source = "registry+https://github.com/rust-lang/crates.io-index" 2171 | checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" 2172 | dependencies = [ 2173 | "js-sys", 2174 | "wasm-bindgen", 2175 | ] 2176 | 2177 | [[package]] 2178 | name = "winapi" 2179 | version = "0.3.9" 2180 | source = "registry+https://github.com/rust-lang/crates.io-index" 2181 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2182 | dependencies = [ 2183 | "winapi-i686-pc-windows-gnu", 2184 | "winapi-x86_64-pc-windows-gnu", 2185 | ] 2186 | 2187 | [[package]] 2188 | name = "winapi-i686-pc-windows-gnu" 2189 | version = "0.4.0" 2190 | source = "registry+https://github.com/rust-lang/crates.io-index" 2191 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2192 | 2193 | [[package]] 2194 | name = "winapi-util" 2195 | version = "0.1.9" 2196 | source = "registry+https://github.com/rust-lang/crates.io-index" 2197 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 2198 | dependencies = [ 2199 | "windows-sys 0.59.0", 2200 | ] 2201 | 2202 | [[package]] 2203 | name = "winapi-x86_64-pc-windows-gnu" 2204 | version = "0.4.0" 2205 | source = "registry+https://github.com/rust-lang/crates.io-index" 2206 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2207 | 2208 | [[package]] 2209 | name = "windows-sys" 2210 | version = "0.48.0" 2211 | source = "registry+https://github.com/rust-lang/crates.io-index" 2212 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2213 | dependencies = [ 2214 | "windows-targets 0.48.5", 2215 | ] 2216 | 2217 | [[package]] 2218 | name = "windows-sys" 2219 | version = "0.59.0" 2220 | source = "registry+https://github.com/rust-lang/crates.io-index" 2221 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2222 | dependencies = [ 2223 | "windows-targets 0.52.6", 2224 | ] 2225 | 2226 | [[package]] 2227 | name = "windows-targets" 2228 | version = "0.48.5" 2229 | source = "registry+https://github.com/rust-lang/crates.io-index" 2230 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2231 | dependencies = [ 2232 | "windows_aarch64_gnullvm 0.48.5", 2233 | "windows_aarch64_msvc 0.48.5", 2234 | "windows_i686_gnu 0.48.5", 2235 | "windows_i686_msvc 0.48.5", 2236 | "windows_x86_64_gnu 0.48.5", 2237 | "windows_x86_64_gnullvm 0.48.5", 2238 | "windows_x86_64_msvc 0.48.5", 2239 | ] 2240 | 2241 | [[package]] 2242 | name = "windows-targets" 2243 | version = "0.52.6" 2244 | source = "registry+https://github.com/rust-lang/crates.io-index" 2245 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2246 | dependencies = [ 2247 | "windows_aarch64_gnullvm 0.52.6", 2248 | "windows_aarch64_msvc 0.52.6", 2249 | "windows_i686_gnu 0.52.6", 2250 | "windows_i686_gnullvm", 2251 | "windows_i686_msvc 0.52.6", 2252 | "windows_x86_64_gnu 0.52.6", 2253 | "windows_x86_64_gnullvm 0.52.6", 2254 | "windows_x86_64_msvc 0.52.6", 2255 | ] 2256 | 2257 | [[package]] 2258 | name = "windows_aarch64_gnullvm" 2259 | version = "0.48.5" 2260 | source = "registry+https://github.com/rust-lang/crates.io-index" 2261 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2262 | 2263 | [[package]] 2264 | name = "windows_aarch64_gnullvm" 2265 | version = "0.52.6" 2266 | source = "registry+https://github.com/rust-lang/crates.io-index" 2267 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2268 | 2269 | [[package]] 2270 | name = "windows_aarch64_msvc" 2271 | version = "0.48.5" 2272 | source = "registry+https://github.com/rust-lang/crates.io-index" 2273 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2274 | 2275 | [[package]] 2276 | name = "windows_aarch64_msvc" 2277 | version = "0.52.6" 2278 | source = "registry+https://github.com/rust-lang/crates.io-index" 2279 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2280 | 2281 | [[package]] 2282 | name = "windows_i686_gnu" 2283 | version = "0.48.5" 2284 | source = "registry+https://github.com/rust-lang/crates.io-index" 2285 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2286 | 2287 | [[package]] 2288 | name = "windows_i686_gnu" 2289 | version = "0.52.6" 2290 | source = "registry+https://github.com/rust-lang/crates.io-index" 2291 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2292 | 2293 | [[package]] 2294 | name = "windows_i686_gnullvm" 2295 | version = "0.52.6" 2296 | source = "registry+https://github.com/rust-lang/crates.io-index" 2297 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2298 | 2299 | [[package]] 2300 | name = "windows_i686_msvc" 2301 | version = "0.48.5" 2302 | source = "registry+https://github.com/rust-lang/crates.io-index" 2303 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2304 | 2305 | [[package]] 2306 | name = "windows_i686_msvc" 2307 | version = "0.52.6" 2308 | source = "registry+https://github.com/rust-lang/crates.io-index" 2309 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2310 | 2311 | [[package]] 2312 | name = "windows_x86_64_gnu" 2313 | version = "0.48.5" 2314 | source = "registry+https://github.com/rust-lang/crates.io-index" 2315 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2316 | 2317 | [[package]] 2318 | name = "windows_x86_64_gnu" 2319 | version = "0.52.6" 2320 | source = "registry+https://github.com/rust-lang/crates.io-index" 2321 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2322 | 2323 | [[package]] 2324 | name = "windows_x86_64_gnullvm" 2325 | version = "0.48.5" 2326 | source = "registry+https://github.com/rust-lang/crates.io-index" 2327 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2328 | 2329 | [[package]] 2330 | name = "windows_x86_64_gnullvm" 2331 | version = "0.52.6" 2332 | source = "registry+https://github.com/rust-lang/crates.io-index" 2333 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2334 | 2335 | [[package]] 2336 | name = "windows_x86_64_msvc" 2337 | version = "0.48.5" 2338 | source = "registry+https://github.com/rust-lang/crates.io-index" 2339 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2340 | 2341 | [[package]] 2342 | name = "windows_x86_64_msvc" 2343 | version = "0.52.6" 2344 | source = "registry+https://github.com/rust-lang/crates.io-index" 2345 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2346 | 2347 | [[package]] 2348 | name = "winnow" 2349 | version = "0.6.22" 2350 | source = "registry+https://github.com/rust-lang/crates.io-index" 2351 | checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" 2352 | dependencies = [ 2353 | "memchr", 2354 | ] 2355 | 2356 | [[package]] 2357 | name = "xdg" 2358 | version = "2.5.2" 2359 | source = "registry+https://github.com/rust-lang/crates.io-index" 2360 | checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" 2361 | 2362 | [[package]] 2363 | name = "yaml-rust" 2364 | version = "0.4.5" 2365 | source = "registry+https://github.com/rust-lang/crates.io-index" 2366 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 2367 | dependencies = [ 2368 | "linked-hash-map", 2369 | ] 2370 | 2371 | [[package]] 2372 | name = "yansi" 2373 | version = "1.0.1" 2374 | source = "registry+https://github.com/rust-lang/crates.io-index" 2375 | checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 2376 | 2377 | [[package]] 2378 | name = "zerocopy" 2379 | version = "0.7.35" 2380 | source = "registry+https://github.com/rust-lang/crates.io-index" 2381 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2382 | dependencies = [ 2383 | "zerocopy-derive", 2384 | ] 2385 | 2386 | [[package]] 2387 | name = "zerocopy-derive" 2388 | version = "0.7.35" 2389 | source = "registry+https://github.com/rust-lang/crates.io-index" 2390 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2391 | dependencies = [ 2392 | "proc-macro2", 2393 | "quote", 2394 | "syn 2.0.96", 2395 | ] 2396 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | default-members = ["tf-ncl"] 3 | members = [ 4 | "tf-ncl", 5 | ] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Tweag I/O Limited. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform Configurations With Nickel 2 | 3 | This repository contains tooling for generating 4 | [Nickel](https://github.com/tweag/nickel) contracts out of 5 | [Terraform](https://www.terraform.io) provider schemas. 6 | 7 | It enables configurations to be checked against provider specific contracts 8 | before calling Terraform to perform the deployment. Nickel can natively generate 9 | outputs as JSON, YAML or TOML. Since Terraform can accept its deployment 10 | configuration as JSON, you can straightforwardly export a Nickel configuration, 11 | adhering to the right format, to Terraform. Tf-Ncl provides a framework for 12 | ensuring a Nickel configuration has this specific format. Specifically, Tf-Ncl 13 | is a tool to generate Nickel contracts that describe the configuration schema 14 | expected by a set of Terraform providers. 15 | 16 | ## Starting a Tf-Ncl configuration 17 | 18 | The easiest way to get started is to use the `hello-tf` flake template: 19 | 20 | ```console 21 | nix flake init -t github:tweag/tf-ncl#hello-tf 22 | ``` 23 | 24 | This will leave you with a `flake.nix` file containing some glue code for 25 | getting a Nickel contract out of `tf-ncl`, evaluating a Nickel configuration 26 | and calling Terraform. It's as easy as 27 | 28 | ```console 29 | nix develop -c run-terraform init 30 | nix develop -c run-terraform apply 31 | ``` 32 | 33 | Without Nix it's a bit more complicated. You will need to obtain the Nickel 34 | contract using the tools in this repository. Take a look at [the working 35 | principle](#how) for an overview of the process. The most involved step will be 36 | calling `schema-merge` with extracted Terraform provider schemas, see [the nix 37 | code](nix/terraform_schema.nix) for inspiration. 38 | 39 | Once you have a project set up, you can start writing Nickel configuration 40 | code. To quote from the `hello-tf` example: 41 | 42 | ```nickel 43 | let Tf = import "./schema.ncl" in 44 | { 45 | config.resource.null_resource.hello-world = { 46 | provisioner.local-exec = [{ 47 | command = m%" 48 | echo 'Hello, world!' 49 | "% 50 | }], 51 | }, 52 | } | Tf.Config 53 | ``` 54 | 55 | Anything goes! You just need to ensure that your Terraform configuration ends 56 | up in the toplevel attribute `config` and that your entire configuration 57 | evaluates to a record satisfying the `Tf.Config` contract. 58 | 59 | To actually turn the Nickel code into a JSON configuration file understood by 60 | Terraform, you need to call `nickel` to export the `renderable_config` toplevel 61 | attribute introduced by the `Tf.Config` contract: 62 | 63 | ```console 64 | nickel export --field renderable_config 65 | ``` 66 | 67 | This can be useful for inspecting the result of your code. But usually it will 68 | be easier to use the wrapper script for Terraform provided in the [`hello-tf` 69 | flake template](examples/hello-tf/flake.nix). 70 | 71 | For inspiration on what's possible with Nickel, take a look at [the 72 | examples](examples/). Happy hacking! 73 | 74 | ## How 75 | 76 | Unfortunately, Terraform doesn't expose an interface for extracting a machine 77 | readable specification for the provider independent configuration it supports. 78 | Because of that this repository contains two tools and some glue written in 79 | Nix. Maybe this flowchart helps: 80 | 81 | ```mermaid 82 | flowchart LR 83 | subgraph Nix 84 | direction TB 85 | providerSpec(Required Providers); 86 | providerSchemasNix(Terraform provider schemas); 87 | providerSpec -- generateJsonSchema --> providerSchemasNix; 88 | end 89 | 90 | subgraph schema-merge 91 | direction TB 92 | providerSchemasGo(Terraform provider schemas); 93 | merged-json-go(Merged JSON w/ Terraform builtins); 94 | providerSchemasGo --> merged-json-go; 95 | end 96 | 97 | subgraph tf-ncl 98 | direction TB 99 | merged-json-rust(Merged JSON w/ Terraform builtins); 100 | nickel-contracts(Monolithic Nickel contract) 101 | merged-json-rust --> nickel-contracts; 102 | end 103 | 104 | Nix --> schema-merge 105 | schema-merge --> tf-ncl 106 | ``` 107 | 108 | The entire process is packaged up in a Nix function `generateSchema` which is 109 | exposed as a flake output. Also, to generate a Nickel contract for a single 110 | provider, there is a flake output `schemas`: 111 | 112 | ```console 113 | nix build github:tweag/tf-ncl#schemas.aws 114 | ``` 115 | 116 | All providers available in `nixpkgs` are supported. The `generateSchema` 117 | function can also be called manually. For example, to get a monolithic Nickel 118 | schema for the `aws`, `github` and `external` Terraform providers, you could 119 | use 120 | 121 | ```console 122 | nix build --impure --expr \ 123 | '(builtins.getFlake "github:tweag/tf-ncl).generateSchema.${builtins.currentSystem} (p: { inherit (p) aws github external; })' 124 | ``` 125 | 126 | ## Status 127 | 128 | This project is in active development and breaking changes should be expected. 129 | 130 | - [x] Automatic contracts for Terraform provider schemas 131 | - [x] Contracts for Terraform state backends [#14][i14], [#15][i15] 132 | - [ ] More documentation [#13][i13] 133 | - [ ] Natural handling of field references [#12][i12] 134 | 135 | [i12]: https://github.com/tweag/tf-ncl/issues/12 136 | [i13]: https://github.com/tweag/tf-ncl/issues/13 137 | [i14]: https://github.com/tweag/tf-ncl/issues/14 138 | [i15]: https://github.com/tweag/tf-ncl/issues/15 139 | 140 | -------------------------------------------------------------------------------- /examples/aws/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | tf-ncl.url = "github:tweag/tf-ncl"; 4 | utils.url = "github:numtide/flake-utils"; 5 | }; 6 | 7 | nixConfig = { 8 | extra-substituters = [ "https://tweag-nickel.cachix.org" ]; 9 | extra-trusted-public-keys = [ "tweag-nickel.cachix.org-1:GIthuiK4LRgnW64ALYEoioVUQBWs0jexyoYVeLDBwRA=" ]; 10 | }; 11 | 12 | outputs = inputs: inputs.utils.lib.eachDefaultSystem (system: 13 | { 14 | devShell = inputs.tf-ncl.lib.${system}.mkDevShell { 15 | providers = p: { 16 | inherit (p) aws null external; 17 | }; 18 | }; 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /examples/aws/main.ncl: -------------------------------------------------------------------------------- 1 | let Tf = import "./tf-ncl-schema.ncl" in 2 | let Aws = import "./modules/aws.ncl" in 3 | let ami-id = match { 4 | 'eu-central-1 => "ami-0f1cf34dcb4057a5f", # Ubuntu 22.10 5 | } 6 | in 7 | let private-key-path = "./" in 8 | ( 9 | { 10 | aws.region = 'eu-central-1, 11 | 12 | aws.state = { 13 | bucket = "tf-ncl-aws-example", 14 | key = "aws-demo.tfstate", 15 | region = aws.region, 16 | }, 17 | 18 | aws.credentials.assume_role.role_arn = "placeholder_iam_role", 19 | 20 | aws.simple.networks.test = { 21 | cidr_block = { 22 | prefix = "10.1.0.0", 23 | length = 16, 24 | }, 25 | }, 26 | 27 | aws.simple.ec2.instances = { 28 | test = { 29 | ami = ami-id aws.region, 30 | type = "t2.micro", 31 | network = aws.simple.networks.test, 32 | public_key_name = "${resource.aws_key_pair.ssh_key.key_name}", 33 | }, 34 | }, 35 | 36 | config = { 37 | data.external.ssh_key = { 38 | program = ["./ssh-pubkey.sh", private-key-path], 39 | }, 40 | 41 | resource.aws_key_pair.ssh_key = { 42 | public_key = "${data.external.ssh_key.result.public_key}", 43 | }, 44 | 45 | output."instance_ip".value = aws.simple.ec2.instances.test.public_ip, 46 | }, 47 | } 48 | | Aws.Simple 49 | | Aws.S3Backend 50 | ) | Tf.Config 51 | 52 | -------------------------------------------------------------------------------- /examples/aws/modules/aws-availability-zones.ncl: -------------------------------------------------------------------------------- 1 | let Utils = import "../utils.ncl" in 2 | let Aws = import "./types.ncl" in 3 | { 4 | aws.availability_zones 5 | | { 6 | _ | { 7 | region | Aws.Region, 8 | data, 9 | path, 10 | } 11 | } 12 | | Utils.Elaborate "data" (fun name => config.data.aws_availability_zones."%{name}") 13 | | Utils.Elaborate "path" (fun name => "data.aws_availability_zones.%{name}"), 14 | 15 | config = 16 | aws.availability_zones 17 | |> std.record.fields 18 | |> std.array.map 19 | ( 20 | fun name => 21 | let cfg = aws.availability_zones."%{name}" 22 | in 23 | { 24 | data.aws_availability_zones."%{name}" = { 25 | state = "available", 26 | }, 27 | } 28 | ) 29 | |> std.record.merge_all, 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/aws/modules/aws-provider.ncl: -------------------------------------------------------------------------------- 1 | let Aws = import "./types.ncl" in 2 | { 3 | aws.region | Aws.Region, 4 | 5 | aws.credentials 6 | | { 7 | profile 8 | | String 9 | | optional, 10 | assume_role 11 | | { 12 | role_arn | String, 13 | session_name 14 | | String 15 | | default 16 | = "tf-ncl", 17 | } 18 | | optional, 19 | }, 20 | 21 | config.provider.aws = [ 22 | { 23 | region = std.string.from aws.region, 24 | } 25 | & ( 26 | if std.record.has_field "profile" aws.credentials then 27 | { profile = aws.credentials.profile } 28 | else 29 | {} 30 | ) 31 | & ( 32 | if std.record.has_field "assume_role" aws.credentials then 33 | { 34 | assume_role = [ 35 | { 36 | role_arn = aws.credentials.assume_role.role_arn, 37 | session_name = aws.credentials.assume_role.session_name, 38 | } 39 | ] 40 | } 41 | else 42 | {} 43 | ) 44 | ], 45 | } 46 | 47 | -------------------------------------------------------------------------------- /examples/aws/modules/aws-s3-backend.ncl: -------------------------------------------------------------------------------- 1 | let Aws = import "./types.ncl" in 2 | { 3 | aws.state 4 | | { 5 | bucket | String, 6 | key | String, 7 | region | Aws.Region, 8 | }, 9 | 10 | config.terraform.backend.s3 = { 11 | bucket = aws.state.bucket, 12 | key = aws.state.key, 13 | region = std.string.from aws.state.region, 14 | }, 15 | } 16 | 17 | -------------------------------------------------------------------------------- /examples/aws/modules/aws-simple-ec2.ncl: -------------------------------------------------------------------------------- 1 | let Aws = import "./types.ncl" in 2 | let Utils = import "../utils.ncl" in 3 | { 4 | aws.simple.ec2.instances 5 | | { 6 | _ | { 7 | ami | String, 8 | type | String, 9 | public_key_name | String, 10 | network, 11 | public_ip 12 | } 13 | } 14 | | Utils.Elaborate "public_ip" (fun name => config.resource.aws_instance."%{name}".public_ip) 15 | | default 16 | = {}, 17 | 18 | config = 19 | aws.simple.ec2.instances 20 | |> std.record.fields 21 | |> std.array.map 22 | ( 23 | fun name => 24 | let cfg = aws.simple.ec2.instances."%{name}" 25 | in 26 | { 27 | resource.aws_security_group."%{name}_forwarder" = { 28 | vpc_id = cfg.network.vpc.id, 29 | ingress = [ 30 | { 31 | from_port = 22, 32 | protocol = "tcp", 33 | to_port = 22, 34 | cidr_blocks = ["0.0.0.0/0"], 35 | 36 | description = "${null}", 37 | ipv6_cidr_blocks = [], 38 | prefix_list_ids = [], 39 | security_groups = [], 40 | self = false, 41 | } 42 | ], 43 | egress = [ 44 | { 45 | from_port = 0, 46 | protocol = "-1", 47 | to_port = 0, 48 | cidr_blocks = ["0.0.0.0/0"], 49 | 50 | description = "${null}", 51 | ipv6_cidr_blocks = [], 52 | prefix_list_ids = [], 53 | security_groups = [], 54 | self = false, 55 | } 56 | ], 57 | }, 58 | 59 | resource.aws_instance."%{name}" = { 60 | ami = cfg.ami, 61 | instance_type = cfg.type, 62 | subnet_id = cfg.network.subnet.id, 63 | vpc_security_group_ids = [config.resource.aws_security_group."%{name}_forwarder".id], 64 | key_name = cfg.public_key_name, 65 | associate_public_ip_address = true, 66 | }, 67 | } 68 | ) 69 | |> std.record.merge_all, 70 | } 71 | 72 | -------------------------------------------------------------------------------- /examples/aws/modules/aws-simple-vpc.ncl: -------------------------------------------------------------------------------- 1 | let Aws = import "./types.ncl" in 2 | let Utils = import "../utils.ncl" in 3 | { 4 | aws.region | Aws.Region, 5 | 6 | aws.simple.networks 7 | | { 8 | _ | { 9 | region 10 | | Aws.Region 11 | | default 12 | = aws.region, 13 | cidr_block | Aws.CidrBlock, 14 | vpc, 15 | subnet, 16 | } 17 | } 18 | | Utils.Elaborate "vpc" (fun name => config.resource.aws_vpc."%{name}_vpc") 19 | | Utils.Elaborate "subnet" (fun name => config.resource.aws_subnet."%{name}") 20 | | default 21 | = {}, 22 | 23 | aws.availability_zones = 24 | aws.simple.networks 25 | |> std.record.fields 26 | |> std.array.map 27 | ( 28 | fun name => 29 | let cfg = aws.simple.networks."%{name}" 30 | in 31 | { 32 | "%{name}_availability_zone" = { 33 | region = cfg.region, 34 | } 35 | } 36 | ) 37 | |> std.record.merge_all, 38 | 39 | config = 40 | aws.simple.networks 41 | |> std.record.fields 42 | |> std.array.map 43 | ( 44 | fun name => 45 | let cfg = aws.simple.networks."%{name}" 46 | in 47 | { 48 | resource.aws_vpc."%{name}_vpc" = { 49 | cidr_block = "%{cfg.cidr_block.prefix}/%{std.string.from cfg.cidr_block.length}", 50 | tags."Name" = "Tf-Ncl %{name} VPC", 51 | }, 52 | 53 | resource.aws_subnet."%{name}" = { 54 | cidr_block = "%{cfg.cidr_block.prefix}/%{std.string.from cfg.cidr_block.length}", 55 | vpc_id = config.resource.aws_vpc."%{name}_vpc".id, 56 | availability_zone = "${%{aws.availability_zones."%{name}_availability_zone".path}.names[0]}", 57 | tags."Name" = "Tf-Ncl %{name} Subnet", 58 | }, 59 | 60 | resource.aws_internet_gateway."%{name}_gw" = { 61 | vpc_id = config.resource.aws_vpc."%{name}_vpc".id, 62 | tags."Name" = "Tf-Ncl %{name} Gateway", 63 | }, 64 | 65 | resource.aws_route_table."%{name}_default_rt" = { 66 | vpc_id = config.resource.aws_vpc."%{name}_vpc".id, 67 | tags."Name" = "Tf-Ncl %{name} Internet Route Table", 68 | }, 69 | 70 | resource.aws_route."%{name}_default_route" = { 71 | route_table_id = config.resource.aws_route_table."%{name}_default_rt".id, 72 | destination_cidr_block = "0.0.0.0/0", 73 | gateway_id = config.resource.aws_internet_gateway."%{name}_gw".id, 74 | }, 75 | 76 | resource.aws_route."%{name}_default6_route" = { 77 | route_table_id = config.resource.aws_route_table."%{name}_default_rt".id, 78 | destination_ipv6_cidr_block = "::/0", 79 | gateway_id = config.resource.aws_internet_gateway."%{name}_gw".id, 80 | }, 81 | 82 | resource.aws_route_table_association."%{name}_public_subnet_association" = { 83 | subnet_id = config.resource.aws_subnet."%{name}".id, 84 | route_table_id = config.resource.aws_route_table."%{name}_default_rt".id, 85 | }, 86 | } 87 | ) 88 | |> std.record.merge_all, 89 | } 90 | 91 | -------------------------------------------------------------------------------- /examples/aws/modules/aws.ncl: -------------------------------------------------------------------------------- 1 | { 2 | Simple = 3 | { .. } 4 | & (import "./aws-availability-zones.ncl") 5 | & (import "./aws-provider.ncl") 6 | & (import "./aws-simple-vpc.ncl") 7 | & (import "./aws-simple-ec2.ncl"), 8 | 9 | S3Backend = import "./aws-s3-backend.ncl", 10 | } 11 | 12 | -------------------------------------------------------------------------------- /examples/aws/modules/types.ncl: -------------------------------------------------------------------------------- 1 | { 2 | CidrBlock = { 3 | prefix | String, 4 | length | Number, 5 | }, 6 | 7 | Region = [| 8 | 'ap-northeast-1, 9 | 'ap-northeast-2, 10 | 'ap-northeast-3, 11 | 'ap-south-1, 12 | 'ap-southeast-1, 13 | 'ap-southeast-2, 14 | 'ca-central-1, 15 | 'eu-central-1, 16 | 'eu-north-1, 17 | 'eu-west-1, 18 | 'eu-west-2, 19 | 'eu-west-3, 20 | 'sa-east-1, 21 | 'us-east-1, 22 | 'us-east-2, 23 | 'us-west-1, 24 | 'us-west-2, 25 | |], 26 | } 27 | 28 | -------------------------------------------------------------------------------- /examples/aws/ssh-pubkey.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | printf '{"public_key": "%s"}\n' "$(ssh-keygen -yf "${1}")" 3 | -------------------------------------------------------------------------------- /examples/aws/utils.ncl: -------------------------------------------------------------------------------- 1 | { 2 | trace_val = fun x => std.trace (std.serialize 'Json x) x, 3 | 4 | Elaborate | String -> (String -> Dyn) -> Dyn -> { _ | Dyn } -> { _ | Dyn } 5 | = fun field elaborate _l r => 6 | std.record.fields r |> std.array.fold_left (fun r name => r & { "%{name}"."%{field}" = elaborate name }) r, 7 | } 8 | 9 | -------------------------------------------------------------------------------- /examples/github-simple/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | tf-ncl.url = "github:tweag/tf-ncl"; 4 | utils.url = "github:numtide/flake-utils"; 5 | }; 6 | 7 | nixConfig = { 8 | extra-substituters = [ "https://tweag-nickel.cachix.org" ]; 9 | extra-trusted-public-keys = [ "tweag-nickel.cachix.org-1:GIthuiK4LRgnW64ALYEoioVUQBWs0jexyoYVeLDBwRA=" ]; 10 | }; 11 | 12 | outputs = inputs: inputs.utils.lib.eachDefaultSystem (system: 13 | { 14 | devShell = inputs.tf-ncl.lib.${system}.mkDevShell { 15 | providers = p: { 16 | inherit (p) github; 17 | }; 18 | }; 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /examples/github-simple/main.ncl: -------------------------------------------------------------------------------- 1 | let Tf = import "./tf-ncl-schema.ncl" in 2 | { 3 | users = [ 4 | "alice", 5 | "bob", 6 | "charlie" 7 | ], 8 | 9 | memberships = 10 | users 11 | |> std.array.map 12 | ( 13 | fun user => 14 | { 15 | resource.github_membership."%{user}-membership" = { 16 | username = user, 17 | role = "member", 18 | } 19 | } 20 | ) 21 | |> std.record.merge_all, 22 | 23 | config = 24 | { 25 | provider.github = [ 26 | { 27 | token = "", 28 | owner = "", 29 | } 30 | ], 31 | } 32 | & memberships, 33 | } | Tf.Config 34 | 35 | -------------------------------------------------------------------------------- /examples/github-users/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | tf-ncl.url = "github:tweag/tf-ncl"; 4 | utils.url = "github:numtide/flake-utils"; 5 | }; 6 | 7 | nixConfig = { 8 | extra-substituters = [ "https://tweag-nickel.cachix.org" ]; 9 | extra-trusted-public-keys = [ "tweag-nickel.cachix.org-1:GIthuiK4LRgnW64ALYEoioVUQBWs0jexyoYVeLDBwRA=" ]; 10 | }; 11 | 12 | outputs = inputs: inputs.utils.lib.eachDefaultSystem (system: 13 | { 14 | devShell = inputs.tf-ncl.lib.${system}.mkDevShell { 15 | providers = p: { 16 | inherit (p) github null external; 17 | }; 18 | }; 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /examples/github-users/lib.ncl: -------------------------------------------------------------------------------- 1 | { 2 | uniq | Array Dyn -> Array Dyn = 3 | let go : { visited : { _ : {} }, out : Array Dyn } -> Dyn -> { visited : { _ : {} }, out : Array Dyn } = fun acc nxt => 4 | let as_str = std.serialize 'Json nxt in 5 | if std.record.has_field as_str acc.visited then 6 | acc 7 | else 8 | { visited = std.record.insert as_str {} acc.visited, out = acc.out @ [nxt] } 9 | in 10 | fun arr => 11 | (std.array.fold_left go { visited = ({} | { _ : {} }), out = [] } (arr | Array Dyn)).out, 12 | 13 | collect_teams = fun users => 14 | users 15 | |> std.record.values 16 | |> std.array.map (fun user => user.extra-teams) 17 | |> std.array.flatten 18 | |> uniq 19 | |> std.array.prepend "all", 20 | 21 | collect_users = fun users team => 22 | users 23 | |> std.record.fields 24 | |> std.array.filter 25 | ( 26 | fun user => 27 | team == "all" || std.array.elem team users."%{user}".extra-teams 28 | ), 29 | 30 | mk_teams = fun config users => 31 | users 32 | |> collect_teams 33 | |> std.array.map 34 | ( 35 | fun team => 36 | { 37 | resource.github_team."%{team}-team" = { 38 | name = team, 39 | privacy = "closed", 40 | }, 41 | resource.github_team_members."%{team}-members" = { 42 | team_id = config.resource.github_team."%{team}-team".id, 43 | members = 44 | collect_users users team 45 | |> std.array.map 46 | ( 47 | fun user => 48 | { 49 | username = user, 50 | role = 51 | if users."%{user}".is-admin then 52 | "maintainer" 53 | else 54 | "member", 55 | } 56 | ), 57 | }, 58 | } 59 | ) 60 | |> std.record.merge_all, 61 | 62 | mk_memberships = fun users => 63 | users 64 | |> std.record.fields 65 | |> std.array.map 66 | ( 67 | fun name => 68 | let user = users."%{name}" in 69 | { 70 | resource.github_membership."%{name}" = { 71 | role = if user.is-admin then "admin" else "member", 72 | username = name, 73 | } 74 | } 75 | ) 76 | |> std.record.merge_all 77 | } 78 | 79 | -------------------------------------------------------------------------------- /examples/github-users/main.ncl: -------------------------------------------------------------------------------- 1 | let Tf = import "./tf-ncl-schema.ncl" in 2 | let User = import "./user-contract.ncl" in 3 | let { TwoAdmins, .. } = import "./validation.ncl" in 4 | let { mk_teams, mk_memberships, .. } = import "./lib.ncl" in 5 | { 6 | Users = { _ : User }, 7 | 8 | users | Users 9 | = import "./users.ncl", 10 | 11 | config = 12 | { 13 | terraform.required_providers = Tf.required_providers, 14 | } 15 | & mk_memberships users 16 | & mk_teams config users, 17 | } | Tf.Config 18 | 19 | -------------------------------------------------------------------------------- /examples/github-users/user-contract.ncl: -------------------------------------------------------------------------------- 1 | { 2 | is-admin 3 | | Bool 4 | | default 5 | = false, 6 | extra-teams 7 | | Array String 8 | | default 9 | = [], 10 | } 11 | 12 | -------------------------------------------------------------------------------- /examples/github-users/users.ncl: -------------------------------------------------------------------------------- 1 | { 2 | yannham = { 3 | is-admin = true, 4 | extra-teams = [ "nickel" ], 5 | }, 6 | vkleen = { 7 | is-admin = false, 8 | extra-teams = [ "nickel" ], 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /examples/github-users/validation.ncl: -------------------------------------------------------------------------------- 1 | let User = import "./user-contract.ncl" in 2 | { 3 | TwoAdmins = fun label value => 4 | let value = std.contract.apply { _ : User } label value in 5 | let admins = value |> std.record.values |> std.array.filter (fun u => u.is-admin) in 6 | if std.array.length admins < 2 then 7 | std.contract.blame_with "We need at least 2 admins" label 8 | else 9 | value, 10 | 11 | NoA = fun label value => 12 | let value = std.contract.apply { _ : Dyn } label value in 13 | let fields = std.record.fields value in 14 | if std.array.any (std.string.contains "a") fields then 15 | std.contract.blame_with "a is not allowed in usernames" label 16 | else 17 | value, 18 | } 19 | 20 | -------------------------------------------------------------------------------- /examples/hello-tf/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | tf-ncl.url = "github:tweag/tf-ncl"; 4 | utils.url = "github:numtide/flake-utils"; 5 | }; 6 | 7 | nixConfig = { 8 | extra-substituters = [ "https://tweag-nickel.cachix.org" ]; 9 | extra-trusted-public-keys = [ "tweag-nickel.cachix.org-1:GIthuiK4LRgnW64ALYEoioVUQBWs0jexyoYVeLDBwRA=" ]; 10 | }; 11 | 12 | outputs = inputs: inputs.utils.lib.eachDefaultSystem (system: 13 | { 14 | devShell = inputs.tf-ncl.lib.${system}.mkDevShell { 15 | providers = p: { 16 | inherit (p) null; 17 | }; 18 | }; 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /examples/hello-tf/main.ncl: -------------------------------------------------------------------------------- 1 | let Tf = import "./tf-ncl-schema.ncl" in 2 | { 3 | config.resource.null_resource.hello-world = { 4 | provisioner.local-exec = [ 5 | { 6 | command = m%" 7 | echo 'Hello, world!' 8 | "% 9 | } 10 | ], 11 | }, 12 | } | Tf.Config 13 | 14 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "advisory-db": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1723137097, 7 | "narHash": "sha256-Q/TeuIV610BJ39UkP4zRm6pG6BWEaOCih/WXNR2V9rk=", 8 | "owner": "rustsec", 9 | "repo": "advisory-db", 10 | "rev": "1d209d3f18c740f104380e988b5aa8eb360190d1", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "rustsec", 15 | "repo": "advisory-db", 16 | "type": "github" 17 | } 18 | }, 19 | "crane": { 20 | "locked": { 21 | "lastModified": 1736566337, 22 | "narHash": "sha256-SC0eDcZPqISVt6R0UfGPyQLrI0+BppjjtQ3wcSlk0oI=", 23 | "owner": "ipetkov", 24 | "repo": "crane", 25 | "rev": "9172acc1ee6c7e1cbafc3044ff850c568c75a5a3", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "ipetkov", 30 | "repo": "crane", 31 | "type": "github" 32 | } 33 | }, 34 | "flake-compat": { 35 | "flake": false, 36 | "locked": { 37 | "lastModified": 1696426674, 38 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 39 | "owner": "edolstra", 40 | "repo": "flake-compat", 41 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 42 | "type": "github" 43 | }, 44 | "original": { 45 | "owner": "edolstra", 46 | "repo": "flake-compat", 47 | "type": "github" 48 | } 49 | }, 50 | "flake-compat_2": { 51 | "flake": false, 52 | "locked": { 53 | "lastModified": 1696426674, 54 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 55 | "owner": "edolstra", 56 | "repo": "flake-compat", 57 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 58 | "type": "github" 59 | }, 60 | "original": { 61 | "owner": "edolstra", 62 | "repo": "flake-compat", 63 | "type": "github" 64 | } 65 | }, 66 | "flake-parts": { 67 | "inputs": { 68 | "nixpkgs-lib": [ 69 | "nickel", 70 | "nix-input", 71 | "nixpkgs" 72 | ] 73 | }, 74 | "locked": { 75 | "lastModified": 1719994518, 76 | "narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=", 77 | "owner": "hercules-ci", 78 | "repo": "flake-parts", 79 | "rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7", 80 | "type": "github" 81 | }, 82 | "original": { 83 | "owner": "hercules-ci", 84 | "repo": "flake-parts", 85 | "type": "github" 86 | } 87 | }, 88 | "flake-utils": { 89 | "inputs": { 90 | "systems": "systems" 91 | }, 92 | "locked": { 93 | "lastModified": 1726560853, 94 | "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", 95 | "owner": "numtide", 96 | "repo": "flake-utils", 97 | "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", 98 | "type": "github" 99 | }, 100 | "original": { 101 | "owner": "numtide", 102 | "repo": "flake-utils", 103 | "type": "github" 104 | } 105 | }, 106 | "flake-utils_2": { 107 | "inputs": { 108 | "systems": "systems_2" 109 | }, 110 | "locked": { 111 | "lastModified": 1710146030, 112 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 113 | "owner": "numtide", 114 | "repo": "flake-utils", 115 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 116 | "type": "github" 117 | }, 118 | "original": { 119 | "owner": "numtide", 120 | "repo": "flake-utils", 121 | "type": "github" 122 | } 123 | }, 124 | "git-hooks-nix": { 125 | "inputs": { 126 | "flake-compat": [ 127 | "nickel", 128 | "nix-input" 129 | ], 130 | "gitignore": [ 131 | "nickel", 132 | "nix-input" 133 | ], 134 | "nixpkgs": [ 135 | "nickel", 136 | "nix-input", 137 | "nixpkgs" 138 | ], 139 | "nixpkgs-stable": [ 140 | "nickel", 141 | "nix-input", 142 | "nixpkgs" 143 | ] 144 | }, 145 | "locked": { 146 | "lastModified": 1721042469, 147 | "narHash": "sha256-6FPUl7HVtvRHCCBQne7Ylp4p+dpP3P/OYuzjztZ4s70=", 148 | "owner": "cachix", 149 | "repo": "git-hooks.nix", 150 | "rev": "f451c19376071a90d8c58ab1a953c6e9840527fd", 151 | "type": "github" 152 | }, 153 | "original": { 154 | "owner": "cachix", 155 | "repo": "git-hooks.nix", 156 | "type": "github" 157 | } 158 | }, 159 | "gitignore": { 160 | "inputs": { 161 | "nixpkgs": [ 162 | "nickel", 163 | "pre-commit-hooks", 164 | "nixpkgs" 165 | ] 166 | }, 167 | "locked": { 168 | "lastModified": 1709087332, 169 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 170 | "owner": "hercules-ci", 171 | "repo": "gitignore.nix", 172 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 173 | "type": "github" 174 | }, 175 | "original": { 176 | "owner": "hercules-ci", 177 | "repo": "gitignore.nix", 178 | "type": "github" 179 | } 180 | }, 181 | "gitignore_2": { 182 | "inputs": { 183 | "nixpkgs": [ 184 | "pre-commit-hooks", 185 | "nixpkgs" 186 | ] 187 | }, 188 | "locked": { 189 | "lastModified": 1709087332, 190 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 191 | "owner": "hercules-ci", 192 | "repo": "gitignore.nix", 193 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 194 | "type": "github" 195 | }, 196 | "original": { 197 | "owner": "hercules-ci", 198 | "repo": "gitignore.nix", 199 | "type": "github" 200 | } 201 | }, 202 | "libgit2": { 203 | "flake": false, 204 | "locked": { 205 | "lastModified": 1715853528, 206 | "narHash": "sha256-J2rCxTecyLbbDdsyBWn9w7r3pbKRMkI9E7RvRgAqBdY=", 207 | "owner": "libgit2", 208 | "repo": "libgit2", 209 | "rev": "36f7e21ad757a3dacc58cf7944329da6bc1d6e96", 210 | "type": "github" 211 | }, 212 | "original": { 213 | "owner": "libgit2", 214 | "ref": "v1.8.1", 215 | "repo": "libgit2", 216 | "type": "github" 217 | } 218 | }, 219 | "nickel": { 220 | "inputs": { 221 | "crane": [ 222 | "crane" 223 | ], 224 | "flake-utils": "flake-utils", 225 | "nix-input": "nix-input", 226 | "nixpkgs": "nixpkgs", 227 | "pre-commit-hooks": "pre-commit-hooks", 228 | "rust-overlay": "rust-overlay" 229 | }, 230 | "locked": { 231 | "lastModified": 1735178135, 232 | "narHash": "sha256-MYI4mjJBAfVkO3dGvWgaFclIJ4asIWDp8K00WfpaMiQ=", 233 | "owner": "tweag", 234 | "repo": "nickel", 235 | "rev": "228712f77fb00bc2bb021858de552789e80eca84", 236 | "type": "github" 237 | }, 238 | "original": { 239 | "owner": "tweag", 240 | "repo": "nickel", 241 | "type": "github" 242 | } 243 | }, 244 | "nix-input": { 245 | "inputs": { 246 | "flake-compat": [ 247 | "nickel", 248 | "pre-commit-hooks", 249 | "flake-compat" 250 | ], 251 | "flake-parts": "flake-parts", 252 | "git-hooks-nix": "git-hooks-nix", 253 | "libgit2": "libgit2", 254 | "nixpkgs": [ 255 | "nickel", 256 | "nixpkgs" 257 | ], 258 | "nixpkgs-23-11": "nixpkgs-23-11", 259 | "nixpkgs-regression": "nixpkgs-regression" 260 | }, 261 | "locked": { 262 | "lastModified": 1727696274, 263 | "narHash": "sha256-H+EeGBRV87NRDXgOQP/aZfof9svbYCSQktpMiLBrqCQ=", 264 | "owner": "nixos", 265 | "repo": "nix", 266 | "rev": "c116030605bf7fecd232d0ff3b6fe066f23e4620", 267 | "type": "github" 268 | }, 269 | "original": { 270 | "owner": "nixos", 271 | "repo": "nix", 272 | "type": "github" 273 | } 274 | }, 275 | "nixpkgs": { 276 | "locked": { 277 | "lastModified": 1726937504, 278 | "narHash": "sha256-bvGoiQBvponpZh8ClUcmJ6QnsNKw0EMrCQJARK3bI1c=", 279 | "owner": "NixOS", 280 | "repo": "nixpkgs", 281 | "rev": "9357f4f23713673f310988025d9dc261c20e70c6", 282 | "type": "github" 283 | }, 284 | "original": { 285 | "id": "nixpkgs", 286 | "ref": "nixos-unstable", 287 | "type": "indirect" 288 | } 289 | }, 290 | "nixpkgs-23-11": { 291 | "locked": { 292 | "lastModified": 1717159533, 293 | "narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=", 294 | "owner": "NixOS", 295 | "repo": "nixpkgs", 296 | "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", 297 | "type": "github" 298 | }, 299 | "original": { 300 | "owner": "NixOS", 301 | "repo": "nixpkgs", 302 | "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", 303 | "type": "github" 304 | } 305 | }, 306 | "nixpkgs-regression": { 307 | "locked": { 308 | "lastModified": 1643052045, 309 | "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", 310 | "owner": "NixOS", 311 | "repo": "nixpkgs", 312 | "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", 313 | "type": "github" 314 | }, 315 | "original": { 316 | "owner": "NixOS", 317 | "repo": "nixpkgs", 318 | "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", 319 | "type": "github" 320 | } 321 | }, 322 | "nixpkgs-stable": { 323 | "locked": { 324 | "lastModified": 1720386169, 325 | "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=", 326 | "owner": "NixOS", 327 | "repo": "nixpkgs", 328 | "rev": "194846768975b7ad2c4988bdb82572c00222c0d7", 329 | "type": "github" 330 | }, 331 | "original": { 332 | "owner": "NixOS", 333 | "ref": "nixos-24.05", 334 | "repo": "nixpkgs", 335 | "type": "github" 336 | } 337 | }, 338 | "nixpkgs_2": { 339 | "locked": { 340 | "lastModified": 1736701207, 341 | "narHash": "sha256-jG/+MvjVY7SlTakzZ2fJ5dC3V1PrKKrUEOEE30jrOKA=", 342 | "owner": "nixos", 343 | "repo": "nixpkgs", 344 | "rev": "ed4a395ea001367c1f13d34b1e01aa10290f67d6", 345 | "type": "github" 346 | }, 347 | "original": { 348 | "owner": "nixos", 349 | "ref": "nixos-unstable", 350 | "repo": "nixpkgs", 351 | "type": "github" 352 | } 353 | }, 354 | "nixpkgs_3": { 355 | "locked": { 356 | "lastModified": 1723221148, 357 | "narHash": "sha256-7pjpeQlZUNQ4eeVntytU3jkw9dFK3k1Htgk2iuXjaD8=", 358 | "owner": "NixOS", 359 | "repo": "nixpkgs", 360 | "rev": "154bcb95ad51bc257c2ce4043a725de6ca700ef6", 361 | "type": "github" 362 | }, 363 | "original": { 364 | "owner": "NixOS", 365 | "ref": "nixpkgs-unstable", 366 | "repo": "nixpkgs", 367 | "type": "github" 368 | } 369 | }, 370 | "nixpkgs_4": { 371 | "locked": { 372 | "lastModified": 1718428119, 373 | "narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=", 374 | "owner": "NixOS", 375 | "repo": "nixpkgs", 376 | "rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5", 377 | "type": "github" 378 | }, 379 | "original": { 380 | "owner": "NixOS", 381 | "ref": "nixpkgs-unstable", 382 | "repo": "nixpkgs", 383 | "type": "github" 384 | } 385 | }, 386 | "pre-commit-hooks": { 387 | "inputs": { 388 | "flake-compat": "flake-compat", 389 | "gitignore": "gitignore", 390 | "nixpkgs": [ 391 | "nickel", 392 | "nixpkgs" 393 | ], 394 | "nixpkgs-stable": "nixpkgs-stable" 395 | }, 396 | "locked": { 397 | "lastModified": 1726745158, 398 | "narHash": "sha256-D5AegvGoEjt4rkKedmxlSEmC+nNLMBPWFxvmYnVLhjk=", 399 | "owner": "cachix", 400 | "repo": "pre-commit-hooks.nix", 401 | "rev": "4e743a6920eab45e8ba0fbe49dc459f1423a4b74", 402 | "type": "github" 403 | }, 404 | "original": { 405 | "owner": "cachix", 406 | "repo": "pre-commit-hooks.nix", 407 | "type": "github" 408 | } 409 | }, 410 | "pre-commit-hooks_2": { 411 | "inputs": { 412 | "flake-compat": "flake-compat_2", 413 | "gitignore": "gitignore_2", 414 | "nixpkgs": [ 415 | "nixpkgs" 416 | ] 417 | }, 418 | "locked": { 419 | "lastModified": 1735882644, 420 | "narHash": "sha256-3FZAG+pGt3OElQjesCAWeMkQ7C/nB1oTHLRQ8ceP110=", 421 | "owner": "cachix", 422 | "repo": "pre-commit-hooks.nix", 423 | "rev": "a5a961387e75ae44cc20f0a57ae463da5e959656", 424 | "type": "github" 425 | }, 426 | "original": { 427 | "owner": "cachix", 428 | "repo": "pre-commit-hooks.nix", 429 | "type": "github" 430 | } 431 | }, 432 | "root": { 433 | "inputs": { 434 | "crane": "crane", 435 | "nickel": "nickel", 436 | "nixpkgs": "nixpkgs_2", 437 | "pre-commit-hooks": "pre-commit-hooks_2", 438 | "rust-overlay": "rust-overlay_2", 439 | "topiary": "topiary", 440 | "utils": "utils" 441 | } 442 | }, 443 | "rust-overlay": { 444 | "inputs": { 445 | "nixpkgs": [ 446 | "nickel", 447 | "nixpkgs" 448 | ] 449 | }, 450 | "locked": { 451 | "lastModified": 1727144949, 452 | "narHash": "sha256-uMZMjoCS2nf40TAE1686SJl3OXWfdfM+BDEfRdr+uLc=", 453 | "owner": "oxalica", 454 | "repo": "rust-overlay", 455 | "rev": "2e19799819104b46019d339e78d21c14372d3666", 456 | "type": "github" 457 | }, 458 | "original": { 459 | "owner": "oxalica", 460 | "repo": "rust-overlay", 461 | "type": "github" 462 | } 463 | }, 464 | "rust-overlay_2": { 465 | "inputs": { 466 | "nixpkgs": [ 467 | "nixpkgs" 468 | ] 469 | }, 470 | "locked": { 471 | "lastModified": 1736735482, 472 | "narHash": "sha256-QOA4jCDyyUM9Y2Vba+HSZ/5LdtCMGaTE/7NkkUzBr50=", 473 | "owner": "oxalica", 474 | "repo": "rust-overlay", 475 | "rev": "cf960a1938ee91200fe0d2f7b2582fde2429d562", 476 | "type": "github" 477 | }, 478 | "original": { 479 | "owner": "oxalica", 480 | "repo": "rust-overlay", 481 | "type": "github" 482 | } 483 | }, 484 | "rust-overlay_3": { 485 | "inputs": { 486 | "nixpkgs": "nixpkgs_4" 487 | }, 488 | "locked": { 489 | "lastModified": 1723429325, 490 | "narHash": "sha256-4x/32xTCd+xCwFoI/kKSiCr5LQA2ZlyTRYXKEni5HR8=", 491 | "owner": "oxalica", 492 | "repo": "rust-overlay", 493 | "rev": "65e3dc0fe079fe8df087cd38f1fe6836a0373aad", 494 | "type": "github" 495 | }, 496 | "original": { 497 | "owner": "oxalica", 498 | "repo": "rust-overlay", 499 | "type": "github" 500 | } 501 | }, 502 | "systems": { 503 | "locked": { 504 | "lastModified": 1681028828, 505 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 506 | "owner": "nix-systems", 507 | "repo": "default", 508 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 509 | "type": "github" 510 | }, 511 | "original": { 512 | "owner": "nix-systems", 513 | "repo": "default", 514 | "type": "github" 515 | } 516 | }, 517 | "systems_2": { 518 | "locked": { 519 | "lastModified": 1681028828, 520 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 521 | "owner": "nix-systems", 522 | "repo": "default", 523 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 524 | "type": "github" 525 | }, 526 | "original": { 527 | "owner": "nix-systems", 528 | "repo": "default", 529 | "type": "github" 530 | } 531 | }, 532 | "systems_3": { 533 | "locked": { 534 | "lastModified": 1681028828, 535 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 536 | "owner": "nix-systems", 537 | "repo": "default", 538 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 539 | "type": "github" 540 | }, 541 | "original": { 542 | "owner": "nix-systems", 543 | "repo": "default", 544 | "type": "github" 545 | } 546 | }, 547 | "topiary": { 548 | "inputs": { 549 | "advisory-db": "advisory-db", 550 | "crane": [ 551 | "crane" 552 | ], 553 | "nixpkgs": "nixpkgs_3", 554 | "rust-overlay": "rust-overlay_3", 555 | "tree-sitter-nickel": "tree-sitter-nickel" 556 | }, 557 | "locked": { 558 | "lastModified": 1736519392, 559 | "narHash": "sha256-7lyNgi+ZP05ZzHKL1s2xs4bOOA5mkEHpNkeV4xYbDbo=", 560 | "owner": "tweag", 561 | "repo": "topiary", 562 | "rev": "76456f203094abf0cd6b19054fa9a6ef26ded977", 563 | "type": "github" 564 | }, 565 | "original": { 566 | "owner": "tweag", 567 | "repo": "topiary", 568 | "type": "github" 569 | } 570 | }, 571 | "tree-sitter-nickel": { 572 | "inputs": { 573 | "flake-utils": "flake-utils_2", 574 | "nixpkgs": [ 575 | "topiary", 576 | "nixpkgs" 577 | ] 578 | }, 579 | "locked": { 580 | "lastModified": 1723707504, 581 | "narHash": "sha256-IvlUwNO/wLLPuqCZf0NtSxMdDx+4ASYYOobklY/97aQ=", 582 | "owner": "nickel-lang", 583 | "repo": "tree-sitter-nickel", 584 | "rev": "88d836a24b3b11c8720874a1a9286b8ae838d30a", 585 | "type": "github" 586 | }, 587 | "original": { 588 | "owner": "nickel-lang", 589 | "repo": "tree-sitter-nickel", 590 | "type": "github" 591 | } 592 | }, 593 | "utils": { 594 | "inputs": { 595 | "systems": "systems_3" 596 | }, 597 | "locked": { 598 | "lastModified": 1731533236, 599 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 600 | "owner": "numtide", 601 | "repo": "flake-utils", 602 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 603 | "type": "github" 604 | }, 605 | "original": { 606 | "owner": "numtide", 607 | "repo": "flake-utils", 608 | "type": "github" 609 | } 610 | } 611 | }, 612 | "root": "root", 613 | "version": 7 614 | } 615 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 4 | utils.url = "github:numtide/flake-utils"; 5 | nickel = { 6 | url = "github:tweag/nickel"; 7 | inputs.crane.follows = "crane"; 8 | }; 9 | topiary = { 10 | url = "github:tweag/topiary"; 11 | inputs.crane.follows = "crane"; 12 | }; 13 | rust-overlay = { 14 | url = "github:oxalica/rust-overlay"; 15 | inputs.nixpkgs.follows = "nixpkgs"; 16 | }; 17 | crane = { 18 | url = "github:ipetkov/crane"; 19 | }; 20 | pre-commit-hooks = { 21 | url = "github:cachix/pre-commit-hooks.nix"; 22 | inputs.nixpkgs.follows = "nixpkgs"; 23 | }; 24 | }; 25 | nixConfig = { 26 | extra-substituters = [ "https://tweag-nickel.cachix.org" ]; 27 | extra-trusted-public-keys = [ "tweag-nickel.cachix.org-1:GIthuiK4LRgnW64ALYEoioVUQBWs0jexyoYVeLDBwRA=" ]; 28 | }; 29 | outputs = { self, utils, ... }@inputs: 30 | utils.lib.eachDefaultSystem 31 | (system: 32 | let 33 | lastModifiedDate = self.lastModifiedDate or self.lastModified or "19700101"; 34 | version = builtins.substring 0 8 lastModifiedDate; 35 | 36 | pkgs = import inputs.nixpkgs { 37 | localSystem = { inherit system; }; 38 | config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ 39 | "terraform" 40 | ]; 41 | overlays = [ 42 | (import inputs.rust-overlay) 43 | ]; 44 | }; 45 | 46 | inherit (pkgs) lib; 47 | 48 | rustToolchain = pkgs.rust-bin.stable.latest.minimal.override { 49 | extensions = [ 50 | "rust-src" 51 | "rust-analysis" 52 | "rustfmt-preview" 53 | "clippy" 54 | ]; 55 | targets = [ (pkgs.rust.toRustTarget pkgs.stdenv.hostPlatform) ]; 56 | }; 57 | 58 | craneLib = (inputs.crane.mkLib pkgs).overrideToolchain rustToolchain; 59 | 60 | tf-ncl-src = pkgs.lib.cleanSourceWith { 61 | src = pkgs.lib.cleanSource ./.; 62 | filter = path: type: builtins.any (filter: filter path type) [ 63 | (path: _type: builtins.match ".*\.ncl$" path != null) 64 | craneLib.filterCargoSources 65 | ]; 66 | }; 67 | 68 | craneArgs = (craneLib.crateNameFromCargoToml { cargoToml = ./tf-ncl/Cargo.toml; }) // { 69 | src = tf-ncl-src; 70 | }; 71 | 72 | cargoArtifacts = craneLib.buildDepsOnly craneArgs; 73 | 74 | tf-ncl = craneLib.buildPackage (craneArgs // { 75 | inherit cargoArtifacts; 76 | }); 77 | 78 | schema-merge = pkgs.buildGoModule { 79 | pname = "schema-merge"; 80 | inherit version; 81 | 82 | src = ./schema-merge; 83 | 84 | vendorHash = "sha256-JWHyBLu5hqO+ed2SwdmLabwbPeKAM30UirfksI3aSPc="; 85 | }; 86 | 87 | eval-github-yaml = pkgs.writeShellScript "eval-github-yaml" '' 88 | set -e 89 | find .github -name '*.ncl' -print0 | while IFS= read -d $'\0' f; do 90 | (echo "# DO NOT EDIT! Generated by eval-github-yaml from $f"; \ 91 | ${inputs.nickel.packages.${system}.default}/bin/nickel export "$f" --format json) > "''${f%.ncl}.yml" 92 | done 93 | ''; 94 | 95 | pre-commit = inputs.pre-commit-hooks.lib.${system}.run { 96 | src = ./.; 97 | tools = { 98 | inherit (pkgs) cargo rustfmt; 99 | }; 100 | hooks = { 101 | nixpkgs-fmt.enable = true; 102 | rustfmt.enable = true; 103 | gofmt.enable = true; 104 | github-ncl = { 105 | enable = true; 106 | name = "github-ncl"; 107 | description = "TODO"; 108 | files = "^(.github/.*\\.ncl|ncl/github/.*\\.ncl)$"; 109 | entry = "${eval-github-yaml}"; 110 | }; 111 | }; 112 | }; 113 | 114 | terraformProviders = pkgs.terraform-providers.actualProviders; 115 | 116 | release = pkgs.runCommand "release-tarball" 117 | { 118 | nativeBuildInputs = [ pkgs.pixz ]; 119 | } '' 120 | mkdir -p $out 121 | mkdir tf-ncl 122 | ${lib.concatLines (lib.flip lib.mapAttrsToList inputs.self.schemas.${system} (provider: schema: '' 123 | cp ${schema} tf-ncl/${provider}.ncl 124 | ''))} 125 | tar --sort=name --mtime='@1' --owner=0 --group=0 --numeric-owner -c tf-ncl/*.ncl | pixz -t > $out/tf-ncl.tar.xz 126 | ''; 127 | 128 | test-single-example = template: pkgs.writeShellScript "test-${template}" '' 129 | set -e -x 130 | temp_directory=$(mktemp -d) 131 | trap 'rm -r -- "$temp_directory"' EXIT 132 | 133 | cd "$temp_directory" 134 | nix flake init --accept-flake-config -t "${self}#${template}" 135 | nix develop --accept-flake-config --override-input tf-ncl "${self}" -c run-nickel 136 | ''; 137 | 138 | test-examples = pkgs.writeShellScriptBin "test-examples" '' 139 | set -e -x 140 | ${lib.concatMapStringsSep "\n" (tmpl: "${test-single-example tmpl}") (lib.attrNames self.templates)} 141 | ''; 142 | in 143 | { 144 | checks = 145 | self.schemas.${system} // 146 | (lib.mapAttrs' 147 | (name: drv: lib.nameValuePair "check-${name}" ( 148 | let 149 | conf = pkgs.writeText "main.tf.ncl" '' 150 | let Tf = import "${drv}" in 151 | ({ 152 | config = { 153 | output = { 154 | "ip".value = "1.2.3.4", 155 | }, 156 | variable."test-var" = { 157 | sensitive = true, 158 | }, 159 | terraform.backend.local = { 160 | path = "dummy path" 161 | }, 162 | }, 163 | } | Tf.Config).renderable_config 164 | ''; 165 | in 166 | pkgs.runCommand "check-${name}" { } '' 167 | ${inputs.nickel.packages.${system}.default}/bin/nickel export ${conf} > $out 168 | '' 169 | )) 170 | self.schemas.${system}) // 171 | { 172 | inherit tf-ncl schema-merge pre-commit; 173 | }; 174 | 175 | packages = { 176 | default = tf-ncl; 177 | terraform = pkgs.terraform; 178 | inherit tf-ncl schema-merge release test-examples; 179 | } // lib.mapAttrs' (name: value: lib.nameValuePair "schema-${name}" value) self.schemas.${system}; 180 | 181 | inherit terraformProviders; 182 | 183 | generateJsonSchema = providerFn: pkgs.callPackage 184 | (import ./nix/terraform_schema.nix (providerFn terraformProviders)) 185 | { inherit (self.packages.${system}) schema-merge; }; 186 | 187 | generateSchema = providerFn: pkgs.callPackage 188 | ./nix/nickel_schema.nix 189 | { jsonSchema = self.generateJsonSchema.${system} providerFn; inherit (self.packages.${system}) tf-ncl; }; 190 | 191 | schemas = lib.mapAttrs 192 | (name: p: self.generateSchema.${system} (_: { ${name} = p; })) 193 | terraformProviders; 194 | 195 | lib = { 196 | mkDevShell = 197 | { providers, extraNickelInput ? "", packages ? [ ] }: pkgs.mkShell { 198 | buildInputs = lib.attrValues 199 | (pkgs.callPackage ./nix/devshell.nix 200 | { 201 | generateSchema = self.generateSchema.${system}; 202 | nickel = inputs.nickel.packages.${system}.nickel-lang-cli; 203 | } 204 | { inherit providers extraNickelInput; }) ++ packages ++ [ 205 | inputs.nickel.packages.${system}.default 206 | inputs.topiary.packages.${system}.default 207 | ]; 208 | shellHook = '' 209 | cat < 2 | config 3 | |> std.record.values 4 | |> std.array.filter (std.is_string) 5 | |> std.string.join ", " 6 | in 7 | let sanitize = fun x => 8 | std.function.pipe 9 | x 10 | [ 11 | std.string.replace_regex "[^A-Za-z0-9-_]" "-", 12 | std.string.lowercase 13 | ] 14 | in 15 | { 16 | configs 17 | | Array { .. } 18 | | not_exported, 19 | matrix 20 | | not_exported 21 | | { 22 | name | String, 23 | job 24 | | { 25 | steps | Array { .. }, 26 | .. 27 | }, 28 | .. 29 | }, 30 | jobs = 31 | configs 32 | |> std.array.map 33 | ( 34 | fun config' => 35 | { 36 | "%{sanitize "%{matrix.name}-%{name_of_config config'}"}" = 37 | ( 38 | matrix & { config = config' } 39 | ).job 40 | & { 41 | name = "%{matrix.name} / (%{name_of_config config'})", 42 | } 43 | } 44 | ) 45 | |> std.record.merge_all 46 | } 47 | 48 | -------------------------------------------------------------------------------- /ncl/github/setup-steps.ncl: -------------------------------------------------------------------------------- 1 | [ 2 | { uses = "actions/checkout@v4" }, 3 | { 4 | uses = "cachix/install-nix-action@v23", 5 | name = "Installing Nix", 6 | with = { 7 | extra_nix_config = "experimental-features = nix-command flakes", 8 | nix_path = "nixpkgs=channel:nixos-unstable" 9 | } 10 | }, 11 | { 12 | name = "Setup Cachix", 13 | with = { 14 | authToken = "${{ secrets.CACHIX_TWEAG_NICKEL_AUTH_TOKEN }}", 15 | name = "tweag-nickel" 16 | }, 17 | uses = "cachix/cachix-action@v12" 18 | } 19 | ] 20 | 21 | -------------------------------------------------------------------------------- /ncl/lib.ncl: -------------------------------------------------------------------------------- 1 | { 2 | deep_filter_map = 3 | { 4 | go : Array String -> (Array String -> Dyn -> { action : [| 'KeepStop, 'Continue, 'Delete |], value : Dyn }) -> { _ : Dyn } -> { _ : Dyn } -> String -> { _ : Dyn } = fun prefix f r acc field_name => 5 | let prefix = prefix @ [field_name] in 6 | let result = f prefix (r."%{field_name}") in 7 | result.action 8 | |> match { 9 | 'KeepStop => std.record.insert field_name result.value acc, 10 | 'Continue => 11 | let new_value = 12 | if std.is_record result.value then 13 | (deep_filter_map_prefix prefix f (result.value | { _ : Dyn })) | Dyn 14 | else 15 | result.value 16 | in 17 | std.record.insert field_name new_value acc, 18 | 'Delete => acc, 19 | }, 20 | 21 | deep_filter_map_prefix : Array String -> (Array String -> Dyn -> { action : [| 'KeepStop, 'Continue, 'Delete |], value : Dyn }) -> { _ : Dyn } -> { _ : Dyn } 22 | = fun prefix f r => 23 | r 24 | |> std.record.fields 25 | |> std.array.fold_left (go prefix f r) {}, 26 | 27 | deep_filter_map : (Array String -> Dyn -> { action : [| 'KeepStop, 'Continue, 'Delete |], value : Dyn }) -> { _ : Dyn } -> { _ : Dyn } 28 | = deep_filter_map_prefix [], 29 | }.deep_filter_map, 30 | 31 | TaggedUnion = fun tag_field tags shapes label value => 32 | let value = 33 | std.contract.apply { "%{tag_field}" | tags, .. } label value 34 | in std.contract.apply ({ "%{tag_field}" } & shapes value."%{tag_field}") label value, 35 | 36 | TerraformReference = Array String, 37 | 38 | TerraformField = fun ctr => 39 | TaggedUnion 40 | "terraform_field_type" 41 | [| 'Undefined, 'Literal, 'Reference, 'ProviderComputed |] 42 | ( 43 | match { 44 | 'Undefined => {}, 45 | 'Literal => { value | ctr }, 46 | 'Reference => { value | Array Dyn }, 47 | 'ProviderComputed => { path | TerraformReference }, 48 | } 49 | ), 50 | 51 | is_terraform_field_record : Dyn -> Bool 52 | = fun v => 53 | std.is_record v && std.record.has_field "terraform_field_type" (v | { _ : Dyn }), 54 | 55 | Tf = fun ctr label value_ => 56 | if is_terraform_field_record value_ then 57 | std.contract.apply (TerraformField ctr) label value_ 58 | else 59 | { terraform_field_type = 'Literal, value | (fun _l v => std.contract.apply ctr label v) = value_ }, 60 | 61 | provider_computed = fun path_ => 62 | { terraform_field_type = 'ProviderComputed, path = path_ }, 63 | 64 | undefined = { terraform_field_type | default = 'Undefined }, 65 | 66 | resolve_reference : Array String -> String 67 | = fun ns => "${%{std.string.join "." ns}}", 68 | 69 | typeof : Dyn -> [| 'TerraformField, 'Array, 'Other |] 70 | = fun v => 71 | if is_terraform_field_record v then 72 | 'TerraformField 73 | else if std.is_array v then 74 | 'Array 75 | else 76 | 'Other, 77 | 78 | resolve_provider_computed = 79 | let { go, resolve_field_action, .. } = { 80 | resolve_field_action | Array String -> TerraformField Dyn -> Dyn 81 | = fun path field => 82 | field.terraform_field_type 83 | |> match { 84 | 'Undefined => 85 | { 86 | action = 'Delete, 87 | value = null 88 | }, 89 | 'Literal => 90 | { 91 | action = 'KeepStop, 92 | value = field.value 93 | }, 94 | 'Reference => 95 | { 96 | action = 'KeepStop, 97 | value = resolve_reference field.value 98 | }, 99 | 'ProviderComputed => 100 | if path == field.path then 101 | { action = 'Delete, value = null } 102 | else 103 | { 104 | action = 'KeepStop, 105 | value = resolve_reference field.path 106 | } 107 | }, 108 | go | Array String -> Dyn -> { action : [| 'KeepStop, 'Continue, 'Delete |], value : Dyn } 109 | = fun path field => 110 | typeof field 111 | |> match { 112 | 'TerraformField => resolve_field_action path field, 113 | 'Array => 114 | { 115 | action = 'KeepStop, 116 | value = std.array.map resolve_provider_computed field 117 | }, 118 | 'Other => 119 | { 120 | action = 'Continue, 121 | value = field 122 | } 123 | }, 124 | } 125 | in 126 | fun v => 127 | if is_terraform_field_record v then 128 | (resolve_field_action [] v).value 129 | else 130 | deep_filter_map go v, 131 | 132 | remove_empty_records = 133 | let { go, .. } = { 134 | go_field : { _ : Dyn } -> String -> { _ : Dyn } = fun r f => 135 | if !std.is_record r."%{f}" then 136 | r 137 | else 138 | let new_field = go (r."%{f}" | { _ : Dyn }) in 139 | if new_field == {} then 140 | std.record.remove f r 141 | else 142 | std.record.update f (new_field | Dyn) r, 143 | go : { _ : Dyn } -> { _ : Dyn } 144 | = fun r => r |> std.record.fields |> std.array.fold_left go_field r, 145 | } 146 | in go, 147 | 148 | is_defined_terraform_field_record | Dyn -> Bool 149 | = fun x => is_terraform_field_record x && x.terraform_field_type != 'Undefined, 150 | 151 | has_defined_field | String -> { _ : Dyn } -> Bool 152 | = fun name r => std.record.has_field name r && is_defined_terraform_field_record r."%{name}", 153 | 154 | has_defined_field_path | Array String -> { _ : Dyn } -> Bool 155 | = fun path r => 156 | if std.array.length path == 1 then 157 | has_defined_field (std.array.first path) r 158 | else 159 | let field = 160 | std.array.first path 161 | in 162 | std.record.has_field field r 163 | && std.is_record r."%{field}" 164 | && has_defined_field_path (std.array.drop_first path) r."%{field}", 165 | 166 | FieldDescriptor = { 167 | prio | [| 'Default, 'Force |], 168 | path | Array String, 169 | .. 170 | }, 171 | 172 | ElaboratedField = { 173 | prio | [| 'Default, 'Force |], 174 | path | Array String, 175 | record | { _ : Dyn }, 176 | }, 177 | 178 | mergeable | { _ : Dyn } -> FieldDescriptor -> Bool 179 | = fun r field => !(has_defined_field_path field.path r && field.prio == 'Default), 180 | 181 | elaborate_field | Array String -> { _ : Dyn } -> FieldDescriptor -> Array ElaboratedField 182 | = 183 | let { candidates, filter_mergeable, elaborate, .. } = { 184 | candidates | { _ : Dyn } -> Array String -> FieldDescriptor -> Array FieldDescriptor 185 | = fun r prefix field => 186 | let head = std.array.first field.path in 187 | let tail = std.array.drop_first field.path in 188 | if head == "_" then 189 | std.record.fields r 190 | |> std.array.map 191 | ( 192 | fun name => 193 | candidates 194 | r 195 | prefix 196 | { 197 | prio = field.prio, 198 | path = [name] @ tail 199 | } 200 | ) 201 | |> std.array.flatten 202 | else if std.array.length field.path == 1 then 203 | [ 204 | { 205 | prio = field.prio, 206 | path = prefix @ [head] 207 | } 208 | ] 209 | else 210 | let r' = 211 | if std.record.has_field head r then r."%{head}" else {} 212 | in candidates r' (prefix @ [head]) { prio = field.prio, path = tail }, 213 | 214 | filter_mergeable | { _ : Dyn } -> Array FieldDescriptor -> Array FieldDescriptor 215 | = fun r => std.array.filter (fun field => mergeable r field), 216 | 217 | elaborate | Array String -> FieldDescriptor -> ElaboratedField 218 | = fun prefix field => 219 | { 220 | prio = field.prio, 221 | path = field.path, 222 | record = elaborated_record prefix field.path field 223 | }, 224 | 225 | elaborated_record | Array String -> Array String -> FieldDescriptor -> { _ : Dyn } 226 | = fun prefix acc field => 227 | let head = std.array.first acc in 228 | if std.array.length acc > 1 then 229 | { "%{head}" = elaborated_record prefix (std.array.drop_first acc) field, .. } 230 | else 231 | { "%{head}" | force = provider_computed (prefix @ field.path), .. }, 232 | } 233 | in fun prefix r field => candidates r [] field |> filter_mergeable r |> std.array.map (elaborate prefix), 234 | 235 | merge_elaborated | { _ : Dyn } -> ElaboratedField -> { _ : Dyn } 236 | = fun record field => 237 | if mergeable record field then 238 | record & field.record 239 | else 240 | record, 241 | 242 | ComputedFields | Array String -> Array FieldDescriptor -> Dyn -> Dyn -> Dyn 243 | = fun prefix fields _l r => fields |> std.array.map (elaborate_field prefix r) |> std.array.flatten |> std.array.fold_left merge_elaborated r, 244 | } 245 | 246 | -------------------------------------------------------------------------------- /nix/devshell.nix: -------------------------------------------------------------------------------- 1 | { lib, writeShellScriptBin, terraform, generateSchema, nickel }: 2 | { 3 | # A function returning an attrset mapping local names for providers to provider derivations from nixpkgs 4 | # It will be passed an attrset with all available providers from nixpkgs 5 | providers 6 | , extraNickelInput ? "" 7 | }: 8 | let 9 | ncl-schema = generateSchema providers; 10 | terraform-with-plugins = terraform.withPlugins (p: lib.attrValues (providers p)); 11 | in 12 | { 13 | terraform = terraform-with-plugins; 14 | 15 | link-schema = writeShellScriptBin "link-schema" '' 16 | set -e 17 | ln -sf ${ncl-schema} tf-ncl-schema.ncl 18 | ''; 19 | 20 | run-nickel = writeShellScriptBin "run-nickel" '' 21 | set -e 22 | link-schema 23 | ${nickel}/bin/nickel export > main.tf.json < $out 5 | '' 6 | -------------------------------------------------------------------------------- /nix/terraform_schema.nix: -------------------------------------------------------------------------------- 1 | providers: 2 | { lib, runCommand, formats, terraform, cacert, schema-merge }: 3 | let 4 | required_providers = providers: lib.mapAttrs 5 | (_: p: { 6 | inherit (p) version; 7 | source = lib.toLower p.provider-source-address; 8 | }) 9 | providers; 10 | 11 | retrieveProviderSchema = name: provider: 12 | let 13 | mainJson = (formats.json { }).generate "main.tf.json" { 14 | terraform.required_providers = required_providers { "${name}" = provider; }; 15 | }; 16 | 17 | terraform-with-plugins = terraform.withPlugins (_: [ provider ]); 18 | in 19 | runCommand "${name}.json" { } '' 20 | cp ${mainJson} main.tf.json 21 | ${terraform-with-plugins}/bin/terraform init 22 | ${terraform-with-plugins}//bin/terraform providers schema -json >$out 23 | ''; 24 | 25 | providersJson = (formats.json { }).generate "providers.json" (required_providers providers); 26 | in 27 | runCommand "schemas" { } '' 28 | mkdir schemas 29 | ${lib.concatStringsSep "\n" (lib.mapAttrsToList 30 | (name: provider: '' 31 | ln -s ${retrieveProviderSchema name provider} schemas/"${name}.json" 32 | '') 33 | providers)} 34 | ln -s ${providersJson} providers.json 35 | 36 | mkdir -p $out 37 | ln -s ${providersJson} $out/providers.json 38 | ${schema-merge}/bin/schema-merge . > $out/schema.json 39 | '' 40 | -------------------------------------------------------------------------------- /schema-merge/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tweag/tf-ncl/schema-merge 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/hashicorp/go-version v1.6.0 7 | github.com/hashicorp/hcl-lang v0.0.0-20221207101932-6bf6bd83e2bc 8 | github.com/hashicorp/terraform-json v0.14.0 9 | github.com/hashicorp/terraform-registry-address v0.1.0 10 | github.com/hashicorp/terraform-schema v0.0.0-20221201104729-8c89111ff59a 11 | github.com/zclconf/go-cty v1.12.1 12 | ) 13 | 14 | require ( 15 | github.com/agext/levenshtein v1.2.3 // indirect 16 | github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect 17 | github.com/hashicorp/errwrap v1.1.0 // indirect 18 | github.com/hashicorp/go-multierror v1.1.1 // indirect 19 | github.com/hashicorp/hcl/v2 v2.15.0 // indirect 20 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect 21 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 22 | golang.org/x/net v0.17.0 // indirect 23 | golang.org/x/text v0.13.0 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /schema-merge/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= 3 | github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 4 | github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= 5 | github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= 6 | github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 14 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 15 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 16 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 17 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 18 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 19 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 20 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 21 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 22 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 23 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 24 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 25 | github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 26 | github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= 27 | github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 28 | github.com/hashicorp/hcl-lang v0.0.0-20221207101932-6bf6bd83e2bc h1:bLoQmlAUJKptQSKVQR3POigsjuTNF4KRkiecZ4pIwqs= 29 | github.com/hashicorp/hcl-lang v0.0.0-20221207101932-6bf6bd83e2bc/go.mod h1:NR4/o2Ur8BItrAtCw6dH5xVpPitcr0D2epIoibezXuU= 30 | github.com/hashicorp/hcl/v2 v2.15.0 h1:CPDXO6+uORPjKflkWCCwoWc9uRp+zSIPcCQ+BrxV7m8= 31 | github.com/hashicorp/hcl/v2 v2.15.0/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng= 32 | github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= 33 | github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= 34 | github.com/hashicorp/terraform-registry-address v0.1.0 h1:W6JkV9wbum+m516rCl5/NjKxCyTVaaUBbzYcMzBDO3U= 35 | github.com/hashicorp/terraform-registry-address v0.1.0/go.mod h1:EnyO2jYO6j29DTHbJcm00E5nQTFeTtyZH3H5ycydQ5A= 36 | github.com/hashicorp/terraform-schema v0.0.0-20221201104729-8c89111ff59a h1:sNA2mF+X9Qx3LLooRo770bLCwuGIp7BhFwYMXmZoqrc= 37 | github.com/hashicorp/terraform-schema v0.0.0-20221201104729-8c89111ff59a/go.mod h1:XnJ9GVulEQnQeiyotSJEEyqHbPFXiaFovCxC4jqcacU= 38 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= 39 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= 40 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 41 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 42 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 43 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 44 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 45 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 46 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 47 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 48 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 49 | github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= 50 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 51 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 52 | github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 53 | github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= 54 | github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= 55 | github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= 56 | github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= 57 | github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= 58 | github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= 59 | github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= 60 | github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= 61 | github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= 62 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 63 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 64 | golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 65 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 66 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 67 | golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 68 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 69 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 70 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 71 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 72 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 73 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 74 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 75 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 76 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 77 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 78 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 79 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 80 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 81 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 82 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 83 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 84 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 85 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 86 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 87 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 88 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 89 | -------------------------------------------------------------------------------- /schema-merge/intermediate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type Attribute struct { 8 | Description string `json:"description"` 9 | Optional bool `json:"optional"` 10 | Computed bool `json:"computed"` 11 | Type Type `json:"type"` 12 | } 13 | 14 | type FieldDescriptor struct { 15 | Force bool `json:"force"` 16 | Path []string `json:"path"` 17 | } 18 | 19 | type TypeTag uint64 20 | 21 | const ( 22 | Dynamic = iota 23 | String 24 | Number 25 | Bool 26 | List 27 | Object 28 | Dictionary 29 | ) 30 | 31 | func (t TypeTag) String() string { 32 | switch t { 33 | case Dynamic: 34 | return "Dynamic" 35 | case String: 36 | return "String" 37 | case Number: 38 | return "Number" 39 | case Bool: 40 | return "Bool" 41 | case List: 42 | return "List" 43 | case Object: 44 | return "Object" 45 | case Dictionary: 46 | return "Dictionary" 47 | } 48 | return "unknown" 49 | } 50 | 51 | type Type struct { 52 | Tag TypeTag 53 | MinItems *uint64 `json:"min,omitempty"` 54 | MaxItems *uint64 `json:"max,omitempty"` 55 | Content *Type `json:"content,omitempty"` 56 | Open bool `json:"open,omitempty"` 57 | Object *map[string]Attribute `json:"object,omitempty"` 58 | } 59 | 60 | type ListVariant struct { 61 | MinItems *uint64 `json:"min,omitempty"` 62 | MaxItems *uint64 `json:"max,omitempty"` 63 | Content *Type `json:"content,omitempty"` 64 | } 65 | 66 | type ObjectVariant struct { 67 | Open bool `json:"open"` 68 | Content *map[string]Attribute `json:"content,omitempty"` 69 | } 70 | 71 | func (t Type) MarshalJSON() (b []byte, e error) { 72 | switch t.Tag { 73 | case List: 74 | return json.Marshal(struct { 75 | List ListVariant 76 | }{ 77 | List: ListVariant{ 78 | MinItems: t.MinItems, 79 | MaxItems: t.MaxItems, 80 | Content: t.Content, 81 | }, 82 | }) 83 | case Object: 84 | return json.Marshal(struct { 85 | Object ObjectVariant 86 | }{ 87 | Object: ObjectVariant{ 88 | Open: t.Open, 89 | Content: t.Object, 90 | }, 91 | }) 92 | case Dictionary: 93 | return json.Marshal(struct { 94 | Dictionary *Type 95 | }{ 96 | Dictionary: t.Content, 97 | }) 98 | default: 99 | return json.Marshal(t.Tag.String()) 100 | } 101 | } 102 | 103 | func merge_objects(os ...map[string]Attribute) map[string]Attribute { 104 | res := map[string]Attribute{} 105 | for _, o := range os { 106 | for k, v := range o { 107 | res[k] = v 108 | } 109 | } 110 | return res 111 | } 112 | -------------------------------------------------------------------------------- /schema-merge/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "os" 8 | 9 | // 10 | "github.com/hashicorp/hcl-lang/lang" 11 | "github.com/hashicorp/hcl-lang/schema" 12 | "github.com/hashicorp/terraform-schema/module" 13 | tfschema "github.com/hashicorp/terraform-schema/schema" 14 | "github.com/zclconf/go-cty/cty" 15 | ) 16 | 17 | func convert_primitive_type(t cty.Type) Type { 18 | switch { 19 | case t == cty.String: 20 | return Type{Tag: String} 21 | case t == cty.Number: 22 | return Type{Tag: Number} 23 | case t == cty.Bool: 24 | return Type{Tag: Bool} 25 | } 26 | return Type{Tag: Dynamic} 27 | } 28 | 29 | func has_object_type(computed_fields *[]FieldDescriptor, path []string, ecs schema.ExprConstraints) (bool, *Type) { 30 | for _, ec := range ecs { 31 | switch c := ec.(type) { 32 | case schema.ObjectExpr: 33 | obj := map[string]Attribute{} 34 | for k, v := range c.Attributes { 35 | obj[k] = convert_attribute(computed_fields, append(path, k), v) 36 | } 37 | return true, &Type{ 38 | Tag: Object, 39 | Open: false, 40 | Object: &obj, 41 | } 42 | } 43 | } 44 | return false, nil 45 | } 46 | 47 | func has_list_type(computed_fields *[]FieldDescriptor, path []string, ecs schema.ExprConstraints) (bool, *Type) { 48 | for _, ec := range ecs { 49 | switch c := ec.(type) { 50 | case schema.SetExpr: 51 | // TODO(vkleen) sets don't play well with the path concept 52 | content := extract_type(computed_fields, path, c.Elem) 53 | 54 | var min_items, max_items *uint64 55 | if c.MinItems != 0 { 56 | min_items = &c.MinItems 57 | } 58 | if c.MaxItems != 0 { 59 | max_items = &c.MaxItems 60 | } 61 | 62 | return true, &Type{ 63 | Tag: List, 64 | MinItems: min_items, 65 | MaxItems: max_items, 66 | Content: &content, 67 | } 68 | case schema.ListExpr: 69 | // TODO(vkleen) lists don't play well with the path concept 70 | content := extract_type(computed_fields, path, c.Elem) 71 | 72 | var min_items, max_items *uint64 73 | if c.MinItems != 0 { 74 | min_items = &c.MinItems 75 | } 76 | if c.MaxItems != 0 { 77 | max_items = &c.MaxItems 78 | } 79 | 80 | return true, &Type{ 81 | Tag: List, 82 | MinItems: min_items, 83 | MaxItems: max_items, 84 | Content: &content, 85 | } 86 | } 87 | } 88 | return false, nil 89 | } 90 | 91 | // TODO(vkleen) Handle enumerations 92 | func extract_type(computed_fields *[]FieldDescriptor, path []string, ecs schema.ExprConstraints) Type { 93 | if is_object, t := has_object_type(computed_fields, path, ecs); is_object { 94 | return *t 95 | } 96 | 97 | // FIXME(vkleen): the Nickel contracts don't react well to lists in the path; if we're going to emit an Array contract, we need to censor computed fields below it for now 98 | if is_list, t := has_list_type(&[]FieldDescriptor{}, path, ecs); is_list { 99 | return *t 100 | } 101 | 102 | for _, ec := range ecs { 103 | switch c := ec.(type) { 104 | case schema.LiteralTypeExpr: 105 | switch { 106 | case c.Type.IsPrimitiveType(): 107 | return convert_primitive_type(c.Type) 108 | } 109 | return Type{Tag: Dynamic} 110 | } 111 | } 112 | return Type{Tag: Dynamic} 113 | } 114 | 115 | func extract_optional_computed(computed_fields *[]FieldDescriptor, path []string, as *schema.AttributeSchema) (bool, bool) { 116 | optional := as.IsOptional 117 | // Terraform treats `id` fields specially 118 | if path[len(path)-1] == "id" { 119 | optional = false 120 | } 121 | 122 | switch { 123 | case optional && !as.IsRequired && !as.IsComputed: 124 | return true, false 125 | case !optional && as.IsRequired && !as.IsComputed: 126 | // FIXME(vkleen): optional fields together with computed fields in the same record break the Nickel contracts 127 | return true, false 128 | case !optional && !as.IsRequired && as.IsComputed: 129 | *computed_fields = append(*computed_fields, FieldDescriptor{ 130 | Force: true, 131 | Path: append([]string(nil), path...), 132 | }) 133 | // FIXME(vkleen): optional fields together with computed fields in the same record break the Nickel contracts 134 | return true, true 135 | case optional && !as.IsRequired && as.IsComputed: 136 | *computed_fields = append(*computed_fields, FieldDescriptor{ 137 | Force: false, 138 | Path: append([]string(nil), path...), 139 | }) 140 | return true, true 141 | } 142 | return true, false 143 | } 144 | 145 | func convert_attribute(computed_fields *[]FieldDescriptor, path []string, as *schema.AttributeSchema) Attribute { 146 | t := extract_type(computed_fields, path, as.Expr) 147 | 148 | var o, c bool 149 | // FIXME(vkleen) Mark lists as "not computed" because of limitations in the Nickel contracts 150 | if t.Tag == List { 151 | o, c = true, false 152 | } else { 153 | o, c = extract_optional_computed(computed_fields, path, as) 154 | } 155 | 156 | attr := Attribute{ 157 | Description: as.Description.Value, 158 | Optional: o, 159 | Computed: c, 160 | Type: t, 161 | } 162 | return attr 163 | } 164 | 165 | type DependentBody struct { 166 | key schema.DependencyKeys 167 | schema *schema.BodySchema 168 | } 169 | 170 | func dependency_keys(bs *schema.BlockSchema) []DependentBody { 171 | ret := []DependentBody{} 172 | for sk, b := range bs.DependentBody { 173 | var dk schema.DependencyKeys 174 | json.Unmarshal([]byte(sk), &dk) 175 | ret = append(ret, DependentBody{ 176 | key: dk, 177 | schema: b, 178 | }) 179 | } 180 | return ret 181 | } 182 | 183 | type Label struct { 184 | possible_values map[string]schema.BodySchema 185 | wildcard bool 186 | } 187 | 188 | func classify_labels(bs *schema.BlockSchema) []Label { 189 | ret := []Label{} 190 | for i, l := range bs.Labels { 191 | if l.Completable { 192 | possible_values := map[string]schema.BodySchema{} 193 | for _, dk := range dependency_keys(bs) { 194 | for _, l := range dk.key.Labels { 195 | if l.Index == i { 196 | possible_values[l.Value] = *dk.schema 197 | } 198 | } 199 | } 200 | ret = append(ret, Label{possible_values: possible_values, wildcard: false}) 201 | } else { 202 | ret = append(ret, Label{possible_values: map[string]schema.BodySchema{}, wildcard: true}) 203 | } 204 | } 205 | return ret 206 | } 207 | 208 | func includes_wildcard_label(labels []Label) bool { 209 | for _, v := range labels { 210 | if v.wildcard { 211 | return true 212 | } 213 | } 214 | return false 215 | } 216 | 217 | func starts_with(pattern []string, path []string) bool { 218 | if len(path) < len(pattern) { 219 | return false 220 | } 221 | 222 | for i, v := range pattern { 223 | if v != path[i] { 224 | return false 225 | } 226 | } 227 | 228 | return true 229 | } 230 | 231 | func fixup_block_type(path []string, labels []Label, bs *schema.BlockSchema) schema.BlockType { 232 | t := bs.Type 233 | 234 | if len(path) == 1 && path[0] == "terraform" { 235 | t = schema.BlockTypeObject 236 | } 237 | 238 | if len(path) == 3 && starts_with([]string{"terraform", "backend"}, path) { 239 | t = schema.BlockTypeObject 240 | } 241 | 242 | has_wildcard := includes_wildcard_label(labels) 243 | if t == schema.BlockTypeNil && !has_wildcard { 244 | t = schema.BlockTypeList 245 | } else if t == schema.BlockTypeNil && has_wildcard { 246 | t = schema.BlockTypeObject 247 | } 248 | 249 | if bs.MaxItems == 1 { 250 | t = schema.BlockTypeObject 251 | } 252 | 253 | return t 254 | } 255 | 256 | func wrap_block_type(path []string, labels []Label, bs *schema.BlockSchema, t schema.BlockType, attr Attribute) Attribute { 257 | switch t { 258 | case schema.BlockTypeObject: 259 | return attr 260 | case schema.BlockTypeList, schema.BlockTypeSet: 261 | return Attribute{ 262 | Description: attr.Description, 263 | Optional: attr.Optional, 264 | Computed: attr.Computed, 265 | Type: Type{ 266 | Tag: List, 267 | MinItems: &bs.MinItems, 268 | MaxItems: &bs.MaxItems, 269 | Content: &attr.Type, 270 | }, 271 | } 272 | default: 273 | panic(errors.New(fmt.Sprint("Unknown block type ", t.GoString()))) 274 | } 275 | } 276 | 277 | var special_open_block_paths = [...][]string{{"data"}, {"resource"}, {"provider"}, {"terraform", "backend"}, {"module", "_"}} 278 | 279 | func check_candidate(path []string, candidate []string) bool { 280 | if len(candidate) != len(path) { 281 | return false 282 | } 283 | for i := range candidate { 284 | if candidate[i] != path[i] { 285 | return false 286 | } 287 | } 288 | return true 289 | } 290 | 291 | func should_be_open(path []string) bool { 292 | for _, candidate := range special_open_block_paths { 293 | if check_candidate(path, candidate) { 294 | return true 295 | } 296 | } 297 | return false 298 | } 299 | 300 | func assemble_blocks(computed_fields *[]FieldDescriptor, path []string, bs *schema.BlockSchema, all_labels []Label, labels []Label, accumulated_bodies []*schema.BodySchema) Attribute { 301 | if len(labels) == 0 { 302 | t := fixup_block_type(path, all_labels, bs) 303 | 304 | // FIXME(vkleen): the Nickel contracts don't react well to lists in the path; if we're going to emit an Array contract, we need to censor computed fields below it for now 305 | var obj map[string]Attribute 306 | if t == schema.BlockTypeList || t == schema.BlockTypeSet { 307 | obj = assemble_bodies(&[]FieldDescriptor{}, path, accumulated_bodies...) 308 | } else { 309 | obj = assemble_bodies(computed_fields, path, accumulated_bodies...) 310 | } 311 | 312 | description := bs.Description.Value 313 | if len(accumulated_bodies) > 0 { 314 | last_description := accumulated_bodies[len(accumulated_bodies)-1].Description 315 | if last_description.Kind != lang.NilKind { 316 | description = last_description.Value 317 | } 318 | } 319 | return wrap_block_type(path, all_labels, bs, t, Attribute{ 320 | Description: description, 321 | // TODO(vkleen): compute these values properly 322 | Optional: true, 323 | Computed: false, 324 | Type: Type{ 325 | Tag: Object, 326 | Open: should_be_open(path), 327 | Object: &obj, 328 | }, 329 | }) 330 | } 331 | l := labels[0] 332 | if l.wildcard { 333 | t := assemble_blocks(computed_fields, append(path, "_"), bs, all_labels, labels[1:], accumulated_bodies).Type 334 | return Attribute{ 335 | Description: bs.Description.Value, 336 | Optional: true, 337 | Computed: false, 338 | Type: Type{ 339 | Tag: Dictionary, 340 | Content: &t, 341 | }, 342 | } 343 | } else { 344 | obj := map[string]Attribute{} 345 | for k, v := range l.possible_values { 346 | obj[k] = assemble_blocks(computed_fields, append(path, k), bs, all_labels, labels[1:], append(accumulated_bodies, &v)) 347 | } 348 | return Attribute{ 349 | Description: bs.Description.Value, 350 | // TODO(vkleen): compute these values properly 351 | Optional: true, 352 | Computed: false, 353 | Type: Type{ 354 | Tag: Object, 355 | Open: should_be_open(path), 356 | Object: &obj, 357 | }, 358 | } 359 | } 360 | } 361 | 362 | func convert_block(computed_fields *[]FieldDescriptor, path []string, bs *schema.BlockSchema) Attribute { 363 | if bs.Body != nil && bs.Body.AnyAttribute != nil { 364 | if bs.Body.Blocks != nil || bs.Body.Attributes != nil { 365 | panic("Don't know how to handle AnyAttribute together with explicit attributes") 366 | } 367 | t := convert_attribute(computed_fields, append(path, "_"), bs.Body.AnyAttribute).Type 368 | return Attribute{ 369 | Description: bs.Description.Value, 370 | // TODO(vkleen): compute these values properly 371 | Optional: true, 372 | Computed: false, 373 | Type: Type{ 374 | Tag: Dictionary, 375 | Content: &t, 376 | }, 377 | } 378 | } 379 | 380 | bodies := []*schema.BodySchema{} 381 | if bs.Body != nil { 382 | bodies = []*schema.BodySchema{bs.Body} 383 | } 384 | 385 | labels := classify_labels(bs) 386 | return assemble_blocks(computed_fields, path, bs, labels, labels, bodies) 387 | } 388 | 389 | func assemble_bodies(computed_fields *[]FieldDescriptor, path []string, bs ...*schema.BodySchema) map[string]Attribute { 390 | schemas := []map[string]Attribute{} 391 | for _, b := range bs { 392 | schemas = append(schemas, assemble_body(computed_fields, path, b)) 393 | } 394 | return merge_objects(schemas...) 395 | } 396 | 397 | func assemble_body(computed_fields *[]FieldDescriptor, path []string, bs *schema.BodySchema) map[string]Attribute { 398 | schema := make(map[string]Attribute) 399 | for key, attr := range bs.Attributes { 400 | schema[key] = convert_attribute(computed_fields, append(path, key), attr) 401 | } 402 | 403 | for key, block := range bs.Blocks { 404 | schema[key] = convert_block(computed_fields, append(path, key), block) 405 | } 406 | 407 | return schema 408 | } 409 | 410 | func main() { 411 | if len(os.Args) < 2 { 412 | panic("No provider schema directory passed") 413 | } 414 | 415 | schema_dir := os.Args[1] 416 | store, e := NewSchemaStore(os.DirFS(schema_dir)) 417 | if e != nil { 418 | panic(e) 419 | } 420 | 421 | coreSchema, err := tfschema.CoreModuleSchemaForVersion(tfschema.LatestAvailableVersion) 422 | if err != nil { 423 | panic(err) 424 | } 425 | 426 | sm := tfschema.NewSchemaMerger(coreSchema) 427 | sm.SetSchemaReader(store) 428 | 429 | tf_schema, e := sm.SchemaForModule(&module.Meta{ 430 | ProviderRequirements: store.ProviderReqs(), 431 | ProviderReferences: store.ProviderRefs(), 432 | }) 433 | if e != nil { 434 | panic(e) 435 | } 436 | 437 | computed_fields := []FieldDescriptor{} 438 | assembled_schema := assemble_body(&computed_fields, []string{}, tf_schema) 439 | json, err := json.Marshal(struct { 440 | ComputedFields []FieldDescriptor `json:"computed_fields"` 441 | Schema map[string]Attribute `json:"schema"` 442 | }{ 443 | ComputedFields: computed_fields, 444 | Schema: assembled_schema, 445 | }) 446 | if err != nil { 447 | panic(err) 448 | } 449 | fmt.Println(string(json)) 450 | } 451 | -------------------------------------------------------------------------------- /schema-merge/schema_store.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/fs" 8 | "path" 9 | 10 | version "github.com/hashicorp/go-version" 11 | tfjson "github.com/hashicorp/terraform-json" 12 | tfaddr "github.com/hashicorp/terraform-registry-address" 13 | tfmodule "github.com/hashicorp/terraform-schema/module" 14 | tfschema "github.com/hashicorp/terraform-schema/schema" 15 | ) 16 | 17 | type ProviderSpec struct { 18 | Source string `json:"source"` 19 | Version string `json:"version"` 20 | } 21 | 22 | type Provider struct { 23 | Version *version.Version 24 | SchemaReader io.Reader 25 | } 26 | 27 | func NewProvider(spec ProviderSpec, reader io.Reader) (*Provider, error) { 28 | version, e := version.NewVersion(spec.Version) 29 | if e != nil { 30 | return nil, e 31 | } 32 | 33 | return &Provider{ 34 | Version: version, 35 | SchemaReader: reader, 36 | }, nil 37 | } 38 | 39 | type SchemaStore struct { 40 | local_names map[string]tfaddr.Provider 41 | providers map[tfaddr.Provider]Provider 42 | } 43 | 44 | func NewSchemaStore(fsys fs.FS) (*SchemaStore, error) { 45 | specs_bytes, e := fs.ReadFile(fsys, "providers.json") 46 | if e != nil { 47 | return nil, e 48 | } 49 | 50 | var specs map[string]ProviderSpec 51 | e = json.Unmarshal(specs_bytes, &specs) 52 | if e != nil { 53 | return nil, e 54 | } 55 | 56 | providers := map[tfaddr.Provider]Provider{} 57 | local_names := map[string]tfaddr.Provider{} 58 | for name, spec := range specs { 59 | k, e := tfaddr.ParseProviderSource(spec.Source) 60 | if e != nil { 61 | return nil, e 62 | } 63 | local_names[name] = k 64 | 65 | reader, e := fsys.Open(path.Join("schemas", fmt.Sprintf("%s.json", name))) 66 | if e != nil { 67 | return nil, e 68 | } 69 | 70 | p, e := NewProvider(spec, reader) 71 | if e != nil { 72 | return nil, e 73 | } 74 | 75 | providers[k] = *p 76 | } 77 | return &SchemaStore{ 78 | local_names: local_names, 79 | providers: providers, 80 | }, nil 81 | } 82 | 83 | func (s *SchemaStore) ProviderReqs() map[tfaddr.Provider]version.Constraints { 84 | ret := map[tfaddr.Provider]version.Constraints{} 85 | for k, v := range s.providers { 86 | ret[k] = version.MustConstraints(version.NewConstraint(fmt.Sprintf("=%s", v.Version))) 87 | } 88 | return ret 89 | } 90 | 91 | func (s *SchemaStore) ProviderRefs() map[tfmodule.ProviderRef]tfaddr.Provider { 92 | ret := map[tfmodule.ProviderRef]tfaddr.Provider{} 93 | for k, v := range s.local_names { 94 | ret[tfmodule.ProviderRef{LocalName: k}] = v 95 | } 96 | return ret 97 | } 98 | 99 | func (s *SchemaStore) ProviderSchema(modPath string, addr tfaddr.Provider, vc version.Constraints) (*tfschema.ProviderSchema, error) { 100 | version := s.providers[addr].Version 101 | if !vc.Check(version) { 102 | // the terraform-schema SchemaMerger doesn't actually care if we return an error 103 | panic("Incompatible provider version requested") 104 | } 105 | 106 | var jsonSchemas tfjson.ProviderSchemas 107 | e := json.NewDecoder(s.providers[addr].SchemaReader).Decode(&jsonSchemas) 108 | if e != nil { 109 | // the terraform-schema SchemaMerger doesn't actually care if we return an error 110 | panic(e) 111 | } 112 | 113 | ps, ok := jsonSchemas.Schemas[addr.String()] 114 | if !ok { 115 | // the terraform-schema SchemaMerger doesn't actually care if we return an error 116 | panic(fmt.Errorf("%q: schema not found", addr)) 117 | } 118 | 119 | schema := tfschema.ProviderSchemaFromJson(ps, addr) 120 | return schema, nil 121 | } 122 | -------------------------------------------------------------------------------- /tf-ncl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tf-ncl" 3 | version = "0.1.0" 4 | authors = ["vkleen"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | clap = { version = "4.0.4", features = ["cargo", "derive"] } 9 | anyhow = "1.0" 10 | serde_json = { version = "1.0", features = ["unbounded_depth"] } 11 | serde = { version = "1.0", features = ["derive"] } 12 | nickel-lang-core = "0.10.0" 13 | pretty = "0.11" 14 | codespan = "0.11.1" 15 | 16 | [dev-dependencies] 17 | pretty_assertions = "1.3" 18 | -------------------------------------------------------------------------------- /tf-ncl/src/intermediate.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Deserializer}; 4 | 5 | #[derive(Debug, Clone, Deserialize)] 6 | pub struct GoSchema { 7 | pub computed_fields: Vec, 8 | pub schema: HashMap, 9 | } 10 | 11 | #[derive(Debug, Clone, Deserialize)] 12 | pub struct Attribute { 13 | pub description: Option, 14 | pub optional: bool, 15 | pub computed: bool, 16 | #[serde(rename = "type")] 17 | pub type_: Type, 18 | } 19 | 20 | #[derive(Debug, Clone, Deserialize)] 21 | pub struct FieldDescriptor { 22 | pub force: bool, 23 | pub path: Vec, 24 | } 25 | 26 | #[derive(Debug, Clone, Deserialize)] 27 | pub enum Type { 28 | Dynamic, 29 | String, 30 | Number, 31 | Bool, 32 | List { 33 | min: Option, 34 | max: Option, 35 | content: Box, 36 | }, 37 | Object { 38 | open: bool, 39 | content: HashMap, 40 | }, 41 | #[serde(deserialize_with = "transparent")] 42 | Dictionary { 43 | inner: Box, 44 | prefix: Vec, 45 | computed_fields: Vec, 46 | }, 47 | } 48 | 49 | // The very complex return type is required to make serde happy. 50 | #[allow(clippy::type_complexity)] 51 | fn transparent<'de, D>(deser: D) -> Result<(Box, Vec, Vec), D::Error> 52 | where 53 | D: Deserializer<'de>, 54 | { 55 | as Deserialize>::deserialize(deser).map(|inner| (inner, vec![], vec![])) 56 | } 57 | 58 | #[derive(Deserialize, Debug)] 59 | pub struct Providers(pub HashMap); 60 | 61 | #[derive(Deserialize, Debug)] 62 | pub struct ProviderConfig { 63 | pub source: String, 64 | pub version: String, 65 | } 66 | 67 | pub struct WithProviders { 68 | pub providers: Providers, 69 | pub data: T, 70 | } 71 | 72 | pub trait IntoWithProviders 73 | where 74 | Self: Sized, 75 | { 76 | fn with_providers(self, providers: Providers) -> WithProviders; 77 | } 78 | 79 | impl IntoWithProviders for T { 80 | fn with_providers(self, providers: Providers) -> WithProviders { 81 | WithProviders { 82 | providers, 83 | data: self, 84 | } 85 | } 86 | } 87 | 88 | fn attribute_at_path<'a>( 89 | schema: &'a mut HashMap, 90 | path: &[String], 91 | ) -> Option<&'a mut Attribute> { 92 | let mut obj = schema; 93 | for p in path.split_last().map(|x| x.1).unwrap_or(&[]) { 94 | obj = obj.get_mut(p).and_then(|attr| match &mut attr.type_ { 95 | Type::Object { open: _, content } => Some(content), 96 | _ => None, 97 | })?; 98 | } 99 | obj.get_mut(path.last()?) 100 | } 101 | 102 | impl FieldDescriptor { 103 | fn split_at_first_wildcard(&self) -> (&[String], &[String]) { 104 | let first_wildcard = self 105 | .path 106 | .iter() 107 | .position(|x| x == "_") 108 | .unwrap_or(self.path.len()); 109 | self.path.split_at(first_wildcard) 110 | } 111 | 112 | fn push_down(self, schema: &mut HashMap) -> Option { 113 | let (prefix, rest) = self.split_at_first_wildcard(); 114 | let Some(attr) = attribute_at_path(schema, prefix) else { 115 | return Some(self); 116 | }; 117 | match &mut attr.type_ { 118 | Type::Dictionary { 119 | inner: _, 120 | prefix: prev_prefix, 121 | computed_fields, 122 | } => { 123 | if prev_prefix.is_empty() { 124 | *prev_prefix = prefix.to_vec(); 125 | } else if prev_prefix != prefix { 126 | return Some(self); 127 | } 128 | computed_fields.push(FieldDescriptor { 129 | path: rest.into(), 130 | ..self 131 | }); 132 | None 133 | } 134 | _ => panic!("Wildcard in field path doesn't correspond to dictionary"), 135 | } 136 | } 137 | } 138 | 139 | impl GoSchema { 140 | pub fn push_down_computed_fields(self) -> Self { 141 | let Self { 142 | computed_fields, 143 | mut schema, 144 | } = self; 145 | Self { 146 | computed_fields: computed_fields 147 | .into_iter() 148 | .filter_map(|f| f.push_down(&mut schema)) 149 | .collect(), 150 | schema, 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /tf-ncl/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod intermediate; 2 | pub mod nickel; 3 | -------------------------------------------------------------------------------- /tf-ncl/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use core::fmt; 3 | use nickel_lang_core::pretty::Allocator; 4 | use pretty::{BoxDoc, Pretty}; 5 | use serde::Deserialize; 6 | use std::{ 7 | io::{self, stdout, Read}, 8 | path::PathBuf, 9 | }; 10 | use tf_ncl::{ 11 | intermediate::{GoSchema, IntoWithProviders, Providers}, 12 | nickel::AsNickel, 13 | }; 14 | 15 | #[derive(Parser, Debug)] 16 | #[command(author, version, about, long_about = None)] 17 | struct Args { 18 | #[arg(value_name = "REQUIRED-PROVIDERS")] 19 | providers: PathBuf, 20 | #[arg(value_name = "TERRAFORM-SCHEMA")] 21 | schema: Option, 22 | } 23 | 24 | fn get_providers(opts: &Args) -> anyhow::Result { 25 | Ok(serde_json::from_reader(std::fs::File::open( 26 | &opts.providers, 27 | )?)?) 28 | } 29 | 30 | fn get_schema(opts: &Args) -> anyhow::Result { 31 | let schema_reader: Box = if let Some(path) = &opts.schema { 32 | Box::new(std::fs::File::open(path)?) 33 | } else { 34 | Box::new(std::io::stdin()) 35 | }; 36 | 37 | let mut deserializer = serde_json::Deserializer::from_reader(schema_reader); 38 | deserializer.disable_recursion_limit(); 39 | 40 | Ok(GoSchema::deserialize(&mut deserializer)?) 41 | } 42 | 43 | struct RenderableSchema<'a> { 44 | schema: BoxDoc<'a>, 45 | providers: BoxDoc<'a>, 46 | } 47 | 48 | impl<'a> RenderableSchema<'a> { 49 | fn render(&self, f: &mut impl io::Write) -> anyhow::Result<()> { 50 | let tfncl_lib = include_str!("../../ncl/lib.ncl"); 51 | 52 | write!( 53 | f, 54 | "{{ 55 | Config = {{ 56 | config | Schema, 57 | renderable_config = TfNcl.mkConfig config, 58 | .. 59 | }}, 60 | Schema = {schema}, 61 | TfNcl = {tfncl_lib} & {{ 62 | # The contract annotation can't be used until nickel#1056 is resolved 63 | mkConfig #| Schema -> {{_: Dyn}} 64 | = fun v => v |> TfNcl.resolve_provider_computed |> TfNcl.remove_empty_records, 65 | }}, 66 | required_providers = {required_providers} 67 | }}", 68 | schema = Display(&self.schema), 69 | required_providers = Display(&self.providers), 70 | )?; 71 | Ok(()) 72 | } 73 | } 74 | 75 | struct Display<'a, 'b>(&'b BoxDoc<'a>); 76 | 77 | impl<'a, 'b> fmt::Display for Display<'a, 'b> { 78 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 79 | self.0.render_fmt(80, f) 80 | } 81 | } 82 | 83 | fn main() -> anyhow::Result<()> { 84 | let opts = Args::parse(); 85 | 86 | let providers = get_providers(&opts)?; 87 | let go_schema = get_schema(&opts)?.push_down_computed_fields(); 88 | 89 | let alloc = Allocator::default(); 90 | let with_providers = go_schema.with_providers(providers); 91 | let doc = RenderableSchema { 92 | schema: with_providers.as_nickel().pretty(&alloc).into_doc(), 93 | providers: with_providers 94 | .providers 95 | .as_nickel() 96 | .pretty(&alloc) 97 | .into_doc(), 98 | }; 99 | 100 | doc.render(&mut stdout()) 101 | } 102 | -------------------------------------------------------------------------------- /tf-ncl/src/nickel.rs: -------------------------------------------------------------------------------- 1 | use crate::intermediate::{self, FieldDescriptor, GoSchema, Providers, WithProviders}; 2 | use nickel_lang_core::{ 3 | term::{ 4 | array::{Array, ArrayAttrs}, 5 | make::builder, 6 | MergePriority, RichTerm, Term, 7 | }, 8 | typ::{DictTypeFlavour, Type, TypeF}, 9 | }; 10 | 11 | pub trait AsNickel { 12 | fn as_nickel(&self) -> RichTerm; 13 | } 14 | 15 | impl AsNickel for WithProviders { 16 | fn as_nickel(&self) -> RichTerm { 17 | as_nickel_record(&self.data.schema) 18 | .path(["terraform", "required_providers"]) 19 | .value(self.providers.as_nickel()) 20 | .build() 21 | } 22 | } 23 | 24 | impl AsNickel for Providers { 25 | fn as_nickel(&self) -> RichTerm { 26 | use builder::*; 27 | Record::from(self.0.iter().map(|(name, provider)| { 28 | Field::name(name).value(Record::from([ 29 | Field::name("source") 30 | .priority(MergePriority::Bottom) 31 | .value(Term::Str(provider.source.clone().into())), 32 | Field::name("version") 33 | .priority(MergePriority::Bottom) 34 | .value(Term::Str(provider.version.clone().into())), 35 | ])) 36 | })) 37 | .build() 38 | } 39 | } 40 | 41 | impl AsNickel for Vec { 42 | fn as_nickel(&self) -> RichTerm { 43 | Term::Array( 44 | Array::from( 45 | self.iter() 46 | .map(|s| RichTerm::from(Term::Str(s.into()))) 47 | .collect(), 48 | ), 49 | ArrayAttrs::default(), 50 | ) 51 | .into() 52 | } 53 | } 54 | 55 | impl AsNickel for Vec { 56 | fn as_nickel(&self) -> RichTerm { 57 | Term::Array( 58 | self.iter().map(|x| x.as_nickel()).collect(), 59 | ArrayAttrs::default(), 60 | ) 61 | .into() 62 | } 63 | } 64 | 65 | impl AsNickel for FieldDescriptor { 66 | fn as_nickel(&self) -> RichTerm { 67 | use builder::*; 68 | 69 | let priority = Term::Enum(if self.force { 70 | "Force".into() 71 | } else { 72 | "Default".into() 73 | }); 74 | Record::new() 75 | .field("prio") 76 | .value(priority) 77 | .field("path") 78 | .value(Term::Array( 79 | self.path 80 | .iter() 81 | .map(|s| RichTerm::from(Term::Str(s.into()))) 82 | .collect(), 83 | ArrayAttrs::default(), 84 | )) 85 | .build() 86 | } 87 | } 88 | 89 | pub trait AsNickelField { 90 | fn as_nickel_field( 91 | &self, 92 | field: builder::Field, 93 | ) -> builder::Field; 94 | } 95 | 96 | impl AsNickelField for &intermediate::Attribute { 97 | fn as_nickel_field( 98 | &self, 99 | field: builder::Field, 100 | ) -> builder::Field { 101 | let intermediate::Attribute { 102 | description, 103 | optional, 104 | computed, 105 | type_, 106 | } = self; 107 | let (t, computed_fields) = type_.as_nickel_contracts(); 108 | let field = field.some_doc(description.clone()).set_optional(*optional); 109 | let field = if let Some(fs) = computed_fields { 110 | field.contracts([t, fs]) 111 | } else { 112 | field.contract(t) 113 | }; 114 | if *computed { 115 | field 116 | .priority(MergePriority::Bottom) 117 | .value(Term::Var("TfNcl.undefined".into())) 118 | } else { 119 | field.no_value() 120 | } 121 | } 122 | } 123 | 124 | pub trait AsNickelContracts { 125 | fn as_nickel_contracts(&self) -> (Type, Option); 126 | } 127 | 128 | enum PrimitiveType { 129 | Dyn, 130 | Str, 131 | Num, 132 | Bool, 133 | } 134 | 135 | impl From for RichTerm { 136 | fn from(t: PrimitiveType) -> Self { 137 | use nickel_lang_core::term::Term::Var; 138 | use PrimitiveType::*; 139 | match t { 140 | Dyn => Var("Dyn".into()).into(), 141 | Str => Var("String".into()).into(), 142 | Num => Var("Number".into()).into(), 143 | Bool => Var("Bool".into()).into(), 144 | } 145 | } 146 | } 147 | 148 | impl AsNickelContracts for &intermediate::Type { 149 | fn as_nickel_contracts(&self) -> (Type, Option) { 150 | use intermediate::Type::*; 151 | use nickel_lang_core::mk_app; 152 | fn tfvar(inner: impl Into) -> Type { 153 | TypeF::Contract(mk_app!(Term::Var("TfNcl.Tf".into()), inner.into())).into() 154 | } 155 | 156 | fn primitive(inner: PrimitiveType) -> (Type, Option) { 157 | (tfvar(inner), None) 158 | } 159 | 160 | match self { 161 | Dynamic => primitive(PrimitiveType::Dyn), 162 | String => primitive(PrimitiveType::Str), 163 | Number => primitive(PrimitiveType::Num), 164 | Bool => primitive(PrimitiveType::Bool), 165 | //TODO(vkleen): min and max should be represented as a contract 166 | //TODO(vkleen): tfvar wrapping is unclear 167 | List { 168 | min: _, 169 | max: _, 170 | content, 171 | } => ( 172 | TypeF::Array(Box::new(content.as_ref().as_nickel_contracts().0)).into(), 173 | None, 174 | ), 175 | Object { open, content } => ( 176 | TypeF::Contract( 177 | builder::Record::from( 178 | content 179 | .iter() 180 | .map(|(k, v)| v.as_nickel_field(builder::Field::name(k))), 181 | ) 182 | .set_open(*open) 183 | .into(), 184 | ) 185 | .into(), 186 | None, 187 | ), 188 | Dictionary { 189 | inner, 190 | prefix, 191 | computed_fields, 192 | } => { 193 | let inner_contract = TypeF::Dict { 194 | type_fields: Box::new(inner.as_ref().as_nickel_contracts().0), 195 | flavour: DictTypeFlavour::Contract, 196 | } 197 | .into(); 198 | ( 199 | inner_contract, 200 | Some( 201 | TypeF::Contract(mk_app!( 202 | Term::Var("TfNcl.ComputedFields".into()), 203 | prefix.as_nickel(), 204 | computed_fields.as_nickel() 205 | )) 206 | .into(), 207 | ), 208 | ) 209 | } 210 | } 211 | } 212 | } 213 | 214 | fn as_nickel_record(r: It) -> builder::Record 215 | where 216 | K: AsRef, 217 | V: AsNickelField, 218 | It: IntoIterator, 219 | { 220 | builder::Record::from( 221 | r.into_iter() 222 | .map(|(k, v)| v.as_nickel_field(builder::Field::name(k))), 223 | ) 224 | } 225 | --------------------------------------------------------------------------------