Compare commits

..

8 Commits

132
Cargo.lock generated

@ -120,6 +120,27 @@ dependencies = [
"owo-colors", "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]] [[package]]
name = "dirstat" name = "dirstat"
version = "0.1.0" version = "0.1.0"
@ -136,10 +157,12 @@ dependencies = [
"num_cpus", "num_cpus",
"once_cell", "once_cell",
"pin-project", "pin-project",
"rustyline",
"serde", "serde",
"serde_cbor", "serde_cbor",
"smallvec", "smallvec",
"tokio", "tokio",
"treemap",
] ]
[[package]] [[package]]
@ -158,6 +181,16 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 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]] [[package]]
name = "fs_extra" name = "fs_extra"
version = "1.2.0" version = "1.2.0"
@ -275,6 +308,17 @@ dependencies = [
"slab", "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]] [[package]]
name = "gimli" name = "gimli"
version = "0.23.0" version = "0.23.0"
@ -464,6 +508,18 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.13.0" version = "1.13.0"
@ -566,12 +622,58 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.18" version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" 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]] [[package]]
name = "serde" name = "serde"
version = "1.0.123" version = "1.0.123"
@ -680,12 +782,42 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "treemap"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1571f89da27a5e1aa83304ee1ab9519ea8c6432b4c8903aaaa6c9a9eecb6f36"
[[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]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 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]] [[package]]
name = "winapi" name = "winapi"
version = "0.2.8" version = "0.2.8"

@ -13,7 +13,10 @@ codegen-units = 1
panic = "unwind" panic = "unwind"
[features] [features]
default = ["splash", "inspect", "defer-drop", "jemalloc", "prealloc"] default = ["splash", "inspect", "defer-drop", "jemalloc", "prealloc", "treemap"]
# When using the REPL to inspect graphs, save command history.
save-history = []
# Use jemalloc as global allocator instead of system allocator. # Use jemalloc as global allocator instead of system allocator.
# May potentially cause some speedups and better memory profile on large runs. # May potentially cause some speedups and better memory profile on large runs.
@ -44,7 +47,9 @@ memmap = {version = "0.7.0", optional = true}
num_cpus = "1.13.0" num_cpus = "1.13.0"
once_cell = "1.5.2" once_cell = "1.5.2"
pin-project = "1.0.5" pin-project = "1.0.5"
rustyline = "7.1.0"
serde = {version = "1.0.123", features=["derive"], optional=true} serde = {version = "1.0.123", features=["derive"], optional=true}
serde_cbor = {version = "0.11.1", optional=true} serde_cbor = {version = "0.11.1", optional=true}
smallvec = "1.6.1" smallvec = "1.6.1"
tokio = {version = "0.2", features=["full"]} tokio = {version = "0.2", features=["full"]}
treemap = {version = "0.3.2", optional=true}

