Skip to content

Commit

Permalink
Fix use in library crates (#4)
Browse files Browse the repository at this point in the history
- The `CARGO_BIN_NAME` env var is not available when building library
  crates, so we have to infer the binary name from the environment at
  runtime.
- Consequently, this allows the macro to be used in tests, so we now
  have a doc-test!
- Also uses explicit type paths to avoid certain kinds of name
  collisions with e.g. top-level `use anyhow::Result`.
  • Loading branch information
parasyte authored Apr 12, 2023
1 parent cb60866 commit 596a1b6
Showing 1 changed file with 59 additions and 4 deletions.
63 changes: 59 additions & 4 deletions onlyargs_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,36 @@
//! The parser generated by this macro is very opinionated. The implementation attempts to be as
//! light as possible while also being usable for most applications.
//!
//! # Example
//!
//! ```
//! use onlyargs_derive::OnlyArgs;
//!
//! /// Doc comments will appear in your application's help text.
//! /// Multi-line comments are also supported.
//! #[derive(Debug, OnlyArgs)]
//! struct Args {
//! /// Optional output path.
//! output: Option<std::path::PathBuf>,
//!
//! /// Enable verbose output.
//! verbose: bool,
//! }
//!
//! let args: Args = onlyargs::parse()?;
//!
//! if let Some(output) = args.output {
//! if args.verbose {
//! eprintln!("Creating file: `{path}`", path = output.display());
//! }
//!
//! // Do something with `output`...
//! }
//! # Ok::<_, onlyargs::CliError>(())
//! ```
//!
//! # DSL reference
//!
//! Only structs with named fields are supported. Doc comments are used for the generated help text.
//! Argument names are generated automatically from field names with only a few rules:
//!
Expand Down Expand Up @@ -276,13 +306,32 @@ pub fn derive_parser(input: TokenStream) -> TokenStream {
.unwrap_or_default();

let name = ast.name;
let doc_comment = format!("\n{}\n", ast.doc.join("\n"));
let doc_comment = if ast.doc.is_empty() {
String::new()
} else {
format!("\n{}\n", ast.doc.join("\n"))
};
let bin_name = std::env::var_os("CARGO_BIN_NAME").and_then(|name| name.into_string().ok());
let help_impl = if bin_name.is_none() {
r#"fn help() -> ! {
let bin_name = ::std::env::args_os()
.next()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
::std::eprintln!("{}", Self::HELP.replace("{bin_name}", &bin_name));
::std::process::exit(0);
}"#
} else {
""
};
let bin_name = bin_name.unwrap_or_else(|| "{bin_name}".to_string());

// Produce final code.
let code = TokenStream::from_str(&format!(
r#"
impl ::onlyargs::OnlyArgs for {name} {{
const HELP: &'static str = concat!(
const HELP: &'static str = ::std::concat!(
env!("CARGO_PKG_NAME"),
" v",
env!("CARGO_PKG_VERSION"),
Expand All @@ -291,7 +340,7 @@ pub fn derive_parser(input: TokenStream) -> TokenStream {
"\n",
{doc_comment:?},
"\nUsage:\n ",
env!("CARGO_BIN_NAME"),
{bin_name:?},
" [flags] [options]",
{positional_header:?},
"\n\nFlags:\n",
Expand All @@ -302,8 +351,14 @@ pub fn derive_parser(input: TokenStream) -> TokenStream {
"\n",
);
fn parse(args: Vec<std::ffi::OsString>) -> Result<Self, ::onlyargs::CliError> {{
{help_impl}
fn parse(args: Vec<::std::ffi::OsString>) ->
::std::result::Result<Self, ::onlyargs::CliError>
{{
use ::onlyargs::traits::*;
use ::std::option::Option::{{None, Some}};
use ::std::result::Result::{{Err, Ok}};
{flags_vars}
{options_vars}
Expand Down

0 comments on commit 596a1b6

Please sign in to comment.