Compare commits

..

16 Commits

Author SHA1 Message Date
andy.boot 6a86e8befd Increment version number 2018-05-09 11:46:42 +01:00
andy boot 3fb91a6c29 Merge pull request #19 from bootandy/rm_dup_input
code to remove duplicate arguments
2018-05-09 11:44:38 +01:00
andy.boot 51561994c5 Break up display_node function slightly
Also: run rustformat
2018-05-09 11:28:23 +01:00
andy.boot ce0e14bf00 Tweak output - the root node now has a: ─┬ 2018-05-09 10:58:32 +01:00
andy.boot dd75ec4aa7 wip: code to remove duplicate arguments
Also handle case where an argument is a substring of another argument
2018-05-09 10:35:30 +01:00
andy boot 65cd42736a Update README.md 2018-05-09 09:43:30 +01:00
andy boot 4792e97177 Merge pull request #18 from bootandy/fixes
Fixes
2018-05-02 09:51:21 +01:00
bootandy 3e9f09e339 Remove unnecessary path & pathbuf code 2018-05-02 00:42:03 +01:00
bootandy dba465a094 Tweak error message 2018-05-02 00:39:57 +01:00
bootandy 25d1ee7b43 Remove naked unwrap 2018-05-02 00:39:57 +01:00
bootandy 2556885622 Rust format 2018-05-02 00:39:57 +01:00
bootandy 8c088a7026 Fix: Passing a string into -n will no longer panic
fixes: #https://github.com/bootandy/dust/issues/16
2018-05-02 00:39:57 +01:00
bootandy 39db8b86fd Replace simple match with map_or 2018-05-01 14:38:34 +01:00
bootandy c5830c5d00 Stop using target_os just use target_family
target_family unix covers linux and mac and wraps up the call for inodes
in a common interface.
2018-05-01 13:59:48 +01:00
bootandy b68c450710 use eprintln! 2018-05-01 13:55:19 +01:00
andy.boot 6e2e5761d8 Increment version 2018-04-27 12:46:27 +01:00
8 changed files with 188 additions and 143 deletions
Generated
+1 -1
View File
@@ -121,7 +121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "du-dust" name = "du-dust"
version = "0.2.4" version = "0.3.1"
dependencies = [ dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"assert_cli 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "assert_cli 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+1 -1
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "du-dust" name = "du-dust"
description = "A more intuitive version of du" description = "A more intuitive version of du"
version = "0.3.0" version = "0.3.1"
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"] authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
documentation = "https://github.com/bootandy/dust" documentation = "https://github.com/bootandy/dust"
+2 -2
View File
@@ -7,11 +7,11 @@ du + rust = dust. Like du but more intuitive
## Install ## Install
### Cargo #### Cargo Install
* cargo install du-dust * cargo install du-dust
### Download #### Download Install
* Download linux / mac binary from [Releases](https://github.com/bootandy/dust/releases) * Download linux / mac binary from [Releases](https://github.com/bootandy/dust/releases)
* unzip file: tar -xvf _downloaded_file.tar.gz_ * unzip file: tar -xvf _downloaded_file.tar.gz_
+46 -28
View File
@@ -1,6 +1,7 @@
extern crate ansi_term; extern crate ansi_term;
use self::ansi_term::Colour::Fixed; use self::ansi_term::Colour::Fixed;
use std::collections::HashSet;
static UNITS: [char; 4] = ['T', 'G', 'M', 'K']; static UNITS: [char; 4] = ['T', 'G', 'M', 'K'];
@@ -8,15 +9,18 @@ pub fn draw_it(
permissions: bool, permissions: bool,
short_paths: bool, short_paths: bool,
depth: Option<u64>, depth: Option<u64>,
base_dirs: Vec<String>, base_dirs: HashSet<String>,
to_display: Vec<(String, u64)>, to_display: Vec<(String, u64)>,
) -> () { ) -> () {
if !permissions { if !permissions {
eprintln!("Did not have permissions for all directories"); eprintln!("Did not have permissions for all directories");
} }
let mut found = HashSet::new();
for f in base_dirs { for &(ref k, _) in to_display.iter() {
display_node(f, &to_display, true, short_paths, depth, "") if base_dirs.contains(k) {
display_node(&k, &mut found, &to_display, true, short_paths, depth, "─┬")
}
} }
} }
@@ -30,65 +34,59 @@ fn get_size(nodes: &Vec<(String, u64)>, node_to_print: &String) -> Option<u64> {
} }
fn display_node<S: Into<String>>( fn display_node<S: Into<String>>(
node_to_print: String, node_to_print: &String,
found: &mut HashSet<String>,
to_display: &Vec<(String, u64)>, to_display: &Vec<(String, u64)>,
is_biggest: bool, is_biggest: bool,
short_paths: bool, short_paths: bool,
depth: Option<u64>, depth: Option<u64>,
indentation_str: S, indentation_str: S,
) { ) {
if found.contains(node_to_print) {
return;
}
found.insert(node_to_print.to_string());
let new_depth = match depth { let new_depth = match depth {
None => None, None => None,
Some(0) => return, Some(0) => return,
Some(d) => Some(d - 1), Some(d) => Some(d - 1),
}; };
match get_size(to_display, &node_to_print) { match get_size(to_display, node_to_print) {
None => println!("Can not find path: {}", node_to_print), None => println!("Can not find path: {}", node_to_print),
Some(size) => { Some(size) => {
let mut is = indentation_str.into(); let is = indentation_str.into();
let ntp: &str = node_to_print.as_ref(); let ntp: &str = node_to_print.as_ref();
print_this_node(ntp, size, is_biggest, short_paths, is.as_ref()); print_this_node(ntp, size, is_biggest, short_paths, is.as_ref());
let new_indent_str = clean_indentation_string(is);
is = is.replace("└─┬", " "); let num_slashes = node_to_print.matches('/').count();
is = is.replace("└──", " "); let mut num_siblings = count_siblings(to_display, num_slashes, ntp);
is = is.replace("├──", "");
is = is.replace("├─┬", "");
let printable_node_slashes = node_to_print.matches('/').count();
let mut num_siblings = to_display.iter().fold(0, |a, b| {
if b.0.starts_with(ntp) && b.0.matches('/').count() == printable_node_slashes + 1 {
a + 1
} else {
a
}
});
let mut is_biggest = true; let mut is_biggest = true;
for &(ref k, _) in to_display.iter() { for &(ref k, _) in to_display.iter() {
if k.starts_with(ntp) && k.matches('/').count() == printable_node_slashes + 1 { if k.starts_with(ntp) && k.matches('/').count() == num_slashes + 1 {
num_siblings -= 1; num_siblings -= 1;
let mut has_children = false; let mut has_children = false;
if new_depth.is_none() || new_depth.unwrap() != 1 { if new_depth.is_none() || new_depth.unwrap() != 1 {
for &(ref k2, _) in to_display.iter() { for &(ref k2, _) in to_display.iter() {
let kk: &str = k.as_ref(); let kk: &str = k.as_ref();
if k2.starts_with(kk) if k2.starts_with(kk) && k2.matches('/').count() == num_slashes + 2 {
&& k2.matches('/').count() == printable_node_slashes + 2
{
has_children = true; has_children = true;
} }
} }
} }
display_node( display_node(
k.to_string(), k,
found,
to_display, to_display,
is_biggest, is_biggest,
short_paths, short_paths,
new_depth, new_depth,
is.to_string() + get_tree_chars(num_siblings, has_children), new_indent_str.to_string() + get_tree_chars(num_siblings, has_children),
); );
is_biggest = false; is_biggest = false;
} }
@@ -97,6 +95,26 @@ fn display_node<S: Into<String>>(
} }
} }
fn clean_indentation_string<S: Into<String>>(s: S) -> String {
let mut is = s.into();
is = is.replace("└─┬", " ");
is = is.replace("└──", " ");
is = is.replace("├──", "");
is = is.replace("├─┬", "");
is = is.replace("─┬", " ");
is
}
fn count_siblings(to_display: &Vec<(String, u64)>, num_slashes: usize, ntp: &str) -> u64 {
to_display.iter().fold(0, |a, b| {
if b.0.starts_with(ntp) && b.0.matches('/').count() == num_slashes + 1 {
a + 1
} else {
a
}
})
}
fn get_tree_chars(num_siblings: u64, has_children: bool) -> &'static str { fn get_tree_chars(num_siblings: u64, has_children: bool) -> &'static str {
if num_siblings == 0 { if num_siblings == 0 {
if has_children { if has_children {
@@ -141,8 +159,8 @@ pub fn format_string(
indentation: &str, indentation: &str,
) -> String { ) -> String {
let printable_name = { let printable_name = {
if short_paths && dir_name.contains('/') { if short_paths {
dir_name.split('/').last().unwrap() dir_name.split('/').last().unwrap_or(dir_name)
} else { } else {
dir_name dir_name
} }
+21 -14
View File
@@ -5,15 +5,15 @@ extern crate walkdir;
use self::display::draw_it; use self::display::draw_it;
use clap::{App, AppSettings, Arg}; use clap::{App, AppSettings, Arg};
use std::io::{self, Write}; use utils::{find_big_ones, get_dir_tree, simplify_dir_names, sort};
use utils::{find_big_ones, get_dir_tree, sort};
mod display; mod display;
mod utils; mod utils;
static DEFAULT_NUMBER_OF_LINES: &'static str = "20"; static DEFAULT_NUMBER_OF_LINES: usize = 20;
fn main() { fn main() {
let def_num_str = DEFAULT_NUMBER_OF_LINES.to_string();
let options = App::new("Dust") let options = App::new("Dust")
.setting(AppSettings::TrailingVarArg) .setting(AppSettings::TrailingVarArg)
.arg( .arg(
@@ -29,7 +29,7 @@ fn main() {
.long("number-of-lines") .long("number-of-lines")
.help("Number of lines of output to show") .help("Number of lines of output to show")
.takes_value(true) .takes_value(true)
.default_value(DEFAULT_NUMBER_OF_LINES), .default_value(def_num_str.as_ref()),
) )
.arg( .arg(
Arg::with_name("display_full_paths") Arg::with_name("display_full_paths")
@@ -46,37 +46,44 @@ fn main() {
.arg(Arg::with_name("inputs").multiple(true)) .arg(Arg::with_name("inputs").multiple(true))
.get_matches(); .get_matches();
let filenames = { let target_dirs = {
match options.values_of("inputs") { match options.values_of("inputs") {
None => vec!["."], None => vec!["."],
Some(r) => r.collect(), Some(r) => r.collect(),
} }
}; };
let number_of_lines = value_t!(options.value_of("number_of_lines"), usize).unwrap(); let number_of_lines = match value_t!(options.value_of("number_of_lines"), usize) {
Ok(v) => v,
Err(_) => {
eprintln!("Ignoring bad value for number_of_lines");
DEFAULT_NUMBER_OF_LINES
}
};
let depth = { let depth = {
if options.is_present("depth") { if options.is_present("depth") {
match value_t!(options.value_of("depth"), u64) { match value_t!(options.value_of("depth"), u64) {
Ok(v) => Some(v + 1), Ok(v) => Some(v + 1),
Err(_) => None, Err(_) => {
eprintln!("Ignoring bad value for depth");
None
}
} }
} else { } else {
None None
} }
}; };
if options.is_present("depth") if options.is_present("depth") && number_of_lines != DEFAULT_NUMBER_OF_LINES {
&& options.value_of("number_of_lines").unwrap() != DEFAULT_NUMBER_OF_LINES eprintln!("Use either -n or -d. Not both");
{
io::stderr()
.write(b"Use either -n for number of directories to show. Or -d for depth. Not both")
.expect("Error writing to stderr. Oh the irony!");
return; return;
} }
let use_apparent_size = options.is_present("display_apparent_size"); let use_apparent_size = options.is_present("display_apparent_size");
let use_full_path = options.is_present("display_full_paths"); let use_full_path = options.is_present("display_full_paths");
let (permissions, nodes, top_level_names) = get_dir_tree(&filenames, use_apparent_size); let simplified_dirs = simplify_dir_names(target_dirs);
let (permissions, nodes, top_level_names) = get_dir_tree(simplified_dirs, use_apparent_size);
let sorted_data = sort(nodes); let sorted_data = sort(nodes);
let biggest_ones = { let biggest_ones = {
if depth.is_none() { if depth.is_none() {
+19 -11
View File
@@ -28,6 +28,15 @@ pub fn test_main_long_paths() {
.unwrap(); .unwrap();
} }
#[test]
pub fn test_main_multi_arg() {
assert_cli::Assert::main_binary()
.with_args(&["src/test_dir/many/", "src/test_dir/", "src/test_dir"])
.stdout()
.is(main_output(true))
.unwrap();
}
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn main_output(short_paths: bool) -> String { fn main_output(short_paths: bool) -> String {
format!( format!(
@@ -35,7 +44,7 @@ fn main_output(short_paths: bool) -> String {
{} {}
{} {}
{}", {}",
format_string("src/test_dir", true, short_paths, " 4.0K", ""), format_string("src/test_dir", true, short_paths, " 4.0K", "─┬"),
format_string("src/test_dir/many", true, short_paths, " 4.0K", " └─┬",), format_string("src/test_dir/many", true, short_paths, " 4.0K", " └─┬",),
format_string( format_string(
"src/test_dir/many/hello_file", "src/test_dir/many/hello_file",
@@ -61,7 +70,7 @@ fn main_output(short_paths: bool) -> String {
{} {}
{} {}
{}", {}",
format_string("src/test_dir", true, short_paths, " 12K", ""), format_string("src/test_dir", true, short_paths, " 12K", "─┬"),
format_string("src/test_dir/many", true, short_paths, " 8.0K", " └─┬",), format_string("src/test_dir/many", true, short_paths, " 8.0K", " └─┬",),
format_string( format_string(
"src/test_dir/many/hello_file", "src/test_dir/many/hello_file",
@@ -140,7 +149,7 @@ fn soft_sym_link_output(dir: &str, file_path: &str, link_name: &str) -> String {
"{} "{}
{} {}
{}", {}",
format_string(dir, true, true, " 8.0K", ""), format_string(dir, true, true, " 8.0K", "─┬"),
format_string(file_path, true, true, " 4.0K", " ├──",), format_string(file_path, true, true, " 4.0K", " ├──",),
format_string(link_name, false, true, " 4.0K", " └──",), format_string(link_name, false, true, " 4.0K", " └──",),
) )
@@ -152,7 +161,7 @@ fn soft_sym_link_output(dir: &str, file_path: &str, link_name: &str) -> String {
"{} "{}
{} {}
{}", {}",
format_string(dir, true, true, " 8.0K", ""), format_string(dir, true, true, " 8.0K", "─┬"),
format_string(file_path, true, true, " 4.0K", " ├──",), format_string(file_path, true, true, " 4.0K", " ├──",),
format_string(link_name, false, true, " 0B", " └──",), format_string(link_name, false, true, " 0B", " └──",),
) )
@@ -200,13 +209,13 @@ fn hard_link_output(dir_s: &str, file_path_s: &str, link_name_s: &str) -> (Strin
let r = format!( let r = format!(
"{} "{}
{}", {}",
format_string(dir_s, true, true, " 4.0K", ""), format_string(dir_s, true, true, " 4.0K", "─┬"),
format_string(file_path_s, true, true, " 4.0K", " └──") format_string(file_path_s, true, true, " 4.0K", " └──")
); );
let r2 = format!( let r2 = format!(
"{} "{}
{}", {}",
format_string(dir_s, true, true, " 4.0K", ""), format_string(dir_s, true, true, " 4.0K", "─┬"),
format_string(link_name_s, true, true, " 4.0K", " └──") format_string(link_name_s, true, true, " 4.0K", " └──")
); );
(r, r2) (r, r2)
@@ -217,13 +226,13 @@ fn hard_link_output(dir_s: &str, file_path_s: &str, link_name_s: &str) -> (Strin
let r = format!( let r = format!(
"{} "{}
{}", {}",
format_string(dir_s, true, true, " 8.0K", ""), format_string(dir_s, true, true, " 8.0K", "─┬"),
format_string(file_path_s, true, true, " 4.0K", " └──") format_string(file_path_s, true, true, " 4.0K", " └──")
); );
let r2 = format!( let r2 = format!(
"{} "{}
{}", {}",
format_string(dir_s, true, true, " 8.0K", ""), format_string(dir_s, true, true, " 8.0K", "─┬"),
format_string(link_name_s, true, true, " 4.0K", " └──") format_string(link_name_s, true, true, " 4.0K", " └──")
); );
(r, r2) (r, r2)
@@ -257,7 +266,7 @@ fn recursive_sym_link_output(dir: &str, link_name: &str) -> String {
format!( format!(
"{} "{}
{}", {}",
format_string(dir, true, true, " 4.0K", ""), format_string(dir, true, true, " 4.0K", "─┬"),
format_string(link_name, true, true, " 4.0K", " └──",), format_string(link_name, true, true, " 4.0K", " └──",),
) )
} }
@@ -266,9 +275,8 @@ fn recursive_sym_link_output(dir: &str, link_name: &str) -> String {
format!( format!(
"{} "{}
{}", {}",
format_string(dir, true, true, " 4.0K", ""), format_string(dir, true, true, " 4.0K", "─┬"),
format_string(link_name, true, true, " 0B", " └──",), format_string(link_name, true, true, " 0B", " └──",),
) )
} }
// TODO: add test for bad path
+70 -20
View File
@@ -1,34 +1,49 @@
use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::cmp::Ordering;
use walkdir::WalkDir; use walkdir::WalkDir;
use std::path::Path;
use std::path::PathBuf;
mod platform; mod platform;
use self::platform::*; use self::platform::*;
pub fn simplify_dir_names(filenames: Vec<&str>) -> HashSet<String> {
let mut top_level_names: HashSet<String> = HashSet::new();
for t in filenames {
let top_level_name = strip_end_slashes(t);
let mut can_add = true;
let mut to_remove: Vec<String> = Vec::new();
for tt in top_level_names.iter() {
let temp = tt.to_string();
if top_level_name.starts_with(&temp) {
can_add = false;
} else if tt.starts_with(&top_level_name) {
to_remove.push(temp);
}
}
for tr in to_remove {
top_level_names.remove(&tr);
}
if can_add {
top_level_names.insert(top_level_name);
}
}
top_level_names
}
pub fn get_dir_tree( pub fn get_dir_tree(
filenames: &Vec<&str>, top_level_names: HashSet<String>,
apparent_size: bool, apparent_size: bool,
) -> (bool, HashMap<String, u64>, Vec<String>) { ) -> (bool, HashMap<String, u64>, HashSet<String>) {
let mut permissions = 0; let mut permissions = 0;
let mut inodes: HashSet<(u64, u64)> = HashSet::new(); let mut inodes: HashSet<(u64, u64)> = HashSet::new();
let mut data: HashMap<String, u64> = HashMap::new(); let mut data: HashMap<String, u64> = HashMap::new();
let mut top_level_names = Vec::new();
for b in filenames { for b in top_level_names.iter() {
let top_level_name = strip_end_slashes(b); examine_dir(&b, apparent_size, &mut inodes, &mut data, &mut permissions);
examine_dir(
&Path::new(&top_level_name).to_path_buf(),
apparent_size,
&mut inodes,
&mut data,
&mut permissions,
);
top_level_names.push(top_level_name);
} }
(permissions == 0, data, top_level_names) (permissions == 0, data, top_level_names)
} }
@@ -42,7 +57,7 @@ fn strip_end_slashes(s: &str) -> String {
} }
fn examine_dir( fn examine_dir(
top_dir: &PathBuf, top_dir: &String,
apparent_size: bool, apparent_size: bool,
inodes: &mut HashSet<(u64, u64)>, inodes: &mut HashSet<(u64, u64)>,
data: &mut HashMap<String, u64>, data: &mut HashMap<String, u64>,
@@ -66,9 +81,9 @@ fn examine_dir(
let mut e_path = e.path().to_path_buf(); let mut e_path = e.path().to_path_buf();
loop { loop {
let path_name = e_path.to_string_lossy().to_string(); let path_name = e_path.to_string_lossy().to_string();
let s = data.entry(path_name).or_insert(0); let s = data.entry(path_name.clone()).or_insert(0);
*s += size; *s += size;
if e_path == *top_dir { if path_name == *top_dir {
break; break;
} }
e_path.pop(); e_path.pop();
@@ -103,3 +118,38 @@ pub fn find_big_ones<'a>(new_l: Vec<(String, u64)>, max_to_show: usize) -> Vec<(
new_l new_l
} }
} }
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_simplify_dir() {
let mut correct = HashSet::new();
correct.insert("a".to_string());
assert!(simplify_dir_names(vec!["a"]) == correct);
}
#[test]
fn test_simplify_dir_rm_subdir() {
let mut correct = HashSet::new();
correct.insert("a/b".to_string());
assert!(simplify_dir_names(vec!["a/b", "a/b/c", "a/b/d/f"]) == correct);
}
#[test]
fn test_simplify_dir_duplicates() {
let mut correct = HashSet::new();
correct.insert("a/b".to_string());
correct.insert("c".to_string());
assert!(simplify_dir_names(vec!["a/b", "a/b//", "c", "c/"]) == correct);
}
#[test]
fn test_simplify_dir_rm_subdir_and_not_substrings() {
let mut correct = HashSet::new();
correct.insert("a/b".to_string());
correct.insert("c/a/b".to_string());
correct.insert("b".to_string());
assert!(simplify_dir_names(vec!["a/b", "c/a/b/", "b"]) == correct);
}
}
+5 -43
View File
@@ -6,58 +6,20 @@ fn get_block_size() -> u64 {
512 512
} }
#[cfg(target_os = "linux")] #[cfg(target_family = "unix")]
pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
use std::os::linux::fs::MetadataExt;
match d.metadata().ok() {
Some(md) => {
let inode = Some((md.st_ino(), md.st_dev()));
if use_apparent_size {
Some((md.len(), inode))
} else {
Some((md.st_blocks() * get_block_size(), inode))
}
}
None => None,
}
}
#[cfg(target_os = "unix")]
pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> { pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
match d.metadata().ok() { d.metadata().ok().map_or(None, |md| {
Some(md) => {
let inode = Some((md.ino(), md.dev())); let inode = Some((md.ino(), md.dev()));
if use_apparent_size { if use_apparent_size {
Some((md.len(), inode)) Some((md.len(), inode))
} else { } else {
Some((md.blocks() * get_block_size(), inode)) Some((md.blocks() * get_block_size(), inode))
} }
} })
None => None,
}
} }
#[cfg(target_os = "macos")] #[cfg(not(target_family = "unix"))]
pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
use std::os::macos::fs::MetadataExt;
match d.metadata().ok() {
Some(md) => {
let inode = Some((md.st_ino(), md.st_dev()));
if use_apparent_size {
Some((md.len(), inode))
} else {
Some((md.st_blocks() * get_block_size(), inode))
}
}
None => None,
}
}
#[cfg(not(any(target_os = "linux", target_os = "unix", target_os = "macos")))]
pub fn get_metadata(d: &DirEntry, _apparent: bool) -> Option<(u64, Option<(u64, u64)>)> { pub fn get_metadata(d: &DirEntry, _apparent: bool) -> Option<(u64, Option<(u64, u64)>)> {
match d.metadata().ok() { d.metadata().ok().map_or(None, |md| Some((md.len(), None)))
Some(md) => Some((md.len(), None)),
None => None,
}
} }