@ -4,7 +4,34 @@ use std::collections::{HashMap, HashSet};
use std::mem::Discriminant; use std::mem::Discriminant;
use std::fmt; use std::fmt;
use config::OutputSerialisationMode; #[cfg(feature="inspect")] use config::OutputSerialisationMode;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum InspectKind
{
Treemap(Option<(u64, u64)>),
}
impl fmt::Display for InspectKind
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
match self {
Self::Treemap(None) => write!(f, "treemap"),
Self::Treemap(Some((x,y))) => write!(f, "treemap:{}:{}", x, y), // Width and height.
}
}
}
impl std::str::FromStr for InspectKind
{
type Err = eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err>
{
todo!()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Argument pub enum Argument
@ -29,6 +56,8 @@ pub enum Argument
StopReading, StopReading,
Inspect(InspectKind),
Input(String), Input(String),
} }
@ -65,23 +94,14 @@ impl Argument
_ => return None, _ => return None,
}) })
} }
/// Convert into a mode change if this is a mode change
// We consume the whole output here in case the mode change needs to traverse it for information. As of now, we have only one non-`Normal` mode: `Help`, which doesn't require any extra information.
#[deprecated(note = "mode selection happens in `into_mode` and early return through `Continue::Abort` will be removed or reworked to not require this function.")]
pub fn try_into_mode(self, _rest: Output) -> Result<Mode, (Self, Output)>
{
match self {
Self::ModeChangeHelp => Ok(Mode::Help),
no => Err((no, _rest)),
}
}
/// Insert this `Argument` into config /// Insert this `Argument` into config
pub fn insert_into_cfg(self, cfg: &mut Config) pub fn insert_into_cfg(self, cfg: &mut Config)
{ {
use Argument::*; use Argument::*;
match self { match self {
Inspect(InspectKind::Treemap(None)) => cfg.inspection.treemap = Some((640, 480)),
Inspect(InspectKind::Treemap(x)) => cfg.inspection.treemap = x,
LimitConcMaxProc => cfg.max_tasks = config::max_tasks_cpus(), LimitConcMaxProc => cfg.max_tasks = config::max_tasks_cpus(),
LimitConc(max) => cfg.max_tasks = Some(max), LimitConc(max) => cfg.max_tasks = Some(max),
UnlimitConc => cfg.max_tasks = None, UnlimitConc => cfg.max_tasks = None,
@ -120,6 +140,8 @@ impl fmt::Display for Argument
use Argument::*; use Argument::*;
match self match self
{ {
Inspect(ins) => write!(f, "--inspect {}", ins),
ModeChangeHelp => write!(f, "--help"), ModeChangeHelp => write!(f, "--help"),
LimitConcMaxProc => write!(f, "-m"), LimitConcMaxProc => write!(f, "-m"),
LimitConc(limit) => write!(f, "--threads {}", limit), LimitConc(limit) => write!(f, "--threads {}", limit),
@ -377,7 +399,12 @@ where I: Iterator<Item=String>
let mut keep_reading = Continue::Yes; let mut keep_reading = Continue::Yes;
let item = match this.trim() let item = match this.trim()
{ {
"--threads" => { "--inspect" => {
let ins = args.next().ok_or(eyre!("`--inspect` expects a parameter"))
.with_suggestion(suggestion_intended_arg.clone())?;
Argument::Inspect(ins.parse().wrap_err(eyre!("Failed to parse parameter for `--inspect`"))?)
},
" --threads" => {
let max = args.next().ok_or(eyre!("`--threads` expects a parameter")) let max = args.next().ok_or(eyre!("`--threads` expects a parameter"))
.with_suggestion(suggestion_intended_arg.clone())?; .with_suggestion(suggestion_intended_arg.clone())?;
match NonZeroUsize::new(max.parse::<usize>() match NonZeroUsize::new(max.parse::<usize>()

@ -121,6 +121,25 @@ impl OutputSerialisationMode
} }
} }
/// What to do with the graph afterwards?
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Inspection
{
pub treemap: Option<(u64, u64)>, //w and h
}
impl Default for Inspection
{
#[inline]
fn default() -> Self
{
Self {
treemap: None
}
}
}
/// Configuration for this run /// Configuration for this run
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Config pub struct Config
@ -133,6 +152,8 @@ pub struct Config
#[cfg(feature="inspect")] #[cfg(feature="inspect")]
pub serialise_output: Option<OutputSerialisationMode>, pub serialise_output: Option<OutputSerialisationMode>,
pub inspection: Inspection,
} }
impl Config impl Config
@ -188,6 +209,8 @@ impl Default for Config
output_level: Default::default(), output_level: Default::default(),
#[cfg(feature="inspect")] #[cfg(feature="inspect")]
serialise_output: None, serialise_output: None,
inspection: Default::default(),
} }
} }
} }

