diff --git a/CHANGELOG.md b/CHANGELOG.md index 39d04f7..b50f039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.17.1] - 2022-08-16 +### Added + +- New `format` option available for `client select` + - Choices are provided via `{"type": "select", "choices": ["...", ...], "current": 0}` + - Selection is specified via `{"type": "selected", "choice": 0}` + +### Fixed + +- `distant client launch` using `--format json` now properly prints out id in + JSON format (`{"type": "launched", "id": "..."}`) +- `distant client connect` using `--format json` now properly prints out id in + JSON format (`{"type": "connected", "id": "..."}`) + ## [0.17.0] - 2022-08-09 ### Added diff --git a/Cargo.toml b/Cargo.toml index f8a74fc..8859dba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "distant" description = "Operate on a remote computer through file and process manipulation" categories = ["command-line-utilities"] keywords = ["cli"] -version = "0.17.0" +version = "0.17.1" authors = ["Chip Senkbeil "] edition = "2021" homepage = "https://github.com/chipsenkbeil/distant" @@ -32,7 +32,7 @@ clap_complete = "3.2.3" config = { version = "0.13.2", default-features = false, features = ["toml"] } derive_more = { version = "0.99.17", default-features = false, features = ["display", "from", "error", "is_variant"] } dialoguer = { version = "0.10.2", default-features = false } -distant-core = { version = "=0.17.0", path = "distant-core", features = ["clap", "schemars"] } +distant-core = { version = "=0.17.1", path = "distant-core", features = ["clap", "schemars"] } directories = "4.0.1" flexi_logger = "0.23.0" indoc = "1.0.7" @@ -55,7 +55,7 @@ winsplit = "0.1.0" whoami = "1.2.1" # Optional native SSH functionality -distant-ssh2 = { version = "=0.17.0", path = "distant-ssh2", default-features = false, features = ["serde"], optional = true } +distant-ssh2 = { version = "=0.17.1", path = "distant-ssh2", default-features = false, features = ["serde"], optional = true } [target.'cfg(unix)'.dependencies] fork = "0.1.19" diff --git a/distant-core/Cargo.toml b/distant-core/Cargo.toml index a5111fe..9ee7b76 100644 --- a/distant-core/Cargo.toml +++ b/distant-core/Cargo.toml @@ -3,7 +3,7 @@ name = "distant-core" description = "Core library for distant, enabling operation on a remote computer through file and process manipulation" categories = ["network-programming"] keywords = ["api", "async"] -version = "0.17.0" +version = "0.17.1" authors = ["Chip Senkbeil "] edition = "2021" homepage = "https://github.com/chipsenkbeil/distant" @@ -19,11 +19,11 @@ async-trait = "0.1.57" bitflags = "1.3.2" bytes = "1.2.1" derive_more = { version = "0.99.17", default-features = false, features = ["as_mut", "as_ref", "deref", "deref_mut", "display", "from", "error", "into", "into_iterator", "is_variant", "try_into"] } -distant-net = { version = "=0.17.0", path = "../distant-net" } +distant-net = { version = "=0.17.1", path = "../distant-net" } futures = "0.3.21" hex = "0.4.3" log = "0.4.17" -notify = { version = "5.0.0-pre.15", features = ["serde"] } +notify = { version = "=5.0.0-pre.15", features = ["serde"] } once_cell = "1.13.0" portable-pty = "0.7.0" rand = { version = "0.8.5", features = ["getrandom"] } diff --git a/distant-net/Cargo.toml b/distant-net/Cargo.toml index d0f8df5..1feb04d 100644 --- a/distant-net/Cargo.toml +++ b/distant-net/Cargo.toml @@ -3,7 +3,7 @@ name = "distant-net" description = "Network library for distant, providing implementations to support client/server architecture" categories = ["network-programming"] keywords = ["api", "async"] -version = "0.17.0" +version = "0.17.1" authors = ["Chip Senkbeil "] edition = "2021" homepage = "https://github.com/chipsenkbeil/distant" diff --git a/distant-ssh2/Cargo.toml b/distant-ssh2/Cargo.toml index 7992207..77851a4 100644 --- a/distant-ssh2/Cargo.toml +++ b/distant-ssh2/Cargo.toml @@ -2,7 +2,7 @@ name = "distant-ssh2" description = "Library to enable native ssh-2 protocol for use with distant sessions" categories = ["network-programming"] -version = "0.17.0" +version = "0.17.1" authors = ["Chip Senkbeil "] edition = "2021" homepage = "https://github.com/chipsenkbeil/distant" @@ -20,7 +20,7 @@ async-compat = "0.2.1" async-once-cell = "0.4.2" async-trait = "0.1.57" derive_more = { version = "0.99.17", default-features = false, features = ["display", "error"] } -distant-core = { version = "=0.17.0", path = "../distant-core" } +distant-core = { version = "=0.17.1", path = "../distant-core" } futures = "0.3.21" hex = "0.4.3" log = "0.4.17" diff --git a/src/cli/commands/client.rs b/src/cli/commands/client.rs index 5fd6b04..60f8ddb 100644 --- a/src/cli/commands/client.rs +++ b/src/cli/commands/client.rs @@ -17,6 +17,7 @@ use distant_core::{ DistantResponseData, Extra, RemoteCommand, Watcher, }; use log::*; +use serde_json::{json, Value}; use std::{ io, path::{Path, PathBuf}, @@ -178,6 +179,9 @@ pub enum ClientSubcommand { /// Connection to use, otherwise will prompt to select connection: Option, + #[clap(short, long, default_value_t, value_enum)] + format: Format, + #[clap(flatten)] network: NetworkConfig, }, @@ -401,7 +405,17 @@ impl ClientSubcommand { *cache.data.selected = id; cache.write_to_disk().await?; - println!("{}", id); + match format { + Format::Shell => println!("{}", id), + Format::Json => println!( + "{}", + serde_json::to_string(&json!({ + "type": "connected", + "id": id, + })) + .unwrap() + ), + } } Self::Launch { config: launcher_config, @@ -465,7 +479,17 @@ impl ClientSubcommand { *cache.data.selected = id; cache.write_to_disk().await?; - println!("{}", id); + match format { + Format::Shell => println!("{}", id), + Format::Json => println!( + "{}", + serde_json::to_string(&json!({ + "type": "launched", + "id": id, + })) + .unwrap() + ), + } } Self::Lsp { connection, @@ -583,6 +607,7 @@ impl ClientSubcommand { } Self::Select { connection, + format, network, .. } => match connection { @@ -608,8 +633,8 @@ impl ClientSubcommand { ))); } - trace!("Building selection prompt of {} choices", list.len()); - let selected = list + // Figure out the current selection + let current = list .iter() .enumerate() .find_map(|(i, (id, _))| { @@ -621,6 +646,7 @@ impl ClientSubcommand { }) .unwrap_or_default(); + trace!("Building selection prompt of {} choices", list.len()); let items: Vec = list .iter() .map(|(_, destination)| { @@ -639,12 +665,51 @@ impl ClientSubcommand { }) .collect(); - trace!("Rendering prompt"); - let selected = Select::with_theme(&ColorfulTheme::default()) - .items(&items) - .default(selected) - .interact_on_opt(&Term::stderr()) - .context("Failed to render prompt")?; + // Prompt for a selection, with None meaning no change + let selected = match format { + Format::Shell => { + trace!("Rendering prompt"); + Select::with_theme(&ColorfulTheme::default()) + .items(&items) + .default(current) + .interact_on_opt(&Term::stderr()) + .context("Failed to render prompt")? + } + + Format::Json => { + // Print out choices + MsgSender::from_stdout() + .send_blocking(&json!({ + "type": "select", + "choices": items, + "current": current, + })) + .context("Failed to send JSON choices")?; + + // Wait for a response + let msg = MsgReceiver::from_stdin() + .recv_blocking::() + .context("Failed to receive JSON selection")?; + + // Verify the response type is "selected" + match msg.get("type") { + Some(value) if value == "selected" => msg + .get("choice") + .and_then(|value| value.as_u64()) + .map(|choice| choice as usize), + Some(value) => { + return Err(CliError::Error(anyhow::anyhow!( + "Unexpected 'type' field value: {value}" + ))) + } + None => { + return Err(CliError::Error(anyhow::anyhow!( + "Missing 'type' field" + ))) + } + } + } + }; match selected { Some(index) => {