├── .cargo └── config.toml ├── .complate └── config.yaml ├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── .gitignore ├── .hoox.yaml ├── .neomake.yaml ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── docs ├── README.md └── complate.png ├── examples ├── git │ ├── .complate │ │ └── config.yaml │ └── commit.sh └── helm │ ├── .complate │ └── config.yaml │ ├── Chart.tpl.yaml │ ├── render.sh │ ├── templates │ └── backend │ │ ├── deployment.yaml │ │ ├── secret.yaml │ │ └── service.yaml │ ├── values.dev.yaml │ └── values.prod.yaml ├── rustfmt.toml ├── src ├── args.rs ├── config.rs ├── lib.rs ├── main.rs ├── reference.rs └── render │ ├── cli.rs │ ├── headless.rs │ └── mod.rs └── test └── .complate └── config.yaml /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-C", "target-feature=+crt-static"] 3 | 4 | [registries.crates-io] 5 | protocol = "sparse" 6 | -------------------------------------------------------------------------------- /.complate/config.yaml: -------------------------------------------------------------------------------- 1 | version: 0.15 2 | templates: 3 | zero: 4 | content: 5 | inline: |- 6 | {{ a.alpha }} 7 | {{ b.bravo }} 8 | variables: 9 | a.alpha: 10 | static: alpha 11 | b.bravo: arg 12 | 13 | one: 14 | content: 15 | file: ./.complate/templates/arbitraty-template-file.tpl 16 | variables: 17 | a.pwd: 18 | env: "PWD" 19 | 20 | two: 21 | content: 22 | inline: |- 23 | {{ a.alpha }} 24 | {{ b.bravo }} 25 | {{ c.charlie }} 26 | {{ d.delta }} 27 | {{ e.echo }} 28 | variables: 29 | a.alpha: 30 | prompt: "alpha" 31 | b.bravo: 32 | shell: "printf bravo" 33 | c.charlie: 34 | static: "charlie" 35 | d.delta: 36 | select: 37 | text: Select the version level that shall be incremented 38 | options: 39 | alpha: 40 | display: alpha 41 | value: 42 | static: alpha 43 | bravo: 44 | display: bravo 45 | value: 46 | shell: printf bravo 47 | e.echo: 48 | check: 49 | text: Select the components that are affected 50 | separator: ", " 51 | options: 52 | alpha: 53 | display: alpha 54 | value: 55 | static: alpha 56 | bravo: 57 | display: bravo 58 | value: 59 | shell: printf bravo 60 | f.foxtrot: 61 | env: "FOXTROT" 62 | 63 | three: 64 | content: 65 | inline: |- 66 | {{ test }} 67 | {{ _decode "dGVzdA==" }} 68 | helpers: 69 | "_decode": printf "$(printf $VALUE | base64 -D)" 70 | variables: 71 | test: 72 | static: "test" 73 | 74 | four: 75 | content: 76 | inline: |- 77 | --- begin message --- 78 | {{#if (eq include "true")}} 79 | included message 80 | {{/if}} 81 | --- end message --- 82 | variables: 83 | include: 84 | select: 85 | text: "Include message?" 86 | options: 87 | yes: 88 | display: "Yes" 89 | value: 90 | static: "true" 91 | no: 92 | display: "No" 93 | value: 94 | static: "false" 95 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # replicadse 4 | patreon: replicadse 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: "version" 7 | required: true 8 | 9 | jobs: 10 | version: 11 | name: version 12 | if: github.ref == 'refs/heads/master' 13 | runs-on: ubuntu-latest 14 | outputs: 15 | version: ${{ steps.export.outputs.version }} 16 | upload_url: ${{ steps.releaseExp.outputs.upload_url }} 17 | steps: 18 | - id: export 19 | run: 'printf "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT' 20 | - id: release 21 | name: Create Release 22 | uses: ncipollo/release-action@v1.12.0 23 | with: 24 | tag: ${{ github.event.inputs.version }} 25 | allowUpdates: true 26 | removeArtifacts: true 27 | makeLatest: true 28 | - id: releaseExp 29 | run: 'printf "upload_url=${{ steps.release.outputs.upload_url }}" >> $GITHUB_OUTPUT' 30 | 31 | crates-io: 32 | name: "crates.io" 33 | runs-on: ubuntu-latest 34 | needs: 35 | - version 36 | steps: 37 | - uses: actions/checkout@v3 38 | - name: install rust 39 | run: "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y" 40 | - name: package assets (manpages) 41 | run: | 42 | cargo run --target=x86_64-unknown-linux-gnu -- man -o ./manpages -f manpages 43 | cd ./manpages 44 | tar -czf ../docs-manpages.tar.gz . 45 | - name: package assets (markdown) 46 | run: | 47 | cargo run --target=x86_64-unknown-linux-gnu -- man -o ./markdown -f markdown 48 | cd ./markdown 49 | tar -czf ../docs-markdown.tar.gz . 50 | - name: package assets (shell completion) 51 | run: | 52 | export GEN_CMD="cargo run --target=x86_64-unknown-linux-gnu -- autocomplete -o ./shell-completion" 53 | $GEN_CMD -s bash 54 | $GEN_CMD -s zsh 55 | $GEN_CMD -s fish 56 | $GEN_CMD -s elvish 57 | $GEN_CMD -s powershell 58 | cd ./shell-completion 59 | tar -czf ../shell-completion.tar.gz . 60 | - name: upload asset (manpages) 61 | uses: actions/upload-release-asset@v1 62 | env: 63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | with: 65 | upload_url: ${{ needs.version.outputs.upload_url }} 66 | asset_path: ./docs-manpages.tar.gz 67 | asset_name: docs-manpages.tar.gz 68 | asset_content_type: application/tar+gzip 69 | - name: upload asset (markdown) 70 | uses: actions/upload-release-asset@v1 71 | env: 72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | with: 74 | upload_url: ${{ needs.version.outputs.upload_url }} 75 | asset_path: ./docs-markdown.tar.gz 76 | asset_name: docs-markdown.tar.gz 77 | asset_content_type: application/tar+gzip 78 | - name: upload asset (shell completion) 79 | uses: actions/upload-release-asset@v1 80 | env: 81 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 82 | with: 83 | upload_url: ${{ needs.version.outputs.upload_url }} 84 | asset_path: ./shell-completion.tar.gz 85 | asset_name: shell-completion.tar.gz 86 | asset_content_type: application/tar+gzip 87 | - name: publish 88 | run: | 89 | cargo login ${{ secrets.CRATES_IO_TOKEN }} 90 | sed 's/version = "0.0.0"/version = "'${{ needs.version.outputs.version }}'"/g' Cargo.toml > Cargo.toml.tmp 91 | mv Cargo.toml.tmp Cargo.toml 92 | cargo publish --target x86_64-unknown-linux-gnu --allow-dirty 93 | 94 | deb: 95 | name: "deb" 96 | runs-on: ubuntu-latest 97 | needs: 98 | - version 99 | - crates-io 100 | steps: 101 | - uses: actions/checkout@v3 102 | - run: | 103 | sudo apt-get install -y curl 104 | - name: install rust 105 | run: | 106 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 107 | cargo install cargo-deb 108 | - name: publish 109 | run: | 110 | sed 's/version = "0.0.0"/version = "'${{ needs.version.outputs.version }}'"/g' Cargo.toml > Cargo.toml.tmp 111 | mv Cargo.toml.tmp Cargo.toml 112 | cargo deb --target=x86_64-unknown-linux-gnu 113 | export DEB_F=$(printf "${{ needs.version.outputs.version }}" | sed 's/-/~/g') 114 | mv ./target/x86_64-unknown-linux-gnu/debian/complate_${DEB_F}-1_amd64.deb ./target/x86_64-unknown-linux-gnu/debian/complate_${{ needs.version.outputs.version }}_amd64.deb || true 115 | - name: Upload Release Asset 116 | id: upload-release-asset 117 | uses: actions/upload-release-asset@v1 118 | env: 119 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 120 | with: 121 | upload_url: ${{ needs.version.outputs.upload_url }} 122 | asset_path: ./target/x86_64-unknown-linux-gnu/debian/complate_${{ needs.version.outputs.version }}_amd64.deb 123 | asset_name: complate-amd64.deb 124 | asset_content_type: application/octet-stream 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | target/ 3 | -------------------------------------------------------------------------------- /.hoox.yaml: -------------------------------------------------------------------------------- 1 | version: "0.0.0" 2 | 3 | .cargo_all: &cargo_all !inline |- 4 | set -e 5 | cargo build --all-features 6 | cargo +nightly fmt --all -- --check 7 | cargo test --all 8 | cargo doc --no-deps 9 | 10 | hooks: 11 | "pre-commit": # pre-commit hook 12 | - command: *cargo_all 13 | "pre-push": # pre-push hook 14 | - command: *cargo_all 15 | -------------------------------------------------------------------------------- /.neomake.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | 3 | nodes: 4 | build: 5 | matrix: 6 | dense: 7 | dimensions: 8 | - - env: 9 | RELEASE: "" 10 | - env: 11 | RELEASE: "--release" 12 | - - env: 13 | FEATURES: "--no-default-features" 14 | - env: 15 | FEATURES: "" 16 | - env: 17 | FEATURES: "--features=backend+cli" 18 | tasks: 19 | - script: | 20 | set -e 21 | cargo build $FEATURES $RELEASE 22 | 23 | "test:regression": 24 | env: 25 | vars: 26 | config: "./test/.complate/config.yaml" 27 | tasks: 28 | - script: | 29 | set -e 30 | export RUST_BACKTRACE=1 31 | cargo test 32 | 33 | "hook:pre-push": 34 | pre: 35 | - build 36 | - "test:regression" 37 | tasks: [] 38 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": [ 3 | "backend+cli", 4 | ], 5 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.20.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.0.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.18" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.1" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.0.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 64 | dependencies = [ 65 | "windows-sys 0.48.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.6" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 73 | dependencies = [ 74 | "anstyle", 75 | "windows-sys 0.59.0", 76 | ] 77 | 78 | [[package]] 79 | name = "anyhow" 80 | version = "1.0.86" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 83 | dependencies = [ 84 | "backtrace", 85 | ] 86 | 87 | [[package]] 88 | name = "async-trait" 89 | version = "0.1.83" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" 92 | dependencies = [ 93 | "proc-macro2", 94 | "quote", 95 | "syn 2.0.87", 96 | ] 97 | 98 | [[package]] 99 | name = "autocfg" 100 | version = "1.1.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 103 | 104 | [[package]] 105 | name = "backtrace" 106 | version = "0.3.68" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" 109 | dependencies = [ 110 | "addr2line", 111 | "cc", 112 | "cfg-if", 113 | "libc", 114 | "miniz_oxide", 115 | "object", 116 | "rustc-demangle", 117 | ] 118 | 119 | [[package]] 120 | name = "bit-set" 121 | version = "0.5.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 124 | dependencies = [ 125 | "bit-vec", 126 | ] 127 | 128 | [[package]] 129 | name = "bit-vec" 130 | version = "0.6.3" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 133 | 134 | [[package]] 135 | name = "bitflags" 136 | version = "1.3.2" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 139 | 140 | [[package]] 141 | name = "block-buffer" 142 | version = "0.7.3" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 145 | dependencies = [ 146 | "block-padding", 147 | "byte-tools", 148 | "byteorder", 149 | "generic-array", 150 | ] 151 | 152 | [[package]] 153 | name = "block-padding" 154 | version = "0.1.5" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 157 | dependencies = [ 158 | "byte-tools", 159 | ] 160 | 161 | [[package]] 162 | name = "byte-tools" 163 | version = "0.3.1" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 166 | 167 | [[package]] 168 | name = "byteorder" 169 | version = "1.4.3" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 172 | 173 | [[package]] 174 | name = "bytes" 175 | version = "1.4.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 178 | 179 | [[package]] 180 | name = "cc" 181 | version = "1.0.69" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" 184 | 185 | [[package]] 186 | name = "cfg-if" 187 | version = "1.0.0" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 190 | 191 | [[package]] 192 | name = "ci_info" 193 | version = "0.14.14" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "840dbb7bdd1f2c4d434d6b08420ef204e0bfad0ab31a07a80a1248d24cc6e38b" 196 | dependencies = [ 197 | "envmnt", 198 | ] 199 | 200 | [[package]] 201 | name = "clap" 202 | version = "4.5.20" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 205 | dependencies = [ 206 | "clap_builder", 207 | ] 208 | 209 | [[package]] 210 | name = "clap-markdown" 211 | version = "0.1.4" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "8ebc67e6266e14f8b31541c2f204724fa2ac7ad5c17d6f5908fbb92a60f42cff" 214 | dependencies = [ 215 | "clap", 216 | ] 217 | 218 | [[package]] 219 | name = "clap_builder" 220 | version = "4.5.20" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 223 | dependencies = [ 224 | "anstream", 225 | "anstyle", 226 | "clap_lex", 227 | "strsim", 228 | ] 229 | 230 | [[package]] 231 | name = "clap_complete" 232 | version = "4.5.37" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "11611dca53440593f38e6b25ec629de50b14cdfa63adc0fb856115a2c6d97595" 235 | dependencies = [ 236 | "clap", 237 | ] 238 | 239 | [[package]] 240 | name = "clap_lex" 241 | version = "0.7.2" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 244 | 245 | [[package]] 246 | name = "clap_mangen" 247 | version = "0.2.24" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "fbae9cbfdc5d4fa8711c09bd7b83f644cb48281ac35bf97af3e47b0675864bdf" 250 | dependencies = [ 251 | "clap", 252 | "roff", 253 | ] 254 | 255 | [[package]] 256 | name = "clitest" 257 | version = "0.0.0" 258 | source = "git+https://github.com/replicadse/clitest_rs?branch=master#94291c7f48f81a88ba03af331417ec7888f5eaa9" 259 | dependencies = [ 260 | "anyhow", 261 | ] 262 | 263 | [[package]] 264 | name = "colorchoice" 265 | version = "1.0.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 268 | 269 | [[package]] 270 | name = "complate" 271 | version = "0.0.0" 272 | dependencies = [ 273 | "anyhow", 274 | "async-trait", 275 | "bytes", 276 | "clap", 277 | "clap-markdown", 278 | "clap_complete", 279 | "clap_mangen", 280 | "clitest", 281 | "dialoguer", 282 | "fancy-regex", 283 | "handlebars", 284 | "hoox", 285 | "indoc", 286 | "mime", 287 | "schemars", 288 | "serde", 289 | "serde_json", 290 | "serde_yaml", 291 | "tokio", 292 | ] 293 | 294 | [[package]] 295 | name = "console" 296 | version = "0.15.7" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" 299 | dependencies = [ 300 | "encode_unicode", 301 | "lazy_static", 302 | "libc", 303 | "unicode-width", 304 | "windows-sys 0.45.0", 305 | ] 306 | 307 | [[package]] 308 | name = "dialoguer" 309 | version = "0.10.4" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" 312 | dependencies = [ 313 | "console", 314 | "shell-words", 315 | "tempfile", 316 | "zeroize", 317 | ] 318 | 319 | [[package]] 320 | name = "digest" 321 | version = "0.8.1" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 324 | dependencies = [ 325 | "generic-array", 326 | ] 327 | 328 | [[package]] 329 | name = "dunce" 330 | version = "1.0.4" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" 333 | 334 | [[package]] 335 | name = "dyn-clone" 336 | version = "1.0.12" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" 339 | 340 | [[package]] 341 | name = "encode_unicode" 342 | version = "0.3.6" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 345 | 346 | [[package]] 347 | name = "envmnt" 348 | version = "0.10.4" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "d73999a2b8871e74c8b8bc23759ee9f3d85011b24fafc91a4b3b5c8cc8185501" 351 | dependencies = [ 352 | "fsio", 353 | "indexmap 1.9.2", 354 | ] 355 | 356 | [[package]] 357 | name = "equivalent" 358 | version = "1.0.1" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 361 | 362 | [[package]] 363 | name = "fake-simd" 364 | version = "0.1.2" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 367 | 368 | [[package]] 369 | name = "fancy-regex" 370 | version = "0.11.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" 373 | dependencies = [ 374 | "bit-set", 375 | "regex", 376 | ] 377 | 378 | [[package]] 379 | name = "fsio" 380 | version = "0.4.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "dad0ce30be0cc441b325c5d705c8b613a0ca0d92b6a8953d41bd236dc09a36d0" 383 | dependencies = [ 384 | "dunce", 385 | ] 386 | 387 | [[package]] 388 | name = "generic-array" 389 | version = "0.12.4" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" 392 | dependencies = [ 393 | "typenum", 394 | ] 395 | 396 | [[package]] 397 | name = "getrandom" 398 | version = "0.2.3" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 401 | dependencies = [ 402 | "cfg-if", 403 | "libc", 404 | "wasi", 405 | ] 406 | 407 | [[package]] 408 | name = "gimli" 409 | version = "0.27.3" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" 412 | 413 | [[package]] 414 | name = "handlebars" 415 | version = "4.3.7" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" 418 | dependencies = [ 419 | "log", 420 | "pest", 421 | "pest_derive", 422 | "serde", 423 | "serde_json", 424 | "thiserror", 425 | ] 426 | 427 | [[package]] 428 | name = "hashbrown" 429 | version = "0.12.3" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 432 | 433 | [[package]] 434 | name = "hashbrown" 435 | version = "0.14.5" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 438 | 439 | [[package]] 440 | name = "hoox" 441 | version = "0.3.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "e16d69a4c72ac350b4a26e4c42287cab71bb5bd1224d8f220c433cd1506a6ab2" 444 | dependencies = [ 445 | "anyhow", 446 | "async-trait", 447 | "ci_info", 448 | "clap", 449 | "clap-markdown", 450 | "clap_complete", 451 | "clap_mangen", 452 | "serde", 453 | "serde_yaml", 454 | "tokio", 455 | "version-compare", 456 | ] 457 | 458 | [[package]] 459 | name = "indexmap" 460 | version = "1.9.2" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 463 | dependencies = [ 464 | "autocfg", 465 | "hashbrown 0.12.3", 466 | ] 467 | 468 | [[package]] 469 | name = "indexmap" 470 | version = "2.3.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" 473 | dependencies = [ 474 | "equivalent", 475 | "hashbrown 0.14.5", 476 | ] 477 | 478 | [[package]] 479 | name = "indoc" 480 | version = "2.0.3" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4" 483 | 484 | [[package]] 485 | name = "is_terminal_polyfill" 486 | version = "1.70.1" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 489 | 490 | [[package]] 491 | name = "itoa" 492 | version = "1.0.9" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 495 | 496 | [[package]] 497 | name = "lazy_static" 498 | version = "1.4.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 501 | 502 | [[package]] 503 | name = "libc" 504 | version = "0.2.147" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 507 | 508 | [[package]] 509 | name = "log" 510 | version = "0.4.14" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 513 | dependencies = [ 514 | "cfg-if", 515 | ] 516 | 517 | [[package]] 518 | name = "maplit" 519 | version = "1.0.2" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 522 | 523 | [[package]] 524 | name = "memchr" 525 | version = "2.5.0" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 528 | 529 | [[package]] 530 | name = "mime" 531 | version = "0.3.17" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 534 | 535 | [[package]] 536 | name = "miniz_oxide" 537 | version = "0.7.1" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 540 | dependencies = [ 541 | "adler", 542 | ] 543 | 544 | [[package]] 545 | name = "object" 546 | version = "0.31.1" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" 549 | dependencies = [ 550 | "memchr", 551 | ] 552 | 553 | [[package]] 554 | name = "opaque-debug" 555 | version = "0.2.3" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 558 | 559 | [[package]] 560 | name = "pest" 561 | version = "2.1.3" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 564 | dependencies = [ 565 | "ucd-trie", 566 | ] 567 | 568 | [[package]] 569 | name = "pest_derive" 570 | version = "2.1.0" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" 573 | dependencies = [ 574 | "pest", 575 | "pest_generator", 576 | ] 577 | 578 | [[package]] 579 | name = "pest_generator" 580 | version = "2.1.3" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" 583 | dependencies = [ 584 | "pest", 585 | "pest_meta", 586 | "proc-macro2", 587 | "quote", 588 | "syn 1.0.75", 589 | ] 590 | 591 | [[package]] 592 | name = "pest_meta" 593 | version = "2.1.3" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" 596 | dependencies = [ 597 | "maplit", 598 | "pest", 599 | "sha-1", 600 | ] 601 | 602 | [[package]] 603 | name = "pin-project-lite" 604 | version = "0.2.15" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 607 | 608 | [[package]] 609 | name = "ppv-lite86" 610 | version = "0.2.10" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 613 | 614 | [[package]] 615 | name = "proc-macro2" 616 | version = "1.0.86" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 619 | dependencies = [ 620 | "unicode-ident", 621 | ] 622 | 623 | [[package]] 624 | name = "quote" 625 | version = "1.0.36" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 628 | dependencies = [ 629 | "proc-macro2", 630 | ] 631 | 632 | [[package]] 633 | name = "rand" 634 | version = "0.8.4" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 637 | dependencies = [ 638 | "libc", 639 | "rand_chacha", 640 | "rand_core", 641 | "rand_hc", 642 | ] 643 | 644 | [[package]] 645 | name = "rand_chacha" 646 | version = "0.3.1" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 649 | dependencies = [ 650 | "ppv-lite86", 651 | "rand_core", 652 | ] 653 | 654 | [[package]] 655 | name = "rand_core" 656 | version = "0.6.3" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 659 | dependencies = [ 660 | "getrandom", 661 | ] 662 | 663 | [[package]] 664 | name = "rand_hc" 665 | version = "0.3.1" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 668 | dependencies = [ 669 | "rand_core", 670 | ] 671 | 672 | [[package]] 673 | name = "redox_syscall" 674 | version = "0.2.10" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 677 | dependencies = [ 678 | "bitflags", 679 | ] 680 | 681 | [[package]] 682 | name = "regex" 683 | version = "1.9.3" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" 686 | dependencies = [ 687 | "aho-corasick", 688 | "memchr", 689 | "regex-automata", 690 | "regex-syntax", 691 | ] 692 | 693 | [[package]] 694 | name = "regex-automata" 695 | version = "0.3.6" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" 698 | dependencies = [ 699 | "aho-corasick", 700 | "memchr", 701 | "regex-syntax", 702 | ] 703 | 704 | [[package]] 705 | name = "regex-syntax" 706 | version = "0.7.4" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" 709 | 710 | [[package]] 711 | name = "remove_dir_all" 712 | version = "0.5.3" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 715 | dependencies = [ 716 | "winapi", 717 | ] 718 | 719 | [[package]] 720 | name = "roff" 721 | version = "0.2.1" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" 724 | 725 | [[package]] 726 | name = "rustc-demangle" 727 | version = "0.1.23" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 730 | 731 | [[package]] 732 | name = "ryu" 733 | version = "1.0.5" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 736 | 737 | [[package]] 738 | name = "schemars" 739 | version = "0.8.12" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" 742 | dependencies = [ 743 | "dyn-clone", 744 | "schemars_derive", 745 | "serde", 746 | "serde_json", 747 | ] 748 | 749 | [[package]] 750 | name = "schemars_derive" 751 | version = "0.8.12" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" 754 | dependencies = [ 755 | "proc-macro2", 756 | "quote", 757 | "serde_derive_internals", 758 | "syn 1.0.75", 759 | ] 760 | 761 | [[package]] 762 | name = "serde" 763 | version = "1.0.215" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 766 | dependencies = [ 767 | "serde_derive", 768 | ] 769 | 770 | [[package]] 771 | name = "serde_derive" 772 | version = "1.0.215" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 775 | dependencies = [ 776 | "proc-macro2", 777 | "quote", 778 | "syn 2.0.87", 779 | ] 780 | 781 | [[package]] 782 | name = "serde_derive_internals" 783 | version = "0.26.0" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" 786 | dependencies = [ 787 | "proc-macro2", 788 | "quote", 789 | "syn 1.0.75", 790 | ] 791 | 792 | [[package]] 793 | name = "serde_json" 794 | version = "1.0.104" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" 797 | dependencies = [ 798 | "itoa", 799 | "ryu", 800 | "serde", 801 | ] 802 | 803 | [[package]] 804 | name = "serde_yaml" 805 | version = "0.9.34+deprecated" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 808 | dependencies = [ 809 | "indexmap 2.3.0", 810 | "itoa", 811 | "ryu", 812 | "serde", 813 | "unsafe-libyaml", 814 | ] 815 | 816 | [[package]] 817 | name = "sha-1" 818 | version = "0.8.2" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" 821 | dependencies = [ 822 | "block-buffer", 823 | "digest", 824 | "fake-simd", 825 | "opaque-debug", 826 | ] 827 | 828 | [[package]] 829 | name = "shell-words" 830 | version = "1.1.0" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 833 | 834 | [[package]] 835 | name = "strsim" 836 | version = "0.11.1" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 839 | 840 | [[package]] 841 | name = "syn" 842 | version = "1.0.75" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" 845 | dependencies = [ 846 | "proc-macro2", 847 | "quote", 848 | "unicode-xid", 849 | ] 850 | 851 | [[package]] 852 | name = "syn" 853 | version = "2.0.87" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 856 | dependencies = [ 857 | "proc-macro2", 858 | "quote", 859 | "unicode-ident", 860 | ] 861 | 862 | [[package]] 863 | name = "tempfile" 864 | version = "3.2.0" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 867 | dependencies = [ 868 | "cfg-if", 869 | "libc", 870 | "rand", 871 | "redox_syscall", 872 | "remove_dir_all", 873 | "winapi", 874 | ] 875 | 876 | [[package]] 877 | name = "thiserror" 878 | version = "1.0.44" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" 881 | dependencies = [ 882 | "thiserror-impl", 883 | ] 884 | 885 | [[package]] 886 | name = "thiserror-impl" 887 | version = "1.0.44" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" 890 | dependencies = [ 891 | "proc-macro2", 892 | "quote", 893 | "syn 2.0.87", 894 | ] 895 | 896 | [[package]] 897 | name = "tokio" 898 | version = "1.41.1" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" 901 | dependencies = [ 902 | "backtrace", 903 | "pin-project-lite", 904 | "tokio-macros", 905 | ] 906 | 907 | [[package]] 908 | name = "tokio-macros" 909 | version = "2.4.0" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 912 | dependencies = [ 913 | "proc-macro2", 914 | "quote", 915 | "syn 2.0.87", 916 | ] 917 | 918 | [[package]] 919 | name = "typenum" 920 | version = "1.13.0" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" 923 | 924 | [[package]] 925 | name = "ucd-trie" 926 | version = "0.1.3" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 929 | 930 | [[package]] 931 | name = "unicode-ident" 932 | version = "1.0.11" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 935 | 936 | [[package]] 937 | name = "unicode-width" 938 | version = "0.1.8" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 941 | 942 | [[package]] 943 | name = "unicode-xid" 944 | version = "0.2.2" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 947 | 948 | [[package]] 949 | name = "unsafe-libyaml" 950 | version = "0.2.11" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 953 | 954 | [[package]] 955 | name = "utf8parse" 956 | version = "0.2.1" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 959 | 960 | [[package]] 961 | name = "version-compare" 962 | version = "0.2.0" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" 965 | 966 | [[package]] 967 | name = "wasi" 968 | version = "0.10.2+wasi-snapshot-preview1" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 971 | 972 | [[package]] 973 | name = "winapi" 974 | version = "0.3.9" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 977 | dependencies = [ 978 | "winapi-i686-pc-windows-gnu", 979 | "winapi-x86_64-pc-windows-gnu", 980 | ] 981 | 982 | [[package]] 983 | name = "winapi-i686-pc-windows-gnu" 984 | version = "0.4.0" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 987 | 988 | [[package]] 989 | name = "winapi-x86_64-pc-windows-gnu" 990 | version = "0.4.0" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 993 | 994 | [[package]] 995 | name = "windows-sys" 996 | version = "0.45.0" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 999 | dependencies = [ 1000 | "windows-targets 0.42.1", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "windows-sys" 1005 | version = "0.48.0" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1008 | dependencies = [ 1009 | "windows-targets 0.48.1", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "windows-sys" 1014 | version = "0.59.0" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1017 | dependencies = [ 1018 | "windows-targets 0.52.6", 1019 | ] 1020 | 1021 | [[package]] 1022 | name = "windows-targets" 1023 | version = "0.42.1" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" 1026 | dependencies = [ 1027 | "windows_aarch64_gnullvm 0.42.1", 1028 | "windows_aarch64_msvc 0.42.1", 1029 | "windows_i686_gnu 0.42.1", 1030 | "windows_i686_msvc 0.42.1", 1031 | "windows_x86_64_gnu 0.42.1", 1032 | "windows_x86_64_gnullvm 0.42.1", 1033 | "windows_x86_64_msvc 0.42.1", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "windows-targets" 1038 | version = "0.48.1" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" 1041 | dependencies = [ 1042 | "windows_aarch64_gnullvm 0.48.0", 1043 | "windows_aarch64_msvc 0.48.0", 1044 | "windows_i686_gnu 0.48.0", 1045 | "windows_i686_msvc 0.48.0", 1046 | "windows_x86_64_gnu 0.48.0", 1047 | "windows_x86_64_gnullvm 0.48.0", 1048 | "windows_x86_64_msvc 0.48.0", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "windows-targets" 1053 | version = "0.52.6" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1056 | dependencies = [ 1057 | "windows_aarch64_gnullvm 0.52.6", 1058 | "windows_aarch64_msvc 0.52.6", 1059 | "windows_i686_gnu 0.52.6", 1060 | "windows_i686_gnullvm", 1061 | "windows_i686_msvc 0.52.6", 1062 | "windows_x86_64_gnu 0.52.6", 1063 | "windows_x86_64_gnullvm 0.52.6", 1064 | "windows_x86_64_msvc 0.52.6", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "windows_aarch64_gnullvm" 1069 | version = "0.42.1" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 1072 | 1073 | [[package]] 1074 | name = "windows_aarch64_gnullvm" 1075 | version = "0.48.0" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1078 | 1079 | [[package]] 1080 | name = "windows_aarch64_gnullvm" 1081 | version = "0.52.6" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1084 | 1085 | [[package]] 1086 | name = "windows_aarch64_msvc" 1087 | version = "0.42.1" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 1090 | 1091 | [[package]] 1092 | name = "windows_aarch64_msvc" 1093 | version = "0.48.0" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1096 | 1097 | [[package]] 1098 | name = "windows_aarch64_msvc" 1099 | version = "0.52.6" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1102 | 1103 | [[package]] 1104 | name = "windows_i686_gnu" 1105 | version = "0.42.1" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 1108 | 1109 | [[package]] 1110 | name = "windows_i686_gnu" 1111 | version = "0.48.0" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1114 | 1115 | [[package]] 1116 | name = "windows_i686_gnu" 1117 | version = "0.52.6" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1120 | 1121 | [[package]] 1122 | name = "windows_i686_gnullvm" 1123 | version = "0.52.6" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1126 | 1127 | [[package]] 1128 | name = "windows_i686_msvc" 1129 | version = "0.42.1" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 1132 | 1133 | [[package]] 1134 | name = "windows_i686_msvc" 1135 | version = "0.48.0" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1138 | 1139 | [[package]] 1140 | name = "windows_i686_msvc" 1141 | version = "0.52.6" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1144 | 1145 | [[package]] 1146 | name = "windows_x86_64_gnu" 1147 | version = "0.42.1" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 1150 | 1151 | [[package]] 1152 | name = "windows_x86_64_gnu" 1153 | version = "0.48.0" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1156 | 1157 | [[package]] 1158 | name = "windows_x86_64_gnu" 1159 | version = "0.52.6" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1162 | 1163 | [[package]] 1164 | name = "windows_x86_64_gnullvm" 1165 | version = "0.42.1" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 1168 | 1169 | [[package]] 1170 | name = "windows_x86_64_gnullvm" 1171 | version = "0.48.0" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1174 | 1175 | [[package]] 1176 | name = "windows_x86_64_gnullvm" 1177 | version = "0.52.6" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1180 | 1181 | [[package]] 1182 | name = "windows_x86_64_msvc" 1183 | version = "0.42.1" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 1186 | 1187 | [[package]] 1188 | name = "windows_x86_64_msvc" 1189 | version = "0.48.0" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1192 | 1193 | [[package]] 1194 | name = "windows_x86_64_msvc" 1195 | version = "0.52.6" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1198 | 1199 | [[package]] 1200 | name = "zeroize" 1201 | version = "1.4.1" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "377db0846015f7ae377174787dd452e1c5f5a9050bc6f954911d01f116daa0cd" 1204 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "complate" 3 | version = "0.0.0" 4 | authors = ["Alexander Weber "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A powerful text templating tool." 8 | homepage = "https://replicadse.github.io/complate" 9 | repository = "https://github.com/replicadse/complate" 10 | keywords = ["cli", "template", "replace", "standardizing"] 11 | categories = ["command-line-utilities"] 12 | readme = "docs/README.md" 13 | autobins = false 14 | 15 | [lib] 16 | name = "complate" 17 | path = "./src/lib.rs" 18 | 19 | [[bin]] 20 | name = "complate" 21 | path = "./src/main.rs" 22 | 23 | [features] 24 | default = ["backend+cli"] 25 | "backend+cli" = ["dialoguer"] 26 | 27 | [dependencies] 28 | clap = "4.3.19" 29 | clap_complete = "4.3.2" 30 | clap_mangen = "0.2.12" 31 | clap-markdown = "0.1.3" 32 | async-trait = "0.1.72" 33 | tokio = { version = "1.29.1", features = ["rt", "rt-multi-thread", "macros"] } 34 | handlebars = "4.3.7" 35 | bytes = "1.4.0" 36 | mime = "0.3.17" 37 | serde = { version = "1.0.181", features = ["derive"] } 38 | serde_json = "1.0.104" 39 | serde_yaml = "0.9.25" 40 | dialoguer = { version = "0.10.4", optional = true } 41 | schemars = "0.8.12" 42 | fancy-regex = "0.11.0" 43 | indoc = "2.0.3" 44 | anyhow = "1.0.86" 45 | 46 | [dev-dependencies] 47 | clitest = { git = "https://github.com/replicadse/clitest_rs", branch = "master" } 48 | hoox = "0.3.0" 49 | 50 | [profile.release] 51 | lto = true 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # complate 2 | 3 | `complate` is a general purpose text templating CLI program that supports interactive mode, prompting the user for values via TUI behaviour and headless mode for use in automation such as CI pipelines. 4 | 5 | ![](complate.png) 6 | 7 | ## Installation 8 | 9 | * The rusty way:\ 10 | `cargo install complate` 11 | * The manual way:\ 12 | Download and install from the GitHub releases. 13 | 14 | ## Config example 15 | 16 | ``` 17 | version: 0.15 18 | templates: 19 | zero: 20 | content: 21 | inline: |- 22 | {{ a.alpha }} 23 | {{ b.bravo }} 24 | variables: 25 | a.alpha: 26 | static: alpha 27 | b.bravo: arg 28 | 29 | one: 30 | content: 31 | file: ./.complate/templates/arbitraty-template-file.tpl 32 | variables: 33 | a.pwd: 34 | env: "PWD" 35 | two: 36 | content: 37 | inline: |- 38 | {{ a.alpha }} 39 | {{ b.bravo }} 40 | {{ c.charlie }} 41 | {{ d.delta }} 42 | {{ e.echo }} 43 | variables: 44 | a.alpha: 45 | prompt: "alpha" 46 | b.bravo: 47 | shell: "printf bravo" 48 | c.charlie: 49 | static: "charlie" 50 | d.delta: 51 | select: 52 | text: Select the version level that shall be incremented 53 | options: 54 | alpha: 55 | display: alpha 56 | value: 57 | static: alpha 58 | bravo: 59 | display: bravo 60 | value: 61 | shell: printf bravo 62 | e.echo: 63 | check: 64 | text: Select the components that are affected 65 | separator: ", " 66 | options: 67 | alpha: 68 | display: alpha 69 | value: 70 | static: alpha 71 | bravo: 72 | display: bravo 73 | value: 74 | shell: printf bravo 75 | f.foxtrot: 76 | env: "FOXTROT" 77 | three: 78 | content: 79 | inline: |- 80 | {{ test }} 81 | {{ _decode "dGVzdA==" }} 82 | helpers: 83 | "_decode": printf "$(printf $VALUE | base64 -D)" 84 | variables: 85 | test: 86 | static: "test" 87 | 88 | ``` 89 | 90 | | Key | Behaviour | Input | 91 | | ------ | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | 92 | | arg | Expects input as argument via command line input | None | 93 | | env | Retrieves value from the specified env var | None | 94 | | static | Simply replaces the variable with a static value | None | 95 | | prompt | Asks the user for text input (can be empty) | The prompt | 96 | | shell | Invokes a shell command to resolve the variable (read from `STDOUT`) | None | 97 | | select | Asks the user to select one item from a list | `text`: string (context), `options`: list (available options to select from) | 98 | | check | Asks the user to select `0..n` item(s) from a list (multiselect) | `text`: string (context), `options`: list of options {display: str, value: str} (the available options to select from) | 99 | 100 | Since the `shell` value provider is able to run arbitrary shell commands, it is only allowed if and only if the `SHELL_TRUST` argument is explicitly set. See the `render` command reference for possible values for this setting. If *not* set, the provider will throw an unrecoverable error and the program will abort. 101 | 102 | ## Command reference 103 | 104 | ### Disclaimer 105 | 106 | All features that are marked as `experimental` are _not_ considered a public API and therefore eplicitly not covered by the backwards-compatibility policy inside a major version (see https://semver.org[semver v2]). Use these features on your own risk! 107 | -------------------------------------------------------------------------------- /docs/complate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cchexcode/complate/e3df663af5868005df47a804fe61dda8b3b21a91/docs/complate.png -------------------------------------------------------------------------------- /examples/git/.complate/config.yaml: -------------------------------------------------------------------------------- 1 | version: 0.12 2 | templates: 3 | commit: 4 | content: 5 | inline: |- 6 | {{ a.ticket }}: {{ b.summary }} 7 | {{ c.remarks }} 8 | values: 9 | a.ticket: 10 | prompt: "Enter the ticket number" 11 | b.summary: 12 | prompt: "Enter your work summary" 13 | c.remarks: 14 | prompt: "Other remarks" 15 | -------------------------------------------------------------------------------- /examples/git/commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | complate -e render -t commit -bcli | git commit -F - 4 | -------------------------------------------------------------------------------- /examples/helm/.complate/config.yaml: -------------------------------------------------------------------------------- 1 | version: 0.12 2 | templates: 3 | chart: 4 | content: 5 | file: "./Chart.tpl.yaml" 6 | values: 7 | version: 8 | prompt: "Enter the semver for the chart and app version" 9 | dev: 10 | content: 11 | file: "./values.dev.yaml" 12 | helpers: 13 | "_decrypt": 14 | shell: |- 15 | printf "$(echo $VALUE | openssl aes-256-cbc -d -a -pass $MY_SECRET_PASS)" 16 | values: 17 | env: 18 | static: "dev" 19 | prod: 20 | content: 21 | file: "./values.prod.yaml" 22 | helpers: 23 | "_decrypt": 24 | shell: |- 25 | printf "$(echo $VALUE | openssl aes-256-cbc -d -a -pass $MY_SECRET_PASS)" 26 | values: 27 | env: 28 | static: "prod" 29 | -------------------------------------------------------------------------------- /examples/helm/Chart.tpl.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: "complate-helm-example" 3 | description: none 4 | 5 | type: application 6 | version: "{{ version }}" 7 | appVersion: "{{ version }}" 8 | 9 | dependencies: [] 10 | -------------------------------------------------------------------------------- /examples/helm/render.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # MY_SECRET_PASS should be set as env var in CI 4 | echo ">>> values dev <<<" 5 | echo "" 6 | complate -e render -t chart -v "version=0.1.0" 7 | echo ">>> values dev <<<" 8 | echo "" 9 | MY_SECRET_PASS="pass:superpass" complate -e render -t dev --trust 10 | echo "" 11 | echo ">>> values prod <<<" 12 | echo "" 13 | MY_SECRET_PASS="pass:superpass" complate -e render -t prod --trust 14 | -------------------------------------------------------------------------------- /examples/helm/templates/backend/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ .Release.Name }}-{{ .Values.env }}-nginx 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: {{ .Release.Name }}-{{ .Values.env }}-nginx 9 | strategy: 10 | type: Recreate 11 | revisionHistoryLimit: 0 12 | template: 13 | metadata: 14 | labels: 15 | app: {{ .Release.Name }}-{{ .Values.env }}-nginx 16 | spec: 17 | containers: 18 | - name: {{ .Release.Name }}-{{ .Values.env }}-nginx 19 | image: nginx:{{ .Chart.AppVersion }} 20 | imagePullPolicy: Always 21 | env: [] 22 | ports: 23 | - containerPort: 80 24 | -------------------------------------------------------------------------------- /examples/helm/templates/backend/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: some-secret 5 | type: Opaque 6 | stringData: 7 | {{- toYaml .Values.secret | nindent 2 }} 8 | -------------------------------------------------------------------------------- /examples/helm/templates/backend/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ .Release.Name }}-{{ .Values.env }}-nginx 5 | spec: 6 | type: ClusterIP 7 | ports: 8 | - port: 8080 9 | targetPort: 80 10 | protocol: TCP 11 | selector: 12 | app: {{ .Release.Name }}-{{ .Values.env }}-nginx 13 | -------------------------------------------------------------------------------- /examples/helm/values.dev.yaml: -------------------------------------------------------------------------------- 1 | env: "{{ env }}" 2 | secret: 3 | apikey: '{{ _decrypt "U2FsdGVkX1/ugUBmzFwL8/GrodAJqhzuZSphwFqBuKa8t1tjWU+OjZ6dmXmO83Wq" }}' 4 | -------------------------------------------------------------------------------- /examples/helm/values.prod.yaml: -------------------------------------------------------------------------------- 1 | env: "{{ env }}" 2 | secret: 3 | apikey: '{{ _decrypt "U2FsdGVkX19o7/B7vdP8iTC5ToQl8mq6i1SEdrRy5R9Q0GhIahXqCY8PXkhTrRIx" }}' 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | condense_wildcard_suffixes=true 2 | format_code_in_doc_comments=true 3 | format_macro_matchers=true 4 | format_strings=true 5 | imports_layout="Vertical" 6 | match_arm_leading_pipes="Always" 7 | match_block_trailing_comma=true 8 | max_width=120 9 | imports_granularity="One" 10 | newline_style="Unix" 11 | normalize_comments=true 12 | normalize_doc_attributes=true 13 | overflow_delimited_expr=true 14 | reorder_impl_items=true 15 | group_imports="One" 16 | type_punctuation_density="Compressed" 17 | use_field_init_shorthand=true 18 | use_try_shorthand=true 19 | where_single_line=true 20 | wrap_comments=true 21 | attr_fn_like_width=120 22 | blank_lines_upper_bound=1 23 | empty_item_single_line=true 24 | force_multiline_blocks=true 25 | format_generated_files=false 26 | merge_derives=true 27 | trailing_semicolon=true 28 | trailing_comma="Vertical" 29 | 30 | error_on_line_overflow=true 31 | error_on_unformatted=true 32 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::render::DirectArguments, 3 | anyhow::Result, 4 | clap::{ 5 | Arg, 6 | ArgAction, 7 | }, 8 | std::{ 9 | collections::HashMap, 10 | str::FromStr, 11 | }, 12 | }; 13 | 14 | #[derive(Debug)] 15 | pub struct CallArgs { 16 | pub privileges: Privilege, 17 | pub command: Command, 18 | } 19 | 20 | impl CallArgs { 21 | pub async fn validate(&self) -> Result<()> { 22 | match self.privileges { 23 | | Privilege::Normal => { 24 | match &self.command { 25 | | Command::Direct(..) => Err(anyhow::anyhow!("experimental command")), 26 | | _ => Ok(()), 27 | } 28 | }, 29 | | Privilege::Experimental => Ok(()), 30 | } 31 | } 32 | } 33 | 34 | #[derive(Debug)] 35 | pub enum Privilege { 36 | Normal, 37 | Experimental, 38 | } 39 | 40 | #[derive(Debug)] 41 | pub enum ManualFormat { 42 | Manpages, 43 | Markdown, 44 | } 45 | 46 | #[derive(Debug)] 47 | pub enum Command { 48 | Manual { path: String, format: ManualFormat }, 49 | Autocomplete { path: String, shell: clap_complete::Shell }, 50 | Init, 51 | Schema, 52 | Render(crate::render::RenderArguments), 53 | Direct(crate::render::DirectArguments), 54 | } 55 | 56 | pub struct ClapArgumentLoader {} 57 | 58 | impl ClapArgumentLoader { 59 | pub fn root_command() -> clap::Command { 60 | let mut backend_values = Vec::from(["headless"]); 61 | if cfg!(feature = "backend+cli") { 62 | backend_values.push("cli"); 63 | } 64 | 65 | clap::Command::new("complate") 66 | .version(env!("CARGO_PKG_VERSION")) 67 | .about("A rusty text templating application for CLIs.") 68 | .author("replicadse ") 69 | .propagate_version(true) 70 | .subcommand_required(true) 71 | .args([Arg::new("experimental") 72 | .short('e') 73 | .long("experimental") 74 | .help("enables experimental features") 75 | .num_args(0)]) 76 | .subcommand( 77 | clap::Command::new("man") 78 | .about("Renders the manual.") 79 | .arg(clap::Arg::new("out").short('o').long("out").required(true)) 80 | .arg( 81 | clap::Arg::new("format") 82 | .short('f') 83 | .long("format") 84 | .value_parser(["manpages", "markdown"]) 85 | .required(true), 86 | ), 87 | ) 88 | .subcommand( 89 | clap::Command::new("autocomplete") 90 | .about("Renders shell completion scripts.") 91 | .arg(clap::Arg::new("out").short('o').long("out").required(true)) 92 | .arg( 93 | clap::Arg::new("shell") 94 | .short('s') 95 | .long("shell") 96 | .value_parser(["bash", "zsh", "fish", "elvish", "powershell"]) 97 | .required(true), 98 | ), 99 | ) 100 | .subcommand( 101 | clap::Command::new("init") 102 | .about("Initializes a dummy default configuration in \"./.complate/config.yaml\"."), 103 | ) 104 | .subcommand(clap::Command::new("schema").about("Renders the configuration schema.")) 105 | .subcommand( 106 | clap::Command::new("render") 107 | .about("Renders a template by replacing values as specified by the configuration.") 108 | .arg( 109 | clap::Arg::new("config") 110 | .short('c') 111 | .long("config") 112 | .help("The configuration file to use.") 113 | .default_value("./.complate/config.yaml"), 114 | ) 115 | .arg( 116 | clap::Arg::new("template") 117 | .short('t') 118 | .long("template") 119 | .help("Specify the template to use from the config and skip it's selection."), 120 | ) 121 | .arg( 122 | clap::Arg::new("trust") 123 | .long("trust") 124 | .help( 125 | "Enables the shell command execution. This is potentially insecure and should only be \ 126 | done for trustworthy sources.", 127 | ) 128 | .action(ArgAction::SetTrue), 129 | ) 130 | .arg( 131 | clap::Arg::new("loose") 132 | .short('l') 133 | .long("loose") 134 | .action(ArgAction::SetTrue) 135 | .help( 136 | "Defines that the templating is done in non-strict mode (allow missing value for \ 137 | variable).", 138 | ), 139 | ) 140 | .arg( 141 | clap::Arg::new("backend") 142 | .short('b') 143 | .long("backend") 144 | .help("The execution backend (cli=native-terminal, ui=ui emulator in terminal).") 145 | .value_parser(backend_values.clone()) 146 | .default_value("headless"), 147 | ) 148 | .arg( 149 | clap::Arg::new("value") 150 | .short('v') 151 | .long("value") 152 | .action(ArgAction::Append) 153 | .help("Overrides a certain value definition with a string."), 154 | ), 155 | ) 156 | .subcommand( 157 | clap::Command::new("direct") 158 | .about("Simply renders a template with a values file. No fancy business here.") 159 | .arg( 160 | clap::Arg::new("template") 161 | .short('t') 162 | .long("template") 163 | .help("Template file."), 164 | ) 165 | .arg(clap::Arg::new("values").short('v').long("values").help("Values file.")), 166 | ) 167 | } 168 | 169 | pub async fn load() -> Result { 170 | let root_command = Self::root_command(); 171 | let command_matches = root_command.get_matches(); 172 | 173 | let privileges = if command_matches.get_flag("experimental") { 174 | Privilege::Experimental 175 | } else { 176 | Privilege::Normal 177 | }; 178 | 179 | if let Some(subc) = command_matches.subcommand_matches("man") { 180 | Ok(CallArgs { 181 | command: Command::Manual { 182 | path: subc.get_one::("out").unwrap().into(), 183 | format: match subc.get_one::("format").unwrap().as_str() { 184 | | "manpages" => ManualFormat::Manpages, 185 | | "markdown" => ManualFormat::Markdown, 186 | | _ => return Err(anyhow::anyhow!("unknown format")), 187 | }, 188 | }, 189 | privileges, 190 | }) 191 | } else if let Some(subc) = command_matches.subcommand_matches("autocomplete") { 192 | Ok(CallArgs { 193 | command: Command::Autocomplete { 194 | path: subc.get_one::("out").unwrap().into(), 195 | shell: clap_complete::Shell::from_str(subc.get_one::("shell").unwrap().as_str()).unwrap(), 196 | }, 197 | privileges, 198 | }) 199 | } else if let Some(..) = command_matches.subcommand_matches("schema") { 200 | Ok(CallArgs { 201 | command: Command::Schema, 202 | privileges, 203 | }) 204 | } else if let Some(..) = command_matches.subcommand_matches("init") { 205 | Ok(CallArgs { 206 | command: Command::Init, 207 | privileges, 208 | }) 209 | } else if let Some(subc) = command_matches.subcommand_matches("direct") { 210 | Ok(CallArgs { 211 | command: Command::Direct(DirectArguments { 212 | template: subc.get_one::("template").unwrap().to_owned(), 213 | values: subc.get_one::("values").unwrap().to_owned(), 214 | }), 215 | privileges, 216 | }) 217 | } else if let Some(subc) = command_matches.subcommand_matches("render") { 218 | let config = std::fs::read_to_string(subc.get_one::("config").unwrap())?; 219 | let template = subc.get_one::("template").map(|v| v.into()); 220 | let shell_trust = if subc.get_flag("trust") { 221 | crate::render::ShellTrust::Ultimate 222 | } else { 223 | crate::render::ShellTrust::None 224 | }; 225 | let loose = subc.get_flag("loose"); 226 | 227 | let mut value_overrides = HashMap::::new(); 228 | if let Some(vo_arg) = subc.get_many::("value") { 229 | for vo in vo_arg { 230 | let spl = vo.splitn(2, "=").collect::>(); 231 | value_overrides.insert(spl[0].into(), spl[1].into()); 232 | } 233 | } 234 | let backend = match subc.get_one::("backend").unwrap().as_str() { 235 | | "headless" => crate::render::Backend::Headless, 236 | #[cfg(feature = "backend+cli")] 237 | | "cli" => crate::render::Backend::CLI, 238 | | _ => return Err(anyhow::anyhow!("no backend specified")), 239 | }; 240 | 241 | Ok(CallArgs { 242 | privileges, 243 | command: Command::Render(crate::render::RenderArguments { 244 | configuration: config, 245 | template, 246 | value_overrides, 247 | shell_trust, 248 | loose, 249 | backend, 250 | }), 251 | }) 252 | } else { 253 | return Err(anyhow::anyhow!("unknown command")); 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{ 2 | BTreeMap, 3 | HashMap, 4 | }; 5 | 6 | #[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] 7 | #[serde(rename_all = "snake_case")] 8 | pub struct Config { 9 | pub version: String, 10 | #[serde(with = "serde_yaml::with::singleton_map_recursive")] 11 | #[schemars(with = "BTreeMap")] 12 | pub templates: BTreeMap, 13 | } 14 | 15 | #[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] 16 | #[serde(rename_all = "snake_case", deny_unknown_fields)] 17 | pub struct Template { 18 | pub content: Content, 19 | #[schemars(with = "std::option::Option>")] 20 | pub variables: std::option::Option>, 21 | #[schemars(with = "std::option::Option>")] 22 | pub helpers: std::option::Option>, 23 | } 24 | 25 | #[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] 26 | #[serde(rename_all = "snake_case", deny_unknown_fields)] 27 | pub enum Content { 28 | File(String), 29 | Inline(String), 30 | } 31 | 32 | #[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] 33 | #[serde(rename_all = "snake_case", deny_unknown_fields)] 34 | pub enum OptionValue { 35 | Static(String), 36 | Shell(String), 37 | } 38 | 39 | #[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] 40 | #[serde(rename_all = "snake_case", deny_unknown_fields)] 41 | pub struct Option { 42 | pub display: String, 43 | pub value: OptionValue, 44 | } 45 | 46 | #[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] 47 | #[serde(rename_all = "snake_case", deny_unknown_fields)] 48 | pub enum VariableDefinition { 49 | Arg, 50 | Env(String), 51 | Static(String), 52 | Prompt(String), 53 | Shell(String), 54 | Select { 55 | text: String, 56 | #[schemars(with = "BTreeMap")] 57 | options: BTreeMap, 58 | }, 59 | Check { 60 | text: String, 61 | separator: String, 62 | #[schemars(with = "BTreeMap")] 63 | options: BTreeMap, 64 | }, 65 | } 66 | 67 | pub async fn default_config() -> &'static str { 68 | include_str!("../.complate/config.yaml") 69 | } 70 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod render; 3 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anyhow::Result, 3 | args::ManualFormat, 4 | std::path::PathBuf, 5 | }; 6 | 7 | mod args; 8 | mod config; 9 | mod reference; 10 | mod render; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | let cmd = args::ClapArgumentLoader::load().await?; 15 | cmd.validate().await?; 16 | 17 | match cmd.command { 18 | | args::Command::Manual { path, format } => { 19 | let out_path = PathBuf::from(path); 20 | std::fs::create_dir_all(&out_path)?; 21 | match format { 22 | | ManualFormat::Manpages => { 23 | reference::build_manpages(&out_path)?; 24 | }, 25 | | ManualFormat::Markdown => { 26 | reference::build_markdown(&out_path)?; 27 | }, 28 | } 29 | Ok(()) 30 | }, 31 | | args::Command::Autocomplete { path, shell } => { 32 | let out_path = PathBuf::from(path); 33 | std::fs::create_dir_all(&out_path)?; 34 | reference::build_shell_completion(&out_path, &shell)?; 35 | Ok(()) 36 | }, 37 | | args::Command::Init => { 38 | std::fs::create_dir_all("./.complate")?; 39 | std::fs::write("./.complate/config.yaml", config::default_config().await)?; 40 | Ok(()) 41 | }, 42 | | args::Command::Render(x) => { 43 | let res = render::select_and_render(x).await?; 44 | print!("{}", res); 45 | Ok(()) 46 | }, 47 | | args::Command::Direct(x) => { 48 | let template = std::fs::read_to_string(x.template)?; 49 | let values = std::fs::read_to_string(x.values)?; 50 | 51 | print!("{}", render::render_direct(template, values).await?); 52 | Ok(()) 53 | }, 54 | | args::Command::Schema => { 55 | println!( 56 | "{}", 57 | serde_json::to_string_pretty(&schemars::schema_for!(config::Config))? 58 | ); 59 | Ok(()) 60 | }, 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | fn setup_test() -> clitest::CliTestSetup { 67 | let mut x = clitest::CliTestSetup::new(); 68 | x.with_env("CFG", CONFIG_PATH); 69 | x 70 | } 71 | 72 | const CONFIG_PATH: &'static str = "./test/.complate/config.yaml"; 73 | 74 | #[test] 75 | fn template_var_static() { 76 | assert!( 77 | "alpha" 78 | == setup_test() 79 | .run("render -c $CFG -t var:static") 80 | .unwrap() 81 | .success() 82 | .unwrap() 83 | .stdout_str() 84 | ); 85 | } 86 | 87 | #[test] 88 | fn template_var_env() { 89 | assert!( 90 | "alpha" 91 | == setup_test() 92 | .with_env("alpha", "alpha") 93 | .run("render -c $CFG -t var:env") 94 | .unwrap() 95 | .success() 96 | .unwrap() 97 | .stdout_str() 98 | ); 99 | } 100 | 101 | #[test] 102 | fn template_var_shell() { 103 | assert!( 104 | "alpha" 105 | == setup_test() 106 | .with_env("alpha", "alpha") 107 | .run("render -c $CFG -t var:shell --trust") 108 | .unwrap() 109 | .success() 110 | .unwrap() 111 | .stdout_str() 112 | ); 113 | } 114 | 115 | #[test] 116 | fn template_overrides() { 117 | assert!( 118 | "alpha" 119 | == setup_test() 120 | .run("render -c $CFG -t override -v a.alpha=\"alpha\"") 121 | .unwrap() 122 | .success() 123 | .unwrap() 124 | .stdout_str() 125 | ); 126 | } 127 | 128 | #[test] 129 | fn template_helper() { 130 | assert_eq!( 131 | "bananarama", 132 | setup_test() 133 | .run("render -c $CFG -t helper --trust") 134 | .unwrap() 135 | .success() 136 | .unwrap() 137 | .stdout_str() 138 | ); 139 | } 140 | 141 | #[test] 142 | fn template_vals_multiple() { 143 | assert!( 144 | "alpha\nbravo" 145 | == setup_test() 146 | .run("render -c $CFG --trust -t vals:multiple -v a.alpha=alpha -v b.bravo=bravo") 147 | .unwrap() 148 | .success() 149 | .unwrap() 150 | .stdout_str() 151 | ); 152 | } 153 | 154 | #[test] 155 | fn template_var_argument() { 156 | assert!( 157 | "alpha" 158 | == setup_test() 159 | .run("render -c $CFG -t var:argument -v a.alpha=alpha") 160 | .unwrap() 161 | .success() 162 | .unwrap() 163 | .stdout_str() 164 | ); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/reference.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::args::ClapArgumentLoader, 3 | anyhow::Result, 4 | clap_complete::Shell, 5 | clap_mangen::Man, 6 | std::{ 7 | fs::File, 8 | io::{ 9 | Error, 10 | Write, 11 | }, 12 | path::Path, 13 | }, 14 | }; 15 | 16 | fn collect_commands() -> Vec<(String, clap::Command)> { 17 | let mut cmds: Vec<(String, clap::Command)> = Vec::new(); 18 | fn rec_add(path: &str, cmds: &mut Vec<(String, clap::Command)>, parent: &clap::Command) { 19 | let new_path = &format!("{}-{}", path, parent.get_name()); 20 | cmds.push((new_path.into(), parent.clone())); 21 | for subc in parent.get_subcommands() { 22 | rec_add(new_path, cmds, subc); 23 | } 24 | } 25 | rec_add("", &mut cmds, &ClapArgumentLoader::root_command()); 26 | cmds 27 | } 28 | 29 | pub fn build_shell_completion(outdir: &Path, shell: &Shell) -> Result<()> { 30 | let mut app = ClapArgumentLoader::root_command(); 31 | clap_complete::generate_to(*shell, &mut app, "complate", &outdir)?; 32 | 33 | Ok(()) 34 | } 35 | 36 | pub fn build_markdown(outdir: &Path) -> Result<(), Error> { 37 | for cmd in collect_commands() { 38 | let file = Path::new(&outdir).join(&format!("{}.md", cmd.0.strip_prefix("-").unwrap())); 39 | let mut file = File::create(&file)?; 40 | file.write(clap_markdown::help_markdown_command(&cmd.1).as_bytes())?; 41 | } 42 | Ok(()) 43 | } 44 | 45 | pub fn build_manpages(outdir: &Path) -> Result<(), Error> { 46 | for cmd in collect_commands() { 47 | let file = Path::new(&outdir).join(&format!("{}.1", cmd.0.strip_prefix("-").unwrap())); 48 | let mut file = File::create(&file)?; 49 | Man::new(cmd.1).render(&mut file)?; 50 | } 51 | 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /src/render/cli.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::UserInput, 3 | anyhow::Result, 4 | async_trait::async_trait, 5 | std::collections::{ 6 | BTreeMap, 7 | HashMap, 8 | }, 9 | }; 10 | 11 | pub struct CLIBackend<'a> { 12 | shell_trust: &'a super::ShellTrust, 13 | } 14 | 15 | impl<'a> CLIBackend<'a> { 16 | pub fn new(shell_trust: &'a super::ShellTrust) -> Self { 17 | Self { shell_trust } 18 | } 19 | } 20 | 21 | #[async_trait] 22 | impl<'a> UserInput for CLIBackend<'a> { 23 | async fn prompt(&self, text: &str) -> Result { 24 | match dialoguer::Input::new().allow_empty(true).with_prompt(text).interact() { 25 | | Ok(res) => Ok(res), 26 | | Err(_) => Err(anyhow::anyhow!("interaction aborted")), 27 | } 28 | } 29 | 30 | async fn select(&self, prompt: &str, options: &BTreeMap) -> Result { 31 | let keys = options.keys().cloned().collect::>(); 32 | let display_vals = options.values().map(|x| x.display.to_owned()).collect::>(); 33 | 34 | let result_idx = dialoguer::Select::new() 35 | .with_prompt(prompt) 36 | .items(&display_vals) 37 | .default(0) 38 | .interact()?; 39 | match &options[&keys[result_idx]].value { 40 | | super::OptionValue::Static(x) => Ok(x.into()), 41 | | super::OptionValue::Shell(cmd) => super::shell(cmd, &HashMap::new(), &self.shell_trust).await, 42 | } 43 | } 44 | 45 | async fn check( 46 | &self, 47 | prompt: &str, 48 | separator: &str, 49 | options: &BTreeMap, 50 | ) -> Result { 51 | let keys = options.keys().cloned().collect::>(); 52 | let display_vals = options.values().map(|x| x.display.to_owned()).collect::>(); 53 | 54 | let indices = dialoguer::MultiSelect::new() 55 | .with_prompt(prompt) 56 | .items(&display_vals) 57 | .interact()?; 58 | 59 | match indices.len() { 60 | | 0usize => Ok("".into()), 61 | | _ => { 62 | let mut d = String::new(); 63 | for i in indices { 64 | let v = match &options[&keys[i]].value { 65 | | super::OptionValue::Static(x) => x.into(), 66 | | super::OptionValue::Shell(cmd) => { 67 | super::shell(&cmd, &HashMap::new(), &self.shell_trust).await? 68 | }, 69 | }; 70 | d.push_str(&v); 71 | d.push_str(separator); 72 | } 73 | d.truncate(d.len() - 2); 74 | Ok(d) 75 | }, 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/render/headless.rs: -------------------------------------------------------------------------------- 1 | use { 2 | anyhow::Result, 3 | async_trait::async_trait, 4 | }; 5 | 6 | pub struct HeadlessBackend {} 7 | 8 | impl HeadlessBackend { 9 | pub fn new() -> Self { 10 | Self {} 11 | } 12 | } 13 | 14 | #[async_trait] 15 | impl super::UserInput for HeadlessBackend { 16 | async fn prompt(&self, _text: &str) -> Result { 17 | Err(anyhow::anyhow!("can not prompt in headless backend").into()) 18 | } 19 | 20 | async fn select( 21 | &self, 22 | _prompt: &str, 23 | _options: &std::collections::BTreeMap, 24 | ) -> Result { 25 | Err(anyhow::anyhow!("can not prompt in headless backend").into()) 26 | } 27 | 28 | async fn check( 29 | &self, 30 | _prompt: &str, 31 | _separator: &str, 32 | _options: &std::collections::BTreeMap, 33 | ) -> Result { 34 | Err(anyhow::anyhow!("can not prompt in headless backend").into()) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/render/mod.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::config::{ 3 | Config, 4 | Content, 5 | OptionValue, 6 | Template, 7 | VariableDefinition, 8 | }, 9 | anyhow::Result, 10 | async_trait::async_trait, 11 | fancy_regex::Regex, 12 | handlebars::RenderError, 13 | std::{ 14 | collections::{ 15 | BTreeMap, 16 | HashMap, 17 | }, 18 | env, 19 | }, 20 | }; 21 | 22 | #[cfg(feature = "backend+cli")] 23 | pub mod cli; 24 | pub mod headless; 25 | 26 | #[derive(Debug)] 27 | pub enum Backend { 28 | Headless, 29 | #[cfg(feature = "backend+cli")] 30 | CLI, 31 | } 32 | 33 | #[derive(Debug)] 34 | pub struct RenderArguments { 35 | pub configuration: String, 36 | pub template: Option, 37 | pub value_overrides: HashMap, 38 | pub shell_trust: ShellTrust, 39 | pub loose: bool, 40 | pub backend: Backend, 41 | } 42 | 43 | #[derive(Debug)] 44 | pub struct DirectArguments { 45 | pub template: String, 46 | pub values: String, 47 | } 48 | 49 | #[derive(Debug, Eq, PartialEq)] 50 | pub enum ShellTrust { 51 | None, 52 | Ultimate, 53 | } 54 | 55 | pub async fn make_handlebars<'a>( 56 | variable_values: &HashMap, 57 | helpers: &'a std::option::Option>, 58 | shell_trust: &ShellTrust, 59 | strict: bool, 60 | ) -> Result<(handlebars::Handlebars<'a>, serde_json::Value)> { 61 | fn recursive_add(namespace: &mut std::collections::VecDeque, parent: &mut serde_json::Value, value: &str) { 62 | let current_namespace = namespace.pop_front().unwrap(); 63 | match namespace.len() { 64 | | 0 => { 65 | parent 66 | .as_object_mut() 67 | .unwrap() 68 | .entry(¤t_namespace) 69 | .or_insert(serde_json::Value::String(value.into())); 70 | }, 71 | | _ => { 72 | let p = parent 73 | .as_object_mut() 74 | .unwrap() 75 | .entry(¤t_namespace) 76 | .or_insert(serde_json::Value::Object(serde_json::Map::new())); 77 | recursive_add(namespace, p, value); 78 | }, 79 | } 80 | } 81 | 82 | let mut values_json = serde_json::Value::Object(serde_json::Map::new()); 83 | for val in variable_values { 84 | let namespaces_vec: Vec = val.0.split('.').map(|s| s.to_string()).collect(); 85 | let mut namespaces = std::collections::VecDeque::from(namespaces_vec); 86 | recursive_add(&mut namespaces, &mut values_json, val.1); 87 | } 88 | 89 | let mut hb = handlebars::Handlebars::new(); 90 | hb.register_escape_fn(|s| s.into()); 91 | hb.set_strict_mode(strict); 92 | 93 | if let Some(helpers) = helpers { 94 | if helpers.len() > 0 && shell_trust != &ShellTrust::Ultimate { 95 | return Err(anyhow::anyhow!("need trust for executing helper functions").into()); 96 | } 97 | 98 | for helper in helpers { 99 | let h_func = move |h: &handlebars::Helper, 100 | _: &handlebars::Handlebars, 101 | _: &handlebars::Context, 102 | _: &mut handlebars::RenderContext, 103 | out: &mut dyn handlebars::Output| 104 | -> handlebars::HelperResult { 105 | let param = h.param(0).ok_or(RenderError::new("parameter is not a string"))?; 106 | let cmd = helper.1; 107 | 108 | let output = std::process::Command::new("sh") 109 | .arg("-c") 110 | .arg(cmd) 111 | .env( 112 | "VALUE", 113 | param 114 | .value() 115 | .as_str() 116 | .ok_or(RenderError::new("parameter is not a string"))?, 117 | ) 118 | .output()?; 119 | if output.status.code().unwrap() != 0 { 120 | return Err(RenderError::new("failed to get command status")); 121 | } 122 | 123 | out.write(String::from_utf8(output.stdout)?.as_str())?; 124 | Ok(()) 125 | }; 126 | hb.register_helper(helper.0, Box::new(h_func)) 127 | } 128 | } 129 | 130 | Ok((hb, values_json)) 131 | } 132 | 133 | pub async fn select_template<'a>( 134 | config: &'a Config, 135 | backend: &Backend, 136 | shell_trust: &ShellTrust, 137 | ) -> Result<&'a Template> { 138 | let templates = config.templates.keys().cloned().collect::>(); 139 | let mut template_map = BTreeMap::new(); 140 | for t in templates { 141 | template_map.insert(t.to_owned(), crate::config::Option { 142 | display: t.to_owned(), 143 | value: OptionValue::Static(t.into()), 144 | }); 145 | } 146 | 147 | let be = backend.to_input(shell_trust)?; 148 | let selection = be.select("", &template_map).await?; 149 | 150 | match config.templates.get(&selection) { 151 | | Some(x) => Ok(x), 152 | | None => Err(anyhow::anyhow!("invalid template selection")), 153 | } 154 | } 155 | 156 | pub async fn populate_variables( 157 | vars: &std::collections::HashMap, 158 | value_overrides: &std::collections::HashMap, 159 | shell_trust: &ShellTrust, 160 | backend: &Backend, 161 | prefix: Option, 162 | ) -> Result> { 163 | let mut values = HashMap::::new(); 164 | for v_override in value_overrides { 165 | values.insert(v_override.0.into(), v_override.1.into()); 166 | } 167 | 168 | for var in vars { 169 | if None == values.get(var.0) { 170 | values.insert(var.0.into(), var.1.execute(shell_trust, backend).await?); 171 | } 172 | } 173 | 174 | let values = values 175 | .iter() 176 | .map(|(k, v)| { 177 | let mut key = k.clone(); 178 | if let Some(p) = &prefix { 179 | key = format!("{}.{}", p, key); 180 | } 181 | (key, v.clone()) 182 | }) 183 | .collect::>(); 184 | Ok(values) 185 | } 186 | 187 | pub async fn render_template( 188 | template: &Template, 189 | value_overrides: &HashMap, 190 | shell_trust: &ShellTrust, 191 | backend: &Backend, 192 | strict: bool, 193 | ) -> Result { 194 | let template_str = match &template.content { 195 | | Content::Inline(x) => x.into(), 196 | | Content::File(x) => std::fs::read_to_string(x)?, 197 | }; 198 | 199 | let values = if let Some(variables) = &template.variables { 200 | populate_variables(variables, value_overrides, shell_trust, backend, None).await? 201 | } else { 202 | HashMap::<_, _>::new() 203 | }; 204 | 205 | let hb = make_handlebars(&values, &template.helpers, shell_trust, strict).await?; 206 | hb.0.render_template(&template_str, &hb.1) 207 | .map_err(|e| anyhow::anyhow!(e)) 208 | } 209 | 210 | pub async fn render_direct(template: String, values: String) -> Result { 211 | let values = serde_json::from_str::(&values)?; 212 | 213 | let mut hb = handlebars::Handlebars::new(); 214 | hb.register_escape_fn(|s| s.into()); 215 | hb.set_strict_mode(true); 216 | 217 | Ok(hb.render_template(&template, &values)?) 218 | } 219 | 220 | pub async fn select_and_render(invoke_options: RenderArguments) -> Result { 221 | #[derive(serde::Deserialize)] 222 | struct WithVersion { 223 | version: String, 224 | } 225 | let version_check: WithVersion = serde_yaml::from_str(&invoke_options.configuration) 226 | .or::(Err(anyhow::anyhow!("config missing version field")))?; 227 | 228 | let version_regex = Regex::new("^([0-9]+)\\.([0-9]+)$")?; 229 | if !version_regex.is_match(&version_check.version)? { 230 | return Err(anyhow::anyhow!("invalid version: {}", version_check.version)); 231 | } 232 | let expected_version = env!("CARGO_PKG_VERSION").split(".").collect::>()[..2].join("."); 233 | if env!("CARGO_PKG_VERSION") != "0.0.0" { 234 | if &version_check.version != &expected_version { 235 | return Err(anyhow::anyhow!("config file version mismatch to binary")); 236 | } 237 | } 238 | 239 | let cfg: Config = serde_yaml::from_str(&invoke_options.configuration)?; 240 | let template = match &invoke_options.template { 241 | | Some(x) => { 242 | cfg.templates 243 | .get(x) 244 | .ok_or_else(|| anyhow::anyhow!("template not found"))? 245 | }, 246 | | None => select_template(&cfg, &invoke_options.backend, &invoke_options.shell_trust).await?, 247 | }; 248 | 249 | render_template( 250 | template, 251 | &invoke_options.value_overrides, 252 | &invoke_options.shell_trust, 253 | &invoke_options.backend, 254 | !invoke_options.loose, 255 | ) 256 | .await 257 | } 258 | 259 | #[async_trait] 260 | pub trait Resolve { 261 | async fn execute(&self, shell_trust: &ShellTrust, backend: &Backend) -> Result; 262 | } 263 | 264 | #[async_trait] 265 | pub trait UserInput: Send+Sync { 266 | async fn prompt(&self, text: &str) -> Result; 267 | async fn select(&self, prompt: &str, options: &BTreeMap) -> Result; 268 | async fn check( 269 | &self, 270 | prompt: &str, 271 | separator: &str, 272 | options: &BTreeMap, 273 | ) -> Result; 274 | } 275 | 276 | impl Backend { 277 | pub fn to_input<'a>(&self, shell_trust: &'a ShellTrust) -> Result> { 278 | Ok(match self { 279 | | Backend::Headless => Box::new(headless::HeadlessBackend::new()) as Box, 280 | #[cfg(feature = "backend+cli")] 281 | | Backend::CLI => Box::new(cli::CLIBackend::new(shell_trust)) as Box, 282 | }) 283 | } 284 | } 285 | 286 | #[async_trait] 287 | impl Resolve for VariableDefinition { 288 | async fn execute(&self, shell_trust: &ShellTrust, backend: &Backend) -> Result { 289 | let backend_impl = backend.to_input(shell_trust)?; 290 | 291 | match self { 292 | | VariableDefinition::Arg => Err(anyhow::anyhow!("variable missing")), 293 | | VariableDefinition::Env(v) => Ok(env::var(v)?), 294 | | VariableDefinition::Static(v) => Ok(v.into()), 295 | | VariableDefinition::Prompt(v) => backend_impl.prompt(v).await, 296 | | VariableDefinition::Shell(cmd) => shell(cmd, &HashMap::new(), shell_trust).await, 297 | | VariableDefinition::Select { text, options } => backend_impl.select(text, options).await, 298 | | VariableDefinition::Check { 299 | text, 300 | separator, 301 | options, 302 | } => backend_impl.check(text, separator, options).await, 303 | } 304 | } 305 | } 306 | 307 | async fn shell(command: &str, env: &HashMap, shell_trust: &ShellTrust) -> Result { 308 | match shell_trust { 309 | | ShellTrust::None => return Err(anyhow::anyhow!("need trust for executing shell commands")), 310 | | ShellTrust::Ultimate => {}, 311 | } 312 | 313 | let output = std::process::Command::new("sh") 314 | .arg("-c") 315 | .arg(command) 316 | .envs(env) 317 | .output()?; 318 | if output.status.code().unwrap() != 0 { 319 | return Err(anyhow::anyhow!("shell command error:\n{}", command)); 320 | } 321 | Ok(String::from_utf8(output.stdout)?) 322 | } 323 | -------------------------------------------------------------------------------- /test/.complate/config.yaml: -------------------------------------------------------------------------------- 1 | version: 0.15 2 | templates: 3 | "var:static": 4 | content: 5 | inline: |- 6 | {{ a.alpha }} 7 | variables: 8 | a.alpha: 9 | static: "alpha" 10 | 11 | "var:env": 12 | content: 13 | inline: |- 14 | {{ a.alpha }} 15 | variables: 16 | a.alpha: 17 | env: "alpha" 18 | 19 | "var:shell": 20 | content: 21 | inline: |- 22 | {{ a.alpha }} 23 | variables: 24 | a.alpha: 25 | shell: 'printf "alpha"' 26 | 27 | "vals:multiple": 28 | content: 29 | inline: |- 30 | {{ a.alpha }} 31 | {{ b.bravo }} 32 | variables: 33 | a.alpha: 34 | shell: "printf alpha" 35 | b.bravo: 36 | shell: "printf bravo" 37 | 38 | "var:argument": 39 | content: 40 | inline: |- 41 | {{ a.alpha }} 42 | variables: 43 | a.alpha: arg 44 | 45 | "override": 46 | content: 47 | inline: |- 48 | {{ a.alpha }} 49 | variables: 50 | a.alpha: 51 | env: "DOES_NOT_EXIST" 52 | 53 | "helper": 54 | content: 55 | inline: |- 56 | {{ _decode "YmFuYW5hcmFtYQ==" }} 57 | helpers: 58 | "_decode": |- 59 | printf "$VALUE" | base64 -D 60 | --------------------------------------------------------------------------------