use clap::{Parser, Subcommand};
use std::path::Path;
use walkdir::WalkDir;
use crate::{
config::Config,
errors::Error,
filters, input,
lists::{self, Flag},
projects,
tasks::SortOrder,
};
#[derive(Subcommand, Debug, Clone)]
pub enum ListCommands {
#[clap(alias = "v")]
View(View),
#[clap(alias = "c")]
Process(Process),
#[clap(alias = "z")]
Prioritize(Prioritize),
#[clap(alias = "t")]
Timebox(Timebox),
#[clap(alias = "l")]
Label(Label),
#[clap(alias = "s")]
Schedule(Schedule),
#[clap(alias = "d")]
Deadline(Deadline),
#[clap(alias = "i")]
Import(Import),
}
#[derive(Parser, Debug, Clone)]
pub struct View {
#[arg(short, long)]
project: Option<String>,
#[arg(short, long)]
filter: Option<String>,
#[arg(short = 't', long, default_value_t = SortOrder::Datetime)]
sort: SortOrder,
}
#[derive(Parser, Debug, Clone)]
pub struct Process {
#[arg(short, long)]
project: Option<String>,
#[arg(short, long)]
filter: Option<String>,
#[arg(short = 't', long, default_value_t = SortOrder::Value)]
sort: SortOrder,
}
#[derive(Parser, Debug, Clone)]
pub struct Timebox {
#[arg(short, long)]
project: Option<String>,
#[arg(short, long)]
filter: Option<String>,
#[arg(short = 't', long, default_value_t = SortOrder::Value)]
sort: SortOrder,
}
#[derive(Parser, Debug, Clone)]
pub struct Prioritize {
#[arg(short, long)]
project: Option<String>,
#[arg(short, long)]
filter: Option<String>,
#[arg(short = 't', long, default_value_t = SortOrder::Value)]
sort: SortOrder,
}
#[derive(Parser, Debug, Clone)]
pub struct Label {
#[arg(short, long)]
filter: Option<String>,
#[arg(short, long)]
project: Option<String>,
#[arg(short, long)]
label: Vec<String>,
#[arg(short = 't', long, default_value_t = SortOrder::Value)]
sort: SortOrder,
}
#[derive(Parser, Debug, Clone)]
pub struct Schedule {
#[arg(short, long)]
project: Option<String>,
#[arg(short, long)]
filter: Option<String>,
#[arg(short, long, default_value_t = false)]
skip_recurring: bool,
#[arg(short, long, default_value_t = false)]
overdue: bool,
#[arg(short = 't', long, default_value_t = SortOrder::Value)]
sort: SortOrder,
}
#[derive(Parser, Debug, Clone)]
pub struct Deadline {
#[arg(short, long)]
project: Option<String>,
#[arg(short, long)]
filter: Option<String>,
#[arg(short = 't', long, default_value_t = SortOrder::Value)]
sort: SortOrder,
}
#[derive(Parser, Debug, Clone)]
pub struct Import {
#[arg(short, long)]
path: Option<String>,
}
pub async fn view(config: &mut Config, args: &View) -> Result<String, Error> {
let View {
project,
filter,
sort,
} = args;
let flag =
super::fetch_project_or_filter(project.as_deref(), filter.as_deref(), config).await?;
lists::view(config, flag, sort).await
}
pub async fn label(config: Config, args: &Label) -> Result<String, Error> {
let Label {
filter,
project,
label: labels,
sort,
} = args;
let labels = super::maybe_fetch_labels(&config, labels).await?;
let flag =
super::fetch_project_or_filter(project.as_deref(), filter.as_deref(), &config).await?;
lists::label(&config, flag, &labels, sort).await
}
pub async fn process(config: Config, args: &Process) -> Result<String, Error> {
let Process {
project,
filter,
sort,
} = args;
let flag =
super::fetch_project_or_filter(project.as_deref(), filter.as_deref(), &config).await?;
lists::process(&config, flag, sort).await
}
pub async fn timebox(config: Config, args: &Timebox) -> Result<String, Error> {
let Timebox {
project,
filter,
sort,
} = args;
let flag =
super::fetch_project_or_filter(project.as_deref(), filter.as_deref(), &config).await?;
lists::timebox(&config, flag, sort).await
}
pub async fn prioritize(config: Config, args: &Prioritize) -> Result<String, Error> {
let Prioritize {
project,
filter,
sort,
} = args;
let flag =
super::fetch_project_or_filter(project.as_deref(), filter.as_deref(), &config).await?;
lists::prioritize(&config, flag, sort).await
}
pub async fn import(config: Config, args: &Import) -> Result<String, Error> {
let Import { path } = args;
let path = super::fetch_string(path.as_deref(), &config, input::PATH)?;
let file_path = select_file(path, &config)?;
lists::import(&config, &file_path).await
}
fn select_file(path_or_file: String, config: &Config) -> Result<String, Error> {
let path = Path::new(&path_or_file);
if Path::is_dir(path) {
let mut options = WalkDir::new(path_or_file)
.into_iter()
.filter_map(|e| e.ok())
.filter(is_md_file)
.map(|e| {
e.path()
.to_str()
.expect("Could not make str out of DirEntry")
.to_string()
})
.collect::<Vec<String>>();
options.sort();
options.dedup();
let path = input::select("Select file to process", options, config.mock_select)?;
Ok(path)
} else if Path::is_file(path) {
Ok(path_or_file)
} else {
Err(Error {
source: "select_file".to_string(),
message: format!("{path_or_file} is neither a file nor a directory"),
})
}
}
fn is_md_file(entry: &walkdir::DirEntry) -> bool {
entry
.file_name()
.to_str()
.unwrap_or_default()
.ends_with(".md")
}
pub async fn schedule(config: Config, args: &Schedule) -> Result<String, Error> {
let Schedule {
project,
filter,
skip_recurring,
overdue,
sort,
} = args;
match super::fetch_project_or_filter(project.as_deref(), filter.as_deref(), &config).await? {
Flag::Filter(filter) => filters::schedule(&config, &filter, sort).await,
Flag::Project(project) => {
let task_filter = if *overdue {
projects::TaskFilter::Overdue
} else {
projects::TaskFilter::Unscheduled
};
projects::schedule(&config, &project, task_filter, *skip_recurring, sort).await
}
}
}
pub async fn deadline(config: Config, args: &Deadline) -> Result<String, Error> {
let Deadline {
project,
filter,
sort,
} = args;
match super::fetch_project_or_filter(project.as_deref(), filter.as_deref(), &config).await? {
Flag::Filter(filter) => filters::deadline(&config, &filter, sort).await,
Flag::Project(project) => projects::deadline(&config, &project, sort).await,
}
}