Skip to main content

qemu_command_builder/args/
device.rs

1use crate::parsers::ARG_DEVICE;
2use crate::parsers::DELIM_COMMA;
3use crate::shell_string::ShellString;
4use crate::to_command::ToCommand;
5use bon::Builder;
6use proptest_derive::Arbitrary;
7use std::collections::BTreeMap;
8use std::str::FromStr;
9
10/// A generic `-device` property rendered after the device driver name.
11#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
12pub struct DeviceProperty {
13    key: ShellString,
14    value: Option<ShellString>,
15}
16
17impl DeviceProperty {
18    /// Creates a `key=value` property.
19    pub fn with_value(key: impl AsRef<str>, value: impl AsRef<str>) -> Self {
20        Self {
21            key: ShellString::from(key.as_ref()),
22            value: Some(ShellString::from(value.as_ref())),
23        }
24    }
25
26    /// Creates a bare `key` property.
27    pub fn flag(key: impl AsRef<str>) -> Self {
28        Self {
29            key: ShellString::from(key.as_ref()),
30            value: None,
31        }
32    }
33}
34
35/// Add a QEMU device driver with optional `prop` or `prop=value` settings.
36///
37/// Valid properties depend on the device driver. Use `-device help` or
38/// `-device driver,help` in QEMU for the device-specific property list.
39#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
40pub struct Device {
41    device: ShellString,
42    #[builder(default)]
43    properties: BTreeMap<ShellString, Option<ShellString>>,
44}
45
46impl Device {
47    /// Creates a new device argument for the given QEMU device driver.
48    pub fn new<S: AsRef<str>>(device: S) -> Self {
49        Device {
50            device: ShellString::from(device.as_ref()),
51            properties: Default::default(),
52        }
53    }
54
55    /// Adds a `key=value` property to the device.
56    pub fn add_prop<K: AsRef<str>, V: AsRef<str>>(&mut self, key: K, value: V) -> &mut Self {
57        self.properties.insert(ShellString::from(key.as_ref()), Some(ShellString::from(value.as_ref())));
58        self
59    }
60
61    /// Adds a bare `key` property to the device.
62    pub fn add_flag<K: AsRef<str>>(&mut self, key: K) -> &mut Self {
63        self.properties.insert(ShellString::from(key.as_ref()), None);
64        self
65    }
66}
67
68impl ToCommand for Device {
69    fn command(&self) -> String {
70        ARG_DEVICE.to_string()
71    }
72
73    fn to_args(&self) -> Vec<String> {
74        let mut args = vec![self.device.as_ref().to_string()];
75
76        for (prop_key, prop_value) in &self.properties {
77            match prop_value {
78                Some(value) => args.push(format!("{}={}", prop_key.as_ref(), value.as_ref())),
79                None => args.push(prop_key.as_ref().to_string()),
80            }
81        }
82
83        vec![args.join(DELIM_COMMA)]
84    }
85}
86
87impl FromStr for Device {
88    type Err = String;
89
90    fn from_str(s: &str) -> Result<Self, Self::Err> {
91        let mut parts = s.split(DELIM_COMMA);
92        let device = parts.next().ok_or_else(|| "empty device argument".to_string())?;
93        if device.is_empty() {
94            return Err("missing device driver".to_string());
95        }
96
97        let mut properties = BTreeMap::new();
98        for part in parts {
99            match part.split_once('=') {
100                Some((key, value)) => {
101                    properties.insert(ShellString::from(key), Some(ShellString::from_str(value)?));
102                }
103                None => {
104                    properties.insert(ShellString::from(part), None);
105                }
106            }
107        }
108
109        Ok(Device {
110            device: ShellString::from(device),
111            properties,
112        })
113    }
114}