├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── jsonnet.rs ├── jsonnet-sys ├── Cargo.toml ├── build.rs └── src │ └── lib.rs └── src ├── lib.rs ├── string.rs └── value.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gitsubmodule 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: cargo 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | - package-ecosystem: cargo 12 | directory: /jsonnet-sys 13 | schedule: 14 | interval: weekly 15 | - package-ecosystem: github-actions 16 | directory: / 17 | schedule: 18 | interval: weekly 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | 3 | on: [pull_request, push] 4 | 5 | env: 6 | RUST_BACKTRACE: '1' 7 | 8 | jobs: 9 | 10 | check: 11 | name: Check 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: check 23 | 24 | test: 25 | name: Test 26 | runs-on: ${{ matrix.os }} 27 | strategy: 28 | matrix: 29 | os: [ubuntu-latest, windows-latest, macOS-latest] 30 | rust: [stable] 31 | steps: 32 | - uses: actions/checkout@v2 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | profile: minimal 36 | toolchain: ${{ matrix.rust }} 37 | override: true 38 | - uses: actions-rs/cargo@v1 39 | with: 40 | command: test 41 | args: --verbose --no-fail-fast --manifest-path=jsonnet-sys/Cargo.toml 42 | - uses: actions-rs/cargo@v1 43 | with: 44 | command: test 45 | args: --verbose --no-fail-fast 46 | 47 | example: 48 | name: Run jsonnet example 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@v2 52 | - uses: actions-rs/toolchain@v1 53 | with: 54 | profile: minimal 55 | toolchain: stable 56 | override: true 57 | - uses: actions-rs/cargo@v1 58 | with: 59 | command: build 60 | args: --examples 61 | - uses: MeilCli/test-command-action@v3.4.10 62 | with: 63 | command: ./target/debug/examples/jsonnet eval --exec 2+2 64 | expect_contain: 4 65 | 66 | rustfmt: 67 | name: Rustfmt 68 | runs-on: ubuntu-latest 69 | steps: 70 | - uses: actions/checkout@v2 71 | - uses: actions-rs/toolchain@v1 72 | with: 73 | profile: minimal 74 | toolchain: stable 75 | override: true 76 | components: rustfmt 77 | - uses: actions-rs/cargo@v1 78 | with: 79 | command: fmt 80 | args: --all --manifest-path=jsonnet-sys/Cargo.toml -- --check 81 | - uses: actions-rs/cargo@v1 82 | with: 83 | command: fmt 84 | args: --all -- --check 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "jsonnet-sys/jsonnet"] 2 | path = jsonnet-sys/jsonnet 3 | url = https://github.com/google/jsonnet.git 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonnet-rs" 3 | version = "0.17.0" 4 | authors = ["Angus Lees "] 5 | license = "Apache-2.0" 6 | keywords = ["json", "jsonnet"] 7 | edition = "2018" 8 | readme = "README.md" 9 | documentation = "https://docs.rs/jsonnet-rs" 10 | repository = "https://github.com/anguslees/rust-jsonnet.git" 11 | description = """ 12 | Bindings to libjsonnet (https://jsonnet.org/), a domain specific 13 | configuration language that helps you define JSON data. 14 | """ 15 | 16 | [lib] 17 | name = "jsonnet" 18 | 19 | [dependencies] 20 | libc = "0.2" 21 | jsonnet-sys = { path = "jsonnet-sys", version = "0.17.0" } 22 | 23 | [dev-dependencies] 24 | clap = "2.31" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-jsonnet 2 | 3 | [![crates.io Version Badge](https://img.shields.io/crates/v/jsonnet-rs.svg)](https://crates.io/crates/jsonnet-rs) 4 | [![docs.rs Doc Badge](https://docs.rs/jsonnet-rs/badge.svg)](https://docs.rs/jsonnet-rs) 5 | [![Build Status Badge](https://github.com/anguslees/rust-jsonnet/workflows/Continuous%20integration/badge.svg?branch=master)](https://github.com/anguslees/rust-jsonnet/actions) 6 | 7 | libjsonnet bindings for Rust 8 | 9 | ```toml 10 | [dependencies] 11 | jsonnet-rs = "0.6" 12 | ``` 13 | 14 | ### Building rust-jsonnet 15 | 16 | Building `jsonnet-sys` requires gcc (via the `cc` Rust crate). 17 | `libjsonnet` is not typically available as an existing shared library, 18 | so `jsonnet-sys` builds and statically links its own copy. 19 | 20 | ```sh 21 | $ git clone https://github.com/anguslees/rust-jsonnet 22 | $ cd rust-jsonnet 23 | $ cargo build 24 | ``` 25 | 26 | See also `examples/jsonnet.rs` for an almost-but-not-quite drop-in 27 | replacement for the official `jsonnet` executable implemented using 28 | this library. 29 | 30 | # License 31 | 32 | `rust-jsonnet` is distributed under the terms of the Apache License 33 | (Version 2.0), the same as `libjsonnet` itself. 34 | 35 | See LICENSE for details. 36 | -------------------------------------------------------------------------------- /examples/jsonnet.rs: -------------------------------------------------------------------------------- 1 | /// An almost-but-not-quite drop-in for the official `jsonnet` 2 | /// executable. 3 | use clap::{crate_authors, crate_version, value_t, App, AppSettings, Arg, ArgMatches, SubCommand}; 4 | use jsonnet::{jsonnet_version, JsonnetVm}; 5 | use std::borrow::Cow; 6 | use std::error::Error as StdError; 7 | use std::io::{self, Write}; 8 | use std::{env, process}; 9 | 10 | fn typed_arg_or_exit<'a, T>(matches: &ArgMatches<'a>, arg: &str) -> Option 11 | where 12 | T: std::str::FromStr, 13 | { 14 | if matches.is_present(arg) { 15 | let v = value_t!(matches, arg, T).unwrap_or_else(|e| e.exit()); 16 | Some(v) 17 | } else { 18 | None 19 | } 20 | } 21 | 22 | /// Parse "foo" into ("foo", None) and "foo=bar" into ("foo", Some("bar")) 23 | fn parse_kv(s: &str) -> (&str, Option<&str>) { 24 | match s.find('=') { 25 | Some(i) => (&s[..i], Some(&s[i + 1..])), 26 | None => (s, None), 27 | } 28 | } 29 | 30 | #[test] 31 | fn test_parse_kv() { 32 | assert_eq!(parse_kv("foo=bar"), ("foo", Some("bar"))); 33 | assert_eq!(parse_kv("foo"), ("foo", None)); 34 | assert_eq!(parse_kv("foo="), ("foo", Some(""))); 35 | } 36 | 37 | fn build_cli<'a, 'b>(version: &'b str) -> App<'a, 'b> { 38 | App::new("jsonnet") 39 | .version(version) 40 | .author(crate_authors!()) 41 | .about("Jsonnet command line tool") 42 | .setting(AppSettings::VersionlessSubcommands) 43 | .setting(AppSettings::SubcommandRequiredElseHelp) 44 | .subcommand(SubCommand::with_name("eval") 45 | .about("Evaluate a jsonnet file/expression") 46 | .arg(Arg::with_name("exec") 47 | .short("e") 48 | .long("exec") 49 | .help("Treat INPUT as code")) 50 | .arg(Arg::with_name("incdir") 51 | .short("j") 52 | .long("jpath") 53 | .value_name("DIR") 54 | .multiple(true) 55 | .use_delimiter(false) 56 | .number_of_values(1) 57 | .help("Specify an additional library search dir")) 58 | .arg(Arg::with_name("string") 59 | .short("S") 60 | .long("string") 61 | .help("Expect a string, manifest as plain text")) 62 | .arg(Arg::with_name("max-stack") 63 | .short("s") 64 | .long("max-stack") 65 | .value_name("N") 66 | .help("Number of allowed stack frames")) 67 | .arg(Arg::with_name("max-trace") 68 | .short("t") 69 | .long("max-trace") 70 | .value_name("N") 71 | .help("Max length of stack trace before cropping")) 72 | .arg(Arg::with_name("gc-min-objects") 73 | .long("gc-min-objects") 74 | .value_name("N") 75 | .help("Do not run garbage collector until this many")) 76 | .arg(Arg::with_name("gc-growth-trigger") 77 | .long("gc-growth-trigger") 78 | .value_name("N") 79 | .help("Run garbage collector after this amount of object growth")) 80 | .arg(Arg::with_name("ext-var") 81 | .short("V") 82 | .long("ext-str") 83 | .value_name("VAR[=VAL]") 84 | .multiple(true) 85 | .number_of_values(1) 86 | .use_delimiter(false) 87 | .help("Provide 'external' variable as a string. If is omitted, get from env var ")) 88 | .arg(Arg::with_name("ext-code") 89 | .long("ext-code") 90 | .value_name("VAR[=CODE]") 91 | .multiple(true) 92 | .number_of_values(1) 93 | .use_delimiter(false) 94 | .help("Provide 'external' variable as code. If is omitted, get from env var ")) 95 | .arg(Arg::with_name("tla-var") 96 | .short("A") 97 | .long("tla-str") 98 | .value_name("VAR[=VAL]") 99 | .multiple(true) 100 | .number_of_values(1) 101 | .use_delimiter(false) 102 | .help("Provide 'top-level argument' as a string. If is omitted, get from env var ")) 103 | .arg(Arg::with_name("tla-code") 104 | .long("tla-code") 105 | .value_name("VAR[=CODE]") 106 | .multiple(true) 107 | .number_of_values(1) 108 | .use_delimiter(false) 109 | .help("Provide 'top-level argument' as code. If is omitted, get from env var ")) 110 | .arg(Arg::with_name("INPUT") 111 | .required(true) 112 | .help("Input jsonnet file"))) 113 | .subcommand(SubCommand::with_name("fmt") 114 | .about("Reformat a jsonnet file/expression") 115 | .arg(Arg::with_name("exec") 116 | .short("e") 117 | .long("exec") 118 | .help("Treat INPUT as code")) 119 | .arg(Arg::with_name("indent") 120 | .short("n") 121 | .long("indent") 122 | .value_name("N") 123 | .default_value("0") 124 | .help("Number of spaces to indent by, 0 means no change")) 125 | .arg(Arg::with_name("max-blank-lines") 126 | .long("max-blank-lines") 127 | .value_name("N") 128 | .default_value("2") 129 | .help("Max vertical spacing, 0 means no change")) 130 | .arg(Arg::with_name("string-style") 131 | .long("string-style") 132 | .possible_values(&["d", "s", "l"]) 133 | .default_value("l") 134 | .value_name("C") 135 | .help("Enforce double, single quotes or 'leave'")) 136 | .arg(Arg::with_name("comment-style") 137 | .long("comment-style") 138 | .possible_values(&["h", "s", "l"]) 139 | .default_value("l") 140 | .value_name("C") 141 | .help("# (h) // (s) or 'leave'. Never changes she-bang")) 142 | .arg(Arg::with_name("no-pretty-field-names") 143 | .long("no-pretty-field-names") 144 | .help("Don't use syntax sugar for fields and indexing")) 145 | .arg(Arg::with_name("pad-arrays") 146 | .long("pad-arrays") 147 | .help("[ 1, 2, 3 ] instead of [1, 2, 3]")) 148 | .arg(Arg::with_name("no-pad-objects") 149 | .long("no-pad-objects") 150 | .help("{ x: 1, x: 2 } instead of {x: 1, y: 2}")) 151 | .arg(Arg::with_name("no-sort-imports") 152 | .long("no-sort-imports") 153 | .help("Don't sort imports")) 154 | .arg(Arg::with_name("debug-desugaring") 155 | .long("debug-desugaring") 156 | .help("Unparse the desugared AST without executing it")) 157 | .arg(Arg::with_name("INPUT") 158 | .required(true) 159 | .help("Input jsonnet file"))) 160 | } 161 | 162 | fn eval<'a, 'b>( 163 | vm: &'a mut JsonnetVm, 164 | matches: &ArgMatches<'b>, 165 | ) -> Result<(), Box> { 166 | if let Some(n) = typed_arg_or_exit(matches, "max-stack") { 167 | vm.max_stack(n); 168 | } 169 | if let Some(n) = typed_arg_or_exit(matches, "max-trace") { 170 | vm.max_trace(Some(n)); 171 | } 172 | if let Some(n) = typed_arg_or_exit(matches, "gc-min-objects") { 173 | vm.gc_min_objects(n); 174 | } 175 | if let Some(n) = typed_arg_or_exit(matches, "gc-growth-trigger") { 176 | vm.gc_growth_trigger(n); 177 | } 178 | if matches.is_present("string") { 179 | vm.string_output(true); 180 | } 181 | if let Some(paths) = matches.values_of_os("incdir") { 182 | for path in paths { 183 | vm.jpath_add(path); 184 | } 185 | } 186 | if let Some(vars) = matches.values_of("ext-var") { 187 | for (var, val) in vars.map(parse_kv) { 188 | let val = val 189 | .map(Cow::from) 190 | .or_else(|| env::var(var).ok().map(Cow::from)) 191 | .unwrap_or("".into()); 192 | vm.ext_var(var, &val); 193 | } 194 | } 195 | if let Some(vars) = matches.values_of("ext-code") { 196 | for (var, val) in vars.map(parse_kv) { 197 | let val = val 198 | .map(Cow::from) 199 | .or_else(|| env::var(var).ok().map(Cow::from)) 200 | .unwrap_or("".into()); 201 | vm.ext_code(var, &val); 202 | } 203 | } 204 | if let Some(vars) = matches.values_of("tla-var") { 205 | for (var, val) in vars.map(parse_kv) { 206 | let val = val 207 | .map(Cow::from) 208 | .or_else(|| env::var(var).ok().map(Cow::from)) 209 | .unwrap_or("".into()); 210 | vm.tla_var(var, &val); 211 | } 212 | } 213 | if let Some(vars) = matches.values_of("tla-code") { 214 | for (var, val) in vars.map(parse_kv) { 215 | let val = val 216 | .map(Cow::from) 217 | .or_else(|| env::var(var).ok().map(Cow::from)) 218 | .unwrap_or("".into()); 219 | vm.tla_code(var, &val); 220 | } 221 | } 222 | 223 | let output = if matches.is_present("exec") { 224 | let expr = matches.value_of("INPUT").unwrap(); 225 | vm.evaluate_snippet("INPUT", expr)? 226 | } else { 227 | let file = matches.value_of_os("INPUT").unwrap(); 228 | vm.evaluate_file(file)? 229 | }; 230 | 231 | print!("{}", output); 232 | Ok(()) 233 | } 234 | 235 | fn fmt<'a, 'b>( 236 | vm: &'a mut JsonnetVm, 237 | matches: &ArgMatches<'b>, 238 | ) -> Result<(), Box> { 239 | if let Some(n) = typed_arg_or_exit(matches, "indent") { 240 | vm.fmt_indent(n); 241 | } 242 | if let Some(n) = typed_arg_or_exit(matches, "max-blank-lines") { 243 | vm.fmt_max_blank_lines(n); 244 | } 245 | if let Some(v) = matches.value_of("string-style") { 246 | let v = match v { 247 | "d" => jsonnet::FmtString::Double, 248 | "s" => jsonnet::FmtString::Single, 249 | "l" => jsonnet::FmtString::Leave, 250 | _ => unreachable!(), 251 | }; 252 | vm.fmt_string(v); 253 | } 254 | if let Some(v) = matches.value_of("comment-style") { 255 | let v = match v { 256 | "h" => jsonnet::FmtComment::Hash, 257 | "s" => jsonnet::FmtComment::Slash, 258 | "l" => jsonnet::FmtComment::Leave, 259 | _ => unreachable!(), 260 | }; 261 | vm.fmt_comment(v); 262 | } 263 | if matches.is_present("no-pretty-field-names") { 264 | vm.fmt_pretty_field_names(false); 265 | } 266 | if matches.is_present("pad-arrays") { 267 | vm.fmt_pad_arrays(true); 268 | } 269 | if matches.is_present("no-pad-objects") { 270 | vm.fmt_pad_objects(false); 271 | } 272 | if matches.is_present("no-sort-imports") { 273 | vm.fmt_sort_import(false); 274 | } 275 | if matches.is_present("debug-desugaring") { 276 | vm.fmt_debug_desugaring(true); 277 | } 278 | 279 | let output = if matches.is_present("exec") { 280 | let expr = matches.value_of("INPUT").unwrap(); 281 | vm.fmt_snippet("INPUT", expr)? 282 | } else { 283 | let file = matches.value_of_os("INPUT").unwrap(); 284 | vm.fmt_file(file)? 285 | }; 286 | 287 | print!("{}", output); 288 | Ok(()) 289 | } 290 | 291 | fn main() { 292 | let version = format!("{} (libjsonnet {})", crate_version!(), jsonnet_version()); 293 | 294 | let matches = build_cli(&version).get_matches(); 295 | 296 | let mut vm = JsonnetVm::new(); 297 | 298 | let result = if let Some(matches) = matches.subcommand_matches("eval") { 299 | eval(&mut vm, matches) 300 | } else if let Some(matches) = matches.subcommand_matches("fmt") { 301 | fmt(&mut vm, matches) 302 | } else { 303 | unreachable!(); 304 | }; 305 | 306 | match result { 307 | Ok(()) => (), 308 | Err(e) => { 309 | write!(&mut io::stderr(), "{}", e).unwrap(); 310 | process::exit(1); 311 | } 312 | }; 313 | } 314 | -------------------------------------------------------------------------------- /jsonnet-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonnet-sys" 3 | version = "0.17.0" 4 | authors = ["Angus Lees "] 5 | links = "jsonnet" 6 | edition = "2018" 7 | build = "build.rs" 8 | documentation = "https://docs.rs/jsonnet-sys" 9 | repository = "https://github.com/anguslees/rust-jsonnet.git" 10 | license = "Apache-2.0" 11 | description = "Native bindings to the libjsonnet library" 12 | exclude = ["/jsonnet/doc/third_party"] 13 | 14 | [lib] 15 | name = "jsonnet_sys" 16 | 17 | [dependencies] 18 | libc = "0.2" 19 | 20 | [build-dependencies] 21 | cc = "1.0" 22 | -------------------------------------------------------------------------------- /jsonnet-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::error::Error; 3 | use std::fs::{create_dir_all, File}; 4 | use std::io::{Read, Write}; 5 | use std::path::{Path, PathBuf}; 6 | use std::process::Command; 7 | 8 | fn main() -> Result<(), Box> { 9 | if !Path::new("jsonnet/.git").exists() { 10 | let _ = Command::new("git") 11 | .args(&["submodule", "update", "--init"]) 12 | .status(); 13 | } 14 | 15 | let dir = Path::new("jsonnet"); 16 | let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR undefined")); 17 | 18 | let embedded = ["std"]; 19 | for f in &embedded { 20 | let output = out_dir.join("include").join(format!("{}.jsonnet.h", f)); 21 | let input = dir.join("stdlib").join(format!("{}.jsonnet", f)); 22 | println!("embedding: {:?} -> {:?}", input, output); 23 | create_dir_all(output.parent().unwrap())?; 24 | let in_f = File::open(input)?; 25 | let mut out_f = File::create(&output)?; 26 | for b in in_f.bytes() { 27 | write!(&mut out_f, "{},", b?)?; 28 | } 29 | writeln!(&mut out_f, "0")?; 30 | } 31 | 32 | let jsonnet_core = [ 33 | "desugarer.cpp", 34 | "formatter.cpp", 35 | "lexer.cpp", 36 | "libjsonnet.cpp", 37 | "parser.cpp", 38 | "pass.cpp", 39 | "static_analysis.cpp", 40 | "string_utils.cpp", 41 | "vm.cpp", 42 | ]; 43 | 44 | let mut c = cc::Build::new(); 45 | c.cpp(true) 46 | .flag_if_supported("-std=c++0x") 47 | .include(out_dir.join("include")) 48 | .include(dir.join("include")) 49 | .include(dir.join("third_party/md5")) 50 | .include(dir.join("third_party/json")); 51 | 52 | for f in &jsonnet_core { 53 | c.file(dir.join("core").join(f)); 54 | } 55 | 56 | c.file(dir.join("third_party/md5/md5.cpp")); 57 | 58 | c.compile("libjsonnet.a"); 59 | 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /jsonnet-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_char, c_double, c_int, c_uint, c_void, size_t}; 2 | 3 | pub type JsonnetImportCallback = extern "C" fn( 4 | ctx: *mut c_void, 5 | base: *const c_char, 6 | rel: *const c_char, 7 | found_here: *mut *mut c_char, 8 | success: *mut c_int, 9 | ) -> *mut c_char; 10 | 11 | pub type JsonnetNativeCallback = extern "C" fn( 12 | ctx: *mut c_void, 13 | argv: *const *const JsonnetJsonValue, 14 | success: *mut c_int, 15 | ) -> *mut JsonnetJsonValue; 16 | 17 | pub enum JsonnetVm {} 18 | pub enum JsonnetJsonValue {} 19 | 20 | extern "C" { 21 | pub fn jsonnet_version() -> *const c_char; 22 | 23 | pub fn jsonnet_make() -> *mut JsonnetVm; 24 | pub fn jsonnet_max_stack(vm: *mut JsonnetVm, v: c_uint); 25 | pub fn jsonnet_gc_min_objects(vm: *mut JsonnetVm, v: c_uint); 26 | pub fn jsonnet_gc_growth_trigger(vm: *mut JsonnetVm, v: c_double); 27 | pub fn jsonnet_string_output(vm: *mut JsonnetVm, v: c_int); 28 | 29 | pub fn jsonnet_json_extract_string( 30 | vm: *mut JsonnetVm, 31 | v: *const JsonnetJsonValue, 32 | ) -> *const c_char; 33 | pub fn jsonnet_json_extract_number( 34 | vm: *mut JsonnetVm, 35 | v: *const JsonnetJsonValue, 36 | out: *mut c_double, 37 | ) -> c_int; 38 | pub fn jsonnet_json_extract_bool(vm: *mut JsonnetVm, v: *const JsonnetJsonValue) -> c_int; 39 | pub fn jsonnet_json_extract_null(vm: *mut JsonnetVm, v: *const JsonnetJsonValue) -> c_int; 40 | 41 | pub fn jsonnet_json_make_string(vm: *mut JsonnetVm, v: *const c_char) -> *mut JsonnetJsonValue; 42 | pub fn jsonnet_json_make_number(vm: *mut JsonnetVm, v: c_double) -> *mut JsonnetJsonValue; 43 | pub fn jsonnet_json_make_bool(vm: *mut JsonnetVm, v: c_int) -> *mut JsonnetJsonValue; 44 | pub fn jsonnet_json_make_null(vm: *mut JsonnetVm) -> *mut JsonnetJsonValue; 45 | pub fn jsonnet_json_make_array(vm: *mut JsonnetVm) -> *mut JsonnetJsonValue; 46 | pub fn jsonnet_json_array_append( 47 | vm: *mut JsonnetVm, 48 | arr: *mut JsonnetJsonValue, 49 | v: *mut JsonnetJsonValue, 50 | ); 51 | pub fn jsonnet_json_make_object(vm: *mut JsonnetVm) -> *mut JsonnetJsonValue; 52 | pub fn jsonnet_json_object_append( 53 | vm: *mut JsonnetVm, 54 | obj: *mut JsonnetJsonValue, 55 | f: *const c_char, 56 | v: *mut JsonnetJsonValue, 57 | ); 58 | 59 | pub fn jsonnet_json_destroy(vm: *mut JsonnetVm, v: *mut JsonnetJsonValue); 60 | 61 | pub fn jsonnet_realloc(vm: *mut JsonnetVm, buf: *const c_char, sz: size_t) -> *mut c_char; 62 | pub fn jsonnet_import_callback( 63 | vm: *mut JsonnetVm, 64 | cb: *const JsonnetImportCallback, 65 | ctx: *mut c_void, 66 | ); 67 | pub fn jsonnet_native_callback( 68 | vm: *mut JsonnetVm, 69 | name: *const c_char, 70 | cb: *const JsonnetNativeCallback, 71 | ctx: *mut c_void, 72 | params: *const *const c_char, 73 | ); 74 | 75 | pub fn jsonnet_ext_var(vm: *mut JsonnetVm, key: *const c_char, val: *const c_char); 76 | pub fn jsonnet_ext_code(vm: *mut JsonnetVm, key: *const c_char, val: *const c_char); 77 | 78 | pub fn jsonnet_tla_var(vm: *mut JsonnetVm, key: *const c_char, val: *const c_char); 79 | pub fn jsonnet_tla_code(vm: *mut JsonnetVm, key: *const c_char, val: *const c_char); 80 | 81 | pub fn jsonnet_fmt_indent(vm: *mut JsonnetVm, n: c_int); 82 | pub fn jsonnet_fmt_max_blank_lines(vm: *mut JsonnetVm, n: c_int); 83 | pub fn jsonnet_fmt_string(vm: *mut JsonnetVm, c: c_int); 84 | pub fn jsonnet_fmt_comment(vm: *mut JsonnetVm, c: c_int); 85 | pub fn jsonnet_fmt_pad_arrays(vm: *mut JsonnetVm, v: c_int); 86 | pub fn jsonnet_fmt_pad_objects(vm: *mut JsonnetVm, v: c_int); 87 | pub fn jsonnet_fmt_pretty_field_names(vm: *mut JsonnetVm, v: c_int); 88 | pub fn jsonnet_fmt_sort_imports(vm: *mut JsonnetVm, v: c_int); 89 | pub fn jsonnet_fmt_debug_desugaring(vm: *mut JsonnetVm, v: c_int); 90 | pub fn jsonnet_fmt_file( 91 | vm: *mut JsonnetVm, 92 | filename: *const c_char, 93 | error: *mut c_int, 94 | ) -> *mut c_char; 95 | pub fn jsonnet_fmt_snippet( 96 | vm: *mut JsonnetVm, 97 | filename: *const c_char, 98 | snippet: *const c_char, 99 | error: *mut c_int, 100 | ) -> *mut c_char; 101 | 102 | pub fn jsonnet_max_trace(vm: *mut JsonnetVm, v: c_uint); 103 | 104 | pub fn jsonnet_jpath_add(vm: *mut JsonnetVm, v: *const c_char); 105 | 106 | pub fn jsonnet_evaluate_file( 107 | vm: *mut JsonnetVm, 108 | filename: *const c_char, 109 | error: *mut c_int, 110 | ) -> *mut c_char; 111 | pub fn jsonnet_evaluate_snippet( 112 | vm: *mut JsonnetVm, 113 | filename: *const c_char, 114 | snippet: *const c_char, 115 | error: *mut c_int, 116 | ) -> *mut c_char; 117 | pub fn jsonnet_evaluate_file_multi( 118 | vm: *mut JsonnetVm, 119 | filename: *const c_char, 120 | error: *mut c_int, 121 | ) -> *mut c_char; 122 | pub fn jsonnet_evaluate_snippet_multi( 123 | vm: *mut JsonnetVm, 124 | filename: *const c_char, 125 | snippet: *const c_char, 126 | error: *mut c_int, 127 | ) -> *mut c_char; 128 | pub fn jsonnet_evaluate_file_stream( 129 | vm: *mut JsonnetVm, 130 | filename: *const c_char, 131 | error: *mut c_int, 132 | ) -> *mut c_char; 133 | pub fn jsonnet_evaluate_snippet_stream( 134 | vm: *mut JsonnetVm, 135 | filename: *const c_char, 136 | snippet: *const c_char, 137 | error: *mut c_int, 138 | ) -> *mut c_char; 139 | 140 | pub fn jsonnet_destroy(vm: *mut JsonnetVm); 141 | } 142 | 143 | #[test] 144 | fn basic_eval() { 145 | use std::ffi::{CStr, CString}; 146 | 147 | let filename = CString::new("testdata").unwrap(); 148 | let snippet = CString::new("40 + 2").unwrap(); 149 | let expected = CString::new("42\n").unwrap(); 150 | 151 | unsafe { 152 | let vm = jsonnet_make(); 153 | 154 | let mut error = 1; 155 | let result = jsonnet_evaluate_snippet(vm, filename.as_ptr(), snippet.as_ptr(), &mut error); 156 | assert_eq!(error, 0); 157 | assert_eq!(CStr::from_ptr(result).to_bytes(), expected.as_bytes()); 158 | 159 | jsonnet_realloc(vm, result, 0); 160 | jsonnet_destroy(vm); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # jsonnet bindings for Rust 2 | //! 3 | //! This library contains bindings to the [jsonnet][1] C library, 4 | //! a template language that generates JSON and YAML. 5 | //! 6 | //! Almost all interaction with this module will be via the 7 | //! `JsonnetVm` type. 8 | //! 9 | //! [1]: https://jsonnet.org/ 10 | //! 11 | //! # Examples 12 | //! 13 | //! ``` 14 | //! use jsonnet::JsonnetVm; 15 | //! 16 | //! let mut vm = JsonnetVm::new(); 17 | //! let output = vm.evaluate_snippet("example", "'Hello ' + 'World'").unwrap(); 18 | //! assert_eq!(output.to_string(), "\"Hello World\"\n"); 19 | //! ``` 20 | 21 | #![deny(missing_docs)] 22 | #![allow(trivial_casts)] 23 | 24 | use libc::{c_char, c_int, c_uint, c_void}; 25 | use std::ffi::{CStr, CString, OsStr}; 26 | use std::ops::Deref; 27 | use std::path::{Path, PathBuf}; 28 | use std::{error, fmt, iter, ptr}; 29 | 30 | mod string; 31 | mod value; 32 | 33 | pub use crate::string::JsonnetString; 34 | use crate::string::JsonnetStringIter; 35 | pub use crate::value::{JsonVal, JsonValue}; 36 | use jsonnet_sys::JsonnetJsonValue; 37 | 38 | /// Error returned from jsonnet routines on failure. 39 | #[derive(Debug, PartialEq, Eq)] 40 | pub struct Error<'a>(JsonnetString<'a>); 41 | 42 | impl<'a> From> for Error<'a> { 43 | fn from(s: JsonnetString<'a>) -> Self { 44 | Error(s) 45 | } 46 | } 47 | 48 | impl<'a> From> for JsonnetString<'a> { 49 | fn from(e: Error<'a>) -> Self { 50 | e.0 51 | } 52 | } 53 | 54 | impl<'a> Deref for Error<'a> { 55 | type Target = JsonnetString<'a>; 56 | fn deref(&self) -> &Self::Target { 57 | &self.0 58 | } 59 | } 60 | 61 | impl<'a> fmt::Display for Error<'a> { 62 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 63 | fmt::Display::fmt(&self.0, f) 64 | } 65 | } 66 | 67 | impl<'a> error::Error for Error<'a> { 68 | fn description(&self) -> &str { 69 | "jsonnet error" 70 | } 71 | } 72 | 73 | /// Result from "multi" eval methods. 74 | /// 75 | /// Contains a list of (&str, &str) pairs. 76 | pub struct EvalMap<'a>(JsonnetString<'a>); 77 | impl<'a> EvalMap<'a> { 78 | /// Returns an iterator over the contained values. 79 | pub fn iter<'b>(&'b self) -> EvalMapIter<'b, 'a> { 80 | EvalMapIter(unsafe { JsonnetStringIter::new(&self.0) }) 81 | } 82 | } 83 | 84 | impl<'a, 'b> IntoIterator for &'b EvalMap<'a> { 85 | type Item = (&'b str, &'b str); 86 | type IntoIter = EvalMapIter<'b, 'a>; 87 | fn into_iter(self) -> Self::IntoIter { 88 | self.iter() 89 | } 90 | } 91 | 92 | /// Iterator returned from "multi" eval methods. 93 | /// 94 | /// Yields (&str, &str) pairs. 95 | #[derive(Debug)] 96 | pub struct EvalMapIter<'a, 'b: 'a>(JsonnetStringIter<'a, 'b>); 97 | 98 | impl<'a, 'b> Iterator for EvalMapIter<'a, 'b> { 99 | type Item = (&'a str, &'a str); 100 | fn next(&mut self) -> Option { 101 | self.0.next().map(|first| { 102 | let second = self.0.next().unwrap(); 103 | (first, second) 104 | }) 105 | } 106 | } 107 | 108 | /// Result from "stream" eval methods. 109 | /// 110 | /// Contains a list of &str. 111 | pub struct EvalList<'a>(JsonnetString<'a>); 112 | impl<'a> EvalList<'a> { 113 | /// Returns an iterator over the contained values. 114 | pub fn iter<'b>(&'b self) -> EvalListIter<'b, 'a> { 115 | EvalListIter(unsafe { JsonnetStringIter::new(&self.0) }) 116 | } 117 | } 118 | 119 | /// Iterator returned from "stream" eval methods. 120 | /// 121 | /// Yields &str items. 122 | pub struct EvalListIter<'a, 'b: 'a>(JsonnetStringIter<'a, 'b>); 123 | 124 | impl<'a, 'b> Iterator for EvalListIter<'a, 'b> { 125 | type Item = &'a str; 126 | fn next(&mut self) -> Option { 127 | self.0.next() 128 | } 129 | } 130 | 131 | impl<'a, 'b> IntoIterator for &'b EvalList<'a> { 132 | type Item = &'b str; 133 | type IntoIter = EvalListIter<'b, 'a>; 134 | fn into_iter(self) -> Self::IntoIter { 135 | self.iter() 136 | } 137 | } 138 | 139 | /// String literal style 140 | #[derive(Debug, PartialEq, Eq)] 141 | pub enum FmtString { 142 | /// Double quoted `"foo"`. 143 | Double, 144 | /// Single quoted `'foo'`. 145 | Single, 146 | /// Leave quoting as-is. 147 | Leave, 148 | } 149 | 150 | impl FmtString { 151 | fn to_char(self) -> c_int { 152 | let c = match self { 153 | FmtString::Double => 'd', 154 | FmtString::Single => 's', 155 | FmtString::Leave => 'l', 156 | }; 157 | c as c_int 158 | } 159 | } 160 | 161 | impl Default for FmtString { 162 | fn default() -> FmtString { 163 | FmtString::Leave 164 | } 165 | } 166 | 167 | /// Comment style 168 | #[derive(Debug, PartialEq, Eq)] 169 | pub enum FmtComment { 170 | /// Hash comments (like shell). 171 | Hash, 172 | /// Double slash comments (like C++). 173 | Slash, 174 | /// Leave comments as-is. 175 | Leave, 176 | } 177 | 178 | impl FmtComment { 179 | fn to_char(self) -> c_int { 180 | let c = match self { 181 | FmtComment::Hash => 'h', 182 | FmtComment::Slash => 's', 183 | FmtComment::Leave => 'l', 184 | }; 185 | c as c_int 186 | } 187 | } 188 | 189 | impl Default for FmtComment { 190 | fn default() -> FmtComment { 191 | FmtComment::Leave 192 | } 193 | } 194 | 195 | /// Return the version string of the Jsonnet interpreter. Conforms to 196 | /// [semantic versioning][1]. 197 | /// 198 | /// [1]: http://semver.org/ 199 | pub fn jsonnet_version() -> &'static str { 200 | let s = unsafe { CStr::from_ptr(jsonnet_sys::jsonnet_version()) }; 201 | s.to_str().unwrap() 202 | } 203 | 204 | #[test] 205 | fn test_version() { 206 | let s = jsonnet_version(); 207 | println!("Got version: {}", s); 208 | // Should be 1.2.3 semver format 209 | assert_eq!(s.split('.').count(), 3); 210 | } 211 | 212 | /// Callback used to load imports. 213 | struct ImportContext<'a, F> { 214 | vm: &'a JsonnetVm, 215 | cb: F, 216 | } 217 | 218 | #[cfg(unix)] 219 | fn bytes2osstr(b: &[u8]) -> &OsStr { 220 | use std::os::unix::ffi::OsStrExt; 221 | OsStr::from_bytes(b) 222 | } 223 | 224 | /// Panics if os contains invalid utf8! 225 | #[cfg(windows)] 226 | fn bytes2osstr(b: &[u8]) -> &OsStr { 227 | let s = std::str::from_utf8(b).unwrap(); 228 | OsStr::new(s) 229 | } 230 | 231 | fn cstr2osstr(cstr: &CStr) -> &OsStr { 232 | bytes2osstr(cstr.to_bytes()) 233 | } 234 | 235 | #[cfg(unix)] 236 | fn osstr2bytes(os: &OsStr) -> &[u8] { 237 | use std::os::unix::ffi::OsStrExt; 238 | os.as_bytes() 239 | } 240 | 241 | /// Panics if os contains invalid utf8! 242 | #[cfg(windows)] 243 | fn osstr2bytes(os: &OsStr) -> &[u8] { 244 | os.to_str().unwrap().as_bytes() 245 | } 246 | 247 | fn osstr2cstring>(os: T) -> CString { 248 | let b = osstr2bytes(os.as_ref()); 249 | CString::new(b).unwrap() 250 | } 251 | 252 | // `jsonnet_sys::JsonnetImportCallback`-compatible function that 253 | // interprets `ctx` as an `ImportContext` and converts arguments 254 | // appropriately. 255 | extern "C" fn import_callback( 256 | ctx: *mut c_void, 257 | base: &c_char, 258 | rel: &c_char, 259 | found_here: &mut *mut c_char, 260 | success: &mut c_int, 261 | ) -> *mut c_char 262 | where 263 | F: Fn(&JsonnetVm, &Path, &Path) -> Result<(PathBuf, String), String>, 264 | { 265 | let ctx = unsafe { &*(ctx as *mut ImportContext) }; 266 | let vm = ctx.vm; 267 | let callback = &ctx.cb; 268 | let base_path = Path::new(cstr2osstr(unsafe { CStr::from_ptr(base) })); 269 | let rel_path = Path::new(cstr2osstr(unsafe { CStr::from_ptr(rel) })); 270 | match callback(vm, base_path, rel_path) { 271 | Ok((found_here_path, contents)) => { 272 | *success = 1; 273 | 274 | let v = { 275 | // Note: PathBuf may not be valid utf8. 276 | let b = osstr2bytes(found_here_path.as_os_str()); 277 | JsonnetString::from_bytes(vm, b) 278 | }; 279 | *found_here = v.into_raw(); 280 | 281 | JsonnetString::new(vm, &contents).into_raw() 282 | } 283 | Err(err) => { 284 | *success = 0; 285 | JsonnetString::new(vm, &err).into_raw() 286 | } 287 | } 288 | } 289 | 290 | /// Callback to provide native extensions to Jsonnet. 291 | /// 292 | /// This callback should not have side-effects! Jsonnet is a lazy 293 | /// functional language and will call your function when you least 294 | /// expect it, more times than you expect, or not at all. 295 | struct NativeContext<'a, F> { 296 | vm: &'a JsonnetVm, 297 | argc: usize, 298 | cb: F, 299 | } 300 | 301 | // `jsonnet_sys::JsonnetNativeCallback`-compatible function that 302 | // interprets `ctx` as a `NativeContext` and converts arguments 303 | // appropriately. 304 | extern "C" fn native_callback<'a, F>( 305 | ctx: *mut libc::c_void, 306 | argv: *const *const JsonnetJsonValue, 307 | success: &mut c_int, 308 | ) -> *mut JsonnetJsonValue 309 | where 310 | F: Fn(&'a JsonnetVm, &[JsonVal<'a>]) -> Result, String>, 311 | { 312 | let ctx = unsafe { &*(ctx as *mut NativeContext) }; 313 | let vm = ctx.vm; 314 | let callback = &ctx.cb; 315 | let args: Vec<_> = (0..ctx.argc) 316 | .map(|i| unsafe { JsonVal::from_ptr(vm, *argv.offset(i as isize)) }) 317 | .collect(); 318 | match callback(vm, &args) { 319 | Ok(v) => { 320 | *success = 1; 321 | v.into_raw() 322 | } 323 | Err(err) => { 324 | *success = 0; 325 | let ret = JsonValue::from_str(vm, &err); 326 | ret.into_raw() 327 | } 328 | } 329 | } 330 | 331 | /// Jsonnet virtual machine context. 332 | pub struct JsonnetVm(*mut jsonnet_sys::JsonnetVm); 333 | 334 | impl JsonnetVm { 335 | /// Create a new Jsonnet virtual machine. 336 | pub fn new() -> Self { 337 | let vm = unsafe { jsonnet_sys::jsonnet_make() }; 338 | JsonnetVm(vm) 339 | } 340 | 341 | fn as_ptr(&self) -> *mut jsonnet_sys::JsonnetVm { 342 | self.0 343 | } 344 | 345 | /// Set the maximum stack depth. 346 | pub fn max_stack(&mut self, v: u32) { 347 | unsafe { jsonnet_sys::jsonnet_max_stack(self.0, v as c_uint) } 348 | } 349 | 350 | /// Set the number of objects required before a garbage collection 351 | /// cycle is allowed. 352 | pub fn gc_min_objects(&mut self, v: u32) { 353 | unsafe { jsonnet_sys::jsonnet_gc_min_objects(self.0, v as c_uint) } 354 | } 355 | 356 | /// Run the garbage collector after this amount of growth in the 357 | /// number of objects. 358 | pub fn gc_growth_trigger(&mut self, v: f64) { 359 | unsafe { jsonnet_sys::jsonnet_gc_growth_trigger(self.0, v) } 360 | } 361 | 362 | /// Expect a string as output and don't JSON encode it. 363 | pub fn string_output(&mut self, v: bool) { 364 | unsafe { jsonnet_sys::jsonnet_string_output(self.0, v as c_int) } 365 | } 366 | 367 | /// Override the callback used to locate imports. 368 | /// 369 | /// # Examples 370 | /// 371 | /// ``` 372 | /// use std::path::{Path,PathBuf}; 373 | /// use std::ffi::OsStr; 374 | /// use jsonnet::JsonnetVm; 375 | /// 376 | /// let mut vm = JsonnetVm::new(); 377 | /// vm.import_callback(|_vm, base, rel| { 378 | /// if rel.file_stem() == Some(OsStr::new("bar")) { 379 | /// let newbase = base.into(); 380 | /// let contents = "2 + 3".to_owned(); 381 | /// Ok((newbase, contents)) 382 | /// } else { 383 | /// Err("not found".to_owned()) 384 | /// } 385 | /// }); 386 | /// 387 | /// { 388 | /// let output = vm.evaluate_snippet("myimport", "import 'x/bar.jsonnet'").unwrap(); 389 | /// assert_eq!(output.to_string(), "5\n"); 390 | /// } 391 | /// 392 | /// { 393 | /// let result = vm.evaluate_snippet("myimport", "import 'x/foo.jsonnet'"); 394 | /// assert!(result.is_err()); 395 | /// } 396 | /// ``` 397 | pub fn import_callback(&mut self, cb: F) 398 | where 399 | F: Fn(&JsonnetVm, &Path, &Path) -> Result<(PathBuf, String), String>, 400 | { 401 | let ctx = ImportContext { vm: self, cb: cb }; 402 | unsafe { 403 | jsonnet_sys::jsonnet_import_callback( 404 | self.as_ptr(), 405 | import_callback:: as *const _, 406 | // TODO: ctx is leaked :( 407 | Box::into_raw(Box::new(ctx)) as *mut _, 408 | ); 409 | } 410 | } 411 | 412 | /// Register a native extension. 413 | /// 414 | /// This will appear in Jsonnet as a function type and can be 415 | /// accessed from `std.native("foo")`. 416 | /// 417 | /// # Examples 418 | /// 419 | /// ``` 420 | /// use jsonnet::{JsonnetVm, JsonVal, JsonValue}; 421 | /// 422 | /// fn myadd<'a>(vm: &'a JsonnetVm, args: &[JsonVal<'a>]) -> Result, String> { 423 | /// let a = args[0].as_num().ok_or("Expected a number")?; 424 | /// let b = args[1].as_num().ok_or("Expected a number")?; 425 | /// Ok(JsonValue::from_num(vm, a + b)) 426 | /// } 427 | /// 428 | /// let mut vm = JsonnetVm::new(); 429 | /// vm.native_callback("myadd", myadd, &["a", "b"]); 430 | /// 431 | /// { 432 | /// let result = vm.evaluate_snippet("nativetest", 433 | /// "std.native('myadd')(2, 3)"); 434 | /// assert_eq!(result.unwrap().as_str(), "5\n"); 435 | /// } 436 | /// 437 | /// { 438 | /// let result = vm.evaluate_snippet("nativefail", 439 | /// "std.native('myadd')('foo', 'bar')"); 440 | /// assert!(result.is_err()); 441 | /// } 442 | /// 443 | /// vm.native_callback("upcase", |vm, args| { 444 | /// let s = args[0].as_str().ok_or("Expected a string")?; 445 | /// Ok(JsonValue::from_str(vm, &s.to_uppercase())) 446 | /// }, &["s"]); 447 | /// { 448 | /// let result = vm.evaluate_snippet("nativeclosure", 449 | /// "std.native('upcase')('foO')"); 450 | /// assert_eq!(result.unwrap().as_str(), "\"FOO\"\n"); 451 | /// } 452 | /// ``` 453 | /// 454 | /// # Panics 455 | /// 456 | /// Panics if `name` or `params` contain any embedded nul characters. 457 | pub fn native_callback(&mut self, name: &str, cb: F, params: &[&str]) 458 | where 459 | for<'a> F: Fn(&'a JsonnetVm, &[JsonVal<'a>]) -> Result, String>, 460 | { 461 | let ctx = NativeContext { 462 | vm: self, 463 | argc: params.len(), 464 | cb: cb, 465 | }; 466 | let cname = CString::new(name).unwrap(); 467 | let cparams: Vec<_> = params 468 | .into_iter() 469 | .map(|&p| CString::new(p).unwrap()) 470 | .collect(); 471 | let cptrs: Vec<_> = cparams 472 | .iter() 473 | .map(|p| p.as_ptr()) 474 | .chain(iter::once(ptr::null())) 475 | .collect(); 476 | unsafe { 477 | jsonnet_sys::jsonnet_native_callback( 478 | self.as_ptr(), 479 | cname.as_ptr(), 480 | native_callback:: as *const _, 481 | // TODO: ctx is leaked :( 482 | Box::into_raw(Box::new(ctx)) as *mut _, 483 | cptrs.as_slice().as_ptr(), 484 | ); 485 | } 486 | } 487 | 488 | /// Bind a Jsonnet external var to the given string. 489 | /// 490 | /// # Panics 491 | /// 492 | /// Panics if `key` or `val` contain embedded nul characters. 493 | pub fn ext_var(&mut self, key: &str, val: &str) { 494 | let ckey = CString::new(key).unwrap(); 495 | let cval = CString::new(val).unwrap(); 496 | unsafe { 497 | jsonnet_sys::jsonnet_ext_var(self.0, ckey.as_ptr(), cval.as_ptr()); 498 | } 499 | } 500 | 501 | /// Bind a Jsonnet external var to the given code. 502 | /// 503 | /// # Panics 504 | /// 505 | /// Panics if `key` or `code` contain embedded nul characters. 506 | pub fn ext_code(&mut self, key: &str, code: &str) { 507 | let ckey = CString::new(key).unwrap(); 508 | let ccode = CString::new(code).unwrap(); 509 | unsafe { 510 | jsonnet_sys::jsonnet_ext_code(self.0, ckey.as_ptr(), ccode.as_ptr()); 511 | } 512 | } 513 | 514 | /// Bind a string top-level argument for a top-level parameter. 515 | /// 516 | /// # Panics 517 | /// 518 | /// Panics if `key` or `val` contain embedded nul characters. 519 | pub fn tla_var(&mut self, key: &str, val: &str) { 520 | let ckey = CString::new(key).unwrap(); 521 | let cval = CString::new(val).unwrap(); 522 | unsafe { 523 | jsonnet_sys::jsonnet_tla_var(self.0, ckey.as_ptr(), cval.as_ptr()); 524 | } 525 | } 526 | 527 | /// Bind a code top-level argument for a top-level parameter. 528 | /// 529 | /// # Panics 530 | /// 531 | /// Panics if `key` or `code` contain embedded nul characters. 532 | pub fn tla_code(&mut self, key: &str, code: &str) { 533 | let ckey = CString::new(key).unwrap(); 534 | let ccode = CString::new(code).unwrap(); 535 | unsafe { 536 | jsonnet_sys::jsonnet_tla_code(self.0, ckey.as_ptr(), ccode.as_ptr()); 537 | } 538 | } 539 | 540 | /// Indentation level when reformatting (number of spaces). 541 | pub fn fmt_indent(&mut self, n: u32) { 542 | unsafe { 543 | jsonnet_sys::jsonnet_fmt_indent(self.0, n as c_int); 544 | } 545 | } 546 | 547 | /// Maximum number of blank lines when reformatting. 548 | pub fn fmt_max_blank_lines(&mut self, n: u32) { 549 | unsafe { 550 | jsonnet_sys::jsonnet_fmt_max_blank_lines(self.0, n as c_int); 551 | } 552 | } 553 | 554 | /// Preferred style for string literals (`""` or `''`). 555 | pub fn fmt_string(&mut self, fmt: FmtString) { 556 | unsafe { 557 | jsonnet_sys::jsonnet_fmt_string(self.0, fmt.to_char()); 558 | } 559 | } 560 | 561 | /// Preferred style for line comments (# or //). 562 | pub fn fmt_comment(&mut self, fmt: FmtComment) { 563 | unsafe { 564 | jsonnet_sys::jsonnet_fmt_comment(self.0, fmt.to_char()); 565 | } 566 | } 567 | 568 | /// Whether to add an extra space on the inside of arrays. 569 | pub fn fmt_pad_arrays(&mut self, pad: bool) { 570 | unsafe { 571 | jsonnet_sys::jsonnet_fmt_pad_arrays(self.0, pad as c_int); 572 | } 573 | } 574 | 575 | /// Whether to add an extra space on the inside of objects. 576 | pub fn fmt_pad_objects(&mut self, pad: bool) { 577 | unsafe { 578 | jsonnet_sys::jsonnet_fmt_pad_objects(self.0, pad as c_int); 579 | } 580 | } 581 | 582 | /// Use syntax sugar where possible with field names. 583 | pub fn fmt_pretty_field_names(&mut self, sugar: bool) { 584 | unsafe { 585 | jsonnet_sys::jsonnet_fmt_pretty_field_names(self.0, sugar as c_int); 586 | } 587 | } 588 | 589 | /// Sort top-level imports in alphabetical order 590 | pub fn fmt_sort_import(&mut self, sort: bool) { 591 | unsafe { 592 | jsonnet_sys::jsonnet_fmt_sort_imports(self.0, sort as c_int); 593 | } 594 | } 595 | 596 | /// Reformat the Jsonnet input after desugaring. 597 | pub fn fmt_debug_desugaring(&mut self, reformat: bool) { 598 | unsafe { 599 | jsonnet_sys::jsonnet_fmt_debug_desugaring(self.0, reformat as c_int); 600 | } 601 | } 602 | 603 | /// Reformat a file containing Jsonnet code, return a Jsonnet string. 604 | /// 605 | /// # Panics 606 | /// 607 | /// Panics if `filename` contains embedded nul characters. 608 | pub fn fmt_file<'a, P>(&'a mut self, filename: P) -> Result, Error<'a>> 609 | where 610 | P: AsRef, 611 | { 612 | let fname = osstr2cstring(filename); 613 | let mut error = 1; 614 | let output = unsafe { 615 | let v = jsonnet_sys::jsonnet_fmt_file(self.0, fname.as_ptr(), &mut error); 616 | JsonnetString::from_ptr(self, v) 617 | }; 618 | match error { 619 | 0 => Ok(output), 620 | _ => Err(Error(output)), 621 | } 622 | } 623 | 624 | /// Reformat a string containing Jsonnet code, return a Jsonnet string. 625 | /// 626 | /// # Panics 627 | /// 628 | /// Panics if `filename` or `snippet` contain embedded nul characters. 629 | pub fn fmt_snippet<'a, P>( 630 | &'a mut self, 631 | filename: P, 632 | snippet: &str, 633 | ) -> Result, Error<'a>> 634 | where 635 | P: AsRef, 636 | { 637 | let fname = osstr2cstring(filename); 638 | let snippet = CString::new(snippet).unwrap(); 639 | let mut error = 1; 640 | let output = unsafe { 641 | let v = jsonnet_sys::jsonnet_fmt_snippet( 642 | self.0, 643 | fname.as_ptr(), 644 | snippet.as_ptr(), 645 | &mut error, 646 | ); 647 | JsonnetString::from_ptr(self, v) 648 | }; 649 | match error { 650 | 0 => Ok(output), 651 | _ => Err(Error(output)), 652 | } 653 | } 654 | 655 | /// Set the number of lines of stack trace to display (None for unlimited). 656 | pub fn max_trace(&mut self, limit: Option) { 657 | let v = limit.unwrap_or(0); 658 | unsafe { 659 | jsonnet_sys::jsonnet_max_trace(self.0, v); 660 | } 661 | } 662 | 663 | /// Add to the default import callback's library search path. 664 | /// 665 | /// Search order is last to first, so more recently appended paths 666 | /// take precedence. 667 | /// 668 | /// # Panics 669 | /// 670 | /// Panics if `path` contains embedded nul characters. 671 | pub fn jpath_add

