aboutsummaryrefslogtreecommitdiffstats
path: root/crates/turtle/src/command/client/stats.rs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-11 00:54:30 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-11 00:54:30 +0200
commit5c39e7cf284a1f6e9a1657f2deb44e359fc47eb8 (patch)
treec64baa8d5866c8e339eaf660dd3f94f30a3f7d8a /crates/turtle/src/command/client/stats.rs
parentchore: Somewhat simplify sync code (diff)
downloadatuin-5c39e7cf284a1f6e9a1657f2deb44e359fc47eb8.zip
chore: Move everything into one big crate
That helps remove duplicated code and rustc/cargo will now also show dead code correctly.
Diffstat (limited to 'crates/turtle/src/command/client/stats.rs')
-rw-r--r--crates/turtle/src/command/client/stats.rs85
1 files changed, 85 insertions, 0 deletions
diff --git a/crates/turtle/src/command/client/stats.rs b/crates/turtle/src/command/client/stats.rs
new file mode 100644
index 00000000..fc10e949
--- /dev/null
+++ b/crates/turtle/src/command/client/stats.rs
@@ -0,0 +1,85 @@
+use clap::Parser;
+use eyre::Result;
+use interim::parse_date_string;
+use time::{Duration, OffsetDateTime, Time};
+
+use crate::atuin_client::{
+ database::{Database, current_context},
+ settings::Settings,
+ theme::Theme,
+};
+
+use crate::atuin_history::stats::{compute, pretty_print};
+
+fn parse_ngram_size(s: &str) -> Result<usize, String> {
+ let value = s
+ .parse::<usize>()
+ .map_err(|_| format!("'{s}' is not a valid window size"))?;
+
+ if value == 0 {
+ return Err("ngram window size must be at least 1".to_string());
+ }
+
+ Ok(value)
+}
+
+#[derive(Parser, Debug)]
+#[command(infer_subcommands = true)]
+pub struct Cmd {
+ /// Compute statistics for the specified period, leave blank for statistics since the beginning. See [this](https://docs.atuin.sh/reference/stats/) for more details.
+ period: Vec<String>,
+
+ /// How many top commands to list
+ #[arg(long, short, default_value = "10")]
+ count: usize,
+
+ /// The number of consecutive commands to consider
+ #[arg(long, short, default_value = "1", value_parser = parse_ngram_size)]
+ ngram_size: usize,
+}
+
+impl Cmd {
+ pub async fn run(&self, db: &impl Database, settings: &Settings, theme: &Theme) -> Result<()> {
+ let context = current_context().await?;
+ let words = if self.period.is_empty() {
+ String::from("all")
+ } else {
+ self.period.join(" ")
+ };
+
+ let now = OffsetDateTime::now_utc().to_offset(settings.timezone.0);
+ let last_night = now.replace_time(Time::MIDNIGHT);
+
+ let history = if words.as_str() == "all" {
+ db.list(&[], &context, None, false, false).await?
+ } else if words.trim() == "today" {
+ let start = last_night;
+ let end = start + Duration::days(1);
+ db.range(start, end).await?
+ } else if words.trim() == "month" {
+ let end = last_night;
+ let start = end - Duration::days(31);
+ db.range(start, end).await?
+ } else if words.trim() == "week" {
+ let end = last_night;
+ let start = end - Duration::days(7);
+ db.range(start, end).await?
+ } else if words.trim() == "year" {
+ let end = last_night;
+ let start = end - Duration::days(365);
+ db.range(start, end).await?
+ } else {
+ let start = parse_date_string(&words, now, settings.dialect.into())?;
+ let end = start + Duration::days(1);
+ db.range(start, end).await?
+ };
+
+ let stats = compute(settings, &history, self.count, self.ngram_size);
+
+ if let Some(stats) = stats {
+ pretty_print(stats, self.ngram_size, theme);
+ }
+
+ Ok(())
+ }
+}