Radish alpha
r
rad:z39mP9rQAaGmERfUMPULfPUi473tY
Radicle terminal user interface
Radicle
Git
bin(inbox): Implement CLI integration
Erik Kundt committed 1 year ago
commit 965bafc99335eedc40c6bbc29db6d2401fe7217b
parent ab585a9
1 file changed +214 -20
modified bin/commands/inbox.rs
@@ -26,6 +26,7 @@ Usage
List options

    --mode <MODE>           Set selection mode; see MODE below (default: operation)
+
    --json                  Return JSON on stderr instead of calling `rad`
    
    --sort-by <field>       Sort by `id` or `timestamp` (default: timestamp)
    --reverse, -r           Reverse the list
@@ -35,7 +36,8 @@ List options

Other options

-
    --help                  Print help    
+
    --no-forward            Don't forward command to `rad` (default: true)
+
    --help                  Print help (enables forwarding)
"#,
};

@@ -45,11 +47,13 @@ pub struct Options {

pub enum Operation {
    List { opts: ListOptions },
+
    Other { args: Vec<OsString> },
}

#[derive(PartialEq, Eq)]
pub enum OperationName {
    List,
+
    Other,
}

#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -57,14 +61,18 @@ pub struct ListOptions {
    mode: Mode,
    filter: inbox::Filter,
    sort_by: inbox::SortBy,
+
    json: bool,
}

impl Args for Options {
    fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
        use lexopt::prelude::*;

-
        let mut parser = lexopt::Parser::from_args(args);
-
        let mut op: Option<OperationName> = None;
+
        let mut parser = lexopt::Parser::from_args(args.clone());
+
        let mut op = OperationName::List;
+
        let mut forward = None;
+
        let mut json = false;
+
        let mut help = false;
        let mut repository_mode = None;
        let mut reverse = None;
        let mut field = None;
@@ -72,12 +80,23 @@ impl Args for Options {

        while let Some(arg) = parser.next()? {
            match arg {
+
                Long("no-forward") => {
+
                    forward = Some(false);
+
                }
+
                Long("json") => {
+
                    json = true;
+
                }
                Long("help") | Short('h') => {
-
                    return Err(Error::Help.into());
+
                    help = true;
+
                    // Only enable forwarding if it was not already disabled explicitly
+
                    forward = match forward {
+
                        Some(false) => Some(false),
+
                        _ => Some(true),
+
                    };
                }

-
                // select options.
-
                Long("mode") | Short('m') if op == Some(OperationName::List) => {
+
                // list options.
+
                Long("mode") | Short('m') if op == OperationName::List => {
                    let val = parser.value()?;
                    let val = val.to_str().unwrap_or_default();

@@ -102,7 +121,7 @@ impl Args for Options {
                    }
                }

-
                Long("repo") if repository_mode.is_none() && op.is_some() => {
+
                Long("repo") if repository_mode.is_none() => {
                    let val = parser.value()?;
                    let repo = terminal::args::rid(&val)?;

@@ -112,14 +131,27 @@ impl Args for Options {
                    repository_mode = Some(RepositoryMode::All);
                }

-
                Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
-
                    "list" => op = Some(OperationName::List),
-
                    unknown => anyhow::bail!("unknown operation '{}'", unknown),
+
                Value(val) if op == OperationName::List => match val.to_string_lossy().as_ref() {
+
                    "list" => op = OperationName::List,
+
                    _ => op = OperationName::Other,
                },
-
                _ => return Err(anyhow!(arg.unexpected())),
+
                _ => {
+
                    if op == OperationName::List {
+
                        return Err(anyhow!(arg.unexpected()));
+
                    }
+
                }
            }
        }

+
        // Disable forwarding if it was not enabled via `--help` or was
+
        // not disabled explicitly.
+
        let forward = forward.unwrap_or_default();
+

+
        // Show local help
+
        if help && !forward {
+
            return Err(Error::Help.into());
+
        }
+

        list_opts.mode = list_opts
            .mode
            .with_repository(repository_mode.unwrap_or_default());
@@ -132,9 +164,14 @@ impl Args for Options {
            inbox::SortBy::default()
        };

-
        let op = match op.ok_or_else(|| anyhow!("an operation must be provided"))? {
-
            OperationName::List => Operation::List { opts: list_opts },
+
        // Map local commands. Forward help and ignore `no-forward`.
+
        let op = match op {
+
            OperationName::List if !forward => Operation::List {
+
                opts: ListOptions { json, ..list_opts },
+
            },
+
            _ => Operation::Other { args },
        };
+

        Ok((Options { op }, vec![]))
    }
}
@@ -163,18 +200,175 @@ pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Resul
                filter: opts.filter.clone(),
                sort_by: opts.sort_by,
            };
-
            let output = list::App::new(context).run().await?;
+
            let selection = list::App::new(context).run().await?;

-
            let output = output
-
                .map(|o| serde_json::to_string(&o).unwrap_or_default())
-
                .unwrap_or_default();
+
            if opts.json {
+
                let selection = selection
+
                    .map(|o| serde_json::to_string(&o).unwrap_or_default())
+
                    .unwrap_or_default();

-
            log::info!("About to print to `stderr`: {}", output);
-
            log::info!("Exiting inbox listing interface..");
+
                log::info!("About to print to `stderr`: {}", selection);
+
                log::info!("Exiting inbox listing interface..");

-
            eprint!("{output}");
+
                eprint!("{selection}");
+
            } else if let Some(selection) = selection {
+
                let mut args = vec![];
+

+
                if let Some(operation) = selection.operation {
+
                    args.push(operation.to_string());
+
                }
+
                if let Some(id) = selection.ids.first() {
+
                    args.push(format!("{id}"));
+
                }
+

+
                let args = args.into_iter().map(OsString::from).collect::<Vec<_>>();
+
                let _ = crate::terminal::run_rad("inbox", &args);
+
            }
+
        }
+
        Operation::Other { args } => {
+
            let _ = crate::terminal::run_rad("inbox", &args);
        }
    }

