use std::io::Write;
use anyhow::{Context, Result};
use clap::Parser;
use crate::cli::drive::format::{output_as, sanitize_for_terminal, OutputFormat};
use crate::cli::drive::helpers::active_account_rules;
use crate::drive::write_gate::FolderPermissionRule;
#[derive(Parser)]
pub struct ShowCommand {
#[arg(short = 'o', long, value_enum, default_value_t = OutputFormat::Table)]
pub output: OutputFormat,
}
impl ShowCommand {
pub fn execute(self) -> Result<()> {
let rules = active_account_rules()?;
run_show(&rules, &self.output)
}
}
fn run_show(rules: &[FolderPermissionRule], output: &OutputFormat) -> Result<()> {
if output_as(&rules.to_vec(), output)? {
return Ok(());
}
let stdout = std::io::stdout();
let mut handle = stdout.lock();
render_rules_table(rules, &mut handle)
}
fn render_rules_table(rules: &[FolderPermissionRule], out: &mut dyn Write) -> Result<()> {
if rules.is_empty() {
writeln!(
out,
"No write-permission rules configured for this account — every \
create/upload/edit is refused everywhere. Add rules under \
drive.accounts.<name>.write_permissions.rules in \
~/.omni-dev/settings.json."
)
.context("Failed to write empty-table message")?;
return Ok(());
}
let ids: Vec<String> = rules
.iter()
.map(|r| sanitize_for_terminal(&r.folder_id))
.collect();
let id_width = "FOLDER_ID"
.len()
.max(ids.iter().map(String::len).max().unwrap_or(0));
let allow_strings: Vec<String> = rules.iter().map(|r| format_op_set(&r.allow)).collect();
let allow_width = "ALLOW"
.len()
.max(allow_strings.iter().map(String::len).max().unwrap_or(0));
let deny_strings: Vec<String> = rules.iter().map(|r| format_op_set(&r.deny)).collect();
let deny_width = "DENY"
.len()
.max(deny_strings.iter().map(String::len).max().unwrap_or(0));
writeln!(
out,
"{:<id_width$} RECURSIVE {:<allow_width$} {:<deny_width$}",
"FOLDER_ID", "ALLOW", "DENY"
)
.context("Failed to write header row")?;
for (i, rule) in rules.iter().enumerate() {
writeln!(
out,
"{:<id_width$} {:<9} {:<allow_width$} {:<deny_width$}",
ids[i], rule.recursive, allow_strings[i], deny_strings[i],
)
.context("Failed to write rule row")?;
}
Ok(())
}
fn format_op_set(
ops: &std::collections::HashSet<crate::drive::write_gate::DriveOperation>,
) -> String {
if ops.is_empty() {
return "-".to_string();
}
let mut rendered: Vec<String> = ops.iter().map(ToString::to_string).collect();
rendered.sort();
rendered.join(",")
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use crate::drive::write_gate::DriveOperation;
fn sample_rule() -> FolderPermissionRule {
FolderPermissionRule {
folder_id: "folder-1".to_string(),
recursive: true,
allow: [DriveOperation::Create, DriveOperation::Upload]
.into_iter()
.collect(),
deny: std::iter::once(DriveOperation::Edit).collect(),
}
}
#[test]
fn render_table_empty_explains_default_deny() {
let mut buf = Vec::new();
render_rules_table(&[], &mut buf).unwrap();
let out = String::from_utf8(buf).unwrap();
assert!(out.contains("No write-permission rules configured"));
assert!(out.contains("write_permissions"));
}
#[test]
fn render_table_writes_header_and_rows() {
let rules = [sample_rule()];
let mut buf = Vec::new();
render_rules_table(&rules, &mut buf).unwrap();
let out = String::from_utf8(buf).unwrap();
assert!(out.contains("FOLDER_ID"));
assert!(out.contains("RECURSIVE"));
assert!(out.contains("ALLOW"));
assert!(out.contains("DENY"));
assert!(out.contains("folder-1"));
assert!(out.contains("true"));
assert!(out.contains("create,upload"));
assert!(out.contains("edit"));
}
#[test]
fn render_table_uses_dash_for_empty_op_sets() {
let rules = [FolderPermissionRule {
folder_id: "folder-1".to_string(),
recursive: false,
allow: std::collections::HashSet::default(),
deny: std::collections::HashSet::default(),
}];
let mut buf = Vec::new();
render_rules_table(&rules, &mut buf).unwrap();
let out = String::from_utf8(buf).unwrap();
assert!(out.contains('-'));
}
#[test]
fn render_table_strips_control_bytes_from_folder_id() {
let rules = [FolderPermissionRule {
folder_id: "fo\rlder\x1b[31m".to_string(),
recursive: false,
allow: std::collections::HashSet::default(),
deny: std::collections::HashSet::default(),
}];
let mut buf = Vec::new();
render_rules_table(&rules, &mut buf).unwrap();
let out = String::from_utf8(buf).unwrap();
assert!(
!out.contains(|c: char| c.is_control() && c != '\n'),
"{out:?}"
);
}
#[test]
fn format_op_set_sorts_alphabetically_and_dedupes_via_hashset() {
let ops: std::collections::HashSet<_> = [DriveOperation::Upload, DriveOperation::Create]
.into_iter()
.collect();
assert_eq!(format_op_set(&ops), "create,upload");
}
#[test]
fn run_show_table_path_writes_to_stdout() {
run_show(&[sample_rule()], &OutputFormat::Table).unwrap();
}
#[test]
fn run_show_json_path_returns_ok() {
run_show(&[sample_rule()], &OutputFormat::Json).unwrap();
}
}