diff --git a/Cargo.lock b/Cargo.lock index 18230ed..c337d7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,6 +120,27 @@ dependencies = [ "owo-colors", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + [[package]] name = "dirstat" version = "0.1.0" @@ -136,6 +157,7 @@ dependencies = [ "num_cpus", "once_cell", "pin-project", + "rustyline", "serde", "serde_cbor", "smallvec", @@ -158,6 +180,16 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "fs_extra" version = "1.2.0" @@ -275,6 +307,17 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.23.0" @@ -464,6 +507,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nix" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -566,12 +621,58 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + [[package]] name = "rustc-demangle" version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" +[[package]] +name = "rustyline" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8227301bfc717136f0ecbd3d064ba8199e44497a0bdd46bb01ede4387cfd2cec" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "dirs-next", + "fs2", + "libc", + "log", + "memchr", + "nix", + "scopeguard", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi 0.3.9", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "serde" version = "1.0.123" @@ -680,12 +781,36 @@ dependencies = [ "syn", ] +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + [[package]] name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "winapi" version = "0.2.8" diff --git a/Cargo.toml b/Cargo.toml index 35eb35b..13ab3bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,9 @@ panic = "unwind" [features] default = ["splash", "inspect", "defer-drop", "jemalloc", "prealloc"] +# When using the REPL to inspect graphs, save command history. +save-history = [] + # Use jemalloc as global allocator instead of system allocator. # May potentially cause some speedups and better memory profile on large runs. jemalloc = ["jemallocator"] @@ -44,6 +47,7 @@ memmap = {version = "0.7.0", optional = true} num_cpus = "1.13.0" once_cell = "1.5.2" pin-project = "1.0.5" +rustyline = "7.1.0" serde = {version = "1.0.123", features=["derive"], optional=true} serde_cbor = {version = "0.11.1", optional=true} smallvec = "1.6.1" diff --git a/src/info.rs b/src/info/mod.rs similarity index 97% rename from src/info.rs rename to src/info/mod.rs index 8367cb8..27087a7 100644 --- a/src/info.rs +++ b/src/info/mod.rs @@ -4,6 +4,8 @@ use super::*; use data::HierarchicalINodeGraph; use config::Config; +pub mod repl; + /// Print the most basic info pub fn print_basic_max_info(cfg: &Config, graph: &HierarchicalINodeGraph) { diff --git a/src/info/repl.rs b/src/info/repl.rs new file mode 100644 index 0000000..d72070b --- /dev/null +++ b/src/info/repl.rs @@ -0,0 +1,124 @@ +//! Graph inspection REPL +use super::*; +use std::{fmt, error}; +use std::path::PathBuf; +use std::io; + +use rustyline::error::ReadlineError; +use rustyline::Editor; + +/// Default history file name +/// +/// # Path lookup +/// * To make this an absolute path, start it with `/` +/// * To make this path relative to the user's home directory, start it with `~/` (Note: If we are unable to find the user's home directory, it is considered a lookup **failure** (*not* a **disable**) and `calculate_history_path()` will return `Err`.) +/// * Otherwise, the path is taken relative to the current working directory +/// +/// # Notes +/// This is only used when the `save-history` feature is enabled. +const DEFAULT_HISTORY_FILE: &'static str = "~/.dirstat_history"; + +/// Get the path to the history file. +/// +/// # Lookup +/// * If the `DIRSTAT_HISTORY` envvar is set and not empty, use this file path. +/// * If the `DIRSTAT_HISTORY` envvar is set and empty, saving history is considered **disabled**, we return `Ok(None)`. +/// * Otherwise, refer to lookup rules for `DEFAULT_HISTORY_FILE`. +pub fn calculate_history_path() -> io::Result> +{ + cfg_if! { + if #[cfg(feature="save-history")] { + todo!() + } else { + unreachable!("Tried to calculate repl history path when binary was compiled with history saving perma-disabled.") + } + } +} + +/// Inspect the graph with commands +pub fn inspect(cfg: &Config, graph: &HierarchicalINodeGraph) -> Result<(), ReplExitError> +{ + let mut repl = Editor::<()>::new(); //TODO: Change `()` to our completer, when we have a decent idea of how they'll work. + + cfg_if! { + if #[cfg(feature="save-history")] { + let history_path = match calculate_history_path() { + Ok(Some(path)) => { + if let Err(err) = repl.load_history(&path) + { + cfg_eprintln!(cfg, "Failed to load repl history from {:?}: {}", path, err); + } + Some(path) + }, + Ok(None) => None, + Err(err) + { + cfg_eprintln!(cfg, "Failed to find repl history: {}", err); + None + } + } + } + } + + let res: Result<(), ReplExitError> = try { + loop { + let line = repl.readline("> ")?; + repl.add_history_entry(&line); + + //TODO: How to interpret commands? + todo!("Interpret commands from `line`."); + } + }; + + cfg_if! { + if #[cfg(feature="save-history")] { + if let Some(path) = history_path { + if let Err(err) = repl.save_history(&path) + { + cfg_eprintln!(cfg, "Failed to save repl history to {:?}: {}", path, err); + } + } + } + } + + res +} + +/// When the inspection repl exists abnormally. +#[derive(Debug)] +pub enum ReplExitError +{ + ReadLine(ReadlineError), +} + +impl From for ReplExitError +{ + #[inline] fn from(from: ReadlineError) -> Self + { + Self::ReadLine(from) + } +} + +impl error::Error for ReplExitError +{ + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(match &self + { + Self::ReadLine(rl) => rl + }) + } +} +impl fmt::Display for ReplExitError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self + { + Self::ReadLine(ReadlineError::Eof) | + Self::ReadLine(ReadlineError::Interrupted) => write!(f, "exit"), + Self::ReadLine(_) => write!(f, "readline error"), + } + } +} + + diff --git a/src/main.rs b/src/main.rs index ae62660..f264790 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#![feature(try_blocks)] #![allow(dead_code)]