    Ok(())
}
+

+
#[cfg(test)]
+
mod cli {
+
    use std::process::Command;
+

+
    use assert_cmd::prelude::*;
+

+
    use predicates::prelude::*;
+

+
    mod assert {
+
        use predicates::prelude::*;
+
        use predicates::str::ContainsPredicate;
+

+
        pub fn is_tui() -> ContainsPredicate {
+
            predicate::str::contains("Inappropriate ioctl for device")
+
        }
+

+
        pub fn is_rad_manual() -> ContainsPredicate {
+
            predicate::str::contains("rad-inbox")
+
        }
+

+
        pub fn is_inbox_help() -> ContainsPredicate {
+
            predicate::str::contains("Terminal interfaces for notifications")
+
        }
+
    }
+

+
    #[test]
+
    // #[ignore = "breaks stdout"]
+
    fn empty_operation() -> Result<(), Box<dyn std::error::Error>> {
+
        let mut cmd = Command::cargo_bin("rad-tui")?;
+

+
        cmd.arg("inbox");
+
        cmd.assert().failure().stdout(assert::is_tui());
+

+
        Ok(())
+
    }
+

+
    #[test]
+
    fn empty_operation_is_not_forwarded() -> Result<(), Box<dyn std::error::Error>> {
+
        let mut cmd = Command::cargo_bin("rad-tui")?;
+

+
        cmd.arg("inbox");
+
        cmd.assert().failure().stdout(assert::is_tui());
+

+
        Ok(())
+
    }
+

+
    #[test]
+
    fn empty_operation_with_help_is_forwarded() -> Result<(), Box<dyn std::error::Error>> {
+
        let mut cmd = Command::cargo_bin("rad-tui")?;
+

+
        cmd.args(["inbox", "--help"]);
+
        cmd.assert().success().stdout(assert::is_rad_manual());
+

+
        Ok(())
+
    }
+

+
    #[test]
+
    fn empty_operation_with_help_is_not_forwarded() -> Result<(), Box<dyn std::error::Error>> {
+
        let mut cmd = Command::cargo_bin("rad-tui")?;
+

+
        cmd.args(["inbox", "--help", "--no-forward"]);
+
        cmd.assert().success().stdout(assert::is_inbox_help());
+

+
        Ok(())
+
    }
+

+
    #[test]
+
    fn empty_operation_is_not_forwarded_explicitly() -> Result<(), Box<dyn std::error::Error>> {
+
        let mut cmd = Command::cargo_bin("rad-tui")?;
+

+
        cmd.args(["inbox", "--no-forward"]);
+
        cmd.assert().failure().stdout(assert::is_tui());
+

+
        Ok(())
+
    }
+

+
    #[test]
+
    fn list_operation_is_not_forwarded() -> Result<(), Box<dyn std::error::Error>> {
+
        let mut cmd = Command::cargo_bin("rad-tui")?;
+

+
        cmd.args(["inbox", "list"]);
+
        cmd.assert().failure().stdout(assert::is_tui());
+

+
        Ok(())
+
    }
+

+
    #[test]
+
    fn list_operation_is_not_forwarded_explicitly() -> Result<(), Box<dyn std::error::Error>> {
+
        let mut cmd = Command::cargo_bin("rad-tui")?;
+

+
        cmd.args(["inbox", "list", "--no-forward"]);
+
        cmd.assert().failure().stdout(assert::is_tui());
+

+
        Ok(())
+
    }
+

+
    #[test]
+
    fn list_operation_with_help_is_forwarded() -> Result<(), Box<dyn std::error::Error>> {
+
        let mut cmd = Command::cargo_bin("rad-tui")?;
+

+
        cmd.args(["inbox", "list", "--help"]);
+
        cmd.assert().success().stdout(assert::is_rad_manual());
+

+
        Ok(())
+
    }
+

+
    #[test]
+
    fn list_operation_with_help_is_not_forwarded() -> Result<(), Box<dyn std::error::Error>> {
+
        let mut cmd = Command::cargo_bin("rad-tui")?;
+

+
        cmd.args(["inbox", "list", "--help", "--no-forward"]);
+
        cmd.assert().success().stdout(assert::is_inbox_help());
+

+
        Ok(())
+
    }
+

+
    #[test]
+
    fn list_operation_with_help_is_not_forwarded_reversed() -> Result<(), Box<dyn std::error::Error>>
+
    {
+
        let mut cmd = Command::cargo_bin("rad-tui")?;
+

+
        cmd.args(["inbox", "list", "--no-forward", "--help"]);
+
        cmd.assert().success().stdout(assert::is_inbox_help());
+

+
        Ok(())
+
    }
+

+
    #[test]
+
    fn unknown_operation_show_is_forwarded() -> Result<(), Box<dyn std::error::Error>> {
+
        let mut cmd = Command::cargo_bin("rad-tui")?;
+

+
        cmd.args(["inbox", "show"]);
+
        cmd.assert()
+
            .success()
+
            .stdout(predicate::str::contains("a Notification ID must be given"));
+

+
        Ok(())
+
    }
+
}