Skip to content

Commit

Permalink
basic frame to support new interactive mode
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Thiel committed Jun 2, 2019
1 parent 495ccbd commit 6d82a72
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 51 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jwalk = "0.4.0"
byte-unit = "2.1.0"
termion = "1.5.2"
atty = "0.2.11"
tui = "0.6.0"

[[bin]]
name="dua"
Expand Down
30 changes: 20 additions & 10 deletions src/aggregate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ pub fn aggregate(
compute_total: bool,
sort_by_size_in_bytes: bool,
paths: impl IntoIterator<Item = impl AsRef<Path>>,
) -> Result<WalkResult, Error> {
) -> Result<(WalkResult, Statistics), Error> {
let mut res = WalkResult::default();
res.stats.smallest_file_in_bytes = u64::max_value();
let mut stats = Statistics::default();
stats.smallest_file_in_bytes = u64::max_value();
let mut total = 0;
let mut num_roots = 0;
let mut aggregates = Vec::new();
Expand All @@ -24,7 +25,7 @@ pub fn aggregate(
let mut num_bytes = 0u64;
let mut num_errors = 0u64;
for entry in options.iter_from_path(path.as_ref(), Sorting::None) {
res.stats.files_traversed += 1;
stats.files_traversed += 1;
match entry {
Ok(entry) => {
let file_size = match entry.metadata {
Expand All @@ -38,10 +39,8 @@ pub fn aggregate(
"we ask for metadata, so we at least have Some(Err(..))). Issue in jwalk?"
),
};
res.stats.largest_file_in_bytes =
res.stats.largest_file_in_bytes.max(file_size);
res.stats.smallest_file_in_bytes =
res.stats.smallest_file_in_bytes.min(file_size);
stats.largest_file_in_bytes = stats.largest_file_in_bytes.max(file_size);
stats.smallest_file_in_bytes = stats.smallest_file_in_bytes.min(file_size);
num_bytes += file_size;
}
Err(_) => num_errors += 1,
Expand All @@ -64,8 +63,8 @@ pub fn aggregate(
res.num_errors += num_errors;
}

if res.stats.files_traversed == 0 {
res.stats.smallest_file_in_bytes = 0;
if stats.files_traversed == 0 {
stats.smallest_file_in_bytes = 0;
}

if sort_by_size_in_bytes {
Expand All @@ -92,7 +91,7 @@ pub fn aggregate(
color::Fg(color::Reset),
)?;
}
Ok(res)
Ok((res, stats))
}

fn path_color(path: impl AsRef<Path>) -> Box<dyn fmt::Display> {
Expand Down Expand Up @@ -127,3 +126,14 @@ fn write_path<C: fmt::Display>(
path_color_reset = options.color.display(color::Fg(color::Reset)),
)
}

/// Statistics obtained during a filesystem walk
#[derive(Default, Debug)]
pub struct Statistics {
/// The amount of files we have seen
pub files_traversed: u64,
/// The size of the smallest file encountered in bytes
pub smallest_file_in_bytes: u64,
/// The size of the largest file encountered in bytes
pub largest_file_in_bytes: u64,
}
12 changes: 0 additions & 12 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,21 +96,9 @@ impl WalkOptions {
}
}

/// Statistics obtained during a filesystem walk
#[derive(Default, Debug)]
pub struct Statistics {
/// The amount of files we have seen
pub files_traversed: u64,
/// The size of the smallest file encountered in bytes
pub smallest_file_in_bytes: u64,
/// The size of the largest file encountered in bytes
pub largest_file_in_bytes: u64,
}

/// Information we gather during a filesystem walk
#[derive(Default)]
pub struct WalkResult {
/// The amount of io::errors we encountered. Can happen when fetching meta-data, or when reading the directory contents.
pub num_errors: u64,
pub stats: Statistics,
}
37 changes: 37 additions & 0 deletions src/interactive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
mod app {
use crate::{WalkOptions, WalkResult};
use failure::Error;
use std::io;
use std::path::PathBuf;
use termion::input::{Keys, TermReadEventsAndRaw};
use tui::{backend::Backend, Terminal};

pub struct App {}

impl App {
pub fn event_loop<B, R>(
&mut self,
terminal: &mut Terminal<B>,
keys: Keys<R>,
) -> Result<WalkResult, Error>
where
B: Backend,
R: io::Read + TermReadEventsAndRaw,
{
unimplemented!()
}

pub fn initialize<B>(
terminal: &mut Terminal<B>,
options: WalkOptions,
input: Vec<PathBuf>,
) -> Result<App, Error>
where
B: Backend,
{
Ok(App {})
}
}
}

pub use self::app::*;
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ extern crate jwalk;

mod aggregate;
mod common;
pub mod interactive;

pub use aggregate::aggregate;
pub use common::*;
65 changes: 39 additions & 26 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ extern crate structopt;
use structopt::StructOpt;

use dua::{ByteFormat, Color};
use failure::Error;
use failure::{Error, ResultExt};
use failure_tools::ok_or_exit;
use std::path::PathBuf;
use std::{fs, io, io::Write, process};
use std::{fs, io, io::Write, path::PathBuf, process};
use termion::input::TermRead;
use termion::{raw::IntoRawMode, screen::AlternateScreen};
use tui::{backend::TermionBackend, Terminal};

mod options;

Expand All @@ -27,51 +29,62 @@ fn run() -> Result<(), Error> {
Color::None
},
};
let (show_statistics, res) = match opt.command {
let res = match opt.command {
Some(Interactive { input }) => {
let mut terminal = {
let stdout = io::stdout().into_raw_mode()?;
let stdout = AlternateScreen::from(stdout);
let backend = TermionBackend::new(stdout);
Terminal::new(backend)
.with_context(|_| "Interactive mode requires a connected terminal")?
};
let mut app = dua::interactive::App::initialize(&mut terminal, walk_options, input)?;
app.event_loop(&mut terminal, io::stdin().keys())?
}
Some(Aggregate {
input,
no_total,
no_sort,
statistics,
}) => (
statistics,
dua::aggregate(
}) => {
let (res, stats) = dua::aggregate(
stdout_locked,
walk_options,
!no_total,
!no_sort,
if input.len() == 0 {
cwd_dirlist()?
} else {
input
},
)?,
),
None => (
false,
paths_from(input)?,
)?;
if statistics {
writeln!(io::stderr(), "{:?}", stats).ok();
}
res
}
None => {
dua::aggregate(
stdout_locked,
walk_options,
true,
true,
if opt.input.len() == 0 {
cwd_dirlist()?
} else {
opt.input
},
)?,
),
paths_from(opt.input)?,
)?
.0
}
};

