pub mod cli;
pub mod config;
pub mod error;
pub mod filters;
pub mod formatter;
pub mod stats;
pub mod walker;
pub use cli::Args;
pub use config::Config;
pub use error::{Error, Result};
pub use filters::{FilterOptions, SortBy};
pub use formatter::{DistributionFormat, DistributionType, FormatOptions, OutputFormat};
pub use stats::{FileStats, TreeStats};
pub use walker::{TreeEntry, Walker};
use std::io::{self, Write};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct MaramOptions {
pub output: OutputFormat,
pub filter: FilterOptions,
pub format: FormatOptions,
pub threads: usize,
pub max_file_size: u64,
pub total_size: bool,
pub dir_sizes: bool,
pub distribution: Option<(DistributionType, usize /* top N */, DistributionFormat)>,
}
impl Default for MaramOptions {
fn default() -> Self {
let color = atty::is(atty::Stream::Stdout) && std::env::var("NO_COLOR").is_err();
Self {
output: OutputFormat::Tree,
filter: FilterOptions::default(),
format: FormatOptions {
unicode: true,
color,
full_path: false,
show_size: true,
show_lines: false,
dir_sizes: false,
},
threads: 0,
max_file_size: 1_073_741_824, total_size: false,
dir_sizes: false,
distribution: None,
}
}
}
impl MaramOptions {
pub fn with_line_counting(mut self, max_size: u64) -> Self {
self.format.show_lines = true;
self.max_file_size = max_size;
self
}
pub fn with_dir_sizes(mut self) -> Self {
self.dir_sizes = true;
self.format.dir_sizes = true;
self
}
pub fn with_total_size(mut self) -> Self {
self.total_size = true;
self
}
}
pub fn generate(path: impl AsRef<Path>, options: &MaramOptions) -> Result<String> {
let mut buffer: Vec<u8> = Vec::with_capacity(8192);
generate_to_writer(path, options, &mut buffer)?;
Ok(String::from_utf8_lossy(&buffer).into_owned())
}
pub fn generate_to_writer(
path: impl AsRef<Path>,
options: &MaramOptions,
out: &mut dyn Write,
) -> Result<()> {
let path = path.as_ref();
log::debug!("Starting generation at: {:?}", path);
let needs_buffering = matches!(options.output, OutputFormat::Json | OutputFormat::Csv)
|| options.distribution.is_some()
|| options.total_size
|| options.dir_sizes
|| options.filter.sort_by.is_some();
if !needs_buffering {
let mut stream = walker::StreamWalker::with_writer(
options.filter.clone(),
options.output,
options.format.show_size,
options.format.show_lines,
options.format.unicode,
options.format.color,
Box::new(WriterAdapter(out)),
);
return stream.stream(path);
}
let mut walker = Walker::new(path, options.filter.clone(), options.threads)?;
walker.set_max_file_size(options.max_file_size);
if options.format.show_lines {
walker.enable_line_counting();
}
if options.dir_sizes || options.format.dir_sizes {
walker.enable_dir_sizes();
}
let entries = walker.walk()?;
match options.output {
OutputFormat::Tree => formatter::write_tree(out, &entries, &options.format)?,
OutputFormat::Json => formatter::write_json(out, &entries)?,
OutputFormat::Csv => formatter::write_csv(out, &entries)?,
OutputFormat::Plain => formatter::write_plain(out, &entries)?,
}
if options.total_size && matches!(options.output, OutputFormat::Tree) {
let stats = TreeStats::from_entries(&entries);
formatter::write_total_size(out, &stats, &options.format)?;
}
if let Some((dist_type, top, dist_fmt)) = &options.distribution {
formatter::write_distribution(out, &entries, dist_type, *top, dist_fmt, &options.format)?;
}
Ok(())
}
pub fn collect(path: impl AsRef<Path>, options: &MaramOptions) -> Result<Vec<TreeEntry>> {
let path = path.as_ref();
let mut walker = Walker::new(path, options.filter.clone(), options.threads)?;
walker.set_max_file_size(options.max_file_size);
if options.format.show_lines {
walker.enable_line_counting();
}
if options.dir_sizes || options.format.dir_sizes {
walker.enable_dir_sizes();
}
walker.walk()
}
pub fn render(entries: &[TreeEntry], options: &MaramOptions) -> Result<String> {
let mut buffer: Vec<u8> = Vec::with_capacity(8192);
render_to_writer(entries, options, &mut buffer)?;
Ok(String::from_utf8_lossy(&buffer).into_owned())
}
pub fn render_to_writer(
entries: &[TreeEntry],
options: &MaramOptions,
out: &mut dyn Write,
) -> Result<()> {
match options.output {
OutputFormat::Tree => formatter::write_tree(out, entries, &options.format)?,
OutputFormat::Json => formatter::write_json(out, entries)?,
OutputFormat::Csv => formatter::write_csv(out, entries)?,
OutputFormat::Plain => formatter::write_plain(out, entries)?,
}
if options.total_size && matches!(options.output, OutputFormat::Tree) {
let stats = TreeStats::from_entries(entries);
formatter::write_total_size(out, &stats, &options.format)?;
}
if let Some((dist_type, top, dist_fmt)) = &options.distribution {
formatter::write_distribution(out, entries, dist_type, *top, dist_fmt, &options.format)?;
}
Ok(())
}
struct WriterAdapter<'a>(&'a mut dyn Write);
impl<'a> Write for WriterAdapter<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
pub fn run_tree(path: &Path, args: &Args, config: &Config) -> Result<()> {
let output = run_tree_output(path, args, config)?;
print!("{}", output);
Ok(())
}
pub fn run_tree_output(path: &Path, args: &Args, config: &Config) -> Result<String> {
let filter_opts = FilterOptions::from_args_and_config(args, config)?;
let format_opts = FormatOptions::from_args_and_config(args, config);
let needs_buffering = matches!(args.output, OutputFormat::Json | OutputFormat::Csv)
|| args.dist.is_some()
|| args.total_size
|| args.dir_sizes
|| filter_opts.sort_by.is_some();
let mut buffer: Vec<u8> = Vec::with_capacity(8192);
if !needs_buffering {
let unicode = args.unicode || config.display.unicode;
let show_size = args.show_size || config.display.show_size;
let show_lines = args.show_lines || config.display.show_lines;
let mut stream_walker = walker::StreamWalker::with_writer(
filter_opts,
args.output,
show_size,
show_lines,
unicode,
format_opts.color,
Box::new(WriterAdapter(&mut buffer)),
);
stream_walker.stream(path)?;
} else {
let mut walker = Walker::new(path, filter_opts, args.threads)?;
walker.set_max_file_size(args.max_file_size);
if args.show_lines {
walker.enable_line_counting();
}
if args.dir_sizes {
walker.enable_dir_sizes();
}
let entries = walker.walk()?;
match args.output {
OutputFormat::Tree => formatter::write_tree(&mut buffer, &entries, &format_opts)?,
OutputFormat::Json => formatter::write_json(&mut buffer, &entries)?,
OutputFormat::Csv => formatter::write_csv(&mut buffer, &entries)?,
OutputFormat::Plain => formatter::write_plain(&mut buffer, &entries)?,
}
if args.total_size && matches!(args.output, OutputFormat::Tree) {
let stats = TreeStats::from_entries(&entries);
formatter::write_total_size(&mut buffer, &stats, &format_opts)?;
}
if let Some(dist_type) = &args.dist {
formatter::write_distribution(
&mut buffer,
&entries,
dist_type,
args.top,
&args.format,
&format_opts,
)?;
}
}
Ok(String::from_utf8_lossy(&buffer).into_owned())
}