Add sessions
This commit is contained in:
parent
099afe66ec
commit
440c4fc233
9 changed files with 130 additions and 37 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -49,12 +49,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "atuin"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"directories",
|
||||
"eyre",
|
||||
"home",
|
||||
"hostname",
|
||||
"indicatif",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
|
@ -314,6 +315,17 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"match_cfg",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.3.0"
|
||||
|
@ -373,6 +385,12 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_cfg"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.4"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "atuin"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Ellie Huxtable <e@elm.sh>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
|
@ -17,6 +17,7 @@ directories = "3.*"
|
|||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
home = "0.5.3"
|
||||
indicatif = "0.15.0"
|
||||
hostname = "0.3.1"
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "0.24.*"
|
||||
|
|
13
README.md
13
README.md
|
@ -5,4 +5,15 @@
|
|||
Through the fathomless deeps of space swims the star turtle Great A’Tuin, bearing on its back the four giant elephants who carry on their shoulders the mass of the Discworld.
|
||||
</blockquote>
|
||||
|
||||
`atuin` manages and synchronizes your shell history!
|
||||
`atuin` manages and synchronizes your shell history! Instead of storing
|
||||
everything in a text file (such as ~/.history), `atuin` uses a sqlite database.
|
||||
This lets us do all kinds of analysis on it!
|
||||
|
||||
As well as the expected command, this stores
|
||||
|
||||
- duration
|
||||
- exit code
|
||||
- working directory
|
||||
- hostname
|
||||
- time
|
||||
- a unique session ID
|
||||
|
|
|
@ -32,13 +32,21 @@ pub enum HistoryCmd {
|
|||
}
|
||||
|
||||
impl HistoryCmd {
|
||||
pub fn run(&self, db: SqliteDatabase) -> Result<()> {
|
||||
pub fn run(&self, db: &mut SqliteDatabase) -> Result<()> {
|
||||
match self {
|
||||
HistoryCmd::Start { command: words } => {
|
||||
let command = words.join(" ");
|
||||
let cwd = env::current_dir()?.display().to_string();
|
||||
|
||||
let h = History::new(chrono::Utc::now().timestamp_nanos(), command, cwd, -1, -1);
|
||||
let h = History::new(
|
||||
chrono::Utc::now().timestamp_nanos(),
|
||||
command,
|
||||
cwd,
|
||||
-1,
|
||||
-1,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
// print the ID
|
||||
// we use this as the key for calling end
|
||||
|
|
|
@ -87,12 +87,23 @@ impl ImportCmd {
|
|||
}
|
||||
|
||||
pub fn run(&self, db: &mut SqliteDatabase) -> Result<()> {
|
||||
println!(" A'Tuin ");
|
||||
println!("=====================");
|
||||
println!(" 🌍 ");
|
||||
println!(" 🐘🐘🐘🐘 ");
|
||||
println!(" 🐢 ");
|
||||
println!("=====================");
|
||||
println!("Importing history...");
|
||||
|
||||
match self {
|
||||
ImportCmd::Auto => {
|
||||
let shell = env::var("SHELL").unwrap_or(String::from("NO_SHELL"));
|
||||
|
||||
match shell.as_str() {
|
||||
"/bin/zsh" => self.import_zsh(db),
|
||||
"/bin/zsh" => {
|
||||
println!("Detected ZSH");
|
||||
self.import_zsh(db)
|
||||
}
|
||||
|
||||
_ => {
|
||||
println!("cannot import {} history", shell);
|
||||
|
|
|
@ -8,7 +8,7 @@ use rusqlite::{params, Connection};
|
|||
use crate::History;
|
||||
|
||||
pub trait Database {
|
||||
fn save(&self, h: History) -> Result<()>;
|
||||
fn save(&mut self, h: History) -> Result<()>;
|
||||
fn save_bulk(&mut self, h: &Vec<History>) -> Result<()>;
|
||||
fn load(&self, id: &str) -> Result<History>;
|
||||
fn list(&self) -> Result<()>;
|
||||
|
@ -53,6 +53,8 @@ impl SqliteDatabase {
|
|||
exit integer not null,
|
||||
command text not null,
|
||||
cwd text not null,
|
||||
session text not null,
|
||||
hostname text not null,
|
||||
|
||||
unique(timestamp, cwd, command)
|
||||
)",
|
||||
|
@ -64,22 +66,11 @@ impl SqliteDatabase {
|
|||
}
|
||||
|
||||
impl Database for SqliteDatabase {
|
||||
fn save(&self, h: History) -> Result<()> {
|
||||
fn save(&mut self, h: History) -> Result<()> {
|
||||
debug!("saving history to sqlite");
|
||||
let v = vec![h];
|
||||
|
||||
self.conn.execute(
|
||||
"insert or ignore into history (
|
||||
id,
|
||||
timestamp,
|
||||
duration,
|
||||
exit,
|
||||
command,
|
||||
cwd
|
||||
) values (?1, ?2, ?3, ?4, ?5, ?6)",
|
||||
params![h.id, h.timestamp, h.duration, h.exit, h.command, h.cwd],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
self.save_bulk(&v)
|
||||
}
|
||||
|
||||
fn save_bulk(&mut self, h: &Vec<History>) -> Result<()> {
|
||||
|
@ -95,9 +86,20 @@ impl Database for SqliteDatabase {
|
|||
duration,
|
||||
exit,
|
||||
command,
|
||||
cwd
|
||||
) values (?1, ?2, ?3, ?4, ?5, ?6)",
|
||||
params![i.id, i.timestamp, i.duration, i.exit, i.command, i.cwd],
|
||||
cwd,
|
||||
session,
|
||||
hostname
|
||||
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
||||
params![
|
||||
i.id,
|
||||
i.timestamp,
|
||||
i.duration,
|
||||
i.exit,
|
||||
i.command,
|
||||
i.cwd,
|
||||
i.session,
|
||||
i.hostname
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
||||
|
@ -110,7 +112,7 @@ impl Database for SqliteDatabase {
|
|||
debug!("loading history item");
|
||||
|
||||
let mut stmt = self.conn.prepare(
|
||||
"select id, timestamp, duration, exit, command, cwd from history
|
||||
"select id, timestamp, duration, exit, command, cwd, session, hostname from history
|
||||
where id = ?1",
|
||||
)?;
|
||||
|
||||
|
@ -122,6 +124,8 @@ impl Database for SqliteDatabase {
|
|||
exit: row.get(3)?,
|
||||
command: row.get(4)?,
|
||||
cwd: row.get(5)?,
|
||||
session: row.get(6)?,
|
||||
hostname: row.get(7)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
|
@ -137,9 +141,9 @@ impl Database for SqliteDatabase {
|
|||
|
||||
self.conn.execute(
|
||||
"update history
|
||||
set timestamp = ?2, duration = ?3, exit = ?4, command = ?5, cwd = ?6
|
||||
set timestamp = ?2, duration = ?3, exit = ?4, command = ?5, cwd = ?6, session = ?7, hostname = ?8
|
||||
where id = ?1",
|
||||
params![h.id, h.timestamp, h.duration, h.exit, h.command, h.cwd],
|
||||
params![h.id, h.timestamp, h.duration, h.exit, h.command, h.cwd, h.session, h.hostname],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -148,9 +152,9 @@ impl Database for SqliteDatabase {
|
|||
fn list(&self) -> Result<()> {
|
||||
debug!("listing history");
|
||||
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT id, timestamp, duration, exit, command, cwd FROM history")?;
|
||||
let mut stmt = self.conn.prepare(
|
||||
"SELECT id, timestamp, duration, exit, command, cwd, session, hostname FROM history",
|
||||
)?;
|
||||
|
||||
let history_iter = stmt.query_map(params![], |row| {
|
||||
Ok(History {
|
||||
|
@ -160,6 +164,8 @@ impl Database for SqliteDatabase {
|
|||
exit: row.get(3)?,
|
||||
command: row.get(4)?,
|
||||
cwd: row.get(5)?,
|
||||
session: row.get(6)?,
|
||||
hostname: row.get(7)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
|
@ -167,8 +173,8 @@ impl Database for SqliteDatabase {
|
|||
let h = h.unwrap();
|
||||
|
||||
println!(
|
||||
"{} | {} | {} | {} | {}",
|
||||
h.timestamp, h.cwd, h.duration, h.exit, h.command
|
||||
"{} | {} | {} | {} | {} | {} | {}",
|
||||
h.timestamp, h.hostname, h.session, h.cwd, h.duration, h.exit, h.command
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use chrono;
|
||||
use std::env;
|
||||
|
||||
use hostname;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -9,10 +11,32 @@ pub struct History {
|
|||
pub exit: i64,
|
||||
pub command: String,
|
||||
pub cwd: String,
|
||||
pub session: String,
|
||||
pub hostname: String,
|
||||
}
|
||||
|
||||
impl History {
|
||||
pub fn new(timestamp: i64, command: String, cwd: String, exit: i64, duration: i64) -> History {
|
||||
pub fn new(
|
||||
timestamp: i64,
|
||||
command: String,
|
||||
cwd: String,
|
||||
exit: i64,
|
||||
duration: i64,
|
||||
session: Option<String>,
|
||||
hostname: Option<String>,
|
||||
) -> History {
|
||||
// get the current session or just generate a random string
|
||||
let env_session =
|
||||
env::var("ATUIN_SESSION").unwrap_or(Uuid::new_v4().to_simple().to_string());
|
||||
|
||||
// best attempt at getting the current hostname, or just unknown
|
||||
let os_hostname = hostname::get().unwrap();
|
||||
let os_hostname = os_hostname.to_str().unwrap();
|
||||
let os_hostname = String::from(os_hostname);
|
||||
|
||||
let session = session.unwrap_or(env_session);
|
||||
let hostname = hostname.unwrap_or(os_hostname);
|
||||
|
||||
History {
|
||||
id: Uuid::new_v4().to_simple().to_string(),
|
||||
timestamp,
|
||||
|
@ -20,6 +44,8 @@ impl History {
|
|||
cwd,
|
||||
exit,
|
||||
duration,
|
||||
session,
|
||||
hostname,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,11 +70,13 @@ fn parse_extended(line: String) -> History {
|
|||
|
||||
// use nanos, because why the hell not? we won't display them.
|
||||
History::new(
|
||||
Utc.timestamp(time, 0).timestamp_nanos(),
|
||||
time * 1_000_000_000,
|
||||
trim_newline(command),
|
||||
String::from("unknown"),
|
||||
-1,
|
||||
duration * 1_000_000_000,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -104,6 +106,8 @@ impl Iterator for ImportZsh {
|
|||
String::from("unknown"),
|
||||
-1,
|
||||
-1,
|
||||
None,
|
||||
None,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -3,6 +3,7 @@ use std::path::PathBuf;
|
|||
use directories::ProjectDirs;
|
||||
use eyre::{eyre, Result};
|
||||
use structopt::StructOpt;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
@ -40,8 +41,11 @@ enum AtuinCmd {
|
|||
#[structopt(about = "import shell history from file")]
|
||||
Import(ImportCmd),
|
||||
|
||||
#[structopt(about = "start a atuin server")]
|
||||
#[structopt(about = "start an atuin server")]
|
||||
Server,
|
||||
|
||||
#[structopt(about = "generates a UUID")]
|
||||
Uuid,
|
||||
}
|
||||
|
||||
impl Atuin {
|
||||
|
@ -66,8 +70,12 @@ impl Atuin {
|
|||
let mut db = SqliteDatabase::new(db_path)?;
|
||||
|
||||
match self.atuin {
|
||||
AtuinCmd::History(history) => history.run(db),
|
||||
AtuinCmd::History(history) => history.run(&mut db),
|
||||
AtuinCmd::Import(import) => import.run(&mut db),
|
||||
AtuinCmd::Uuid => {
|
||||
println!("{}", Uuid::new_v4().to_simple().to_string());
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue