use super::expansion::{protects_dollars, quote_bare_after};
use crate::commands::variables::{DialStringTarget, VariablesType};
const LEG_SPLIT_PASSES: u32 = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum EscapedField {
Value {
scope: VariablesType,
commas_separate: bool,
},
Endpoint,
Key {
scope: VariablesType,
commas_separate: bool,
},
}
impl EscapedField {
fn escapes_comma(self) -> bool {
match self {
Self::Value {
commas_separate, ..
}
| Self::Key {
commas_separate, ..
} => commas_separate,
Self::Endpoint => true,
}
}
fn escapes_pipe(self) -> bool {
match self {
Self::Value { scope, .. } | Self::Key { scope, .. } => scope == VariablesType::Channel,
Self::Endpoint => true,
}
}
}
fn leg_split_passes(scope: VariablesType) -> u32 {
match scope {
VariablesType::Enterprise | VariablesType::Default => 0,
VariablesType::Channel => LEG_SPLIT_PASSES,
}
}
fn passes(target: DialStringTarget, field: EscapedField) -> u32 {
target.argument_passes()
+ match field {
EscapedField::Value { scope, .. } | EscapedField::Key { scope, .. } => {
target
.block_parse()
.cleanup_passes()
+ leg_split_passes(scope)
}
EscapedField::Endpoint => LEG_SPLIT_PASSES,
}
}
fn backslash_escape(target: DialStringTarget, field: EscapedField) -> String {
"\\".repeat(1 << passes(target, field))
}
fn space_escape(target: DialStringTarget, field: EscapedField) -> String {
format!("{}s", "\\".repeat(1 << (passes(target, field) - 1)))
}
fn quote_escape(target: DialStringTarget, field: EscapedField) -> String {
quote_bare_after(target, passes(target, field))
}
fn channel_comma_guard(target: DialStringTarget) -> String {
quote_bare_after(target, target.argument_passes() + 1).repeat(2)
}
fn caret_guard(target: DialStringTarget, field: EscapedField) -> String {
quote_bare_after(target, passes(target, field) - 1).repeat(2)
}
pub(crate) fn escape_value(
value: &str,
target: impl Into<DialStringTarget>,
commas_separate: bool,
vars_type: VariablesType,
) -> String {
escape_text(
value,
target.into(),
EscapedField::Value {
scope: vars_type,
commas_separate,
},
)
}
pub(crate) fn escape_text(text: &str, target: DialStringTarget, field: EscapedField) -> String {
let value = text;
let escaped = value
.replace('\\', &backslash_escape(target, field))
.replace('\'', "e_escape(target, field));
let dollars = protects_dollars(value, target);
let escaped = if dollars {
escaped.replace('$', "\\$")
} else {
escaped
};
let escaped = if field.escapes_comma() {
escaped.replace(',', "\\,")
} else {
escaped
};
let escaped = if field.escapes_pipe() {
escaped.replace('|', "\\|")
} else {
escaped
};
let escaped = match field {
EscapedField::Key { .. } => escaped.replace('=', "\\="),
EscapedField::Value { .. } | EscapedField::Endpoint => escaped,
};
let escaped = match field {
EscapedField::Value {
scope: VariablesType::Channel,
commas_separate,
} => guard_channel_commas(
escaped,
commas_separate && value.ends_with('\\'),
target,
commas_separate,
),
EscapedField::Key {
scope: VariablesType::Channel,
commas_separate,
} => guard_channel_commas(escaped, false, target, commas_separate),
EscapedField::Value { .. } | EscapedField::Key { .. } | EscapedField::Endpoint => escaped,
};
let space = space_escape(target, field);
let escaped = match escaped.strip_prefix(' ') {
Some(rest) => format!("{space}{rest}"),
None => escaped,
};
let escaped = match escaped.strip_suffix(' ') {
Some(rest) => format!("{rest}{space}"),
None => escaped,
};
let escaped = match field {
EscapedField::Key { .. } if value.starts_with("^^") => {
format!("{}{escaped}", caret_guard(target, field))
}
EscapedField::Value { .. } | EscapedField::Key { .. } | EscapedField::Endpoint => escaped,
};
let escaped = if dollars {
format!("\\'{escaped}")
} else {
escaped
};
if escaped.contains(' ') {
format!("'{}'", escaped)
} else {
escaped
}
}
fn guard_channel_commas(
escaped: String,
separator_follows: bool,
target: DialStringTarget,
commas_separate: bool,
) -> String {
let guard = channel_comma_guard(target);
if !commas_separate {
let backslash = backslash_escape(
target,
EscapedField::Value {
scope: VariablesType::Channel,
commas_separate,
},
);
escaped.replace(&format!("{backslash},"), &format!("{backslash}{guard},"))
} else if separator_follows {
escaped + &guard
} else {
escaped
}
}