use std::io::{self, Write};
use crate::{output::render, Config, Format, Output, Result};
#[derive(Debug, Clone, Copy)]
pub struct Ui {
config: Config,
}
impl Ui {
pub fn new() -> Self {
Self {
config: Config::default(),
}
}
pub fn with_config(config: Config) -> Self {
Self { config }
}
pub fn config(&self) -> &Config {
&self.config
}
pub fn with_format(mut self, format: Format) -> Self {
self.config.format = format;
self
}
pub fn interactive(mut self, value: bool) -> Self {
self.config.interactive = value;
self
}
pub fn auto_yes(mut self, value: bool) -> Self {
self.config.auto_yes = value;
self
}
#[cfg(feature = "logger")]
pub fn logger(&self) -> crate::logger::Logger<'_> {
crate::logger::Logger::new(&self.config)
}
#[cfg(feature = "prompt")]
pub fn text(&self, message: &str, default: Option<&str>, help: Option<&str>) -> Result<String> {
crate::prompt::text(&self.config, message, default, help)
}
#[cfg(feature = "prompt")]
pub fn confirm(&self, message: &str, default: bool) -> Result<bool> {
crate::prompt::confirm(&self.config, message, default)
}
#[cfg(feature = "prompt")]
pub fn select(&self, request: &crate::prompt::SelectRequest) -> Result<String> {
crate::prompt::select(&self.config, request)
}
#[cfg(feature = "prompt")]
pub fn multiselect(&self, request: &crate::prompt::MultiSelectRequest) -> Result<Vec<String>> {
crate::prompt::multiselect(&self.config, request)
}
pub fn render(&self, output: &Output) -> Result<String> {
render::render_output(self.config.format, output)
}
pub fn print(&self, output: &Output) -> Result<()> {
let rendered = self.render(output)?;
let mut stdout = io::stdout();
stdout.write_all(rendered.as_bytes())?;
stdout.flush()?;
Ok(())
}
}
impl Default for Ui {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ui_new_creates_default_config() {
let ui = Ui::new();
let config = ui.config();
assert!(!config.interactive);
assert!(!config.auto_yes);
assert_eq!(config.format, Format::Text);
}
#[test]
fn ui_with_config_uses_provided_config() {
let custom_config = Config {
interactive: true,
auto_yes: true,
format: Format::Markdown,
color: crate::ColorMode::Always,
level: crate::Level::Debug,
};
let ui = Ui::with_config(custom_config);
assert_eq!(ui.config(), &custom_config);
}
#[test]
fn ui_with_format_changes_format() {
let ui = Ui::new()
.with_format(Format::Markdown)
.with_format(Format::Json);
assert_eq!(ui.config().format, Format::Json);
}
#[test]
fn ui_interactive_true() {
let ui = Ui::new().interactive(true);
assert!(ui.config().interactive);
}
#[test]
fn ui_interactive_false() {
let ui = Ui::new().interactive(true).interactive(false);
assert!(!ui.config().interactive);
}
#[test]
fn ui_auto_yes_true() {
let ui = Ui::new().auto_yes(true);
assert!(ui.config().auto_yes);
}
#[test]
fn ui_auto_yes_false() {
let ui = Ui::new().auto_yes(true).auto_yes(false);
assert!(!ui.config().auto_yes);
}
#[test]
fn ui_builder_is_fluent() {
let ui = Ui::new()
.with_format(Format::Markdown)
.interactive(true)
.auto_yes(true);
assert_eq!(ui.config().format, Format::Markdown);
assert!(ui.config().interactive);
assert!(ui.config().auto_yes);
}
#[test]
fn ui_render_plain_format() {
let ui = Ui::new().with_format(Format::Plain);
let output = Output::new().plain("test");
let rendered = ui.render(&output).unwrap();
assert_eq!(rendered, "test\n");
}
#[test]
fn ui_render_text_format() {
let ui = Ui::new().with_format(Format::Text);
let output = Output::new().paragraph("Hello");
let rendered = ui.render(&output).unwrap();
assert!(rendered.contains("Hello"));
}
#[test]
fn ui_render_markdown_format() {
let ui = Ui::new().with_format(Format::Markdown);
let output = Output::new().heading(1, "Title");
let rendered = ui.render(&output).unwrap();
assert!(rendered.contains("# Title"));
}
#[test]
fn ui_render_json_format() {
let ui = Ui::new().with_format(Format::Json);
let output = Output::new().data("key", "value");
let rendered = ui.render(&output).unwrap();
assert!(rendered.contains("key"));
assert!(rendered.contains("value"));
}
#[test]
fn ui_render_jsonl_format() {
let ui = Ui::new().with_format(Format::Jsonl);
let output = Output::new()
.jsonl_record(serde_json::json!({"a": 1}))
.jsonl_record(serde_json::json!({"b": 2}));
let rendered = ui.render(&output).unwrap();
assert!(rendered.contains("\"a\""));
assert!(rendered.contains("\"b\""));
}
#[test]
fn ui_render_title_in_text() {
let ui = Ui::new().with_format(Format::Text);
let output = Output::new().title("Status");
let rendered = ui.render(&output).unwrap();
assert!(rendered.contains("Status"));
assert!(rendered.contains("======"));
}
#[test]
fn ui_render_subtitle_in_markdown() {
let ui = Ui::new().with_format(Format::Markdown);
let output = Output::new().subtitle("Subtitle text");
let rendered = ui.render(&output).unwrap();
assert!(rendered.contains("_Subtitle text_"));
}
#[test]
fn ui_render_empty_output() {
let ui = Ui::new().with_format(Format::Markdown);
let output = Output::new();
let rendered = ui.render(&output).unwrap();
assert_eq!(rendered.trim(), "");
}
#[test]
fn ui_render_multiple_blocks() {
let ui = Ui::new().with_format(Format::Markdown);
let output = Output::new()
.heading(1, "H1")
.paragraph("P1")
.heading(2, "H2")
.paragraph("P2");
let rendered = ui.render(&output).unwrap();
assert!(rendered.contains("# H1"));
assert!(rendered.contains("P1"));
assert!(rendered.contains("## H2"));
assert!(rendered.contains("P2"));
}
#[test]
fn ui_default_is_same_as_new() {
let ui1 = Ui::new();
let ui2 = Ui::default();
assert_eq!(ui1.config(), ui2.config());
}
#[test]
fn ui_copy() {
let ui1 = Ui::new().with_format(Format::Json);
let ui2 = ui1;
assert_eq!(ui2.config().format, Format::Json);
}
}