1#![deny(missing_docs, clippy::missing_docs_in_private_items)]
2#![cfg_attr(windows, doc = include_str!("..\\README.md"))]
3#![cfg_attr(not(windows), doc = include_str!("../README.md"))]
4
5use std::env::{self, VarError};
6
7use proc_macro::{Literal, TokenStream, TokenTree};
8use proc_macro_error::{abort_call_site, proc_macro_error};
9
10mod encrypt;
11use encrypt::encrypt;
12
13macro_rules! syntax_error {
15 ($macro_name:ident) => {
16 match $macro_name {
17 "envc" => abort_call_site!("Invalid syntax. Valid forms are `envc!(\"VAR_NAME\")` and `envc!(\"VAR_NAME\", \"custom error message\")`."),
18
19 "option_envc" => abort_call_site!("Invalid syntax. Expected input of the form `option_envc!(\"VAR_NAME\")`"),
20
21 _ => panic!("Unknown macro name")
22 }
23 };
24}
25
26#[allow(missing_docs)]
27#[proc_macro_error]
28#[proc_macro]
29pub fn envc(tokens: TokenStream) -> TokenStream {
30 let (env_var_key, abort_message) = match parse(tokens, "envc") {
31 Input::VariableName(variable_name) => (
32 variable_name.clone(),
33 format!("environment variable `{}` not defined", &variable_name),
34 ),
35 Input::VariableNameAndAbortMessage {
36 variable_name,
37 abort_message,
38 } => (variable_name, abort_message),
39 };
40
41 match env::var(&env_var_key) {
42 Ok(unencrypted_variable) => encrypt(unencrypted_variable),
43
44 Err(VarError::NotUnicode(_)) => {
45 abort_call_site!(
46 "Environment variable ${} contains non-unicode value",
47 &env_var_key
48 )
49 }
50
51 Err(VarError::NotPresent) => abort_call_site!("{}", abort_message),
52 }
53}
54
55#[allow(missing_docs)]
56#[proc_macro_error]
57#[proc_macro]
58pub fn option_envc(tokens: TokenStream) -> TokenStream {
59 let env_var_key = match parse(tokens, "option_envc") {
60 Input::VariableName(variable_name) => variable_name,
61 Input::VariableNameAndAbortMessage { .. } => abort_call_site!(
62 "Invalid syntax. Expected input of the form `option_envc!(\"VAR_NAME\")`"
63 ),
64 };
65
66 match env::var(&env_var_key) {
67 Err(VarError::NotUnicode(_)) => {
68 abort_call_site!(
69 "Environment variable ${} contains non-unicode value",
70 &env_var_key
71 )
72 }
73 maybe_missing => encrypt(maybe_missing.ok()),
74 }
75}
76
77fn stringify(literal: &Literal) -> Option<String> {
80 let stringified = literal.to_string();
81 if stringified.starts_with('"') && stringified.ends_with('"') {
82 Some(stringified[1..stringified.len() - 1].to_owned())
83 } else {
84 None
85 }
86}
87
88enum Input {
90 VariableName(String),
92
93 VariableNameAndAbortMessage {
95 variable_name: String,
97
98 abort_message: String,
100 },
101}
102
103fn parse(tokens: TokenStream, macro_name: &str) -> Input {
105 let tokens_vec = tokens.into_iter().collect::<Vec<_>>();
106
107 match *tokens_vec.as_slice() {
108 [TokenTree::Literal(ref variable_literal)] => {
110 if let Some(variable) = stringify(variable_literal) {
111 Input::VariableName(variable)
112 } else {
113 syntax_error!(macro_name)
114 }
115 }
116
117 [TokenTree::Literal(ref variable_literal), TokenTree::Punct(ref comma), TokenTree::Literal(ref message_literal)] => {
119 match (
120 stringify(variable_literal),
121 comma.as_char(),
122 stringify(message_literal),
123 ) {
124 (Some(variable_name), ',', Some(abort_message)) => {
125 Input::VariableNameAndAbortMessage {
126 variable_name,
127 abort_message,
128 }
129 }
130 _ => syntax_error!(macro_name),
131 }
132 }
133 _ => syntax_error!(macro_name),
134 }
135}