if show_statistics {
writeln!(io::stderr(), "{:?}", res.stats).ok();
}
if res.num_errors > 0 {
process::exit(1);
}
Ok(())
}

fn paths_from(paths: Vec<PathBuf>) -> Result<Vec<PathBuf>, io::Error> {
if paths.len() == 0 {
cwd_dirlist()
} else {
Ok(paths)
}
}

fn cwd_dirlist() -> Result<Vec<PathBuf>, io::Error> {
let mut v: Vec<_> = fs::read_dir(".")?
.filter_map(|e| {
Expand Down
11 changes: 9 additions & 2 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,20 @@ pub struct Args {
#[structopt(short = "f", long = "format")]
pub format: Option<ByteFormat>,

/// One or more input files. If unset, we will use all entries in the current working directory.
/// One or more input files or directories. If unset, we will use all entries in the current working directory.
#[structopt(parse(from_os_str))]
pub input: Vec<PathBuf>,
}

#[derive(Debug, StructOpt)]
pub enum Command {
/// Launch the terminal user interface
#[structopt(name = "interactive", alias = "i")]
Interactive {
/// One or more input files or directories. If unset, we will use all entries in the current working directory.
#[structopt(parse(from_os_str))]
input: Vec<PathBuf>,
},
/// Aggregrate the consumed space of one or more directories or files
#[structopt(name = "aggregate", alias = "a")]
Aggregate {
Expand All @@ -60,7 +67,7 @@ pub enum Command {
/// If set, no total column will be computed for multiple inputs
#[structopt(long = "no-total")]
no_total: bool,
/// One or more input files. If unset, we will use all entries in the current working directory.
/// One or more input files or directories. If unset, we will use all entries in the current working directory.
#[structopt(parse(from_os_str))]
input: Vec<PathBuf>,
},
Expand Down
3 changes: 3 additions & 0 deletions tests/snapshots/failure-interactive-without-tty
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[?1049h[?1049lerror: Interactive mode requires a connected terminal
Caused by:
1: Inappropriate ioctl for device (os error 25)
8 changes: 7 additions & 1 deletion tests/stateless-journey.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ WITH_FAILURE=1
(with "no option to adjust the total"
it "produces a human-readable aggregate, with total" && {
WITH_SNAPSHOT="$snapshot/success-no-arguments-multiple-input-paths" \
expect_run ${SUCCESSFULLY} "$exe" aggregate . . dir ./dir/ ./dir/sub
expect_run ${SUCCESSFULLY} "$exe" a . . dir ./dir/ ./dir/sub
}
)
(with "the --no-total option set"
Expand Down Expand Up @@ -96,4 +96,10 @@ WITH_FAILURE=1
)
)
)
(with "interactive mode"
it "fails as there is no TTY connected" && {
WITH_SNAPSHOT="$snapshot/failure-interactive-without-tty" \
expect_run ${WITH_FAILURE} "$exe" i
}
)
)

0 comments on commit 6d82a72

Please sign in to comment.