aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock45
-rw-r--r--Cargo.toml1
-rw-r--r--pish_derive/.gitignore1
-rw-r--r--pish_derive/Cargo.lock47
-rw-r--r--pish_derive/Cargo.toml12
-rw-r--r--pish_derive/src/lib.rs96
-rw-r--r--src/run/builtin.rs48
-rw-r--r--src/run/mod.rs2
8 files changed, 252 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b8af854..1c8a615 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -29,17 +29,45 @@ name = "pish"
version = "0.3.0"
dependencies = [
"libc",
+ "pish_derive",
"sqlite",
"termios",
]
[[package]]
+name = "pish_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -74,6 +102,17 @@ dependencies = [
]
[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
name = "termios"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -81,3 +120,9 @@ checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
dependencies = [
"libc",
]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/Cargo.toml b/Cargo.toml
index d29df84..3707d2a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,3 +9,4 @@ license-file = "LICENSE"
libc = "0.2.182"
sqlite = "0.37.0"
termios = "0.3"
+pish_derive = { path = "./pish_derive" }
diff --git a/pish_derive/.gitignore b/pish_derive/.gitignore
new file mode 100644
index 0000000..c41cc9e
--- /dev/null
+++ b/pish_derive/.gitignore
@@ -0,0 +1 @@
+/target \ No newline at end of file
diff --git a/pish_derive/Cargo.lock b/pish_derive/Cargo.lock
new file mode 100644
index 0000000..af838d6
--- /dev/null
+++ b/pish_derive/Cargo.lock
@@ -0,0 +1,47 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "pish_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/pish_derive/Cargo.toml b/pish_derive/Cargo.toml
new file mode 100644
index 0000000..11fea7e
--- /dev/null
+++ b/pish_derive/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "pish_derive"
+version = "0.1.0"
+edition = "2024"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1.0.106"
+quote = "1.0.45"
+syn = "2.0.117"
diff --git a/pish_derive/src/lib.rs b/pish_derive/src/lib.rs
new file mode 100644
index 0000000..2ac98f7
--- /dev/null
+++ b/pish_derive/src/lib.rs
@@ -0,0 +1,96 @@
+use proc_macro::{Literal, TokenStream};
+use quote::{ToTokens, quote};
+use syn::{Data, DeriveInput, Fields, parse_macro_input};
+
+#[proc_macro_derive(FromArgs)]
+pub fn derive_cli(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+
+ let name = input.ident;
+
+ let fields = match input.data {
+ Data::Struct(data) => data.fields,
+ _ => panic!("Cli can only be derived for structs"),
+ };
+
+ let mut field_parsers_init = Vec::new();
+ let mut field_parsers = Vec::new();
+ let mut field_parsers_post = Vec::new();
+ let mut field_names = Vec::new();
+
+ if let Fields::Named(fields_named) = fields {
+ for field in fields_named.named {
+ let ident = field.ident.unwrap();
+ let name_str = ident.to_string();
+
+ let mut long_name = b"--".to_vec();
+ long_name.extend_from_slice(name_str.as_bytes());
+ let long_name = proc_macro2::Literal::byte_string(&long_name);
+
+ field_names.push(ident.clone());
+
+ // initialization
+ field_parsers_init.push(quote! {
+ let mut #ident = None;
+ });
+
+ let is_bool = field.ty.to_token_stream().to_string() == String::from("bool");
+ let is_option = field.ty.to_token_stream().to_string().starts_with("Option"); // bad bad detection
+
+ // in the loop
+ if is_bool {
+ field_parsers.push(quote! {
+ if arg == #long_name {
+ #ident = Some(true);
+ continue;
+ }
+ });
+ } else {
+ field_parsers.push(quote! {
+ if arg == #long_name {
+ let Some(val) = iter.next() else {
+ return Err(ArgParseError::MissingArgValue(#name_str));
+ };
+ match String::from_utf8_lossy(val).parse() {
+ Ok(parsed) => {
+ #ident = Some(parsed);
+ continue;
+ }
+ Err(err) => {
+ return Err(ArgParseError::ArgValueParseError(#name_str, format!("{err:?}")));
+ }
+ }
+ }
+ });
+ }
+
+ // after loop
+ if is_bool {
+ field_parsers_post.push(quote!{ let #ident = #ident.unwrap_or(false); });
+ } else if !is_option {
+ field_parsers_post.push(quote!{ let Some(#ident) = #ident else { return Err(ArgParseError::MissingArg(#name_str)) }; });
+ }
+ }
+ }
+
+ let expanded = quote! {
+ impl ArgParse for #name {
+ fn parse<'a>(args: &'a [BString]) -> std::result::Result<Self, ArgParseError<'a>> {
+ let mut iter = args.iter().skip(1);
+
+ #(#field_parsers_init)*
+
+ while let Some(arg) = iter.next() {
+ #(#field_parsers)*;
+ return Err(ArgParseError::LeftoverArg(arg));
+ }
+
+ #(#field_parsers_post)*
+
+ Ok(Self { #( #field_names ),* })
+ }
+ }
+ };
+
+ TokenStream::from(expanded)
+}
diff --git a/src/run/builtin.rs b/src/run/builtin.rs
index c9456cd..366aaa5 100644
--- a/src/run/builtin.rs
+++ b/src/run/builtin.rs
@@ -3,10 +3,49 @@
use std::sync::{Arc, Mutex};
use std::{env::*, fs::OpenOptions, path::PathBuf};
+use pish_derive::FromArgs;
+
use super::{Builtin, BuiltinError as Error, BuiltinResult as Result};
use crate::parse::CmdDisplay;
use crate::*;
+pub enum ArgParseError<'a> {
+ LeftoverArg(&'a [u8]),
+ MissingArg(&'static str),
+ MissingArgValue(&'static str),
+ ArgValueParseError(&'static str, String),
+}
+
+pub trait ArgParse: Sized {
+ fn parse<'a>(args: &'a [BString]) -> std::result::Result<Self, ArgParseError<'a>>;
+}
+
+fn args<T: ArgParse>(args: &[BString], w: &mut dyn Write) -> std::result::Result<T, Error> {
+ let err = match T::parse(args) {
+ Ok(t) => return Ok(t),
+ Err(e) => e,
+ };
+
+ match err {
+ ArgParseError::LeftoverArg(items) => {
+ w.write_all(b"leftover argument: ")?;
+ w.write_all(items)?;
+ w.write_all(b"\n")?;
+ }
+ ArgParseError::MissingArg(arg) => {
+ write!(w, "argument `{arg}` is missing\n")?;
+ }
+ ArgParseError::MissingArgValue(arg) => {
+ write!(w, "argument `{arg}` is missing its value\n")?;
+ }
+ ArgParseError::ArgValueParseError(arg, err) => {
+ write!(w, "failed to parse value of `{arg}`: {err}")?;
+ }
+ }
+
+ Err(Error::Exit(-2))
+}
+
pub struct cd;
impl Builtin for cd {
fn name(&self) -> &str {
@@ -206,6 +245,15 @@ impl Builtin for builtins {
}
}
+#[derive(FromArgs)]
+struct HistoryArgs {
+ local: bool,
+
+ here: bool,
+ at: Option<PathBuf>,
+ // TODO: temporal control, i.e. before & after
+}
+
pub struct history;
impl Builtin for history {
fn name(&self) -> &str {
diff --git a/src/run/mod.rs b/src/run/mod.rs
index 0b9aef8..d386590 100644
--- a/src/run/mod.rs
+++ b/src/run/mod.rs
@@ -155,6 +155,7 @@ impl Executor {
Ok(Err(e)) => match e {
BuiltinError::IO(_) => code = -1,
BuiltinError::Exit(c) => code = c,
+ BuiltinError::ParseError(_) => code = -2,
},
Err(_) => code = 127,
}
@@ -290,6 +291,7 @@ pub fn run(se: Arc<Mutex<Session>>, cmd: Vec<u8>) {
#[derive(Debug)]
pub enum BuiltinError {
IO(std::io::Error),
+ ParseError(&'static str),
Exit(i32),
}