mirror of
https://github.com/kdheepak/taskwarrior-tui.git
synced 2025-08-25 08:47:18 +02:00
WIP
This commit is contained in:
parent
84589c3ab3
commit
ff6c0e60b9
6 changed files with 220 additions and 94 deletions
|
@ -1,8 +1,14 @@
|
||||||
{
|
{
|
||||||
|
"task_report": {
|
||||||
|
"looping": false,
|
||||||
|
"selection_indicator": "• ",
|
||||||
|
},
|
||||||
"keybindings": {
|
"keybindings": {
|
||||||
// KeyBindings for TaskReport
|
// KeyBindings for TaskReport
|
||||||
"TaskReport": {
|
"TaskReport": {
|
||||||
"<q>": "Quit", // Quit the application
|
"<q>": "Quit", // Quit the application
|
||||||
|
"<j>": "MoveDown", // MoveDown
|
||||||
|
"<k>": "MoveUp", // MoveUp
|
||||||
"<Ctrl-d>": "Quit", // Another way to quit
|
"<Ctrl-d>": "Quit", // Another way to quit
|
||||||
"<Ctrl-c>": "Quit", // Yet another way to quit
|
"<Ctrl-c>": "Quit", // Yet another way to quit
|
||||||
"<Ctrl-z>": "Suspend" // Suspend the application
|
"<Ctrl-z>": "Suspend" // Suspend the application
|
||||||
|
|
18
src/app.rs
18
src/app.rs
|
@ -33,7 +33,7 @@ pub struct App {
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(tick_rate: f64, frame_rate: f64, report: &str) -> Result<Self> {
|
pub fn new(tick_rate: f64, frame_rate: f64, report: &str) -> Result<Self> {
|
||||||
let app = TaskReport::new().report(report.into());
|
let app = TaskReport::new().report(report.into());
|
||||||
let config = Config::new()?;
|
let config = Config::new().unwrap();
|
||||||
let mode = Mode::TaskReport;
|
let mode = Mode::TaskReport;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
tick_rate,
|
tick_rate,
|
||||||
|
@ -75,11 +75,21 @@ impl App {
|
||||||
tui::Event::Render => action_tx.send(Action::Render)?,
|
tui::Event::Render => action_tx.send(Action::Render)?,
|
||||||
tui::Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
|
tui::Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
|
||||||
tui::Event::Key(key) => {
|
tui::Event::Key(key) => {
|
||||||
self.last_tick_key_events.push(key);
|
|
||||||
if let Some(keymap) = self.config.keybindings.get(&self.mode) {
|
if let Some(keymap) = self.config.keybindings.get(&self.mode) {
|
||||||
if let Some(action) = keymap.get(&self.last_tick_key_events) {
|
if let Some(action) = keymap.get(&vec![key.clone()]) {
|
||||||
|
log::info!("Got action: {action:?}");
|
||||||
action_tx.send(action.clone())?;
|
action_tx.send(action.clone())?;
|
||||||
};
|
} else {
|
||||||
|
// If the key was not handled as a single key action,
|
||||||
|
// then consider it for multi-key combinations.
|
||||||
|
self.last_tick_key_events.push(key);
|
||||||
|
|
||||||
|
// Check for multi-key combinations
|
||||||
|
if let Some(action) = keymap.get(&self.last_tick_key_events) {
|
||||||
|
log::info!("Got action: {action:?}");
|
||||||
|
action_tx.send(action.clone())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
|
|
|
@ -40,7 +40,7 @@ pub trait Component {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn update(&mut self, command: Action) -> Result<Option<Action>> {
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
fn draw(&mut self, f: &mut Frame<'_>, rect: Rect) -> Result<()>;
|
fn draw(&mut self, f: &mut Frame<'_>, rect: Rect) -> Result<()>;
|
||||||
|
|
|
@ -18,75 +18,6 @@ use crate::{
|
||||||
config::{Config, KeyBindings},
|
config::{Config, KeyBindings},
|
||||||
};
|
};
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format_date(dt: NaiveDateTime) -> String {
|
|
||||||
let offset = Local.offset_from_utc_datetime(&dt);
|
|
||||||
let dt = DateTime::<Local>::from_naive_utc_and_offset(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;
|
|
||||||
"-"
|
|
||||||
} 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)
|
|
||||||
};
|
|
||||||
} 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct TaskReport {
|
pub struct TaskReport {
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
|
@ -97,13 +28,15 @@ pub struct TaskReport {
|
||||||
pub current_context_filter: String,
|
pub current_context_filter: String,
|
||||||
pub tasks: Vec<Task>,
|
pub tasks: Vec<Task>,
|
||||||
pub rows: Vec<Vec<String>>,
|
pub rows: Vec<Vec<String>>,
|
||||||
pub headers: Vec<String>,
|
|
||||||
pub state: TableState,
|
pub state: TableState,
|
||||||
pub columns: Vec<String>,
|
pub columns: Vec<String>,
|
||||||
pub labels: Vec<String>,
|
pub labels: Vec<String>,
|
||||||
pub date_time_vague_precise: bool,
|
pub date_time_vague_precise: bool,
|
||||||
pub virtual_tags: Vec<String>,
|
pub virtual_tags: Vec<String>,
|
||||||
pub description_width: usize,
|
pub description_width: usize,
|
||||||
|
pub current_selection: usize,
|
||||||
|
pub current_selection_id: Option<u64>,
|
||||||
|
pub current_selection_uuid: Option<Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskReport {
|
impl TaskReport {
|
||||||
|
@ -178,9 +111,9 @@ impl TaskReport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let num_labels = self.labels.len();
|
if self.labels.len() != self.columns.len() {
|
||||||
let num_columns = self.columns.len();
|
return Err(color_eyre::eyre::eyre!(format!("`{}` expects to have the same number of labels and columns ({} != {}). Compare their values as shown by `task show report.{}.` and fix your taskwarrior config.", env!("CARGO_PKG_NAME"), self.labels.len(), self.columns.len(), &self.report)));
|
||||||
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, &self.report);
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -470,7 +403,7 @@ impl TaskReport {
|
||||||
|
|
||||||
task.arg(&self.report);
|
task.arg(&self.report);
|
||||||
|
|
||||||
log::info!("Running `{:?}`", task);
|
log::debug!("Running `{:?}`", task);
|
||||||
let output = task.output()?;
|
let output = task.output()?;
|
||||||
let data = String::from_utf8_lossy(&output.stdout);
|
let data = String::from_utf8_lossy(&output.stdout);
|
||||||
let error = String::from_utf8_lossy(&output.stderr);
|
let error = String::from_utf8_lossy(&output.stderr);
|
||||||
|
@ -479,7 +412,7 @@ impl TaskReport {
|
||||||
let imported = import(data.as_bytes());
|
let imported = import(data.as_bytes());
|
||||||
if imported.is_ok() {
|
if imported.is_ok() {
|
||||||
self.tasks = imported?;
|
self.tasks = imported?;
|
||||||
log::info!("Imported {} tasks", self.tasks.len());
|
log::debug!("Imported {} tasks", self.tasks.len());
|
||||||
self.send_action(Action::ShowTaskReport)?;
|
self.send_action(Action::ShowTaskReport)?;
|
||||||
} else {
|
} else {
|
||||||
imported?;
|
imported?;
|
||||||
|
@ -490,6 +423,50 @@ impl TaskReport {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) {
|
||||||
|
if self.tasks.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = {
|
||||||
|
if self.current_selection >= self.tasks.len() - 1 {
|
||||||
|
if self.config.task_report.looping {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
self.current_selection
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.current_selection + 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.current_selection = i;
|
||||||
|
self.current_selection_id = None;
|
||||||
|
self.current_selection_uuid = None;
|
||||||
|
self.state.select(Some(self.current_selection));
|
||||||
|
log::info!("{:?}", self.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&mut self) {
|
||||||
|
if self.tasks.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = {
|
||||||
|
if self.current_selection == 0 {
|
||||||
|
if self.config.task_report.looping {
|
||||||
|
self.tasks.len() - 1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.current_selection - 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.current_selection = i;
|
||||||
|
self.current_selection_id = None;
|
||||||
|
self.current_selection_uuid = None;
|
||||||
|
self.state.select(Some(self.current_selection));
|
||||||
|
log::info!("{:?}", self.state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for TaskReport {
|
impl Component for TaskReport {
|
||||||
|
@ -503,30 +480,124 @@ impl Component for TaskReport {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, command: Action) -> Result<Option<Action>> {
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
match command {
|
match action {
|
||||||
Action::Tick => {
|
Action::Tick => {
|
||||||
self.task_export()?;
|
self.task_export()?;
|
||||||
self.export_headers()?;
|
self.export_headers()?;
|
||||||
self.generate_rows()?;
|
self.generate_rows()?;
|
||||||
},
|
},
|
||||||
|
Action::MoveDown => self.next(),
|
||||||
|
Action::MoveUp => self.previous(),
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&mut self, f: &mut Frame<'_>, rect: Rect) -> Result<()> {
|
fn draw(&mut self, f: &mut Frame<'_>, rect: Rect) -> Result<()> {
|
||||||
let mut constraints = vec![];
|
if self.rows.len() == 0 {
|
||||||
|
f.render_widget(Paragraph::new("No data found").block(Block::new().borders(Borders::all())), rect);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let mut total_fixed_widths = 0;
|
||||||
|
let mut constraints = Vec::with_capacity(self.rows[0].len());
|
||||||
|
|
||||||
for i in 0..self.rows[0].len() {
|
for i in 0..self.rows[0].len() {
|
||||||
constraints.push(Constraint::Percentage(100 / self.rows[0].len() as u16));
|
if self.columns[i] == "description" {
|
||||||
|
constraints.push(Constraint::Min(0)); // temporary, will update later
|
||||||
|
} else {
|
||||||
|
let max_width = self.rows.iter().map(|row| row[i].len() as u16).max().unwrap_or(0);
|
||||||
|
total_fixed_widths += max_width + 2; // adding 2 for padding
|
||||||
|
constraints.push(Constraint::Length(max_width + 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pos) = self.columns.iter().position(|x| x == "description") {
|
||||||
|
let description_width = rect.width.saturating_sub(total_fixed_widths).saturating_sub(4);
|
||||||
|
constraints[pos] = Constraint::Length(description_width);
|
||||||
}
|
}
|
||||||
let rows = self.rows.iter().map(|row| Row::new(row.clone()));
|
let rows = self.rows.iter().map(|row| Row::new(row.clone()));
|
||||||
let table = Table::new(rows).header(Row::new(self.headers.clone())).widths(&constraints);
|
let table = Table::new(rows)
|
||||||
|
.header(Row::new(self.columns.clone()))
|
||||||
|
.widths(&constraints)
|
||||||
|
.block(Block::new().borders(Borders::ALL))
|
||||||
|
.highlight_symbol(&self.config.task_report.selection_indicator)
|
||||||
|
.highlight_spacing(HighlightSpacing::Always);
|
||||||
f.render_stateful_widget(table, rect, &mut self.state);
|
f.render_stateful_widget(table, rect, &mut self.state);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_date(dt: NaiveDateTime) -> String {
|
||||||
|
let offset = Local.offset_from_utc_datetime(&dt);
|
||||||
|
let dt = DateTime::<Local>::from_naive_utc_and_offset(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;
|
||||||
|
"-"
|
||||||
|
} 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)
|
||||||
|
};
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
|
||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{collections::HashMap, fmt, path::PathBuf};
|
use std::{collections::HashMap, fmt, path::PathBuf};
|
||||||
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
|
use config::Value;
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
use derive_deref::{Deref, DerefMut};
|
use derive_deref::{Deref, DerefMut};
|
||||||
use ratatui::style::{Color, Modifier, Style};
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
|
@ -11,6 +12,24 @@ use crate::{action::Action, app::Mode};
|
||||||
|
|
||||||
const CONFIG: &str = include_str!("../.config/config.json5");
|
const CONFIG: &str = include_str!("../.config/config.json5");
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
|
pub struct TaskReportConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
pub looping: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub selection_indicator: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Value> for TaskReportConfig {
|
||||||
|
fn into(self) -> Value {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert("looping".to_string(), Value::from(self.looping));
|
||||||
|
map.insert("selection_indicator".to_string(), Value::from(self.selection_indicator));
|
||||||
|
|
||||||
|
Value::from(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -21,7 +40,9 @@ pub struct AppConfig {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize)]
|
#[derive(Clone, Debug, Default, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(default, flatten)]
|
#[serde(default)]
|
||||||
|
pub task_report: TaskReportConfig,
|
||||||
|
#[serde(default)]
|
||||||
pub config: AppConfig,
|
pub config: AppConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub keybindings: KeyBindings,
|
pub keybindings: KeyBindings,
|
||||||
|
@ -35,15 +56,23 @@ impl Config {
|
||||||
let data_dir = crate::utils::get_data_dir();
|
let data_dir = crate::utils::get_data_dir();
|
||||||
let config_dir = crate::utils::get_config_dir();
|
let config_dir = crate::utils::get_config_dir();
|
||||||
let mut builder = config::Config::builder()
|
let mut builder = config::Config::builder()
|
||||||
|
.set_default("task_report", default_config.task_report)?
|
||||||
.set_default("_data_dir", data_dir.to_str().unwrap())?
|
.set_default("_data_dir", data_dir.to_str().unwrap())?
|
||||||
.set_default("_config_dir", config_dir.to_str().unwrap())?;
|
.set_default("_config_dir", config_dir.to_str().unwrap())?;
|
||||||
|
|
||||||
builder = builder
|
// List of potential configuration files provided by the user
|
||||||
.add_source(config::File::from(config_dir.join("config.json5")).format(config::FileFormat::Json5).required(false))
|
let config_files = [
|
||||||
.add_source(config::File::from(config_dir.join("config.json")).format(config::FileFormat::Json).required(false))
|
("config.json5", config::FileFormat::Json5),
|
||||||
.add_source(config::File::from(config_dir.join("config.yaml")).format(config::FileFormat::Yaml).required(false))
|
("config.json", config::FileFormat::Json),
|
||||||
.add_source(config::File::from(config_dir.join("config.toml")).format(config::FileFormat::Toml).required(false))
|
("config.yaml", config::FileFormat::Yaml),
|
||||||
.add_source(config::File::from(config_dir.join("config.ini")).format(config::FileFormat::Ini).required(false));
|
("config.toml", config::FileFormat::Toml),
|
||||||
|
("config.ini", config::FileFormat::Ini),
|
||||||
|
];
|
||||||
|
for (file, format) in &config_files {
|
||||||
|
if config_dir.join(file).exists() {
|
||||||
|
builder = builder.add_source(config::File::from(config_dir.join(file)).format(*format).required(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut cfg: Self = builder.build()?.try_deserialize()?;
|
let mut cfg: Self = builder.build()?.try_deserialize()?;
|
||||||
|
|
||||||
|
@ -53,6 +82,12 @@ impl Config {
|
||||||
user_bindings.entry(key.clone()).or_insert_with(|| cmd.clone());
|
user_bindings.entry(key.clone()).or_insert_with(|| cmd.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (mode, default_styles) in default_config.styles.iter() {
|
||||||
|
let user_styles = cfg.styles.entry(*mode).or_default();
|
||||||
|
for (style_key, style) in default_styles.iter() {
|
||||||
|
user_styles.entry(style_key.clone()).or_insert_with(|| style.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,10 @@ async fn tokio_main() -> Result<()> {
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
tokio_main().await.unwrap();
|
if let Err(e) = tokio_main().await {
|
||||||
Ok(())
|
eprintln!("{} error: Something went wrong", env!("CARGO_PKG_NAME"));
|
||||||
|
Err(e)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue