use clap::{Parser, ValueEnum};
use std::path::PathBuf;
use crate::models::{DetailLevel, DocConfig, GroupBy, SortMethod};
#[derive(Parser, Debug)]
#[command(name = "vimanam", version)]
#[command(about = "OpenAPI to Markdown documentation generator", long_about = None)]
pub struct Cli {
#[arg(value_name = "FILE")]
pub input: PathBuf,
#[arg(short, long, value_name = "FILE")]
pub output: Option<PathBuf>,
#[arg(long)]
pub method: bool,
#[arg(long, value_enum, default_value = "service")]
pub group_by: GroupByArg,
#[arg(long)]
pub flat: bool,
#[arg(long, value_delimiter = ',')]
pub service_filter: Option<Vec<String>>,
#[arg(long)]
pub path_filter: Option<String>,
#[arg(long, value_delimiter = ',')]
pub method_filter: Option<Vec<String>>,
#[arg(long)]
pub exclude_deprecated: bool,
#[arg(long)]
pub required_only: bool,
#[arg(long, value_enum, default_value = "summary")]
pub detail: DetailLevelArg,
#[arg(long)]
pub include_schemas: bool,
#[arg(long)]
pub inline_schemas: bool,
#[arg(long)]
pub include_examples: bool,
#[arg(long)]
pub include_auth: bool,
#[arg(long)]
pub no_toc: bool,
#[arg(long, value_enum, default_value = "alpha")]
pub sort: SortArg,
#[arg(long, value_name = "N")]
pub max_tokens: Option<usize>,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum GroupByArg {
Service,
Method,
Path,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum DetailLevelArg {
Summary,
Basic,
Standard,
Full,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum SortArg {
Alpha,
PathLength,
None,
}
impl From<GroupByArg> for GroupBy {
fn from(arg: GroupByArg) -> Self {
match arg {
GroupByArg::Service => GroupBy::Service,
GroupByArg::Method => GroupBy::Method,
GroupByArg::Path => GroupBy::Path,
}
}
}
impl From<DetailLevelArg> for DetailLevel {
fn from(arg: DetailLevelArg) -> Self {
match arg {
DetailLevelArg::Summary => DetailLevel::Summary,
DetailLevelArg::Basic => DetailLevel::Basic,
DetailLevelArg::Standard => DetailLevel::Standard,
DetailLevelArg::Full => DetailLevel::Full,
}
}
}
impl From<SortArg> for SortMethod {
fn from(arg: SortArg) -> Self {
match arg {
SortArg::Alpha => SortMethod::Alphabetical,
SortArg::PathLength => SortMethod::PathLength,
SortArg::None => SortMethod::None,
}
}
}
pub fn build_config(cli: &Cli) -> DocConfig {
let group_by = if cli.flat {
GroupBy::Flat
} else if cli.method {
GroupBy::Method
} else {
cli.group_by.into()
};
let config = DocConfig {
group_by,
service_filter: cli.service_filter.clone(),
path_filter: cli.path_filter.clone(),
method_filter: cli
.method_filter
.as_ref()
.map(|methods| methods.iter().map(|m| m.to_uppercase()).collect()),
exclude_deprecated: cli.exclude_deprecated,
required_only: cli.required_only,
detail_level: cli.detail.into(),
include_schemas: cli.include_schemas,
inline_schemas: cli.inline_schemas,
include_examples: cli.include_examples,
include_auth: cli.include_auth,
include_toc: !cli.no_toc,
sort_method: cli.sort.into(),
max_tokens: cli.max_tokens,
};
let detail_name = detail_arg_name(cli.detail);
if config.include_schemas && config.detail_level != DetailLevel::Full {
eprintln!(
"vimanam: --include-schemas has no effect at --detail {detail_name}; use --detail full."
);
}
if config.include_examples && config.detail_level != DetailLevel::Full {
eprintln!(
"vimanam: --include-examples has no effect at --detail {detail_name}; use --detail full."
);
}
if config.inline_schemas && !config.include_schemas {
eprintln!("vimanam: --inline-schemas has no effect without --include-schemas.");
}
config
}
fn detail_arg_name(detail: DetailLevelArg) -> &'static str {
match detail {
DetailLevelArg::Summary => "summary",
DetailLevelArg::Basic => "basic",
DetailLevelArg::Standard => "standard",
DetailLevelArg::Full => "full",
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::assert_matches;
#[test]
fn test_detail_level_conversion() {
let cli = Cli {
input: PathBuf::from("spec.json"),
output: None,
method: false,
group_by: GroupByArg::Service,
flat: false,
service_filter: None,
path_filter: None,
method_filter: None,
exclude_deprecated: false,
required_only: false,
detail: DetailLevelArg::Summary,
include_schemas: false,
inline_schemas: false,
include_examples: false,
include_auth: false,
no_toc: false,
sort: SortArg::Alpha,
max_tokens: None,
};
let config = build_config(&cli);
assert_matches!(config.detail_level, DetailLevel::Summary);
let mut cli_basic = cli;
cli_basic.detail = DetailLevelArg::Basic;
let config_basic = build_config(&cli_basic);
assert_matches!(config_basic.detail_level, DetailLevel::Basic);
}
}