@ -15,8 +15,123 @@ pub struct INodeInfoGraph
children: HashMap<INode, Vec<INode>>, //reverse lookup for directory INodes and their parent INodes children: HashMap<INode, Vec<INode>>, //reverse lookup for directory INodes and their parent INodes
} }
#[derive(Debug, Clone)]
pub struct INodeInfoGraphEntry<'a>
{
master: &'a INodeInfoGraph,
inode: INode,
}
impl<'a> INodeInfoGraphEntry<'a>
{
/// Create an iterator over children of this graph item.
///
/// # Note
/// Returns `None` if this is not a directory item.
pub fn level(&self) -> Option<Level<'a>>
{
match self.master.inodes.get(&self.inode)
{
Some(FsInfo::Directory(parent)) => {
Some(
Level{
master: self.master,
inode: self.inode.clone(),
children: match self.master.children.get(&self.inode) {
Some(iter) => iter.iter(),
_ => [].iter(),
},
}
)
},
Some(FsInfo::File(_, _)) => None,
None => {
panic!("No lookup information for inode {:?}", self.inode)
}//Some(Level{master: self.master, inode: None, children: [].iter()}),
}
}
/// The `FsInfo` for this item.
pub fn info(&self) -> &FsInfo
{
self.master.inodes.get(&self.inode).unwrap()
}
/// The path for this inode
pub fn path(&self) -> &PathBuf
{
self.master.paths_reverse.get(&self.inode).unwrap()
}
}
#[derive(Debug, Clone)]
pub struct Level<'a>
{
master: &'a INodeInfoGraph,
inode: INode,// Should only ever be `Directory` for `Level`.
children: std::slice::Iter<'a, INode>,
}
#[derive(Debug, Clone)]
pub struct TopLevel<'a>
{
master: &'a INodeInfoGraph,
children: std::vec::IntoIter<&'a INode>,
}
impl<'a> Iterator for TopLevel<'a>
{
type Item = INodeInfoGraphEntry<'a>;
fn next(&mut self) -> Option<Self::Item>
{
let master = self.master;
self.children.next().map(|inode| {
INodeInfoGraphEntry{
master,
inode: inode.clone(),
}
})
}
}
impl<'a> Iterator for Level<'a>
{
type Item = INodeInfoGraphEntry<'a>;
fn next(&mut self) -> Option<Self::Item>
{
let master = self.master;
self.children.next().map(|inode| {
INodeInfoGraphEntry{
master,
inode: inode.clone(),
}
})
}
}
impl INodeInfoGraph impl INodeInfoGraph
{ {
/// Total size of all files
pub fn total_size(&self) -> u64
{
self.inodes.iter().map(|(_, v)| {
match v {
FsInfo::File(len, _) => *len,
_ => 0,
}
}).sum()
}
/// An iterator over the top level of items
pub fn top_level(&self) -> TopLevel<'_>
{
let top_level = self.inodes.iter().filter(|(node, _)| {
!self.inodes.contains_key(node)
});
TopLevel {
master: self,
children: top_level.map(|(k, _)| k).collect::<Vec<_>>().into_iter(),
}
}
/// Create a new graph from these linked `HashMap`s /// Create a new graph from these linked `HashMap`s
#[inline] pub fn new(inodes: HashMap<INode, FsInfo>, paths: HashMap<PathBuf, INode>) -> Self #[inline] pub fn new(inodes: HashMap<INode, FsInfo>, paths: HashMap<PathBuf, INode>) -> Self
{ {
@ -48,6 +163,11 @@ impl INodeInfoGraph
self.paths_reverse.extend(self.paths.iter().map(|(x,y)| (*y,x.clone()))); self.paths_reverse.extend(self.paths.iter().map(|(x,y)| (*y,x.clone())));
self self
} }
/// Total number of items in the graph
#[inline] pub fn len(&self) -> usize
{
self.children.len()
}
/// Get the FsInfo of this `INode` /// Get the FsInfo of this `INode`
#[inline] pub fn get_info(&self, node: impl Borrow<INode>) -> Option<&FsInfo> #[inline] pub fn get_info(&self, node: impl Borrow<INode>) -> Option<&FsInfo>
{ {
@ -57,17 +177,17 @@ impl INodeInfoGraph
/// An iterator over top-level children of this node /// An iterator over top-level children of this node
pub fn children_of(&self, node: impl Borrow<INode>) -> !//Children<'_> pub fn children_of(&self, node: impl Borrow<INode>) -> !//Children<'_>
{ {
todo!(); todo!();
/*Children(self, match self.children.get(node.borrow()) { /*Children(self, match self.children.get(node.borrow()) {
Some(slc) => slc.iter(), Some(slc) => slc.iter(),
_ => [].iter(), _ => [].iter(),
})*/ })*/
} }
/// An iterator over all the directories in this /// An iterator over all the directories in this
pub fn directories(&self) -> !//Directories<'_> pub fn directories(&self) -> !//Directories<'_>
{ {
todo!()//Directories(self, self.children.keys()) todo!()//Directories(self, self.children.keys())
}*/ }*/
/// Convert into a hierarchical representation /// Convert into a hierarchical representation
pub fn into_hierarchical(self) -> HierarchicalINodeGraph pub fn into_hierarchical(self) -> HierarchicalINodeGraph
@ -264,6 +384,12 @@ impl HierarchicalINodeGraph
hi.len().map(|x| (path.as_path(), x)) hi.len().map(|x| (path.as_path(), x))
}).max_by_key(|x| x.1) }).max_by_key(|x| x.1)
} }
/// Complete number of items in the tree
#[inline] pub fn len(&self) -> usize
{
self.table.len()
}
} }
impl From<INodeInfoGraph> for HierarchicalINodeGraph impl From<INodeInfoGraph> for HierarchicalINodeGraph

