Skip to content

Commit

Permalink
Merge pull request #505 from FineFindus/feat/zx-args-parser
Browse files Browse the repository at this point in the history
zx: use clap for arg parsing
  • Loading branch information
zeenix authored Jan 22, 2024
2 parents 2357284 + 0fe4b11 commit c03e249
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 117 deletions.
1 change: 1 addition & 0 deletions zbus_xmlgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ zbus = { path = "../zbus", version = "4.0.0" }
zbus_xml = { path = "../zbus_xml", version = "4.0.0" }
zvariant = { path = "../zvariant", version = "4" }
snakecase = "0.1.0"
clap = { version = "4.4", features = ["derive", "wrap_help"] }

[dev-dependencies]
pretty_assertions = "1.3"
45 changes: 45 additions & 0 deletions zbus_xmlgen/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use std::path::PathBuf;

use clap::Parser;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
#[clap(subcommand)]
pub command: Command,

/// Specify the destination for saving the output. If no argument is provided, the parsed
/// interfaces will be stored in separate files. If a filename is provided, the output will
/// be saved to that file. Use '-' to print the output to stdout.
#[clap(short, long, allow_hyphen_values = true, global = true)]
pub output: Option<String>,
}

#[derive(Parser, Debug, Clone)]
pub enum Command {
/// Generate code for interfaces in the specified file.
#[clap()]
File { path: PathBuf },

/// Generate code for interfaces from the specified system service.
#[clap()]
System {
service: String,
object_path: String,
},

/// Generate code for interfaces from the current users session.
#[clap()]
Session {
service: String,
object_path: String,
},

/// Generate code for interfaces from the specified address.
#[clap()]
Address {
address: String,
service: String,
object_path: String,
},
}
199 changes: 82 additions & 117 deletions zbus_xmlgen/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
#![deny(rust_2018_idioms)]

use std::{
env::args,
error::Error,
fs::{File, OpenOptions},
io::{Read, Write},
path::Path,
process::{ChildStdin, Command, Stdio},
result::Result,
};

