Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
kdheepak committed Sep 27, 2023
1 parent f6c2033 commit 8302c88
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 6 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ log = "0.4.20"
path-clean = "1.0.1"
pretty_assertions = "1.4.0"
rand = "0.8.5"
ratatui = { version = "0.23.0", features = ["all-widgets"] }
ratatui = { version = "0.23.0", features = ["serde", "all-widgets"] }
regex = "1.9.5"
rustyline = { version = "12.0.0", features = ["with-file-history", "derive"] }
serde = { version = "1.0.188", features = ["derive"] }
Expand Down
3 changes: 2 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ pub struct App {
impl App {
pub fn new(tick_rate: f64, frame_rate: f64, report: &str) -> Result<Self> {
let app = TaskReport::new().report(report.into());
let config = Config::new().unwrap();
let mut config = Config::new()?;
config.taskwarrior_config()?;
let mode = Mode::TaskReport;
Ok(Self {
tick_rate,
Expand Down
40 changes: 37 additions & 3 deletions src/components/task_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,38 @@ impl TaskReport {
}
widths
}

fn style_for_task(&self, task: &Task) -> Style {
let virtual_tag_names_in_precedence = &self.config.taskwarrior.rule_precedence_color;

let mut style = Style::default();

for tag_name in virtual_tag_names_in_precedence.iter().rev() {
if tag_name == "uda." || tag_name == "priority" {
if let Some(p) = task.priority() {
let s = self.config.taskwarrior.color.uda_priority.get(p).copied().unwrap_or_default();
style = style.patch(s);
}
} else if tag_name == "tag." {
if let Some(tags) = task.tags() {
for t in tags {
let s = self.config.taskwarrior.color.tag.get(t).copied().unwrap_or_default();
style = style.patch(s);
}
}
} else if tag_name == "project." {
if let Some(p) = task.project() {
let s = self.config.taskwarrior.color.project.get(p).copied().unwrap_or_default();
style = style.patch(s);
}
} else if task.tags().unwrap_or(&vec![]).contains(&tag_name.to_string().replace('.', "").to_uppercase()) {
let s = self.config.taskwarrior.color.tag.get(tag_name).copied().unwrap_or_default();
style = style.patch(s);
}
}

style
}
}

impl Component for TaskReport {
Expand Down Expand Up @@ -539,15 +571,17 @@ impl Component for TaskReport {
}
let widths = self.calculate_widths(rect.width);
let constraints: Vec<Constraint> = widths.iter().map(|i| Constraint::Min(*i as u16)).collect();
let rows = self.rows.iter().map(|row| Row::new(row.clone()));
let rows = self.rows.iter().enumerate().map(|(i, row)| {
let style = self.style_for_task(&self.tasks[i]);
Row::new(row.clone())
});
let table = Table::new(rows)
.header(Row::new(self.labels.clone()))
.header(Row::new(self.labels.iter().map(|l| Cell::from(l.clone()).underlined())))
.widths(&constraints)
.highlight_symbol(&self.config.task_report.selection_indicator)
.highlight_spacing(HighlightSpacing::Always)
.column_spacing(column_spacing);
f.render_stateful_widget(table, rect, &mut self.state);

Ok(())
}
}
Expand Down
210 changes: 209 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,75 @@ use crate::{action::Action, app::Mode};

const CONFIG: &str = include_str!("../.config/config.json5");

#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct TaskwarriorColorConfig {
pub active: Style,
pub alternate: Style,
pub blocked: Style,
pub blocking: Style,
pub burndown_done: Style,
pub burndown_pending: Style,
pub burndown_started: Style,
pub calendar_due: Style,
pub calendar_due_today: Style,
pub calendar_holiday: Style,
pub calendar_overdue: Style,
pub calendar_scheduled: Style,
pub calendar_today: Style,
pub calendar_weekend: Style,
pub calendar_weeknumber: Style,
pub completed: Style,
pub debug: Style,
pub deleted: Style,
pub due: Style,
pub due_today: Style,
pub error: Style,
pub footnote: Style,
pub header: Style,
pub history_add: Style,
pub history_delete: Style,
pub history_done: Style,
pub label: Style,
pub label_sort: Style,
pub overdue: Style,
pub project_basics: Style,
pub project_none: Style,
pub project_wth: Style,
pub recurring: Style,
pub scheduled: Style,
pub summary_background: Style,
pub summary_bar: Style,
pub sync_added: Style,
pub sync_changed: Style,
pub sync_rejected: Style,
pub tag_next: Style,
pub tag_none: Style,
pub tagged: Style,
pub uda_priority: HashMap<String, Style>,
pub tag: HashMap<String, Style>,
pub project: HashMap<String, Style>,
pub undo_after: Style,
pub undo_before: Style,
pub until: Style,
pub warning: Style,
}

#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct TaskwarriorConfig {
#[serde(default)]
pub rule_precedence_color: Vec<String>,
#[serde(default)]
pub uda_priority_values: Vec<String>,
#[serde(default)]
pub weekstart: bool,
#[serde(default)]
pub due: usize,
#[serde(default)]
pub color: TaskwarriorColorConfig,
#[serde(default)]
pub data_location: String,
}

