use crate::plot_schema::{
FieldInput, FieldType, PlotSchema, PlotTypeSchema, build_config, config_to_json,
config_to_toml, has_errors, prefill_inputs,
};
use crate::tui::locale::DocLang;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
Json,
Toml,
}
impl OutputFormat {
pub fn extension(self) -> &'static str {
match self {
OutputFormat::Json => "json",
OutputFormat::Toml => "toml",
}
}
pub fn label(self) -> &'static str {
match self {
OutputFormat::Json => "JSON",
OutputFormat::Toml => "TOML",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GenStatus {
Copied,
ClipboardUnavailable,
Saved(String),
Error(String),
}
#[derive(Debug, Clone)]
pub struct PlotGen {
pub schema: PlotSchema,
pub lang: DocLang,
pub type_index: usize,
pub inputs: Vec<FieldInput>,
pub selected: usize,
pub output: OutputFormat,
pub errors: Vec<Option<String>>,
pub generated: String,
pub status: Option<GenStatus>,
pub print_on_exit: Option<String>,
pub no_preview: bool,
}
impl PlotGen {
pub fn new(lang: DocLang) -> Self {
let schema =
PlotSchema::parse(lang.schema_source()).expect("embedded plot schema must be valid");
let mut form = Self {
schema,
lang,
type_index: 0,
inputs: Vec::new(),
selected: 0,
output: OutputFormat::Json,
errors: Vec::new(),
generated: String::new(),
status: None,
print_on_exit: None,
no_preview: false,
};
form.reset_to_example();
form
}
pub fn current_type(&self) -> &PlotTypeSchema {
&self.schema.plot_types[self.type_index]
}
pub fn set_type(&mut self, index: usize) {
if index >= self.schema.plot_types.len() {
return;
}
self.type_index = index;
self.status = None;
self.reset_to_example();
}
pub fn reset_to_example(&mut self) {
self.inputs = prefill_inputs(self.current_type());
self.selected = 0;
self.regen();
}
pub fn regen(&mut self) {
let (value, errors) = build_config(self.current_type(), &self.inputs);
self.errors = errors;
self.generated = match self.output {
OutputFormat::Json => config_to_json(&value),
OutputFormat::Toml => config_to_toml(&value).unwrap_or_else(|e| format!("// {e}")),
};
}
pub fn cycle_output(&mut self) {
self.output = match self.output {
OutputFormat::Json => OutputFormat::Toml,
OutputFormat::Toml => OutputFormat::Json,
};
self.regen();
}
pub fn current_field(&self) -> Option<&crate::plot_schema::FieldSchema> {
self.current_type().fields.get(self.selected)
}
pub fn set_text_field(&mut self, field_idx: usize, text: String) {
if let Some(input) = self.inputs.get_mut(field_idx) {
input.text = text;
}
self.regen_if_preview();
}
pub fn toggle_bool(&mut self, field_idx: usize) {
if let Some(input) = self.inputs.get_mut(field_idx) {
input.bool_value = !input.bool_value;
}
self.regen_if_preview();
}
pub fn cycle_enum(&mut self, field_idx: usize, delta: isize) {
let count = self.current_type().fields[field_idx].options.len() as isize;
if count > 0
&& let Some(input) = self.inputs.get_mut(field_idx)
{
input.enum_index = (input.enum_index as isize + delta).rem_euclid(count) as usize;
}
self.regen_if_preview();
}
fn regen_if_preview(&mut self) {
if !self.no_preview {
self.regen();
}
}
pub fn set_no_preview(&mut self, on: bool) {
self.no_preview = on;
}
pub fn has_errors(&self) -> bool {
has_errors(&self.errors)
}
pub fn field_display(&self, field_idx: usize) -> String {
let field = &self.current_type().fields[field_idx];
let input = &self.inputs[field_idx];
match field.kind {
FieldType::Bool => {
if input.bool_value {
"[x]".to_string()
} else {
"[ ]".to_string()
}
}
FieldType::Enum => match field.options.get(input.enum_index) {
Some(option) => option.label.clone(),
None => String::new(),
},
_ => input.text.clone(),
}
}
pub fn copy(&mut self, clipboard: &mut Option<arboard::Clipboard>) {
if self.has_errors() {
let msg = self
.errors
.iter()
.flatten()
.next()
.cloned()
.unwrap_or_else(|| "fix the errors first".to_string());
self.status = Some(GenStatus::Error(msg));
return;
}
let text = self.generated.clone();
match clipboard {
Some(clipboard) => match clipboard.set_text(text) {
Ok(()) => self.status = Some(GenStatus::Copied),
Err(_) => {
self.status = Some(GenStatus::ClipboardUnavailable);
self.print_on_exit = Some(self.generated.clone());
}
},
None => {
self.status = Some(GenStatus::ClipboardUnavailable);
self.print_on_exit = Some(self.generated.clone());
}
}
}
pub fn save_file_name(&self) -> String {
format!(
"plot-{}.{}",
self.current_type().id,
self.output.extension()
)
}
pub fn save(&mut self) {
if self.has_errors() {
let msg = self
.errors
.iter()
.flatten()
.next()
.cloned()
.unwrap_or_else(|| "fix the errors first".to_string());
self.status = Some(GenStatus::Error(msg));
return;
}
let path = std::env::current_dir()
.unwrap_or_default()
.join(self.save_file_name());
match crate::tui::book_toml::atomic_write(&path, &self.generated) {
Ok(()) => self.status = Some(GenStatus::Saved(path.display().to_string())),
Err(e) => {
self.status = Some(GenStatus::Error(format!(
"cannot write {}: {e}",
path.display()
)));
}
}
}
}