use clap::Parser;
use zbus::{
blocking::{connection, fdo::IntrospectableProxy, Connection},
names::BusName,
Expand All @@ -19,15 +18,7 @@ use zbus_xml::{Interface, Node};
use zbus_xmlgen::GenTrait;
use zvariant::ObjectPath;

fn usage() {
eprintln!(
r#"Usage:
zbus-xmlgen <interface.xml> [-o|--output <file_path>]
zbus-xmlgen --system|--session <service> <object_path> [-o|--output <file_path>]
zbus-xmlgen --address <address> <service> <object_path> [-o|--output <file_path>]
"#
);
}
mod cli;

enum OutputTarget {
SingleFile(File),
Expand All @@ -36,86 +27,30 @@ enum OutputTarget {
}

fn main() -> Result<(), Box<dyn Error>> {
let input_src;

let proxy = |conn: Connection, service, path| -> IntrospectableProxy<'_> {
IntrospectableProxy::builder(&conn)
.destination(service)
.expect("invalid destination")
.path(path)
.expect("invalid path")
.build()
.unwrap()
};

let (node, service, path) = match args().nth(1) {
Some(bus) if bus == "--system" || bus == "--session" => {
let connection = if bus == "--system" {
Connection::system()?
} else {
Connection::session()?
};
let service: BusName<'_> = args()
.nth(2)
.expect("Missing param for service")
.try_into()?;
let path: ObjectPath<'_> = args()
.nth(3)
.expect("Missing param for object path")
.try_into()?;

input_src = format!(
"Interface '{}' from service '{}' on {} bus",
path,
service,
bus.trim_start_matches("--")
);

let xml = proxy(connection, service.clone(), path.clone()).introspect()?;
(
Node::from_reader(xml.as_bytes())?,
Some(service),
Some(path),
)
}
Some(address) if address == "--address" => {
let address = args().nth(2).expect("Missing param for address path");
let service: BusName<'_> = args()
.nth(3)
.expect("Missing param for service")
.try_into()?;
let path: ObjectPath<'_> = args()
.nth(4)
.expect("Missing param for object path")
.try_into()?;

let connection = connection::Builder::address(&*address)?.build()?;

input_src = format!("Interface '{path}' from service '{service}'");

let xml = proxy(connection, service.clone(), path.clone()).introspect()?;
(
Node::from_reader(xml.as_bytes())?,
Some(service),
Some(path),
)
}
Some(help) if help == "--help" || help == "-h" => {
usage();
return Ok(());
}
Some(path) => {
input_src = Path::new(&path)
.file_name()
.unwrap()
.to_string_lossy()
.to_string();
let args = cli::Args::parse();

let DBusInfo(node, service, path, input_src) = match args.command {
cli::Command::System {
service,
object_path,
} => DBusInfo::new(Connection::system()?, service, object_path)?,
cli::Command::Session {
service,
object_path,
} => DBusInfo::new(Connection::session()?, service, object_path)?,
cli::Command::Address {
address,
service,
object_path,
} => DBusInfo::new(
connection::Builder::address(&*address)?.build()?,
service,
object_path,
)?,
cli::Command::File { path } => {
let input_src = path.file_name().unwrap().to_string_lossy().to_string();
let f = File::open(path)?;
(Node::from_reader(f)?, None, None)
}
None => {
usage();
return Ok(());
DBusInfo(Node::from_reader(f)?, None, None, input_src)
}
};

Expand All @@ -125,18 +60,10 @@ fn main() -> Result<(), Box<dyn Error>> {
.iter()
.partition(|&i| i.name().starts_with(fdo_iface_prefix));

let output_arg = args()
.position(|arg| arg == "-o" || arg == "--output")
.map(|pos| pos + 1)
.and_then(|pos| args().nth(pos));
let mut output_target = match output_arg.as_deref() {
let mut output_target = match args.output.as_deref() {
Some("-") => OutputTarget::Stdout,
Some(path) => {
let file = OpenOptions::new()
.create(true)
.append(true)
.open(path)
.expect("Failed to open file");
let file = OpenOptions::new().create(true).write(true).open(path)?;
OutputTarget::SingleFile(file)
}
_ => OutputTarget::MultipleFiles,
Expand All @@ -151,29 +78,64 @@ fn main() -> Result<(), Box<dyn Error>> {
&input_src,
)?;
match output_target {
OutputTarget::SingleFile(ref mut file) => {
file.write_all(output.as_bytes())?;
}
OutputTarget::Stdout => println!("{:?}", output),
OutputTarget::MultipleFiles => {
std::fs::write(
format!(
"{}.rs",
interface
.name()
.split('.')
.last()
.expect("Failed to split name")
),
output,
)?;
}
OutputTarget::Stdout => println!("{}", output),
OutputTarget::SingleFile(ref mut file) => file.write_all(output.as_bytes())?,
OutputTarget::MultipleFiles => std::fs::write(
format!(
"{}.rs",
interface
.name()
.split('.')
.last()
.expect("Failed to split name")
),
output,
)?,
};
}

Ok(())
}

struct DBusInfo<'a>(
Node<'a>,
Option<BusName<'a>>,
Option<ObjectPath<'a>>,
String,
);

impl<'a> DBusInfo<'a> {
fn new(
connection: Connection,
service: String,
object_path: String,
) -> Result<Self, Box<dyn Error>> {
let service: BusName<'_> = service.try_into()?;
let path: ObjectPath<'_> = object_path.try_into()?;

let input_src = format!(
"Interface '{}' from service '{}' on system bus",
path, service,
);

let xml = IntrospectableProxy::builder(&connection)
.destination(service.clone())
.expect("invalid destination")
.path(path.clone())
.expect("invalid path")
.build()
.unwrap()
.introspect()?;

Ok(DBusInfo(
Node::from_reader(xml.as_bytes())?,
Some(service),
Some(path),
input_src,
))
}
}

fn write_interfaces(
interfaces: &[&Interface<'_>],
standard_interfaces: &[&Interface<'_>],
Expand All @@ -184,6 +146,9 @@ fn write_interfaces(
let mut process = match Command::new("rustfmt")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
// rustfmt may post warnings about features not being enabled on stable rust
// these can be distracting and are irrevelant to the user, so we hide them
.stderr(Stdio::null())
.spawn()
{
Err(why) => panic!("couldn't spawn rustfmt: {}", why),
Expand Down

0 comments on commit c03e249

Please sign in to comment.