From 33f8abc7ce23e48a84c14b7b1235911f23e1458e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 19 Jun 2024 22:24:20 +0100 Subject: [PATCH 1/4] feat(cli): drop the --format option * Only use JSON. --- rust/agama-cli/src/config.rs | 10 ++--- rust/agama-cli/src/main.rs | 8 +--- rust/agama-cli/src/printers.rs | 74 ---------------------------------- 3 files changed, 5 insertions(+), 87 deletions(-) delete mode 100644 rust/agama-cli/src/printers.rs diff --git a/rust/agama-cli/src/config.rs b/rust/agama-cli/src/config.rs index 1f97056616..20b0651f2b 100644 --- a/rust/agama-cli/src/config.rs +++ b/rust/agama-cli/src/config.rs @@ -1,9 +1,6 @@ use std::io::{self, Read}; -use crate::{ - error::CliError, - printers::{print, Format}, -}; +use crate::error::CliError; use agama_lib::{ auth::AuthToken, connection, install_settings::InstallSettings, Store as SettingsStore, }; @@ -28,7 +25,7 @@ pub enum ConfigAction { Load, } -pub async fn run(subcommand: ConfigCommands, format: Format) -> anyhow::Result<()> { +pub async fn run(subcommand: ConfigCommands) -> anyhow::Result<()> { let Some(token) = AuthToken::find() else { println!("You need to login for generating a valid token"); return Ok(()); @@ -41,7 +38,8 @@ pub async fn run(subcommand: ConfigCommands, format: Format) -> anyhow::Result<( match command { ConfigAction::Show => { let model = store.load().await?; - print(model, std::io::stdout(), format)?; + let json = serde_json::to_string_pretty(&model)?; + println!("{}", json); Ok(()) } ConfigAction::Load => { diff --git a/rust/agama-cli/src/main.rs b/rust/agama-cli/src/main.rs index 87cf6ba4bc..e3bde3f913 100644 --- a/rust/agama-cli/src/main.rs +++ b/rust/agama-cli/src/main.rs @@ -5,7 +5,6 @@ mod commands; mod config; mod error; mod logs; -mod printers; mod profile; mod progress; mod questions; @@ -18,7 +17,6 @@ use auth::run as run_auth_cmd; use commands::Commands; use config::run as run_config_cmd; use logs::run as run_logs_cmd; -use printers::Format; use profile::run as run_profile_cmd; use progress::InstallerProgress; use questions::run as run_questions_cmd; @@ -39,10 +37,6 @@ use std::{ struct Cli { #[command(subcommand)] pub command: Commands, - - /// Format output - #[arg(value_enum, short, long, default_value_t = Format::Json)] - pub format: Format, } async fn probe() -> anyhow::Result<()> { @@ -129,7 +123,7 @@ async fn run_command(cli: Cli) -> anyhow::Result<()> { Commands::Config(subcommand) => { let manager = build_manager().await?; wait_for_services(&manager).await?; - run_config_cmd(subcommand, cli.format).await + run_config_cmd(subcommand).await } Commands::Probe => { let manager = build_manager().await?; diff --git a/rust/agama-cli/src/printers.rs b/rust/agama-cli/src/printers.rs deleted file mode 100644 index 4fe28e9c8a..0000000000 --- a/rust/agama-cli/src/printers.rs +++ /dev/null @@ -1,74 +0,0 @@ -use serde::Serialize; -use std::fmt::Debug; -use std::io::Write; - -/// Prints the content using the given format -/// -/// # Example -/// -///```rust -/// use agama_lib::users; -/// use agama_cli::printers::{print, Format}; -/// use std::io; -/// -/// let user = users::User { login: "jane doe".to_string() }; -/// print(user, io::stdout(), Some(Format::Json)) -/// .expect("Something went wrong!") -/// ``` -pub fn print(content: T, writer: W, format: Format) -> anyhow::Result<()> -where - T: serde::Serialize + Debug, - W: Write, -{ - let printer: Box> = match format { - Format::Json => Box::new(JsonPrinter { content, writer }), - Format::Yaml => Box::new(YamlPrinter { content, writer }), - _ => Box::new(TextPrinter { content, writer }), - }; - printer.print() -} - -/// Supported output formats -#[derive(clap::ValueEnum, Clone)] -pub enum Format { - Json, - Yaml, - Text, -} - -pub trait Printer { - fn print(self: Box) -> anyhow::Result<()>; -} - -pub struct JsonPrinter { - content: T, - writer: W, -} - -impl Printer for JsonPrinter { - fn print(mut self: Box) -> anyhow::Result<()> { - let json = serde_json::to_string(&self.content)?; - Ok(writeln!(self.writer, "{}", json)?) - } -} -pub struct TextPrinter { - content: T, - writer: W, -} - -impl Printer for TextPrinter { - fn print(mut self: Box) -> anyhow::Result<()> { - Ok(writeln!(self.writer, "{:?}", &self.content)?) - } -} - -pub struct YamlPrinter { - content: T, - writer: W, -} - -impl Printer for YamlPrinter { - fn print(self: Box) -> anyhow::Result<()> { - Ok(serde_yaml::to_writer(self.writer, &self.content)?) - } -} From 2a1b45374486a8bc65c218cd65d50737f4e42ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 19 Jun 2024 15:45:42 +0100 Subject: [PATCH 2/4] refactor(rust): simplify config command handling --- rust/agama-cli/src/config.rs | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/rust/agama-cli/src/config.rs b/rust/agama-cli/src/config.rs index 20b0651f2b..9c6319cb9b 100644 --- a/rust/agama-cli/src/config.rs +++ b/rust/agama-cli/src/config.rs @@ -1,6 +1,5 @@ use std::io::{self, Read}; -use crate::error::CliError; use agama_lib::{ auth::AuthToken, connection, install_settings::InstallSettings, Store as SettingsStore, }; @@ -20,11 +19,6 @@ pub enum ConfigCommands { Load, } -pub enum ConfigAction { - Show, - Load, -} - pub async fn run(subcommand: ConfigCommands) -> anyhow::Result<()> { let Some(token) = AuthToken::find() else { println!("You need to login for generating a valid token"); @@ -34,15 +28,14 @@ pub async fn run(subcommand: ConfigCommands) -> anyhow::Result<()> { let client = agama_lib::http_client(token.as_str())?; let store = SettingsStore::new(connection().await?, client).await?; - let command = parse_config_command(subcommand)?; - match command { - ConfigAction::Show => { + match subcommand { + ConfigCommands::Show => { let model = store.load().await?; let json = serde_json::to_string_pretty(&model)?; println!("{}", json); Ok(()) } - ConfigAction::Load => { + ConfigCommands::Load => { let mut stdin = io::stdin(); let mut contents = String::new(); stdin.read_to_string(&mut contents)?; @@ -51,10 +44,3 @@ pub async fn run(subcommand: ConfigCommands) -> anyhow::Result<()> { } } } - -fn parse_config_command(subcommand: ConfigCommands) -> Result { - match subcommand { - ConfigCommands::Show => Ok(ConfigAction::Show), - ConfigCommands::Load => Ok(ConfigAction::Load), - } -} From b4d17a042c2e85ead4b86e91bfc4dcc36fe02ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 19 Jun 2024 17:21:35 +0100 Subject: [PATCH 3/4] feat(cli): add an "agama config edit" command --- rust/agama-cli/src/config.rs | 77 ++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/rust/agama-cli/src/config.rs b/rust/agama-cli/src/config.rs index 9c6319cb9b..538d05b886 100644 --- a/rust/agama-cli/src/config.rs +++ b/rust/agama-cli/src/config.rs @@ -1,13 +1,23 @@ -use std::io::{self, Read}; +use std::{ + io::{self, Read}, + path::PathBuf, + process::Command, +}; +use crate::show_progress; use agama_lib::{ auth::AuthToken, connection, install_settings::InstallSettings, Store as SettingsStore, }; +use anyhow::anyhow; use clap::Subcommand; +use std::io::Write; +use tempfile::Builder; + +const DEFAULT_EDITOR: &str = "/usr/bin/vi"; #[derive(Subcommand, Debug)] pub enum ConfigCommands { - /// Generates an installation profile with the current settings. + /// Generate an installation profile with the current settings. /// /// It is possible that many configuration settings do not have a value. Those settings /// are not included in the output. @@ -15,8 +25,20 @@ pub enum ConfigCommands { /// The output of command can be used as input for the "agama config load". Show, - /// Reads and loads a profile from the standard input. + /// Read and load a profile from the standard input. Load, + + /// Edit and update installation option using an external editor. + /// + /// The changes are not applied if the editor exits with an error code. + /// + /// If an editor is not specified, it honors the EDITOR environment variable. It falls back to + /// `/usr/bin/vi` as a last resort. + Edit { + /// Editor command (including additional arguments if needed) + #[arg(short, long)] + editor: Option, + }, } pub async fn run(subcommand: ConfigCommands) -> anyhow::Result<()> { @@ -42,5 +64,54 @@ pub async fn run(subcommand: ConfigCommands) -> anyhow::Result<()> { let result: InstallSettings = serde_json::from_str(&contents)?; Ok(store.store(&result).await?) } + ConfigCommands::Edit { editor } => { + let model = store.load().await?; + let editor = editor + .or_else(|| std::env::var("EDITOR").ok()) + .unwrap_or(DEFAULT_EDITOR.to_string()); + let result = edit(&model, &editor)?; + tokio::spawn(async move { + show_progress().await.unwrap(); + }); + store.store(&result).await?; + Ok(()) + } } } + +/// Edit the installation settings using an external editor. +/// +/// If the editor does not return a successful error code, it returns an error. +/// +/// * `model`: current installation settings. +/// * `editor`: editor command. +fn edit(model: &InstallSettings, editor: &str) -> anyhow::Result { + let content = serde_json::to_string_pretty(model)?; + let mut file = Builder::new().suffix(".json").tempfile()?; + let path = PathBuf::from(file.path()); + write!(file, "{}", content)?; + + let mut command = editor_command(&editor); + let status = command.arg(path.as_os_str()).status()?; + if status.success() { + return Ok(InstallSettings::from_file(path)?); + } + + Err(anyhow!( + "Ignoring the changes becase the editor was closed with an error code." + )) +} + +/// Return the Command to run the editor. +/// +/// Separate the program and the arguments and build a Command struct. +/// +/// * `command`: command to run as editor. +fn editor_command(command: &str) -> Command { + let mut parts = command.split_whitespace(); + let program = parts.next().unwrap_or(DEFAULT_EDITOR); + + let mut command = Command::new(program); + command.args(parts.collect::>()); + command +} From a94bd503f259568ae558d7741928cd9a43bdb2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 20 Jun 2024 14:00:30 +0100 Subject: [PATCH 4/4] doc(cli): update changes file --- rust/package/agama.changes | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rust/package/agama.changes b/rust/package/agama.changes index 714a954bb5..6216895e1b 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Thu Jun 20 12:58:32 UTC 2024 - Imobach Gonzalez Sosa + +- Add a new "config edit" command allows editing installation + settings using an external editor (gh#openSUSE/agama#1360). +- Remove the "--format" option (gh#openSUSE/agama#1360). + ------------------------------------------------------------------- Thu Jun 20 05:32:42 UTC 2024 - Imobach Gonzalez Sosa