use std::io::Read;
use anyhow::{bail, Context as _, Result};
use camino::Utf8PathBuf;
use clap::{Args, Subcommand};
use crate::{Context, Input};
use self::read::ReadArgs;
mod ast;
mod check;
mod list;
mod read;
#[derive(Debug, Args)]
#[command(args_conflicts_with_subcommands = true)]
pub struct RecipeArgs {
#[command(subcommand)]
command: Option<RecipeCommand>,
#[command(flatten)]
read_args: ReadArgs,
}
#[derive(Debug, Subcommand)]
enum RecipeCommand {
#[command(alias = "r")]
Read(ReadArgs),
#[command(alias = "c")]
Check(check::CheckArgs),
#[command(alias = "l")]
List(list::ListArgs),
Ast(ast::AstArgs),
}
pub fn run(ctx: &Context, args: RecipeArgs) -> Result<()> {
let command = args.command.unwrap_or(RecipeCommand::Read(args.read_args));
match command {
RecipeCommand::Read(args) => read::run(ctx, args),
RecipeCommand::Check(args) => check::run(ctx, args),
RecipeCommand::List(args) => list::run(ctx, args),
RecipeCommand::Ast(args) => ast::run(ctx, args),
}
}
#[derive(Debug, Args)]
struct RecipeInputArgs {
file: Option<Utf8PathBuf>,
#[arg(short, long, required_unless_present = "file")]
name: Option<String>,
}
impl RecipeInputArgs {
pub fn read(&self) -> Result<Input> {
let (text, recipe_name, file_name) = if let Some(path) = &self.file {
if !path.is_file() {
bail!("Input is not a file");
}
let recipe_name = path.file_stem().expect("file without filename").to_string();
let file_name = path.file_name().expect("file without filename").to_string();
let text = std::fs::read_to_string(path).context("Failed to read input file")?;
(text, Some(recipe_name), Some(file_name))
} else {
let mut buf = String::new();
std::io::stdin()
.read_to_string(&mut buf)
.context("Failed to read stdin")?;
(buf, None, None)
};
if let Some(name) = self.name.as_ref().or(recipe_name.as_ref()) {
Ok(Input {
text,
recipe_name: name.to_owned(), file_name: file_name.unwrap_or_else(|| name.to_owned()),
path: self.file.clone(),
})
} else {
bail!("No name for the recipe")
}
}
}