use crate::config::Config;
use crate::config::load_bundle;
use crate::profile::{FamilyName, resolve_profile_for_family};
use crate::{
FragmentOutput, Framing, PrompterError, RenderOutput, default_post_prompt, default_pre_prompt,
format_system_prefix,
};
use chrono::Local;
use std::collections::HashSet;
use std::env;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use tftio_cli_common::{JsonOutput, render_response};
pub fn render_to_writer(
cfg: &Config,
mut w: impl Write,
profiles: &[String],
family: Option<&FamilyName>,
separator: Option<&str>,
pre_prompt: Option<&str>,
post_prompt: Option<&str>,
framing: Framing,
output: JsonOutput,
) -> Result<(), PrompterError> {
let mut seen_files = HashSet::new();
let mut files: Vec<(PathBuf, PathBuf)> = Vec::new();
for profile in profiles {
let mut stack = Vec::new();
resolve_profile_for_family(
profile,
cfg,
family,
&mut seen_files,
&mut stack,
&mut files,
)?;
}
if output.is_json() {
let pre_prompt_text = match pre_prompt {
Some(explicit) => explicit.to_string(),
None if framing.is_full() => default_pre_prompt(),
None => String::new(),
};
let system_info = if framing.is_full() {
let date = Local::now().format("%Y-%m-%d").to_string();
let os = env::consts::OS;
let arch = env::consts::ARCH;
format!("Today is {date}, and you are running on a {arch}/{os} system.")
} else {
String::new()
};
let mut fragments = Vec::new();
for (path, library_root) in &files {
let content = fs::read_to_string(path).map_err(|source| PrompterError::Io {
path: path.clone(),
source,
})?;
let rel_path = path
.strip_prefix(library_root)
.unwrap_or(path)
.display()
.to_string();
fragments.push(FragmentOutput {
path: rel_path,
content,
});
}
let payload = serde_json::to_value(RenderOutput {
profile: profiles.join(", "),
pre_prompt: pre_prompt_text,
system_info,
fragments,
})?;
writeln!(
&mut w,
"{}",
render_response("run", JsonOutput::Json, payload, String::new())
)
.map_err(PrompterError::Write)?;
} else {
let default_pre = default_pre_prompt();
let pre_prompt_text = match (pre_prompt, framing) {
(Some(explicit), _) => Some(explicit),
(None, Framing::Full) => Some(default_pre.as_str()),
(None, Framing::Bare) => None,
};
if let Some(text) = pre_prompt_text {
w.write_all(text.as_bytes()).map_err(PrompterError::Write)?;
}
if framing.is_full() {
w.write_all(b"\n").map_err(PrompterError::Write)?;
let prefix = format_system_prefix();
w.write_all(prefix.as_bytes())
.map_err(PrompterError::Write)?;
}
let sep = separator.unwrap_or("");
for (index, (path, _library_root)) in files.iter().enumerate() {
if framing.is_full() || index > 0 {
w.write_all(b"\n").map_err(PrompterError::Write)?;
}
let bytes = fs::read(path).map_err(|source| PrompterError::Io {
path: path.clone(),
source,
})?;
w.write_all(&bytes).map_err(PrompterError::Write)?;
if !sep.is_empty() {
w.write_all(sep.as_bytes()).map_err(PrompterError::Write)?;
}
}
let default_post = default_post_prompt();
let post_prompt_text = match framing {
Framing::Full => Some(
post_prompt
.or(cfg.post_prompt.as_deref())
.unwrap_or(&default_post),
),
Framing::Bare => post_prompt,
};
if let Some(text) = post_prompt_text {
if framing.is_full() {
w.write_all(b"\n\n").map_err(PrompterError::Write)?;
}
w.write_all(text.as_bytes()).map_err(PrompterError::Write)?;
}
}
Ok(())
}
pub fn run_render_stdout(
profiles: &[String],
family: Option<&FamilyName>,
separator: Option<&str>,
pre_prompt: Option<&str>,
post_prompt: Option<&str>,
framing: Framing,
config_override: Option<&Path>,
output: JsonOutput,
) -> Result<(), PrompterError> {
let (_cfg_path, cfg) = load_bundle(config_override)?;
let stdout = io::stdout();
let handle = stdout.lock();
render_to_writer(
&cfg,
handle,
profiles,
family,
separator,
pre_prompt,
post_prompt,
framing,
output,
)
}
pub fn render_to_vec(
profiles: &[String],
family: Option<&FamilyName>,
config_override: Option<&Path>,
) -> Result<Vec<u8>, PrompterError> {
let (_cfg_path, cfg) = load_bundle(config_override)?;
let mut buf = Vec::new();
render_to_writer(
&cfg,
&mut buf,
profiles,
family,
None,
None,
None,
Framing::Full,
JsonOutput::Text,
)?;
Ok(buf)
}