mirror of
https://github.com/Noratrieb/badargs.git
synced 2026-01-14 19:55:08 +01:00
oh shit this might actually be quite usable
This commit is contained in:
parent
c126dda2df
commit
cdb67f070c
5 changed files with 221 additions and 48 deletions
|
|
@ -3,4 +3,6 @@ A Rust fully type-safe argument parser without proc-macros
|
||||||
|
|
||||||
Usability comes after that
|
Usability comes after that
|
||||||
|
|
||||||
oh and it's also not even close to being usable in any way at all so there's that
|
oh and it's also not even close to being usable in any way at all so there's that
|
||||||
|
|
||||||
|
oh and also 0 dependencies btw
|
||||||
|
|
@ -1,24 +1,14 @@
|
||||||
use badargs::{CliArg, CliArgInfo, Template};
|
use badargs::arg;
|
||||||
use std::collections::HashMap;
|
use badargs::CliArg;
|
||||||
|
|
||||||
#[derive(Default)]
|
arg!(OutFile: "output", "o" -> Option<String>);
|
||||||
struct OutFile;
|
arg!(Force: "force", "f" -> bool);
|
||||||
|
arg!(OLevel: "optimize" -> usize);
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = badargs::badargs(Template {
|
let args = badargs::badargs::<(OutFile, (Force, OLevel))>().unwrap();
|
||||||
options: {
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
map.insert(
|
|
||||||
Box::new(OutFile),
|
|
||||||
CliArgInfo {
|
|
||||||
name: "output".to_string(),
|
|
||||||
allow_short: true,
|
|
||||||
takes_value: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
map
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let outfile = args.get::<OutFile>();
|
let _outfile = args.get::<OutFile>();
|
||||||
|
let _force = args.get::<Force>();
|
||||||
|
let _o_level = args.get::<OLevel>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
141
src/lib.rs
141
src/lib.rs
|
|
@ -1,43 +1,128 @@
|
||||||
use std::any::{Any, TypeId};
|
mod macros;
|
||||||
use std::collections::HashMap;
|
mod schema;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
use crate::parse::CliArgs;
|
||||||
pub enum CliOption {
|
use crate::schema::{IntoSchema, SchemaKind};
|
||||||
Flag(bool),
|
|
||||||
Value(String),
|
pub use error::ArgError;
|
||||||
|
pub use macros::*;
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, ArgError>;
|
||||||
|
|
||||||
|
pub trait CliReturnValue: sealed::SealedCliReturnValue {
|
||||||
|
fn kind() -> schema::SchemaKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_cli_return {
|
||||||
|
($(for $ty:ty => $type:ident);+) => {$(
|
||||||
|
impl CliReturnValue for $ty {
|
||||||
|
fn kind() -> SchemaKind {
|
||||||
|
SchemaKind::$type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_cli_return!(
|
||||||
|
for String => String;
|
||||||
|
for Option<String> => OptionString;
|
||||||
|
for bool => Bool;
|
||||||
|
for isize => INum;
|
||||||
|
for usize => UNum
|
||||||
|
);
|
||||||
|
|
||||||
|
mod sealed {
|
||||||
|
pub trait SealedCliReturnValue {}
|
||||||
|
macro_rules! impl_ {
|
||||||
|
($($name:ty),+) => {$(impl SealedCliReturnValue for $name{})+};
|
||||||
|
}
|
||||||
|
impl_!(String, Option<String>, bool, usize, isize);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CliArg {
|
pub trait CliArg {
|
||||||
type Content;
|
type Content: CliReturnValue;
|
||||||
}
|
fn long() -> &'static str;
|
||||||
|
fn short() -> Option<&'static str>;
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct Template {
|
|
||||||
pub options: HashMap<Box<dyn Any>, CliArgInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CliArgInfo {
|
|
||||||
pub name: String,
|
|
||||||
pub allow_short: bool,
|
|
||||||
pub takes_value: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct BadArgs {
|
pub struct BadArgs {
|
||||||
options: HashMap<TypeId, CliArgInfo>,
|
args: CliArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BadArgs {
|
impl BadArgs {
|
||||||
pub fn get<T: Default>(&self) -> Option<&CliArgInfo> {
|
pub fn get<T>(&self) -> &T::Content
|
||||||
self.options.get(&T::type_id())
|
where
|
||||||
|
T: CliArg,
|
||||||
|
{
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn badargs(template: Template) -> BadArgs {
|
pub fn badargs<S>() -> Result<BadArgs>
|
||||||
let options = template
|
where
|
||||||
.options
|
S: IntoSchema,
|
||||||
.into_iter()
|
{
|
||||||
.map(|(key, value)| (key.type_id(), value))
|
let arg_schema = schema::parse_schema::<S>()?;
|
||||||
.collect();
|
|
||||||
BadArgs { options }
|
let args = CliArgs::from_args(arg_schema, std::env::args_os())?;
|
||||||
|
|
||||||
|
Ok(BadArgs { args })
|
||||||
|
}
|
||||||
|
|
||||||
|
mod error {
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ArgError {
|
||||||
|
InvalidUtf8,
|
||||||
|
NameAlreadyExists(&'static str),
|
||||||
|
InvalidSchema(String),
|
||||||
|
IdkYet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod parse {
|
||||||
|
use super::Result;
|
||||||
|
use crate::schema::Schema;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct CliArgs {
|
||||||
|
pub isize: HashMap<&'static str, isize>,
|
||||||
|
pub usize: HashMap<&'static str, isize>,
|
||||||
|
pub string: HashMap<&'static str, String>,
|
||||||
|
pub option_string: HashMap<&'static str, Option<String>>,
|
||||||
|
pub bool: HashMap<&'static str, bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CliArgs {
|
||||||
|
pub fn from_args(_schema: Schema, args: impl Iterator<Item = OsString>) -> Result<Self> {
|
||||||
|
let mut result = Self::default();
|
||||||
|
let mut args = args;
|
||||||
|
while let Some(_arg) = args.next() {}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::CliArg;
|
||||||
|
|
||||||
|
struct OutFile;
|
||||||
|
impl CliArg for OutFile {
|
||||||
|
type Content = Option<String>;
|
||||||
|
|
||||||
|
fn long() -> &'static str {
|
||||||
|
"output"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short() -> Option<&'static str> {
|
||||||
|
Some("o")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_single_schema() {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
src/macros.rs
Normal file
25
src/macros.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! arg {
|
||||||
|
($name:ident: $long:literal, $short:literal -> $result:ty) => {
|
||||||
|
arg!(@$name: ($long, ::std::option::Option::Some($short)) -> $result);
|
||||||
|
};
|
||||||
|
($name:ident: $long:literal -> $result:ty) => {
|
||||||
|
arg!(@$name: ($long, ::std::option::Option::None) -> $result);
|
||||||
|
};
|
||||||
|
(@$name:ident: ($long:literal, $short:expr) -> $result:ty) => {
|
||||||
|
#[derive(Default)]
|
||||||
|
struct $name;
|
||||||
|
|
||||||
|
impl ::badargs::CliArg for $name {
|
||||||
|
type Content = $result;
|
||||||
|
|
||||||
|
fn long() -> &'static str {
|
||||||
|
$long
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short() -> Option<&'static str> {
|
||||||
|
$short
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
71
src/schema.rs
Normal file
71
src/schema.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use super::Result;
|
||||||
|
use crate::{ArgError, CliArg, CliReturnValue};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum SchemaKind {
|
||||||
|
String,
|
||||||
|
OptionString,
|
||||||
|
Bool,
|
||||||
|
INum,
|
||||||
|
UNum,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct SchemaCommand {
|
||||||
|
short: Option<&'static str>,
|
||||||
|
kind: SchemaKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
||||||
|
pub struct Schema {
|
||||||
|
commands: HashMap<&'static str, SchemaCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Schema {
|
||||||
|
pub fn add_command(&mut self, name: &'static str, command: SchemaCommand) -> Result<()> {
|
||||||
|
if let Some(_) = self.commands.insert(name, command) {
|
||||||
|
Err(ArgError::NameAlreadyExists(name))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoSchema {
|
||||||
|
fn add_schema(schema: &mut Schema) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow using multiple schema values, these tuples can be nested :D
|
||||||
|
impl<S1, S2> IntoSchema for (S1, S2)
|
||||||
|
where
|
||||||
|
S1: IntoSchema,
|
||||||
|
S2: IntoSchema,
|
||||||
|
{
|
||||||
|
fn add_schema(schema: &mut Schema) -> Result<()> {
|
||||||
|
S1::add_schema(schema)?;
|
||||||
|
S2::add_schema(schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_schema<S>() -> Result<Schema>
|
||||||
|
where
|
||||||
|
S: IntoSchema,
|
||||||
|
{
|
||||||
|
let mut schema = Schema::default();
|
||||||
|
S::add_schema(&mut schema)?;
|
||||||
|
Ok(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create the Schema from the CliArg type
|
||||||
|
impl<T> IntoSchema for T
|
||||||
|
where
|
||||||
|
T: CliArg,
|
||||||
|
{
|
||||||
|
fn add_schema(schema: &mut Schema) -> Result<()> {
|
||||||
|
let kind = T::Content::kind();
|
||||||
|
let name = T::long();
|
||||||
|
let short = T::short();
|
||||||
|
schema.add_command(name, SchemaCommand { short, kind })
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue