Skip to main content

qemu_command_builder/args/
plugin.rs

1use crate::parsers::ARG_PLUGIN;
2use crate::to_command::ToCommand;
3use bon::Builder;
4use proptest_derive::Arbitrary;
5use std::path::PathBuf;
6use std::str::FromStr;
7
8/// A QEMU `-plugin [file=]file[,arg=value,...]` definition.
9#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
10pub struct Plugin {
11    /// The shared library implementing the plugin.
12    file: Option<PathBuf>,
13    /// Plugin-specific key/value arguments.
14    args: Option<Vec<(String, String)>>,
15}
16
17impl ToCommand for Plugin {
18    fn command(&self) -> String {
19        ARG_PLUGIN.to_string()
20    }
21
22    fn to_args(&self) -> Vec<String> {
23        let mut args = vec![];
24
25        if let Some(file) = &self.file {
26            args.push(format!("file={}", file.display()));
27        }
28        if let Some(argss) = &self.args {
29            for arg in argss {
30                args.push(format!("{}={}", arg.0, arg.1));
31            }
32        }
33        vec![args.join(",")]
34    }
35}
36
37impl FromStr for Plugin {
38    type Err = String;
39
40    fn from_str(s: &str) -> Result<Self, Self::Err> {
41        let mut parts = s.split(',');
42        let first = parts.next().ok_or_else(|| "empty -plugin argument".to_string())?;
43
44        let file = if let Some(value) = first.strip_prefix("file=") {
45            Some(PathBuf::from(value))
46        } else if first.contains('=') {
47            return Err(format!("unsupported first -plugin component: {first}"));
48        } else {
49            Some(PathBuf::from(first))
50        };
51
52        let mut args = Vec::new();
53        for part in parts {
54            let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid -plugin option: {part}"))?;
55            args.push((key.to_string(), value.to_string()));
56        }
57
58        Ok(Self {
59            file,
60            args: (!args.is_empty()).then_some(args),
61        })
62    }
63}