aboutsummaryrefslogtreecommitdiffstats
path: root/crates/turtle/src/command/client/wrapped.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/turtle/src/command/client/wrapped.rs')
-rw-r--r--crates/turtle/src/command/client/wrapped.rs327
1 files changed, 0 insertions, 327 deletions
diff --git a/crates/turtle/src/command/client/wrapped.rs b/crates/turtle/src/command/client/wrapped.rs
deleted file mode 100644
index 2ce19bf7..00000000
--- a/crates/turtle/src/command/client/wrapped.rs
+++ /dev/null
@@ -1,327 +0,0 @@
-use crossterm::style::{ResetColor, SetAttribute};
-use eyre::Result;
-use std::collections::{HashMap, HashSet};
-use time::{Date, Duration, Month, OffsetDateTime, Time};
-
-use crate::atuin_client::database::ClientSqlite;
-use crate::atuin_client::settings::Settings;
-
-use crate::atuin_history::stats::{Stats, compute};
-
-#[derive(Debug)]
-struct WrappedStats {
- nav_commands: usize,
- pkg_commands: usize,
- error_rate: f64,
- first_half_commands: Vec<(String, usize)>,
- second_half_commands: Vec<(String, usize)>,
- git_percentage: f64,
- busiest_hour: Option<(String, usize)>,
-}
-
-impl WrappedStats {
- #[expect(clippy::too_many_lines, clippy::cast_precision_loss)]
- fn new(
- settings: &Settings,
- stats: &Stats,
- history: &[crate::atuin_client::history::History],
- ) -> Self {
- let nav_commands = stats
- .top
- .iter()
- .filter(|(cmd, _)| {
- let cmd = &cmd[0];
- cmd == "cd"
- || cmd == "ls"
- || cmd == "ll"
- || cmd == "pwd"
- || cmd == "pushd"
- || cmd == "popd"
- })
- .map(|(_, count)| count)
- .sum();
-
- let pkg_managers = [
- "cargo",
- "npm",
- "pnpm",
- "yarn",
- "pip",
- "pip3",
- "pipenv",
- "poetry",
- "pipx",
- "uv",
- "brew",
- "apt",
- "apt-get",
- "apk",
- "pacman",
- "yay",
- "paru",
- "yum",
- "dnf",
- "dnf5",
- "rpm",
- "rpm-ostree",
- "zypper",
- "pkg",
- "chocolatey",
- "choco",
- "scoop",
- "winget",
- "gem",
- "bundle",
- "shards",
- "composer",
- "gradle",
- "maven",
- "mvn",
- "go get",
- "nuget",
- "dotnet",
- "mix",
- "hex",
- "rebar3",
- "nix",
- "nix-env",
- "cabal",
- "opam",
- ];
-
- let pkg_commands = history
- .iter()
- .filter(|h| {
- let cmd = h.command.clone();
- pkg_managers.iter().any(|pm| cmd.starts_with(pm))
- })
- .count();
-
- // Error analysis
- let mut command_errors: HashMap<String, (usize, usize)> = HashMap::new(); // (total_uses, errors)
- let midyear = history[0].timestamp + Duration::days(182); // Split year in half
-
- let mut first_half_commands: HashMap<String, usize> = HashMap::new();
- let mut second_half_commands: HashMap<String, usize> = HashMap::new();
- let mut hours: HashMap<String, usize> = HashMap::new();
-
- for entry in history {
- let cmd = entry
- .command
- .split_whitespace()
- .next()
- .unwrap_or("")
- .to_string();
- let (total, errors) = command_errors.entry(cmd.clone()).or_insert((0, 0));
- *total += 1;
- if entry.exit != 0 {
- *errors += 1;
- }
-
- // Track command evolution
- if entry.timestamp < midyear {
- *first_half_commands.entry(cmd.clone()).or_default() += 1;
- } else {
- *second_half_commands.entry(cmd).or_default() += 1;
- }
-
- // Track hourly distribution
- let local_time = entry
- .timestamp
- .to_offset(time::UtcOffset::current_local_offset().unwrap_or(settings.timezone.0));
- let hour = format!("{:02}:00", local_time.time().hour());
- *hours.entry(hour).or_default() += 1;
- }
-
- let total_errors: usize = command_errors.values().map(|(_, errors)| errors).sum();
- let total_commands: usize = command_errors.values().map(|(total, _)| total).sum();
- let error_rate = total_errors as f64 / total_commands as f64;
-
- // Process command evolution data
- let mut first_half: Vec<_> = first_half_commands.into_iter().collect();
- let mut second_half: Vec<_> = second_half_commands.into_iter().collect();
- first_half.sort_by_key(|(_, count)| std::cmp::Reverse(*count));
- second_half.sort_by_key(|(_, count)| std::cmp::Reverse(*count));
- first_half.truncate(5);
- second_half.truncate(5);
-
- // Calculate git percentage
- let git_commands: usize = stats
- .top
- .iter()
- .filter(|(cmd, _)| cmd[0].starts_with("git"))
- .map(|(_, count)| count)
- .sum();
- let git_percentage = git_commands as f64 / stats.total_commands as f64;
-
- // Find busiest hour
- let busiest_hour = hours.into_iter().max_by_key(|(_, count)| *count);
-
- Self {
- nav_commands,
- pkg_commands,
- error_rate,
- first_half_commands: first_half,
- second_half_commands: second_half,
- git_percentage,
- busiest_hour,
- }
- }
-}
-
-pub(crate) fn print_wrapped_header(year: i32) {
- let reset = ResetColor;
- let bold = SetAttribute(crossterm::style::Attribute::Bold);
-
- println!("{bold}╭────────────────────────────────────╮{reset}");
- println!("{bold}│ ATUIN WRAPPED {year} │{reset}");
- println!("{bold}│ Your Year in Shell History │{reset}");
- println!("{bold}╰────────────────────────────────────╯{reset}");
- println!();
-}
-
-#[expect(clippy::cast_precision_loss)]
-fn print_fun_facts(wrapped_stats: &WrappedStats, stats: &Stats, year: i32) {
- let reset = ResetColor;
- let bold = SetAttribute(crossterm::style::Attribute::Bold);
-
- if wrapped_stats.git_percentage > 0.05 {
- println!(
- "{bold}🌟 You're a Git Power User!{reset} {bold}{:.1}%{reset} of your commands were Git operations\n",
- wrapped_stats.git_percentage * 100.0
- );
- }
- // Navigation patterns
- let nav_percentage = wrapped_stats.nav_commands as f64 / stats.total_commands as f64 * 100.0;
- if nav_percentage > 0.05 {
- println!(
- "{bold}🚀 You're a Navigator!{reset} {bold}{nav_percentage:.1}%{reset} of your time was spent navigating directories\n",
- );
- }
-
- // Command vocabulary
- println!(
- "{bold}📚 Command Vocabulary{reset}: You know {bold}{}{reset} unique commands\n",
- stats.unique_commands
- );
-
- // Package management
- println!(
- "{bold}📦 Package Management{reset}: You ran {bold}{}{reset} package-related commands\n",
- wrapped_stats.pkg_commands
- );
-
- // Error patterns
- let error_percentage = wrapped_stats.error_rate * 100.0;
- println!(
- "{bold}🚨 Error Analysis{reset}: Your commands failed {bold}{error_percentage:.1}%{reset} of the time\n",
- );
-
- // Command evolution
- println!("🔍 Command Evolution:");
-
- // print stats for each half and compare
- println!(" {bold}Top Commands{reset} in the first half of {year}:");
- for (cmd, count) in wrapped_stats.first_half_commands.iter().take(3) {
- println!(" {bold}{cmd}{reset} ({count} times)");
- }
-
- println!(" {bold}Top Commands{reset} in the second half of {year}:");
- for (cmd, count) in wrapped_stats.second_half_commands.iter().take(3) {
- println!(" {bold}{cmd}{reset} ({count} times)");
- }
-
- // Find new favorite commands (in top 5 of second half but not in first half)
- let first_half_set: HashSet<_> = wrapped_stats
- .first_half_commands
- .iter()
- .map(|(cmd, _)| cmd)
- .collect();
- let new_favorites: Vec<_> = wrapped_stats
- .second_half_commands
- .iter()
- .filter(|(cmd, _)| !first_half_set.contains(cmd))
- .take(2)
- .collect();
-
- if !new_favorites.is_empty() {
- println!(" {bold}New favorites{reset} in the second half:");
- for (cmd, count) in new_favorites {
- println!(" {bold}{cmd}{reset} ({count} times)");
- }
- }
-
- // Time patterns
- if let Some((hour, count)) = &wrapped_stats.busiest_hour {
- println!("\n🕘 Most Productive Hour: {bold}{hour}{reset} ({count} commands)");
-
- // Night owl or early bird
- let hour_num = hour
- .split(':')
- .next()
- .unwrap_or("0")
- .parse::<u32>()
- .unwrap_or(0);
- if hour_num >= 22 || hour_num <= 4 {
- println!(" You're quite the night owl! 🦉");
- } else if (5..=7).contains(&hour_num) {
- println!(" Early bird gets the worm! 🐦");
- }
- }
-
- println!();
-}
-
-pub(crate) async fn run(year: Option<i32>, db: &ClientSqlite, settings: &Settings) -> Result<()> {
- let now = OffsetDateTime::now_utc().to_offset(settings.timezone.0);
- let month = now.month();
-
- // If we're in December, then wrapped is for the current year. If not, it's for the previous year
- let year = year.unwrap_or_else(|| {
- if month == Month::December {
- now.year()
- } else {
- now.year() - 1
- }
- });
-
- let start = OffsetDateTime::new_in_offset(
- Date::from_calendar_date(year, Month::January, 1).unwrap(),
- Time::MIDNIGHT,
- now.offset(),
- );
- let end = OffsetDateTime::new_in_offset(
- Date::from_calendar_date(year, Month::December, 31).unwrap(),
- Time::MIDNIGHT + Duration::days(1) - Duration::nanoseconds(1),
- now.offset(),
- );
-
- let history = db.range(start, end).await?;
- if history.is_empty() {
- println!(
- "Your history for {year} is empty!\nMaybe 'atuin import' could help you import your previous history 🪄"
- );
- return Ok(());
- }
-
- // Compute overall stats using existing functionality
- let stats = compute(settings, &history, 10, 1).expect("Failed to compute stats");
- let wrapped_stats = WrappedStats::new(settings, &stats, &history);
-
- // Print wrapped format
- print_wrapped_header(year);
-
- println!("🎉 In {year}, you typed {} commands!", stats.total_commands);
- println!(
- " That's ~{} commands every day\n",
- stats.total_commands / 365
- );
-
- println!("Your Top Commands:");
- crate::atuin_history::stats::pretty_print(stats.clone(), 1);
- println!();
-
- print_fun_facts(&wrapped_stats, &stats, year);
-
- Ok(())
-}