diff --git a/Cargo.toml b/Cargo.toml index 3f5b885..415b33a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,12 +5,12 @@ edition = "2021" authors = ["Terts Diepraam"] license = "MIT" -homepage = "https://github.com/tertsdiepraam/uutils-args" -repository = "https://github.com/tertsdiepraam/uutils-args" +homepage = "https://github.com/uutils/uutils-args" +repository = "https://github.com/uutils/uutils-args" readme = "README.md" [dependencies] -derive = { version = "0.1.0", path = "derive" } +uutils-args-derive = { version = "0.1.0", path = "derive" } strsim = "0.10.0" lexopt = "0.3.0" diff --git a/LICENSE b/LICENSE index db1aed9..21bd444 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) Terts Diepraam +Copyright (c) uutils developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 2f71c24..0cfcbb2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,139 @@ # uutils-args -An experimental derive-based argument parser specifically for uutils +Argument parsing for the [uutils coreutils](https://www.github.com/uutils/coreutils) project. + +It is designed to be flexible, while providing default +behaviour that aligns with GNU coreutils. + +## Features + + - A derive macro for declarative argument definition. + - Automatic help generation. + - Positional and optional arguments. + - Automatically parsing values into Rust types. + - Define a custom exit code on errors. + - Automatically accept unambiguous abbreviations of long options. + - Handles invalid UTF-8 gracefully. + +## When you should not use this library + +The goal of this library is to make it easy to build applications that +mimic the behaviour of the GNU coreutils. There are other applications +that have similar behaviour, which are C application that use `getopt` +and `getopt_long`. If you want to mimic that behaviour exactly, this +is the library for you. If you want to write basically anything else, +you should probably pick another argument parser. + +## Getting Started + +Parsing with this library consists of two "phases". In the first +phase, the arguments are mapped to an iterator of an `enum` +implementing [`Arguments`]. The second phase is mapping these +arguments onto a `struct` implementing [`Options`]. By defining +your arguments this way, there is a clear divide between the public +API and the internal representation of the settings of your app. + +For more information on these traits, see their respective documentation: + +- [`Arguments`] +- [`Options`] + +Below is a minimal example of a full CLI application using this library. + +```rust +use uutils_args::{Arguments, Initial, Options}; + +#[derive(Arguments)] +enum Arg { + // The doc strings below will be part of the `--help` text + // First we define a simple flag: + /// Do not transform input text to uppercase + #[option("-n", "--no-caps")] + NoCaps, + + // This option takes a value: + /// Add exclamation marks to output + #[option("-e N", "--exclaim=N")] + ExclamationMarks(u8), + + // This is a positional argument, the range specifies that + // at least one positional argument must be passed. + #[positional(1..)] + Text(String), +} + +#[derive(Initial)] +struct Settings { + // We can change the default value with the field attribute. + #[initial(true)] + caps: bool, + exclamation_marks: u8, + text: String, +} + +// To implement `Options`, we only need to provide the `apply` method. +// The `parse` method will be automatically generated. +impl Options for Settings { + fn apply(&mut self, arg: Arg) { + match arg { + Arg::NoCaps => self.caps = false, + Arg::ExclamationMarks(n) => self.exclamation_marks += n, + Arg::Text(s) => { + if self.text.is_empty() { + self.text.push_str(&s); + } else { + self.text.push(' '); + self.text.push_str(&s); + } + } + } + } +} + +fn run(args: &'static [&'static str]) -> String { + let s = Settings::parse(args); + let mut output = if s.caps { + s.text.to_uppercase() + } else { + s.text + }; + for i in 0..s.exclamation_marks { + output.push('!'); + } + output +} + +// The first argument is the binary name. In this example it's ignored. +assert_eq!(run(&["shout", "hello"]), "HELLO"); +assert_eq!(run(&["shout", "-e3", "hello"]), "HELLO!!!"); +assert_eq!(run(&["shout", "-e", "3", "hello"]), "HELLO!!!"); +assert_eq!(run(&["shout", "--no-caps", "hello"]), "hello"); +assert_eq!(run(&["shout", "-e3", "-n", "hello"]), "hello!!!"); +assert_eq!(run(&["shout", "-e3", "hello", "world"]), "HELLO WORLD!!!"); +``` + +## Additional functionality + +To make it easier to implement [`Arguments`] and [`Options`], there are +two additional traits: + +- [`Initial`] is an alternative to the [`Default`] trait from the standard + library, with a richer derive macro. +- [`Value`] allows for easy parsing from `OsStr` to any type + implementing [`Value`]. This crate also provides a derive macro for + this trait. + +## Examples + +The following files contain examples of commands defined with +`uutils_args`: + +- [hello world](https://github.com/uutils/uutils-args/blob/main/examples/hello_world.rs) +- [arch](https://github.com/uutils/uutils-args/blob/main/tests/coreutils/arch.rs) +- [b2sum](https://github.com/uutils/uutils-args/blob/main/tests/coreutils/b2sum.rs) +- [base32](https://github.com/uutils/uutils-args/blob/main/tests/coreutils/base32.rs) +- [basename](https://github.com/uutils/uutils-args/blob/main/tests/coreutils/basename.rs) +- [cat](https://github.com/uutils/uutils-args/blob/main/tests/coreutils/cat.rs) +- [echo](https://github.com/uutils/uutils-args/blob/main/tests/coreutils/echo.rs) +- [ls](https://github.com/uutils/uutils-args/blob/main/tests/coreutils/ls.rs) +- [mktemp](https://github.com/uutils/uutils-args/blob/main/tests/coreutils/mktemp.rs) diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 2b73b0d..fba8054 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "derive" +name = "uutils-args-derive" version = "0.1.0" edition = "2021" @@ -10,6 +10,5 @@ proc_macro = true [dependencies] proc-macro2 = "1.0.47" -pulldown-cmark = "0.9.2" quote = "1.0.21" syn = { version = "2.0.18 ", features = ["full"] } diff --git a/derive/LICENSE b/derive/LICENSE new file mode 120000 index 0000000..ea5b606 --- /dev/null +++ b/derive/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/derive/src/argument.rs b/derive/src/argument.rs index 3e14da7..3854be5 100644 --- a/derive/src/argument.rs +++ b/derive/src/argument.rs @@ -1,3 +1,6 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + use std::ops::RangeInclusive; use proc_macro2::TokenStream; diff --git a/derive/src/attributes.rs b/derive/src/attributes.rs index 2a19604..e8c915c 100644 --- a/derive/src/attributes.rs +++ b/derive/src/attributes.rs @@ -1,3 +1,6 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + use std::ops::RangeInclusive; use syn::{ diff --git a/derive/src/flags.rs b/derive/src/flags.rs index b999f0a..7cab9e5 100644 --- a/derive/src/flags.rs +++ b/derive/src/flags.rs @@ -1,3 +1,6 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + use proc_macro2::TokenStream; use quote::quote; diff --git a/derive/src/help.rs b/derive/src/help.rs index a853b77..8348ad0 100644 --- a/derive/src/help.rs +++ b/derive/src/help.rs @@ -1,3 +1,6 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + use std::{ io::Read, path::{Path, PathBuf}, diff --git a/derive/src/help_parser.rs b/derive/src/help_parser.rs index 8faa4e6..3d9e5ae 100644 --- a/derive/src/help_parser.rs +++ b/derive/src/help_parser.rs @@ -1,5 +1,3 @@ -// This file is part of the uutils coreutils package. -// // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. diff --git a/derive/src/initial.rs b/derive/src/initial.rs index 1cb0e83..ced4032 100644 --- a/derive/src/initial.rs +++ b/derive/src/initial.rs @@ -1,3 +1,6 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + use syn::{ parse::{Parse, ParseStream}, parse_macro_input, Data, DeriveInput, Fields, Token, diff --git a/derive/src/lib.rs b/derive/src/lib.rs index e78c358..8c3d4cc 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,3 +1,6 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + mod argument; mod attributes; mod flags; diff --git a/design/problems_with_clap.md b/design/problems_with_clap.md index 9293984..689165c 100644 --- a/design/problems_with_clap.md +++ b/design/problems_with_clap.md @@ -185,7 +185,7 @@ libraries. - Extremely flexible, even supports `dd`-style. - A different configuration between short and long options requires a workaround. - A many-to-many relation ship is possible, though not very ergonomic. - - For more information, see: https://github.com/tertsdiepraam/uutils-args/issues/17 + - For more information, see: https://github.com/uutils/uutils-args/issues/17 - [`gumdrop`](https://github.com/murarth/gumdrop) - Does not handle invalid UTF-8. - Not configurable enough. diff --git a/src/error.rs b/src/error.rs index 9c6c2a0..e2c9faa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,6 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + use std::{ error::Error as StdError, ffi::OsString, diff --git a/src/lib.rs b/src/lib.rs index 80a95df..72321c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,148 +1,13 @@ -//! Argument parsing for the uutils coreutils project -//! -//! This crate provides the argument parsing for the -//! [uutils coreutils](https://www.github.com/uutils/coreutils) -//! It is designed to be flexible, while providing default -//! behaviour that aligns with GNU coreutils. -//! -//! # Features -//! -//! - A derive macro for declarative argument definition. -//! - Automatic help generation. -//! - Positional and optional arguments. -//! - Automatically parsing values into Rust types. -//! - Define a custom exit code on errors. -//! - Automatically accept unambiguous abbreviations of long options. -//! - Handles invalid UTF-8 gracefully. -//! -//! # When you should not use this library -//! -//! The goal of this library is to make it easy to build applications that -//! mimic the behaviour of the GNU coreutils. There are other applications -//! that have similar behaviour, which are C application that use `getopt` -//! and `getopt_long`. If you want to mimic that behaviour exactly, this -//! is the library for you. If you want to write basically anything else, -//! you should probably pick another argument parser. -//! -//! # Getting Started -//! -//! Parsing with this library consists of two "phases". In the first -//! phase, the arguments are mapped to an iterator of an `enum` -//! implementing [`Arguments`]. The second phase is mapping these -//! arguments onto a `struct` implementing [`Options`]. By defining -//! your arguments this way, there is a clear divide between the public -//! API and the internal representation of the settings of your app. -//! -//! For more information on these traits, see their respective documentation: -//! -//! - [`Arguments`] -//! - [`Options`] -//! -//! Below is a minimal example of a full CLI application using this library. -//! -//! ``` -//! use uutils_args::{Arguments, Initial, Options}; -//! -//! #[derive(Arguments)] -//! enum Arg { -//! // The doc strings below will be part of the `--help` text -//! // First we define a simple flag: -//! /// Do not transform input text to uppercase -//! #[option("-n", "--no-caps")] -//! NoCaps, -//! -//! // This option takes a value: -//! /// Add exclamation marks to output -//! #[option("-e N", "--exclaim=N")] -//! ExclamationMarks(u8), -//! -//! // This is a positional argument, the range specifies that -//! // at least one positional argument must be passed. -//! #[positional(1..)] -//! Text(String), -//! } -//! -//! #[derive(Initial)] -//! struct Settings { -//! // We can change the default value with the field attribute. -//! #[initial(true)] -//! caps: bool, -//! exclamation_marks: u8, -//! text: String, -//! } -//! -//! // To implement `Options`, we only need to provide the `apply` method. -//! // The `parse` method will be automatically generated. -//! impl Options for Settings { -//! fn apply(&mut self, arg: Arg) { -//! match arg { -//! Arg::NoCaps => self.caps = false, -//! Arg::ExclamationMarks(n) => self.exclamation_marks += n, -//! Arg::Text(s) => { -//! if self.text.is_empty() { -//! self.text.push_str(&s); -//! } else { -//! self.text.push(' '); -//! self.text.push_str(&s); -//! } -//! } -//! } -//! } -//! } -//! -//! fn run(args: &'static [&'static str]) -> String { -//! let s = Settings::parse(args); -//! let mut output = if s.caps { -//! s.text.to_uppercase() -//! } else { -//! s.text -//! }; -//! for i in 0..s.exclamation_marks { -//! output.push('!'); -//! } -//! output -//! } -//! -//! // The first argument is the binary name. In this example it's ignored. -//! assert_eq!(run(&["shout", "hello"]), "HELLO"); -//! assert_eq!(run(&["shout", "-e3", "hello"]), "HELLO!!!"); -//! assert_eq!(run(&["shout", "-e", "3", "hello"]), "HELLO!!!"); -//! assert_eq!(run(&["shout", "--no-caps", "hello"]), "hello"); -//! assert_eq!(run(&["shout", "-e3", "-n", "hello"]), "hello!!!"); -//! assert_eq!(run(&["shout", "-e3", "hello", "world"]), "HELLO WORLD!!!"); -//! ``` -//! -//! # Additional functionality -//! -//! To make it easier to implement [`Arguments`] and [`Options`], there are -//! two additional traits: -//! -//! - [`Initial`] is an alternative to the [`Default`] trait from the standard -//! library, with a richer derive macro. -//! - [`Value`] allows for easy parsing from `OsStr` to any type -//! implementing [`Value`]. This crate also provides a derive macro for -//! this trait. -//! -//! # Examples -//! -//! The following files contain examples of commands defined with -//! `uutils_args`: -//! -//! - [hello world](https://github.com/tertsdiepraam/uutils-args/blob/main/examples/hello_world.rs) -//! - [arch](https://github.com/tertsdiepraam/uutils-args/blob/main/tests/coreutils/arch.rs) -//! - [b2sum](https://github.com/tertsdiepraam/uutils-args/blob/main/tests/coreutils/b2sum.rs) -//! - [base32](https://github.com/tertsdiepraam/uutils-args/blob/main/tests/coreutils/base32.rs) -//! - [basename](https://github.com/tertsdiepraam/uutils-args/blob/main/tests/coreutils/basename.rs) -//! - [cat](https://github.com/tertsdiepraam/uutils-args/blob/main/tests/coreutils/cat.rs) -//! - [echo](https://github.com/tertsdiepraam/uutils-args/blob/main/tests/coreutils/echo.rs) -//! - [ls](https://github.com/tertsdiepraam/uutils-args/blob/main/tests/coreutils/ls.rs) -//! - [mktemp](https://github.com/tertsdiepraam/uutils-args/blob/main/tests/coreutils/mktemp.rs) +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +#![doc = include_str!("../README.md")] mod error; mod value; -pub use derive::*; pub use lexopt; +pub use uutils_args_derive::*; pub use error::Error; pub use value::{Value, ValueError, ValueResult}; @@ -177,7 +42,7 @@ fn exit_if_err(res: Result, exit_code: i32) -> T { /// meaning that we can parse the individual arguments to `T`.\ /// /// Usually, this trait will be implemented via the -/// [derive macro](derive::Arguments) and does not need to be implemented +/// [derive macro](derive@Arguments) and does not need to be implemented /// manually. pub trait Arguments: Sized { /// The exit code to exit the program with on error. @@ -301,7 +166,7 @@ impl ArgumentIter { /// The `Initial` trait is used by `Options` to construct the initial /// state of the options before any arguments are parsed. /// -/// The [derive macro](derive::Initial) supports setting the initial +/// The [derive macro](derive@Initial) supports setting the initial /// value per field and parsing the initial values from environment /// variables. Otherwise, it will be equivalent to the derive macro /// for the [`Default`] trait. diff --git a/src/value.rs b/src/value.rs index e891d0b..e95b9a4 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,3 +1,6 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + use crate::error::Error; use std::{ ffi::{OsStr, OsString},