mod endpoint;
mod examples;
mod schema;
mod views;
use std::io::Write;
use anyhow::Result;
use crate::models::{ApiDocumentation, DetailLevel, DocConfig, GroupBy};
pub fn generate_markdown<W: Write>(
writer: &mut W,
doc: &ApiDocumentation,
config: &DocConfig,
) -> Result<()> {
match config.max_tokens {
Some(budget) => generate_within_budget(writer, doc, config, budget),
None => render(writer, doc, config),
}
}
fn render<W: Write>(writer: &mut W, doc: &ApiDocumentation, config: &DocConfig) -> Result<()> {
if config.detail_level == DetailLevel::Summary {
views::generate_summary(writer, doc, config)
} else {
match config.group_by {
GroupBy::Service => views::generate_by_service(writer, doc, config),
GroupBy::Method => views::generate_by_method(writer, doc, config),
GroupBy::Path => views::generate_by_path(writer, doc, config),
GroupBy::Flat => views::generate_flat(writer, doc, config),
}
}
}
const DETAIL_LADDER: [DetailLevel; 4] = [
DetailLevel::Full,
DetailLevel::Standard,
DetailLevel::Basic,
DetailLevel::Summary,
];
fn generate_within_budget<W: Write>(
writer: &mut W,
doc: &ApiDocumentation,
config: &DocConfig,
budget: usize,
) -> Result<()> {
let start = DETAIL_LADDER
.iter()
.position(|level| *level == config.detail_level)
.unwrap_or(0);
let mut chosen: Option<(DetailLevel, Vec<u8>, usize)> = None;
for level in &DETAIL_LADDER[start..] {
let mut trial_config = config.clone();
trial_config.detail_level = level.clone();
let mut buffer = Vec::new();
render(&mut buffer, doc, &trial_config)?;
let tokens = estimate_tokens(&buffer);
let fits = tokens <= budget;
chosen = Some((level.clone(), buffer, tokens));
if fits {
break;
}
}
let (level, buffer, tokens) = chosen.expect("at least one detail level is rendered");
if level != config.detail_level {
eprintln!(
"vimanam: output exceeded the {budget}-token budget at --detail {}; \
reduced to --detail {} (~{tokens} tokens).",
detail_level_name(&config.detail_level),
detail_level_name(&level),
);
} else if tokens > budget {
eprintln!(
"vimanam: output is ~{tokens} tokens, over the {budget}-token budget; \
already at the lowest detail level (--detail {}).",
detail_level_name(&level),
);
}
writer.write_all(&buffer)?;
Ok(())
}
fn estimate_tokens(rendered: &[u8]) -> usize {
String::from_utf8_lossy(rendered)
.chars()
.count()
.div_ceil(4)
}
fn detail_level_name(level: &DetailLevel) -> &'static str {
match level {
DetailLevel::Summary => "summary",
DetailLevel::Basic => "basic",
DetailLevel::Standard => "standard",
DetailLevel::Full => "full",
}
}