@ -18,6 +18,7 @@ pub use graph::{
INodeInfoGraph, INodeInfoGraph,
HierarchicalINodeGraph, HierarchicalINodeGraph,
FsObjKind as FsKind, FsObjKind as FsKind,
INodeInfoGraphEntry,
}; };
/// A raw file or directory inode number /// A raw file or directory inode number

@ -18,6 +18,8 @@ pub mod prelude
pub use super::StreamGateExt as _; pub use super::StreamGateExt as _;
pub use super::StreamLagExt as _; pub use super::StreamLagExt as _;
pub use super::INodeExt as _; pub use super::INodeExt as _;
pub use super::OptionIterator;
pub use super::MaybeVec; pub use super::MaybeVec;
} }
@ -203,6 +205,63 @@ where S: Stream
} }
} }
/// An iterator that can be constructed from an `Option<Iterator>`.
#[derive(Debug, Clone)]
pub struct OptionIterator<I>(Option<I>);
impl<I> OptionIterator<I>
{
/// Consume into the inner `Option`.
#[inline] pub fn into_inner(self) -> Option<I>
{
self.0
}
/// Does this `OptionIterator` have a value?
pub fn is_some(&self) -> bool
{
self.0.is_some()
}
}
impl<I: Iterator> From<Option<I>> for OptionIterator<I>
{
fn from(from: Option<I>) -> Self
{
Self(from)
}
}
impl<I: Iterator> Iterator for OptionIterator<I>
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item>
{
self.0.as_mut().map(|x| x.next()).flatten()
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.0.as_ref() {
Some(i) => i.size_hint(),
_ => (0, Some(0)),
}
}
}
impl<I: Iterator> std::iter::FusedIterator for OptionIterator<I>
where I: std::iter::FusedIterator{}
impl<I: Iterator> ExactSizeIterator for OptionIterator<I>
where I: ExactSizeIterator{}
impl<I: Iterator> DoubleEndedIterator for OptionIterator<I>
where I: DoubleEndedIterator
{
fn next_back(&mut self) -> Option<Self::Item> {
self.0.as_mut().map(|x| x.next_back()).flatten()
}
}
/// Create a duration with time suffix `h`, `m`, `s`, `ms` or `ns`. /// Create a duration with time suffix `h`, `m`, `s`, `ms` or `ns`.
/// ///
/// # Combination /// # Combination

