From 0266b111e1bfb6ff9bf3969fa8fbe0685f440a92 Mon Sep 17 00:00:00 2001 From: bbb651 Date: Mon, 13 Jan 2025 21:01:52 +0200 Subject: [PATCH] cli: Always print help to stdout and support json for explicit `help` --- src/cli.rs | 12 +++++-- src/ipc/client.rs | 7 ++-- src/main.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 041b03439..13eb843b7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -38,7 +38,7 @@ pub enum Sub { /// Communicate with the running niri instance. Msg { #[command(subcommand)] - msg: Msg, + msg: Option>, /// Format output as JSON. #[arg(short, long)] json: bool, @@ -75,7 +75,7 @@ pub enum Msg { /// Perform an action. Action { #[command(subcommand)] - action: Action, + action: Option>, }, /// Change output configuration temporarily. /// @@ -99,3 +99,11 @@ pub enum Msg { /// Request an error from the running niri instance. RequestError, } + +#[derive(Debug, Subcommand)] +#[command(disable_help_subcommand(true))] +pub enum SubcommandOrHelp { + #[command(flatten)] + Subcommand(T), + Help, +} diff --git a/src/ipc/client.rs b/src/ipc/client.rs index 8682d8d3f..b60e60238 100644 --- a/src/ipc/client.rs +++ b/src/ipc/client.rs @@ -10,7 +10,7 @@ use niri_ipc::{ }; use serde_json::json; -use crate::cli::Msg; +use crate::cli::{Msg, SubcommandOrHelp}; use crate::utils::version; pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> { @@ -19,7 +19,10 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> { Msg::Outputs => Request::Outputs, Msg::FocusedWindow => Request::FocusedWindow, Msg::FocusedOutput => Request::FocusedOutput, - Msg::Action { action } => Request::Action(action.clone()), + Msg::Action { + action: Some(SubcommandOrHelp::Subcommand(action)), + } => Request::Action(action.clone()), + Msg::Action { action: _ } => unreachable!(), Msg::Output { output, action } => Request::Output { output: output.clone(), action: action.clone(), diff --git a/src/main.rs b/src/main.rs index 55fc032f7..eea5c4cf1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,9 +9,9 @@ use std::path::PathBuf; use std::process::Command; use std::{env, mem}; -use clap::Parser; +use clap::{CommandFactory, Parser}; use directories::ProjectDirs; -use niri::cli::{Cli, Sub}; +use niri::cli::{Cli, Msg, Sub, SubcommandOrHelp}; #[cfg(feature = "dbus")] use niri::dbus; use niri::ipc::client::handle_msg; @@ -26,6 +26,7 @@ use niri_config::Config; use niri_ipc::socket::SOCKET_PATH_ENV; use portable_atomic::Ordering; use sd_notify::NotifyState; +use serde::Serialize; use smithay::reexports::calloop::EventLoop; use smithay::reexports::wayland_server::Display; use tracing_subscriber::EnvFilter; @@ -66,6 +67,7 @@ fn main() -> Result<(), Box> { .init(); let cli = Cli::parse(); + let mut command = Cli::command(); if cli.session { // If we're starting as a session, assume that the intention is to start on a TTY. Remove @@ -100,7 +102,82 @@ fn main() -> Result<(), Box> { info!("config is valid"); return Ok(()); } - Sub::Msg { msg, json } => { + Sub::Msg { msg: None, .. } => { + let subcommand = command.find_subcommand_mut("msg").unwrap(); + subcommand.print_help().unwrap(); + return Ok(()); + } + Sub::Msg { + msg: Some(SubcommandOrHelp::Help), + json, + } => { + let subcommand = command.find_subcommand_mut("msg").unwrap(); + if json { + #[derive(Serialize)] + struct ActionJson<'a> { + name: &'a str, + description: Option, + } + let actions = subcommand + .get_subcommands() + .map(|action| ActionJson { + name: action.get_name(), + description: action.get_about().map(|about| format!("{about}")), + }) + .collect::>(); + serde_json::to_writer(std::io::stdout(), &actions).unwrap(); + } else { + subcommand.print_help().unwrap(); + } + return Ok(()); + } + Sub::Msg { + msg: Some(SubcommandOrHelp::Subcommand(Msg::Action { action: None, .. })), + .. + } => { + let subcommand = command + .find_subcommand_mut("msg") + .unwrap() + .find_subcommand_mut("action") + .unwrap(); + subcommand.print_help().unwrap(); + return Ok(()); + } + Sub::Msg { + msg: + Some(SubcommandOrHelp::Subcommand(Msg::Action { + action: Some(SubcommandOrHelp::Help), + })), + json, + } => { + let subcommand = command + .find_subcommand_mut("msg") + .unwrap() + .find_subcommand_mut("action") + .unwrap(); + if json { + #[derive(Serialize)] + struct ActionJson<'a> { + name: &'a str, + description: Option, + } + let actions = subcommand + .get_subcommands() + .map(|action| ActionJson { + name: action.get_name(), + description: action.get_about().map(|about| format!("{about}")), + }) + .collect::>(); + serde_json::to_writer(std::io::stdout(), &actions).unwrap(); + } else { + subcommand.print_help().unwrap(); + } + return Ok(()); + } + Sub::Msg { + msg: Some(SubcommandOrHelp::Subcommand(msg)), + json, + } => { handle_msg(msg, json)?; return Ok(()); }