(&mut self, path: P) 672 | where 673 | P: AsRef, 674 | { 675 | let v = osstr2cstring(path); 676 | unsafe { 677 | jsonnet_sys::jsonnet_jpath_add(self.0, v.as_ptr()); 678 | } 679 | } 680 | 681 | /// Evaluate a file containing Jsonnet code, returning a JSON string. 682 | /// 683 | /// # Errors 684 | /// 685 | /// Returns any jsonnet error during evaluation. 686 | /// 687 | /// # Panics 688 | /// 689 | /// Panics if `filename` contains embedded nul characters. 690 | pub fn evaluate_file<'a, P>(&'a mut self, filename: P) -> Result, Error<'a>> 691 | where 692 | P: AsRef, 693 | { 694 | let fname = osstr2cstring(filename); 695 | let mut error = 1; 696 | let output = unsafe { 697 | let v = jsonnet_sys::jsonnet_evaluate_file(self.0, fname.as_ptr(), &mut error); 698 | JsonnetString::from_ptr(self, v) 699 | }; 700 | match error { 701 | 0 => Ok(output), 702 | _ => Err(Error(output)), 703 | } 704 | } 705 | 706 | /// Evaluate a string containing Jsonnet code, returning a JSON string. 707 | /// 708 | /// The `filename` argument is only used in error messages. 709 | /// 710 | /// # Errors 711 | /// 712 | /// Returns any jsonnet error during evaluation. 713 | /// 714 | /// # Panics 715 | /// 716 | /// Panics if `filename` or `snippet` contain embedded nul characters. 717 | pub fn evaluate_snippet<'a, P>( 718 | &'a mut self, 719 | filename: P, 720 | snippet: &str, 721 | ) -> Result, Error<'a>> 722 | where 723 | P: AsRef, 724 | { 725 | let fname = osstr2cstring(filename); 726 | let snip = CString::new(snippet).unwrap(); 727 | let mut error = 1; 728 | let output = unsafe { 729 | let v = jsonnet_sys::jsonnet_evaluate_snippet( 730 | self.0, 731 | fname.as_ptr(), 732 | snip.as_ptr(), 733 | &mut error, 734 | ); 735 | JsonnetString::from_ptr(self, v) 736 | }; 737 | match error { 738 | 0 => Ok(output), 739 | _ => Err(Error(output)), 740 | } 741 | } 742 | 743 | /// Evaluate a file containing Jsonnet code, returning a number of 744 | /// named JSON files. 745 | /// 746 | /// # Errors 747 | /// 748 | /// Returns any jsonnet error during evaluation. 749 | /// 750 | /// # Panics 751 | /// 752 | /// Panics if `filename` contains embedded nul characters. 753 | pub fn evaluate_file_multi<'a, P>(&'a mut self, filename: P) -> Result, Error<'a>> 754 | where 755 | P: AsRef, 756 | { 757 | let fname = osstr2cstring(filename); 758 | let mut error = 1; 759 | let output = unsafe { 760 | let v = jsonnet_sys::jsonnet_evaluate_file_multi(self.0, fname.as_ptr(), &mut error); 761 | JsonnetString::from_ptr(self, v) 762 | }; 763 | match error { 764 | 0 => Ok(EvalMap(output)), 765 | _ => Err(Error(output)), 766 | } 767 | } 768 | 769 | /// Evaluate a file containing Jsonnet code, returning a number of 770 | /// named JSON files. 771 | /// 772 | /// # Errors 773 | /// 774 | /// Returns any jsonnet error during evaluation. 775 | /// 776 | /// # Panics 777 | /// 778 | /// Panics if `filename` or `snippet` contain embedded nul characters. 779 | /// 780 | /// # Examples 781 | /// 782 | /// ``` 783 | /// use std::collections::HashMap; 784 | /// use jsonnet::JsonnetVm; 785 | /// 786 | /// let mut vm = JsonnetVm::new(); 787 | /// let output = vm.evaluate_snippet_multi("multi", 788 | /// "{'foo.json': 'foo', 'bar.json': 'bar'}").unwrap(); 789 | /// 790 | /// let map: HashMap<_,_> = output.iter().collect(); 791 | /// assert_eq!(*map.get("bar.json").unwrap(), "\"bar\"\n"); 792 | /// ``` 793 | pub fn evaluate_snippet_multi<'a, P>( 794 | &'a mut self, 795 | filename: P, 796 | snippet: &str, 797 | ) -> Result, Error<'a>> 798 | where 799 | P: AsRef, 800 | { 801 | let fname = osstr2cstring(filename); 802 | let snippet = CString::new(snippet).unwrap(); 803 | let mut error = 1; 804 | let output = unsafe { 805 | let v = jsonnet_sys::jsonnet_evaluate_snippet_multi( 806 | self.0, 807 | fname.as_ptr(), 808 | snippet.as_ptr(), 809 | &mut error, 810 | ); 811 | JsonnetString::from_ptr(self, v) 812 | }; 813 | match error { 814 | 0 => Ok(EvalMap(output)), 815 | _ => Err(Error(output)), 816 | } 817 | } 818 | 819 | /// Evaluate a file containing Jsonnet code, returning a number of 820 | /// JSON files. 821 | /// 822 | /// # Errors 823 | /// 824 | /// Returns any jsonnet error during evaluation. 825 | /// 826 | /// # Panics 827 | /// 828 | /// Panics if `filename` contains embedded nul characters. 829 | pub fn evaluate_file_stream<'a, P>(&'a mut self, filename: P) -> Result, Error<'a>> 830 | where 831 | P: AsRef, 832 | { 833 | let fname = osstr2cstring(filename); 834 | let mut error = 1; 835 | let output = unsafe { 836 | let v = jsonnet_sys::jsonnet_evaluate_file_stream(self.0, fname.as_ptr(), &mut error); 837 | JsonnetString::from_ptr(self, v) 838 | }; 839 | match error { 840 | 0 => Ok(EvalList(output)), 841 | _ => Err(Error(output)), 842 | } 843 | } 844 | 845 | /// Evaluate a string containing Jsonnet code, returning a number 846 | /// of JSON files. 847 | /// 848 | /// # Errors 849 | /// 850 | /// Returns any jsonnet error during evaluation. 851 | /// 852 | /// # Panics 853 | /// 854 | /// Panics if `filename` or `snippet` contain embedded nul characters. 855 | /// 856 | /// # Examples 857 | /// 858 | /// ``` 859 | /// use jsonnet::JsonnetVm; 860 | /// 861 | /// let mut vm = JsonnetVm::new(); 862 | /// let output = vm.evaluate_snippet_stream("stream", 863 | /// "['foo', 'bar']").unwrap(); 864 | /// 865 | /// let list: Vec<_> = output.iter().collect(); 866 | /// assert_eq!(list, vec!["\"foo\"\n", "\"bar\"\n"]); 867 | /// ``` 868 | pub fn evaluate_snippet_stream<'a, P>( 869 | &'a mut self, 870 | filename: P, 871 | snippet: &str, 872 | ) -> Result, Error<'a>> 873 | where 874 | P: AsRef, 875 | { 876 | let fname = osstr2cstring(filename); 877 | let snippet = CString::new(snippet).unwrap(); 878 | let mut error = 1; 879 | let output = unsafe { 880 | let v = jsonnet_sys::jsonnet_evaluate_snippet_stream( 881 | self.0, 882 | fname.as_ptr(), 883 | snippet.as_ptr(), 884 | &mut error, 885 | ); 886 | JsonnetString::from_ptr(self, v) 887 | }; 888 | match error { 889 | 0 => Ok(EvalList(output)), 890 | _ => Err(Error(output)), 891 | } 892 | } 893 | } 894 | 895 | impl Drop for JsonnetVm { 896 | fn drop(&mut self) { 897 | assert!(!self.0.is_null()); 898 | unsafe { jsonnet_sys::jsonnet_destroy(self.0) } 899 | } 900 | } 901 | 902 | #[test] 903 | fn basic_eval() { 904 | let mut vm = JsonnetVm::new(); 905 | let result = vm.evaluate_snippet("example", "'Hello ' + 'World'"); 906 | println!("result is {:?}", result); 907 | assert!(result.is_ok()); 908 | assert_eq!(result.unwrap().to_string(), "\"Hello World\"\n"); 909 | } 910 | 911 | #[test] 912 | fn basic_eval_err() { 913 | let mut vm = JsonnetVm::new(); 914 | let result = vm.evaluate_snippet("example", "bogus"); 915 | println!("result is {:?}", result); 916 | assert!(result.is_err()); 917 | assert!(result.unwrap_err().contains("Unknown variable: bogus")); 918 | } 919 | -------------------------------------------------------------------------------- /src/string.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_char, size_t}; 2 | use std::ffi::CStr; 3 | use std::marker::PhantomData; 4 | use std::ops::Deref; 5 | use std::{fmt, mem, ptr}; 6 | 7 | use super::JsonnetVm; 8 | use jsonnet_sys; 9 | 10 | /// Rust wrapper for libjsonnet string values. 11 | /// 12 | /// This string is allocated against a particular JsonnetVm, and may 13 | /// not outlive the JsonentVm. It is UTF-8, and may not contain 14 | /// embedded nul characters (ie: it is javascript and C -safe). 15 | pub struct JsonnetString<'a> { 16 | vm: &'a JsonnetVm, 17 | data: *mut c_char, 18 | } 19 | 20 | impl<'a> JsonnetString<'a> { 21 | /// Allocate a new JsonnetString and copy `v` into it. 22 | /// 23 | /// # Panics 24 | /// 25 | /// Panics if `v` contains an embedded nul character. 26 | pub fn new(vm: &'a JsonnetVm, v: &str) -> Self { 27 | JsonnetString::from_bytes(vm, v.as_bytes()) 28 | } 29 | 30 | /// Allocate a new JsonnetString and copy `v` into it. 31 | /// 32 | /// # Panics 33 | /// 34 | /// Panics if `v` contains an embedded nul character. In most 35 | /// cases, jsonnet requires strings to be utf8, so prefer 36 | /// `JsonnetString::new` over this function. 37 | pub fn from_bytes(vm: &'a JsonnetVm, v: &[u8]) -> Self { 38 | unsafe { 39 | let p = jsonnet_sys::jsonnet_realloc(vm.as_ptr(), ptr::null(), v.len() + 1); 40 | ptr::copy_nonoverlapping(v.as_ptr(), p as *mut u8, v.len()); 41 | *(p.offset(v.len() as isize)) = 0; // trailing nul for C string 42 | Self::from_ptr(vm, p) 43 | } 44 | } 45 | 46 | /// Construct a `JsonnetString` from a pointer returned from a 47 | /// low-level jsonnet C function. 48 | /// 49 | /// # Safety 50 | /// 51 | /// It is assumed that `p` points to a UTF-8 C string (ie: nul 52 | /// terminated). It is up to the caller to ensure that `p` was 53 | /// indeed allocated by `vm`. 54 | pub unsafe fn from_ptr(vm: &'a JsonnetVm, p: *mut c_char) -> Self { 55 | JsonnetString { vm: vm, data: p } 56 | } 57 | 58 | fn realloc(&mut self, size: size_t) { 59 | unsafe { 60 | self.data = jsonnet_sys::jsonnet_realloc(self.vm.as_ptr(), self.data, size); 61 | } 62 | } 63 | 64 | /// Returns the contents as a `&str`. 65 | pub fn as_str(&self) -> &'a str { 66 | self.as_cstr().to_str().unwrap() 67 | } 68 | 69 | /// Returns the contents as a `&CStr`. 70 | pub fn as_cstr(&self) -> &'a CStr { 71 | unsafe { CStr::from_ptr(self.data) } 72 | } 73 | 74 | /// Returns the inner pointer to this jsonnet string. 75 | /// 76 | /// The returned pointer will be valid for as long as `self` is. 77 | pub fn as_ptr(&self) -> *const c_char { 78 | self.data 79 | } 80 | 81 | /// Transfer ownership to a C caller (presumably a jsonnet 82 | /// function). 83 | /// 84 | /// If you call this, it is up to you to ensure that the string is 85 | /// freed correctly (using the appropriate jsonnet function), or 86 | /// the memory will leak. 87 | pub fn into_raw(self) -> *mut c_char { 88 | let result = self.data; 89 | mem::forget(self); 90 | result 91 | } 92 | } 93 | 94 | impl<'a> Deref for JsonnetString<'a> { 95 | type Target = str; 96 | fn deref(&self) -> &str { 97 | self.as_str() 98 | } 99 | } 100 | 101 | impl<'a> fmt::Debug for JsonnetString<'a> { 102 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 103 | fmt::Debug::fmt(self.as_str(), f) 104 | } 105 | } 106 | 107 | impl<'a> fmt::Display for JsonnetString<'a> { 108 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 109 | fmt::Display::fmt(self.as_str(), f) 110 | } 111 | } 112 | 113 | impl<'a> Drop for JsonnetString<'a> { 114 | fn drop(&mut self) { 115 | self.realloc(0); 116 | } 117 | } 118 | 119 | impl<'a> PartialEq for JsonnetString<'a> { 120 | fn eq<'b>(&self, other: &JsonnetString<'b>) -> bool { 121 | self.as_str() == other.as_str() 122 | } 123 | } 124 | 125 | impl<'a> Eq for JsonnetString<'a> {} 126 | 127 | #[test] 128 | fn simple() { 129 | let vm = JsonnetVm::new(); 130 | let s = JsonnetString::new(&vm, "1234"); 131 | assert_eq!(s.as_str(), "1234"); 132 | assert_eq!(s.as_cstr().to_bytes_with_nul(), b"1234\0"); 133 | } 134 | 135 | /// An iterator over nul-separated multi-valued jsonnet string. 136 | #[derive(Debug)] 137 | pub struct JsonnetStringIter<'a, 'b: 'a> { 138 | cursor: *const c_char, 139 | marker: PhantomData<&'a JsonnetString<'b>>, 140 | } 141 | 142 | impl<'a, 'b> JsonnetStringIter<'a, 'b> { 143 | /// Caller takes responsibility for ensuring this actually is a 144 | /// multi-valued string. 145 | pub unsafe fn new(inner: &'a JsonnetString<'b>) -> Self { 146 | let cursor = inner.as_ptr(); 147 | JsonnetStringIter { 148 | cursor: cursor, 149 | marker: PhantomData, 150 | } 151 | } 152 | 153 | fn end(&self) -> bool { 154 | let c = unsafe { *self.cursor }; 155 | c == 0 156 | } 157 | 158 | // Caller checks that cursor is actually pointing at another string. 159 | unsafe fn take_next(&mut self) -> &'a CStr { 160 | let ret = CStr::from_ptr(self.cursor); 161 | let len = ret.to_bytes_with_nul().len(); 162 | self.cursor = self.cursor.offset(len as isize); 163 | ret 164 | } 165 | } 166 | 167 | impl<'a, 'b> Iterator for JsonnetStringIter<'a, 'b> { 168 | type Item = &'a str; 169 | fn next(&mut self) -> Option { 170 | if self.end() { 171 | None 172 | } else { 173 | let v = unsafe { self.take_next() }; 174 | Some(v.to_str().unwrap()) 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/value.rs: -------------------------------------------------------------------------------- 1 | use libc::c_int; 2 | use std::ffi::{CStr, CString}; 3 | use std::marker; 4 | use std::mem; 5 | 6 | use super::JsonnetVm; 7 | use jsonnet_sys::{self, JsonnetJsonValue}; 8 | 9 | /// Rust wrapper for borrowed libjsonnet JSON values. 10 | /// 11 | /// See `JsonValue` for the owned version. 12 | pub struct JsonVal<'a> { 13 | vm: &'a JsonnetVm, 14 | value: *const JsonnetJsonValue, 15 | } 16 | 17 | impl<'a> JsonVal<'a> { 18 | /// Construct a `JsonVal` from a pointer returned from a 19 | /// low-level jsonnet C function. 20 | /// 21 | /// # Safety 22 | /// 23 | /// It is up to the caller to ensure that `p` was indeed allocated 24 | /// by `vm`. 25 | pub unsafe fn from_ptr(vm: &'a JsonnetVm, p: *const JsonnetJsonValue) -> Self { 26 | JsonVal { vm: vm, value: p } 27 | } 28 | 29 | /// Returns the inner pointer to this jsonnet value. 30 | /// 31 | /// The returned pointer will be valid for as long as `self` is. 32 | pub fn as_ptr(&self) -> *const JsonnetJsonValue { 33 | self.value 34 | } 35 | 36 | /// Returns the value, if it is a string. 37 | pub fn as_str(&self) -> Option<&str> { 38 | unsafe { 39 | let p = jsonnet_sys::jsonnet_json_extract_string(self.vm.as_ptr(), self.as_ptr()); 40 | if p.is_null() { 41 | None 42 | } else { 43 | // Jsonnet asserts this is UTF-8 44 | Some(CStr::from_ptr(p).to_str().unwrap()) 45 | } 46 | } 47 | } 48 | 49 | /// Returns the value, if it is a number. 50 | pub fn as_num(&self) -> Option { 51 | let mut number = 0.0; 52 | let ok = unsafe { 53 | jsonnet_sys::jsonnet_json_extract_number(self.vm.as_ptr(), self.as_ptr(), &mut number) 54 | }; 55 | if ok != 0 { 56 | Some(number) 57 | } else { 58 | None 59 | } 60 | } 61 | 62 | /// Returns the value, if it is a bool. 63 | pub fn as_bool(&self) -> Option { 64 | let v = unsafe { jsonnet_sys::jsonnet_json_extract_bool(self.vm.as_ptr(), self.as_ptr()) }; 65 | match v { 66 | 0 => Some(false), 67 | 1 => Some(true), 68 | 2 => None, 69 | _ => unreachable!(), 70 | } 71 | } 72 | 73 | /// Returns `Some(())` if the value is `null`. 74 | pub fn as_null(&self) -> Option<()> { 75 | let v = unsafe { jsonnet_sys::jsonnet_json_extract_null(self.vm.as_ptr(), self.as_ptr()) }; 76 | match v { 77 | 0 => None, 78 | 1 => Some(()), 79 | _ => unreachable!(), 80 | } 81 | } 82 | } 83 | 84 | /// Rust wrapper for owned libjsonnet JSON values. 85 | /// 86 | /// These are used as return values from jsonnet native callbacks. 87 | /// See `JsonVal` for the borrowed version. 88 | pub struct JsonValue<'a> { 89 | vm: &'a JsonnetVm, 90 | value: *mut JsonnetJsonValue, 91 | _marker: marker::PhantomData, 92 | } 93 | 94 | impl<'a> JsonValue<'a> { 95 | /// Construct a `JsonValue` from a pointer returned from a 96 | /// low-level jsonnet C function. 97 | /// 98 | /// # Safety 99 | /// 100 | /// It is up to the caller to ensure that `p` was indeed allocated 101 | /// by `vm`. 102 | pub unsafe fn from_ptr(vm: &'a JsonnetVm, p: *mut JsonnetJsonValue) -> Self { 103 | JsonValue { 104 | vm: vm, 105 | value: p, 106 | _marker: marker::PhantomData, 107 | } 108 | } 109 | 110 | /// Returns the inner pointer to this jsonnet value. 111 | /// 112 | /// The returned pointer will be valid for as long as `self` is. 113 | pub fn as_ptr(&self) -> *const JsonnetJsonValue { 114 | self.value 115 | } 116 | 117 | /// Returns the value, if it is a string. 118 | pub fn as_str(&self) -> Option<&str> { 119 | unsafe { 120 | let p = jsonnet_sys::jsonnet_json_extract_string(self.vm.as_ptr(), self.as_ptr()); 121 | if p.is_null() { 122 | None 123 | } else { 124 | // Jsonnet asserts this is UTF-8 125 | Some(CStr::from_ptr(p).to_str().unwrap()) 126 | } 127 | } 128 | } 129 | 130 | /// Returns the value, if it is a number. 131 | pub fn as_num(&self) -> Option { 132 | let mut number = 0.0; 133 | let ok = unsafe { 134 | jsonnet_sys::jsonnet_json_extract_number(self.vm.as_ptr(), self.as_ptr(), &mut number) 135 | }; 136 | if ok != 0 { 137 | Some(number) 138 | } else { 139 | None 140 | } 141 | } 142 | 143 | /// Returns the value, if it is a bool. 144 | pub fn as_bool(&self) -> Option { 145 | let v = unsafe { jsonnet_sys::jsonnet_json_extract_bool(self.vm.as_ptr(), self.as_ptr()) }; 146 | match v { 147 | 0 => Some(false), 148 | 1 => Some(true), 149 | 2 => None, 150 | _ => unreachable!(), 151 | } 152 | } 153 | 154 | /// Returns `Some(())` if the value is `null`. 155 | pub fn as_null(&self) -> Option<()> { 156 | let v = unsafe { jsonnet_sys::jsonnet_json_extract_null(self.vm.as_ptr(), self.as_ptr()) }; 157 | match v { 158 | 0 => None, 159 | 1 => Some(()), 160 | _ => unreachable!(), 161 | } 162 | } 163 | 164 | /// Convert the given UTF8 string to a JsonValue. 165 | /// 166 | /// # Panics 167 | /// 168 | /// Panics if `v` contains an embedded nul character. 169 | pub fn from_str(vm: &'a JsonnetVm, v: &str) -> Self { 170 | let cstr = CString::new(v).unwrap(); 171 | unsafe { 172 | let p = jsonnet_sys::jsonnet_json_make_string(vm.as_ptr(), cstr.as_ptr()); 173 | Self::from_ptr(vm, p) 174 | } 175 | } 176 | 177 | /// Convert the given double to a JsonValue. 178 | pub fn from_num(vm: &'a JsonnetVm, v: f64) -> Self { 179 | unsafe { 180 | let p = jsonnet_sys::jsonnet_json_make_number(vm.as_ptr(), v); 181 | Self::from_ptr(vm, p) 182 | } 183 | } 184 | 185 | /// Convert the given bool to a JsonValue. 186 | pub fn from_bool(vm: &'a JsonnetVm, v: bool) -> Self { 187 | unsafe { 188 | let p = jsonnet_sys::jsonnet_json_make_bool(vm.as_ptr(), v as c_int); 189 | Self::from_ptr(vm, p) 190 | } 191 | } 192 | 193 | /// Make a JsonValue representing `null`. 194 | pub fn null(vm: &'a JsonnetVm) -> Self { 195 | unsafe { 196 | let p = jsonnet_sys::jsonnet_json_make_null(vm.as_ptr()); 197 | Self::from_ptr(vm, p) 198 | } 199 | } 200 | 201 | /// Convert the given list into a JsonValue array. 202 | pub fn from_array(vm: &'a JsonnetVm, iter: T) -> Self 203 | where 204 | T: IntoIterator>, 205 | { 206 | unsafe { 207 | let p = jsonnet_sys::jsonnet_json_make_array(vm.as_ptr()); 208 | for item in iter { 209 | jsonnet_sys::jsonnet_json_array_append(vm.as_ptr(), p, item.into_raw()); 210 | } 211 | Self::from_ptr(vm, p) 212 | } 213 | } 214 | 215 | /// Convert the given map into a JsonValue object. 216 | pub fn from_map<'b, T>(vm: &'a JsonnetVm, iter: T) -> Self 217 | where 218 | T: IntoIterator)>, 219 | { 220 | unsafe { 221 | let p = jsonnet_sys::jsonnet_json_make_object(vm.as_ptr()); 222 | for (f, v) in iter { 223 | jsonnet_sys::jsonnet_json_object_append(vm.as_ptr(), p, f.as_ptr(), v.into_raw()); 224 | } 225 | Self::from_ptr(vm, p) 226 | } 227 | } 228 | 229 | /// Transfer ownership to a C caller (presumably a jsonnet 230 | /// function). 231 | /// 232 | /// If you call this, it is up to you to ensure that the value is 233 | /// freed correctly (using the appropriate jsonnet function), or 234 | /// the memory will leak. 235 | pub fn into_raw(self) -> *mut JsonnetJsonValue { 236 | let result = self.value; 237 | mem::forget(self); 238 | result 239 | } 240 | } 241 | 242 | impl<'a> Drop for JsonValue<'a> { 243 | fn drop(&mut self) { 244 | unsafe { 245 | jsonnet_sys::jsonnet_json_destroy(self.vm.as_ptr(), self.value); 246 | } 247 | } 248 | } 249 | --------------------------------------------------------------------------------