diff options
author | Sebastian Thiel <sthiel@thoughtworks.com> | 2019-06-03 09:21:22 +0530 |
---|---|---|
committer | Sebastian Thiel <sthiel@thoughtworks.com> | 2019-06-03 09:21:22 +0530 |
commit | 7eb857467c6d2603129edbaea636ef0d118fa064 (patch) | |
tree | 8fad44d65c091fadd2903a2d3c6f4ba8a9bd6bd2 | |
parent | b919c501a249dcf626e390d496faf6d31a9e71ac (diff) |
bytes formatting for interactive + footer
-rw-r--r-- | src/aggregate.rs | 2 | ||||
-rw-r--r-- | src/common.rs | 83 | ||||
-rw-r--r-- | src/interactive/app.rs | 33 | ||||
-rw-r--r-- | src/interactive/widgets.rs | 70 | ||||
-rw-r--r-- | src/main.rs | 4 | ||||
-rw-r--r-- | tests/interactive.rs | 4 |
6 files changed, 138 insertions, 58 deletions
diff --git a/src/aggregate.rs b/src/aggregate.rs index e6f52f6..6b616e8 100644 --- a/src/aggregate.rs +++ b/src/aggregate.rs @@ -113,7 +113,7 @@ fn write_path<C: fmt::Display>( writeln!( out, "{byte_color}{:>10}{byte_color_reset}\t{path_color}{}{path_color_reset}{}", - options.format_bytes(num_bytes), + options.byte_format.display(num_bytes), path.as_ref().display(), if num_errors == 0 { Cow::Borrowed("") diff --git a/src/common.rs b/src/common.rs index 0ed32f4..86e5ac4 100644 --- a/src/common.rs +++ b/src/common.rs @@ -3,6 +3,7 @@ use std::fmt; use std::path::Path; /// Specifies a way to format bytes +#[derive(Clone, Copy)] pub enum ByteFormat { /// metric format, based on 1000. Metric, @@ -12,8 +13,54 @@ pub enum ByteFormat { Bytes, } +impl ByteFormat { + pub fn display(&self, bytes: u64) -> ByteFormatDisplay { + ByteFormatDisplay { + format: *self, + bytes, + } + } +} + +pub struct ByteFormatDisplay { + format: ByteFormat, + bytes: u64, +} + +impl fmt::Display for ByteFormatDisplay { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + use byte_unit::Byte; + use ByteFormat::*; + + let binary = match self.format { + Bytes => return write!(f, "{} b", self.bytes), + Binary => true, + Metric => false, + }; + let b = Byte::from_bytes(self.bytes as u128) + .get_appropriate_unit(binary) + .format(2); + let mut splits = b.split(' '); + match (splits.next(), splits.next()) { + (Some(bytes), Some(unit)) => write!( + f, + "{:>8} {:>unit_width$}", + bytes, + unit, + unit_width = match self.format { + Binary => 3, + Metric => 2, + _ => 2, + } + ), + _ => f.write_str(&b), + } + } +} + /// Identify the kind of sorting to apply during filesystem iteration -pub enum Sorting { +#[derive(Clone)] +pub enum TraversalSorting { None, AlphabeticalByFileName, } @@ -51,49 +98,23 @@ where } /// Configures a filesystem walk, including output and formatting options. +#[derive(Clone)] pub struct WalkOptions { /// The amount of threads to use. Refer to [`WalkDir::num_threads()`](https://docs.rs/jwalk/0.4.0/jwalk/struct.WalkDir.html#method.num_threads) /// for more information. pub threads: usize, pub byte_format: ByteFormat, pub color: Color, - pub sorting: Sorting, + pub sorting: TraversalSorting, } impl WalkOptions { - pub(crate) fn format_bytes(&self, b: u64) -> String { - use byte_unit::Byte; - use ByteFormat::*; - let binary = match self.byte_format { - Bytes => return format!("{} b", b), - Binary => true, - Metric => false, - }; - let b = Byte::from_bytes(b as u128) - .get_appropriate_unit(binary) - .format(2); - let mut splits = b.split(' '); - match (splits.next(), splits.next()) { - (Some(bytes), Some(unit)) => format!( - "{:>8} {:>unit_width$}", - bytes, - unit, - unit_width = match self.byte_format { - Binary => 3, - Metric => 2, - _ => 2, - } - ), - _ => b, - } - } - pub(crate) fn iter_from_path(&self, path: &Path) -> WalkDir { WalkDir::new(path) .preload_metadata(true) .sort(match self.sorting { - Sorting::None => false, - Sorting::AlphabeticalByFileName => true, + TraversalSorting::None => false, + TraversalSorting::AlphabeticalByFileName => true, }) .skip_hidden(false) .num_threads(self.threads) diff --git a/src/interactive/app.rs b/src/interactive/app.rs index f36ab66..0d3cfc8 100644 --- a/src/interactive/app.rs +++ b/src/interactive/app.rs @@ -1,4 +1,4 @@ -use crate::{WalkOptions, WalkResult}; +use crate::{ByteFormat, WalkOptions, WalkResult}; use failure::Error; use petgraph::{prelude::NodeIndex, Directed, Direction, Graph}; use std::{ @@ -37,18 +37,26 @@ pub struct Traversal { pub io_errors: u64, } +/// Options to configure how we display things +#[derive(Clone, Copy)] +pub struct DisplayOptions { + pub byte_format: ByteFormat, +} + +impl From<WalkOptions> for DisplayOptions { + fn from(WalkOptions { byte_format, .. }: WalkOptions) -> Self { + DisplayOptions { byte_format } + } +} + impl Traversal { pub fn from_walk( options: WalkOptions, input: Vec<PathBuf>, mut update: impl FnMut(&Traversal) -> Result<(), Error>, ) -> Result<Traversal, Error> { - fn set_size_or_panic( - tree: &mut Tree, - parent_node_idx: TreeIndex, - current_size_at_depth: u64, - ) { - tree.node_weight_mut(parent_node_idx) + fn set_size_or_panic(tree: &mut Tree, node_idx: TreeIndex, current_size_at_depth: u64) { + tree.node_weight_mut(node_idx) .expect("node for parent index we just retrieved") .size = current_size_at_depth; } @@ -199,7 +207,7 @@ impl TerminalApp { for key in keys.filter_map(Result::ok) { match key { - Ctrl('c') | Char('\n') => break, + Ctrl('c') | Char('\n') | Char('q') => break, _ => dbg!(&key), }; } @@ -216,11 +224,16 @@ impl TerminalApp { where B: Backend, { + let display_options: DisplayOptions = options.clone().into(); Ok(TerminalApp { - traversal: Traversal::from_walk(options, input, |traversal| { + traversal: Traversal::from_walk(options, input, move |traversal| { terminal.draw(|mut f| { let full_screen = f.size(); - super::widgets::InitWindow { traversal }.render(&mut f, full_screen) + super::widgets::InitWindow { + traversal, + display: display_options, + } + .render(&mut f, full_screen) })?; Ok(()) })?, diff --git a/src/interactive/widgets.rs b/src/interactive/widgets.rs index c1cf0b9..915044c 100644 --- a/src/interactive/widgets.rs +++ b/src/interactive/widgets.rs @@ -1,5 +1,6 @@ -use super::{Traversal, Tree, TreeIndex}; -use petgraph::Direction; +use super::{DisplayOptions, Traversal, Tree, TreeIndex}; +use tui::layout::{Constraint, Direction, Layout}; +use tui::style::{Color, Style}; use tui::{ buffer::Buffer, layout::{Corner, Rect}, @@ -9,36 +10,81 @@ use tui::{ pub struct Entries<'a> { pub tree: &'a Tree, pub root: TreeIndex, + pub display: DisplayOptions, } pub struct InitWindow<'a> { pub traversal: &'a Traversal, + pub display: DisplayOptions, +} + +fn get_size_or_panic(tree: &Tree, node_idx: TreeIndex) -> u64 { + tree.node_weight(node_idx) + .expect("node should always be retrievable with valid index") + .size } impl<'a> Widget for InitWindow<'a> { fn draw(&mut self, area: Rect, buf: &mut Buffer) { - let Self { - traversal: Traversal { - tree, root_index, .. - }, - } = self; + let regions = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Max(256), Constraint::Length(1)].as_ref()) + .split(area); + let (entries, footer) = (regions[0], regions[1]); Entries { - tree: tree, - root: *root_index, + tree: &self.traversal.tree, + root: self.traversal.root_index, + display: self.display, } - .draw(area, buf); + .draw(entries, buf); + + let bg_color = Color::White; + let text_color = Color::Black; + let margin = 1; + self.background(footer, buf, bg_color); + buf.set_stringn( + footer.x + margin, + footer.y, + format!( + "Total disk usage: {}", + format!( + "{}", + self.display.byte_format.display(get_size_or_panic( + &self.traversal.tree, + self.traversal.root_index + )) + ) + .trim() + ), + (footer.width - margin) as usize, + Style { + fg: text_color, + bg: bg_color, + ..Default::default() + }, + ) } } impl<'a> Widget for Entries<'a> { fn draw(&mut self, area: Rect, buf: &mut Buffer) { - let Self { tree, root } = self; + use petgraph::Direction; + let Self { + tree, + root, + display, + } = self; List::new( tree.neighbors_directed(*root, Direction::Outgoing) .filter_map(|w| { tree.node_weight(w).map(|w| { Text::Raw( - format!("{} | ----- | {}", w.size, w.name.to_string_lossy()).into(), + format!( + "{} | ----- | {}", + display.byte_format.display(w.size), + w.name.to_string_lossy() + ) + .into(), ) }) }), diff --git a/src/main.rs b/src/main.rs index 7eddb14..0ff205f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ extern crate structopt; use structopt::StructOpt; -use dua::{ByteFormat, Color, Sorting}; +use dua::{ByteFormat, Color, TraversalSorting}; use failure::{Error, ResultExt}; use failure_tools::ok_or_exit; use std::{fs, io, io::Write, path::PathBuf, process}; @@ -28,7 +28,7 @@ fn run() -> Result<(), Error> { } else { Color::None }, - sorting: Sorting::None, + sorting: TraversalSorting::None, }; let res = match opt.command { Some(Interactive { input }) => { diff --git a/tests/interactive.rs b/tests/interactive.rs index 14684e4..159f05e 100644 --- a/tests/interactive.rs +++ b/tests/interactive.rs @@ -1,6 +1,6 @@ mod app { use dua::interactive::{EntryData, TerminalApp, Tree, TreeIndexType}; - use dua::{ByteFormat, Color, Sorting, WalkOptions}; + use dua::{ByteFormat, Color, TraversalSorting, WalkOptions}; use failure::Error; use petgraph::prelude::NodeIndex; use pretty_assertions::assert_eq; @@ -51,7 +51,7 @@ mod app { threads: 1, byte_format: ByteFormat::Metric, color: Color::None, - sorting: Sorting::AlphabeticalByFileName, + sorting: TraversalSorting::AlphabeticalByFileName, }, vec![input], )?; |