use crate::cli::Cli;
use crate::file;
use crate::registry;
use clap::{Command, CommandFactory};
use eyre::{ContextCompat, Result, ensure};
use indoc::formatdoc;
use itertools::Itertools;
const IDIOMATIC_FILES_START: &str = "<!-- mise:idiomatic-version-files:start -->";
const IDIOMATIC_FILES_END: &str = "<!-- mise:idiomatic-version-files:end -->";
#[derive(Debug, clap::Args)]
#[clap(hide = true)]
pub struct RenderHelp {}
impl RenderHelp {
pub fn run(self) -> Result<()> {
xx::file::mkdirp("docs/.vitepress")?;
file::write("docs/.vitepress/cli_commands.ts", render_command_ts())?;
render_idiomatic_version_files()?;
if cfg!(windows) {
cmd!("prettier.cmd", "--write", "docs/.vitepress/cli_commands.ts").run()?;
} else {
cmd!("prettier", "--write", "docs/.vitepress/cli_commands.ts").run()?;
}
Ok(())
}
}
fn render_idiomatic_version_files() -> Result<()> {
let path = "docs/configuration.md";
let content = file::read_to_string(path)?;
let start = content
.find(IDIOMATIC_FILES_START)
.wrap_err("missing idiomatic version files start marker")?
+ IDIOMATIC_FILES_START.len();
let end = content
.find(IDIOMATIC_FILES_END)
.wrap_err("missing idiomatic version files end marker")?;
ensure!(start <= end, "idiomatic version files markers are reversed");
let table = render_idiomatic_version_files_table();
let rendered = format!(
"{}\n\n{}\n\n{}",
&content[..start],
table.trim(),
&content[end..]
);
file::write(path, rendered)?;
Ok(())
}
fn render_idiomatic_version_files_table() -> String {
let rows = registry::baked_registry()
.iter()
.filter(|(plugin, tool)| *plugin == tool.short && !tool.idiomatic_files.is_empty())
.map(|(plugin, tool)| {
let files = tool
.idiomatic_files
.iter()
.map(|file| format!("`{}`", file.path))
.join(", ");
(plugin, files)
})
.sorted_by_key(|(plugin, _)| *plugin)
.collect_vec();
let plugin_width = rows
.iter()
.map(|(plugin, _)| plugin.len())
.max()
.unwrap_or_default()
.max("Plugin".len());
let files_width = rows
.iter()
.map(|(_, files)| files.len())
.max()
.unwrap_or_default()
.max("Idiomatic Files".len());
let mut table = format!(
"| {plugin:<plugin_width$} | {files:<files_width$} |\n\
| {plugin_rule:<plugin_width$} | {files_rule:<files_width$} |\n",
plugin = "Plugin",
files = "Idiomatic Files",
plugin_rule = "-".repeat(plugin_width),
files_rule = "-".repeat(files_width),
);
for (plugin, files) in rows {
table.push_str(&format!(
"| {plugin:<plugin_width$} | {files:<files_width$} |\n"
));
}
table
}
fn render_command_ts() -> String {
let mut doc = String::new();
doc.push_str(&formatdoc! {r#"
// This file is generated by `mise render-help`
// Do not edit this file directly
export type Command = {{
hide: boolean,
subcommands?: {{
[key: string]: Command,
}},
}};
"#});
doc.push_str("export const commands: { [key: string]: Command } = {\n");
let cli = Cli::command()
.term_width(80)
.max_term_width(80)
.disable_help_subcommand(true)
.disable_help_flag(true);
let mut cli = super::expand_deferred_subcommands(cli);
for command in cli
.get_subcommands_mut()
.sorted_by_cached_key(|c| c.get_name().to_string())
{
doc.push_str(&render_command(command, 2));
}
doc.push_str("};\n");
doc
}
fn render_command(command: &mut Command, indent: usize) -> String {
let pad = " ".repeat(indent);
let mut output = format!(
"{pad}\"{}\": {{\n{pad} hide: {},\n",
command.get_name(),
command.is_hide_set()
);
if command.has_subcommands() {
output.push_str(&format!("{pad} subcommands: {{\n"));
for subcommand in command
.get_subcommands_mut()
.sorted_by_cached_key(|c| c.get_name().to_string())
{
output.push_str(&render_command(subcommand, indent + 4));
}
output.push_str(&format!("{pad} }},\n"));
}
output.push_str(&format!("{pad}}},\n"));
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn idiomatic_version_files_table_comes_from_baked_registry() {
let table = render_idiomatic_version_files_table();
assert!(
table
.lines()
.any(|line| line.starts_with("| cmake ") && line.contains("`CMakeLists.txt`"))
);
assert!(table.lines().any(|line| {
line.starts_with("| node ")
&& line.contains("`.nvmrc`, `.node-version`, `package.json`")
}));
assert!(
table
.lines()
.any(|line| line.starts_with("| zig ") && line.contains("`.zig-version`"))
);
}
}