2018-05-11 02:32:31 +02:00
|
|
|
use std::collections::HashSet;
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
2020-04-22 21:45:47 +02:00
|
|
|
use crate::error::*;
|
2018-08-22 22:29:12 +02:00
|
|
|
|
2022-02-07 19:48:57 +00:00
|
|
|
#[non_exhaustive]
|
2018-05-11 02:32:31 +02:00
|
|
|
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
|
2020-03-21 20:54:16 +01:00
|
|
|
pub enum StyleComponent {
|
2018-05-11 02:32:31 +02:00
|
|
|
Auto,
|
2020-05-12 22:24:51 -07:00
|
|
|
#[cfg(feature = "git")]
|
2018-05-11 02:32:31 +02:00
|
|
|
Changes,
|
|
|
|
Grid,
|
2020-10-06 16:57:47 +01:00
|
|
|
Rule,
|
2018-05-11 02:32:31 +02:00
|
|
|
Header,
|
2022-02-07 19:48:57 +00:00
|
|
|
HeaderFilename,
|
|
|
|
HeaderFilesize,
|
2020-04-22 20:36:20 +02:00
|
|
|
LineNumbers,
|
2019-05-24 18:24:13 -07:00
|
|
|
Snip,
|
2018-05-11 02:32:31 +02:00
|
|
|
Full,
|
2022-05-04 15:31:32 -04:00
|
|
|
Default,
|
2018-05-11 02:32:31 +02:00
|
|
|
Plain,
|
|
|
|
}
|
|
|
|
|
2020-03-21 20:54:16 +01:00
|
|
|
impl StyleComponent {
|
|
|
|
pub fn components(self, interactive_terminal: bool) -> &'static [StyleComponent] {
|
2019-03-08 10:46:49 +00:00
|
|
|
match self {
|
2020-03-21 20:54:16 +01:00
|
|
|
StyleComponent::Auto => {
|
2018-09-26 19:16:03 +02:00
|
|
|
if interactive_terminal {
|
2022-05-04 15:31:32 -04:00
|
|
|
StyleComponent::Default.components(interactive_terminal)
|
2018-09-26 19:16:03 +02:00
|
|
|
} else {
|
2020-03-21 20:54:16 +01:00
|
|
|
StyleComponent::Plain.components(interactive_terminal)
|
2018-09-26 19:16:03 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-12 22:24:51 -07:00
|
|
|
#[cfg(feature = "git")]
|
2020-03-21 20:54:16 +01:00
|
|
|
StyleComponent::Changes => &[StyleComponent::Changes],
|
|
|
|
StyleComponent::Grid => &[StyleComponent::Grid],
|
2020-10-06 16:57:47 +01:00
|
|
|
StyleComponent::Rule => &[StyleComponent::Rule],
|
2022-02-07 19:48:57 +00:00
|
|
|
StyleComponent::Header => &[StyleComponent::HeaderFilename],
|
|
|
|
StyleComponent::HeaderFilename => &[StyleComponent::HeaderFilename],
|
|
|
|
StyleComponent::HeaderFilesize => &[StyleComponent::HeaderFilesize],
|
2020-04-22 20:36:20 +02:00
|
|
|
StyleComponent::LineNumbers => &[StyleComponent::LineNumbers],
|
2020-03-21 20:54:16 +01:00
|
|
|
StyleComponent::Snip => &[StyleComponent::Snip],
|
|
|
|
StyleComponent::Full => &[
|
2020-05-12 22:24:51 -07:00
|
|
|
#[cfg(feature = "git")]
|
2020-03-21 20:54:16 +01:00
|
|
|
StyleComponent::Changes,
|
|
|
|
StyleComponent::Grid,
|
2022-02-07 19:48:57 +00:00
|
|
|
StyleComponent::HeaderFilename,
|
|
|
|
StyleComponent::HeaderFilesize,
|
2020-04-22 20:36:20 +02:00
|
|
|
StyleComponent::LineNumbers,
|
2020-03-21 20:54:16 +01:00
|
|
|
StyleComponent::Snip,
|
2018-05-11 02:32:31 +02:00
|
|
|
],
|
2022-05-04 15:31:32 -04:00
|
|
|
StyleComponent::Default => &[
|
|
|
|
#[cfg(feature = "git")]
|
|
|
|
StyleComponent::Changes,
|
|
|
|
StyleComponent::Grid,
|
|
|
|
StyleComponent::HeaderFilename,
|
|
|
|
StyleComponent::LineNumbers,
|
|
|
|
StyleComponent::Snip,
|
|
|
|
],
|
2020-03-21 20:54:16 +01:00
|
|
|
StyleComponent::Plain => &[],
|
2018-05-11 02:32:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-21 20:54:16 +01:00
|
|
|
impl FromStr for StyleComponent {
|
2018-05-11 02:32:31 +02:00
|
|
|
type Err = Error;
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self> {
|
|
|
|
match s {
|
2020-03-21 20:54:16 +01:00
|
|
|
"auto" => Ok(StyleComponent::Auto),
|
2020-05-12 22:24:51 -07:00
|
|
|
#[cfg(feature = "git")]
|
2020-03-21 20:54:16 +01:00
|
|
|
"changes" => Ok(StyleComponent::Changes),
|
|
|
|
"grid" => Ok(StyleComponent::Grid),
|
2020-10-06 16:57:47 +01:00
|
|
|
"rule" => Ok(StyleComponent::Rule),
|
2020-03-21 20:54:16 +01:00
|
|
|
"header" => Ok(StyleComponent::Header),
|
2022-02-07 19:48:57 +00:00
|
|
|
"header-filename" => Ok(StyleComponent::HeaderFilename),
|
|
|
|
"header-filesize" => Ok(StyleComponent::HeaderFilesize),
|
2020-04-22 20:36:20 +02:00
|
|
|
"numbers" => Ok(StyleComponent::LineNumbers),
|
2020-03-21 20:54:16 +01:00
|
|
|
"snip" => Ok(StyleComponent::Snip),
|
|
|
|
"full" => Ok(StyleComponent::Full),
|
2022-05-04 15:31:32 -04:00
|
|
|
"default" => Ok(StyleComponent::Default),
|
2020-03-21 20:54:16 +01:00
|
|
|
"plain" => Ok(StyleComponent::Plain),
|
2024-02-24 22:36:14 +01:00
|
|
|
_ => Err(format!("Unknown style '{s}'").into()),
|
2018-05-11 02:32:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-15 08:25:53 +07:00
|
|
|
#[derive(Debug, Clone, Default)]
|
2020-03-21 20:54:16 +01:00
|
|
|
pub struct StyleComponents(pub HashSet<StyleComponent>);
|
2018-05-11 02:32:31 +02:00
|
|
|
|
2020-03-21 20:54:16 +01:00
|
|
|
impl StyleComponents {
|
|
|
|
pub fn new(components: &[StyleComponent]) -> StyleComponents {
|
|
|
|
StyleComponents(components.iter().cloned().collect())
|
2019-10-20 21:53:34 +02:00
|
|
|
}
|
|
|
|
|
2020-03-30 10:37:29 -07:00
|
|
|
#[cfg(feature = "git")]
|
2018-05-11 02:32:31 +02:00
|
|
|
pub fn changes(&self) -> bool {
|
2020-03-21 20:54:16 +01:00
|
|
|
self.0.contains(&StyleComponent::Changes)
|
2018-05-11 02:32:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn grid(&self) -> bool {
|
2020-03-21 20:54:16 +01:00
|
|
|
self.0.contains(&StyleComponent::Grid)
|
2018-05-11 02:32:31 +02:00
|
|
|
}
|
|
|
|
|
2020-10-06 16:57:47 +01:00
|
|
|
pub fn rule(&self) -> bool {
|
|
|
|
self.0.contains(&StyleComponent::Rule)
|
|
|
|
}
|
|
|
|
|
2018-05-11 02:32:31 +02:00
|
|
|
pub fn header(&self) -> bool {
|
2022-02-07 19:48:57 +00:00
|
|
|
self.header_filename() || self.header_filesize()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn header_filename(&self) -> bool {
|
|
|
|
self.0.contains(&StyleComponent::HeaderFilename)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn header_filesize(&self) -> bool {
|
|
|
|
self.0.contains(&StyleComponent::HeaderFilesize)
|
2018-05-11 02:32:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn numbers(&self) -> bool {
|
2020-04-22 20:36:20 +02:00
|
|
|
self.0.contains(&StyleComponent::LineNumbers)
|
2018-05-11 02:32:31 +02:00
|
|
|
}
|
2018-09-07 10:51:47 -07:00
|
|
|
|
2019-05-24 18:24:13 -07:00
|
|
|
pub fn snip(&self) -> bool {
|
2020-03-21 20:54:16 +01:00
|
|
|
self.0.contains(&StyleComponent::Snip)
|
2019-05-24 18:24:13 -07:00
|
|
|
}
|
|
|
|
|
2018-09-07 10:51:47 -07:00
|
|
|
pub fn plain(&self) -> bool {
|
2020-03-21 20:54:16 +01:00
|
|
|
self.0.iter().all(|c| c == &StyleComponent::Plain)
|
2018-09-07 10:51:47 -07:00
|
|
|
}
|
2023-01-15 19:04:16 +09:00
|
|
|
|
|
|
|
pub fn insert(&mut self, component: StyleComponent) {
|
|
|
|
self.0.insert(component);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear(&mut self) {
|
|
|
|
self.0.clear();
|
|
|
|
}
|
2018-05-11 02:32:31 +02:00
|
|
|
}
|
2024-04-06 14:54:10 -07:00
|
|
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
enum ComponentAction {
|
|
|
|
Override,
|
|
|
|
Add,
|
|
|
|
Remove,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ComponentAction {
|
|
|
|
fn extract_from_str(string: &str) -> (ComponentAction, &str) {
|
|
|
|
match string.chars().next() {
|
|
|
|
Some('-') => (ComponentAction::Remove, string.strip_prefix('-').unwrap()),
|
|
|
|
Some('+') => (ComponentAction::Add, string.strip_prefix('+').unwrap()),
|
|
|
|
_ => (ComponentAction::Override, string),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A list of [StyleComponent] that can be parsed from a string.
|
|
|
|
pub struct StyleComponentList(Vec<(ComponentAction, StyleComponent)>);
|
|
|
|
|
|
|
|
impl StyleComponentList {
|
|
|
|
fn expand_into(&self, components: &mut HashSet<StyleComponent>, interactive_terminal: bool) {
|
|
|
|
for (action, component) in self.0.iter() {
|
|
|
|
let subcomponents = component.components(interactive_terminal);
|
|
|
|
|
|
|
|
use ComponentAction::*;
|
|
|
|
match action {
|
|
|
|
Override | Add => components.extend(subcomponents),
|
|
|
|
Remove => components.retain(|c| !subcomponents.contains(c)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if any component in the list was not prefixed with `+` or `-`.
|
|
|
|
fn contains_override(&self) -> bool {
|
|
|
|
self.0.iter().any(|(a, _)| *a == ComponentAction::Override)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Combines multiple [StyleComponentList]s into a single [StyleComponents] set.
|
|
|
|
///
|
|
|
|
/// ## Precedence
|
|
|
|
/// The most recent list will take precedence and override all previous lists
|
|
|
|
/// unless it only contains components prefixed with `-` or `+`. When this
|
|
|
|
/// happens, the list's components will be merged into the previous list.
|
|
|
|
///
|
|
|
|
/// ## Example
|
|
|
|
/// ```text
|
|
|
|
/// [numbers,grid] + [header,changes] -> [header,changes]
|
|
|
|
/// [numbers,grid] + [+header,-grid] -> [numbers,header]
|
|
|
|
/// ```
|
|
|
|
pub fn to_components(
|
|
|
|
lists: impl IntoIterator<Item = StyleComponentList>,
|
|
|
|
interactive_terminal: bool,
|
|
|
|
) -> StyleComponents {
|
|
|
|
StyleComponents(
|
|
|
|
lists
|
|
|
|
.into_iter()
|
|
|
|
.fold(HashSet::new(), |mut components, list| {
|
|
|
|
if list.contains_override() {
|
|
|
|
components.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
list.expand_into(&mut components, interactive_terminal);
|
|
|
|
components
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for StyleComponentList {
|
|
|
|
fn default() -> Self {
|
|
|
|
StyleComponentList(vec![(ComponentAction::Override, StyleComponent::Default)])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromStr for StyleComponentList {
|
|
|
|
type Err = Error;
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self> {
|
|
|
|
Ok(StyleComponentList(
|
|
|
|
s.split(",")
|
|
|
|
.map(|s| ComponentAction::extract_from_str(s)) // If the component starts with "-", it's meant to be removed
|
|
|
|
.map(|(a, s)| Ok((a, StyleComponent::from_str(s)?)))
|
|
|
|
.collect::<Result<Vec<(ComponentAction, StyleComponent)>>>()?,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use std::collections::HashSet;
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
|
use super::ComponentAction::*;
|
|
|
|
use super::StyleComponent::*;
|
|
|
|
use super::StyleComponentList;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn style_component_list_parse() {
|
|
|
|
assert_eq!(
|
|
|
|
StyleComponentList::from_str("grid,+numbers,snip,-snip,header")
|
|
|
|
.expect("no error")
|
|
|
|
.0,
|
|
|
|
vec![
|
|
|
|
(Override, Grid),
|
|
|
|
(Add, LineNumbers),
|
|
|
|
(Override, Snip),
|
|
|
|
(Remove, Snip),
|
|
|
|
(Override, Header),
|
|
|
|
]
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(StyleComponentList::from_str("not-a-component").is_err());
|
|
|
|
assert!(StyleComponentList::from_str("grid,not-a-component").is_err());
|
|
|
|
assert!(StyleComponentList::from_str("numbers,-not-a-component").is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn style_component_list_to_components() {
|
|
|
|
assert_eq!(
|
|
|
|
StyleComponentList::to_components(
|
|
|
|
vec![StyleComponentList::from_str("grid,numbers").expect("no error")],
|
|
|
|
false
|
|
|
|
)
|
|
|
|
.0,
|
|
|
|
HashSet::from([Grid, LineNumbers])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn style_component_list_to_components_removes_negated() {
|
|
|
|
assert_eq!(
|
|
|
|
StyleComponentList::to_components(
|
|
|
|
vec![StyleComponentList::from_str("grid,numbers,-grid").expect("no error")],
|
|
|
|
false
|
|
|
|
)
|
|
|
|
.0,
|
|
|
|
HashSet::from([LineNumbers])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn style_component_list_to_components_expands_subcomponents() {
|
|
|
|
assert_eq!(
|
|
|
|
StyleComponentList::to_components(
|
|
|
|
vec![StyleComponentList::from_str("full").expect("no error")],
|
|
|
|
false
|
|
|
|
)
|
|
|
|
.0,
|
|
|
|
HashSet::from_iter(Full.components(true).to_owned())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn style_component_list_expand_negates_subcomponents() {
|
|
|
|
assert!(!StyleComponentList::to_components(
|
|
|
|
vec![StyleComponentList::from_str("full,-numbers").expect("no error")],
|
|
|
|
true
|
|
|
|
)
|
|
|
|
.numbers());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn style_component_list_to_components_precedence_overrides_previous_lists() {
|
|
|
|
assert_eq!(
|
|
|
|
StyleComponentList::to_components(
|
|
|
|
vec![
|
|
|
|
StyleComponentList::from_str("grid").expect("no error"),
|
|
|
|
StyleComponentList::from_str("numbers").expect("no error"),
|
|
|
|
],
|
|
|
|
false
|
|
|
|
)
|
|
|
|
.0,
|
|
|
|
HashSet::from([LineNumbers])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn style_component_list_to_components_precedence_merges_previous_lists() {
|
|
|
|
assert_eq!(
|
|
|
|
StyleComponentList::to_components(
|
|
|
|
vec![
|
|
|
|
StyleComponentList::from_str("grid,header").expect("no error"),
|
|
|
|
StyleComponentList::from_str("-grid").expect("no error"),
|
|
|
|
StyleComponentList::from_str("+numbers").expect("no error"),
|
|
|
|
],
|
|
|
|
false
|
|
|
|
)
|
|
|
|
.0,
|
|
|
|
HashSet::from([HeaderFilename, LineNumbers])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|