use std::fmt;
use std::process;
use clap::{CommandFactory, Parser};
use clap_help::Printer;
use owo_colors::{OwoColorize, Style};
use serde::Serialize;
use shadow_rs::formatcp;
use supports_color::Stream;
use taplo::formatter::Options;
use taplo::rowan::{NodeOrToken, WalkEvent};
use termimad::ansi;
use crate::build;
use crate::examples::*;
static INTRO: &str = formatcp!(
r#"
*{}* is a cli tool for copying to the clipboard
> **NOTE**: This is a work in progress and is missing some features.
"#,
build::PROJECT_NAME
);
static EXAMPLES_TEMPLATE: &str = "
**Examples:**
${examples
**${example-number})** ${example-title}:
```sh
${example-cmd}
```
> *${example-comments}*
---
}
";
#[derive(Parser, Debug)]
#[command(name = build::PROJECT_NAME, author, about, disable_help_flag = true)]
pub struct Args {
#[arg(short, long)]
pub help: bool,
#[arg(short = 'V', long)]
pub version: bool,
#[arg(name = "TEXT")]
pub text: Option<String>,
}
impl Args {
pub fn init_cli() -> Args {
let args = Self::parse();
if args.help {
Self::print_help();
process::exit(0)
}
if args.version {
Self::print_version();
process::exit(0)
}
args
}
pub fn print_help() {
let mut printer = Printer::new(Args::command())
.with("introduction", INTRO)
.with("options", clap_help::TEMPLATE_OPTIONS_MERGED_VALUE)
.without("author");
printer.template_keys_mut().push("examples");
printer.set_template("examples", EXAMPLES_TEMPLATE);
for (i, example) in EXAMPLES.iter().enumerate() {
printer
.expander_mut()
.sub("examples")
.set("example-number", i + 1)
.set("example-title", example.title)
.set("example-cmd", example.cmd)
.set_md("example-comments", example.comments);
}
let skin = printer.skin_mut();
let color = ansi(190);
skin.headers[0].compound_style.set_fg(color);
skin.bold.set_fg(color);
skin.italic = termimad::CompoundStyle::with_fg(color);
printer.print_help();
}
pub fn print_version() {
let mut version_info = VersionInfoDisplay::new();
let color = Color::Auto;
if color.supports_color_on(Stream::Stdout) {
version_info.colorize();
}
if build::BUILD_RUST_CHANNEL == "debug" {
let text = " DEBUG BUILD ";
if supports_color::on_cached(Stream::Stdout).is_some() {
eprintln!("{}", text.on_yellow().black().bold());
} else {
eprintln!("{}", text);
}
}
println!("{}", version_info);
}
}
#[derive(Debug, Default)]
struct Styles {
name_style: Style,
value_style: Style,
}
impl Styles {
fn colorize(&mut self) {
self.name_style = Style::new().blue();
self.value_style = Style::new().yellow();
}
}
#[derive(Debug, Serialize)]
struct VersionInfoDisplay<'a> {
#[serde(rename = "version")]
pkg_version: &'a str,
branch: &'a str,
commit_hash: &'a str,
build_time: &'a str,
build_env: &'a str,
build_channel: &'a str,
#[serde(skip)]
styles: Box<Styles>,
}
impl VersionInfoDisplay<'_> {
fn new() -> Self {
#[allow(clippy::const_is_empty)]
let pkg_version = if build::TAG.is_empty() {
formatcp!("{}-dev", build::PKG_VERSION)
} else {
build::PKG_VERSION
};
#[allow(clippy::const_is_empty)]
let commit_hash = if !build::GIT_CLEAN {
formatcp!("{}+", build::SHORT_COMMIT)
} else {
build::SHORT_COMMIT
};
Self {
pkg_version,
branch: build::BRANCH,
commit_hash,
build_time: build::BUILD_TIME,
build_env: build::RUST_VERSION,
build_channel: build::RUST_CHANNEL,
styles: Box::default(),
}
}
fn colorize(&mut self) {
self.styles.colorize();
}
}
impl fmt::Display for VersionInfoDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let toml = toml::to_string(&self).map_err(|_| fmt::Error)?;
if self.styles.name_style == Style::new() && self.styles.value_style == Style::new() {
return write!(f, "{}", toml);
}
let formatted = taplo::formatter::format(
&toml,
Options {
align_entries: true,
..Default::default()
},
);
let syntax = taplo::parser::parse(&formatted);
if !syntax.errors.is_empty() {
return Err(fmt::Error);
}
let mut output = String::with_capacity(formatted.len());
let tokens = syntax
.into_syntax()
.preorder_with_tokens()
.filter_map(|event| match event {
WalkEvent::Enter(NodeOrToken::Token(token)) => Some(token),
_ => None,
});
for token in tokens {
let text = token.text();
match token.kind() {
taplo::syntax::SyntaxKind::STRING
| taplo::syntax::SyntaxKind::INTEGER
| taplo::syntax::SyntaxKind::FLOAT => {
output.push_str(&text.style(self.styles.value_style).to_string());
}
taplo::syntax::SyntaxKind::IDENT | taplo::syntax::SyntaxKind::PERIOD => {
output.push_str(&text.style(self.styles.name_style).to_string());
}
_ => output.push_str(text),
}
}
write!(f, "{}", output)
}
}
#[derive(Debug)]
pub enum Color {
Always,
Auto,
Never,
}
impl Color {
fn supports_color_on(self, stream: Stream) -> bool {
match self {
Color::Always => true,
Color::Auto => supports_color::on_cached(stream).is_some(),
Color::Never => false,
}
}
}