├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── const_env ├── Cargo.toml ├── LICENSE ├── README.md ├── src │ └── lib.rs └── tests │ ├── run-pass │ ├── tracked │ │ └── tracked.rs │ └── untracked │ │ ├── array_literals.rs │ │ ├── default.rs │ │ ├── explicit_item_name.rs │ │ ├── implicit_item_name.rs │ │ ├── negative.rs │ │ ├── quoted_literals.rs │ │ ├── smoke.rs │ │ └── struct_literals.rs │ └── run_tests.rs └── const_env_impl ├── Cargo.toml ├── LICENSE ├── README.md ├── src └── lib.rs └── tests └── from_env.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["const_env", "const_env_impl"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | This crate's implementation strategy in `master` has recently been updated to support a 4 | more powerful `env_lit!` macro that supports arbitrary literals (or really, arbitrary 5 | expressions). Need to finish testing the implementation (I think there are some quirks 6 | around string escaping that need to be thought through) and update this README to explain 7 | how to use `env_lit` instead of `from_env`. 8 | 9 | # const_env 10 | 11 | - [Motivation](#motivation) 12 | - [Crate Features](#crate-features) 13 | - [Usage](#usage) 14 | - [Supported Types](#supported-types) 15 | - [Limitations](#known-limitations) 16 | - [Alternatives](#alternatives) 17 | 18 | ## Motivation 19 | 20 | **Your goal**: You want to define various constants for your code at compile time based on environment variables. 21 | 22 | **Your problem**: Its only possible to do this for `&'static str` constants in today's Rust. 23 | 24 | ```rust 25 | const MY_STR: &'static str = env!("SOME_ENV"); 26 | ``` 27 | 28 | You cannot similarly initialize other types such as constant `u32`s, `bool`s, or 29 | other primitive types. See [issues](https://github.com/rust-lang/rfcs/issues/1907) and 30 | [workarounds](https://stackoverflow.com/questions/37526598/overriding-constant-via-compiler-option). 31 | Eventually you will be able to do so once support for running `parse` and `unwrap` in 32 | `const fn` lands, but for now this crate offers an easy workaround that you can use today. 33 | 34 | ## Crate Features 35 | 36 | | Feature name | Enabled by default? | Requires nightly? | Description | 37 | |---|---|---|---| 38 | | `tracked` | No | yes | Use the unstable [proc_macro_tracked_env](https://github.com/rust-lang/rust/issues/74690) feature to inform the build system about the used environment variables. | 39 | 40 | ## Usage 41 | 42 | Add the dependency. If your crate uses nightly, enable the `tracked` feature for better 43 | build dependency tracking. 44 | 45 | ```toml 46 | # If using a stable compiler: 47 | [dependencies] 48 | const_env = "0.1" 49 | 50 | # If using a nightly compiler: 51 | [dependencies] 52 | const_env = { version = "0.1", features = ["tracked"] } 53 | ``` 54 | 55 | At the top of your file import the `from_env!` macro. 56 | 57 | ```rust 58 | use const_env::from_env; 59 | ``` 60 | 61 | Use the macro to override the value of constants based on environment variables at build time. 62 | 63 | ```rust 64 | #[from_env] 65 | const FOO: u32 = 123; 66 | 67 | // This test will PASS if invoked as `FOO=456 cargo test` 68 | #[test] 69 | fn test() { 70 | assert_eq!(456, FOO); 71 | } 72 | ``` 73 | 74 | By default, the macro looks for an environment variable with the same name as the constant that it is attached to. 75 | 76 | ```rust 77 | // Use `FOO=true cargo build` to configure the value. 78 | #[from_env] 79 | const FOO: bool = false; 80 | ``` 81 | 82 | But you can specify a name explicitly too. 83 | 84 | ```rust 85 | // Use `BAR=true cargo build` to configure the value. 86 | #[from_env("BAR")] 87 | const FOO: bool = false; 88 | ``` 89 | 90 | The expression that you assign in your source acts as a default in case the environment variable does not exist. 91 | 92 | ```rust 93 | // If env var FOO is not set then the FOO constant will have the default value of '🦀'. 94 | #[from_env] 95 | const FOO: char = '🦀'; 96 | ``` 97 | 98 | Both `const` and `static` declarations are supported. 99 | 100 | ```rust 101 | // Both of these may be set by `FOO=abc BAZ=def cargo build`. 102 | #[from_env] 103 | const FOO: &'static str = "hello"; 104 | #[from_env("BAZ")] 105 | static BAR: &'static [u8] = b"world"; 106 | ``` 107 | 108 | ## Supported Types 109 | 110 | Strings! 111 | 112 | ```rust 113 | #[from_env] 114 | const FOO: &'static str = "hello"; 115 | 116 | // example: `FOO=abc cargo build` 117 | // results in: 118 | const FOO: &'static str = "abc"; 119 | ``` 120 | 121 | Byte strings! 122 | 123 | ```rust 124 | #[from_env] 125 | const FOO: &'static [u8] = b"hello"; 126 | 127 | // example: `FOO=world cargo build` 128 | // results in: 129 | const FOO: &'static [u8] = b"world"; 130 | ``` 131 | 132 | Bytes! 133 | ```rust 134 | #[from_env] 135 | const FOO: u8 = b'⚙'; 136 | 137 | // example: `FOO=🦀 cargo build` 138 | // results in: 139 | const FOO: u8 = b'🦀'; 140 | ``` 141 | 142 | Characters! 143 | 144 | ```rust 145 | #[from_env] 146 | const FOO: char = '⚙'; 147 | 148 | // example: `FOO=🦀 cargo build` 149 | // results in: 150 | const FOO: car = '🦀'; 151 | ``` 152 | 153 | Integers of all shapes and sizes! 154 | 155 | ```rust 156 | #[from_env] 157 | const FOO: u32 = 123; 158 | #[from_env] 159 | const BAR: i64 = 456; 160 | #[from_env] 161 | const BAZ: usize = 0; 162 | 163 | // example: `FOO=321 BAR=-456 BAZ=1usize cargo build` 164 | // results in: 165 | const FOO: u32 = 321; 166 | const BAR: i64 = -456; 167 | const BAZ: usize = 1usize; 168 | ``` 169 | 170 | Floats of all shapes and sizes! 171 | 172 | ```rust 173 | #[from_env] 174 | const FOO: f32 = 123.0; 175 | #[from_env] 176 | const BAR: f64 = 456.0; 177 | #[from_env] 178 | const BAZ: f32 = 0.0; 179 | 180 | // example: `FOO=321.0 BAR=-456.0 BAZ=1f32 cargo build` 181 | // results in: 182 | const FOO: f32 = 321.0; 183 | const BAR: f64 = -456.0; 184 | const BAZ: f32 = 1f32; 185 | ``` 186 | 187 | Booleans! 188 | 189 | ```rust 190 | #[from_env] 191 | const FOO: bool = false; 192 | 193 | // example: `FOO=true cargo build` 194 | // results in: 195 | const FOO: bool = true; 196 | ``` 197 | 198 | ## Known Limitations 199 | 200 | - Only top-level `const` and `static` declarations are supported. 201 | 202 | ## Alternatives 203 | 204 | - Writing a `build.rs` script which looks at the env vars and generates code based on them. This is conceptually similar to how this crate works, except that this crate uses a procedural macro instead of a build script. 205 | - Wait for [const fn](https://github.com/rust-lang/rust/issues/57563) to be finished. Ultimated 206 | this crate does the equivalent of `static FOO: u32 = std::option_env!("FOO").map(|val| val.parse::().unwrap_or(0)).unwrap_or(0);`, 207 | so once you can simply write that code this crate will become superfluous. -------------------------------------------------------------------------------- /const_env/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "const_env" 3 | version = "0.1.3" 4 | authors = ["Drake Tetreault "] 5 | edition = "2021" 6 | description = "Configure const and static items by environment variables." 7 | homepage = "https://github.com/EkardNT/const_env" 8 | repository = "https://github.com/EkardNT/const_env" 9 | readme = "README.md" 10 | keywords = ["env", "envvar", "environment", "variable"] 11 | categories = ["development-tools::build-utils", "config"] 12 | license = "CC0-1.0" 13 | 14 | [lib] 15 | proc_macro = true 16 | 17 | [features] 18 | tracked = [] 19 | 20 | [dependencies.const_env_impl] 21 | version = "=0.1.3" 22 | path = "../const_env_impl" 23 | 24 | [dev-dependencies] 25 | compiletest_rs = { version = "*", features = ["stable"] } -------------------------------------------------------------------------------- /const_env/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /const_env/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /const_env/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | feature = "tracked", 3 | feature(proc_macro_tracked_env) 4 | )] 5 | 6 | extern crate proc_macro; 7 | 8 | use proc_macro::TokenStream; 9 | 10 | /// Configure a `const` or `static` item from an environment variable. 11 | #[proc_macro_attribute] 12 | pub fn env_item(attr: TokenStream, item: TokenStream) -> TokenStream { 13 | #[cfg(not(feature = "tracked"))] 14 | let read_env = StableEnv {}; 15 | #[cfg(feature = "tracked")] 16 | let read_env = TrackedEnv {}; 17 | const_env_impl::from_env(attr.into(), item.into(), read_env).into() 18 | } 19 | 20 | #[proc_macro] 21 | pub fn env_lit(tokens: TokenStream) -> TokenStream { 22 | #[cfg(not(feature = "tracked"))] 23 | let read_env = StableEnv {}; 24 | #[cfg(feature = "tracked")] 25 | let read_env = TrackedEnv {}; 26 | const_env_impl::env_lit(tokens.into(), read_env).into() 27 | } 28 | 29 | #[cfg(feature = "tracked")] 30 | struct TrackedEnv; 31 | 32 | #[cfg(feature = "tracked")] 33 | impl const_env_impl::ReadEnv for TrackedEnv { 34 | fn read_env(&self, var_name: &String) -> Option { 35 | proc_macro::tracked_env::var(var_name).ok() 36 | } 37 | } 38 | 39 | struct StableEnv; 40 | 41 | impl const_env_impl::ReadEnv for StableEnv { 42 | fn read_env(&self, var_name: &String) -> Option { 43 | std::env::var(var_name).ok() 44 | } 45 | } -------------------------------------------------------------------------------- /const_env/tests/run-pass/tracked/tracked.rs: -------------------------------------------------------------------------------- 1 | extern crate const_env; 2 | 3 | use const_env::{env_item, env_lit}; 4 | 5 | #[env_item("TRACKED_STR")] 6 | const TRACKED_STR: &'static str = "foo"; 7 | 8 | const TRACKED_STR_LIT: &'static str = env_lit!("TRACKED_STR", "foo"); 9 | 10 | #[env_item("TRACKED_U32")] 11 | const TRACKED_U32: u32 = 123; 12 | 13 | const TRACKED_U32_LIT: u32 = env_lit!("TRACKED_U32", 123); 14 | 15 | fn main() { 16 | assert_eq!("tracked", TRACKED_STR); 17 | assert_eq!("tracked", TRACKED_STR_LIT); 18 | assert_eq!(4321, TRACKED_U32); 19 | assert_eq!(4321, TRACKED_U32_LIT); 20 | } -------------------------------------------------------------------------------- /const_env/tests/run-pass/untracked/array_literals.rs: -------------------------------------------------------------------------------- 1 | extern crate const_env; 2 | 3 | use const_env::env_lit; 4 | 5 | const USIZE_ARRAY: [usize; 3] = env_lit!("INT_ARRAY", [1, 2, 3]); 6 | 7 | const STRING_ARRAY: [&'static str; 1] = env_lit!("STRING_ARRAY", ["foo"]); 8 | 9 | const TUPLE_ARRAY: [(&'static str, bool); 2] = env_lit!("TUPLE_ARRAY", [("hello", true), ("world", false)]); 10 | 11 | fn main() { 12 | assert_eq!([10, 11, 12], USIZE_ARRAY); 13 | assert_eq!(["bar"], STRING_ARRAY); 14 | assert_eq!([("goodbye", false), ("planet", true)], TUPLE_ARRAY); 15 | } -------------------------------------------------------------------------------- /const_env/tests/run-pass/untracked/default.rs: -------------------------------------------------------------------------------- 1 | extern crate const_env; 2 | 3 | use const_env::{env_item, env_lit}; 4 | 5 | #[env_item] 6 | const DEFAULT_CHAR: char = 'A'; 7 | 8 | const DEFAULT_CHAR_LIT: char = env_lit!("DEFAULT_CHAR", 'A'); 9 | 10 | #[env_item("DEFAULT_BYTE_STR")] 11 | const DEFAULT_BYTE_STR: &'static [u8] = b"abcdefg"; 12 | 13 | const DEFAULT_BYTE_STR_LIT: &'static [u8] = env_lit!("DEFAULT_BYTE_STR", b"abcdefg"); 14 | 15 | fn main() { 16 | assert_eq!('A', DEFAULT_CHAR); 17 | assert_eq!('A', DEFAULT_CHAR_LIT); 18 | assert_eq!(b"abcdefg", DEFAULT_BYTE_STR); 19 | assert_eq!(b"abcdefg", DEFAULT_BYTE_STR_LIT); 20 | } -------------------------------------------------------------------------------- /const_env/tests/run-pass/untracked/explicit_item_name.rs: -------------------------------------------------------------------------------- 1 | extern crate const_env; 2 | 3 | use const_env::env_item; 4 | 5 | #[env_item("EXPLICIT_OVERRIDE_ISIZE")] 6 | const EXPLICIT_ISIZE: isize = 0; 7 | 8 | #[env_item("EXPLICIT_OVERRIDE_F64")] 9 | const EXPLICIT_F64: f64 = 0.0; 10 | 11 | fn main() { 12 | assert_eq!(123, EXPLICIT_ISIZE); 13 | assert_eq!(123.0, EXPLICIT_F64); 14 | } -------------------------------------------------------------------------------- /const_env/tests/run-pass/untracked/implicit_item_name.rs: -------------------------------------------------------------------------------- 1 | extern crate const_env; 2 | 3 | use const_env::env_item; 4 | 5 | #[env_item] 6 | const IMPLICIT_ISIZE: isize = 0; 7 | 8 | #[env_item] 9 | const IMPLICIT_F64: f64 = 0.0; 10 | 11 | fn main() { 12 | assert_eq!(123, IMPLICIT_ISIZE); 13 | assert_eq!(123.0, IMPLICIT_F64); 14 | } -------------------------------------------------------------------------------- /const_env/tests/run-pass/untracked/negative.rs: -------------------------------------------------------------------------------- 1 | extern crate const_env; 2 | 3 | use const_env::{env_item, env_lit}; 4 | 5 | #[env_item] 6 | const NEGATIVE_I16: i16 = 0; 7 | 8 | const NEGATIVE_I16_LIT: i16 = env_lit!("NEGATIVE_I16", 0); 9 | 10 | #[env_item] 11 | const NEGATIVE_ISIZE: isize = -1; 12 | 13 | const NEGATIVE_ISIZE_LIT: isize = env_lit!("NEGATIVE_ISIZE", -1); 14 | 15 | #[env_item] 16 | const NEGATIVE_F32: f32 = 0.0; 17 | 18 | const NEGATIVE_F32_LIT: f32 = env_lit!("NEGATIVE_F32", 0.0); 19 | 20 | fn main() { 21 | assert_eq!(-123, NEGATIVE_I16); 22 | assert_eq!(-123, NEGATIVE_I16_LIT); 23 | assert_eq!(-321, NEGATIVE_ISIZE); 24 | assert_eq!(-321, NEGATIVE_ISIZE_LIT); 25 | assert_eq!(-456.0, NEGATIVE_F32); 26 | assert_eq!(-456.0, NEGATIVE_F32_LIT); 27 | } -------------------------------------------------------------------------------- /const_env/tests/run-pass/untracked/quoted_literals.rs: -------------------------------------------------------------------------------- 1 | extern crate const_env; 2 | 3 | use const_env::env_lit; 4 | 5 | const STR: &'static str = env_lit!("QUOTED_STR", "foo"); 6 | const BYTE_STR: &'static [u8] = env_lit!("QUOTED_BYTE_STR", b"123"); 7 | // const CHAR: char = env_lit!("QUOTED_CHAR", 'A'); 8 | // const BYTE: u8 = env_lit!("QUOTED_BYTE", b'A'); 9 | 10 | fn main() { 11 | assert_eq!("hello world", STR); 12 | assert_eq!(b"01abcS", BYTE_STR); 13 | // assert_eq!('\t', CHAR); 14 | // assert_eq!(b'\n', BYTE); 15 | } -------------------------------------------------------------------------------- /const_env/tests/run-pass/untracked/smoke.rs: -------------------------------------------------------------------------------- 1 | extern crate const_env; 2 | 3 | use const_env::{env_item, env_lit}; 4 | 5 | #[env_item("SMOKE_STR")] 6 | const SMOKE_STR: &'static str = "foo"; 7 | 8 | const SMOKE_STR_LIT: &'static str = env_lit!("SMOKE_STR", "foo"); 9 | 10 | #[env_item("SMOKE_U32")] 11 | const SMOKE_U32: u32 = 123; 12 | 13 | const SMOKE_U32_LIT: u32 = env_lit!("SMOKE_U32", 123); 14 | 15 | fn main() { 16 | assert_eq!("bar", SMOKE_STR); 17 | assert_eq!("bar", SMOKE_STR_LIT); 18 | assert_eq!(321, SMOKE_U32); 19 | assert_eq!(321, SMOKE_U32_LIT); 20 | } -------------------------------------------------------------------------------- /const_env/tests/run-pass/untracked/struct_literals.rs: -------------------------------------------------------------------------------- 1 | extern crate const_env; 2 | 3 | use const_env::env_lit; 4 | 5 | #[derive(Eq, PartialEq, Debug)] 6 | struct Vec2 { 7 | x: T, 8 | y: T 9 | } 10 | 11 | static ORIGIN: Vec2 = env_lit!("ORIGIN", Vec2 { x: 0., y: 0.}); 12 | 13 | fn main() { 14 | assert_eq!(Vec2 { x: 1., y: 2.}, ORIGIN); 15 | } -------------------------------------------------------------------------------- /const_env/tests/run_tests.rs: -------------------------------------------------------------------------------- 1 | use std::env::set_var; 2 | use std::path::PathBuf; 3 | 4 | use compiletest_rs as ct; 5 | 6 | fn run_test(mode: &str, dir: &str, configure: impl FnOnce(&mut ct::Config)) { 7 | let mut config = ct::Config::default(); 8 | config.mode = mode.parse().expect("Invalid mode"); 9 | config.src_base = PathBuf::from(format!("tests/{}", dir)); 10 | configure(&mut config); 11 | config.link_deps(); 12 | config.clean_rmeta(); 13 | 14 | ct::run_tests(&config); 15 | } 16 | 17 | #[test] 18 | fn smoke() { 19 | set_var("EXPLICIT_OVERRIDE_ISIZE", "123"); 20 | set_var("EXPLICIT_OVERRIDE_F64", "123.0"); 21 | set_var("IMPLICIT_ISIZE", "123"); 22 | set_var("IMPLICIT_F64", "123.0"); 23 | set_var("NEGATIVE_I16", "-123"); 24 | set_var("NEGATIVE_ISIZE", "-321"); 25 | set_var("NEGATIVE_F32", "-456f32"); 26 | set_var("SMOKE_STR", "bar"); 27 | set_var("SMOKE_U32", "321"); 28 | set_var("QUOTED_STR", "hello world"); 29 | set_var("QUOTED_BYTE_STR", "01abcS"); 30 | set_var("QUOTED_CHAR", "\t"); 31 | set_var("QUOTED_BYTE", "\\n"); 32 | set_var("INT_ARRAY", "[10, 11, 12]"); 33 | set_var("STRING_ARRAY", "[\"bar\"]"); 34 | set_var("TUPLE_ARRAY", r#"[("goodbye", false), ("planet", true)]"#); 35 | set_var("ORIGIN", "Vec2 { x: 1., y: 2.}"); 36 | run_test("run-pass", "run-pass/untracked", |_| {}); 37 | } 38 | 39 | #[cfg(feature = "tracked")] 40 | #[test] 41 | fn tracked() { 42 | set_var("TRACKED_STR", "tracked"); 43 | set_var("TRACKED_U32", "4321"); 44 | run_test("run-pass", "run-pass/tracked", |config| { 45 | config.target_rustcflags = Some("--cfg feature=\"tracked\"".to_string()) 46 | }); 47 | } -------------------------------------------------------------------------------- /const_env_impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "const_env_impl" 3 | version = "0.1.3" 4 | authors = ["Drake Tetreault "] 5 | edition = "2021" 6 | description = "Configure const and static items by environment variables." 7 | homepage = "https://github.com/EkardNT/const_env" 8 | repository = "https://github.com/EkardNT/const_env" 9 | readme = "README.md" 10 | keywords = ["env", "envvar", "environment", "variable"] 11 | categories = ["development-tools::build-utils", "config"] 12 | license = "CC0-1.0" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | proc-macro2 = "1.0" 18 | syn = { version = "1.0", features = ["full", "extra-traits"] } 19 | quote = "1.0" -------------------------------------------------------------------------------- /const_env_impl/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /const_env_impl/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /const_env_impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::{ToTokens, quote_spanned}; 5 | use syn::{Expr, ExprLit, Lit}; 6 | use syn::spanned::Spanned; 7 | 8 | pub trait ReadEnv { 9 | fn read_env(&self, var_name: &String) -> Option; 10 | } 11 | 12 | pub struct TestEnv { 13 | env_vars: HashMap 14 | } 15 | 16 | impl TestEnv { 17 | pub fn builder() -> TestEnvBuilder { 18 | TestEnvBuilder { 19 | env_vars: HashMap::new() 20 | } 21 | } 22 | } 23 | 24 | impl ReadEnv for TestEnv { 25 | fn read_env(&self, var_name: &String) -> Option { 26 | self.env_vars.get(var_name).cloned() 27 | } 28 | } 29 | 30 | pub struct TestEnvBuilder { 31 | env_vars: HashMap 32 | } 33 | 34 | impl TestEnvBuilder { 35 | pub fn set(mut self, name: impl Into, value: impl Into) -> Self { 36 | self.env_vars.insert(name.into(), value.into()); 37 | self 38 | } 39 | 40 | pub fn build(self) -> TestEnv { 41 | TestEnv { 42 | env_vars: self.env_vars 43 | } 44 | } 45 | } 46 | 47 | struct MacroInput { 48 | env_var_name: syn::LitStr, 49 | default_value: syn::Expr, 50 | } 51 | 52 | impl syn::parse::Parse for MacroInput { 53 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 54 | let args = syn::punctuated::Punctuated::::parse_terminated(input)?; 55 | if args.len() != 2 { 56 | return Err(syn::Error::new(input.span(), "Exactly 2 arguments expected")); 57 | } 58 | let env_var_name = match args.first().unwrap() { 59 | Expr::Lit(ExprLit { lit: syn::Lit::Str(lit_str), .. }) => { 60 | lit_str.clone() 61 | }, 62 | otherwise => return Err(syn::Error::new(otherwise.span(), "Expected first argument to be a string literal")) 63 | }; 64 | let default_value = args.last().unwrap().clone(); 65 | Ok(Self { 66 | env_var_name, 67 | default_value 68 | }) 69 | } 70 | } 71 | 72 | /// Include environment variable contents as a Rust literal. 73 | pub fn env_lit(tokens: TokenStream, read_env: impl ReadEnv) -> TokenStream { 74 | let input: MacroInput = match syn::parse2(tokens) { 75 | Ok(input) => input, 76 | Err(err) => return err.to_compile_error() 77 | }; 78 | let env_var_value = match read_env.read_env(&input.env_var_name.value()) { 79 | Some(env_var_value) => env_var_value, 80 | None => return input.default_value.into_token_stream() 81 | }; 82 | let env_var_value_tokens = match env_var_value.parse::() { 83 | Ok(tokens) => tokens, 84 | Err(err) => return syn::Error::new(input.env_var_name.span(), format!("{}", err)).to_compile_error() 85 | }; 86 | // Special case logic for quoted literals such as strings. We want to allow users not 87 | // to need to quote their environment variable values for strings, even though this is 88 | // technically inconsistent with other literals. So here we handle string-like literals 89 | // specially by auto-adding quotes. Note that we only do this for top-level string literals - 90 | // other instances of literal strings, such as elements of an array, require the user to 91 | // manually quote them. 92 | match input.default_value { 93 | syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(_), ..}) => { 94 | let quoted = format!("\"{}\"", env_var_value); 95 | match syn::parse_str::("ed) { 96 | Ok(literal) => literal.to_token_stream(), 97 | Err(err) => syn::Error::new(input.env_var_name.span(), format!("Invalid string literal contents: {}", err)).to_compile_error() 98 | } 99 | } 100 | syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::ByteStr(_), ..}) => { 101 | let quoted = format!("b\"{}\"", env_var_value); 102 | match syn::parse_str::("ed) { 103 | Ok(literal) => literal.to_token_stream(), 104 | Err(err) => syn::Error::new(input.env_var_name.span(), format!("Invalid byte string literal contents: {}", err)).to_compile_error() 105 | } 106 | } 107 | syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Char(_), ..}) => { 108 | let quoted = format!("'{}'", env_var_value); 109 | match syn::parse_str::("ed) { 110 | Ok(literal) => literal.to_token_stream(), 111 | Err(err) => syn::Error::new(input.env_var_name.span(), format!("Invalid char literal contents: {}", err)).to_compile_error() 112 | } 113 | } 114 | syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Byte(_), ..}) => { 115 | let quoted = format!("b'{}'", env_var_value); 116 | match syn::parse_str::("ed) { 117 | Ok(literal) => literal.to_token_stream(), 118 | Err(err) => syn::Error::new(input.env_var_name.span(), format!("Invalid byte literal contents: {}", err)).to_compile_error() 119 | } 120 | } 121 | _ => env_var_value_tokens 122 | } 123 | } 124 | 125 | /// Inner implementation details of `const_env::from_env`. 126 | pub fn from_env(attr: TokenStream, item: TokenStream, read_env: impl ReadEnv) -> TokenStream { 127 | if let Ok(mut item_const) = syn::parse2::(item.clone()) { 128 | let default_var_name = format!("{}", item_const.ident); 129 | let var_name = extract_var_name(attr, default_var_name); 130 | let var_value = match read_env.read_env(&var_name) { 131 | Some(val) => val, 132 | None => return item 133 | }; 134 | let new_expr = value_to_literal(&var_value, &item_const.expr); 135 | let span = item_const.span(); 136 | item_const.expr = Box::new(new_expr); 137 | quote_spanned!(span => #item_const) 138 | } else if let Ok(mut item_static) = syn::parse2::(item.clone()) { 139 | let default_var_name = format!("{}", item_static.ident); 140 | let var_name = extract_var_name(attr, default_var_name); 141 | let var_value = match read_env.read_env(&var_name) { 142 | Some(val) => val, 143 | None => return item 144 | }; 145 | let new_expr = value_to_literal(&var_value, &item_static.expr); 146 | let span = item_static.span(); 147 | item_static.expr = Box::new(new_expr); 148 | quote_spanned!(span => #item_static) 149 | } else { 150 | panic!("TODO: error reporting"); 151 | } 152 | } 153 | 154 | fn extract_var_name(attr: TokenStream, default: String) -> String { 155 | if attr.is_empty() { 156 | return default; 157 | } 158 | let expr: Expr = syn::parse2(attr) 159 | .expect("Unable to parse attribute args as expression"); 160 | extract_var_name_from_expr(&expr) 161 | } 162 | 163 | fn extract_var_name_from_expr(expr: &Expr) -> String { 164 | match expr { 165 | Expr::Lit(literal) => { 166 | match &literal.lit { 167 | Lit::Str(lit_str) => { 168 | lit_str.value() 169 | }, 170 | _ => panic!("Attribute arguments are not a valid string literal") 171 | } 172 | }, 173 | Expr::Paren(paren) => { 174 | extract_var_name_from_expr(&paren.expr) 175 | }, 176 | _ => { 177 | panic!("Attribute arguments are not a valid string literal expression: {:?}", expr) 178 | } 179 | } 180 | } 181 | 182 | fn value_to_literal(value: &str, original_expr: &Expr) -> Expr { 183 | match original_expr { 184 | Expr::Array(_) => { 185 | syn::Expr::Array(syn::parse_str::(value).expect("Failed to parse environment variable contents as valid array")) 186 | }, 187 | Expr::Unary(_) => { 188 | // A unary sign indicates this is a numeric literal which doesn't need any 189 | // escaping, so we can parse it directly. 190 | let new: Expr = syn::parse_str(value) 191 | .expect("Failed to parse environment variable contents as valid expression"); 192 | return new; 193 | }, 194 | Expr::Lit(literal) => { 195 | let new_lit = match &literal.lit { 196 | Lit::Str(original) => { 197 | let mut new: syn::LitStr = syn::parse_str(&format!("\"{}\"", value)) 198 | .expect("Failed to parse environment variable contents as literal string"); 199 | new.set_span(original.span()); 200 | Lit::Str(new) 201 | }, 202 | Lit::ByteStr(original) => { 203 | let mut new: syn::LitByteStr = syn::parse_str(&format!("b\"{}\"", value)) 204 | .expect("Failed to parse environment variable contents as literal byte string"); 205 | new.set_span(original.span()); 206 | Lit::ByteStr(new) 207 | }, 208 | Lit::Byte(original) => { 209 | let mut new: syn::LitByte = syn::parse_str(&format!("b'{}'", value)) 210 | .expect("Failed to parse environment variable contents as literal byte"); 211 | new.set_span(original.span()); 212 | Lit::Byte(new) 213 | }, 214 | Lit::Char(original) => { 215 | let mut new: syn::LitChar = syn::parse_str(&format!("'{}'", value)) 216 | .expect("Failed to parse environment variable contents as literal character"); 217 | new.set_span(original.span()); 218 | Lit::Char(new) 219 | }, 220 | // These variants do not need any escaping and can be parsed as an expression 221 | // directly. 222 | Lit::Bool(_) | Lit::Int(_) | Lit::Float(_) | Lit::Verbatim(_) => { 223 | let new: Expr = syn::parse_str(value) 224 | .expect("Failed to parse environment variable contents as valid expression"); 225 | return new; 226 | } 227 | }; 228 | ExprLit { 229 | attrs: literal.attrs.clone(), 230 | lit: new_lit 231 | }.into() 232 | }, 233 | _ => panic!("Original const expression was not a recognized literal expression") 234 | } 235 | } -------------------------------------------------------------------------------- /const_env_impl/tests/from_env.rs: -------------------------------------------------------------------------------- 1 | use const_env_impl::{from_env, TestEnv}; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | 6 | #[test] 7 | fn test_str() { 8 | let env = TestEnv::builder() 9 | .set("MYVAR", "world") 10 | .build(); 11 | let attr: TokenStream = quote! { 12 | ("MYVAR") 13 | }; 14 | let item: TokenStream = quote! { 15 | const MYVAR: &'static str = "Hello"; 16 | }; 17 | let expected: TokenStream = quote! { 18 | const MYVAR: &'static str = "world"; 19 | }; 20 | let result = from_env(attr, item, env); 21 | assert_eq!(format!("{}", expected), format!("{}", result)); 22 | } 23 | 24 | #[test] 25 | fn test_str_with_escapes() { 26 | let env = TestEnv::builder() 27 | .set("MYVAR", "world\\tfoo") 28 | .build(); 29 | let attr: TokenStream = quote! { 30 | ("MYVAR") 31 | }; 32 | let item: TokenStream = quote! { 33 | const MYVAR: &'static str = "Hello"; 34 | }; 35 | let expected: TokenStream = quote! { 36 | const MYVAR: &'static str = "world\tfoo"; 37 | }; 38 | let result = from_env(attr, item, env); 39 | assert_eq!(format!("{}", expected), format!("{}", result)); 40 | } 41 | 42 | #[test] 43 | fn test_byte_str() { 44 | let env = TestEnv::builder() 45 | .set("MYVAR", "world") 46 | .build(); 47 | let attr: TokenStream = quote! { 48 | ("MYVAR") 49 | }; 50 | let item: TokenStream = quote! { 51 | const MYVAR: &'static [u8] = b"Hello"; 52 | }; 53 | let expected: TokenStream = quote! { 54 | const MYVAR: &'static [u8] = b"world"; 55 | }; 56 | let result = from_env(attr, item, env); 57 | assert_eq!(format!("{}", expected), format!("{}", result)); 58 | } 59 | 60 | #[test] 61 | fn test_u32() { 62 | let env = TestEnv::builder() 63 | .set("MYVAR", "1") 64 | .build(); 65 | let attr: TokenStream = quote! { 66 | ("MYVAR") 67 | }; 68 | let item: TokenStream = quote! { 69 | const MYVAR: u32 = 0; 70 | }; 71 | let expected: TokenStream = quote! { 72 | const MYVAR: u32 = 1; 73 | }; 74 | let result = from_env(attr, item, env); 75 | assert_eq!(format!("{}", expected), format!("{}", result)); 76 | } 77 | 78 | #[test] 79 | fn test_u32_with_suffix() { 80 | let env = TestEnv::builder() 81 | .set("MYVAR", "1u32") 82 | .build(); 83 | let attr: TokenStream = quote! { 84 | ("MYVAR") 85 | }; 86 | let item: TokenStream = quote! { 87 | const MYVAR: u32 = 0; 88 | }; 89 | let expected: TokenStream = quote! { 90 | const MYVAR: u32 = 1u32; 91 | }; 92 | let result = from_env(attr, item, env); 93 | assert_eq!(format!("{}", expected), format!("{}", result)); 94 | } 95 | 96 | #[test] 97 | fn test_i64() { 98 | let env = TestEnv::builder() 99 | .set("MYVAR", "1") 100 | .build(); 101 | let attr: TokenStream = quote! { 102 | ("MYVAR") 103 | }; 104 | let item: TokenStream = quote! { 105 | const MYVAR: i64 = 0; 106 | }; 107 | let expected: TokenStream = quote! { 108 | const MYVAR: i64 = 1; 109 | }; 110 | let result = from_env(attr, item, env); 111 | assert_eq!(format!("{}", expected), format!("{}", result)); 112 | } 113 | 114 | #[test] 115 | fn test_i64_with_suffix() { 116 | let env = TestEnv::builder() 117 | .set("MYVAR", "1i64") 118 | .build(); 119 | let attr: TokenStream = quote! { 120 | ("MYVAR") 121 | }; 122 | let item: TokenStream = quote! { 123 | const MYVAR: i64 = 0; 124 | }; 125 | let expected: TokenStream = quote! { 126 | const MYVAR: i64 = 1i64; 127 | }; 128 | let result = from_env(attr, item, env); 129 | assert_eq!(format!("{}", expected), format!("{}", result)); 130 | } 131 | 132 | #[test] 133 | fn test_i64_with_negative() { 134 | let env = TestEnv::builder() 135 | .set("MYVAR", "-1") 136 | .build(); 137 | let attr: TokenStream = quote! { 138 | ("MYVAR") 139 | }; 140 | let item: TokenStream = quote! { 141 | const MYVAR: i64 = -0; 142 | }; 143 | let expected: TokenStream = quote! { 144 | const MYVAR: i64 = -1; 145 | }; 146 | let result = from_env(attr, item, env); 147 | assert_eq!(format!("{}", expected), format!("{}", result)); 148 | } 149 | 150 | #[test] 151 | fn test_char() { 152 | let env = TestEnv::builder() 153 | .set("MYVAR", "b") 154 | .build(); 155 | let attr: TokenStream = quote! { 156 | ("MYVAR") 157 | }; 158 | let item: TokenStream = quote! { 159 | const MYVAR: char = 'a'; 160 | }; 161 | let expected: TokenStream = quote! { 162 | const MYVAR: char = 'b'; 163 | }; 164 | let result = from_env(attr, item, env); 165 | assert_eq!(format!("{}", expected), format!("{}", result)); 166 | } 167 | 168 | #[test] 169 | fn test_byte() { 170 | let env = TestEnv::builder() 171 | .set("MYVAR", "b") 172 | .build(); 173 | let attr: TokenStream = quote! { 174 | ("MYVAR") 175 | }; 176 | let item: TokenStream = quote! { 177 | const MYVAR: u8 = b'a'; 178 | }; 179 | let expected: TokenStream = quote! { 180 | const MYVAR: u8 = b'b'; 181 | }; 182 | let result = from_env(attr, item, env); 183 | assert_eq!(format!("{}", expected), format!("{}", result)); 184 | } 185 | 186 | #[test] 187 | fn test_byte_with_escape() { 188 | let env = TestEnv::builder() 189 | .set("MYVAR", "\\n") 190 | .build(); 191 | let attr: TokenStream = quote! { 192 | ("MYVAR") 193 | }; 194 | let item: TokenStream = quote! { 195 | const MYVAR: u8 = b'\t'; 196 | }; 197 | let expected: TokenStream = quote! { 198 | const MYVAR: u8 = b'\n'; 199 | }; 200 | let result = from_env(attr, item, env); 201 | assert_eq!(format!("{}", expected), format!("{}", result)); 202 | } 203 | 204 | #[test] 205 | fn test_f32() { 206 | let env = TestEnv::builder() 207 | .set("MYVAR", "1.0") 208 | .build(); 209 | let attr: TokenStream = quote! { 210 | ("MYVAR") 211 | }; 212 | let item: TokenStream = quote! { 213 | const MYVAR: f32 = 0.0; 214 | }; 215 | let expected: TokenStream = quote! { 216 | const MYVAR: f32 = 1.0; 217 | }; 218 | let result = from_env(attr, item, env); 219 | assert_eq!(format!("{}", expected), format!("{}", result)); 220 | } 221 | 222 | #[test] 223 | fn test_f32_with_suffix() { 224 | let env = TestEnv::builder() 225 | .set("MYVAR", "1f32") 226 | .build(); 227 | let attr: TokenStream = quote! { 228 | ("MYVAR") 229 | }; 230 | let item: TokenStream = quote! { 231 | const MYVAR: f32 = 0.0; 232 | }; 233 | let expected: TokenStream = quote! { 234 | const MYVAR: f32 = 1f32; 235 | }; 236 | let result = from_env(attr, item, env); 237 | assert_eq!(format!("{}", expected), format!("{}", result)); 238 | } 239 | 240 | #[test] 241 | fn test_f32_with_negative() { 242 | let env = TestEnv::builder() 243 | .set("MYVAR", "-1.0") 244 | .build(); 245 | let attr: TokenStream = quote! { 246 | ("MYVAR") 247 | }; 248 | let item: TokenStream = quote! { 249 | const MYVAR: f32 = -0.0; 250 | }; 251 | let expected: TokenStream = quote! { 252 | const MYVAR: f32 = -1.0; 253 | }; 254 | let result = from_env(attr, item, env); 255 | assert_eq!(format!("{}", expected), format!("{}", result)); 256 | } 257 | 258 | #[test] 259 | fn test_bool() { 260 | let env = TestEnv::builder() 261 | .set("MYVAR", "true") 262 | .build(); 263 | let attr: TokenStream = quote! { 264 | ("MYVAR") 265 | }; 266 | let item: TokenStream = quote! { 267 | const MYVAR: bool = false; 268 | }; 269 | let expected: TokenStream = quote! { 270 | const MYVAR: bool = true; 271 | }; 272 | let result = from_env(attr, item, env); 273 | assert_eq!(format!("{}", expected), format!("{}", result)); 274 | } 275 | 276 | #[test] 277 | fn test_str_static() { 278 | let env = TestEnv::builder() 279 | .set("MYVAR", "world") 280 | .build(); 281 | let attr: TokenStream = quote! { 282 | ("MYVAR") 283 | }; 284 | let item: TokenStream = quote! { 285 | static MYVAR: &'static str = "Hello"; 286 | }; 287 | let expected: TokenStream = quote! { 288 | static MYVAR: &'static str = "world"; 289 | }; 290 | let result = from_env(attr, item, env); 291 | assert_eq!(format!("{}", expected), format!("{}", result)); 292 | } 293 | 294 | #[test] 295 | fn test_i16_negative() { 296 | let env = TestEnv::builder() 297 | .set("MYVAR", "-123") 298 | .build(); 299 | let attr: TokenStream = quote! { 300 | ("MYVAR") 301 | }; 302 | let item: TokenStream = quote! { 303 | static MYVAR: i16 = 0; 304 | }; 305 | let expected: TokenStream = quote! { 306 | static MYVAR: i16 = -123; 307 | }; 308 | let result = from_env(attr, item, env); 309 | assert_eq!(format!("{}", expected), format!("{}", result)); 310 | } 311 | 312 | #[test] 313 | fn test_f32_negative() { 314 | let env = TestEnv::builder() 315 | .set("MYVAR", "-123.0") 316 | .build(); 317 | let attr: TokenStream = quote! { 318 | ("MYVAR") 319 | }; 320 | let item: TokenStream = quote! { 321 | static MYVAR: f32 = 0.0; 322 | }; 323 | let expected: TokenStream = quote! { 324 | static MYVAR: f32 = -123.0; 325 | }; 326 | let result = from_env(attr, item, env); 327 | assert_eq!(format!("{}", expected), format!("{}", result)); 328 | } 329 | 330 | #[test] 331 | fn test_default_name() { 332 | let env = TestEnv::builder() 333 | .set("MYVAR", "world") 334 | .build(); 335 | let attr: TokenStream = TokenStream::new(); 336 | let item: TokenStream = quote! { 337 | static MYVAR: &'static str = "Hello"; 338 | }; 339 | let expected: TokenStream = quote! { 340 | static MYVAR: &'static str = "world"; 341 | }; 342 | let result = from_env(attr, item, env); 343 | assert_eq!(format!("{}", expected), format!("{}", result)); 344 | } 345 | 346 | #[test] 347 | fn test_i32_negative_whitespace() { 348 | let env = TestEnv::builder() 349 | .set("MYVAR", " - 123 ") 350 | .build(); 351 | let attr: TokenStream = quote! { 352 | ("MYVAR") 353 | }; 354 | let item: TokenStream = quote! { 355 | static MYVAR: i32 = 0; 356 | }; 357 | let expected: TokenStream = quote! { 358 | static MYVAR: i32 = -123; 359 | }; 360 | let result = from_env(attr, item, env); 361 | assert_eq!(format!("{}", expected), format!("{}", result)); 362 | } --------------------------------------------------------------------------------