use crate::cli::OutputFormat;
use clap::builder::StyledStr;
use clap::ColorChoice;
use itertools::Itertools;
pub const PREFIX: &str = "claptrap";
pub const SUBCOMMAND_PREFIX: &str = "subcommand";
pub const SUBCOMMAND_VALUE_SEPARATOR: &str = "::";
pub const SUBCOMMAND_PATH_SEPARATOR: &str = "_";
#[derive(Debug, Eq, PartialEq)]
pub enum Output {
Cat(CatCmd),
Variables(Vec<Var>),
}
impl Output {
#[must_use]
pub fn render(&self, format: OutputFormat) -> String {
match format {
OutputFormat::Posix => match self {
Self::Cat(cmd) => cmd.render_posix(),
Self::Variables(vars) => vars.iter().map(Var::render_posix).join("\n"),
},
OutputFormat::Fish => match self {
Self::Cat(cmd) => cmd.render_fish(),
Self::Variables(vars) => vars.iter().map(Var::render_fish).join("\n"),
},
OutputFormat::PowerShell => match self {
Self::Cat(cmd) => cmd.render_powershell(),
Self::Variables(vars) => vars.iter().map(Var::render_powershell).join("\n"),
},
}
}
}
pub use cat::{CatCmd, ExitCode};
pub use var::Var;
mod var {
use super::{PREFIX, SUBCOMMAND_PATH_SEPARATOR, SUBCOMMAND_PREFIX, SUBCOMMAND_VALUE_SEPARATOR};
use itertools::Itertools;
#[derive(Debug, Eq, PartialEq)]
pub enum Var {
Subcommand(Vec<String>),
Single(Vec<String>, String, String),
Many(Vec<String>, String, Vec<String>),
}
impl Var {
#[must_use]
pub(super) fn render_posix(&self) -> String {
fn quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "'\\''"))
}
match self {
Self::Subcommand(path) => {
let path = quote(&path.iter().join(SUBCOMMAND_VALUE_SEPARATOR));
format!("{PREFIX}__{SUBCOMMAND_PREFIX}={path}")
}
Self::Single(path, name, value) => {
let value = quote(value);
if path.is_empty() {
format!("{PREFIX}_{name}={value}")
} else {
format!(
"{}_{}_{}={}",
PREFIX,
path.iter().format(SUBCOMMAND_PATH_SEPARATOR),
name,
value
)
}
}
Self::Many(path, name, values) => {
let values = values.iter().map(|v| quote(v)).join(" ");
if path.is_empty() {
format!("{PREFIX}_{name}=({values})")
} else {
format!(
"{}_{}_{}=({})",
PREFIX,
path.iter().format(SUBCOMMAND_PATH_SEPARATOR),
name,
values
)
}
}
}
}
#[must_use]
pub(super) fn render_powershell(&self) -> String {
fn quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "''"))
}
match self {
Self::Subcommand(path) => {
let path = path.iter().join(SUBCOMMAND_VALUE_SEPARATOR);
let path = quote(&path);
format!("$env:{PREFIX}__{SUBCOMMAND_PREFIX} = {path}")
}
Self::Single(p, name, value) => {
let value = quote(value);
if p.is_empty() {
format!("$env:{PREFIX}_{name} = {value}")
} else {
format!(
"$env:{}_{}_{} = {}",
PREFIX,
p.iter().format(SUBCOMMAND_PATH_SEPARATOR),
name,
value
)
}
}
Self::Many(p, name, values) => {
let values = values.iter().map(|v| quote(v)).format(", ");
if p.is_empty() {
format!("$env:{PREFIX}_{name} = @({values})")
} else {
format!(
"$env:{}_{}_{} = @({})",
PREFIX,
p.iter().format(SUBCOMMAND_PATH_SEPARATOR),
name,
values
)
}
}
}
}
#[must_use]
pub(super) fn render_fish(&self) -> String {
fn quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "\\''"))
}
match self {
Self::Subcommand(path) => {
let path = quote(&path.iter().join(SUBCOMMAND_VALUE_SEPARATOR));
format!("set -gx {PREFIX}__{SUBCOMMAND_PREFIX} {path}")
}
Self::Single(p, name, value) => {
let value = quote(value);
if p.is_empty() {
format!("set -gx {PREFIX}_{name} {value}")
} else {
format!(
"set -gx {}_{}_{} {}",
PREFIX,
p.iter().format(SUBCOMMAND_PATH_SEPARATOR),
name,
value
)
}
}
Self::Many(p, name, values) => {
let values = values.iter().map(|v| quote(v)).join(" ");
if p.is_empty() {
format!("set -gx {PREFIX}_{name} {values}")
} else {
format!(
"set -gx {}_{}_{} {}",
PREFIX,
p.iter().format(SUBCOMMAND_PATH_SEPARATOR),
name,
values
)
}
}
}
}
}
}
mod cat {
use super::{ColorChoice, StyledStr};
use crc32fast::hash;
use std::fmt::Display;
#[derive(Debug, Eq, PartialEq)]
pub struct CatCmd {
pub data: StyledStr,
pub exit_code: ExitCode,
pub color: ColorChoice,
}
impl CatCmd {
#[must_use]
pub fn new(cmd: StyledStr, exit_code: ExitCode, color: ColorChoice) -> Self {
Self {
data: cmd,
exit_code,
color,
}
}
#[cfg(test)]
#[must_use]
pub fn render(&self) -> String {
self.render_posix()
}
#[must_use]
pub(super) fn render_posix(&self) -> String {
let msg = match self.color {
ColorChoice::Never => format!("{}", self.data),
_ => format!("{}", self.data.ansi()),
};
let mut delimiter = format!("EOF_{:08x}", hash(msg.as_bytes()));
while msg.contains(&delimiter) {
delimiter.push('_');
}
format!(
"command cat <<'{}'\n{}{}\nexit {}",
delimiter, msg, delimiter, self.exit_code
)
}
#[must_use]
pub(super) fn render_powershell(&self) -> String {
let msg = match self.color {
ColorChoice::Never => format!("{}", self.data),
_ => format!("{}", self.data.ansi()),
};
let msg = Self::ps_quote_double(&msg);
format!("Write-Output {msg}\nexit {}", self.exit_code)
}
fn ps_quote_double(value: &str) -> String {
format!(
"@\"\n{}\n\"@",
value
.replace('`', "``")
.replace('"', "`\"")
.replace('$', "`$")
)
}
#[must_use]
pub(super) fn render_fish(&self) -> String {
let msg = match self.color {
ColorChoice::Never => format!("{}", self.data),
_ => format!("{}", self.data.ansi()),
};
let msg = Self::quote(&msg);
format!("command printf %s {msg}\nexit {}", self.exit_code)
}
fn quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "\\''"))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ExitCode {
Success = 0,
Error = 1,
Usage = 2,
Panic = 3,
}
impl Display for ExitCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", *self as i32)
}
}
}