mirror of
https://github.com/atuinsh/atuin.git
synced 2025-06-20 09:58:00 +02:00
Add timezone configuration option & CLI overrides (#1517)
* Allow specifying a timezone in history search/list * Fix clippy complaints * Add a bit more comment on supporting named timezones * Add rudimentary tests * Ditch local timezone test * Timezone configuration support * Set default timezone to `local` * `--tz` -> `--timezone` `--tz` is kept as a visible alias
This commit is contained in:
parent
8372abb613
commit
318bdd8955
133
Cargo.lock
generated
133
Cargo.lock
generated
@ -55,6 +55,21 @@ version = "0.2.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.11"
|
version = "0.6.11"
|
||||||
@ -240,6 +255,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_regex",
|
"serde_regex",
|
||||||
|
"serde_with",
|
||||||
"sha2",
|
"sha2",
|
||||||
"shellexpand",
|
"shellexpand",
|
||||||
"sql-builder",
|
"sql-builder",
|
||||||
@ -568,6 +584,19 @@ dependencies = [
|
|||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"windows-targets 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "cipher"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -865,6 +894,41 @@ dependencies = [
|
|||||||
"syn 2.0.48",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"darling_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "der"
|
name = "der"
|
||||||
version = "0.7.8"
|
version = "0.7.8"
|
||||||
@ -1567,6 +1631,35 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.59"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -1591,6 +1684,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown 0.12.3",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1601,6 +1695,7 @@ checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.14.3",
|
"hashbrown 0.14.3",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2946,6 +3041,35 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with"
|
||||||
|
version = "3.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f5c9fdb6b00a489875b22efd4b78fe2b363b72265cc5f6eb2e2b9ee270e6140c"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"chrono",
|
||||||
|
"hex",
|
||||||
|
"indexmap 1.9.3",
|
||||||
|
"indexmap 2.1.0",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_with_macros",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with_macros"
|
||||||
|
version = "3.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbff351eb4b33600a2e138dfa0b10b65a238ea8ff8fb2387c422c5022a3e8298"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@ -4088,6 +4212,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
|
@ -22,7 +22,7 @@ atuin-common = { path = "../atuin-common", version = "17.2.1" }
|
|||||||
|
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
time = { workspace = true }
|
time = { workspace = true, features = ["macros", "formatting"] }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
eyre = { workspace = true }
|
eyre = { workspace = true }
|
||||||
directories = { workspace = true }
|
directories = { workspace = true }
|
||||||
@ -53,6 +53,7 @@ thiserror = { workspace = true }
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
crypto_secretbox = "0.1.1"
|
crypto_secretbox = "0.1.1"
|
||||||
generic-array = { version = "0.14", features = ["serde"] }
|
generic-array = { version = "0.14", features = ["serde"] }
|
||||||
|
serde_with = "3.5.1"
|
||||||
|
|
||||||
# encryption
|
# encryption
|
||||||
rusty_paseto = { version = "0.6.0", default-features = false }
|
rusty_paseto = { version = "0.6.0", default-features = false }
|
||||||
|
@ -16,6 +16,12 @@
|
|||||||
## date format used, either "us" or "uk"
|
## date format used, either "us" or "uk"
|
||||||
# dialect = "us"
|
# dialect = "us"
|
||||||
|
|
||||||
|
## default timezone to use when displaying time
|
||||||
|
## either "l", "local" to use the system's current local timezone, or an offset
|
||||||
|
## from UTC in the format of "<+|->H[H][:M[M][:S[S]]]"
|
||||||
|
## for example: "+9", "-05", "+03:30", "-01:23:45", etc.
|
||||||
|
# timezone = "local"
|
||||||
|
|
||||||
## enable or disable automatic sync
|
## enable or disable automatic sync
|
||||||
# auto_sync = true
|
# auto_sync = true
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
|
fmt,
|
||||||
io::prelude::*,
|
io::prelude::*,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
@ -11,13 +12,18 @@ use clap::ValueEnum;
|
|||||||
use config::{
|
use config::{
|
||||||
builder::DefaultState, Config, ConfigBuilder, Environment, File as ConfigFile, FileFormat,
|
builder::DefaultState, Config, ConfigBuilder, Environment, File as ConfigFile, FileFormat,
|
||||||
};
|
};
|
||||||
use eyre::{eyre, Context, Result};
|
use eyre::{bail, eyre, Context, Error, Result};
|
||||||
use fs_err::{create_dir_all, File};
|
use fs_err::{create_dir_all, File};
|
||||||
use parse_duration::parse;
|
use parse_duration::parse;
|
||||||
use regex::RegexSet;
|
use regex::RegexSet;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
use serde_with::DeserializeFromStr;
|
||||||
|
use time::{
|
||||||
|
format_description::{well_known::Rfc3339, FormatItem},
|
||||||
|
macros::format_description,
|
||||||
|
OffsetDateTime, UtcOffset,
|
||||||
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub const HISTORY_PAGE_SIZE: i64 = 100;
|
pub const HISTORY_PAGE_SIZE: i64 = 100;
|
||||||
@ -123,6 +129,46 @@ impl From<Dialect> for interim::Dialect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type wrapper around `time::UtcOffset` to support a wider variety of timezone formats.
|
||||||
|
///
|
||||||
|
/// Note that the parsing of this struct needs to be done before starting any
|
||||||
|
/// multithreaded runtime, otherwise it will fail on most Unix systems.
|
||||||
|
///
|
||||||
|
/// See: https://github.com/atuinsh/atuin/pull/1517#discussion_r1447516426
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, DeserializeFromStr)]
|
||||||
|
pub struct Timezone(pub UtcOffset);
|
||||||
|
impl fmt::Display for Timezone {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// format: <+|-><hour>[:<minute>[:<second>]]
|
||||||
|
static OFFSET_FMT: &[FormatItem<'_>] =
|
||||||
|
format_description!("[offset_hour sign:mandatory padding:none][optional [:[offset_minute padding:none][optional [:[offset_second padding:none]]]]]");
|
||||||
|
impl FromStr for Timezone {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self> {
|
||||||
|
// local timezone
|
||||||
|
if matches!(s.to_lowercase().as_str(), "l" | "local") {
|
||||||
|
let offset = UtcOffset::current_local_offset()?;
|
||||||
|
return Ok(Self(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
// offset from UTC
|
||||||
|
if let Ok(offset) = UtcOffset::parse(s, OFFSET_FMT) {
|
||||||
|
return Ok(Self(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDEA: Currently named timezones are not supported, because the well-known crate
|
||||||
|
// for this is `chrono_tz`, which is not really interoperable with the datetime crate
|
||||||
|
// that we currently use - `time`. If ever we migrate to using `chrono`, this would
|
||||||
|
// be a good feature to add.
|
||||||
|
|
||||||
|
bail!(r#""{s}" is not a valid timezone spec"#)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Copy)]
|
#[derive(Clone, Debug, Deserialize, Copy)]
|
||||||
pub enum Style {
|
pub enum Style {
|
||||||
#[serde(rename = "auto")]
|
#[serde(rename = "auto")]
|
||||||
@ -251,6 +297,7 @@ pub struct Sync {
|
|||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub dialect: Dialect,
|
pub dialect: Dialect,
|
||||||
|
pub timezone: Timezone,
|
||||||
pub style: Style,
|
pub style: Style,
|
||||||
pub auto_sync: bool,
|
pub auto_sync: bool,
|
||||||
pub update_check: bool,
|
pub update_check: bool,
|
||||||
@ -305,11 +352,6 @@ pub struct Settings {
|
|||||||
// config! Keep secrets and settings apart.
|
// config! Keep secrets and settings apart.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub session_token: String,
|
pub session_token: String,
|
||||||
|
|
||||||
// This is determined at startup and cached.
|
|
||||||
// This is due to non-threadsafe get-env limitations.
|
|
||||||
#[serde(skip)]
|
|
||||||
pub local_tz: Option<time::UtcOffset>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
@ -488,6 +530,7 @@ impl Settings {
|
|||||||
.set_default("key_path", key_path.to_str())?
|
.set_default("key_path", key_path.to_str())?
|
||||||
.set_default("session_path", session_path.to_str())?
|
.set_default("session_path", session_path.to_str())?
|
||||||
.set_default("dialect", "us")?
|
.set_default("dialect", "us")?
|
||||||
|
.set_default("timezone", "local")?
|
||||||
.set_default("auto_sync", true)?
|
.set_default("auto_sync", true)?
|
||||||
.set_default("update_check", cfg!(feature = "check-update"))?
|
.set_default("update_check", cfg!(feature = "check-update"))?
|
||||||
.set_default("sync_address", "https://api.atuin.sh")?
|
.set_default("sync_address", "https://api.atuin.sh")?
|
||||||
@ -599,8 +642,6 @@ impl Settings {
|
|||||||
settings.session_token = String::from("not logged in");
|
settings.session_token = String::from("not logged in");
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.local_tz = time::UtcOffset::current_local_offset().ok();
|
|
||||||
|
|
||||||
Ok(settings)
|
Ok(settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -621,3 +662,42 @@ impl Default for Settings {
|
|||||||
.expect("Could not deserialize config")
|
.expect("Could not deserialize config")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use eyre::Result;
|
||||||
|
|
||||||
|
use super::Timezone;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_parse_offset_timezone_spec() -> Result<()> {
|
||||||
|
assert_eq!(Timezone::from_str("+02")?.0.as_hms(), (2, 0, 0));
|
||||||
|
assert_eq!(Timezone::from_str("-04")?.0.as_hms(), (-4, 0, 0));
|
||||||
|
assert_eq!(Timezone::from_str("+05:30")?.0.as_hms(), (5, 30, 0));
|
||||||
|
assert_eq!(Timezone::from_str("-09:30")?.0.as_hms(), (-9, -30, 0));
|
||||||
|
|
||||||
|
// single digit hours are allowed
|
||||||
|
assert_eq!(Timezone::from_str("+2")?.0.as_hms(), (2, 0, 0));
|
||||||
|
assert_eq!(Timezone::from_str("-4")?.0.as_hms(), (-4, 0, 0));
|
||||||
|
assert_eq!(Timezone::from_str("+5:30")?.0.as_hms(), (5, 30, 0));
|
||||||
|
assert_eq!(Timezone::from_str("-9:30")?.0.as_hms(), (-9, -30, 0));
|
||||||
|
|
||||||
|
// fully qualified form
|
||||||
|
assert_eq!(Timezone::from_str("+09:30:00")?.0.as_hms(), (9, 30, 0));
|
||||||
|
assert_eq!(Timezone::from_str("-09:30:00")?.0.as_hms(), (-9, -30, 0));
|
||||||
|
|
||||||
|
// these offsets don't really exist but are supported anyway
|
||||||
|
assert_eq!(Timezone::from_str("+0:5")?.0.as_hms(), (0, 5, 0));
|
||||||
|
assert_eq!(Timezone::from_str("-0:5")?.0.as_hms(), (0, -5, 0));
|
||||||
|
assert_eq!(Timezone::from_str("+01:23:45")?.0.as_hms(), (1, 23, 45));
|
||||||
|
assert_eq!(Timezone::from_str("-01:23:45")?.0.as_hms(), (-1, -23, -45));
|
||||||
|
|
||||||
|
// require a leading sign for clarity
|
||||||
|
assert!(Timezone::from_str("5").is_err());
|
||||||
|
assert!(Timezone::from_str("10:30").is_err());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -16,7 +16,7 @@ use atuin_client::{
|
|||||||
record::sqlite_store::SqliteStore,
|
record::sqlite_store::SqliteStore,
|
||||||
settings::{
|
settings::{
|
||||||
FilterMode::{Directory, Global, Session},
|
FilterMode::{Directory, Global, Session},
|
||||||
Settings,
|
Settings, Timezone,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -71,6 +71,14 @@ pub enum Cmd {
|
|||||||
#[arg(action = clap::ArgAction::Set)]
|
#[arg(action = clap::ArgAction::Set)]
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
|
|
||||||
|
/// Display the command time in another timezone other than the configured default.
|
||||||
|
///
|
||||||
|
/// This option takes one of the following kinds of values:
|
||||||
|
/// - the special value "local" (or "l") which refers to the system time zone
|
||||||
|
/// - an offset from UTC (e.g. "+9", "-2:30")
|
||||||
|
#[arg(long, visible_alias = "tz")]
|
||||||
|
timezone: Option<Timezone>,
|
||||||
|
|
||||||
/// Available variables: {command}, {directory}, {duration}, {user}, {host}, {exit} and {time}.
|
/// Available variables: {command}, {directory}, {duration}, {user}, {host}, {exit} and {time}.
|
||||||
/// Example: --format "{time} - [{duration}] - {directory}$\t{command}"
|
/// Example: --format "{time} - [{duration}] - {directory}$\t{command}"
|
||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
@ -86,6 +94,14 @@ pub enum Cmd {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
cmd_only: bool,
|
cmd_only: bool,
|
||||||
|
|
||||||
|
/// Display the command time in another timezone other than the configured default.
|
||||||
|
///
|
||||||
|
/// This option takes one of the following kinds of values:
|
||||||
|
/// - the special value "local" (or "l") which refers to the system time zone
|
||||||
|
/// - an offset from UTC (e.g. "+9", "-2:30")
|
||||||
|
#[arg(long, visible_alias = "tz")]
|
||||||
|
timezone: Option<Timezone>,
|
||||||
|
|
||||||
/// Available variables: {command}, {directory}, {duration}, {user}, {host} and {time}.
|
/// Available variables: {command}, {directory}, {duration}, {user}, {host} and {time}.
|
||||||
/// Example: --format "{time} - [{duration}] - {directory}$\t{command}"
|
/// Example: --format "{time} - [{duration}] - {directory}$\t{command}"
|
||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
@ -121,6 +137,7 @@ pub fn print_list(
|
|||||||
format: Option<&str>,
|
format: Option<&str>,
|
||||||
print0: bool,
|
print0: bool,
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
|
tz: Timezone,
|
||||||
) {
|
) {
|
||||||
let w = std::io::stdout();
|
let w = std::io::stdout();
|
||||||
let mut w = w.lock();
|
let mut w = w.lock();
|
||||||
@ -150,8 +167,12 @@ pub fn print_list(
|
|||||||
let entry_terminator = if print0 { "\0" } else { "\n" };
|
let entry_terminator = if print0 { "\0" } else { "\n" };
|
||||||
let flush_each_line = print0;
|
let flush_each_line = print0;
|
||||||
|
|
||||||
for h in iterator {
|
for history in iterator {
|
||||||
let fh = FmtHistory(h, CmdFormat::for_output(&w));
|
let fh = FmtHistory {
|
||||||
|
history,
|
||||||
|
cmd_format: CmdFormat::for_output(&w),
|
||||||
|
tz: &tz,
|
||||||
|
};
|
||||||
let args = parsed_fmt.with_args(&fh);
|
let args = parsed_fmt.with_args(&fh);
|
||||||
let write = write!(w, "{args}{entry_terminator}");
|
let write = write!(w, "{args}{entry_terminator}");
|
||||||
if let Err(err) = args.status() {
|
if let Err(err) = args.status() {
|
||||||
@ -179,14 +200,19 @@ fn check_for_write_errors(write: Result<(), io::Error>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper around `History` so we can format output dynamically at runtime
|
/// Type wrapper around `History` with formatting settings.
|
||||||
struct FmtHistory<'a>(&'a History, CmdFormat);
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
struct FmtHistory<'a> {
|
||||||
|
history: &'a History,
|
||||||
|
cmd_format: CmdFormat,
|
||||||
|
tz: &'a Timezone,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
enum CmdFormat {
|
enum CmdFormat {
|
||||||
Literal,
|
Literal,
|
||||||
Escaped,
|
Escaped,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CmdFormat {
|
impl CmdFormat {
|
||||||
fn for_output<O: IsTerminal>(out: &O) -> Self {
|
fn for_output<O: IsTerminal>(out: &O) -> Self {
|
||||||
if out.is_terminal() {
|
if out.is_terminal() {
|
||||||
@ -205,35 +231,41 @@ impl FormatKey for FmtHistory<'_> {
|
|||||||
#[allow(clippy::cast_sign_loss)]
|
#[allow(clippy::cast_sign_loss)]
|
||||||
fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> {
|
fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> {
|
||||||
match key {
|
match key {
|
||||||
"command" => match self.1 {
|
"command" => match self.cmd_format {
|
||||||
CmdFormat::Literal => f.write_str(self.0.command.trim()),
|
CmdFormat::Literal => f.write_str(self.history.command.trim()),
|
||||||
CmdFormat::Escaped => f.write_str(&self.0.command.trim().escape_control()),
|
CmdFormat::Escaped => f.write_str(&self.history.command.trim().escape_control()),
|
||||||
}?,
|
}?,
|
||||||
"directory" => f.write_str(self.0.cwd.trim())?,
|
"directory" => f.write_str(self.history.cwd.trim())?,
|
||||||
"exit" => f.write_str(&self.0.exit.to_string())?,
|
"exit" => f.write_str(&self.history.exit.to_string())?,
|
||||||
"duration" => {
|
"duration" => {
|
||||||
let dur = Duration::from_nanos(std::cmp::max(self.0.duration, 0) as u64);
|
let dur = Duration::from_nanos(std::cmp::max(self.history.duration, 0) as u64);
|
||||||
format_duration_into(dur, f)?;
|
format_duration_into(dur, f)?;
|
||||||
}
|
}
|
||||||
"time" => {
|
"time" => {
|
||||||
self.0
|
self.history
|
||||||
.timestamp
|
.timestamp
|
||||||
|
.to_offset(self.tz.0)
|
||||||
.format(TIME_FMT)
|
.format(TIME_FMT)
|
||||||
.map_err(|_| fmt::Error)?
|
.map_err(|_| fmt::Error)?
|
||||||
.fmt(f)?;
|
.fmt(f)?;
|
||||||
}
|
}
|
||||||
"relativetime" => {
|
"relativetime" => {
|
||||||
let since = OffsetDateTime::now_utc() - self.0.timestamp;
|
let since = OffsetDateTime::now_utc() - self.history.timestamp;
|
||||||
let d = Duration::try_from(since).unwrap_or_default();
|
let d = Duration::try_from(since).unwrap_or_default();
|
||||||
format_duration_into(d, f)?;
|
format_duration_into(d, f)?;
|
||||||
}
|
}
|
||||||
"host" => f.write_str(
|
"host" => f.write_str(
|
||||||
self.0
|
self.history
|
||||||
.hostname
|
.hostname
|
||||||
.split_once(':')
|
.split_once(':')
|
||||||
.map_or(&self.0.hostname, |(host, _)| host),
|
.map_or(&self.history.hostname, |(host, _)| host),
|
||||||
|
)?,
|
||||||
|
"user" => f.write_str(
|
||||||
|
self.history
|
||||||
|
.hostname
|
||||||
|
.split_once(':')
|
||||||
|
.map_or("", |(_, user)| user),
|
||||||
)?,
|
)?,
|
||||||
"user" => f.write_str(self.0.hostname.split_once(':').map_or("", |(_, user)| user))?,
|
|
||||||
_ => return Err(FormatKeyError::UnknownKey),
|
_ => return Err(FormatKeyError::UnknownKey),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -353,6 +385,7 @@ impl Cmd {
|
|||||||
include_deleted: bool,
|
include_deleted: bool,
|
||||||
print0: bool,
|
print0: bool,
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
|
tz: Timezone,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let filters = match (session, cwd) {
|
let filters = match (session, cwd) {
|
||||||
(true, true) => [Session, Directory],
|
(true, true) => [Session, Directory],
|
||||||
@ -374,6 +407,7 @@ impl Cmd {
|
|||||||
},
|
},
|
||||||
print0,
|
print0,
|
||||||
reverse,
|
reverse,
|
||||||
|
tz,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -411,11 +445,13 @@ impl Cmd {
|
|||||||
cmd_only,
|
cmd_only,
|
||||||
print0,
|
print0,
|
||||||
reverse,
|
reverse,
|
||||||
|
timezone,
|
||||||
format,
|
format,
|
||||||
} => {
|
} => {
|
||||||
let mode = ListMode::from_flags(human, cmd_only);
|
let mode = ListMode::from_flags(human, cmd_only);
|
||||||
|
let tz = timezone.unwrap_or(settings.timezone);
|
||||||
Self::handle_list(
|
Self::handle_list(
|
||||||
db, settings, context, session, cwd, mode, format, false, print0, reverse,
|
db, settings, context, session, cwd, mode, format, false, print0, reverse, tz,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@ -423,10 +459,12 @@ impl Cmd {
|
|||||||
Self::Last {
|
Self::Last {
|
||||||
human,
|
human,
|
||||||
cmd_only,
|
cmd_only,
|
||||||
|
timezone,
|
||||||
format,
|
format,
|
||||||
} => {
|
} => {
|
||||||
let last = db.last().await?;
|
let last = db.last().await?;
|
||||||
let last = last.as_ref().map(std::slice::from_ref).unwrap_or_default();
|
let last = last.as_ref().map(std::slice::from_ref).unwrap_or_default();
|
||||||
|
let tz = timezone.unwrap_or(settings.timezone);
|
||||||
print_list(
|
print_list(
|
||||||
last,
|
last,
|
||||||
ListMode::from_flags(human, cmd_only),
|
ListMode::from_flags(human, cmd_only),
|
||||||
@ -436,6 +474,7 @@ impl Cmd {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
tz,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -10,7 +10,7 @@ use atuin_client::{
|
|||||||
encryption,
|
encryption,
|
||||||
history::{store::HistoryStore, History},
|
history::{store::HistoryStore, History},
|
||||||
record::sqlite_store::SqliteStore,
|
record::sqlite_store::SqliteStore,
|
||||||
settings::{FilterMode, KeymapMode, SearchMode, Settings},
|
settings::{FilterMode, KeymapMode, SearchMode, Settings, Timezone},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::history::ListMode;
|
use super::history::ListMode;
|
||||||
@ -101,6 +101,14 @@ pub struct Cmd {
|
|||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
|
|
||||||
|
/// Display the command time in another timezone other than the configured default.
|
||||||
|
///
|
||||||
|
/// This option takes one of the following kinds of values:
|
||||||
|
/// - the special value "local" (or "l") which refers to the system time zone
|
||||||
|
/// - an offset from UTC (e.g. "+9", "-2:30")
|
||||||
|
#[arg(long, visible_alias = "tz")]
|
||||||
|
timezone: Option<Timezone>,
|
||||||
|
|
||||||
/// Available variables: {command}, {directory}, {duration}, {user}, {host}, {time}, {exit} and
|
/// Available variables: {command}, {directory}, {duration}, {user}, {host}, {time}, {exit} and
|
||||||
/// {relativetime}.
|
/// {relativetime}.
|
||||||
/// Example: --format "{time} - [{duration}] - {directory}$\t{command}"
|
/// Example: --format "{time} - [{duration}] - {directory}$\t{command}"
|
||||||
@ -220,12 +228,15 @@ impl Cmd {
|
|||||||
None => Some(settings.history_format.as_str()),
|
None => Some(settings.history_format.as_str()),
|
||||||
_ => self.format.as_deref(),
|
_ => self.format.as_deref(),
|
||||||
};
|
};
|
||||||
|
let tz = self.timezone.unwrap_or(settings.timezone);
|
||||||
|
|
||||||
super::history::print_list(
|
super::history::print_list(
|
||||||
&entries,
|
&entries,
|
||||||
ListMode::from_flags(self.human, self.cmd_only),
|
ListMode::from_flags(self.human, self.cmd_only),
|
||||||
format,
|
format,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
tz,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -86,8 +86,7 @@ impl Cmd {
|
|||||||
self.period.join(" ")
|
self.period.join(" ")
|
||||||
};
|
};
|
||||||
|
|
||||||
let now = OffsetDateTime::now_utc();
|
let now = OffsetDateTime::now_utc().to_offset(settings.timezone.0);
|
||||||
let now = settings.local_tz.map_or(now, |local| now.to_offset(local));
|
|
||||||
let last_night = now.replace_time(Time::MIDNIGHT);
|
let last_night = now.replace_time(Time::MIDNIGHT);
|
||||||
|
|
||||||
let history = if words.as_str() == "all" {
|
let history = if words.as_str() == "all" {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user