@ -0,0 +1,124 @@
use super::*;
use treemap::{
Rect,
Mappable,
TreemapLayout
};
use data::{FsInfo, INodeInfoGraph, INodeInfoGraphEntry};
/// A treemap of all **files** in the graph.
#[derive(Debug, Clone, PartialEq)]
pub struct Treemap
{
//layout: TreemapLayout,
nodes: Vec<MapNode>,
}
impl Treemap
{
/// All nodes of the map.
#[inline] pub fn nodes(&self) -> &[MapNode]
{
&self.nodes[..]
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct MapNode
{
name: String,
vw_size: f64, // Should be halved each iteration
vw_bounds: Rect, // should be Rect::new() before aligntment
}
impl MapNode
{
/// The calculated bounds of the node
#[inline] pub fn bounds(&self) -> &Rect
{
&self.vw_bounds
}
/// The name of the node
#[inline] pub fn name(&self) -> &str
{
&self.name[..]
}
#[inline] fn size(&self) -> f64 //Is this useful for consumer?
{
self.vw_size
}
#[inline] fn new(name: String) -> Self
{
Self {
vw_size: 1.0,
vw_bounds: Rect::new(),
name,
}
}
}
/// Create a treemap from this graph.
pub fn treemap(_cfg: &Config, graph: &INodeInfoGraph, (w, h): (f64, f64)) -> Treemap
{
let layout = TreemapLayout::new();
let mut nodes = Vec::with_capacity(graph.len());
//TODO: Recursively walk the graph, halving size with each iteration. (Maybe we need `INodeInfoGraph` here, not `Hierarchicalinodegraph`?)
let total_size = graph.total_size();
let size = 1.0;
fn calc_path<'a, I: IntoIterator<Item = INodeInfoGraphEntry<'a>>>(insert: &'a mut Vec<MapNode>, from: I, total_size: u64, size: f64, scale: f64)
{
for top in from {
let path = top.path();
match top.info() {
FsInfo::Directory(_) => {
//TODO: Do we add dir itself? I think not?
// Add children
let size = size * 0.5;
calc_path(insert, top.level().unwrap(), total_size, size, scale);
},
&FsInfo::File(sz, _) => {
let fract = (sz as f64) / (total_size as f64);
insert.push(MapNode {
name: path.to_string_lossy().into_owned(),
vw_size: fract * scale,
vw_bounds: Rect::new(),
})
},
}
}
}
calc_path(&mut nodes, graph.top_level(), total_size, size, 1.0);
layout.layout_items(&mut nodes[..], Rect {
x: 0.0,
y: 0.0,
w, h,
});
Treemap {
//layout,
nodes
}
}
impl Mappable for MapNode
{
fn size(&self) -> f64
{
self.vw_size
}
fn bounds(&self) -> &Rect
{
&self.vw_bounds
}
fn set_bounds(&mut self, bounds: Rect)
{
self.vw_bounds = bounds;
}
}

@ -4,6 +4,8 @@ use super::*;
use data::HierarchicalINodeGraph; use data::HierarchicalINodeGraph;
use config::Config; use config::Config;
//pub mod repl;
/// Print the most basic info /// Print the most basic info
pub fn print_basic_max_info(cfg: &Config, graph: &HierarchicalINodeGraph) pub fn print_basic_max_info(cfg: &Config, graph: &HierarchicalINodeGraph)
{ {
@ -11,3 +13,9 @@ pub fn print_basic_max_info(cfg: &Config, graph: &HierarchicalINodeGraph)
cfg_println!(Quiet; cfg, "Max size dir: {:?}", graph.path_max_size_for(data::FsKind::Directory)); cfg_println!(Quiet; cfg, "Max size dir: {:?}", graph.path_max_size_for(data::FsKind::Directory));
cfg_println!(Quiet; cfg, "Max size all: {:?}", graph.path_max_size()); cfg_println!(Quiet; cfg, "Max size all: {:?}", graph.path_max_size());
} }
#[cfg(feature="treemap")]
mod map;
#[cfg(feature="treemap")]
pub use map::*;

@ -0,0 +1,131 @@
//! Graph inspection REPL
use super::*;
use std::{fmt, error};
use std::path::PathBuf;
use std::io;
use rustyline::error::ReadlineError;
use rustyline::Editor;
mod env;
mod command;
mod opcodes;
/// 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<Option<PathBuf>>
{
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
///
/// # Note
/// This function synchronously blocks the current thread.
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<ReadlineError> 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"),
}
}
}

@ -0,0 +1,54 @@
//! Repl commands
use super::*;
use std::str::FromStr;
use super::env::*;
#[derive(Debug)]
pub struct Context<'a>
{
/// Environment containing variable name mappings.
env: &'a mut Lexenv,
}
/// Trait for commands.
///
/// # Defining commands
/// A command object should be created once only, and then referenced and executed using `params` and through mutating `cx`.
pub trait Command: fmt::Debug
{
fn execute(&self, cx: &mut Context<'_>, params: Vec<Value>) -> eyre::Result<()>;
}
/// Command structurally parsed.
///
/// Can be converted into `Command` with the `TryInto` trait.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IR
{
op: String,
params: Vec<Value>,
}
impl FromStr for IR
{
type Err = CommandParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
todo!()
}
}
/// Error when parsing a command into `IR`.
#[derive(Debug)]
pub struct CommandParseError(String);
impl error::Error for CommandParseError{}
impl fmt::Display for CommandParseError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "failed to parse command from {:?}", self.0)
}
}

