envvault/cli/
env_parser.rs1use std::collections::HashMap;
6use std::fs;
7use std::path::Path;
8
9use crate::errors::{EnvVaultError, Result};
10
11pub fn parse_env_line(line: &str) -> Option<(&str, &str)> {
16 let trimmed = line.trim();
17
18 if trimmed.is_empty() || trimmed.starts_with('#') {
20 return None;
21 }
22
23 let trimmed = trimmed.strip_prefix("export ").unwrap_or(trimmed);
25
26 let (key, value) = trimmed.split_once('=')?;
28 let key = key.trim();
29 let value = value.trim();
30
31 let value = value
33 .strip_prefix('"')
34 .and_then(|v| v.strip_suffix('"'))
35 .or_else(|| value.strip_prefix('\'').and_then(|v| v.strip_suffix('\'')))
36 .unwrap_or(value);
37
38 if key.is_empty() {
39 return None;
40 }
41
42 Some((key, value))
43}
44
45pub fn parse_env_file(path: &Path) -> Result<HashMap<String, String>> {
47 let content = fs::read_to_string(path)
48 .map_err(|e| EnvVaultError::CommandFailed(format!("failed to read file: {e}")))?;
49
50 let mut secrets = HashMap::new();
51
52 for line in content.lines() {
53 if let Some((key, value)) = parse_env_line(line) {
54 secrets.insert(key.to_string(), value.to_string());
55 }
56 }
57
58 Ok(secrets)
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn parse_simple_key_value() {
67 assert_eq!(parse_env_line("KEY=value"), Some(("KEY", "value")));
68 }
69
70 #[test]
71 fn parse_export_prefix() {
72 assert_eq!(
73 parse_env_line("export DATABASE_URL=postgres://localhost/db"),
74 Some(("DATABASE_URL", "postgres://localhost/db"))
75 );
76 }
77
78 #[test]
79 fn parse_value_with_equals() {
80 assert_eq!(parse_env_line("KEY=val=ue"), Some(("KEY", "val=ue")));
81 }
82
83 #[test]
84 fn parse_double_quoted_value() {
85 assert_eq!(
86 parse_env_line(r#"KEY="hello world""#),
87 Some(("KEY", "hello world"))
88 );
89 }
90
91 #[test]
92 fn parse_single_quoted_value() {
93 assert_eq!(
94 parse_env_line("KEY='hello world'"),
95 Some(("KEY", "hello world"))
96 );
97 }
98
99 #[test]
100 fn parse_empty_value() {
101 assert_eq!(parse_env_line("KEY="), Some(("KEY", "")));
102 }
103
104 #[test]
105 fn parse_empty_quoted_value() {
106 assert_eq!(parse_env_line(r#"KEY="""#), Some(("KEY", "")));
107 }
108
109 #[test]
110 fn parse_skips_comments() {
111 assert_eq!(parse_env_line("# this is a comment"), None);
112 }
113
114 #[test]
115 fn parse_skips_blank_lines() {
116 assert_eq!(parse_env_line(""), None);
117 assert_eq!(parse_env_line(" "), None);
118 }
119
120 #[test]
121 fn parse_skips_lines_without_equals() {
122 assert_eq!(parse_env_line("NOEQUALS"), None);
123 }
124
125 #[test]
126 fn parse_trims_whitespace() {
127 assert_eq!(parse_env_line(" KEY = value "), Some(("KEY", "value")));
128 }
129}