mirror of
https://github.com/kdheepak/taskwarrior-tui.git
synced 2025-08-27 06:37:19 +02:00
feat: Update dependencies ✨
This commit is contained in:
parent
054b46aa56
commit
6d5d7e92f1
27 changed files with 8039 additions and 8088 deletions
|
@ -9,474 +9,439 @@ use unicode_truncate::UnicodeTruncateStr;
|
|||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
pub fn format_date_time(dt: NaiveDateTime) -> String {
|
||||
let dt = Local.from_local_datetime(&dt).unwrap();
|
||||
dt.format("%Y-%m-%d %H:%M:%S").to_string()
|
||||
let dt = Local.from_local_datetime(&dt).unwrap();
|
||||
dt.format("%Y-%m-%d %H:%M:%S").to_string()
|
||||
}
|
||||
|
||||
pub fn format_date(dt: NaiveDateTime) -> String {
|
||||
let offset = Local.offset_from_utc_datetime(&dt);
|
||||
let dt = DateTime::<Local>::from_utc(dt, offset);
|
||||
dt.format("%Y-%m-%d").to_string()
|
||||
let offset = Local.offset_from_utc_datetime(&dt);
|
||||
let dt = DateTime::<Local>::from_utc(dt, offset);
|
||||
dt.format("%Y-%m-%d").to_string()
|
||||
}
|
||||
|
||||
pub fn vague_format_date_time(from_dt: NaiveDateTime, to_dt: NaiveDateTime, with_remainder: bool) -> String {
|
||||
let to_dt = Local.from_local_datetime(&to_dt).unwrap();
|
||||
let from_dt = Local.from_local_datetime(&from_dt).unwrap();
|
||||
let mut seconds = (to_dt - from_dt).num_seconds();
|
||||
let minus = if seconds < 0 {
|
||||
seconds *= -1;
|
||||
"-"
|
||||
let to_dt = Local.from_local_datetime(&to_dt).unwrap();
|
||||
let from_dt = Local.from_local_datetime(&from_dt).unwrap();
|
||||
let mut seconds = (to_dt - from_dt).num_seconds();
|
||||
let minus = if seconds < 0 {
|
||||
seconds *= -1;
|
||||
"-"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let year = 60 * 60 * 24 * 365;
|
||||
let month = 60 * 60 * 24 * 30;
|
||||
let week = 60 * 60 * 24 * 7;
|
||||
let day = 60 * 60 * 24;
|
||||
let hour = 60 * 60;
|
||||
let minute = 60;
|
||||
|
||||
if seconds >= 60 * 60 * 24 * 365 {
|
||||
return if with_remainder {
|
||||
format!("{}{}y{}mo", minus, seconds / year, (seconds - year * (seconds / year)) / month)
|
||||
} else {
|
||||
""
|
||||
format!("{}{}y", minus, seconds / year)
|
||||
};
|
||||
|
||||
let year = 60 * 60 * 24 * 365;
|
||||
let month = 60 * 60 * 24 * 30;
|
||||
let week = 60 * 60 * 24 * 7;
|
||||
let day = 60 * 60 * 24;
|
||||
let hour = 60 * 60;
|
||||
let minute = 60;
|
||||
|
||||
if seconds >= 60 * 60 * 24 * 365 {
|
||||
return if with_remainder {
|
||||
format!(
|
||||
"{}{}y{}mo",
|
||||
minus,
|
||||
seconds / year,
|
||||
(seconds - year * (seconds / year)) / month
|
||||
)
|
||||
} else {
|
||||
format!("{}{}y", minus, seconds / year)
|
||||
};
|
||||
} else if seconds >= 60 * 60 * 24 * 90 {
|
||||
return if with_remainder {
|
||||
format!(
|
||||
"{}{}mo{}w",
|
||||
minus,
|
||||
seconds / month,
|
||||
(seconds - month * (seconds / month)) / week
|
||||
)
|
||||
} else {
|
||||
format!("{}{}mo", minus, seconds / month)
|
||||
};
|
||||
} else if seconds >= 60 * 60 * 24 * 14 {
|
||||
return if with_remainder {
|
||||
format!(
|
||||
"{}{}w{}d",
|
||||
minus,
|
||||
seconds / week,
|
||||
(seconds - week * (seconds / week)) / day
|
||||
)
|
||||
} else {
|
||||
format!("{}{}w", minus, seconds / week)
|
||||
};
|
||||
} else if seconds >= 60 * 60 * 24 {
|
||||
return if with_remainder {
|
||||
format!(
|
||||
"{}{}d{}h",
|
||||
minus,
|
||||
seconds / day,
|
||||
(seconds - day * (seconds / day)) / hour
|
||||
)
|
||||
} else {
|
||||
format!("{}{}d", minus, seconds / day)
|
||||
};
|
||||
} else if seconds >= 60 * 60 {
|
||||
return if with_remainder {
|
||||
format!(
|
||||
"{}{}h{}min",
|
||||
minus,
|
||||
seconds / hour,
|
||||
(seconds - hour * (seconds / hour)) / minute
|
||||
)
|
||||
} else {
|
||||
format!("{}{}h", minus, seconds / hour)
|
||||
};
|
||||
} else if seconds >= 60 {
|
||||
return if with_remainder {
|
||||
format!(
|
||||
"{}{}min{}s",
|
||||
minus,
|
||||
seconds / minute,
|
||||
(seconds - minute * (seconds / minute))
|
||||
)
|
||||
} else {
|
||||
format!("{}{}min", minus, seconds / minute)
|
||||
};
|
||||
}
|
||||
format!("{}{}s", minus, seconds)
|
||||
} else if seconds >= 60 * 60 * 24 * 90 {
|
||||
return if with_remainder {
|
||||
format!("{}{}mo{}w", minus, seconds / month, (seconds - month * (seconds / month)) / week)
|
||||
} else {
|
||||
format!("{}{}mo", minus, seconds / month)
|
||||
};
|
||||
} else if seconds >= 60 * 60 * 24 * 14 {
|
||||
return if with_remainder {
|
||||
format!("{}{}w{}d", minus, seconds / week, (seconds - week * (seconds / week)) / day)
|
||||
} else {
|
||||
format!("{}{}w", minus, seconds / week)
|
||||
};
|
||||
} else if seconds >= 60 * 60 * 24 {
|
||||
return if with_remainder {
|
||||
format!("{}{}d{}h", minus, seconds / day, (seconds - day * (seconds / day)) / hour)
|
||||
} else {
|
||||
format!("{}{}d", minus, seconds / day)
|
||||
};
|
||||
} else if seconds >= 60 * 60 {
|
||||
return if with_remainder {
|
||||
format!("{}{}h{}min", minus, seconds / hour, (seconds - hour * (seconds / hour)) / minute)
|
||||
} else {
|
||||
format!("{}{}h", minus, seconds / hour)
|
||||
};
|
||||
} else if seconds >= 60 {
|
||||
return if with_remainder {
|
||||
format!("{}{}min{}s", minus, seconds / minute, (seconds - minute * (seconds / minute)))
|
||||
} else {
|
||||
format!("{}{}min", minus, seconds / minute)
|
||||
};
|
||||
}
|
||||
format!("{}{}s", minus, seconds)
|
||||
}
|
||||
|
||||
pub struct TaskReportTable {
|
||||
pub labels: Vec<String>,
|
||||
pub columns: Vec<String>,
|
||||
pub tasks: Vec<Vec<String>>,
|
||||
pub virtual_tags: Vec<String>,
|
||||
pub description_width: usize,
|
||||
pub date_time_vague_precise: bool,
|
||||
pub labels: Vec<String>,
|
||||
pub columns: Vec<String>,
|
||||
pub tasks: Vec<Vec<String>>,
|
||||
pub virtual_tags: Vec<String>,
|
||||
pub description_width: usize,
|
||||
pub date_time_vague_precise: bool,
|
||||
}
|
||||
|
||||
impl TaskReportTable {
|
||||
pub fn new(data: &str, report: &str) -> Result<Self> {
|
||||
let virtual_tags = vec![
|
||||
"PROJECT",
|
||||
"BLOCKED",
|
||||
"UNBLOCKED",
|
||||
"BLOCKING",
|
||||
"DUE",
|
||||
"DUETODAY",
|
||||
"TODAY",
|
||||
"OVERDUE",
|
||||
"WEEK",
|
||||
"MONTH",
|
||||
"QUARTER",
|
||||
"YEAR",
|
||||
"ACTIVE",
|
||||
"SCHEDULED",
|
||||
"PARENT",
|
||||
"CHILD",
|
||||
"UNTIL",
|
||||
"WAITING",
|
||||
"ANNOTATED",
|
||||
"READY",
|
||||
"YESTERDAY",
|
||||
"TOMORROW",
|
||||
"TAGGED",
|
||||
"PENDING",
|
||||
"COMPLETED",
|
||||
"DELETED",
|
||||
"UDA",
|
||||
"ORPHAN",
|
||||
"PRIORITY",
|
||||
"PROJECT",
|
||||
"LATEST",
|
||||
"RECURRING",
|
||||
"INSTANCE",
|
||||
"TEMPLATE",
|
||||
];
|
||||
let mut task_report_table = Self {
|
||||
labels: vec![],
|
||||
columns: vec![],
|
||||
tasks: vec![vec![]],
|
||||
virtual_tags: virtual_tags.iter().map(ToString::to_string).collect::<Vec<_>>(),
|
||||
description_width: 100,
|
||||
date_time_vague_precise: false,
|
||||
};
|
||||
task_report_table.export_headers(Some(data), report)?;
|
||||
Ok(task_report_table)
|
||||
pub fn new(data: &str, report: &str) -> Result<Self> {
|
||||
let virtual_tags = vec![
|
||||
"PROJECT",
|
||||
"BLOCKED",
|
||||
"UNBLOCKED",
|
||||
"BLOCKING",
|
||||
"DUE",
|
||||
"DUETODAY",
|
||||
"TODAY",
|
||||
"OVERDUE",
|
||||
"WEEK",
|
||||
"MONTH",
|
||||
"QUARTER",
|
||||
"YEAR",
|
||||
"ACTIVE",
|
||||
"SCHEDULED",
|
||||
"PARENT",
|
||||
"CHILD",
|
||||
"UNTIL",
|
||||
"WAITING",
|
||||
"ANNOTATED",
|
||||
"READY",
|
||||
"YESTERDAY",
|
||||
"TOMORROW",
|
||||
"TAGGED",
|
||||
"PENDING",
|
||||
"COMPLETED",
|
||||
"DELETED",
|
||||
"UDA",
|
||||
"ORPHAN",
|
||||
"PRIORITY",
|
||||
"PROJECT",
|
||||
"LATEST",
|
||||
"RECURRING",
|
||||
"INSTANCE",
|
||||
"TEMPLATE",
|
||||
];
|
||||
let mut task_report_table = Self {
|
||||
labels: vec![],
|
||||
columns: vec![],
|
||||
tasks: vec![vec![]],
|
||||
virtual_tags: virtual_tags.iter().map(ToString::to_string).collect::<Vec<_>>(),
|
||||
description_width: 100,
|
||||
date_time_vague_precise: false,
|
||||
};
|
||||
task_report_table.export_headers(Some(data), report)?;
|
||||
Ok(task_report_table)
|
||||
}
|
||||
|
||||
pub fn export_headers(&mut self, data: Option<&str>, report: &str) -> Result<()> {
|
||||
self.columns = vec![];
|
||||
self.labels = vec![];
|
||||
|
||||
let data = if let Some(s) = data {
|
||||
s.to_string()
|
||||
} else {
|
||||
let output = Command::new("task")
|
||||
.arg("show")
|
||||
.arg("rc.defaultwidth=0")
|
||||
.arg(format!("report.{}.columns", report))
|
||||
.output()?;
|
||||
String::from_utf8_lossy(&output.stdout).into_owned()
|
||||
};
|
||||
|
||||
for line in data.split('\n') {
|
||||
if line.starts_with(format!("report.{}.columns", report).as_str()) {
|
||||
let column_names = line.split_once(' ').unwrap().1;
|
||||
for column in column_names.split(',') {
|
||||
self.columns.push(column.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn export_headers(&mut self, data: Option<&str>, report: &str) -> Result<()> {
|
||||
self.columns = vec![];
|
||||
self.labels = vec![];
|
||||
let output = Command::new("task")
|
||||
.arg("show")
|
||||
.arg("rc.defaultwidth=0")
|
||||
.arg(format!("report.{}.labels", report))
|
||||
.output()?;
|
||||
let data = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
let data = if let Some(s) = data {
|
||||
s.to_string()
|
||||
for line in data.split('\n') {
|
||||
if line.starts_with(format!("report.{}.labels", report).as_str()) {
|
||||
let label_names = line.split_once(' ').unwrap().1;
|
||||
for label in label_names.split(',') {
|
||||
self.labels.push(label.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.labels.is_empty() {
|
||||
for label in &self.columns {
|
||||
let label = label.split('.').collect::<Vec<&str>>()[0];
|
||||
let label = if label == "id" { "ID" } else { label };
|
||||
let mut c = label.chars();
|
||||
let label = match c.next() {
|
||||
None => String::new(),
|
||||
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
|
||||
};
|
||||
if !label.is_empty() {
|
||||
self.labels.push(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
let num_labels = self.labels.len();
|
||||
let num_columns = self.columns.len();
|
||||
assert!(num_labels == num_columns, "Must have the same number of labels (currently {}) and columns (currently {}). Compare their values as shown by \"task show report.{}.\" and fix your taskwarrior config.", num_labels, num_columns, report);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_table(&mut self, tasks: &[Task]) {
|
||||
self.tasks = vec![];
|
||||
|
||||
// get all tasks as their string representation
|
||||
for task in tasks {
|
||||
if self.columns.is_empty() {
|
||||
break;
|
||||
}
|
||||
let mut item = vec![];
|
||||
for name in &self.columns {
|
||||
let s = self.get_string_attribute(name, task, tasks);
|
||||
item.push(s);
|
||||
}
|
||||
self.tasks.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simplify_table(&mut self) -> (Vec<Vec<String>>, Vec<String>) {
|
||||
// find which columns are empty
|
||||
if self.tasks.is_empty() {
|
||||
return (vec![], vec![]);
|
||||
}
|
||||
|
||||
let mut null_columns = vec![0; self.tasks[0].len()];
|
||||
|
||||
for task in &self.tasks {
|
||||
for (i, s) in task.iter().enumerate() {
|
||||
null_columns[i] += s.len();
|
||||
}
|
||||
}
|
||||
|
||||
// filter out columns where everything is empty
|
||||
let mut tasks = vec![];
|
||||
for task in &self.tasks {
|
||||
let t = task.clone();
|
||||
let t: Vec<String> = t
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(i, _)| null_columns[i] != 0)
|
||||
.map(|(_, e)| e.clone())
|
||||
.collect();
|
||||
tasks.push(t);
|
||||
}
|
||||
|
||||
// filter out header where all columns are empty
|
||||
let headers: Vec<String> = self
|
||||
.labels
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(i, _)| null_columns[i] != 0)
|
||||
.map(|(_, e)| e.clone())
|
||||
.collect();
|
||||
|
||||
(tasks, headers)
|
||||
}
|
||||
|
||||
pub fn get_string_attribute(&self, attribute: &str, task: &Task, tasks: &[Task]) -> String {
|
||||
match attribute {
|
||||
"id" => task.id().unwrap_or_default().to_string(),
|
||||
"scheduled.relative" => match task.scheduled() {
|
||||
Some(v) => vague_format_date_time(
|
||||
Local::now().naive_utc(),
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"due.relative" => match task.due() {
|
||||
Some(v) => vague_format_date_time(
|
||||
Local::now().naive_utc(),
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"due" => match task.due() {
|
||||
Some(v) => format_date(NaiveDateTime::new(v.date(), v.time())),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"until.remaining" => match task.until() {
|
||||
Some(v) => vague_format_date_time(
|
||||
Local::now().naive_utc(),
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"until" => match task.until() {
|
||||
Some(v) => format_date(NaiveDateTime::new(v.date(), v.time())),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"entry.age" => vague_format_date_time(
|
||||
NaiveDateTime::new(task.entry().date(), task.entry().time()),
|
||||
Local::now().naive_utc(),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
"entry" => format_date(NaiveDateTime::new(task.entry().date(), task.entry().time())),
|
||||
"start.age" => match task.start() {
|
||||
Some(v) => vague_format_date_time(
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
Local::now().naive_utc(),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"start" => match task.start() {
|
||||
Some(v) => format_date(NaiveDateTime::new(v.date(), v.time())),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"end.age" => match task.end() {
|
||||
Some(v) => vague_format_date_time(
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
Local::now().naive_utc(),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"end" => match task.end() {
|
||||
Some(v) => format_date(NaiveDateTime::new(v.date(), v.time())),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"status.short" => task.status().to_string().chars().next().unwrap().to_string(),
|
||||
"status" => task.status().to_string(),
|
||||
"priority" => match task.priority() {
|
||||
Some(p) => p.clone(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"project" => match task.project() {
|
||||
Some(p) => p.to_string(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"depends.count" => match task.depends() {
|
||||
Some(v) => {
|
||||
if v.is_empty() {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("{}", v.len())
|
||||
}
|
||||
}
|
||||
None => "".to_string(),
|
||||
},
|
||||
"depends" => match task.depends() {
|
||||
Some(v) => {
|
||||
if v.is_empty() {
|
||||
"".to_string()
|
||||
} else {
|
||||
let mut dt = vec![];
|
||||
for u in v {
|
||||
if let Some(t) = tasks.iter().find(|t| t.uuid() == u) {
|
||||
dt.push(t.id().unwrap());
|
||||
}
|
||||
}
|
||||
join(dt.iter().map(ToString::to_string), " ")
|
||||
}
|
||||
}
|
||||
None => "".to_string(),
|
||||
},
|
||||
"tags.count" => match task.tags() {
|
||||
Some(v) => {
|
||||
let t = v.iter().filter(|t| !self.virtual_tags.contains(t)).count();
|
||||
if t == 0 {
|
||||
"".to_string()
|
||||
} else {
|
||||
t.to_string()
|
||||
}
|
||||
}
|
||||
None => "".to_string(),
|
||||
},
|
||||
"tags" => match task.tags() {
|
||||
Some(v) => v.iter().filter(|t| !self.virtual_tags.contains(t)).cloned().collect::<Vec<_>>().join(","),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"recur" => match task.recur() {
|
||||
Some(v) => v.clone(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"wait" => match task.wait() {
|
||||
Some(v) => vague_format_date_time(
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
Local::now().naive_utc(),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"wait.remaining" => match task.wait() {
|
||||
Some(v) => vague_format_date_time(
|
||||
Local::now().naive_utc(),
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"description.count" => {
|
||||
let c = if let Some(a) = task.annotations() {
|
||||
format!("[{}]", a.len())
|
||||
} else {
|
||||
let output = Command::new("task")
|
||||
.arg("show")
|
||||
.arg("rc.defaultwidth=0")
|
||||
.arg(format!("report.{}.columns", report))
|
||||
.output()?;
|
||||
String::from_utf8_lossy(&output.stdout).into_owned()
|
||||
Default::default()
|
||||
};
|
||||
|
||||
for line in data.split('\n') {
|
||||
if line.starts_with(format!("report.{}.columns", report).as_str()) {
|
||||
let column_names = line.split_once(' ').unwrap().1;
|
||||
for column in column_names.split(',') {
|
||||
self.columns.push(column.to_string());
|
||||
}
|
||||
}
|
||||
format!("{} {}", task.description(), c)
|
||||
}
|
||||
"description.truncated_count" => {
|
||||
let c = if let Some(a) = task.annotations() {
|
||||
format!("[{}]", a.len())
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let d = task.description().to_string();
|
||||
let mut available_width = self.description_width;
|
||||
if self.description_width >= c.len() {
|
||||
available_width = self.description_width - c.len();
|
||||
}
|
||||
|
||||
let output = Command::new("task")
|
||||
.arg("show")
|
||||
.arg("rc.defaultwidth=0")
|
||||
.arg(format!("report.{}.labels", report))
|
||||
.output()?;
|
||||
let data = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
for line in data.split('\n') {
|
||||
if line.starts_with(format!("report.{}.labels", report).as_str()) {
|
||||
let label_names = line.split_once(' ').unwrap().1;
|
||||
for label in label_names.split(',') {
|
||||
self.labels.push(label.to_string());
|
||||
}
|
||||
}
|
||||
let (d, _) = d.unicode_truncate(available_width);
|
||||
let mut d = d.to_string();
|
||||
if d != *task.description() {
|
||||
d = format!("{}\u{2026}", d);
|
||||
}
|
||||
|
||||
if self.labels.is_empty() {
|
||||
for label in &self.columns {
|
||||
let label = label.split('.').collect::<Vec<&str>>()[0];
|
||||
let label = if label == "id" { "ID" } else { label };
|
||||
let mut c = label.chars();
|
||||
let label = match c.next() {
|
||||
None => String::new(),
|
||||
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
|
||||
};
|
||||
if !label.is_empty() {
|
||||
self.labels.push(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
let num_labels = self.labels.len();
|
||||
let num_columns = self.columns.len();
|
||||
assert!(num_labels == num_columns, "Must have the same number of labels (currently {}) and columns (currently {}). Compare their values as shown by \"task show report.{}.\" and fix your taskwarrior config.", num_labels, num_columns, report);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_table(&mut self, tasks: &[Task]) {
|
||||
self.tasks = vec![];
|
||||
|
||||
// get all tasks as their string representation
|
||||
for task in tasks {
|
||||
if self.columns.is_empty() {
|
||||
break;
|
||||
}
|
||||
let mut item = vec![];
|
||||
for name in &self.columns {
|
||||
let s = self.get_string_attribute(name, task, tasks);
|
||||
item.push(s);
|
||||
}
|
||||
self.tasks.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simplify_table(&mut self) -> (Vec<Vec<String>>, Vec<String>) {
|
||||
// find which columns are empty
|
||||
if self.tasks.is_empty() {
|
||||
return (vec![], vec![]);
|
||||
}
|
||||
|
||||
let mut null_columns = vec![0; self.tasks[0].len()];
|
||||
|
||||
for task in &self.tasks {
|
||||
for (i, s) in task.iter().enumerate() {
|
||||
null_columns[i] += s.len();
|
||||
}
|
||||
}
|
||||
|
||||
// filter out columns where everything is empty
|
||||
let mut tasks = vec![];
|
||||
for task in &self.tasks {
|
||||
let t = task.clone();
|
||||
let t: Vec<String> = t
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(i, _)| null_columns[i] != 0)
|
||||
.map(|(_, e)| e.clone())
|
||||
.collect();
|
||||
tasks.push(t);
|
||||
}
|
||||
|
||||
// filter out header where all columns are empty
|
||||
let headers: Vec<String> = self
|
||||
.labels
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(i, _)| null_columns[i] != 0)
|
||||
.map(|(_, e)| e.clone())
|
||||
.collect();
|
||||
|
||||
(tasks, headers)
|
||||
}
|
||||
|
||||
pub fn get_string_attribute(&self, attribute: &str, task: &Task, tasks: &[Task]) -> String {
|
||||
match attribute {
|
||||
"id" => task.id().unwrap_or_default().to_string(),
|
||||
"scheduled.relative" => match task.scheduled() {
|
||||
Some(v) => vague_format_date_time(
|
||||
Local::now().naive_utc(),
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"due.relative" => match task.due() {
|
||||
Some(v) => vague_format_date_time(
|
||||
Local::now().naive_utc(),
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"due" => match task.due() {
|
||||
Some(v) => format_date(NaiveDateTime::new(v.date(), v.time())),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"until.remaining" => match task.until() {
|
||||
Some(v) => vague_format_date_time(
|
||||
Local::now().naive_utc(),
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"until" => match task.until() {
|
||||
Some(v) => format_date(NaiveDateTime::new(v.date(), v.time())),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"entry.age" => vague_format_date_time(
|
||||
NaiveDateTime::new(task.entry().date(), task.entry().time()),
|
||||
Local::now().naive_utc(),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
"entry" => format_date(NaiveDateTime::new(task.entry().date(), task.entry().time())),
|
||||
"start.age" => match task.start() {
|
||||
Some(v) => vague_format_date_time(
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
Local::now().naive_utc(),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"start" => match task.start() {
|
||||
Some(v) => format_date(NaiveDateTime::new(v.date(), v.time())),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"end.age" => match task.end() {
|
||||
Some(v) => vague_format_date_time(
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
Local::now().naive_utc(),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"end" => match task.end() {
|
||||
Some(v) => format_date(NaiveDateTime::new(v.date(), v.time())),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"status.short" => task.status().to_string().chars().next().unwrap().to_string(),
|
||||
"status" => task.status().to_string(),
|
||||
"priority" => match task.priority() {
|
||||
Some(p) => p.clone(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"project" => match task.project() {
|
||||
Some(p) => p.to_string(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"depends.count" => match task.depends() {
|
||||
Some(v) => {
|
||||
if v.is_empty() {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("{}", v.len())
|
||||
}
|
||||
}
|
||||
None => "".to_string(),
|
||||
},
|
||||
"depends" => match task.depends() {
|
||||
Some(v) => {
|
||||
if v.is_empty() {
|
||||
"".to_string()
|
||||
} else {
|
||||
let mut dt = vec![];
|
||||
for u in v {
|
||||
if let Some(t) = tasks.iter().find(|t| t.uuid() == u) {
|
||||
dt.push(t.id().unwrap());
|
||||
}
|
||||
}
|
||||
join(dt.iter().map(ToString::to_string), " ")
|
||||
}
|
||||
}
|
||||
None => "".to_string(),
|
||||
},
|
||||
"tags.count" => match task.tags() {
|
||||
Some(v) => {
|
||||
let t = v.iter().filter(|t| !self.virtual_tags.contains(t)).count();
|
||||
if t == 0 {
|
||||
"".to_string()
|
||||
} else {
|
||||
t.to_string()
|
||||
}
|
||||
}
|
||||
None => "".to_string(),
|
||||
},
|
||||
"tags" => match task.tags() {
|
||||
Some(v) => v
|
||||
.iter()
|
||||
.filter(|t| !self.virtual_tags.contains(t))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"recur" => match task.recur() {
|
||||
Some(v) => v.clone(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"wait" => match task.wait() {
|
||||
Some(v) => vague_format_date_time(
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
Local::now().naive_utc(),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"wait.remaining" => match task.wait() {
|
||||
Some(v) => vague_format_date_time(
|
||||
Local::now().naive_utc(),
|
||||
NaiveDateTime::new(v.date(), v.time()),
|
||||
self.date_time_vague_precise,
|
||||
),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"description.count" => {
|
||||
let c = if let Some(a) = task.annotations() {
|
||||
format!("[{}]", a.len())
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
format!("{} {}", task.description(), c)
|
||||
}
|
||||
"description.truncated_count" => {
|
||||
let c = if let Some(a) = task.annotations() {
|
||||
format!("[{}]", a.len())
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let d = task.description().to_string();
|
||||
let mut available_width = self.description_width;
|
||||
if self.description_width >= c.len() {
|
||||
available_width = self.description_width - c.len();
|
||||
}
|
||||
let (d, _) = d.unicode_truncate(available_width);
|
||||
let mut d = d.to_string();
|
||||
if d != *task.description() {
|
||||
d = format!("{}\u{2026}", d);
|
||||
}
|
||||
format!("{}{}", d, c)
|
||||
}
|
||||
"description.truncated" => {
|
||||
let d = task.description().to_string();
|
||||
let available_width = self.description_width;
|
||||
let (d, _) = d.unicode_truncate(available_width);
|
||||
let mut d = d.to_string();
|
||||
if d != *task.description() {
|
||||
d = format!("{}\u{2026}", d);
|
||||
}
|
||||
d
|
||||
}
|
||||
"description.desc" | "description" => task.description().to_string(),
|
||||
"urgency" => match &task.urgency() {
|
||||
Some(f) => format!("{:.2}", *f),
|
||||
None => "0.00".to_string(),
|
||||
},
|
||||
s => {
|
||||
let u = &task.uda();
|
||||
let v = u.get(s);
|
||||
if v.is_none() {
|
||||
return "".to_string();
|
||||
}
|
||||
match v.unwrap() {
|
||||
UDAValue::Str(s) => s.to_string(),
|
||||
UDAValue::F64(f) => f.to_string(),
|
||||
UDAValue::U64(u) => u.to_string(),
|
||||
}
|
||||
}
|
||||
format!("{}{}", d, c)
|
||||
}
|
||||
"description.truncated" => {
|
||||
let d = task.description().to_string();
|
||||
let available_width = self.description_width;
|
||||
let (d, _) = d.unicode_truncate(available_width);
|
||||
let mut d = d.to_string();
|
||||
if d != *task.description() {
|
||||
d = format!("{}\u{2026}", d);
|
||||
}
|
||||
d
|
||||
}
|
||||
"description.desc" | "description" => task.description().to_string(),
|
||||
"urgency" => match &task.urgency() {
|
||||
Some(f) => format!("{:.2}", *f),
|
||||
None => "0.00".to_string(),
|
||||
},
|
||||
s => {
|
||||
let u = &task.uda();
|
||||
let v = u.get(s);
|
||||
if v.is_none() {
|
||||
return "".to_string();
|
||||
}
|
||||
match v.unwrap() {
|
||||
UDAValue::Str(s) => s.to_string(),
|
||||
UDAValue::F64(f) => f.to_string(),
|
||||
UDAValue::U64(u) => u.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue