use super::{Shell, path_utils::*};
use crate::{ZvError, app::App};
use color_eyre::eyre::eyre;
use std::path::Path;
use tokio::{fs::OpenOptions, io::AsyncWriteExt};
pub async fn write_shell_file_with_line_endings(
file_path: &Path,
content: &str,
) -> Result<(), ZvError> {
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(file_path)
.await
.map_err(|e| {
ZvError::ZvExportError(eyre!(e).wrap_err(format!(
"Failed to open file for writing: {}",
file_path.display()
)))
})?;
let normalized_content = normalize_line_endings_for_file(file_path, content);
file.write_all(normalized_content.as_bytes())
.await
.map_err(|e| ZvError::ZvExportError(eyre!(e).wrap_err("Failed to write to file")))?;
let final_newline = if should_use_unix_line_endings(file_path) {
"\n"
} else {
"\r\n"
};
file.write_all(final_newline.as_bytes())
.await
.map_err(|e| {
ZvError::ZvExportError(eyre!(e).wrap_err("Failed to write newline to file"))
})?;
Ok(())
}
impl Shell {
pub fn export_without_dump<'a>(&self, app: &'a App, using_env_var: bool) -> (&'a Path, String) {
let (zv_dir_str, zv_bin_path_str) = get_path_strings(self, app, using_env_var);
let env_content = self.generate_env_content(&zv_dir_str, &zv_bin_path_str, using_env_var);
(app.env_path().as_path(), env_content)
}
pub async fn export_unix(&self, app: &App, using_env_var: bool) -> Result<(), ZvError> {
if self.windows_shell() && !self.is_powershell_in_unix() {
return Ok(());
}
let (env_file, content) = self.export_without_dump(app, using_env_var);
write_env_file_if_needed(env_file, &content).await
}
#[inline]
fn windows_shell(&self) -> bool {
use super::ShellType;
matches!(self.shell_type, ShellType::Cmd | ShellType::PowerShell)
}
}
async fn write_env_file_if_needed(env_file: &Path, content: &str) -> Result<(), ZvError> {
let should_write = if env_file.exists() {
match tokio::fs::read_to_string(env_file).await {
Ok(existing_content) => {
let normalized_existing = normalize_line_endings_for_comparison(&existing_content);
let normalized_new = normalize_line_endings_for_comparison(content);
normalized_existing.trim() != normalized_new.trim()
}
Err(_) => {
tracing::warn!("Could not read existing env file, will overwrite");
true
}
}
} else {
true
};
if should_write {
write_env_file(env_file, content).await?;
}
Ok(())
}
fn normalize_line_endings_for_comparison(content: &str) -> String {
content.replace("\r\n", "\n")
}
async fn write_env_file(env_file: &Path, content: &str) -> Result<(), ZvError> {
write_shell_file_with_line_endings(env_file, content).await
}
fn normalize_line_endings_for_file(env_file: &Path, content: &str) -> String {
if should_use_unix_line_endings(env_file) {
content.replace("\r\n", "\n")
} else {
content.replace("\r\n", "\n").replace('\n', "\r\n")
}
}
fn should_use_unix_line_endings(env_file: &Path) -> bool {
match env_file.extension().and_then(|ext| ext.to_str()) {
Some("bat") | Some("cmd") | Some("ps1") => false,
_ => true,
}
}