use anyhow::{bail, Context, Result};
use clap::{crate_version, ArgAction, Command, CommandFactory};
use serde::{de, forward_to_deserialize_any, Deserialize};
use std::collections::{hash_map::RandomState, HashMap};
use std::fmt::Write as _;
use std::fs::OpenOptions;
use std::io::{BufWriter, Write};
use std::path::Path;
use crate::io::BUFFER_SIZE;
use super::{Cmd, InstallConfig, PackExampleConfigConfig, PackManConfig};
pub fn pack_man(config: PackManConfig) -> Result<()> {
pack_one_man(&config, Cmd::command())
}
fn pack_one_man(config: &PackManConfig, cmd: Command) -> Result<()> {
let name = cmd.get_name();
let path = Path::new(&config.directory).join(format!("{}.8", name));
println!("Generating {}...", path.display());
let out = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&path)
.with_context(|| format!("opening {}", path.display()))?;
let mut buf = BufWriter::with_capacity(BUFFER_SIZE, out);
clap_mangen::Man::new(cmd.clone())
.title("coreos-installer")
.section("8")
.source(format!("coreos-installer {}", crate_version!()))
.render(&mut buf)
.with_context(|| format!("rendering {name}.8"))?;
buf.flush().context("flushing man page")?;
drop(buf);
for subcmd in cmd.get_subcommands().filter(|c| !c.is_hide_set()) {
let subname = format!("{name}-{}", subcmd.get_name());
pack_one_man(
config,
subcmd.clone().name(subname).version(crate_version!()),
)?;
}
Ok(())
}
pub fn pack_example_config(_config: PackExampleConfigConfig) -> Result<()> {
let mut fields = Vec::new();
InstallConfig::deserialize(&mut FieldLister {
fields: &mut fields,
})
.unwrap_err();
let mut out = String::new();
let cmd = InstallConfig::command();
let arg_map: HashMap<_, _, RandomState> =
HashMap::from_iter(cmd.get_arguments().map(|arg| (arg.get_id().as_str(), arg)));
for field in &fields {
if let Some(arg) = arg_map.get(&*field.replace('-', "_")) {
if let Some(help) = arg.get_help() {
let help = format!("{}", help);
let help = match field.as_ref() {
"fetch-retries" => r#"Fetch retries, or string "infinite""#,
"network-dir" => "Source directory for copy-network",
"append-karg" => "Append default kernel arguments",
"delete-karg" => "Delete default kernel arguments",
_ => &help,
};
writeln!(out, "# {help}").unwrap();
}
let value_names = arg.get_value_names().map(|v| v[0].as_str());
let desc = match arg.get_action() {
ArgAction::Set => {
match field.as_ref() {
"dest-device" => "path",
_ => value_names.expect("missing value name"),
}
.into()
}
ArgAction::Append => {
let value_name = match field.as_ref() {
"save-partlabel" => "glob",
"save-partindex" => "id-or-range",
_ => value_names.expect("missing value name"),
};
format!("[{0}, {0}]", value_name)
}
_ => {
"true".into()
}
};
writeln!(out, "{field}: {desc}").unwrap();
} else {
bail!("couldn't look up field {}", field);
}
}
serde_yaml::from_str::<serde_yaml::Value>(&out).context("re-parsing output")?;
print!("{}", out);
Ok(())
}
struct FieldLister<'a> {
fields: &'a mut Vec<String>,
}
impl<'de, 'a> de::Deserializer<'de> for &'a mut FieldLister<'a> {
type Error = de::value::Error;
fn deserialize_struct<V: de::Visitor<'de>>(
self,
_name: &'static str,
fields: &'static [&'static str],
_visitor: V,
) -> Result<V::Value, Self::Error> {
self.fields.extend(fields.iter().map(|v| v.to_string()));
Err(de::Error::custom("expected failure"))
}
fn deserialize_any<V: de::Visitor<'de>>(self, _visitor: V) -> Result<V::Value, Self::Error> {
unimplemented!()
}
forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
bytes byte_buf option unit unit_struct newtype_struct seq tuple
tuple_struct map enum identifier ignored_any
}
}