-
-
Notifications
You must be signed in to change notification settings - Fork 166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
IPC: Accept multiple actions from stdin #1017
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
pub(crate) mod one_or_many { | ||
use serde::{Deserialize, Deserializer, Serialize, Serializer}; | ||
|
||
pub(crate) fn serialize<T: Serialize, S: Serializer>( | ||
value: &Vec<T>, | ||
serializer: S, | ||
) -> Result<S::Ok, S::Error> { | ||
if value.len() == 1 { | ||
value[0].serialize(serializer) | ||
} else { | ||
value.serialize(serializer) | ||
} | ||
} | ||
|
||
pub(crate) fn deserialize<'de, T: Deserialize<'de>, D: Deserializer<'de>>( | ||
deserializer: D, | ||
) -> Result<Vec<T>, D::Error> { | ||
#[derive(Deserialize)] | ||
#[serde(untagged)] | ||
enum OneOrMany<T> { | ||
Many(Vec<T>), | ||
One(T), | ||
} | ||
|
||
match OneOrMany::deserialize(deserializer)? { | ||
OneOrMany::Many(v) => Ok(v), | ||
OneOrMany::One(v) => Ok(vec![v]), | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use std::fmt::Debug; | ||
|
||
use serde_json::de::SliceRead; | ||
use serde_json::{Deserializer, Serializer, Value}; | ||
|
||
use super::*; | ||
|
||
fn test_serialize<T: Serialize>(value: &Vec<T>, expected: &str) { | ||
let mut bytes = Vec::new(); | ||
let mut serializer = Serializer::new(&mut bytes); | ||
serialize(value, &mut serializer).expect("failed to serialize"); | ||
assert_eq!(String::from_utf8_lossy(&bytes), expected); | ||
} | ||
|
||
fn test_deserialize<'de, T>(value: &'de str, expected: &Vec<T>) | ||
where | ||
T: Deserialize<'de> + Debug + PartialEq, | ||
{ | ||
let mut deserailier = Deserializer::new(SliceRead::new(value.as_bytes())); | ||
let result: Vec<T> = deserialize(&mut deserailier).expect("failed to deserialize"); | ||
assert_eq!(&result, expected); | ||
} | ||
|
||
#[test] | ||
fn serialize_one() { | ||
test_serialize(&vec![Value::Null], "null"); | ||
} | ||
|
||
#[test] | ||
fn deserialize_one() { | ||
test_deserialize("null", &vec![Value::Null]); | ||
} | ||
|
||
#[test] | ||
fn serialize_many() { | ||
test_serialize(&vec![Value::Null, Value::Null], "[null,null]"); | ||
} | ||
|
||
#[test] | ||
fn deserialize_many() { | ||
test_deserialize("[null,null]", &vec![Value::Null, Value::Null]); | ||
} | ||
|
||
#[test] | ||
fn serialize_none() { | ||
test_serialize(&Vec::<Value>::new(), "[]"); | ||
} | ||
|
||
#[test] | ||
fn deserialize_none() { | ||
test_deserialize("[]", &Vec::<Value>::new()); | ||
} | ||
|
||
#[test] | ||
fn serialize_derive() { | ||
#[derive(Debug, Serialize, PartialEq)] | ||
enum Request { | ||
Action(#[serde(with = "self")] Vec<String>), | ||
} | ||
let request = serde_json::to_string(&Request::Action(vec!["foo".to_string()])) | ||
.expect("failed to serialize"); | ||
assert_eq!(request, r#"{"Action":"foo"}"#); | ||
let request = | ||
serde_json::to_string(&Request::Action(vec!["foo".to_string(), "bar".to_string()])) | ||
.expect("failed to serialize"); | ||
assert_eq!(request, r#"{"Action":["foo","bar"]}"#); | ||
} | ||
|
||
#[test] | ||
fn deserialize_derive() { | ||
#[derive(Debug, Deserialize, PartialEq)] | ||
enum Request { | ||
Action(#[serde(with = "self")] Vec<String>), | ||
} | ||
let request: Request = | ||
serde_json::from_str(r#"{"Action":"foo"}"#).expect("failed to deserialize"); | ||
assert_eq!(request, Request::Action(vec!["foo".to_string()])); | ||
let request: Request = | ||
serde_json::from_str(r#"{"Action":["foo","bar"]}"#).expect("failed to deserialize"); | ||
assert_eq!( | ||
request, | ||
Request::Action(vec!["foo".to_string(), "bar".to_string()]) | ||
); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -74,9 +74,13 @@ pub enum Msg { | |
FocusedWindow, | ||
/// Perform an action. | ||
Action { | ||
// NOTE: This is only optional because of clap_derive limitations and will never be `None`. | ||
// If an action is not provided it reads actions from stdin and turns to [`Msg::Actions`]. | ||
Comment on lines
+77
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly, could this remain as non-optional, with a separate There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To me this feels a bit weird to me, because it is the same subcommand in terms of functionality, just with different input, unlike how There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yeah, I want that to keep printing help. Maybe then something more descriptive like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Another option is checking if it's a terminal and printing the help message only in that case, then the help message can show a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nah, I think an explicit separate subcommand is better. Also I guess if we pick There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or maybe ActionBatch? |
||
#[command(subcommand)] | ||
action: Action, | ||
action: Option<Action>, | ||
}, | ||
#[clap(skip)] | ||
Actions { actions: Vec<Action> }, | ||
/// Change output configuration temporarily. | ||
/// | ||
/// The configuration is changed temporarily and not saved into the config file. If the output | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't we just add a separate
Actions
variant and avoid the whole custom parsing?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that makes sense, it's simpler and also more ergonomic for
niri-ipc
. The "compactness" of the json representation doesn't really matter.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish rust allowed somehow matching over both a
T
andVec<T>
as&[T]
, the code in the match arm ends up duplicated and it's awkward to break into a helper since it's async and environment capturing, it's just short enough that it's probably fine..?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you could try something like
async fn do_actions(ctx, actions: impl Iterator<Item=Action>)
?