@ -0,0 +1,242 @@
//! Execution environment for repl
use super::*;
use std::str::FromStr;
use std::collections::{BTreeMap, HashMap};
#[derive(Debug)]
pub struct Lexenv
{
/// Maps symbol name to value in generations.
kvstack: BTreeMap<usize, HashMap<String, Value>>,
/// Current generation of the satck
current_generation: usize,
}
impl Lexenv
{
/// The generation number of the current level.
pub fn depth(&self) -> usize
{
self.current_generation
}
/// Create a new empty lexenv
pub fn new() -> Self
{
Self {
kvstack: BTreeMap::new(),
current_generation: 0,
}
}
/// All valid symbols at this level.
///
/// # Ordering
/// Each symbol's level will appear in the order from level 0 to the current level, however the order of intra-level symbols is undefined.
pub fn symbols(&self) -> impl Iterator<Item = &'_ Value> + '_
{
self.kvstack.range(0..=self.current_generation).flat_map(|(_, v)| v.values())
}
/// All valid symbols **in** this level.
pub fn symbols_local(&self) -> impl Iterator<Item = &'_ Value> + '_
{
OptionIterator::from(self.kvstack.get(&self.current_generation).map(|x| x.values()))
}
/// Remove the current level, but leave its memory allocated for further use.
pub fn pop(&mut self)
{
self.kvstack.entry(self.current_generation).or_insert_with(|| HashMap::new()).clear();
if self.current_generation > 0 {
self.current_generation-=1;
}
}
/// Remove a symbol from the **current** level.
pub fn remove(&mut self, name: &str) -> Option<Value>
{
self.kvstack.entry(self.current_generation).or_insert_with(|| HashMap::new()).remove(name)
}
/// Insert a new value mapping into the current level.
pub fn insert(&mut self, name: String, value: Value)
{
self.kvstack.entry(self.current_generation).or_insert_with(|| HashMap::new()).insert(name, value);
}
/// Look up a symbol in this or any of the above levels.
pub fn lookup(&self, name: &str) -> Option<&Value>
{
for (_, lvmap) in self.kvstack.range(0..=self.current_generation).rev()
{
let m = lvmap.get(name);
if m.is_some() {
return m;
}
}
None
}
/// Look up a symbol in this level.
pub fn lookup_local(&self, name: &str) -> Option<&Value>
{
self.kvstack.get(&self.current_generation).map(|map| map.get(name)).flatten()
}
/// Create a new, empty level.
pub fn push(&mut self)
{
self.current_generation+=1;
}
/// Remove the current level, deallocating any memory it was using.
pub fn pop_clear(&mut self)
{
if self.current_generation > 0 {
self.kvstack.remove(&self.current_generation);
self.current_generation -=1;
} else {
self.kvstack.entry(0).or_insert_with(|| HashMap::new()).clear();
}
}
}
/// The value type
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Value
{
String(String),
Symbol(String),
List(Vec<Value>),
}
impl Value
{
/// Parse from an iterator of `char`s.
pub fn parse_chars<T>(ch: &mut T) -> Result<Self, ValueParseError>
where T: Iterator<Item = char>
{
match ch.next()
{
Some('(') => {
todo!("list");
},
Some('"') => {
todo!("string");
},
Some(first_chr) => {
todo!("symbol");
},
_ => Err(ValueParseError(String::default())),
}
}
/// Parse a `Value` from this string and then return the rest of the string.
#[deprecated]
pub fn parse_running(s: &str) -> Result<(Self, &'_ str), ValueParseError>
{
match s.trim().as_bytes()
{
& [b'(', ..] => {
todo!("list");
},
& [b'"', ..] => {
todo!("string");
},
_ => {
todo!("shmbol");
}
}
}
}
impl FromStr for Value
{
type Err = ValueParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse_running(s).map(|(x, _)| x)
}
}
impl Value
{
pub fn try_as_symbol(&self) -> Result<&str, ValueTypeError>
{
match self {
Self::Symbol(s) => Ok(&s[..]),
_ => Err(ValueTypeError::Symbol),
}
}
pub fn try_as_string(&self) -> Result<&str, ValueTypeError>
{
match self {
Self::Symbol(s) |
Self::String(s) => Ok(&s[..]),
_ => Err(ValueTypeError::String),
}
}
pub fn try_as_list(&self) -> Result<&[Value], ValueTypeError>
{
match self {
Self::List(l) => Ok(&l[..]),
_ => Err(ValueTypeError::List),
}
}
pub fn as_symbol(&self) -> Option<&str>
{
match self {
Self::Symbol(s) => Some(&s[..]),
_ => None,
}
}
pub fn as_string(&self) -> Option<&str>
{
match self {
Self::Symbol(s) |
Self::String(s) => Some(&s[..]),
_ => None,
}
}
pub fn as_list(&self) -> Option<&[Value]>
{
match self {
Self::List(l) => Some(&l[..]),
_ => None,
}
}
}
/// Error when using `try_as_*` functions on `Value`.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
pub enum ValueTypeError
{
Symbol,
String,
List,
}
/// Error when parsing a `Value` from a stirng.
#[derive(Debug)]
pub struct ValueParseError(String);
impl error::Error for ValueParseError{}
impl fmt::Display for ValueParseError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "cannot parse {:?}", self.0)
}
}
impl error::Error for ValueTypeError{}
impl fmt::Display for ValueTypeError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "type error: expected ")?;
match self {
Self::Symbol => write!(f, "symbol"),
Self::String => write!(f, "string"),
Self::List => write!(f, "list"),
}
}
}

