pub mod cargo;
pub mod local;
pub mod makefile;
pub mod npm;
use crate::Result;
use std::{cmp::Ordering, collections::HashMap, path::Path};
use regex::Regex;
use serde_derive::{Deserialize, Serialize};
use tabled::Tabled;
use self::local::Local;
const CMD_NONE: &str = "unassigned";
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DSFile {
pub tasks: HashMap<String, Task>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ProviderConfig {
pub matchers: Option<Matchers>,
pub defaults: Vec<Task>,
}
pub trait TaskProvider {
fn parse(&self, path: &Path) -> Result<Vec<Task>>;
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
pub enum ProviderKind {
#[default]
#[serde(rename = "local")]
Local,
#[serde(rename = "npm")]
Npm,
#[serde(rename = "make")]
Make,
#[serde(rename = "cargo")]
Cargo,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
pub struct Task {
#[serde(default)]
pub provider: ProviderKind,
#[serde(default)]
pub task: String,
pub exec: String,
#[serde(default)]
pub emoji: String,
#[serde(default)]
pub emoji_text: String,
#[serde(default)]
pub sh: bool,
#[serde(default)]
pub details: Option<String>,
}
#[derive(Tabled)]
pub struct TableEntry {
#[tabled(rename = "")]
pub symbol: String,
pub task: String,
pub exec: String,
pub details: String,
}
type Matchers = Vec<Matcher>;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Matcher {
task: String,
#[serde(with = "serde_regex")]
expr: Regex,
}
pub fn parse(path: &Path) -> Result<(Vec<Task>, Vec<Vec<Task>>)> {
let (local_provider, other_providers): (Local, Vec<Box<dyn TaskProvider>>) = (
Local::default(),
vec![
Box::<self::cargo::Cargo>::default(),
Box::<self::npm::Npm>::default(),
Box::<self::makefile::Makefile>::default(),
],
);
let local = local_provider.parse(path)?;
let rest = other_providers
.iter()
.map(|provider| provider.parse(path))
.collect::<Result<Vec<_>>>()?;
Ok((local, rest))
}
#[must_use]
pub fn resolve<'a>(
task: Option<&'a String>,
include_unassigned: bool,
local: &'a [Task],
discovered: &'a [Vec<Task>],
) -> Vec<&'a Task> {
let mut all = Vec::new();
all.extend(local);
for group in discovered.iter() {
all.extend(
group
.iter()
.filter(|t| !local.iter().any(|loc| loc.task.eq(&t.task)))
.filter(|t| t.task != CMD_NONE || include_unassigned),
);
}
all.sort_by(|a, b| {
if a.task.is_empty() && !b.task.is_empty() {
Ordering::Greater
} else if !a.task.is_empty() && b.task.is_empty() {
Ordering::Less
} else {
a.task.cmp(&b.task)
}
});
if let Some(task) = task {
all.into_iter()
.filter(|c| c.task.eq(task))
.collect::<Vec<_>>()
} else {
all
}
}
pub fn discover(input: Option<&'_ String>, path: &Path, all: bool) -> Result<Vec<Task>> {
let (local, discovered) = parse(path)?;
let resolved = resolve(input, all, &local, &discovered[..]);
Ok(resolved.into_iter().cloned().collect())
}