use std::io;
use csv;
use formats::json;
use formats::table::{Align, Table};
use twiggy_ir as ir;
use twiggy_opt as opt;
use twiggy_traits as traits;
struct Top {
items: Vec<ir::Id>,
opts: opt::Top,
}
impl traits::Emit for Top {
#[cfg(feature = "emit_text")]
fn emit_text(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> {
struct TableRow {
size: u32,
size_percent: f64,
name: String,
};
fn process_item(id: ir::Id, items: &ir::Items, retained: bool) -> TableRow {
let item = &items[id];
let size = if retained {
items.retained_size(id)
} else {
item.size()
};
let size_percent = (f64::from(size)) / (f64::from(items.size())) * 100.0;
let name = item.name().to_string();
TableRow {
size,
size_percent,
name,
}
};
fn summarize_rows(rows: impl Iterator<Item = TableRow>) -> (u32, f64, u32) {
rows.fold(
(0, 0.0, 0),
|(total_size, total_percent, remaining_count),
TableRow {
size, size_percent, ..
}| {
(
total_size + size,
total_percent + size_percent,
remaining_count + 1,
)
},
)
}
let max_items = self.opts.max_items() as usize;
let retained = self.opts.retained();
let sort_label = if retained { "Retained" } else { "Shallow" };
let mut table = Table::with_header(vec![
(Align::Right, format!("{} Bytes", sort_label)),
(Align::Right, format!("{} %", sort_label)),
(Align::Left, "Item".to_string()),
]);
self.items
.iter()
.take(max_items)
.map(|&id| process_item(id, items, retained))
.for_each(
|TableRow {
size,
size_percent,
name,
}| {
table.add_row(vec![
size.to_string(),
format!("{:.2}%", size_percent),
name,
])
},
);
let remaining_rows = self
.items
.iter()
.skip(max_items)
.map(|&id| process_item(id, items, retained));
let (rem_size, rem_size_percent, rem_count) = summarize_rows(remaining_rows);
if rem_count > 0 {
let rem_name_col = format!("... and {} more.", rem_count);
let (rem_size_col, rem_size_percent_col) = if retained {
("...".to_string(), "...".to_string())
} else {
(rem_size.to_string(), format!("{:.2}%", rem_size_percent))
};
table.add_row(vec![rem_size_col, rem_size_percent_col, rem_name_col]);
}
let all_rows = self
.items
.iter()
.map(|&id| process_item(id, items, retained));
let (total_size, total_size_percent, total_count) = summarize_rows(all_rows);
let total_name_col = format!("Σ [{} Total Rows]", total_count);
let (total_size_col, total_size_percent_col) = if retained {
("...".to_string(), "...".to_string())
} else {
(
total_size.to_string(),
format!("{:.2}%", total_size_percent),
)
};
table.add_row(vec![total_size_col, total_size_percent_col, total_name_col]);
write!(dest, "{}", &table)?;
Ok(())
}
#[cfg(feature = "emit_json")]
fn emit_json(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> {
let mut arr = json::array(dest)?;
let max_items = self.opts.max_items() as usize;
let items_iter = self.items.iter();
for &id in items_iter.take(max_items) {
let item = &items[id];
let mut obj = arr.object()?;
obj.field("name", item.name())?;
let size = item.size();
let size_percent = f64::from(size) / f64::from(items.size()) * 100.0;
obj.field("shallow_size", size)?;
obj.field("shallow_size_percent", size_percent)?;
if self.opts.retained() {
let size = items.retained_size(id);
let size_percent = f64::from(size) / f64::from(items.size()) * 100.0;
obj.field("retained_size", size)?;
obj.field("retained_size_percent", size_percent)?;
}
}
Ok(())
}
#[cfg(feature = "emit_csv")]
fn emit_csv(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> {
let mut wtr = csv::Writer::from_writer(dest);
#[derive(Serialize, Debug)]
#[serde(rename_all = "PascalCase")]
struct CsvRecord {
name: String,
shallow_size: u32,
shallow_size_percent: f64,
retained_size: Option<u32>,
retained_size_percent: Option<f64>,
}
let max_items = self.opts.max_items() as usize;
let items_iter = self.items.iter();
for &id in items_iter.take(max_items) {
let item = &items[id];
let (shallow_size, shallow_size_percent) = {
let size = item.size();
let size_percent = f64::from(size) / f64::from(items.size()) * 100.0;
(size, size_percent)
};
let (retained_size, retained_size_percent) = if self.opts.retained() {
let size = items.retained_size(id);
let size_percent = f64::from(size) / f64::from(items.size()) * 100.0;
(Some(size), Some(size_percent))
} else {
(None, None)
};
wtr.serialize(CsvRecord {
name: item.name().to_string(),
shallow_size,
shallow_size_percent,
retained_size,
retained_size_percent,
})?;
wtr.flush()?;
}
Ok(())
}
}
pub fn top(items: &mut ir::Items, opts: &opt::Top) -> Result<Box<traits::Emit>, traits::Error> {
if opts.retaining_paths() {
return Err(traits::Error::with_msg(
"retaining paths are not yet implemented",
));
}
if opts.retained() {
items.compute_retained_sizes();
}
let mut top_items: Vec<_> = items
.iter()
.filter(|item| item.id() != items.meta_root())
.collect();
top_items.sort_by(|a, b| {
if opts.retained() {
items
.retained_size(b.id())
.cmp(&items.retained_size(a.id()))
} else {
b.size().cmp(&a.size())
}
});
let top_items: Vec<_> = top_items.into_iter().map(|i| i.id()).collect();
let top = Top {
items: top_items,
opts: opts.clone(),
};
Ok(Box::new(top) as Box<traits::Emit>)
}