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

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