1use crate::EnvVar;
2use color_eyre::Result;
3use std::fs;
4use std::path::Path;
5
6#[derive(Debug, Clone, Copy)]
7pub enum ExportFormat {
8 DotEnv,
9 Json,
10 Yaml,
11 Text,
12 PowerShell,
13 Shell,
14}
15
16impl ExportFormat {
17 pub fn from_extension(path: &str) -> Result<Self> {
24 let ext = Path::new(path).extension().and_then(|s| s.to_str()).unwrap_or("");
25
26 match ext.to_lowercase().as_str() {
27 "env" => Ok(Self::DotEnv),
28 "json" => Ok(Self::Json),
29 "yaml" | "yml" => Ok(Self::Yaml),
30 "txt" | "text" => Ok(Self::Text),
31 "ps1" => Ok(Self::PowerShell),
32 "sh" | "bash" => Ok(Self::Shell),
33 _ => {
34 let filename = Path::new(path).file_name().and_then(|s| s.to_str()).unwrap_or("");
36
37 if filename.starts_with('.') && filename.contains("env") {
38 Ok(Self::DotEnv)
39 } else {
40 Ok(Self::Text) }
42 }
43 }
44 }
45}
46
47pub struct Exporter {
48 variables: Vec<EnvVar>,
49 include_metadata: bool,
50}
51
52impl Exporter {
53 #[must_use]
54 pub const fn new(variables: Vec<EnvVar>, include_metadata: bool) -> Self {
55 Self {
56 variables,
57 include_metadata,
58 }
59 }
60
61 #[must_use]
62 pub fn count(&self) -> usize {
63 self.variables.len()
64 }
65
66 pub fn export_to_file(&self, path: &str, format: ExportFormat) -> Result<()> {
75 let content = match format {
76 ExportFormat::DotEnv => self.to_dotenv(),
77 ExportFormat::Json => self.to_json()?,
78 ExportFormat::Yaml => self.to_yaml(),
79 ExportFormat::Text => self.to_text(),
80 ExportFormat::PowerShell => self.to_powershell(),
81 ExportFormat::Shell => self.to_shell(),
82 };
83
84 fs::write(path, content)?;
85 Ok(())
86 }
87
88 fn to_dotenv(&self) -> String {
89 let mut lines = Vec::new();
90
91 if self.include_metadata {
92 lines.push("# Environment variables exported by envx".to_string());
93 lines.push(format!(
94 "# Date: {}",
95 chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
96 ));
97 lines.push(format!("# Count: {}", self.variables.len()));
98 lines.push(String::new());
99 }
100
101 for var in &self.variables {
102 if self.include_metadata {
103 lines.push(format!(
104 "# Source: {:?}, Modified: {}",
105 var.source,
106 var.modified.format("%Y-%m-%d %H:%M:%S")
107 ));
108 }
109
110 let needs_quotes = var.value.contains(' ')
113 || var.value.contains('=')
114 || var.value.contains('#')
115 || var.value.contains('"')
116 || var.value.contains('\'')
117 || var.value.contains('\n')
118 || var.value.contains('\r')
119 || var.value.contains('\t');
120
121 if needs_quotes {
122 let escaped_value = var
124 .value
125 .replace('"', "\\\"") .replace('\n', "\\n") .replace('\r', "\\r") .replace('\t', "\\t"); lines.push(format!("{}=\"{}\"", var.name, escaped_value));
132 } else {
133 lines.push(format!("{}={}", var.name, var.value));
136 }
137 }
138
139 lines.join("\n")
140 }
141
142 fn to_json(&self) -> Result<String> {
143 if self.include_metadata {
144 let export_data = serde_json::json!({
146 "exported_at": chrono::Utc::now(),
147 "count": self.variables.len(),
148 "variables": self.variables
149 });
150 Ok(serde_json::to_string_pretty(&export_data)?)
151 } else {
152 let mut map = serde_json::Map::new();
154 for var in &self.variables {
155 map.insert(var.name.clone(), serde_json::Value::String(var.value.clone()));
156 }
157 Ok(serde_json::to_string_pretty(&map)?)
158 }
159 }
160
161 fn to_yaml(&self) -> String {
162 let mut lines = Vec::new();
163
164 if self.include_metadata {
165 lines.push("# Environment variables exported by envx".to_string());
166 lines.push(format!(
167 "# Date: {}",
168 chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
169 ));
170 lines.push("---".to_string());
171 }
172
173 for var in &self.variables {
174 if self.include_metadata {
175 lines.push(format!("# Source: {:?}", var.source));
176 }
177
178 let value = if var.value.contains(':')
181 || var.value.contains('#')
182 || var.value.contains('"')
183 || var.value.contains('\'')
184 || var.value.contains('\n')
185 || var.value.contains('\r')
186 || var.value.contains('\t')
187 || var.value.starts_with(' ')
188 || var.value.ends_with(' ')
189 || var.value.starts_with('-')
190 || var.value.starts_with('*')
191 || var.value.starts_with('&')
192 || var.value.starts_with('!')
193 || var.value.starts_with('[')
194 || var.value.starts_with('{')
195 || var.value.starts_with('>')
196 || var.value.starts_with('|')
197 {
198 let escaped = var
200 .value
201 .replace('"', "\\\"") .replace('\n', "\\n") .replace('\r', "\\r") .replace('\t', "\\t"); format!("\"{escaped}\"")
208 } else {
209 var.value.clone()
210 };
211
212 lines.push(format!("{}: {}", var.name, value));
213 }
214
215 lines.join("\n")
216 }
217
218 fn to_text(&self) -> String {
219 let mut lines = Vec::new();
220
221 if self.include_metadata {
222 lines.push("# Environment Variables Export".to_string());
223 lines.push(format!("# Generated: {}", chrono::Utc::now()));
224 lines.push(format!("# Total: {} variables", self.variables.len()));
225 lines.push("#".repeat(50));
226 lines.push(String::new());
227 }
228
229 for var in &self.variables {
230 if self.include_metadata {
231 lines.push(format!("# Name: {}", var.name));
232 lines.push(format!("# Source: {:?}", var.source));
233 lines.push(format!("# Modified: {}", var.modified));
234 }
235 lines.push(format!("{}={}", var.name, var.value));
236 if self.include_metadata {
237 lines.push(String::new());
238 }
239 }
240
241 lines.join("\n")
242 }
243
244 fn to_powershell(&self) -> String {
245 let mut lines = Vec::new();
246
247 lines.push("# PowerShell Environment Variables Script".to_string());
248 lines.push(format!("# Generated by envx - {}", chrono::Utc::now()));
249 lines.push(String::new());
250
251 for var in &self.variables {
252 if self.include_metadata {
253 lines.push(format!("# {} ({:?})", var.name, var.source));
254 }
255
256 let escaped_value = var.value.replace('`', "``").replace('"', "`\"");
258 lines.push(format!("$env:{} = \"{}\"", var.name, escaped_value));
259 }
260
261 lines.join("\n")
262 }
263
264 fn to_shell(&self) -> String {
265 let mut lines = Vec::new();
266
267 lines.push("#!/bin/bash".to_string());
268 lines.push("# Shell Environment Variables Script".to_string());
269 lines.push(format!("# Generated by envx - {}", chrono::Utc::now()));
270 lines.push(String::new());
271
272 for var in &self.variables {
273 if self.include_metadata {
274 lines.push(format!("# {} ({:?})", var.name, var.source));
275 }
276
277 let escaped_value = var
279 .value
280 .replace('\\', "\\\\")
281 .replace('"', "\\\"")
282 .replace('$', "\\$")
283 .replace('`', "\\`");
284
285 lines.push(format!("export {}=\"{}\"", var.name, escaped_value));
286 }
287
288 lines.join("\n")
289 }
290}
291
292#[cfg(test)]
295mod tests {
296 #![allow(clippy::cognitive_complexity)]
297 use super::*;
298 use crate::EnvVar;
299 use crate::EnvVarSource as VarSource;
300 use chrono::{DateTime, Utc};
301 use std::fs;
302 use tempfile::NamedTempFile;
303
304 fn create_test_vars() -> Vec<EnvVar> {
306 vec![
307 EnvVar {
308 name: "SIMPLE_VAR".to_string(),
309 value: "simple_value".to_string(),
310 source: VarSource::User,
311 modified: Utc::now(),
312 original_value: None,
313 },
314 EnvVar {
315 name: "PATH_VAR".to_string(),
316 value: "C:\\Program Files\\App;C:\\Windows\\System32".to_string(),
317 source: VarSource::System,
318 modified: Utc::now(),
319 original_value: None,
320 },
321 EnvVar {
322 name: "QUOTED_VAR".to_string(),
323 value: "value with \"quotes\" and 'single quotes'".to_string(),
324 source: VarSource::User,
325 modified: Utc::now(),
326 original_value: None,
327 },
328 EnvVar {
329 name: "SPECIAL_CHARS".to_string(),
330 value: "line1\nline2\ttab\\backslash".to_string(),
331 source: VarSource::Process,
332 modified: Utc::now(),
333 original_value: None,
334 },
335 EnvVar {
336 name: "EMPTY_VAR".to_string(),
337 value: String::new(),
338 source: VarSource::User,
339 modified: Utc::now(),
340 original_value: None,
341 },
342 EnvVar {
343 name: "UNICODE_VAR".to_string(),
344 value: "Hello δΈη π".to_string(),
345 source: VarSource::User,
346 modified: Utc::now(),
347 original_value: None,
348 },
349 ]
350 }
351
352 #[test]
353 fn test_export_format_from_extension() {
354 assert!(matches!(
355 ExportFormat::from_extension("file.env").unwrap(),
356 ExportFormat::DotEnv
357 ));
358 assert!(matches!(
359 ExportFormat::from_extension("file.ENV").unwrap(),
360 ExportFormat::DotEnv
361 ));
362 assert!(matches!(
363 ExportFormat::from_extension("file.json").unwrap(),
364 ExportFormat::Json
365 ));
366 assert!(matches!(
367 ExportFormat::from_extension("file.JSON").unwrap(),
368 ExportFormat::Json
369 ));
370 assert!(matches!(
371 ExportFormat::from_extension("file.yaml").unwrap(),
372 ExportFormat::Yaml
373 ));
374 assert!(matches!(
375 ExportFormat::from_extension("file.yml").unwrap(),
376 ExportFormat::Yaml
377 ));
378 assert!(matches!(
379 ExportFormat::from_extension("file.txt").unwrap(),
380 ExportFormat::Text
381 ));
382 assert!(matches!(
383 ExportFormat::from_extension("file.text").unwrap(),
384 ExportFormat::Text
385 ));
386 assert!(matches!(
387 ExportFormat::from_extension("file.ps1").unwrap(),
388 ExportFormat::PowerShell
389 ));
390 assert!(matches!(
391 ExportFormat::from_extension("file.sh").unwrap(),
392 ExportFormat::Shell
393 ));
394 assert!(matches!(
395 ExportFormat::from_extension("file.bash").unwrap(),
396 ExportFormat::Shell
397 ));
398
399 assert!(matches!(
401 ExportFormat::from_extension(".env").unwrap(),
402 ExportFormat::DotEnv
403 ));
404 assert!(matches!(
405 ExportFormat::from_extension(".env.local").unwrap(),
406 ExportFormat::DotEnv
407 ));
408 assert!(matches!(
409 ExportFormat::from_extension(".env.production").unwrap(),
410 ExportFormat::DotEnv
411 ));
412
413 assert!(matches!(
415 ExportFormat::from_extension("file.xyz").unwrap(),
416 ExportFormat::Text
417 ));
418 assert!(matches!(
419 ExportFormat::from_extension("file").unwrap(),
420 ExportFormat::Text
421 ));
422 }
423
424 #[test]
425 fn test_exporter_new() {
426 let vars = create_test_vars();
427 let exporter = Exporter::new(vars.clone(), true);
428
429 assert_eq!(exporter.count(), vars.len());
430 }
431
432 #[test]
433 fn test_to_dotenv_without_metadata() {
434 let vars = create_test_vars();
435 let exporter = Exporter::new(vars, false);
436
437 let output = exporter.to_dotenv();
438
439 assert!(output.contains("SIMPLE_VAR=simple_value"));
441
442 assert!(output.contains("PATH_VAR=\"C:\\Program Files\\App;C:\\Windows\\System32\""));
445 assert!(output.contains("QUOTED_VAR=\"value with \\\"quotes\\\" and 'single quotes'\""));
446
447 assert!(output.contains("SPECIAL_CHARS=\"line1\\nline2\\ttab\\backslash\""));
449
450 assert!(output.contains("EMPTY_VAR="));
452
453 assert!(!output.contains("# Environment variables exported by envx"));
455 assert!(!output.contains("# Source:"));
456 }
457
458 #[test]
459 fn test_to_dotenv_with_metadata() {
460 let vars = create_test_vars();
461 let exporter = Exporter::new(vars, true);
462
463 let output = exporter.to_dotenv();
464
465 assert!(output.contains("# Environment variables exported by envx"));
467 assert!(output.contains("# Date:"));
468 assert!(output.contains("# Count: 6"));
469 assert!(output.contains("# Source:"));
470 assert!(output.contains("Modified:"));
471 }
472
473 #[test]
474 fn test_to_dotenv_edge_cases() {
475 let vars = vec![
476 EnvVar {
477 name: "HASH_VALUE".to_string(),
478 value: "value#with#hashes".to_string(),
479 source: VarSource::User,
480 modified: Utc::now(),
481 original_value: None,
482 },
483 EnvVar {
484 name: "EQUALS_VALUE".to_string(),
485 value: "key=value=pairs".to_string(),
486 source: VarSource::User,
487 modified: Utc::now(),
488 original_value: None,
489 },
490 EnvVar {
491 name: "SPACES_AROUND".to_string(),
492 value: " spaces at start and end ".to_string(),
493 source: VarSource::User,
494 modified: Utc::now(),
495 original_value: None,
496 },
497 ];
498
499 let exporter = Exporter::new(vars, false);
500 let output = exporter.to_dotenv();
501
502 assert!(output.contains("HASH_VALUE=\"value#with#hashes\""));
504 assert!(output.contains("EQUALS_VALUE=\"key=value=pairs\""));
505 assert!(output.contains("SPACES_AROUND=\" spaces at start and end \""));
506 }
507
508 #[test]
509 fn test_to_json_without_metadata() {
510 let vars = create_test_vars();
511 let exporter = Exporter::new(vars, false);
512
513 let output = exporter.to_json().unwrap();
514 let json: serde_json::Value = serde_json::from_str(&output).unwrap();
515
516 assert!(json.is_object());
518 assert_eq!(json["SIMPLE_VAR"], "simple_value");
519 assert_eq!(json["PATH_VAR"], "C:\\Program Files\\App;C:\\Windows\\System32");
520 assert_eq!(json["QUOTED_VAR"], "value with \"quotes\" and 'single quotes'");
521 assert_eq!(json["SPECIAL_CHARS"], "line1\nline2\ttab\\backslash");
522 assert_eq!(json["EMPTY_VAR"], "");
523 assert_eq!(json["UNICODE_VAR"], "Hello δΈη π");
524 }
525
526 #[test]
527 fn test_to_json_with_metadata() {
528 let vars = create_test_vars();
529 let exporter = Exporter::new(vars, true);
530
531 let output = exporter.to_json().unwrap();
532 let json: serde_json::Value = serde_json::from_str(&output).unwrap();
533
534 assert!(json.is_object());
536 assert!(json["exported_at"].is_string());
537 assert_eq!(json["count"], 6);
538 assert!(json["variables"].is_array());
539
540 let variables = json["variables"].as_array().unwrap();
541 assert_eq!(variables.len(), 6);
542
543 let first_var = &variables[0];
545 assert!(first_var["name"].is_string());
546 assert!(first_var["value"].is_string());
547 assert!(first_var["source"].is_string());
548 assert!(first_var["modified"].is_string());
549 }
550
551 #[test]
552 fn test_to_yaml_without_metadata() {
553 let vars = create_test_vars();
554 let exporter = Exporter::new(vars, false);
555
556 let output = exporter.to_yaml();
557
558 assert!(output.contains("SIMPLE_VAR: simple_value"));
560 assert!(output.contains("EMPTY_VAR: "));
561
562 assert!(output.contains("PATH_VAR: \"C:\\Program Files\\App;C:\\Windows\\System32\""));
564
565 assert!(output.contains("QUOTED_VAR: \"value with \\\"quotes\\\" and 'single quotes'\""));
567
568 assert!(!output.contains("# Environment variables exported by envx"));
570 }
571
572 #[test]
573 fn test_to_yaml_with_metadata() {
574 let vars = create_test_vars();
575 let exporter = Exporter::new(vars, true);
576
577 let output = exporter.to_yaml();
578
579 assert!(output.contains("# Environment variables exported by envx"));
581 assert!(output.contains("# Date:"));
582 assert!(output.contains("---"));
583 assert!(output.contains("# Source:"));
584 }
585
586 #[test]
587 fn test_to_yaml_special_cases() {
588 let vars = vec![
589 EnvVar {
590 name: "URL".to_string(),
591 value: "https://example.com:8080/path".to_string(),
592 source: VarSource::User,
593 modified: Utc::now(),
594 original_value: None,
595 },
596 EnvVar {
597 name: "COMMENT".to_string(),
598 value: "value # with comment".to_string(),
599 source: VarSource::User,
600 modified: Utc::now(),
601 original_value: None,
602 },
603 EnvVar {
604 name: "LEADING_SPACE".to_string(),
605 value: " value".to_string(),
606 source: VarSource::User,
607 modified: Utc::now(),
608 original_value: None,
609 },
610 EnvVar {
611 name: "TRAILING_SPACE".to_string(),
612 value: "value ".to_string(),
613 source: VarSource::User,
614 modified: Utc::now(),
615 original_value: None,
616 },
617 ];
618
619 let exporter = Exporter::new(vars, false);
620 let output = exporter.to_yaml();
621
622 assert!(output.contains("URL: \"https://example.com:8080/path\""));
624 assert!(output.contains("COMMENT: \"value # with comment\""));
625 assert!(output.contains("LEADING_SPACE: \" value\""));
626 assert!(output.contains("TRAILING_SPACE: \"value \""));
627 }
628
629 #[test]
630 fn test_to_text() {
631 let vars = create_test_vars();
632
633 let exporter = Exporter::new(vars.clone(), false);
635 let output = exporter.to_text();
636
637 assert!(output.contains("SIMPLE_VAR=simple_value"));
638 assert!(output.contains("PATH_VAR=C:\\Program Files\\App;C:\\Windows\\System32"));
639 assert!(!output.contains("# Environment Variables Export"));
640
641 let exporter = Exporter::new(vars, true);
643 let output = exporter.to_text();
644
645 assert!(output.contains("# Environment Variables Export"));
646 assert!(output.contains("# Generated:"));
647 assert!(output.contains("# Total: 6 variables"));
648 assert!(output.contains("# Name: SIMPLE_VAR"));
649 assert!(output.contains("# Source:"));
650 assert!(output.contains("# Modified:"));
651 }
652
653 #[test]
654 fn test_to_powershell() {
655 let vars = create_test_vars();
656 let exporter = Exporter::new(vars, false);
657
658 let output = exporter.to_powershell();
659
660 assert!(output.contains("# PowerShell Environment Variables Script"));
662 assert!(output.contains("# Generated by envx"));
663
664 assert!(output.contains("$env:SIMPLE_VAR = \"simple_value\""));
666 assert!(output.contains("$env:PATH_VAR = \"C:\\Program Files\\App;C:\\Windows\\System32\""));
667
668 assert!(output.contains("$env:QUOTED_VAR = \"value with `\"quotes`\" and 'single quotes'\""));
670 assert!(output.contains("$env:SPECIAL_CHARS = \"line1\nline2\ttab\\backslash\""));
671 }
672
673 #[test]
674 fn test_to_powershell_escaping() {
675 let vars = vec![
676 EnvVar {
677 name: "BACKTICK".to_string(),
678 value: "value`with`backticks".to_string(),
679 source: VarSource::User,
680 modified: Utc::now(),
681 original_value: None,
682 },
683 EnvVar {
684 name: "DOLLAR".to_string(),
685 value: "$variable $test".to_string(),
686 source: VarSource::User,
687 modified: Utc::now(),
688 original_value: None,
689 },
690 ];
691
692 let exporter = Exporter::new(vars, false);
693 let output = exporter.to_powershell();
694
695 assert!(output.contains("$env:BACKTICK = \"value``with``backticks\""));
697 assert!(output.contains("$env:DOLLAR = \"$variable $test\""));
699 }
700
701 #[test]
702 fn test_to_shell() {
703 let vars = create_test_vars();
704 let exporter = Exporter::new(vars, false);
705
706 let output = exporter.to_shell();
707
708 assert!(output.contains("#!/bin/bash"));
710 assert!(output.contains("# Shell Environment Variables Script"));
711 assert!(output.contains("# Generated by envx"));
712
713 assert!(output.contains("export SIMPLE_VAR=\"simple_value\""));
715 assert!(output.contains("export PATH_VAR=\"C:\\\\Program Files\\\\App;C:\\\\Windows\\\\System32\""));
716
717 assert!(output.contains("export QUOTED_VAR=\"value with \\\"quotes\\\" and 'single quotes'\""));
719 assert!(output.contains("export SPECIAL_CHARS=\"line1\nline2\ttab\\\\backslash\""));
720 }
721
722 #[test]
723 fn test_to_shell_escaping() {
724 let vars = vec![
725 EnvVar {
726 name: "DOLLAR".to_string(),
727 value: "$HOME/path".to_string(),
728 source: VarSource::User,
729 modified: Utc::now(),
730 original_value: None,
731 },
732 EnvVar {
733 name: "BACKTICK".to_string(),
734 value: "`command`".to_string(),
735 source: VarSource::User,
736 modified: Utc::now(),
737 original_value: None,
738 },
739 EnvVar {
740 name: "BACKSLASH".to_string(),
741 value: "path\\to\\file".to_string(),
742 source: VarSource::User,
743 modified: Utc::now(),
744 original_value: None,
745 },
746 ];
747
748 let exporter = Exporter::new(vars, false);
749 let output = exporter.to_shell();
750
751 assert!(output.contains("export DOLLAR=\"\\$HOME/path\""));
753 assert!(output.contains("export BACKTICK=\"\\`command\\`\""));
754 assert!(output.contains("export BACKSLASH=\"path\\\\to\\\\file\""));
755 }
756
757 #[test]
758 fn test_export_to_file() {
759 let vars = create_test_vars();
760 let exporter = Exporter::new(vars, false);
761
762 let formats = vec![
764 (ExportFormat::DotEnv, ".env"),
765 (ExportFormat::Json, ".json"),
766 (ExportFormat::Yaml, ".yaml"),
767 (ExportFormat::Text, ".txt"),
768 (ExportFormat::PowerShell, ".ps1"),
769 (ExportFormat::Shell, ".sh"),
770 ];
771
772 for (format, ext) in formats {
773 let temp_file = NamedTempFile::with_suffix(ext).unwrap();
774 let path = temp_file.path().to_str().unwrap();
775
776 exporter.export_to_file(path, format).unwrap();
777
778 let content = fs::read_to_string(path).unwrap();
780 assert!(!content.is_empty());
781 assert!(content.contains("SIMPLE_VAR"));
782 }
783 }
784
785 #[test]
786 fn test_empty_export() {
787 let exporter = Exporter::new(vec![], true);
788
789 assert_eq!(exporter.count(), 0);
790
791 let dotenv = exporter.to_dotenv();
793 assert!(dotenv.contains("# Count: 0"));
794
795 let json = exporter.to_json().unwrap();
796 assert!(json.contains("\"count\": 0"));
797 assert!(json.contains("\"variables\": []"));
798
799 let yaml = exporter.to_yaml();
800 assert!(yaml.contains("---"));
801
802 let text = exporter.to_text();
803 assert!(text.contains("# Total: 0 variables"));
804
805 let ps = exporter.to_powershell();
806 assert!(ps.contains("# PowerShell Environment Variables Script"));
807
808 let sh = exporter.to_shell();
809 assert!(sh.contains("#!/bin/bash"));
810 }
811
812 #[test]
813 fn test_variable_name_edge_cases() {
814 let vars = vec![
815 EnvVar {
816 name: "SIMPLE-NAME-WITH-DASHES".to_string(),
817 value: "value1".to_string(),
818 source: VarSource::User,
819 modified: Utc::now(),
820 original_value: None,
821 },
822 EnvVar {
823 name: "NAME.WITH.DOTS".to_string(),
824 value: "value2".to_string(),
825 source: VarSource::User,
826 modified: Utc::now(),
827 original_value: None,
828 },
829 EnvVar {
830 name: "_UNDERSCORE_START".to_string(),
831 value: "value3".to_string(),
832 source: VarSource::User,
833 modified: Utc::now(),
834 original_value: None,
835 },
836 EnvVar {
837 name: "123_NUMBER_START".to_string(),
838 value: "value4".to_string(),
839 source: VarSource::User,
840 modified: Utc::now(),
841 original_value: None,
842 },
843 ];
844
845 let exporter = Exporter::new(vars, false);
846
847 let dotenv = exporter.to_dotenv();
849 assert!(dotenv.contains("SIMPLE-NAME-WITH-DASHES=value1"));
850 assert!(dotenv.contains("NAME.WITH.DOTS=value2"));
851
852 let json = exporter.to_json().unwrap();
853 assert!(json.contains("\"SIMPLE-NAME-WITH-DASHES\": \"value1\""));
854
855 let yaml = exporter.to_yaml();
856 assert!(yaml.contains("SIMPLE-NAME-WITH-DASHES: value1"));
857
858 let ps = exporter.to_powershell();
859 assert!(ps.contains("$env:SIMPLE-NAME-WITH-DASHES = \"value1\""));
860
861 let sh = exporter.to_shell();
862 assert!(sh.contains("export SIMPLE-NAME-WITH-DASHES=\"value1\""));
863 }
864
865 #[test]
866 fn test_very_long_values() {
867 let long_value = "a".repeat(1000);
868 let vars = vec![EnvVar {
869 name: "LONG_VALUE".to_string(),
870 value: long_value.clone(),
871 source: VarSource::User,
872 modified: Utc::now(),
873 original_value: None,
874 }];
875
876 let exporter = Exporter::new(vars, false);
877
878 let dotenv = exporter.to_dotenv();
880 assert!(dotenv.contains(&format!("LONG_VALUE={long_value}")));
881
882 let json = exporter.to_json().unwrap();
883 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
884 assert_eq!(parsed["LONG_VALUE"].as_str().unwrap().len(), 1000);
885 }
886
887 #[test]
888 fn test_metadata_consistency() {
889 let fixed_time = DateTime::parse_from_rfc3339("2024-01-01T12:00:00Z")
890 .unwrap()
891 .with_timezone(&Utc);
892
893 let vars = vec![EnvVar {
894 name: "TEST_VAR".to_string(),
895 value: "test_value".to_string(),
896 source: VarSource::System,
897 modified: fixed_time,
898 original_value: None,
899 }];
900
901 let exporter = Exporter::new(vars, true);
902
903 let dotenv = exporter.to_dotenv();
905 assert!(dotenv.contains("# Source: System"));
906 assert!(dotenv.contains("2024-01-01 12:00:00"));
907
908 let text = exporter.to_text();
909 assert!(text.contains("# Source: System"));
910
911 let ps = exporter.to_powershell();
912 assert!(ps.contains("# TEST_VAR (System)"));
913
914 let sh = exporter.to_shell();
915 assert!(sh.contains("# TEST_VAR (System)"));
916 }
917}