1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-27 09:39:08 -05:00
denoland-deno/cli/info.rs

299 lines
7.8 KiB
Rust

// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::colors;
use crate::media_type::serialize_media_type;
use crate::media_type::MediaType;
use deno_core::serde::Serialize;
use deno_core::serde::Serializer;
use deno_core::ModuleSpecifier;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::fmt;
use std::path::PathBuf;
/// The core structure representing information about a specific "root" file in
/// a module graph. This is used to represent information as part of the `info`
/// subcommand.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ModuleGraphInfo {
pub compiled: Option<PathBuf>,
pub dep_count: usize,
#[serde(serialize_with = "serialize_media_type")]
pub file_type: MediaType,
pub files: ModuleInfoMap,
#[serde(skip_serializing)]
pub info: ModuleInfo,
pub local: PathBuf,
pub map: Option<PathBuf>,
pub module: ModuleSpecifier,
pub total_size: usize,
}
impl fmt::Display for ModuleGraphInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"{} {}",
colors::bold("local:"),
self.local.to_string_lossy()
)?;
writeln!(f, "{} {}", colors::bold("type:"), self.file_type)?;
if let Some(ref compiled) = self.compiled {
writeln!(
f,
"{} {}",
colors::bold("compiled:"),
compiled.to_string_lossy()
)?;
}
if let Some(ref map) = self.map {
writeln!(f, "{} {}", colors::bold("map:"), map.to_string_lossy())?;
}
writeln!(
f,
"{} {} unique {}",
colors::bold("deps:"),
self.dep_count,
colors::gray(&format!(
"(total {})",
human_size(self.info.total_size.unwrap_or(0) as f64)
))
)?;
writeln!(f)?;
writeln!(
f,
"{} {}",
self.info.name,
colors::gray(&format!("({})", human_size(self.info.size as f64)))
)?;
let dep_count = self.info.deps.len();
for (idx, dep) in self.info.deps.iter().enumerate() {
dep.write_info(f, "", idx == dep_count - 1)?;
}
Ok(())
}
}
/// Represents a unique dependency within the graph of the the dependencies for
/// a given module.
#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ModuleInfo {
pub deps: Vec<ModuleInfo>,
pub name: ModuleSpecifier,
pub size: usize,
pub total_size: Option<usize>,
}
impl PartialOrd for ModuleInfo {
fn partial_cmp(&self, other: &ModuleInfo) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ModuleInfo {
fn cmp(&self, other: &ModuleInfo) -> Ordering {
self.name.to_string().cmp(&other.name.to_string())
}
}
impl ModuleInfo {
pub fn write_info(
&self,
f: &mut fmt::Formatter<'_>,
prefix: &str,
last: bool,
) -> fmt::Result {
let sibling_connector = if last { '└' } else { '├' };
let child_connector = if self.deps.is_empty() { '─' } else { '┬' };
let totals = if self.total_size.is_some() {
colors::gray(&format!(" ({})", human_size(self.size as f64)))
} else {
colors::gray(" *")
};
writeln!(
f,
"{} {}{}",
colors::gray(&format!(
"{}{}{}",
prefix, sibling_connector, child_connector
)),
self.name,
totals
)?;
let mut prefix = prefix.to_string();
if last {
prefix.push(' ');
} else {
prefix.push('│');
}
prefix.push(' ');
let dep_count = self.deps.len();
for (idx, dep) in self.deps.iter().enumerate() {
dep.write_info(f, &prefix, idx == dep_count - 1)?;
}
Ok(())
}
}
/// A flat map of dependencies for a given module graph.
#[derive(Debug)]
pub struct ModuleInfoMap(pub HashMap<ModuleSpecifier, ModuleInfoMapItem>);
impl ModuleInfoMap {
pub fn new(map: HashMap<ModuleSpecifier, ModuleInfoMapItem>) -> Self {
ModuleInfoMap(map)
}
}
impl Serialize for ModuleInfoMap {
/// Serializes inner hash map which is ordered by the key
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let ordered: BTreeMap<_, _> =
self.0.iter().map(|(k, v)| (k.to_string(), v)).collect();
ordered.serialize(serializer)
}
}
/// An entry in the `ModuleInfoMap` the provides the size of the module and
/// a vector of its dependencies, which should also be available as entries
/// in the map.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ModuleInfoMapItem {
pub deps: Vec<ModuleSpecifier>,
pub size: usize,
}
/// A function that converts a float to a string the represents a human
/// readable version of that number.
pub fn human_size(size: f64) -> String {
let negative = if size.is_sign_positive() { "" } else { "-" };
let size = size.abs();
let units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
if size < 1_f64 {
return format!("{}{}{}", negative, size, "B");
}
let delimiter = 1024_f64;
let exponent = std::cmp::min(
(size.ln() / delimiter.ln()).floor() as i32,
(units.len() - 1) as i32,
);
let pretty_bytes = format!("{:.2}", size / delimiter.powi(exponent))
.parse::<f64>()
.unwrap()
* 1_f64;
let unit = units[exponent as usize];
format!("{}{}{}", negative, pretty_bytes, unit)
}
#[cfg(test)]
mod test {
use super::*;
use deno_core::resolve_url_or_path;
use deno_core::serde_json::json;
#[test]
fn human_size_test() {
assert_eq!(human_size(16_f64), "16B");
assert_eq!(human_size((16 * 1024) as f64), "16KB");
assert_eq!(human_size((16 * 1024 * 1024) as f64), "16MB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(3.0)), "16GB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(4.0)), "16TB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(5.0)), "16PB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(6.0)), "16EB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(7.0)), "16ZB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(8.0)), "16YB");
}
fn get_fixture() -> ModuleGraphInfo {
let spec_c = resolve_url_or_path("https://deno.land/x/a/b/c.ts").unwrap();
let spec_d = resolve_url_or_path("https://deno.land/x/a/b/c.ts").unwrap();
let deps = vec![ModuleInfo {
deps: Vec::new(),
name: spec_d.clone(),
size: 12345,
total_size: None,
}];
let info = ModuleInfo {
deps,
name: spec_c.clone(),
size: 12345,
total_size: Some(12345),
};
let mut items = HashMap::new();
items.insert(
spec_c,
ModuleInfoMapItem {
deps: vec![spec_d.clone()],
size: 12345,
},
);
items.insert(
spec_d,
ModuleInfoMapItem {
deps: Vec::new(),
size: 12345,
},
);
let files = ModuleInfoMap(items);
ModuleGraphInfo {
compiled: Some(PathBuf::from("/a/b/c.js")),
dep_count: 99,
file_type: MediaType::TypeScript,
files,
info,
local: PathBuf::from("/a/b/c.ts"),
map: None,
module: resolve_url_or_path("https://deno.land/x/a/b/c.ts").unwrap(),
total_size: 999999,
}
}
#[test]
fn test_module_graph_info_display() {
let fixture = get_fixture();
let actual = fixture.to_string();
assert!(actual.contains(" /a/b/c.ts"));
assert!(actual.contains(" 99 unique"));
assert!(actual.contains("(12.06KB)"));
assert!(actual.contains("\n\nhttps://deno.land/x/a/b/c.ts"));
}
#[test]
fn test_module_graph_info_json() {
let fixture = get_fixture();
let actual = json!(fixture);
assert_eq!(
actual,
json!({
"compiled": "/a/b/c.js",
"depCount": 99,
"fileType": "TypeScript",
"files": {
"https://deno.land/x/a/b/c.ts":{
"deps": [],
"size": 12345
}
},
"local": "/a/b/c.ts",
"map": null,
"module": "https://deno.land/x/a/b/c.ts",
"totalSize": 999999
})
);
}
}