#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct TaskReportConfig {
#[serde(default)]
Expand Down Expand Up @@ -64,8 +133,10 @@ pub struct AppConfig {
#[derive(Clone, Debug, Default, Deserialize)]
pub struct Config {
#[serde(default)]
pub task_report: TaskReportConfig,
pub taskwarrior: TaskwarriorConfig,
#[serde(default)]
pub task_report: TaskReportConfig,
#[serde(default, flatten)]
pub config: AppConfig,
#[serde(default)]
pub keybindings: KeyBindings,
Expand Down Expand Up @@ -113,6 +184,143 @@ impl Config {

Ok(cfg)
}

pub fn taskwarrior_config(&mut self) -> Result<()> {
let output = std::process::Command::new("task")
.arg("rc.color=off")
.arg("rc._forcecolor=off")
.arg("rc.defaultwidth=0")
.arg("show")
.output()?;

if !output.status.success() {
let output = std::process::Command::new("task").arg("diagnostics").output()?;
return Err(color_eyre::eyre::eyre!(
"Unable to run `task show`.\n{}\n{}\nPlease check your configuration or open a issue on github.",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
));
}

let data = String::from_utf8_lossy(&output.stdout);

self.rule_precedence_color(&data);
self.uda_priority_values(&data);
self.weekstart(&data);
self.due(&data);
self.data_location(&data);
Ok(())
}

fn color(&mut self, data: &str) -> Result<()> {
self.taskwarrior.color.active = parse_style(&get_config("color.active", data)?);
self.taskwarrior.color.alternate = parse_style(&get_config("color.alternate", data)?);
self.taskwarrior.color.blocked = parse_style(&get_config("color.blocked", data)?);
self.taskwarrior.color.blocking = parse_style(&get_config("color.blocking", data)?);
self.taskwarrior.color.burndown_done = parse_style(&get_config("color.burndown.done", data)?);
self.taskwarrior.color.burndown_pending = parse_style(&get_config("color.burndown.pending", data)?);
self.taskwarrior.color.burndown_started = parse_style(&get_config("color.burndown.started", data)?);
self.taskwarrior.color.calendar_due = parse_style(&get_config("color.calendar.due", data)?);
self.taskwarrior.color.calendar_due_today = parse_style(&get_config("color.calendar.due.today", data)?);
self.taskwarrior.color.calendar_holiday = parse_style(&get_config("color.calendar.holiday", data)?);
self.taskwarrior.color.calendar_overdue = parse_style(&get_config("color.calendar.overdue", data)?);
self.taskwarrior.color.calendar_scheduled = parse_style(&get_config("color.calendar.scheduled", data)?);
self.taskwarrior.color.calendar_today = parse_style(&get_config("color.calendar.today", data)?);
self.taskwarrior.color.calendar_weekend = parse_style(&get_config("color.calendar.weekend", data)?);
self.taskwarrior.color.calendar_weeknumber = parse_style(&get_config("color.calendar.weeknumber", data)?);
self.taskwarrior.color.completed = parse_style(&get_config("color.completed", data)?);
self.taskwarrior.color.debug = parse_style(&get_config("color.debug", data)?);
self.taskwarrior.color.deleted = parse_style(&get_config("color.deleted", data)?);
self.taskwarrior.color.due = parse_style(&get_config("color.due", data)?);
self.taskwarrior.color.due_today = parse_style(&get_config("color.due.today", data)?);
self.taskwarrior.color.error = parse_style(&get_config("color.error", data)?);
self.taskwarrior.color.footnote = parse_style(&get_config("color.footnote", data)?);
self.taskwarrior.color.header = parse_style(&get_config("color.header", data)?);
self.taskwarrior.color.history_add = parse_style(&get_config("color.history.add", data)?);
self.taskwarrior.color.history_delete = parse_style(&get_config("color.history.delete", data)?);
self.taskwarrior.color.history_done = parse_style(&get_config("color.history.done", data)?);
self.taskwarrior.color.label = parse_style(&get_config("color.label", data)?);
self.taskwarrior.color.label_sort = parse_style(&get_config("color.label.sort", data)?);
self.taskwarrior.color.overdue = parse_style(&get_config("color.overdue", data)?);
self.taskwarrior.color.project_basics = parse_style(&get_config("color.project.basics", data)?);
self.taskwarrior.color.project_none = parse_style(&get_config("color.project.none", data)?);
self.taskwarrior.color.project_wth = parse_style(&get_config("color.project.wth", data)?);
self.taskwarrior.color.recurring = parse_style(&get_config("color.recurring", data)?);
self.taskwarrior.color.scheduled = parse_style(&get_config("color.scheduled", data)?);
self.taskwarrior.color.summary_background = parse_style(&get_config("color.summary.background", data)?);
self.taskwarrior.color.summary_bar = parse_style(&get_config("color.summary.bar", data)?);
self.taskwarrior.color.sync_added = parse_style(&get_config("color.sync.added", data)?);
self.taskwarrior.color.sync_changed = parse_style(&get_config("color.sync.changed", data)?);
self.taskwarrior.color.sync_rejected = parse_style(&get_config("color.sync.rejected", data)?);
self.taskwarrior.color.tag_next = parse_style(&get_config("color.tag.next", data)?);
self.taskwarrior.color.tag_none = parse_style(&get_config("color.tag.none", data)?);
self.taskwarrior.color.tagged = parse_style(&get_config("color.tagged", data)?);
self.taskwarrior.color.uda_priority.insert("H".into(), parse_style(&get_config("color.uda.priority.H", data)?));
self.taskwarrior.color.uda_priority.insert("L".into(), parse_style(&get_config("color.uda.priority.L", data)?));
self.taskwarrior.color.uda_priority.insert("M".into(), parse_style(&get_config("color.uda.priority.M", data)?));
self.taskwarrior.color.uda_priority.insert("U".into(), parse_style(&get_config("color.uda.priority.U", data)?));
self.taskwarrior.color.undo_after = parse_style(&get_config("color.undo.after", data)?);
self.taskwarrior.color.undo_before = parse_style(&get_config("color.undo.before", data)?);
self.taskwarrior.color.until = parse_style(&get_config("color.until", data)?);
self.taskwarrior.color.warning = parse_style(&get_config("color.warning", data)?);
Ok(())
}

fn data_location(&mut self, data: &str) {
self.taskwarrior.data_location = get_config("data.location", data).unwrap();
}

fn rule_precedence_color(&mut self, data: &str) {
let data = get_config("rule.precedence.color", data).unwrap();
self.taskwarrior.rule_precedence_color = data.split(',').map(ToString::to_string).collect::<Vec<_>>();
}

fn weekstart(&mut self, data: &str) {
let data = try_get_config("weekstart", data).unwrap_or_default();
self.taskwarrior.weekstart = data.eq_ignore_ascii_case("Monday");
}

fn due(&mut self, data: &str) {
self.taskwarrior.due = try_get_config("due", data).unwrap_or_default().parse::<usize>().unwrap_or(7)
}

fn uda_priority_values(&mut self, data: &str) {
let data = get_config("uda.priority.values", data).unwrap();
self.taskwarrior.uda_priority_values = data.split(',').map(ToString::to_string).collect::<Vec<_>>();
}
}

fn get_config(config: &str, data: &str) -> Result<String> {
try_get_config(config, data).ok_or(color_eyre::eyre::eyre!("Unable to parse `task show {config}`"))
}

fn try_get_config(config: &str, data: &str) -> Option<String> {
let mut config_lines = Vec::new();

for line in data.split('\n') {
if config_lines.is_empty() {
if line.starts_with(config) {
config_lines.push(line.trim_start_matches(config).trim_start().trim_end().to_string());
} else {
let config = &config.replace('-', "_");
if line.starts_with(config) {
config_lines.push(line.trim_start_matches(config).trim_start().trim_end().to_string());
}
}
} else {
if !line.starts_with(" ") {
return Some(config_lines.join(" "));
}

config_lines.push(line.trim_start().trim_end().to_string());
}
}

if !config_lines.is_empty() {
return Some(config_lines.join(" "));
}

None
}

#[derive(Clone, Debug, Default, Deref, DerefMut)]
Expand Down

0 comments on commit 8302c88

Please sign in to comment.