Complete Gotcha issue CLI workflow
This commit is contained in:
@@ -169,20 +169,30 @@ fn print_user(user: &User) {
|
||||
}
|
||||
|
||||
fn print_repositories(repositories: &[Repository]) {
|
||||
println!("REPOSITORY\tVISIBILITY\tUPDATED\tDESCRIPTION");
|
||||
for repository in repositories {
|
||||
println!(
|
||||
"{}\t{}\t{}\t{}",
|
||||
repository.full_name.as_deref().unwrap_or("unknown"),
|
||||
if repository.private.unwrap_or(false) {
|
||||
"private"
|
||||
} else {
|
||||
"public"
|
||||
},
|
||||
repository.updated_at.as_deref().unwrap_or("-"),
|
||||
repository.description.as_deref().unwrap_or("")
|
||||
);
|
||||
}
|
||||
print_table(
|
||||
&[
|
||||
("REPOSITORY", 40),
|
||||
("VISIBILITY", 10),
|
||||
("UPDATED", 25),
|
||||
("DESCRIPTION", 60),
|
||||
],
|
||||
repositories
|
||||
.iter()
|
||||
.map(|repository| {
|
||||
vec![
|
||||
repository.full_name.as_deref().unwrap_or("unknown").into(),
|
||||
if repository.private.unwrap_or(false) {
|
||||
"private"
|
||||
} else {
|
||||
"public"
|
||||
}
|
||||
.into(),
|
||||
repository.updated_at.as_deref().unwrap_or("-").into(),
|
||||
repository.description.as_deref().unwrap_or("").into(),
|
||||
]
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
fn print_repository(repository: &Repository) {
|
||||
@@ -216,6 +226,93 @@ fn print_json(value: &Value) -> Result<(), serde_json::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_table(columns: &[(&str, usize)], rows: Vec<Vec<String>>) {
|
||||
println!("{}", format_table(columns, &rows, terminal_width()));
|
||||
}
|
||||
|
||||
fn format_table(columns: &[(&str, usize)], rows: &[Vec<String>], width: usize) -> String {
|
||||
let minimums = columns
|
||||
.iter()
|
||||
.map(|(header, _)| header.chars().count())
|
||||
.collect::<Vec<_>>();
|
||||
let mut widths = minimums.clone();
|
||||
for (column, (_, maximum)) in columns.iter().enumerate() {
|
||||
widths[column] = rows
|
||||
.iter()
|
||||
.filter_map(|row| row.get(column))
|
||||
.map(|value| value.chars().count())
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
.max(widths[column])
|
||||
.min(*maximum);
|
||||
}
|
||||
let separator_width = columns.len().saturating_sub(1) * 2;
|
||||
while widths.iter().sum::<usize>() + separator_width > width {
|
||||
let Some(column) = widths
|
||||
.iter()
|
||||
.zip(&minimums)
|
||||
.enumerate()
|
||||
.filter(|(_, (current, minimum))| current > minimum)
|
||||
.max_by_key(|(_, (current, minimum))| *current - *minimum)
|
||||
.map(|(column, _)| column)
|
||||
else {
|
||||
break;
|
||||
};
|
||||
widths[column] -= 1;
|
||||
}
|
||||
|
||||
let mut lines = Vec::with_capacity(rows.len() + 2);
|
||||
lines.push(table_row(
|
||||
&columns
|
||||
.iter()
|
||||
.map(|(header, _)| (*header).to_owned())
|
||||
.collect::<Vec<_>>(),
|
||||
&widths,
|
||||
));
|
||||
lines.push(
|
||||
widths
|
||||
.iter()
|
||||
.map(|width| "-".repeat(*width))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
);
|
||||
lines.extend(rows.iter().map(|row| table_row(row, &widths)));
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
fn table_row(values: &[String], widths: &[usize]) -> String {
|
||||
values
|
||||
.iter()
|
||||
.zip(widths)
|
||||
.map(|(value, width)| table_cell(value, *width))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
.trim_end()
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
fn table_cell(value: &str, width: usize) -> String {
|
||||
let value = value.lines().next().unwrap_or_default();
|
||||
let length = value.chars().count();
|
||||
if length <= width {
|
||||
return format!("{value}{}", " ".repeat(width - length));
|
||||
}
|
||||
let mut shortened = value
|
||||
.chars()
|
||||
.take(width.saturating_sub(1))
|
||||
.collect::<String>();
|
||||
shortened.push('…');
|
||||
shortened
|
||||
}
|
||||
|
||||
fn terminal_width() -> usize {
|
||||
env::var("COLUMNS")
|
||||
.ok()
|
||||
.and_then(|value| value.parse().ok())
|
||||
.filter(|width| *width >= 40)
|
||||
.unwrap_or(100)
|
||||
}
|
||||
|
||||
fn requested_help(command: &[String]) -> Result<Option<&'static str>, String> {
|
||||
match command {
|
||||
[] => Ok(Some(ROOT_HELP)),
|
||||
@@ -393,4 +490,23 @@ mod tests {
|
||||
.starts_with("Usage: gotcha repo show")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_bounded_aligned_tables_without_tabs() {
|
||||
let table = format_table(
|
||||
&[("INDEX", 8), ("STATE", 10), ("TITLE", 60)],
|
||||
&[vec![
|
||||
"22".into(),
|
||||
"open".into(),
|
||||
"A deliberately long issue title that must be shortened".into(),
|
||||
]],
|
||||
40,
|
||||
);
|
||||
|
||||
assert!(!table.contains('\t'));
|
||||
assert!(table.contains('…'));
|
||||
assert_eq!(table.lines().count(), 3);
|
||||
assert!(table.lines().all(|line| line.chars().count() <= 40));
|
||||
assert!(table.lines().next().unwrap().starts_with("INDEX STATE"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user