Compare commits
1 Commits
main
...
arg-parsin
Author | SHA1 | Date | |
---|---|---|---|
3703a776a5 |
40
Cargo.lock
generated
40
Cargo.lock
generated
@ -20,7 +20,7 @@ dependencies = [
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
"which",
|
||||
]
|
||||
|
||||
@ -86,12 +86,6 @@ version = "0.2.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
|
||||
|
||||
[[package]]
|
||||
name = "libc_alloc"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a090348b66d90d8507e30f0d2bd88e5a5c454bd1733fc6d617cbc3471bf69ea"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
@ -192,11 +186,13 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
|
||||
[[package]]
|
||||
name = "spd"
|
||||
version = "0.0.3"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"libc",
|
||||
"libc_alloc",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"tabwriter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -210,12 +206,38 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tabwriter"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36205cfc997faadcc4b0b87aaef3fbedafe20d38d4959a7ca6ff803564051111"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.0"
|
||||
|
@ -1,19 +1,20 @@
|
||||
[package]
|
||||
name = "spd"
|
||||
version = "0.0.3"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
license = "LGPL-3.0-or-later"
|
||||
authors = [
|
||||
"Emma Tebibyte <emma@tebibyte.media>",
|
||||
"Douman <douman@gmx.se>",
|
||||
"Matt Mastracci <matthew@mastracci.com>",
|
||||
]
|
||||
description ="Minimal standard library replacement containing essential functionality."
|
||||
repository = "https://git.tebibyte.media/emma/spd"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.140"
|
||||
libc_alloc = "1.0.4"
|
||||
quote = "1.0.26"
|
||||
syn = { version = "2.0.8", features = [ "extra-traits" ] }
|
||||
tabwriter = "1.2.1"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.64.0"
|
||||
|
@ -68,7 +68,7 @@ impl Args {
|
||||
///On error returns pair: `(string index, Utf8Error)`
|
||||
///
|
||||
///The function is safe as long as you pass C style main function arguments.
|
||||
pub unsafe fn new(argc: isize, argv: *const *const u8) -> Result<Self, (usize, crate::str::Utf8Error)> {
|
||||
pub unsafe fn new(argc: isize, argv: *const *const u8) -> Result<Self, (usize, core::str::Utf8Error)> {
|
||||
assert!(argc > 0);
|
||||
assert!(!argv.is_null());
|
||||
|
||||
@ -102,7 +102,7 @@ impl Args {
|
||||
///Returns slice of raw C strings
|
||||
pub fn as_slice(&self) -> &[*const u8] {
|
||||
unsafe {
|
||||
crate::slice::from_raw_parts(self.argv, self.argc)
|
||||
core::slice::from_raw_parts(self.argv, self.argc)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
|
||||
mod args;
|
||||
pub use args::*;
|
||||
use args::*;
|
||||
|
||||
#[allow(unused)]
|
||||
#[cold]
|
||||
@ -79,10 +79,10 @@ unsafe fn invalid_cli_args_error() -> libc::c_int {
|
||||
///Converts C string to Rust's, verifying it is UTF-8
|
||||
///
|
||||
///It is UB to pass non-C string as it requires \0
|
||||
pub unsafe fn c_str_to_rust(ptr: *const u8) -> Result<&'static str, crate::str::Utf8Error> {
|
||||
pub unsafe fn c_str_to_rust(ptr: *const u8) -> Result<&'static str, core::str::Utf8Error> {
|
||||
let len = libc::strlen(ptr as *const i8);
|
||||
let parts = crate::slice::from_raw_parts(ptr, len);
|
||||
crate::str::from_utf8(parts)
|
||||
let parts = core::slice::from_raw_parts(ptr, len);
|
||||
core::str::from_utf8(parts)
|
||||
}
|
||||
|
||||
///Converts C string to Rust's one assuming it is UTF-8
|
||||
@ -90,8 +90,8 @@ pub unsafe fn c_str_to_rust(ptr: *const u8) -> Result<&'static str, crate::str::
|
||||
///It is UB to pass non-C string as it requires \0
|
||||
pub unsafe fn c_str_to_rust_unchecked(ptr: *const u8) -> &'static str {
|
||||
let len = libc::strlen(ptr as *const i8);
|
||||
let parts = crate::slice::from_raw_parts(ptr, len);
|
||||
crate::str::from_utf8_unchecked(parts)
|
||||
let parts = core::slice::from_raw_parts(ptr, len);
|
||||
core::str::from_utf8_unchecked(parts)
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
|
799
src/env/derive/mod.rs
vendored
Normal file
799
src/env/derive/mod.rs
vendored
Normal file
@ -0,0 +1,799 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
|
||||
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
*
|
||||
* This file is part of SPD.
|
||||
*
|
||||
* SPD is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* SPD is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with SPD. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and permission
|
||||
* notice:
|
||||
*
|
||||
* Copyright 2017 Douman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
//! Command line argument parser derive
|
||||
|
||||
extern crate alloc;
|
||||
extern crate proc_macro;
|
||||
|
||||
mod utils;
|
||||
use utils::*;
|
||||
|
||||
use crate::println;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use alloc::{
|
||||
string::String,
|
||||
borrow::ToOwned,
|
||||
vec,
|
||||
vec::*,
|
||||
};
|
||||
use core::fmt::Write;
|
||||
|
||||
struct Argument {
|
||||
field_name: String,
|
||||
name: String,
|
||||
desc: String,
|
||||
required: bool,
|
||||
is_optional: bool,
|
||||
default: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
enum OptValueType {
|
||||
Help,
|
||||
Bool,
|
||||
Value,
|
||||
MultiValue,
|
||||
}
|
||||
|
||||
struct Opt {
|
||||
arg: Argument,
|
||||
long: String,
|
||||
short: Option<String>,
|
||||
typ: OptValueType,
|
||||
}
|
||||
|
||||
struct Command {
|
||||
variant_name: String,
|
||||
command_name: String,
|
||||
desc: String,
|
||||
}
|
||||
|
||||
const FROM_FN: &str = "core::str::FromStr::from_str";
|
||||
const TAB: &str = " ";
|
||||
const PARSER_TRAIT: &str = "arg::Args";
|
||||
const DEFAULT_INIT: &str = "Default::default()";
|
||||
const INVALID_ARG_TYPE_STRING: &str = "Attribute accepts only str";
|
||||
const INVALID_REQUIRED_BOOL: &str = "Attribute required cannot be applied to bool switch";
|
||||
const UNKNOWN_ARG_ATTR: &str = "Unknown attribute is used";
|
||||
const ARG_INVALID_CHARS: &[char] = &[' ', '\t'];
|
||||
const ARG_NAME_SPACE_ERROR: &str = "Name contains space character";
|
||||
|
||||
fn parse_segment(segment: &syn::PathSegment) -> OptValueType {
|
||||
if segment.ident == "bool" {
|
||||
OptValueType::Bool
|
||||
} else if segment.ident == "Vec" {
|
||||
OptValueType::MultiValue
|
||||
} else {
|
||||
OptValueType::Value
|
||||
}
|
||||
}
|
||||
|
||||
fn from_enum(ast: &syn::DeriveInput, payload: &syn::DataEnum) -> TokenStream {
|
||||
let mut about_prog = String::new();
|
||||
for attr in ast.attrs.iter() {
|
||||
let attr = match attr.parse_meta() {
|
||||
Ok(attr) => attr,
|
||||
Err(error) => {
|
||||
return syn::Error::new_spanned(attr, format!("cannot parse attribute: {error}")).to_compile_error().into()
|
||||
}
|
||||
};
|
||||
|
||||
match attr {
|
||||
syn::Meta::NameValue(value) => if value.path.is_ident("doc") {
|
||||
if let syn::Lit::Str(ref text) = value.lit {
|
||||
about_prog.push_str(&text.value());
|
||||
about_prog.push_str("\n");
|
||||
}
|
||||
} else {
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
about_prog.pop();
|
||||
|
||||
let mut commands = Vec::new();
|
||||
for variant in payload.variants.iter() {
|
||||
let mut desc = String::new();
|
||||
let variant_name = variant.ident.to_string();
|
||||
if variant_name.is_empty() {
|
||||
return syn::Error::new_spanned(&variant.ident, "Oi, mate, You cannot have enum variant without name").to_compile_error().into()
|
||||
}
|
||||
let command_name = to_hyphenated_lower_case(&variant_name);
|
||||
if command_name.eq_ignore_ascii_case("help") {
|
||||
return syn::Error::new_spanned(&variant.ident, "Oi, mate, You cannot use variant 'Help'").to_compile_error().into()
|
||||
}
|
||||
|
||||
for attr in variant.attrs.iter() {
|
||||
let attr = match attr.parse_meta() {
|
||||
Ok(attr) => attr,
|
||||
Err(error) => {
|
||||
return syn::Error::new_spanned(attr, format!("cannot parse attribute: {error}")).to_compile_error().into()
|
||||
}
|
||||
};
|
||||
|
||||
match attr {
|
||||
syn::Meta::NameValue(value) => if value.path.is_ident("doc") {
|
||||
if let syn::Lit::Str(ref text) = value.lit {
|
||||
desc.push_str(&text.value());
|
||||
desc.push_str(" ");
|
||||
}
|
||||
},
|
||||
_ => continue
|
||||
}
|
||||
}
|
||||
|
||||
let field = match &variant.fields {
|
||||
syn::Fields::Unit => return syn::Error::new_spanned(&variant.fields, "Unit variant cannot be used").to_compile_error().into(),
|
||||
syn::Fields::Named(_) => return syn::Error::new_spanned(&variant.fields, "I'm too lazy to support named variant").to_compile_error().into(),
|
||||
syn::Fields::Unnamed(fields) => {
|
||||
if fields.unnamed.empty_or_trailing() {
|
||||
return syn::Error::new_spanned(&fields, "MUST specify single field").to_compile_error().into();
|
||||
} else if fields.unnamed.len() > 1 {
|
||||
return syn::Error::new_spanned(&fields, "MUST not specify more than 1 field").to_compile_error().into();
|
||||
} else {
|
||||
fields.unnamed.first().unwrap()
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
match &field.ty {
|
||||
syn::Type::Path(ref ty) => {
|
||||
let ty = ty.path.segments.last().expect("To have at least one segment");
|
||||
if ty.ident == "Option" {
|
||||
return syn::Error::new_spanned(&ty, "Command cannot be optional").to_compile_error().into()
|
||||
} else {
|
||||
match parse_segment(ty) {
|
||||
OptValueType::Bool => return syn::Error::new_spanned(&ty, "Command value cannot be boolean").to_compile_error().into(),
|
||||
OptValueType::MultiValue => return syn::Error::new_spanned(&ty, "Command value Vec<_>").to_compile_error().into(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
},
|
||||
ty => {
|
||||
return syn::Error::new_spanned(&ty, "Expected simple ident or path").to_compile_error().into()
|
||||
}
|
||||
}
|
||||
|
||||
commands.push(Command {
|
||||
command_name,
|
||||
variant_name,
|
||||
desc
|
||||
})
|
||||
}
|
||||
|
||||
if commands.is_empty() {
|
||||
return syn::Error::new_spanned(&ast, "Enum must have at least one variant").to_compile_error().into()
|
||||
}
|
||||
|
||||
let (impl_gen, type_gen, where_clause) = ast.generics.split_for_impl();
|
||||
|
||||
let help_msg = {
|
||||
use std::io::Write;
|
||||
use tabwriter::TabWriter;
|
||||
|
||||
let mut tw = TabWriter::new(vec![]);
|
||||
|
||||
let _ = write!(tw, "COMMANDS:\n");
|
||||
for command in commands.iter() {
|
||||
let _ = write!(tw, "\t{}\t{}\n", command.command_name, command.desc);
|
||||
}
|
||||
|
||||
let _ = tw.flush();
|
||||
|
||||
String::from_utf8(tw.into_inner().unwrap()).unwrap()
|
||||
};
|
||||
|
||||
let mut result = String::new();
|
||||
let _ = writeln!(result, "{} {} for {}{} {{", quote!(impl #impl_gen), PARSER_TRAIT, ast.ident, quote!(#type_gen #where_clause));
|
||||
|
||||
let _ = writeln!(result, "{}const HELP: &'static str = \"{}\";", TAB, help_msg);
|
||||
|
||||
//from_args START
|
||||
let _ = writeln!(result, "{}fn from_args<'a, T: IntoIterator<Item = &'a str>>(_args_: T) -> Result<Self, arg::ParseKind<'a>> {{", TAB);
|
||||
|
||||
let _ = writeln!(result, "{0}{0}let mut _args_ = _args_.into_iter();\n", TAB);
|
||||
|
||||
//args START
|
||||
let _ = writeln!(result, "{0}{0}while let Some(_arg_) = _args_.next() {{", TAB);
|
||||
|
||||
//help
|
||||
let _ = writeln!(result, "{0}{0}{0}if _arg_.eq_ignore_ascii_case(\"help\") {{", TAB);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}return Err(arg::ParseKind::Top(arg::ParseError::HelpRequested(Self::HELP)));", TAB);
|
||||
let _ = write!(result, "{0}{0}{0}}}", TAB);
|
||||
|
||||
for command in commands.iter() {
|
||||
//arg START
|
||||
let _ = writeln!(result, " else if _arg_.eq_ignore_ascii_case(\"{}\") {{", command.command_name);
|
||||
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}match {1}::from_args(_args_) {{", TAB, PARSER_TRAIT);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}Ok(res) => return Ok(Self::{1}(res)),", TAB, command.variant_name);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}Err(arg::ParseKind::Top(error)) => return Err(arg::ParseKind::Sub(\"{1}\", error)),", TAB, command.command_name);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}Err(arg::ParseKind::Sub(name, error)) => return Err(arg::ParseKind::Sub(name, error)),", TAB);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}}}", TAB);
|
||||
|
||||
//arg END
|
||||
let _ = write!(result, "{0}{0}{0}}}", TAB);
|
||||
}
|
||||
|
||||
//args END
|
||||
let _ = writeln!(result, "\n{0}{0}}}", TAB);
|
||||
|
||||
let _ = writeln!(result, "{0}{0}Err(arg::ParseKind::Top(arg::ParseError::RequiredArgMissing(\"command\")))", TAB);
|
||||
|
||||
//from_args END
|
||||
let _ = writeln!(result, "{}}}", TAB);
|
||||
|
||||
let _ = writeln!(result, "}}");
|
||||
|
||||
if let Ok(val) = std::env::var("ARG_RS_PRINT_PARSER") {
|
||||
match val.trim() {
|
||||
"0" | "false" => (),
|
||||
_ => println!("{result}"),
|
||||
}
|
||||
}
|
||||
result.parse().expect("To parse generated code")
|
||||
}
|
||||
|
||||
fn from_struct(ast: &syn::DeriveInput, payload: &syn::DataStruct) -> TokenStream {
|
||||
let mut about_prog = String::new();
|
||||
for attr in ast.attrs.iter() {
|
||||
let attr = match attr.parse_meta() {
|
||||
Ok(attr) => attr,
|
||||
Err(error) => {
|
||||
return syn::Error::new_spanned(attr, format!("cannot parse attribute: {error}")).to_compile_error().into()
|
||||
}
|
||||
};
|
||||
|
||||
match attr {
|
||||
syn::Meta::NameValue(value) => if value.path.is_ident("doc") {
|
||||
if let syn::Lit::Str(ref text) = value.lit {
|
||||
about_prog.push_str(&text.value());
|
||||
about_prog.push_str("\n");
|
||||
}
|
||||
} else {
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
about_prog.pop();
|
||||
|
||||
let mut options = Vec::new();
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
options.push(Opt {
|
||||
arg: Argument {
|
||||
field_name: "_".to_owned(),
|
||||
name: "help".to_owned(),
|
||||
desc: "Prints this help information".to_owned(),
|
||||
required: false,
|
||||
is_optional: false,
|
||||
default: None,
|
||||
},
|
||||
short: Some("h".to_owned()),
|
||||
long: "help".to_owned(),
|
||||
typ: OptValueType::Help,
|
||||
});
|
||||
|
||||
let mut sub_command = None;
|
||||
let mut multi_argument = None;
|
||||
|
||||
for field in payload.fields.iter() {
|
||||
let field_name = field.ident.as_ref().unwrap().to_string();
|
||||
let name = field.ident.as_ref().unwrap().to_string().trim_matches(|ch| !char::is_alphanumeric(ch)).to_owned();
|
||||
let mut desc = String::new();
|
||||
let mut short = None;
|
||||
let mut long = None;
|
||||
let mut required = false;
|
||||
let mut is_sub = false;
|
||||
|
||||
let (is_optional, typ) = match field.ty {
|
||||
syn::Type::Path(ref ty) => {
|
||||
let ty = ty.path.segments.last().expect("To have at least one segment");
|
||||
|
||||
if ty.ident == "Option" {
|
||||
let ty = match &ty.arguments {
|
||||
syn::PathArguments::AngleBracketed(ref args) => match args.args.len() {
|
||||
0 => return syn::Error::new_spanned(&ty.ident, "Oi, mate, Option is without type arguments. Fix it").to_compile_error().into(),
|
||||
1 => match args.args.first().unwrap() {
|
||||
syn::GenericArgument::Type(syn::Type::Path(ty)) => parse_segment(ty.path.segments.last().expect("To have at least one segment")),
|
||||
_ => return syn::Error::new_spanned(&ty.ident, "Oi, mate, Option should have type argument, but got some other shite. Fix it").to_compile_error().into(),
|
||||
},
|
||||
_ => return syn::Error::new_spanned(&ty.ident, "Oi, mate, Option has too many type arguments. Fix it").to_compile_error().into()
|
||||
},
|
||||
syn::PathArguments::None => return syn::Error::new_spanned(&ty.ident, "Oi, mate, Option is without type arguments. Fix it").to_compile_error().into(),
|
||||
syn::PathArguments::Parenthesized(_) => return syn::Error::new_spanned(&ty.ident, "Oi, mate, you got wrong brackets for your Option . Fix it").to_compile_error().into(),
|
||||
};
|
||||
|
||||
(true, ty)
|
||||
} else {
|
||||
(false, parse_segment(ty))
|
||||
}
|
||||
},
|
||||
_ => (false, OptValueType::Value),
|
||||
};
|
||||
|
||||
if is_optional && typ == OptValueType::MultiValue {
|
||||
return syn::Error::new_spanned(field, "Option<Vec<_>> makes no sense. Just use plain Vec<_>").to_compile_error().into();
|
||||
}
|
||||
|
||||
let mut default = None;
|
||||
|
||||
for attr in field.attrs.iter() {
|
||||
let attr = match attr.parse_meta() {
|
||||
Ok(attr) => attr,
|
||||
Err(error) => {
|
||||
return syn::Error::new_spanned(attr, format!("cannot parse attribute: {error}")).to_compile_error().into()
|
||||
}
|
||||
};
|
||||
|
||||
match attr {
|
||||
syn::Meta::NameValue(value) => if value.path.is_ident("doc") {
|
||||
if let syn::Lit::Str(ref text) = value.lit {
|
||||
desc.push_str(&text.value());
|
||||
desc.push_str(" ");
|
||||
}
|
||||
},
|
||||
syn::Meta::List(value) => if value.path.is_ident("arg") {
|
||||
for value_attr in value.nested.iter() {
|
||||
match value_attr {
|
||||
syn::NestedMeta::Meta(value_attr) => {
|
||||
match value_attr {
|
||||
syn::Meta::Path(value_attr) => if value_attr.is_ident("short") {
|
||||
short = Some(format!("{}", name.chars().next().unwrap()).to_lowercase());
|
||||
} else if value_attr.is_ident("long") {
|
||||
long = Some(name.to_lowercase());
|
||||
} else if value_attr.is_ident("default_value") {
|
||||
default = Some(DEFAULT_INIT.to_owned());
|
||||
} else if value_attr.is_ident("required") {
|
||||
if typ != OptValueType::Bool {
|
||||
required = true
|
||||
} else {
|
||||
return syn::Error::new_spanned(value_attr, INVALID_REQUIRED_BOOL).to_compile_error().into();
|
||||
}
|
||||
} else if value_attr.is_ident("sub") {
|
||||
if typ == OptValueType::Value {
|
||||
is_sub = true;
|
||||
} else {
|
||||
return syn::Error::new_spanned(value_attr, "Sub-command must be simple value").to_compile_error().into();
|
||||
}
|
||||
}
|
||||
syn::Meta::NameValue(value_attr) => if value_attr.path.is_ident("short") {
|
||||
if let syn::Lit::Str(ref text) = value_attr.lit {
|
||||
let value_attr_text = text.value();
|
||||
|
||||
if value_attr_text.contains(ARG_INVALID_CHARS) {
|
||||
return syn::Error::new_spanned(value_attr.lit.clone(), ARG_NAME_SPACE_ERROR).to_compile_error().into();
|
||||
}
|
||||
|
||||
short = Some(value_attr_text);
|
||||
} else {
|
||||
return syn::Error::new_spanned(value_attr.path.clone(), INVALID_ARG_TYPE_STRING).to_compile_error().into();
|
||||
}
|
||||
} else if value_attr.path.is_ident("long") {
|
||||
if let syn::Lit::Str(ref text) = value_attr.lit {
|
||||
let value_attr_text = text.value();
|
||||
|
||||
if value_attr_text.contains(ARG_INVALID_CHARS) {
|
||||
return syn::Error::new_spanned(value_attr.lit.clone(), ARG_NAME_SPACE_ERROR).to_compile_error().into();
|
||||
}
|
||||
|
||||
long = Some(value_attr_text)
|
||||
} else {
|
||||
return syn::Error::new_spanned(value_attr.path.clone(), INVALID_ARG_TYPE_STRING).to_compile_error().into();
|
||||
}
|
||||
} else if value_attr.path.is_ident("default_value") {
|
||||
if let syn::Lit::Str(ref text) = value_attr.lit {
|
||||
default = Some(text.value());
|
||||
} else {
|
||||
return syn::Error::new_spanned(value_attr.path.clone(), INVALID_ARG_TYPE_STRING).to_compile_error().into();
|
||||
}
|
||||
} else {
|
||||
return syn::Error::new_spanned(value_attr.path.clone(), UNKNOWN_ARG_ATTR).to_compile_error().into();
|
||||
}
|
||||
_ => {
|
||||
},
|
||||
}
|
||||
},
|
||||
syn::NestedMeta::Lit(_) => (),
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
desc.pop();
|
||||
|
||||
if required && default.is_some() {
|
||||
return syn::Error::new_spanned(field.ident.clone(), "Marked as required, but default value is provided?").to_compile_error().into();
|
||||
} else if is_optional && default.is_some() {
|
||||
return syn::Error::new_spanned(field.ident.clone(), "Optional, but default value is provided?").to_compile_error().into();
|
||||
} else if is_sub && is_optional {
|
||||
return syn::Error::new_spanned(field.ident.clone(), "Sub-command cannot be optional").to_compile_error().into();
|
||||
} else if is_sub && default.is_some() {
|
||||
return syn::Error::new_spanned(field.ident.clone(), "Sub-command cannot have default value").to_compile_error().into();
|
||||
} else if !required && !is_optional && default.is_none() {
|
||||
default = Some(DEFAULT_INIT.to_owned());
|
||||
}
|
||||
|
||||
if short.is_none() && long.is_none() {
|
||||
if typ == OptValueType::MultiValue {
|
||||
if multi_argument.is_some() {
|
||||
return syn::Error::new_spanned(field.ident.clone(), "Second argument collection. There can be only one").to_compile_error().into();
|
||||
} else if sub_command.is_some() {
|
||||
return syn::Error::new_spanned(field.ident.clone(), "Multi-argument collection and sub-command are mutually exclusive").to_compile_error().into();
|
||||
}
|
||||
|
||||
multi_argument = Some(Argument {
|
||||
field_name,
|
||||
name,
|
||||
desc,
|
||||
required,
|
||||
is_optional,
|
||||
default,
|
||||
});
|
||||
|
||||
} else if is_sub {
|
||||
if sub_command.is_some() {
|
||||
return syn::Error::new_spanned(field.ident.clone(), "Second sub-command. There can be only one").to_compile_error().into();
|
||||
} else if multi_argument.is_some() {
|
||||
return syn::Error::new_spanned(field.ident.clone(), "Sub-command and multi-argument collection are mutually exclusive").to_compile_error().into();
|
||||
}
|
||||
|
||||
sub_command = Some(Argument {
|
||||
field_name,
|
||||
name,
|
||||
desc,
|
||||
required: true,
|
||||
is_optional: false,
|
||||
default: None,
|
||||
});
|
||||
} else {
|
||||
arguments.push(Argument {
|
||||
field_name,
|
||||
name,
|
||||
desc,
|
||||
required,
|
||||
is_optional,
|
||||
default,
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
let long = match long {
|
||||
Some(long) => long,
|
||||
None => name.clone()
|
||||
};
|
||||
|
||||
options.push(Opt {
|
||||
arg: Argument {
|
||||
field_name,
|
||||
name,
|
||||
desc,
|
||||
required,
|
||||
is_optional,
|
||||
default,
|
||||
},
|
||||
short,
|
||||
long,
|
||||
typ
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let (impl_gen, type_gen, where_clause) = ast.generics.split_for_impl();
|
||||
|
||||
let help_msg = {
|
||||
use std::io::Write;
|
||||
use tabwriter::TabWriter;
|
||||
|
||||
let mut tw = TabWriter::new(vec![]);
|
||||
|
||||
let _ = write!(tw, "{}
|
||||
USAGE:", about_prog);
|
||||
|
||||
if !options.is_empty() {
|
||||
let _ = write!(tw, " [OPTIONS]");
|
||||
}
|
||||
|
||||
for argument in arguments.iter() {
|
||||
let _ = if argument.required {
|
||||
write!(tw, " <{}>", argument.name)
|
||||
} else {
|
||||
write!(tw, " [{}]", argument.name)
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(argument) = multi_argument.as_ref() {
|
||||
let _ = if argument.required {
|
||||
write!(tw, " <{}>...", argument.name)
|
||||
} else {
|
||||
write!(tw, " [{}]...", argument.name)
|
||||
};
|
||||
} else if let Some(argument) = sub_command.as_ref() {
|
||||
let _ = write!(tw, " <{}>", argument.name);
|
||||
}
|
||||
|
||||
if !options.is_empty() {
|
||||
let _ = write!(tw, "\n\nOPTIONS:\n");
|
||||
}
|
||||
|
||||
for option in options.iter() {
|
||||
let _ = write!(tw, "\t");
|
||||
if let Some(short) = option.short.as_ref() {
|
||||
let _ = write!(tw, "-{},", short);
|
||||
}
|
||||
let _ = write!(tw, "\t");
|
||||
|
||||
let _ = write!(tw, "--{}", option.long);
|
||||
|
||||
let _ = match option.typ {
|
||||
OptValueType::MultiValue => write!(tw, " <{}>...", option.arg.name),
|
||||
OptValueType::Value => write!(tw, " <{}>", option.arg.name),
|
||||
_ => Ok(()),
|
||||
};
|
||||
|
||||
let _ = write!(tw, "\t{}\n", option.arg.desc);
|
||||
}
|
||||
|
||||
if !arguments.is_empty() || multi_argument.is_some() || sub_command.is_some() {
|
||||
let _ = write!(tw, "\nARGS:\n");
|
||||
}
|
||||
|
||||
for argument in arguments.iter() {
|
||||
let _ = if argument.required {
|
||||
writeln!(tw, "\t<{}>\t{}", argument.name, argument.desc)
|
||||
} else {
|
||||
writeln!(tw, "\t[{}]\t{}", argument.name, argument.desc)
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(argument) = multi_argument.as_ref() {
|
||||
let _ = writeln!(tw, "\t<{}>...\t{}", argument.name, argument.desc);
|
||||
} else if let Some(command) = sub_command.as_ref() {
|
||||
let _ = writeln!(tw, "\t<{}>\t{}", command.name, command.desc);
|
||||
}
|
||||
|
||||
let _ = tw.flush();
|
||||
|
||||
String::from_utf8(tw.into_inner().unwrap()).unwrap()
|
||||
};
|
||||
|
||||
let mut result = String::new();
|
||||
let _ = writeln!(result, "{} {} for {}{} {{", quote!(impl #impl_gen), PARSER_TRAIT, ast.ident, quote!(#type_gen #where_clause));
|
||||
let _ = writeln!(result, "{}const HELP: &'static str = \"{}\";", TAB, help_msg);
|
||||
|
||||
let _ = writeln!(result, "{}fn from_args<'a, T: IntoIterator<Item = &'a str>>(_args_: T) -> Result<Self, arg::ParseKind<'a>> {{", TAB);
|
||||
|
||||
for option in options.iter() {
|
||||
if option.arg.field_name == "_" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let _ = match option.typ {
|
||||
OptValueType::MultiValue => writeln!(result, "{0}{0}let mut {1} = Vec::new();", TAB, option.arg.field_name),
|
||||
OptValueType::Bool => writeln!(result, "{0}{0}let mut {1} = false;", TAB, option.arg.field_name),
|
||||
_ => writeln!(result, "{0}{0}let mut {1} = None;", TAB, option.arg.field_name),
|
||||
};
|
||||
}
|
||||
|
||||
for argument in arguments.iter() {
|
||||
let _ = writeln!(result, "{0}{0}let mut {1} = None;", TAB, argument.field_name);
|
||||
}
|
||||
|
||||
if let Some(argument) = multi_argument.as_ref() {
|
||||
let _ = writeln!(result, "{0}{0}let mut {1} = Vec::new();", TAB, argument.field_name);
|
||||
} else if let Some(command) = sub_command.as_ref() {
|
||||
let _ = writeln!(result, "{0}{0}let mut {1} = None;", TAB, command.field_name);
|
||||
}
|
||||
|
||||
let _ = writeln!(result, "{0}{0}let mut _args_ = _args_.into_iter();\n", TAB);
|
||||
let _ = writeln!(result, "{0}{0}while let Some(_arg_) = _args_.next() {{", TAB);
|
||||
|
||||
//options
|
||||
let _ = writeln!(result, "{0}{0}{0}if let Some(_arg_) = _arg_.strip_prefix('-') {{", TAB);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}match _arg_ {{", TAB);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}\"h\" | \"-help\" => return Err(arg::ParseKind::Top(arg::ParseError::HelpRequested(Self::HELP))),", TAB);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}\"\" => (),", TAB);
|
||||
|
||||
for option in options.iter() {
|
||||
if option.arg.field_name == "_" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let _ = write!(result, "{0}{0}{0}{0}{0}", TAB);
|
||||
|
||||
if let Some(short) = option.short.as_ref() {
|
||||
let _ = write!(result, "\"{}\" | ", short);
|
||||
}
|
||||
|
||||
let _ = write!(result, "\"-{}\" => ", option.long);
|
||||
|
||||
let _ = match option.typ {
|
||||
OptValueType::Help => panic!("Option Help is invalid here. Bug report it"),
|
||||
OptValueType::Bool => write!(result, "{{ {0} = !{0}; continue }},", option.arg.field_name),
|
||||
OptValueType::Value => write!(result, "match _args_.next() {{
|
||||
{0}{0}{0}{0}{0}{0}Some(_next_arg_) => match {1}(_next_arg_) {{
|
||||
{0}{0}{0}{0}{0}{0}{0}Ok(value) => {{ {2} = Some(value); continue }},
|
||||
{0}{0}{0}{0}{0}{0}{0}Err(_) => return Err(arg::ParseKind::Top(arg::ParseError::InvalidFlagValue(\"{3}\", _next_arg_))),
|
||||
{0}{0}{0}{0}{0}{0}}},
|
||||
{0}{0}{0}{0}{0}{0}None => return Err(arg::ParseKind::Top(arg::ParseError::MissingValue(\"{3}\"))),
|
||||
{0}{0}{0}{0}{0}}}", TAB, FROM_FN, option.arg.field_name, option.arg.name),
|
||||
OptValueType::MultiValue => write!(result, "match _args_.next() {{
|
||||
{0}{0}{0}{0}{0}{0}Some(_next_arg_) => match {1}(_next_arg_) {{
|
||||
{0}{0}{0}{0}{0}{0}{0}Ok(value) => {{ {2}.push(value); continue }},
|
||||
{0}{0}{0}{0}{0}{0}{0}Err(_) => return Err(arg::ParseKind::Top(arg::ParseError::InvalidFlagValue(\"{3}\", _next_arg_))),
|
||||
{0}{0}{0}{0}{0}{0}}},
|
||||
{0}{0}{0}{0}{0}{0}None => return Err(arg::ParseKind::Top(arg::ParseError::MissingValue(\"{3}\"))),
|
||||
{0}{0}{0}{0}{0}}}", TAB, FROM_FN, option.arg.field_name, option.arg.name),
|
||||
};
|
||||
let _ = writeln!(result, "");
|
||||
}
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}_ => return Err(arg::ParseKind::Top(arg::ParseError::UnknownFlag(_arg_))),", TAB);
|
||||
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}}}", TAB);
|
||||
let _ = writeln!(result, "{0}{0}{0}}}", TAB);
|
||||
//rest args
|
||||
for (idx, arg) in arguments.iter().enumerate() {
|
||||
if idx == 0 {
|
||||
let _ = writeln!(result, "{0}{0}{0}if {1}.is_none() {{", TAB, arg.field_name);
|
||||
} else {
|
||||
let _ = writeln!(result, "{0}{0}{0}}} else if {1}.is_none() {{", TAB, arg.field_name);
|
||||
}
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}match {1}(_arg_) {{", TAB, FROM_FN);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}Ok(_res_) => {1} = Some(_res_),", TAB, arg.field_name);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}Err(_) => return Err(arg::ParseKind::Top(arg::ParseError::InvalidArgValue(\"{1}\", _arg_))),", TAB, arg.field_name);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}}}", TAB);
|
||||
}
|
||||
//too many args?
|
||||
if !arguments.is_empty() {
|
||||
let _ = writeln!(result, "{0}{0}{0}}} else {{", TAB);
|
||||
}
|
||||
|
||||
if let Some(arg) = multi_argument.as_ref() {
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}match {1}(_arg_) {{", TAB, FROM_FN);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}Ok(_res_) => {1}.push(_res_),", TAB, arg.field_name);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}Err(_) => return Err(arg::ParseKind::Top(arg::ParseError::InvalidArgValue(\"{1}\", _arg_))),", TAB, arg.field_name);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}}}", TAB);
|
||||
} else if let Some(command) = sub_command.as_ref() {
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}match {1}::from_args(core::iter::once(_arg_).chain(_args_)) {{", TAB, PARSER_TRAIT);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}Ok(_res_) => {{ {1} = Some(_res_); break; }},", TAB, command.field_name);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}Err(arg::ParseKind::Top(_)) => return Err(arg::ParseKind::Top(arg::ParseError::RequiredArgMissing(\"{1}\"))),", TAB, command.field_name);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}{0}Err(arg::ParseKind::Sub(name, error)) => return Err(arg::ParseKind::Sub(name, error)),", TAB);
|
||||
let _ = writeln!(result, "{0}{0}{0}{0}}}", TAB);
|
||||
} else {
|
||||
let _ = writeln!(result, "{0}{0}{0}{0} return Err(arg::ParseKind::Top(arg::ParseError::TooManyArgs));", TAB);
|
||||
}
|
||||
//exit args
|
||||
if !arguments.is_empty() {
|
||||
let _ = writeln!(result, "{0}{0}{0}}}", TAB);
|
||||
let _ = writeln!(result, "{0}{0}}}", TAB);
|
||||
} else {
|
||||
let _ = writeln!(result, "{0}{0}}}", TAB);
|
||||
}
|
||||
|
||||
//Set defaults
|
||||
for option in options.iter() {
|
||||
if option.arg.field_name == "_" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let _ = match option.typ {
|
||||
OptValueType::MultiValue => Ok(()),
|
||||
OptValueType::Bool => Ok(()),
|
||||
_ => match option.arg.default {
|
||||
Some(ref default) => writeln!(result, "{0}{0}let {1} = if let Some(value) = {1} {{ value }} else {{ {2} }};", TAB, option.arg.field_name, default),
|
||||
None => match option.arg.is_optional {
|
||||
true => Ok(()),
|
||||
false => writeln!(result, "{0}{0}let {1} = if let Some(value) = {1} {{ value }} else {{ return Err(arg::ParseKind::Top(arg::ParseError::RequiredArgMissing(\"{2}\"))) }};", TAB, option.arg.field_name, option.arg.name),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
for arg in arguments.iter() {
|
||||
let _ = match arg.default {
|
||||
Some(ref default) => writeln!(result, "{0}{0}let {1} = if let Some(value) = {1} {{ value }} else {{ {2} }};", TAB, arg.field_name, default),
|
||||
None => match arg.is_optional {
|
||||
true => Ok(()),
|
||||
false => writeln!(result, "{0}{0}let {1} = if let Some(value) = {1} {{ value }} else {{ return Err(arg::ParseKind::Top(arg::ParseError::RequiredArgMissing(\"{2}\"))) }};", TAB, arg.field_name, arg.name),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(command) = sub_command.as_ref() {
|
||||
let _ = writeln!(result, "{0}{0}let {1} = if let Some(value) = {1} {{ value }} else {{ return Err(arg::ParseKind::Top(arg::ParseError::RequiredArgMissing(\"{2}\"))) }};", TAB, command.field_name, command.name);
|
||||
}
|
||||
|
||||
//Fill result
|
||||
let _ = writeln!(result, "{0}{0}Ok(Self {{", TAB);
|
||||
|
||||
for option in options.iter() {
|
||||
if option.arg.field_name == "_" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let _ = if option.arg.is_optional && option.typ == OptValueType::Bool {
|
||||
writeln!(result, "{0}{0}{0}{1}: Some({1}),", TAB, option.arg.field_name)
|
||||
} else {
|
||||
writeln!(result, "{0}{0}{0}{1},", TAB, option.arg.field_name)
|
||||
};
|
||||
}
|
||||
|
||||
for arg in arguments.iter() {
|
||||
let _ = writeln!(result, "{0}{0}{0}{1},", TAB, arg.field_name);
|
||||
}
|
||||
|
||||
if let Some(arg) = multi_argument.as_ref() {
|
||||
let _ = writeln!(result, "{0}{0}{0}{1},", TAB, arg.field_name);
|
||||
} else if let Some(arg) = sub_command.as_ref() {
|
||||
let _ = writeln!(result, "{0}{0}{0}{1},", TAB, arg.field_name);
|
||||
}
|
||||
|
||||
let _ = writeln!(result, "{0}{0}}})", TAB);
|
||||
|
||||
//Exit fn
|
||||
let _ = writeln!(result, "{}}}", TAB);
|
||||
|
||||
let _ = writeln!(result, "}}");
|
||||
|
||||
if let Ok(val) = std::env::var("ARG_RS_PRINT_PARSER") {
|
||||
match val.trim() {
|
||||
"0" | "false" => (),
|
||||
_ => println!("{result}"),
|
||||
}
|
||||
}
|
||||
result.parse().expect("To parse generated code")
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Args, attributes(parser, arg))]
|
||||
pub fn parser_derive(input: TokenStream) -> TokenStream {
|
||||
const INVALID_INPUT_TYPE: &str = "Unsupported parser input type. Expect: struct";
|
||||
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
||||
|
||||
match ast.data {
|
||||
syn::Data::Struct(ref data) => from_struct(&ast, data),
|
||||
syn::Data::Enum(ref data) => from_enum(&ast, data),
|
||||
_ => syn::Error::new_spanned(ast.ident, INVALID_INPUT_TYPE).to_compile_error().into(),
|
||||
}
|
||||
}
|
76
src/env/derive/utils.rs
vendored
Normal file
76
src/env/derive/utils.rs
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
|
||||
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
*
|
||||
* This file is part of SPD.
|
||||
*
|
||||
* SPD is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* SPD is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with SPD. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and permission
|
||||
* notice:
|
||||
*
|
||||
* Copyright 2017 Douman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::string::String;
|
||||
|
||||
pub fn to_hyphenated_lower_case(input: &str) -> String {
|
||||
let mut output = String::with_capacity(input.len());
|
||||
|
||||
let mut chars = input.chars();
|
||||
let mut prev_upper = false;
|
||||
if let Some(ch) = chars.next() {
|
||||
if ch.is_uppercase() {
|
||||
prev_upper = true;
|
||||
for ch in ch.to_lowercase() {
|
||||
output.push(ch);
|
||||
}
|
||||
} else {
|
||||
output.push(ch);
|
||||
}
|
||||
}
|
||||
|
||||
for ch in chars {
|
||||
if ch.is_uppercase() {
|
||||
if !prev_upper {
|
||||
output.push('-');
|
||||
}
|
||||
|
||||
for ch in ch.to_lowercase() {
|
||||
output.push(ch);
|
||||
}
|
||||
|
||||
prev_upper = true;
|
||||
} else {
|
||||
output.push(ch);
|
||||
|
||||
prev_upper = false;
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
157
src/env/mod.rs
vendored
Normal file
157
src/env/mod.rs
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
|
||||
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
*
|
||||
* This file is part of SPD.
|
||||
*
|
||||
* SPD is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* SPD is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with SPD. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and permission
|
||||
* notice:
|
||||
*
|
||||
* Copyright 2017 Douman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#![no_std]
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::needless_lifetimes))]
|
||||
|
||||
use core::fmt;
|
||||
|
||||
mod derive;
|
||||
mod split;
|
||||
|
||||
use derive::*;
|
||||
use split::Split;
|
||||
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
///Parse errors
|
||||
pub enum ParseKind<'a> {
|
||||
///Main command result
|
||||
Top(ParseError<'a>),
|
||||
///Sub-command name and result
|
||||
Sub(&'static str, ParseError<'a>),
|
||||
}
|
||||
|
||||
impl<'a> ParseKind<'a> {
|
||||
#[inline]
|
||||
///Returns whether help is requested
|
||||
pub fn is_help(&self) -> bool {
|
||||
match self {
|
||||
Self::Top(err) => err.is_help(),
|
||||
Self::Sub(_, err) => err.is_help(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<ParseError<'a>> for ParseKind<'a> {
|
||||
#[inline(always)]
|
||||
fn eq(&self, right: &ParseError<'a>) -> bool {
|
||||
match self {
|
||||
Self::Top(left) => PartialEq::eq(left, right),
|
||||
Self::Sub(_, left) => PartialEq::eq(left, right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
///Parse errors
|
||||
pub enum ParseError<'a> {
|
||||
///User requested help.
|
||||
///
|
||||
///Contains slice with `Args::HELP`
|
||||
HelpRequested(&'static str),
|
||||
///Too many arguments are specified.
|
||||
TooManyArgs,
|
||||
///Argument is required, but missing
|
||||
///
|
||||
///Contains name of argument
|
||||
RequiredArgMissing(&'a str),
|
||||
///Flag is specified, but value is missing.
|
||||
///
|
||||
///Contains full flag name.
|
||||
MissingValue(&'a str),
|
||||
///Flag is specified with invalid value
|
||||
///
|
||||
///Contains full flag name and provided value.
|
||||
InvalidFlagValue(&'a str, &'a str),
|
||||
///Argument is supplied with invalid vlaue
|
||||
///
|
||||
///Contains argument name and provided value.
|
||||
InvalidArgValue(&'a str, &'a str),
|
||||
///Unknown flag is specified.
|
||||
UnknownFlag(&'a str)
|
||||
}
|
||||
|
||||
impl<'a> ParseError<'a> {
|
||||
///Returns whether help is requested
|
||||
pub fn is_help(&self) -> bool {
|
||||
match self {
|
||||
ParseError::HelpRequested(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for ParseError<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ParseError::HelpRequested(help) => f.write_str(help),
|
||||
ParseError::TooManyArgs => f.write_str("Too many arguments are provided"),
|
||||
ParseError::RequiredArgMissing(arg) => write!(f, "Argument '{}' is required, but not provided", arg),
|
||||
ParseError::MissingValue(arg) => write!(f, "Flag '{}' is provided without value", arg),
|
||||
ParseError::InvalidFlagValue(arg, value) => write!(f, "Flag '{}' is provided with '{}' which is invalid", arg, value),
|
||||
ParseError::InvalidArgValue(arg, value) => write!(f, "Argument '{}' is provided with '{}' which is invalid", arg, value),
|
||||
ParseError::UnknownFlag(flag) => write!(f, "Unknown flag '{}' is provided", flag),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for ParseKind<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ParseKind::Top(res) => fmt::Display::fmt(res, f),
|
||||
ParseKind::Sub(name, res) => write!(f, "{name}: {res}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///Describers command line argument parser
|
||||
pub trait Args: Sized {
|
||||
///Help message for parser.
|
||||
const HELP: &'static str;
|
||||
|
||||
///Parses arguments from iterator of strings
|
||||
fn from_args<'a, T: IntoIterator<Item = &'a str>>(args: T) -> Result<Self, ParseKind<'a>>;
|
||||
|
||||
///Parses arguments from string, which gets tokenized and passed to from.
|
||||
fn from_text<'a>(text: &'a str) -> Result<Self, ParseKind<'a>> {
|
||||
Self::from_args(Split::from_str(text))
|
||||
}
|
||||
}
|
88
src/env/split.rs
vendored
Normal file
88
src/env/split.rs
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
|
||||
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
*
|
||||
* This file is part of SPD.
|
||||
*
|
||||
* SPD is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* SPD is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with SPD. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and permission
|
||||
* notice:
|
||||
*
|
||||
* Copyright 2017 Douman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
///Simple split of string into arguments
|
||||
pub struct Split<'a> {
|
||||
string: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Split<'a> {
|
||||
///Creates new instance
|
||||
pub const fn from_str(string: &'a str) -> Self {
|
||||
Self {
|
||||
string
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
///Retrieves next argument
|
||||
pub fn next_arg(&mut self) -> Option<&str> {
|
||||
self.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Split<'a> {
|
||||
type Item = &'a str;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.string = self.string.trim_start();
|
||||
|
||||
if self.string.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (end_char, from) = match self.string.get(0..=0) {
|
||||
Some("'") => ('\'', 1),
|
||||
Some("\"") => ('"', 1),
|
||||
_ => (' ', 0),
|
||||
};
|
||||
|
||||
match self.string[from..].find(end_char) {
|
||||
Some(end_idx) => {
|
||||
let end_idx = end_idx + from;
|
||||
let result = &self.string[from..end_idx];
|
||||
self.string = &self.string[end_idx+1..];
|
||||
Some(result.trim_start())
|
||||
},
|
||||
None => {
|
||||
let result = &self.string[from..];
|
||||
self.string = "";
|
||||
Some(result.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
src/lib.rs
25
src/lib.rs
@ -46,23 +46,11 @@
|
||||
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
extern crate core;
|
||||
|
||||
pub mod entry;
|
||||
// pub mod env;
|
||||
mod macros;
|
||||
pub mod process;
|
||||
|
||||
pub use alloc::{
|
||||
borrow::*,
|
||||
boxed::*,
|
||||
collections::*,
|
||||
rc::*,
|
||||
string::*,
|
||||
vec::*,
|
||||
};
|
||||
pub use core::*;
|
||||
|
||||
pub use macros::*;
|
||||
|
||||
pub use libc_dbg as dbg;
|
||||
@ -73,15 +61,4 @@ pub use libc_print as print;
|
||||
pub use libc_println as println;
|
||||
pub use libc_writeln as writeln;
|
||||
|
||||
pub use libc_alloc::LibcAlloc;
|
||||
|
||||
pub const SOURCE: &str = "https://git.tebibyte.media/emma/spd.git";
|
||||
|
||||
#[global_allocator]
|
||||
pub static ALLOCATOR: LibcAlloc = LibcAlloc;
|
||||
|
||||
#[panic_handler]
|
||||
pub fn panic(info: &panic::PanicInfo) -> ! {
|
||||
eprintln!("{:?}", info);
|
||||
loop {}
|
||||
}
|
||||
|
@ -85,10 +85,13 @@
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
#![no_std]
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused)]
|
||||
#![warn(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
use core::{convert::TryFrom, file, line, stringify};
|
||||
|
||||
/// This forces a "C" library linkage
|
||||
#[cfg(not(windows))]
|
||||
#[link(name = "c")]
|
||||
@ -108,9 +111,9 @@ pub const __LIBC_STDERR: i32 = 2;
|
||||
#[doc(hidden)]
|
||||
pub struct __LibCWriter(i32);
|
||||
|
||||
impl crate::fmt::Write for __LibCWriter {
|
||||
impl core::fmt::Write for __LibCWriter {
|
||||
#[inline]
|
||||
fn write_str(&mut self, s: &str) -> crate::fmt::Result {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
__libc_println(self.0, s)
|
||||
}
|
||||
}
|
||||
@ -122,24 +125,24 @@ impl __LibCWriter {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_fmt(&mut self, args: crate::fmt::Arguments) -> crate::fmt::Result {
|
||||
crate::fmt::Write::write_fmt(self, args)
|
||||
pub fn write_fmt(&mut self, args: core::fmt::Arguments) -> core::fmt::Result {
|
||||
core::fmt::Write::write_fmt(self, args)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_str(&mut self, s: &str) -> crate::fmt::Result {
|
||||
pub fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
__libc_println(self.0, s)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_nl(&mut self) -> crate::fmt::Result {
|
||||
pub fn write_nl(&mut self) -> core::fmt::Result {
|
||||
__libc_println(self.0, __LIBC_NEWLINE)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn __libc_println(handle: i32, msg: &str) -> crate::fmt::Result {
|
||||
pub fn __libc_println(handle: i32, msg: &str) -> core::fmt::Result {
|
||||
let msg = msg.as_bytes();
|
||||
|
||||
let mut written = 0;
|
||||
@ -159,7 +162,7 @@ unsafe fn libc_write(handle: i32, bytes: &[u8]) -> Option<usize> {
|
||||
usize::try_from(unsafe {
|
||||
libc::write(
|
||||
handle,
|
||||
bytes.as_ptr().cast::<crate::ffi::c_void>(),
|
||||
bytes.as_ptr().cast::<core::ffi::c_void>(),
|
||||
bytes.len(),
|
||||
)
|
||||
})
|
||||
@ -171,7 +174,7 @@ unsafe fn libc_write(handle: i32, bytes: &[u8]) -> Option<usize> {
|
||||
usize::try_from(unsafe {
|
||||
libc::write(
|
||||
handle,
|
||||
bytes.as_ptr().cast::<crate::ffi::c_void>(),
|
||||
bytes.as_ptr().cast::<core::ffi::c_void>(),
|
||||
libc::c_uint::try_from(bytes.len()).unwrap_or(libc::c_uint::MAX),
|
||||
)
|
||||
})
|
||||
|
@ -35,6 +35,8 @@
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
||||
extern crate core;
|
||||
|
||||
pub fn exit(code: u32) -> ! {
|
||||
unsafe { libc::exit(code as i32 as libc::c_int); }
|
||||
}
|
||||
|
222
tests/args.rs
Normal file
222
tests/args.rs
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
|
||||
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
*
|
||||
* This file is part of SPD.
|
||||
*
|
||||
* SPD is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* SPD is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with SPD. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and permission
|
||||
* notice:
|
||||
*
|
||||
* Copyright 2017 Douman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::env::args::Args;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Args, Debug)]
|
||||
struct Test4 {
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Args, Debug)]
|
||||
struct Test3 {
|
||||
paths: Vec<String>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Args, Debug)]
|
||||
struct Test2 {
|
||||
#[arg(short = "u")]
|
||||
u: bool,
|
||||
|
||||
paths: Vec<String>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
///my_exe 0.1.0
|
||||
///About my program
|
||||
///
|
||||
///About my program
|
||||
struct MyArgs {
|
||||
#[arg(short, long, required)]
|
||||
///Required argument
|
||||
required: u32,
|
||||
|
||||
#[arg(short, long)]
|
||||
///Optional argument
|
||||
optional: Option<u32>,
|
||||
|
||||
#[arg(short, long)]
|
||||
///About this flag
|
||||
flag: bool,
|
||||
|
||||
#[arg(long = "verbose")]
|
||||
///Verbose mode
|
||||
verbose: bool,
|
||||
|
||||
#[arg(short = "v", long = "velocity", default_value = "42")]
|
||||
///This is felocity. Default value is 42.
|
||||
speed: u32,
|
||||
|
||||
#[arg(short = "g", long = "gps")]
|
||||
///GPS coordinates.
|
||||
gps: Vec<u32>,
|
||||
|
||||
#[arg(required)]
|
||||
///To store path
|
||||
path: String,
|
||||
|
||||
#[arg(required)]
|
||||
///To store path 2
|
||||
path2: String,
|
||||
|
||||
///To store rest of paths
|
||||
remain_paths: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
///My subcommand
|
||||
enum BigSubArgs {
|
||||
///my arguments
|
||||
MyArgs(MyArgs),
|
||||
///test args
|
||||
Test(Test2),
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct BigArgs {
|
||||
#[arg(long = "verbose")]
|
||||
///Verbose mode
|
||||
verbose: bool,
|
||||
#[arg(sub)]
|
||||
cmd: BigSubArgs,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_error_on_missing_args() {
|
||||
let result = MyArgs::from_text("-f --verbose path1").unwrap_err();
|
||||
assert_eq!(result, arg::ParseError::RequiredArgMissing("required"));
|
||||
|
||||
let result = MyArgs::from_text("-f -r 5 --verbose path1").unwrap_err();
|
||||
assert_eq!(result, arg::ParseError::RequiredArgMissing("path2"));
|
||||
|
||||
let result = MyArgs::from_text("-f -r 5 --verbose").unwrap_err();
|
||||
assert_eq!(result, arg::ParseError::RequiredArgMissing("path"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_error_on_missing_flag_value() {
|
||||
let result = MyArgs::from_text("-f -r").unwrap_err();
|
||||
assert_eq!(result, arg::ParseError::MissingValue("required"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_error_on_invalid_flag_value() {
|
||||
let result = MyArgs::from_text("-f -r gg").unwrap_err();
|
||||
assert_eq!(result, arg::ParseError::InvalidFlagValue("required", "gg"));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_handle_all_flags() {
|
||||
let result = MyArgs::from_text("-f -r 5 --verbose -v 32 -g 1 --gps 55 path1 path2 rest1 rest2").unwrap();
|
||||
assert!(result.flag);
|
||||
assert!(result.verbose);
|
||||
assert_eq!(result.optional, None);
|
||||
assert_eq!(result.required, 5);
|
||||
assert_eq!(result.speed, 32);
|
||||
assert_eq!(result.gps, &[1, 55]);
|
||||
assert_eq!(result.path, "path1");
|
||||
assert_eq!(result.path2, "path2");
|
||||
assert_eq!(result.remain_paths, &["rest1", "rest2"]);
|
||||
|
||||
let result = MyArgs::from_text("-f -r 5 --verbose -o 13 -v 32 -g 1 --gps 55 path1 path2 rest1 rest2").unwrap();
|
||||
assert!(result.flag);
|
||||
assert!(result.verbose);
|
||||
assert_eq!(result.optional, Some(13));
|
||||
assert_eq!(result.required, 5);
|
||||
assert_eq!(result.speed, 32);
|
||||
assert_eq!(result.gps, &[1, 55]);
|
||||
assert_eq!(result.path, "path1");
|
||||
assert_eq!(result.path2, "path2");
|
||||
assert_eq!(result.remain_paths, &["rest1", "rest2"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_invalid_sub_command() {
|
||||
let result = BigArgs::from_text("--verbose my-invalid-args -f -r 5 --verbose -v 32 -g 1 --gps 55 path1 path2 rest1 rest2").unwrap_err();
|
||||
assert_eq!(result, arg::ParseKind::Top(arg::ParseError::RequiredArgMissing("cmd")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_sub_command_with_wrong_args() {
|
||||
let result = BigArgs::from_text("--verbose my-args -f -r 5 --verbose -v 32 -g 1 --gps lolka path1 path2 rest1 rest2").unwrap_err();
|
||||
assert_eq!(result, arg::ParseKind::Sub("my-args", arg::ParseError::InvalidFlagValue("gps", "lolka")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_handle_all_flags_as_sub_command() {
|
||||
let result = BigArgs::from_text("--verbose my-args -f -r 5 --verbose -v 32 -g 1 --gps 55 path1 path2 rest1 rest2").unwrap();
|
||||
assert!(result.verbose);
|
||||
let result = match result.cmd {
|
||||
BigSubArgs::MyArgs(args) => args,
|
||||
unexpected => panic!("invalid sub command result: {:?}", unexpected),
|
||||
};
|
||||
assert!(result.flag);
|
||||
assert!(result.verbose);
|
||||
assert_eq!(result.optional, None);
|
||||
assert_eq!(result.required, 5);
|
||||
assert_eq!(result.speed, 32);
|
||||
assert_eq!(result.gps, &[1, 55]);
|
||||
assert_eq!(result.path, "path1");
|
||||
assert_eq!(result.path2, "path2");
|
||||
assert_eq!(result.remain_paths, &["rest1", "rest2"]);
|
||||
|
||||
let result = BigArgs::from_text("--verbose test -u path1 path2 rest1 rest2").unwrap();
|
||||
assert!(result.verbose);
|
||||
let result = match result.cmd {
|
||||
BigSubArgs::Test(args) => args,
|
||||
unexpected => panic!("invalid sub command result: {:?}", unexpected),
|
||||
};
|
||||
assert!(result.u);
|
||||
assert_eq!(result.paths, &["path1", "path2", "rest1", "rest2"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_supply_default_value() {
|
||||
let result = MyArgs::from_text("-f -r 5 --verbose -g 1 --gps 55 path1 path2 rest1 rest2").unwrap();
|
||||
assert_eq!(result.speed, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shoukd_handle_dash() {
|
||||
let result = MyArgs::from_text("-f -r 5 --verbose -g 1 --gps 55 path1 path2 rest1 -").unwrap();
|
||||
assert_eq!(result.remain_paths[1], "-");
|
||||
}
|
||||
|
Reference in New Issue
Block a user