mod cfg;
mod cmd;
mod i18n;
mod option;
use {
core::{
fmt::{self, Display},
option::Option as RustOption,
},
std::borrow::Cow,
};
pub use self::{
cfg::*,
cmd::*,
i18n::*,
option::*,
};
#[cfg(unix)]
const LINE_BREAK: &str = "\n";
#[cfg(not(unix))]
const LINE_BREAK: &str = "\r\n";
#[macro_export]
macro_rules! make_cmds {
($($e: expr),+ $(,)?) => {{
vec!($(std::borrow::Cow::<dia_args::docs::Cmd>::from($e),)+)
}};
}
#[macro_export]
macro_rules! make_options {
($($e: expr),+ $(,)?) => {{
vec!($(std::borrow::Cow::<dia_args::docs::Option>::from($e),)+)
}};
}
pub const NO_VALUES: &[&str] = &[];
pub struct Docs<'a> {
pub name: Cow<'a, str>,
pub docs: Cow<'a, str>,
pub cfg: Cfg,
pub i18n: I18n<'a>,
pub options: RustOption<Vec<Cow<'a, Option<'a>>>>,
pub commands: RustOption<Vec<Cow<'a, Cmd<'a>>>>,
pub project: RustOption<Project<'a>>,
}
impl<'a> Docs<'a> {
pub fn new(name: Cow<'a, str>, docs: Cow<'a, str>) -> Self {
Self {
name,
docs,
cfg: Cfg::default(),
i18n: I18n::default(),
options: None,
commands: None,
project: None,
}
}
pub fn print(self) -> crate::Result<()> {
crate::lock_write_out(self.to_string().trim());
crate::lock_write_out([b'\n']);
Ok(())
}
}
impl Display for Docs<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let tab = self.cfg.tab_len().saturating_mul(self.cfg.tab_level().into());
let next_tab = self.cfg.tab_len().saturating_mul(self.cfg.tab_level().saturating_add(1).into());
f.write_str(&format(&self.name, tab, self.cfg.columns()))?;
f.write_str(LINE_BREAK)?;
f.write_str(&format(&self.docs, next_tab, self.cfg.columns()))?;
f.write_str(LINE_BREAK)?;
f.write_str(&format(self.i18n.options.to_uppercase(), tab, self.cfg.columns()))?;
f.write_str(LINE_BREAK)?;
match self.options.as_ref() {
Some(options) => {
let cfg = self.cfg.increment_level();
for option in options {
option.format(&cfg, &self.i18n, f)?;
}
},
None => {
f.write_str(&format(&self.i18n.no_options, next_tab, self.cfg.columns()))?;
f.write_str(LINE_BREAK)?;
},
};
f.write_str(&format(self.i18n.commands.to_uppercase(), tab, self.cfg.columns()))?;
f.write_str(LINE_BREAK)?;
match self.commands.as_ref() {
Some(commands) => {
let cfg = self.cfg.increment_level();
for command in commands {
command.format(&cfg, &self.i18n, f)?;
}
},
None => {
f.write_str(&format(&self.i18n.no_commands, next_tab, self.cfg.columns()))?;
f.write_str(LINE_BREAK)?;
},
};
if let Some(project) = self.project.as_ref() {
f.write_str(&format(self.i18n.project.to_uppercase(), tab, self.cfg.columns()))?;
f.write_str(LINE_BREAK)?;
if let Some(home) = project.home {
f.write_str(&format(format!("- {}: {}", self.i18n.home, home), next_tab, self.cfg.columns()))?;
}
f.write_str(&format(format!("- {}: {}", self.i18n.license, project.license_name), next_tab, self.cfg.columns()))?;
if let Some(license) = project.license.as_ref() {
f.write_str(LINE_BREAK)?;
f.write_str(&format(license, next_tab, self.cfg.columns()))?;
}
}
Ok(())
}
}
#[derive(Debug)]
pub struct Project<'a> {
home: RustOption<&'a str>,
license_name: &'a str,
license: RustOption<Cow<'a, str>>,
}
impl<'a> Project<'a> {
pub const fn new(home: RustOption<&'a str>, license_name: &'a str, license: RustOption<Cow<'a, str>>) -> Self {
Self {
home,
license_name,
license,
}
}
}
fn format<S>(s: S, size_of_indentation: usize, columns: usize) -> String where S: AsRef<str> {
let s = s.as_ref();
if s.is_empty() || size_of_indentation >= columns {
return String::new();
}
let mut result = String::with_capacity(s.len().saturating_add(s.len() / 10));
let tab = concat!(' ').repeat(size_of_indentation);
for line in s.lines() {
let line_indentation = match line.split_whitespace().next() {
Some(word) => match word.chars().next() {
Some('-') | Some('+') | Some('*') | Some('~') | Some('$') | Some('#') =>
Some(concat!(' ').repeat(word.chars().count().saturating_add(1))),
_ => None,
},
None => None,
};
let mut col = 0;
for (idx, word) in line.split_whitespace().enumerate() {
if idx == 0 {
result += &tab;
col = size_of_indentation;
}
let chars: Vec<_> = word.chars().collect();
if col + if col == size_of_indentation { 0 } else { 1 } + chars.len() <= columns {
if col > size_of_indentation {
result.push(' ');
col += 1;
}
col += chars.len();
result.extend(chars.into_iter());
} else {
for (i, c) in chars.into_iter().enumerate() {
if i == 0 || col >= columns {
result += LINE_BREAK;
result += &tab;
col = size_of_indentation;
if let Some(line_indentation) = line_indentation.as_ref() {
result += line_indentation;
col += line_indentation.len();
}
}
result.push(c);
col += 1;
}
};
}
result += LINE_BREAK;
}
result
}