oh shit this might actually be quite usable

This commit is contained in:
nora 2021-09-24 22:17:30 +02:00
parent c126dda2df
commit cdb67f070c
5 changed files with 221 additions and 48 deletions

View file

@ -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

View file

@ -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>();
} }

View file

@ -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
View 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
View 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 })
}
}