use std::{
fs::File,
io::{Read as _, Write as _},
path::PathBuf,
};
use clap::{Args, Subcommand, ValueEnum};
use darkomen::portrait::heads;
use darkomen::portrait::keyframes;
use darkomen::portrait::sequences;
#[derive(Args)]
pub struct PortraitArgs {
#[command(subcommand)]
pub subcommand: Option<PortraitSubcommands>,
}
#[derive(Subcommand)]
#[expect(
clippy::enum_variant_names,
reason = "These are edit subcommands and we might have non-edit subcommands later."
)]
pub enum PortraitSubcommands {
EditHeads(EditHeadsArgs),
EditKeyframes(EditKeyframesArgs),
EditSequences(EditSequencesArgs),
}
#[derive(Args)]
pub struct EditHeadsArgs {
#[arg(index = 1)]
pub heads_file: String,
#[arg(short, long, default_value = "code --wait")]
pub editor: String,
#[arg(short, long, default_value_t=Format::Json)]
#[clap(value_enum)]
pub format: Format,
}
#[derive(Args)]
pub struct EditKeyframesArgs {
#[arg(index = 1)]
pub keyframes_file: String,
#[arg(short, long, default_value = "code --wait")]
pub editor: String,
#[arg(short, long, default_value_t=Format::Json)]
#[clap(value_enum)]
pub format: Format,
}
#[derive(Args)]
pub struct EditSequencesArgs {
#[arg(index = 1)]
pub sequences_file: String,
#[arg(short, long, default_value = "code --wait")]
pub editor: String,
#[arg(short, long, default_value_t=Format::Json)]
#[clap(value_enum)]
pub format: Format,
}
#[derive(Clone, ValueEnum)]
pub enum Format {
Json,
Ron,
}
pub fn run(args: &PortraitArgs) -> anyhow::Result<()> {
match &args.subcommand {
Some(PortraitSubcommands::EditHeads(edit_args)) => edit_heads_file(edit_args)?,
Some(PortraitSubcommands::EditKeyframes(edit_args)) => edit_keyframes_file(edit_args)?,
Some(PortraitSubcommands::EditSequences(edit_args)) => edit_sequences_file(edit_args)?,
None => {}
}
Ok(())
}
fn edit_heads_file(args: &EditHeadsArgs) -> anyhow::Result<()> {
let heads_file: PathBuf = args.heads_file.clone().into();
let file = File::open(heads_file.clone())?;
let heads_db = heads::Decoder::new(file).decode()?;
let (as_string, extension) = match args.format {
Format::Ron => (
ron::ser::to_string_pretty(&heads_db, ron::ser::PrettyConfig::default())?,
"ron",
),
Format::Json => (serde_json::to_string_pretty(&heads_db)?, "json"),
};
let prefix = format!(
"{}.",
heads_file
.file_stem()
.and_then(|stem| stem.to_str())
.unwrap_or("heads"),
);
let suffix = format!(".{extension}");
let mut temp_file = tempfile::Builder::new()
.prefix(&prefix)
.suffix(&suffix)
.tempfile()?;
temp_file.write_all(as_string.as_bytes())?;
temp_file.flush()?;
let (editor, editor_args) = {
let mut parts = args.editor.split_whitespace();
let editor = parts.next().unwrap();
let editor_args = parts.collect::<Vec<_>>();
(editor, editor_args)
};
let mut command = std::process::Command::new(editor);
command.args(editor_args);
println!("Waiting for editor to close...");
command.arg(temp_file.path()).status()?;
println!("Editor closed");
let mut modified_string = String::new();
temp_file.reopen()?.read_to_string(&mut modified_string)?;
let modified_heads_db = match args.format {
Format::Ron => ron::de::from_str(&modified_string)?,
Format::Json => serde_json::from_str(&modified_string)?,
};
let file = File::create(heads_file)?;
heads::Encoder::new(file).encode(&modified_heads_db)?;
println!("Heads database file successfully edited");
Ok(())
}
fn edit_keyframes_file(args: &EditKeyframesArgs) -> anyhow::Result<()> {
let keyframes_file: PathBuf = args.keyframes_file.clone().into();
let file = File::open(keyframes_file.clone())?;
let keyframes_db = keyframes::Decoder::new(file).decode()?;
let (as_string, extension) = match args.format {
Format::Ron => (
ron::ser::to_string_pretty(&keyframes_db, ron::ser::PrettyConfig::default())?,
"ron",
),
Format::Json => (serde_json::to_string_pretty(&keyframes_db)?, "json"),
};
let prefix = format!(
"{}.",
keyframes_file
.file_stem()
.and_then(|stem| stem.to_str())
.unwrap_or("keyframes"),
);
let suffix = format!(".{extension}");
let mut temp_file = tempfile::Builder::new()
.prefix(&prefix)
.suffix(&suffix)
.tempfile()?;
temp_file.write_all(as_string.as_bytes())?;
temp_file.flush()?;
let (editor, editor_args) = {
let mut parts = args.editor.split_whitespace();
let editor = parts.next().unwrap();
let editor_args = parts.collect::<Vec<_>>();
(editor, editor_args)
};
let mut command = std::process::Command::new(editor);
command.args(editor_args);
println!("Waiting for editor to close...");
command.arg(temp_file.path()).status()?;
println!("Editor closed");
let mut modified_string = String::new();
temp_file.reopen()?.read_to_string(&mut modified_string)?;
let modified_keyframes_db = match args.format {
Format::Ron => ron::de::from_str(&modified_string)?,
Format::Json => serde_json::from_str(&modified_string)?,
};
let file = File::create(keyframes_file)?;
keyframes::Encoder::new(file).encode(&modified_keyframes_db)?;
println!("Keyframes file successfully edited");
Ok(())
}
fn edit_sequences_file(args: &EditSequencesArgs) -> anyhow::Result<()> {
let sequences_file: PathBuf = args.sequences_file.clone().into();
let file = File::open(sequences_file.clone())?;
let sequences_db = sequences::Decoder::new(file).decode()?;
let (as_string, extension) = match args.format {
Format::Ron => (
ron::ser::to_string_pretty(&sequences_db, ron::ser::PrettyConfig::default())?,
"ron",
),
Format::Json => (serde_json::to_string_pretty(&sequences_db)?, "json"),
};
let prefix = format!(
"{}.",
sequences_file
.file_stem()
.and_then(|stem| stem.to_str())
.unwrap_or("sequences"),
);
let suffix = format!(".{extension}");
let mut temp_file = tempfile::Builder::new()
.prefix(&prefix)
.suffix(&suffix)
.tempfile()?;
temp_file.write_all(as_string.as_bytes())?;
temp_file.flush()?;
let (editor, editor_args) = {
let mut parts = args.editor.split_whitespace();
let editor = parts.next().unwrap();
let editor_args = parts.collect::<Vec<_>>();
(editor, editor_args)
};
let mut command = std::process::Command::new(editor);
command.args(editor_args);
println!("Waiting for editor to close...");
command.arg(temp_file.path()).status()?;
println!("Editor closed");
let mut modified_string = String::new();
temp_file.reopen()?.read_to_string(&mut modified_string)?;
let modified_sequences_db = match args.format {
Format::Ron => ron::de::from_str(&modified_string)?,
Format::Json => serde_json::from_str(&modified_string)?,
};
let file = File::create(sequences_file)?;
sequences::Encoder::new(file).encode(&modified_sequences_db)?;
println!("Sequences file successfully edited");
Ok(())
}