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 6f7d3e5 commit c01c9f6
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 124 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 @@ -49,7 +49,7 @@ toml = "0.8.0"
tracing = "0.1.37"
tracing-error = "0.2.0"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
tui-input = "0.8.0"
tui-input = { version = "0.8.0", features = ["serde"] }
unicode-segmentation = "1.10.1"
unicode-truncate = "0.2.0"
unicode-width = "0.1.10"
Expand Down
2 changes: 1 addition & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl App {
pub fn new(tick_rate: f64, frame_rate: f64, report: &str) -> Result<Self> {
let app = TaskReport::new().report(report.into());
let mut config = Config::new()?;
config.taskwarrior_config()?;
config.taskwarrior.taskwarrior_config(report)?;
let mode = Mode::TaskReport;
Ok(Self {
tick_rate,
Expand Down
76 changes: 62 additions & 14 deletions src/components/task_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use ratatui::{prelude::*, widgets::*};
use serde_derive::{Deserialize, Serialize};
use task_hookrs::{import::import, status::TaskStatus, task::Task, uda::UDAValue};
use tokio::sync::mpsc::UnboundedSender;
use tui_input::backend::crossterm::EventHandler;
use tui_input::{backend::crossterm::EventHandler, Input};
use unicode_truncate::UnicodeTruncateStr;
use unicode_width::UnicodeWidthStr;
use uuid::Uuid;
Expand Down Expand Up @@ -55,26 +55,49 @@ const VIRTUAL_TAGS: [&str; 34] = [
"TEMPLATE",
];

#[derive(Default)]
pub enum Mode {
#[default]
Report,
Filter,
Add,
Annotate,
Subprocess,
Log,
Modify,
HelpPopup,
ContextMenu,
Jump,
DeletePrompt,
UndoPrompt,
DonePrompt,
Error,
}

#[derive(Default)]
pub struct TaskReport {
pub config: Config,
pub columns: Vec<String>,
pub command_tx: Option<UnboundedSender<Action>>,
pub config: Config,
pub current_context: String,
pub current_context_filter: String,
pub current_filter: String,
pub current_selection: usize,
pub current_selection_id: Option<u64>,
pub current_selection_uuid: Option<Uuid>,
pub date_time_vague_precise: bool,
pub description_width: usize,
pub input: Input,
pub labels: Vec<String>,
pub last_export: Option<std::time::SystemTime>,
pub report: String,
pub filter: String,
pub current_context_filter: String,
pub tasks: Vec<Task>,
pub rows: Vec<Vec<String>>,
pub row_heights: Vec<u16>,
pub rows: Vec<Vec<String>>,
pub state: TableState,
pub columns: Vec<String>,
pub labels: Vec<String>,
pub date_time_vague_precise: bool,
pub task_details: HashMap<Uuid, String>,
pub tasks: Vec<Task>,
pub virtual_tags: Vec<String>,
pub description_width: usize,
pub current_selection: usize,
pub current_selection_id: Option<u64>,
pub current_selection_uuid: Option<Uuid>,
pub mode: Mode,
}

impl TaskReport {
Expand Down Expand Up @@ -432,6 +455,29 @@ impl TaskReport {
}
}

pub fn get_context(&mut self) -> Result<()> {
let output = std::process::Command::new("task").arg("_get").arg("rc.context").output()?;
self.current_context = String::from_utf8_lossy(&output.stdout).to_string();
self.current_context = self.current_context.strip_suffix('\n').unwrap_or("").to_string();

// support new format for context
let output = std::process::Command::new("task")
.arg("_get")
.arg(format!("rc.context.{}.read", self.current_context))
.output()?;
self.current_context_filter = String::from_utf8_lossy(&output.stdout).to_string();
self.current_context_filter = self.current_context_filter.strip_suffix('\n').unwrap_or("").to_string();

// If new format is not used, check if old format is used
if self.current_context_filter.is_empty() {
let output =
std::process::Command::new("task").arg("_get").arg(format!("rc.context.{}", self.current_context)).output()?;
self.current_context_filter = String::from_utf8_lossy(&output.stdout).to_string();
self.current_context_filter = self.current_context_filter.strip_suffix('\n').unwrap_or("").to_string();
}
Ok(())
}

pub fn task_export(&mut self) -> Result<()> {
let mut task = std::process::Command::new("task");

Expand All @@ -443,7 +489,9 @@ impl TaskReport {
.arg("rc._forcecolor=off");
// .arg("rc.verbose:override=false");

if let Some(args) = shlex::split(format!(r#"rc.report.{}.filter='{}'"#, self.report, self.filter.trim()).trim()) {
if let Some(args) =
shlex::split(format!(r#"rc.report.{}.filter='{}'"#, self.report, self.current_filter.trim()).trim())
{
for arg in args {
task.arg(arg);
}
Expand Down
229 changes: 121 additions & 108 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,133 @@ const CONFIG: &str = include_str!("../.config/config.json5");

#[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: HashMap<String, Style>,
#[serde(default)]
pub data_location: String,
pub filter: String,
}

impl TaskwarriorConfig {
pub fn taskwarrior_config(&mut self, report: &str) -> 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);
self.color(&data);
self.filter(&data, &report);
Ok(())
}

fn color(&mut self, data: &str) {
let mut color = HashMap::new();
for line in data.split('\n') {
if line.starts_with("color.") {
let mut i = line.split(' ');
let attribute = i.next();
let line = i.collect::<Vec<_>>().join(" ");
let line = line.trim_start_matches(' ');
let style = parse_style(line);
if let Some(attr) = attribute {
color.insert(attr.to_string(), style);
};
}
}
log::info!("{color:?}");
self.color = color;
}

fn filter(&mut self, data: &str, report: &str) {
let filter = if report == "all" {
"".into()
} else if let Some(s) =
// TODO: move this to configuration struct
Self::try_get_config(format!("uda.taskwarrior-tui.task-report.{}.filter", report).as_str(), data)
{
s
} else {
Self::get_config(format!("report.{}.filter", report).as_str(), data).unwrap_or_default()
};
let filter = if filter.trim_start().trim_end().is_empty() { filter } else { format!("{} ", filter) };
self.filter = filter;
}

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

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

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

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

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

fn get_config(config: &str, data: &str) -> Result<String> {
Self::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, Serialize, Deserialize, Default)]
Expand Down Expand Up @@ -131,108 +246,6 @@ 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);
self.color(&data);
Ok(())
}

fn color(&mut self, data: &str) {
let mut color = HashMap::new();
for line in data.split('\n') {
if line.starts_with("color.") {
let mut i = line.split(' ');
let attribute = i.next();
let line = i.collect::<Vec<_>>().join(" ");
let line = line.trim_start_matches(' ');
let style = parse_style(line);
if let Some(attr) = attribute {
color.insert(attr.to_string(), style);
};
}
}
log::info!("{color:?}");
self.taskwarrior.color = color;
}

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 c01c9f6

Please sign in to comment.