@ -0,0 +1,13 @@
//! Defined commands
use super::*;
use env::*;
use command::*;
/// Contains all operations
#[derive(Debug, Clone)]
pub struct Operations
{
}

@ -1,3 +1,4 @@
#![feature(try_blocks)]
#![allow(dead_code)] #![allow(dead_code)]
@ -98,6 +99,7 @@ async fn write_graph(graph: Arc<data::HierarchicalINodeGraph>) -> eyre::Result<(
.wrap_err(eyre!("Failed to open file for mapping")) .wrap_err(eyre!("Failed to open file for mapping"))
.with_section(|| format!("{:?}", output_file).header("File was"))?; .with_section(|| format!("{:?}", output_file).header("File was"))?;
let mut file = file.into_std().await; let mut file = file.into_std().await;
cfg_eprintln!(Verbose; cfg, "Opened file for prealloc-write");
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
serial::write_sync_map(&mut file, graph.as_ref()) serial::write_sync_map(&mut file, graph.as_ref())
}).await.wrap_err(eyre!("Prealloc panicked while dumping")) }).await.wrap_err(eyre!("Prealloc panicked while dumping"))
@ -125,13 +127,31 @@ async fn normal(cfg: config::Config) -> eyre::Result<()>
}; };
let cfg = cfg.make_global(); let cfg = cfg.make_global();
let graph = tokio::task::spawn_blocking(move || { let (treemap, graph) = tokio::task::spawn_blocking(move || {
cfg_println!(Verbose; cfg, "Computing hierarchy..."); cfg_println!(Verbose; cfg, "Computing hierarchy...");
// Create treemap
#[cfg(feature="treemap")]
let treemap = {
//Check config for if the treemap flag is present. If it is, get the width and height from there too.
if let Some(&(x,y)) = cfg.inspection.treemap.as_ref() {
Some(info::treemap(cfg, &graph, (
x as f64,
y as f64,
)))
} else {
None
}
};
#[cfg(not(feature="treemap"))]
let treemap = Option::<std::convert::Infallible>::None;
// Create recursive graph
let mut graph = graph.into_hierarchical(); let mut graph = graph.into_hierarchical();
cfg_println!(Verbose; cfg, "Computing sizes..."); cfg_println!(Verbose; cfg, "Computing sizes...");
graph.compute_recursive_sizes(); graph.compute_recursive_sizes();
graph (treemap, graph)
}).await.expect("Failed to compute hierarchy from graph"); }).await.expect("Failed to compute hierarchy from graph");
//#[cfg(debug_assertions)] cfg_eprintln!(Verbose; cfg, "{:?}", graph); //#[cfg(debug_assertions)] cfg_eprintln!(Verbose; cfg, "{:?}", graph);
@ -153,6 +173,12 @@ async fn normal(cfg: config::Config) -> eyre::Result<()>
} }
}; };
#[cfg(feature="treemap")]
if let Some(treemap) = treemap {
//TODO: Print treemap
todo!("{:?}",treemap);
}
info::print_basic_max_info(&cfg, &graph); info::print_basic_max_info(&cfg, &graph);
#[cfg(feature="inspect")] #[cfg(feature="inspect")]
@ -172,7 +198,7 @@ async fn parse_mode() -> eyre::Result<()>
{ {
match arg::parse_args() match arg::parse_args()
.wrap_err(eyre!("Failed to parse args")) .wrap_err(eyre!("Failed to parse args"))
.with_suggestion(|| "Try `--help`")? .with_suggestion(|| "Try running `--help`")?
{ {
arg::Mode::Normal(cfg) => { arg::Mode::Normal(cfg) => {
#[cfg(debug_assertions)] eprintln!("cfg: {:#?}", cfg); #[cfg(debug_assertions)] eprintln!("cfg: {:#?}", cfg);

Loading…
Cancel
Save