qemu_command_builder/args/
audiodev.rs1use crate::parsers::ARG_AUDIODEV;
2use std::str::FromStr;
3
4use bon::Builder;
5use proptest_derive::Arbitrary;
6
7use crate::parsers::DELIM_COMMA;
8use crate::to_command::ToCommand;
9
10#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
12pub struct AudioDevProperty {
13 pub key: String,
15 pub value: Option<String>,
17}
18
19#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
25pub struct AudioDev {
26 driver: String,
28 props: Vec<AudioDevProperty>,
30 json: Option<String>,
31}
32
33impl AudioDev {
34 pub fn new(driver: impl Into<String>) -> Self {
35 Self {
36 driver: driver.into(),
37 props: Vec::new(),
38 json: None,
39 }
40 }
41 pub fn from_json(json: impl Into<String>) -> Result<Self, String> {
43 let json = json.into();
44 if !json.trim().starts_with('{') || !json.trim().ends_with('}') {
45 return Err("-audiodev JSON must be a JSON object".to_string());
46 }
47 Ok(Self {
48 driver: String::new(),
49 props: Vec::new(),
50 json: Some(json),
51 })
52 }
53
54 pub fn add_prop(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
55 self.props.push(AudioDevProperty {
56 key: key.into(),
57 value: Some(value.into()),
58 });
59 self
60 }
61
62 pub fn add_flag(&mut self, key: impl Into<String>) -> &mut Self {
63 self.props.push(AudioDevProperty { key: key.into(), value: None });
64 self
65 }
66}
67
68impl ToCommand for AudioDev {
69 fn command(&self) -> String {
70 ARG_AUDIODEV.to_string()
71 }
72 fn to_args(&self) -> Vec<String> {
73 if let Some(json) = &self.json {
74 return vec![json.clone()];
75 }
76 let mut args = vec![self.driver.clone()];
77
78 for prop in &self.props {
79 if let Some(value) = &prop.value {
80 args.push(format!("{}={}", prop.key, value));
81 } else {
82 args.push(prop.key.clone());
83 }
84 }
85 vec![args.join(DELIM_COMMA)]
86 }
87}
88
89impl FromStr for AudioDev {
90 type Err = String;
91
92 fn from_str(s: &str) -> Result<Self, Self::Err> {
93 if s.trim().starts_with('{') {
94 return Self::from_json(s);
95 }
96 let mut parts = s.split(DELIM_COMMA);
97 let first = parts.next().ok_or_else(|| "empty -audiodev argument".to_string())?;
98
99 let driver = if let Some(value) = first.strip_prefix("driver=") {
100 value.to_string()
101 } else if !first.contains('=') {
102 first.to_string()
103 } else {
104 return Err(format!("unsupported first -audiodev component: {first}"));
105 };
106
107 let mut props = Vec::new();
108 for part in parts {
109 if let Some((key, value)) = part.split_once('=') {
110 props.push(AudioDevProperty {
111 key: key.to_string(),
112 value: Some(value.to_string()),
113 });
114 } else {
115 props.push(AudioDevProperty { key: part.to_string(), value: None });
116 }
117 }
118
119 let has_id = props.iter().any(|prop| prop.key == "id");
120 if !has_id {
121 return Err("-audiodev requires id=".to_string());
122 }
123
124 Ok(Self { driver, props, json: None })
125 }
126}