mirror of
https://github.com/kdheepak/taskwarrior-tui.git
synced 2025-08-24 23:46:41 +02:00
feat: Use color_eyre instead of anyhow ✨
This commit is contained in:
parent
fe171ea35a
commit
2bb15f7cf2
18 changed files with 1180 additions and 412 deletions
976
Cargo.lock
generated
976
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
38
Cargo.toml
38
Cargo.toml
|
@ -7,38 +7,45 @@ repository = "https://github.com/kdheepak/taskwarrior-tui/"
|
||||||
homepage = "https://kdheepak.com/taskwarrior-tui"
|
homepage = "https://kdheepak.com/taskwarrior-tui"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = ["Dheepak Krishnamurthy <me@kdheepak.com>"]
|
authors = ["Dheepak Krishnamurthy <me@kdheepak.com>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
keywords = ["taskwarrior", "tui"]
|
keywords = ["taskwarrior", "tui"]
|
||||||
categories = ["command-line-utilities"]
|
categories = ["command-line-utilities"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.75"
|
|
||||||
better-panic = "0.3.0"
|
|
||||||
cassowary = "0.3.0"
|
cassowary = "0.3.0"
|
||||||
chrono = "0.4.26"
|
chrono = "0.4.28"
|
||||||
clap = { version = "4.4.1", features = ["derive"] }
|
clap = { version = "4.4.2", features = ["std", "color", "help", "usage", "error-context", "suggestions", "derive", "cargo", "wrap_help", "unicode", "string", "unstable-styles"] }
|
||||||
crossterm = { version = "0.27.0", features = [
|
color-eyre = "0.6.2"
|
||||||
"event-stream",
|
config = "0.13.3"
|
||||||
] }
|
crossterm = { version = "0.27.0", features = ["event-stream", "serde"] }
|
||||||
dirs = "5.0.1"
|
directories = "5.0.1"
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
|
human-panic = "1.2.0"
|
||||||
itertools = "0.11.0"
|
itertools = "0.11.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
libc = "0.2.147"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
log4rs = "1.2.0"
|
|
||||||
path-clean = "1.0.1"
|
path-clean = "1.0.1"
|
||||||
|
pretty_assertions = "1.4.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
regex = "1.9.4"
|
ratatui = "0.23.0"
|
||||||
|
regex = "1.9.5"
|
||||||
rustyline = { version = "12.0.0", features = ["with-file-history", "derive"] }
|
rustyline = { version = "12.0.0", features = ["with-file-history", "derive"] }
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
|
serde_derive = "1.0.188"
|
||||||
serde_json = "1.0.105"
|
serde_json = "1.0.105"
|
||||||
shellexpand = "3.1.0"
|
shellexpand = "3.1.0"
|
||||||
shlex = "1.1.0"
|
shlex = "1.1.0"
|
||||||
|
signal-hook = "0.3.17"
|
||||||
|
strip-ansi-escapes = "0.2.0"
|
||||||
task-hookrs = "0.9.0"
|
task-hookrs = "0.9.0"
|
||||||
tokio = { version = "1.32.0", features = ["full"] }
|
tokio = { version = "1.32.0", features = ["full"] }
|
||||||
tokio-stream = "0.1.14"
|
tokio-util = "0.7.8"
|
||||||
ratatui = "0.23.0"
|
tracing = "0.1.37"
|
||||||
|
tracing-error = "0.2.0"
|
||||||
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
|
tui-logger = { version = "0.9.5", features = ["ratatui-support", "tracing-support"] }
|
||||||
unicode-segmentation = "1.10.1"
|
unicode-segmentation = "1.10.1"
|
||||||
unicode-truncate = "0.2.0"
|
unicode-truncate = "0.2.0"
|
||||||
unicode-width = "0.1.10"
|
unicode-width = "0.1.10"
|
||||||
|
@ -55,11 +62,10 @@ buildflags = ["--release"]
|
||||||
taskwarrior-tui = { path = "/usr/bin/taskwarrior-tui" }
|
taskwarrior-tui = { path = "/usr/bin/taskwarrior-tui" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 1
|
|
||||||
incremental = true
|
incremental = true
|
||||||
lto = "off"
|
lto = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
clap = { version = "4.4.1", features = ["derive"] }
|
clap = { version = "4.4.2", features = ["derive"] }
|
||||||
clap_complete = "4.4.0"
|
clap_complete = "4.4.0"
|
||||||
shlex = "1.1.0"
|
shlex = "1.1.0"
|
||||||
|
|
54
build.rs
54
build.rs
|
@ -18,7 +18,61 @@ fn run_pandoc() -> Result<Output, std::io::Error> {
|
||||||
cmd.output()
|
cmd.output()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_commit_hash() {
|
||||||
|
let git_output = std::process::Command::new("git").args(["rev-parse", "--git-dir"]).output().ok();
|
||||||
|
let git_dir = git_output.as_ref().and_then(|output| {
|
||||||
|
std::str::from_utf8(&output.stdout)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.strip_suffix('\n').or_else(|| s.strip_suffix("\r\n")))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tell cargo to rebuild if the head or any relevant refs change.
|
||||||
|
if let Some(git_dir) = git_dir {
|
||||||
|
let git_path = std::path::Path::new(git_dir);
|
||||||
|
let refs_path = git_path.join("refs");
|
||||||
|
if git_path.join("HEAD").exists() {
|
||||||
|
println!("cargo:rerun-if-changed={}/HEAD", git_dir);
|
||||||
|
}
|
||||||
|
if git_path.join("packed-refs").exists() {
|
||||||
|
println!("cargo:rerun-if-changed={}/packed-refs", git_dir);
|
||||||
|
}
|
||||||
|
if refs_path.join("heads").exists() {
|
||||||
|
println!("cargo:rerun-if-changed={}/refs/heads", git_dir);
|
||||||
|
}
|
||||||
|
if refs_path.join("tags").exists() {
|
||||||
|
println!("cargo:rerun-if-changed={}/refs/tags", git_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let git_output = std::process::Command::new("git")
|
||||||
|
.args(["describe", "--always", "--tags", "--long", "--dirty"])
|
||||||
|
.output()
|
||||||
|
.ok();
|
||||||
|
let git_info = git_output
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|output| std::str::from_utf8(&output.stdout).ok().map(str::trim));
|
||||||
|
let cargo_pkg_version = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
// Default git_describe to cargo_pkg_version
|
||||||
|
let mut git_describe = String::from(cargo_pkg_version);
|
||||||
|
|
||||||
|
if let Some(git_info) = git_info {
|
||||||
|
// If the `git_info` contains `CARGO_PKG_VERSION`, we simply use `git_info` as it is.
|
||||||
|
// Otherwise, prepend `CARGO_PKG_VERSION` to `git_info`.
|
||||||
|
if git_info.contains(cargo_pkg_version) {
|
||||||
|
// Remove the 'g' before the commit sha
|
||||||
|
let git_info = &git_info.replace('g', "");
|
||||||
|
git_describe = git_info.to_string();
|
||||||
|
} else {
|
||||||
|
git_describe = format!("v{}-{}", cargo_pkg_version, git_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("cargo:rustc-env=TASKWARRIOR_TUI_GIT_INFO={}", git_describe);
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
get_commit_hash();
|
||||||
let mut app = generate_cli_app();
|
let mut app = generate_cli_app();
|
||||||
let name = app.get_name().to_string();
|
let name = app.get_name().to_string();
|
||||||
let outdir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("completions/");
|
let outdir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("completions/");
|
||||||
|
|
|
@ -17,8 +17,8 @@ _taskwarrior-tui() {
|
||||||
_arguments "${_arguments_options[@]}" \
|
_arguments "${_arguments_options[@]}" \
|
||||||
'-d+[Sets the data folder for taskwarrior-tui]:FOLDER: ' \
|
'-d+[Sets the data folder for taskwarrior-tui]:FOLDER: ' \
|
||||||
'--data=[Sets the data folder for taskwarrior-tui]:FOLDER: ' \
|
'--data=[Sets the data folder for taskwarrior-tui]:FOLDER: ' \
|
||||||
'-c+[Sets the config folder for taskwarrior-tui (currently not used)]:FOLDER: ' \
|
'-c+[Sets the config folder for taskwarrior-tui]:FOLDER: ' \
|
||||||
'--config=[Sets the config folder for taskwarrior-tui (currently not used)]:FOLDER: ' \
|
'--config=[Sets the config folder for taskwarrior-tui]:FOLDER: ' \
|
||||||
'--taskdata=[Sets the .task folder using the TASKDATA environment variable for taskwarrior]:FOLDER: ' \
|
'--taskdata=[Sets the .task folder using the TASKDATA environment variable for taskwarrior]:FOLDER: ' \
|
||||||
'--taskrc=[Sets the .taskrc file using the TASKRC environment variable for taskwarrior]:FILE: ' \
|
'--taskrc=[Sets the .taskrc file using the TASKRC environment variable for taskwarrior]:FILE: ' \
|
||||||
'-r+[Sets default report]:STRING: ' \
|
'-r+[Sets default report]:STRING: ' \
|
||||||
|
|
|
@ -23,8 +23,8 @@ Register-ArgumentCompleter -Native -CommandName 'taskwarrior-tui' -ScriptBlock {
|
||||||
'taskwarrior-tui' {
|
'taskwarrior-tui' {
|
||||||
[CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Sets the data folder for taskwarrior-tui')
|
[CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Sets the data folder for taskwarrior-tui')
|
||||||
[CompletionResult]::new('--data', 'data', [CompletionResultType]::ParameterName, 'Sets the data folder for taskwarrior-tui')
|
[CompletionResult]::new('--data', 'data', [CompletionResultType]::ParameterName, 'Sets the data folder for taskwarrior-tui')
|
||||||
[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Sets the config folder for taskwarrior-tui (currently not used)')
|
[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Sets the config folder for taskwarrior-tui')
|
||||||
[CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'Sets the config folder for taskwarrior-tui (currently not used)')
|
[CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'Sets the config folder for taskwarrior-tui')
|
||||||
[CompletionResult]::new('--taskdata', 'taskdata', [CompletionResultType]::ParameterName, 'Sets the .task folder using the TASKDATA environment variable for taskwarrior')
|
[CompletionResult]::new('--taskdata', 'taskdata', [CompletionResultType]::ParameterName, 'Sets the .task folder using the TASKDATA environment variable for taskwarrior')
|
||||||
[CompletionResult]::new('--taskrc', 'taskrc', [CompletionResultType]::ParameterName, 'Sets the .taskrc file using the TASKRC environment variable for taskwarrior')
|
[CompletionResult]::new('--taskrc', 'taskrc', [CompletionResultType]::ParameterName, 'Sets the .taskrc file using the TASKRC environment variable for taskwarrior')
|
||||||
[CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Sets default report')
|
[CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Sets default report')
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
complete -c taskwarrior-tui -s d -l data -d 'Sets the data folder for taskwarrior-tui' -r
|
complete -c taskwarrior-tui -s d -l data -d 'Sets the data folder for taskwarrior-tui' -r
|
||||||
complete -c taskwarrior-tui -s c -l config -d 'Sets the config folder for taskwarrior-tui (currently not used)' -r
|
complete -c taskwarrior-tui -s c -l config -d 'Sets the config folder for taskwarrior-tui' -r
|
||||||
complete -c taskwarrior-tui -l taskdata -d 'Sets the .task folder using the TASKDATA environment variable for taskwarrior' -r
|
complete -c taskwarrior-tui -l taskdata -d 'Sets the .task folder using the TASKDATA environment variable for taskwarrior' -r
|
||||||
complete -c taskwarrior-tui -l taskrc -d 'Sets the .taskrc file using the TASKRC environment variable for taskwarrior' -r
|
complete -c taskwarrior-tui -l taskrc -d 'Sets the .taskrc file using the TASKRC environment variable for taskwarrior' -r
|
||||||
complete -c taskwarrior-tui -s r -l report -d 'Sets default report' -r
|
complete -c taskwarrior-tui -s r -l report -d 'Sets default report' -r
|
||||||
|
|
16
src/app.rs
16
src/app.rs
|
@ -5,13 +5,13 @@ use std::{
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
fs, io,
|
fs, io,
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
path::Path,
|
path::{Path, PathBuf},
|
||||||
sync::{mpsc, Arc, Mutex},
|
sync::{mpsc, Arc, Mutex},
|
||||||
time::{Duration, Instant, SystemTime},
|
time::{Duration, Instant, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context as AnyhowContext, Result};
|
|
||||||
use chrono::{DateTime, Datelike, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeZone, Timelike};
|
use chrono::{DateTime, Datelike, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeZone, Timelike};
|
||||||
|
use color_eyre::eyre::{anyhow, Context as AnyhowContext, Result};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{DisableMouseCapture, EnableMouseCapture},
|
event::{DisableMouseCapture, EnableMouseCapture},
|
||||||
execute,
|
execute,
|
||||||
|
@ -57,7 +57,8 @@ use crate::{
|
||||||
scrollbar::Scrollbar,
|
scrollbar::Scrollbar,
|
||||||
table::{Row, Table, TableMode, TableState},
|
table::{Row, Table, TableMode, TableState},
|
||||||
task_report::TaskReportTable,
|
task_report::TaskReportTable,
|
||||||
ui, utils,
|
ui,
|
||||||
|
utils::{self, get_data_dir},
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_LINE: usize = 4096;
|
const MAX_LINE: usize = 4096;
|
||||||
|
@ -177,7 +178,6 @@ pub struct TaskwarriorTui {
|
||||||
pub all_tasks: Vec<Task>,
|
pub all_tasks: Vec<Task>,
|
||||||
pub task_details: HashMap<Uuid, String>,
|
pub task_details: HashMap<Uuid, String>,
|
||||||
pub marked: HashSet<Uuid>,
|
pub marked: HashSet<Uuid>,
|
||||||
// stores index of current task that is highlighted
|
|
||||||
pub current_selection: usize,
|
pub current_selection: usize,
|
||||||
pub current_selection_uuid: Option<Uuid>,
|
pub current_selection_uuid: Option<Uuid>,
|
||||||
pub current_selection_id: Option<u64>,
|
pub current_selection_id: Option<u64>,
|
||||||
|
@ -240,7 +240,7 @@ impl TaskwarriorTui {
|
||||||
.output()
|
.output()
|
||||||
.context("Unable to run `task --version`")?;
|
.context("Unable to run `task --version`")?;
|
||||||
|
|
||||||
let task_version = Versioning::new(String::from_utf8_lossy(&output.stdout).trim()).context("Unable to get version string")?;
|
let task_version = Versioning::new(String::from_utf8_lossy(&output.stdout).trim()).ok_or(anyhow!("Unable to get version string"))?;
|
||||||
|
|
||||||
let (w, h) = crossterm::terminal::size().unwrap_or((50, 15));
|
let (w, h) = crossterm::terminal::size().unwrap_or((50, 15));
|
||||||
|
|
||||||
|
@ -251,6 +251,8 @@ impl TaskwarriorTui {
|
||||||
};
|
};
|
||||||
let event_loop = crate::event::EventLoop::new(tick_rate, init_event_loop);
|
let event_loop = crate::event::EventLoop::new(tick_rate, init_event_loop);
|
||||||
|
|
||||||
|
let data_dir = get_data_dir();
|
||||||
|
|
||||||
let mut app = Self {
|
let mut app = Self {
|
||||||
should_quit: false,
|
should_quit: false,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
|
@ -280,8 +282,8 @@ impl TaskwarriorTui {
|
||||||
keyconfig: kc,
|
keyconfig: kc,
|
||||||
terminal_width: w,
|
terminal_width: w,
|
||||||
terminal_height: h,
|
terminal_height: h,
|
||||||
filter_history: HistoryContext::new("filter.history"),
|
filter_history: HistoryContext::new("filter.history", data_dir.clone()),
|
||||||
command_history: HistoryContext::new("command.history"),
|
command_history: HistoryContext::new("command.history", data_dir.clone()),
|
||||||
history_status: None,
|
history_status: None,
|
||||||
completion_list: CompletionList::with_items(vec![]),
|
completion_list: CompletionList::with_items(vec![]),
|
||||||
show_completion_pane: false,
|
show_completion_pane: false,
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub fn generate_cli_app() -> clap::Command {
|
||||||
.short('c')
|
.short('c')
|
||||||
.long("config")
|
.long("config")
|
||||||
.value_name("FOLDER")
|
.value_name("FOLDER")
|
||||||
.help("Sets the config folder for taskwarrior-tui (currently not used)")
|
.help("Sets the config folder for taskwarrior-tui")
|
||||||
.action(clap::ArgAction::Set),
|
.action(clap::ArgAction::Set),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{collections::HashMap, error::Error, str};
|
use std::{collections::HashMap, error::Error, str};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use color_eyre::eyre::{eyre, Context, Result};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
symbols::{bar::FULL, line::DOUBLE_VERTICAL},
|
symbols::{bar::FULL, line::DOUBLE_VERTICAL},
|
||||||
|
@ -465,14 +465,14 @@ impl Config {
|
||||||
|
|
||||||
fn get_rule_precedence_color(data: &str) -> Vec<String> {
|
fn get_rule_precedence_color(data: &str) -> Vec<String> {
|
||||||
let data = Self::get_config("rule.precedence.color", data)
|
let data = Self::get_config("rule.precedence.color", data)
|
||||||
.context("Unable to parse `task show rule.precedence.color`.")
|
.ok_or_else(|| eyre!("Unable to parse `task show rule.precedence.color`."))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
data.split(',').map(ToString::to_string).collect::<Vec<_>>()
|
data.split(',').map(ToString::to_string).collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_uda_priority_values(data: &str) -> Vec<String> {
|
fn get_uda_priority_values(data: &str) -> Vec<String> {
|
||||||
let data = Self::get_config("uda.priority.values", data)
|
let data = Self::get_config("uda.priority.values", data)
|
||||||
.context("Unable to parse `task show uda.priority.values`.")
|
.ok_or_else(|| eyre!("Unable to parse `task show uda.priority.values`."))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
data.split(',').map(ToString::to_string).collect::<Vec<_>>()
|
data.split(',').map(ToString::to_string).collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
@ -489,7 +489,7 @@ impl Config {
|
||||||
|
|
||||||
fn get_data_location(data: &str) -> String {
|
fn get_data_location(data: &str) -> String {
|
||||||
Self::get_config("data.location", data)
|
Self::get_config("data.location", data)
|
||||||
.context("Unable to parse `task show data.location`.")
|
.ok_or_else(|| eyre!("Unable to parse `task show data.location`."))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use color_eyre::eyre::{anyhow, Result};
|
||||||
use rustyline::{
|
use rustyline::{
|
||||||
error::ReadlineError,
|
error::ReadlineError,
|
||||||
history::{DefaultHistory, History, SearchDirection},
|
history::{DefaultHistory, History, SearchDirection},
|
||||||
|
@ -16,17 +16,9 @@ pub struct HistoryContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HistoryContext {
|
impl HistoryContext {
|
||||||
pub fn new(filename: &str) -> Self {
|
pub fn new(filename: &str, data_path: PathBuf) -> Self {
|
||||||
let history = DefaultHistory::new();
|
let history = DefaultHistory::new();
|
||||||
|
|
||||||
let data_path = if let Ok(s) = std::env::var("TASKWARRIOR_TUI_DATA") {
|
|
||||||
PathBuf::from(s)
|
|
||||||
} else {
|
|
||||||
dirs::data_local_dir()
|
|
||||||
.map(|d| d.join("taskwarrior-tui"))
|
|
||||||
.expect("Unable to create configuration directory for taskwarrior-tui")
|
|
||||||
};
|
|
||||||
|
|
||||||
std::fs::create_dir_all(&data_path).unwrap_or_else(|_| panic!("Unable to create configuration directory in {:?}", &data_path));
|
std::fs::create_dir_all(&data_path).unwrap_or_else(|_| panic!("Unable to create configuration directory in {:?}", &data_path));
|
||||||
|
|
||||||
let data_path = data_path.join(filename);
|
let data_path = data_path.join(filename);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{collections::HashSet, error::Error, hash::Hash};
|
use std::{collections::HashSet, error::Error, hash::Hash};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use color_eyre::eyre::{anyhow, Result};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
166
src/main.rs
166
src/main.rs
|
@ -17,6 +17,7 @@ mod pane;
|
||||||
mod scrollbar;
|
mod scrollbar;
|
||||||
mod table;
|
mod table;
|
||||||
mod task_report;
|
mod task_report;
|
||||||
|
mod tui;
|
||||||
mod ui;
|
mod ui;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
@ -29,8 +30,8 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use app::{Mode, TaskwarriorTui};
|
use app::{Mode, TaskwarriorTui};
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor,
|
cursor,
|
||||||
event::{DisableMouseCapture, EnableMouseCapture, EventStream},
|
event::{DisableMouseCapture, EnableMouseCapture, EventStream},
|
||||||
|
@ -39,91 +40,20 @@ use crossterm::{
|
||||||
};
|
};
|
||||||
use futures::stream::{FuturesUnordered, StreamExt};
|
use futures::stream::{FuturesUnordered, StreamExt};
|
||||||
use log::{debug, error, info, log_enabled, trace, warn, Level, LevelFilter};
|
use log::{debug, error, info, log_enabled, trace, warn, Level, LevelFilter};
|
||||||
use log4rs::{
|
|
||||||
append::file::FileAppender,
|
|
||||||
config::{Appender, Config, Logger, Root},
|
|
||||||
encode::pattern::PatternEncoder,
|
|
||||||
};
|
|
||||||
use path_clean::PathClean;
|
|
||||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||||
|
use utils::{get_config_dir, get_data_dir};
|
||||||
|
|
||||||
use crate::{action::Action, event::Event, keyconfig::KeyConfig};
|
use crate::{
|
||||||
|
action::Action,
|
||||||
|
event::Event,
|
||||||
|
keyconfig::KeyConfig,
|
||||||
|
utils::{initialize_logging, initialize_panic_handler},
|
||||||
|
};
|
||||||
|
|
||||||
const LOG_PATTERN: &str = "{d(%Y-%m-%d %H:%M:%S)} | {l} | {f}:{L} | {m}{n}";
|
const LOG_PATTERN: &str = "{d(%Y-%m-%d %H:%M:%S)} | {l} | {f}:{L} | {m}{n}";
|
||||||
|
|
||||||
pub fn destruct_terminal() {
|
#[tokio::main]
|
||||||
disable_raw_mode().unwrap();
|
async fn main() -> Result<()> {
|
||||||
execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture).unwrap();
|
|
||||||
execute!(io::stdout(), cursor::Show).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn initialize_logging() {
|
|
||||||
let data_local_dir = if let Ok(s) = std::env::var("TASKWARRIOR_TUI_DATA") {
|
|
||||||
PathBuf::from(s)
|
|
||||||
} else {
|
|
||||||
dirs::data_local_dir()
|
|
||||||
.expect("Unable to find data directory for taskwarrior-tui")
|
|
||||||
.join("taskwarrior-tui")
|
|
||||||
};
|
|
||||||
|
|
||||||
std::fs::create_dir_all(&data_local_dir).unwrap_or_else(|_| panic!("Unable to create {:?}", data_local_dir));
|
|
||||||
|
|
||||||
let logfile = FileAppender::builder()
|
|
||||||
.encoder(Box::new(PatternEncoder::new(LOG_PATTERN)))
|
|
||||||
.append(false)
|
|
||||||
.build(data_local_dir.join("taskwarrior-tui.log"))
|
|
||||||
.expect("Failed to build log file appender.");
|
|
||||||
|
|
||||||
let levelfilter = match std::env::var("TASKWARRIOR_TUI_LOG_LEVEL").unwrap_or_else(|_| "info".to_string()).as_str() {
|
|
||||||
"off" => LevelFilter::Off,
|
|
||||||
"warn" => LevelFilter::Warn,
|
|
||||||
"info" => LevelFilter::Info,
|
|
||||||
"debug" => LevelFilter::Debug,
|
|
||||||
"trace" => LevelFilter::Trace,
|
|
||||||
_ => LevelFilter::Info,
|
|
||||||
};
|
|
||||||
let config = Config::builder()
|
|
||||||
.appender(Appender::builder().build("logfile", Box::new(logfile)))
|
|
||||||
.logger(Logger::builder().build("taskwarrior_tui", levelfilter))
|
|
||||||
.build(Root::builder().appender("logfile").build(LevelFilter::Info))
|
|
||||||
.expect("Failed to build logging config.");
|
|
||||||
|
|
||||||
log4rs::init_config(config).expect("Failed to initialize logging.");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn absolute_path(path: impl AsRef<Path>) -> io::Result<PathBuf> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
|
|
||||||
let absolute_path = if path.is_absolute() {
|
|
||||||
path.to_path_buf()
|
|
||||||
} else {
|
|
||||||
env::current_dir()?.join(path)
|
|
||||||
}
|
|
||||||
.clean();
|
|
||||||
|
|
||||||
Ok(absolute_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn tui_main(report: &str) -> Result<()> {
|
|
||||||
panic::set_hook(Box::new(|panic_info| {
|
|
||||||
destruct_terminal();
|
|
||||||
better_panic::Settings::auto().create_panic_handler()(panic_info);
|
|
||||||
}));
|
|
||||||
|
|
||||||
let mut app = app::TaskwarriorTui::new(report, true).await?;
|
|
||||||
|
|
||||||
let mut terminal = app.start_tui()?;
|
|
||||||
|
|
||||||
let r = app.run(&mut terminal).await;
|
|
||||||
|
|
||||||
app.pause_tui().await?;
|
|
||||||
|
|
||||||
r
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
better_panic::install();
|
|
||||||
|
|
||||||
let matches = cli::generate_cli_app().get_matches();
|
let matches = cli::generate_cli_app().get_matches();
|
||||||
|
|
||||||
let config = matches.get_one::<String>("config");
|
let config = matches.get_one::<String>("config");
|
||||||
|
@ -133,58 +63,42 @@ fn main() -> Result<()> {
|
||||||
let binding = String::from("next");
|
let binding = String::from("next");
|
||||||
let report = matches.get_one::<String>("report").unwrap_or(&binding);
|
let report = matches.get_one::<String>("report").unwrap_or(&binding);
|
||||||
|
|
||||||
if let Some(e) = config {
|
let config_dir = config.map(PathBuf::from).unwrap_or_else(get_config_dir);
|
||||||
if env::var("TASKWARRIOR_TUI_CONFIG").is_err() {
|
let data_dir = data.map(PathBuf::from).unwrap_or_else(get_data_dir);
|
||||||
// if environment variable is not set, this env::var returns an error
|
|
||||||
env::set_var(
|
|
||||||
"TASKWARRIOR_TUI_CONFIG",
|
|
||||||
absolute_path(PathBuf::from(e)).expect("Unable to get path for config"),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
warn!("TASKWARRIOR_TUI_CONFIG environment variable cannot be set.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(e) = data {
|
// if let Some(e) = taskrc {
|
||||||
if env::var("TASKWARRIOR_TUI_DATA").is_err() {
|
// if env::var("TASKRC").is_err() {
|
||||||
// if environment variable is not set, this env::var returns an error
|
// // if environment variable is not set, this env::var returns an error
|
||||||
env::set_var(
|
// env::set_var("TASKRC", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskrc"))
|
||||||
"TASKWARRIOR_TUI_DATA",
|
// } else {
|
||||||
absolute_path(PathBuf::from(e)).expect("Unable to get path for data"),
|
// warn!("TASKRC environment variable cannot be set.")
|
||||||
)
|
// }
|
||||||
} else {
|
// }
|
||||||
warn!("TASKWARRIOR_TUI_DATA environment variable cannot be set.")
|
//
|
||||||
}
|
// if let Some(e) = taskdata {
|
||||||
}
|
// if env::var("TASKDATA").is_err() {
|
||||||
|
// // if environment variable is not set, this env::var returns an error
|
||||||
|
// env::set_var("TASKDATA", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskdata"))
|
||||||
|
// } else {
|
||||||
|
// warn!("TASKDATA environment variable cannot be set.")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
if let Some(e) = taskrc {
|
initialize_logging(data_dir)?;
|
||||||
if env::var("TASKRC").is_err() {
|
initialize_panic_handler()?;
|
||||||
// if environment variable is not set, this env::var returns an error
|
|
||||||
env::set_var("TASKRC", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskrc"))
|
|
||||||
} else {
|
|
||||||
warn!("TASKRC environment variable cannot be set.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(e) = taskdata {
|
|
||||||
if env::var("TASKDATA").is_err() {
|
|
||||||
// if environment variable is not set, this env::var returns an error
|
|
||||||
env::set_var("TASKDATA", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskdata"))
|
|
||||||
} else {
|
|
||||||
warn!("TASKDATA environment variable cannot be set.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize_logging();
|
|
||||||
|
|
||||||
debug!("getting matches from clap...");
|
debug!("getting matches from clap...");
|
||||||
debug!("report = {:?}", &report);
|
debug!("report = {:?}", &report);
|
||||||
debug!("config = {:?}", &config);
|
debug!("config = {:?}", &config);
|
||||||
|
|
||||||
let r = tokio::runtime::Builder::new_multi_thread()
|
let mut app = app::TaskwarriorTui::new(report, true).await?;
|
||||||
.enable_all()
|
|
||||||
.build()?
|
let mut terminal = app.start_tui()?;
|
||||||
.block_on(async { tui_main(report).await });
|
|
||||||
|
let r = app.run(&mut terminal).await;
|
||||||
|
|
||||||
|
app.pause_tui().await?;
|
||||||
|
|
||||||
if let Err(err) = r {
|
if let Err(err) = r {
|
||||||
eprintln!("\x1b[0;31m[taskwarrior-tui error]\x1b[0m: {}\n\nIf you need additional help, please report as a github issue on https://github.com/kdheepak/taskwarrior-tui", err);
|
eprintln!("\x1b[0;31m[taskwarrior-tui error]\x1b[0m: {}\n\nIf you need additional help, please report as a github issue on https://github.com/kdheepak/taskwarrior-tui", err);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context as AnyhowContext, Result};
|
use color_eyre::eyre::{anyhow, Context as AnyhowContext, Result};
|
||||||
|
|
||||||
const NAME: &str = "Name";
|
const NAME: &str = "Name";
|
||||||
const TYPE: &str = "Remaining";
|
const TYPE: &str = "Remaining";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::ops::Index;
|
use std::ops::Index;
|
||||||
|
|
||||||
use anyhow::Result;
|
use color_eyre::eyre::Result;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::Action,
|
action::Action,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context as AnyhowContext, Result};
|
use color_eyre::eyre::{anyhow, Context as AnyhowContext, Result};
|
||||||
|
|
||||||
const COL_WIDTH: usize = 21;
|
const COL_WIDTH: usize = 21;
|
||||||
const PROJECT_HEADER: &str = "Name";
|
const PROJECT_HEADER: &str = "Name";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{error::Error, process::Command};
|
use std::{error::Error, process::Command};
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use chrono::{DateTime, Datelike, Local, NaiveDate, NaiveDateTime, TimeZone};
|
use chrono::{DateTime, Datelike, Local, NaiveDate, NaiveDateTime, TimeZone};
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
use itertools::join;
|
use itertools::join;
|
||||||
use task_hookrs::{task::Task, uda::UDAValue};
|
use task_hookrs::{task::Task, uda::UDAValue};
|
||||||
use unicode_truncate::UnicodeTruncateStr;
|
use unicode_truncate::UnicodeTruncateStr;
|
||||||
|
@ -14,7 +14,7 @@ pub fn format_date_time(dt: NaiveDateTime) -> String {
|
||||||
|
|
||||||
pub fn format_date(dt: NaiveDateTime) -> String {
|
pub fn format_date(dt: NaiveDateTime) -> String {
|
||||||
let offset = Local.offset_from_utc_datetime(&dt);
|
let offset = Local.offset_from_utc_datetime(&dt);
|
||||||
let dt = DateTime::<Local>::from_utc(dt, offset);
|
let dt = DateTime::<Local>::from_naive_utc_and_offset(dt, offset);
|
||||||
dt.format("%Y-%m-%d").to_string()
|
dt.format("%Y-%m-%d").to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
150
src/tui.rs
Normal file
150
src/tui.rs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
use crossterm::{
|
||||||
|
cursor,
|
||||||
|
event::{Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent},
|
||||||
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
|
};
|
||||||
|
use futures::{FutureExt, StreamExt};
|
||||||
|
use ratatui::backend::CrosstermBackend as Backend;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use tokio::{
|
||||||
|
sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
|
||||||
|
task::JoinHandle,
|
||||||
|
};
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
pub type CrosstermFrame<'a> = ratatui::Frame<'a, Backend<std::io::Stderr>>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
|
pub enum Event {
|
||||||
|
Quit,
|
||||||
|
Error,
|
||||||
|
Closed,
|
||||||
|
Tick,
|
||||||
|
Key(KeyEvent),
|
||||||
|
Mouse(MouseEvent),
|
||||||
|
Resize(u16, u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Tui {
|
||||||
|
pub terminal: ratatui::Terminal<Backend<std::io::Stderr>>,
|
||||||
|
pub task: JoinHandle<()>,
|
||||||
|
pub cancellation_token: CancellationToken,
|
||||||
|
pub event_rx: UnboundedReceiver<Event>,
|
||||||
|
pub event_tx: UnboundedSender<Event>,
|
||||||
|
pub tick_rate: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tui {
|
||||||
|
pub fn new(tick_rate: usize) -> Result<Self> {
|
||||||
|
let terminal = ratatui::Terminal::new(Backend::new(std::io::stderr()))?;
|
||||||
|
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
||||||
|
let cancellation_token = CancellationToken::new();
|
||||||
|
let task = tokio::spawn(async {});
|
||||||
|
Ok(Self {
|
||||||
|
terminal,
|
||||||
|
task,
|
||||||
|
cancellation_token,
|
||||||
|
event_rx,
|
||||||
|
event_tx,
|
||||||
|
tick_rate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&mut self) {
|
||||||
|
let tick_rate = std::time::Duration::from_millis(self.tick_rate as u64);
|
||||||
|
self.cancellation_token.cancel();
|
||||||
|
self.cancellation_token = CancellationToken::new();
|
||||||
|
let _cancellation_token = self.cancellation_token.clone();
|
||||||
|
let _event_tx = self.event_tx.clone();
|
||||||
|
self.task = tokio::spawn(async move {
|
||||||
|
let mut reader = crossterm::event::EventStream::new();
|
||||||
|
let mut interval = tokio::time::interval(tick_rate);
|
||||||
|
loop {
|
||||||
|
let delay = interval.tick();
|
||||||
|
let crossterm_event = reader.next().fuse();
|
||||||
|
tokio::select! {
|
||||||
|
_ = _cancellation_token.cancelled() => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
maybe_event = crossterm_event => {
|
||||||
|
match maybe_event {
|
||||||
|
Some(Ok(evt)) => {
|
||||||
|
match evt {
|
||||||
|
CrosstermEvent::Key(key) => {
|
||||||
|
if key.kind == KeyEventKind::Press {
|
||||||
|
_event_tx.send(Event::Key(key)).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CrosstermEvent::Resize(x, y) => {
|
||||||
|
_event_tx.send(Event::Resize(x, y)).unwrap();
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Err(_)) => {
|
||||||
|
_event_tx.send(Event::Error).unwrap();
|
||||||
|
}
|
||||||
|
None => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = delay => {
|
||||||
|
_event_tx.send(Event::Tick).unwrap();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enter(&mut self) -> Result<()> {
|
||||||
|
crossterm::terminal::enable_raw_mode()?;
|
||||||
|
crossterm::execute!(std::io::stderr(), EnterAlternateScreen, cursor::Hide)?;
|
||||||
|
self.start();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exit(&self) -> Result<()> {
|
||||||
|
crossterm::execute!(std::io::stderr(), LeaveAlternateScreen, cursor::Show)?;
|
||||||
|
crossterm::terminal::disable_raw_mode()?;
|
||||||
|
self.cancellation_token.cancel();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn suspend(&self) -> Result<()> {
|
||||||
|
self.exit()?;
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resume(&mut self) -> Result<()> {
|
||||||
|
self.enter()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn next(&mut self) -> Option<Event> {
|
||||||
|
self.event_rx.recv().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Tui {
|
||||||
|
type Target = ratatui::Terminal<Backend<std::io::Stderr>>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.terminal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Tui {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.terminal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Tui {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.exit().unwrap();
|
||||||
|
}
|
||||||
|
}
|
148
src/utils.rs
148
src/utils.rs
|
@ -15,3 +15,151 @@ impl ChangeListener for Changeset {
|
||||||
|
|
||||||
fn replace(&mut self, idx: usize, old: &str, new: &str) {}
|
fn replace(&mut self, idx: usize, old: &str, new: &str) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use color_eyre::eyre::{anyhow, Context, Result};
|
||||||
|
use directories::ProjectDirs;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use tracing::error;
|
||||||
|
use tracing_error::ErrorLayer;
|
||||||
|
use tracing_subscriber::{self, filter::EnvFilter, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer};
|
||||||
|
|
||||||
|
use crate::tui::Tui;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref CRATE_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string();
|
||||||
|
pub static ref DATA_FOLDER: Option<PathBuf> = std::env::var(format!("{}_DATA", CRATE_NAME.clone())).ok().map(PathBuf::from);
|
||||||
|
pub static ref CONFIG_FOLDER: Option<PathBuf> = std::env::var(format!("{}_CONFIG", CRATE_NAME.clone())).ok().map(PathBuf::from);
|
||||||
|
pub static ref GIT_COMMIT_HASH: String = std::env::var(format!("{}_GIT_INFO", CRATE_NAME.clone())).unwrap_or_else(|_| String::from("Unknown"));
|
||||||
|
pub static ref LOG_FILE: String = format!("{}.log", CRATE_NAME.to_lowercase());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn project_directory() -> Option<ProjectDirs> {
|
||||||
|
ProjectDirs::from("com", "kdheepak", CRATE_NAME.clone().to_lowercase().as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initialize_panic_handler() -> Result<()> {
|
||||||
|
let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default().into_hooks();
|
||||||
|
eyre_hook.install()?;
|
||||||
|
std::panic::set_hook(Box::new(move |panic_info| {
|
||||||
|
if let Ok(t) = Tui::new(0) {
|
||||||
|
if let Err(r) = t.exit() {
|
||||||
|
error!("Unable to exit Terminal: {:?}", r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let msg = format!("{}", panic_hook.panic_report(panic_info));
|
||||||
|
tracing::error!("{}", strip_ansi_escapes::strip_str(&msg));
|
||||||
|
use human_panic::{handle_dump, print_msg, Metadata};
|
||||||
|
let meta = Metadata {
|
||||||
|
version: env!("CARGO_PKG_VERSION").into(),
|
||||||
|
name: env!("CARGO_PKG_NAME").into(),
|
||||||
|
authors: env!("CARGO_PKG_AUTHORS").replace(':', ", ").into(),
|
||||||
|
homepage: env!("CARGO_PKG_HOMEPAGE").into(),
|
||||||
|
};
|
||||||
|
let file_path = handle_dump(&meta, panic_info);
|
||||||
|
print_msg(file_path, &meta).expect("human-panic: printing error message to console failed");
|
||||||
|
eprintln!("{}", msg);
|
||||||
|
std::process::exit(libc::EXIT_FAILURE);
|
||||||
|
}));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_data_dir() -> PathBuf {
|
||||||
|
let directory = if let Some(s) = DATA_FOLDER.clone() {
|
||||||
|
s
|
||||||
|
} else if let Some(proj_dirs) = project_directory() {
|
||||||
|
proj_dirs.data_local_dir().to_path_buf()
|
||||||
|
} else {
|
||||||
|
PathBuf::from(".").join(".data")
|
||||||
|
};
|
||||||
|
directory
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config_dir() -> PathBuf {
|
||||||
|
let directory = if let Some(s) = CONFIG_FOLDER.clone() {
|
||||||
|
s
|
||||||
|
} else if let Some(proj_dirs) = project_directory() {
|
||||||
|
proj_dirs.config_local_dir().to_path_buf()
|
||||||
|
} else {
|
||||||
|
PathBuf::from(".").join(".config")
|
||||||
|
};
|
||||||
|
directory
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initialize_logging(directory: PathBuf) -> Result<()> {
|
||||||
|
std::fs::create_dir_all(directory.clone())?;
|
||||||
|
let log_path = directory.join(LOG_FILE.clone());
|
||||||
|
let log_file = std::fs::File::create(log_path)?;
|
||||||
|
let file_subscriber = tracing_subscriber::fmt::layer()
|
||||||
|
.with_file(true)
|
||||||
|
.with_line_number(true)
|
||||||
|
.with_writer(log_file)
|
||||||
|
.with_target(false)
|
||||||
|
.with_ansi(false)
|
||||||
|
.with_filter(EnvFilter::from_default_env());
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(file_subscriber)
|
||||||
|
.with(tui_logger::tracing_subscriber_layer())
|
||||||
|
.with(ErrorLayer::default())
|
||||||
|
.init();
|
||||||
|
let default_level = std::env::var("RUST_LOG").map_or(log::LevelFilter::Info, |val| match val.to_lowercase().as_str() {
|
||||||
|
"off" => log::LevelFilter::Off,
|
||||||
|
"error" => log::LevelFilter::Error,
|
||||||
|
"warn" => log::LevelFilter::Warn,
|
||||||
|
"info" => log::LevelFilter::Info,
|
||||||
|
"debug" => log::LevelFilter::Debug,
|
||||||
|
"trace" => log::LevelFilter::Trace,
|
||||||
|
_ => log::LevelFilter::Info,
|
||||||
|
});
|
||||||
|
tui_logger::set_default_level(default_level);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to the `std::dbg!` macro, but generates `tracing` events rather
|
||||||
|
/// than printing to stdout.
|
||||||
|
///
|
||||||
|
/// By default, the verbosity level for the generated events is `DEBUG`, but
|
||||||
|
/// this can be customized.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! trace_dbg {
|
||||||
|
(target: $target:expr, level: $level:expr, $ex:expr) => {{
|
||||||
|
match $ex {
|
||||||
|
value => {
|
||||||
|
tracing::event!(target: $target, $level, ?value, stringify!($ex));
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
(level: $level:expr, $ex:expr) => {
|
||||||
|
trace_dbg!(target: module_path!(), level: $level, $ex)
|
||||||
|
};
|
||||||
|
(target: $target:expr, $ex:expr) => {
|
||||||
|
trace_dbg!(target: $target, level: tracing::Level::DEBUG, $ex)
|
||||||
|
};
|
||||||
|
($ex:expr) => {
|
||||||
|
trace_dbg!(level: tracing::Level::DEBUG, $ex)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version() -> String {
|
||||||
|
let author = clap::crate_authors!();
|
||||||
|
|
||||||
|
let commit_hash = GIT_COMMIT_HASH.clone();
|
||||||
|
|
||||||
|
// let current_exe_path = PathBuf::from(clap::crate_name!()).display().to_string();
|
||||||
|
let config_dir_path = get_config_dir().display().to_string();
|
||||||
|
let data_dir_path = get_data_dir().display().to_string();
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"\
|
||||||
|
{commit_hash}
|
||||||
|
|
||||||
|
Authors: {author}
|
||||||
|
|
||||||
|
Config directory: {config_dir_path}
|
||||||
|
Data directory: {data_dir_path}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue