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
|
||||
|
||||
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 std::collections::HashMap;
|
||||
use badargs::arg;
|
||||
use badargs::CliArg;
|
||||
|
||||
#[derive(Default)]
|
||||
struct OutFile;
|
||||
arg!(OutFile: "output", "o" -> Option<String>);
|
||||
arg!(Force: "force", "f" -> bool);
|
||||
arg!(OLevel: "optimize" -> usize);
|
||||
|
||||
fn main() {
|
||||
let args = badargs::badargs(Template {
|
||||
options: {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(
|
||||
Box::new(OutFile),
|
||||
CliArgInfo {
|
||||
name: "output".to_string(),
|
||||
allow_short: true,
|
||||
takes_value: true,
|
||||
},
|
||||
);
|
||||
map
|
||||
},
|
||||
});
|
||||
let args = badargs::badargs::<(OutFile, (Force, OLevel))>().unwrap();
|
||||
|
||||
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};
|
||||
use std::collections::HashMap;
|
||||
mod macros;
|
||||
mod schema;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CliOption {
|
||||
Flag(bool),
|
||||
Value(String),
|
||||
use crate::parse::CliArgs;
|
||||
use crate::schema::{IntoSchema, SchemaKind};
|
||||
|
||||
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 {
|
||||
type Content;
|
||||
}
|
||||
|
||||
#[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,
|
||||
type Content: CliReturnValue;
|
||||
fn long() -> &'static str;
|
||||
fn short() -> Option<&'static str>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct BadArgs {
|
||||
options: HashMap<TypeId, CliArgInfo>,
|
||||
args: CliArgs,
|
||||
}
|
||||
|
||||
impl BadArgs {
|
||||
pub fn get<T: Default>(&self) -> Option<&CliArgInfo> {
|
||||
self.options.get(&T::type_id())
|
||||
pub fn get<T>(&self) -> &T::Content
|
||||
where
|
||||
T: CliArg,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn badargs(template: Template) -> BadArgs {
|
||||
let options = template
|
||||
.options
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key.type_id(), value))
|
||||
.collect();
|
||||
BadArgs { options }
|
||||
pub fn badargs<S>() -> Result<BadArgs>
|
||||
where
|
||||
S: IntoSchema,
|
||||
{
|
||||
let arg_schema = schema::parse_schema::<S>()?;
|
||||
|
||||
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