feat(builtins): use proc macro for builtins
This commit is contained in:
parent
bd23c3e40d
commit
23f69a33e5
7 changed files with 198 additions and 41 deletions
102
Cargo.lock
generated
102
Cargo.lock
generated
|
@ -33,18 +33,65 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"darling_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encode_unicode"
|
name = "encode_unicode"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.1"
|
version = "1.9.1"
|
||||||
|
@ -114,6 +161,18 @@ dependencies = [
|
||||||
"oyster_runtime",
|
"oyster_runtime",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oyster_builtin_proc"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"oyster_runtime",
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oyster_lineedit"
|
name = "oyster_lineedit"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
@ -132,24 +191,36 @@ version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"insta",
|
"insta",
|
||||||
"nix",
|
"nix",
|
||||||
|
"oyster_builtin_proc",
|
||||||
"oyster_parser",
|
"oyster_parser",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro-crate"
|
||||||
version = "1.0.40"
|
version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
|
checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"thiserror",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.20"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
|
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -210,10 +281,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3"
|
checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "strsim"
|
||||||
version = "1.0.98"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.102"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -250,6 +327,15 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.5.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
18
crates/oyster_builtin_proc/Cargo.toml
Normal file
18
crates/oyster_builtin_proc/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "oyster_builtin_proc"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
doctest = false
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
darling = "~0.14.1"
|
||||||
|
proc-macro-crate = "^1.2.1"
|
||||||
|
proc-macro2 = { version = "1.0.47", default-features = false }
|
||||||
|
quote = { version = "^1.0.21", default-features = false }
|
||||||
|
syn = { version = "^1.0.102", default-features = false, features = [ "full" ] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
oyster_runtime = { path = "../oyster_runtime" }
|
53
crates/oyster_builtin_proc/src/lib.rs
Normal file
53
crates/oyster_builtin_proc/src/lib.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use darling::FromMeta;
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::Span;
|
||||||
|
use proc_macro_crate::{crate_name, FoundCrate};
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse_macro_input, AttributeArgs, Ident, ItemFn, Signature};
|
||||||
|
|
||||||
|
#[derive(FromMeta)]
|
||||||
|
struct AttrArgs {
|
||||||
|
#[darling(default)]
|
||||||
|
description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn builtin(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
let attrs = parse_macro_input!(attr as AttributeArgs);
|
||||||
|
let ItemFn {
|
||||||
|
vis, sig, block, ..
|
||||||
|
} = parse_macro_input!(item as ItemFn);
|
||||||
|
let Signature {
|
||||||
|
ident,
|
||||||
|
fn_token,
|
||||||
|
inputs,
|
||||||
|
..
|
||||||
|
} = sig;
|
||||||
|
|
||||||
|
let name = ident.to_string();
|
||||||
|
let AttrArgs { description } = match AttrArgs::from_list(&attrs) {
|
||||||
|
Ok(args) => args,
|
||||||
|
Err(err) => return err.write_errors().into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let builtin = match crate_name("oyster_runtime").expect("oyster_runtime missing") {
|
||||||
|
FoundCrate::Itself => quote! { crate::builtins::Builtin },
|
||||||
|
FoundCrate::Name(name) => {
|
||||||
|
let ident = Ident::new(&name, Span::call_site());
|
||||||
|
quote! { #ident::builtins::Builtin }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TokenStream::from(quote! {
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
#vis static #ident: #builtin = {
|
||||||
|
#fn_token #ident(#inputs) #block
|
||||||
|
|
||||||
|
#builtin {
|
||||||
|
name: #name,
|
||||||
|
description: #description,
|
||||||
|
fun: #ident,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
10
crates/oyster_builtin_proc/tests/it/main.rs
Normal file
10
crates/oyster_builtin_proc/tests/it/main.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use oyster_builtin_proc::builtin;
|
||||||
|
use oyster_runtime::Shell;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normal_usage() {
|
||||||
|
#[builtin(description = "some text")]
|
||||||
|
fn test(_: &mut Shell, _: &[Cow<str>]) {}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
oyster_builtin_proc = { path = "../oyster_builtin_proc" }
|
||||||
oyster_parser = { path = "../oyster_parser" }
|
oyster_parser = { path = "../oyster_parser" }
|
||||||
thiserror = "1.0.35"
|
thiserror = "1.0.35"
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use std::{borrow::Cow, collections::HashMap};
|
use std::{borrow::Cow, collections::HashMap};
|
||||||
|
|
||||||
|
use oyster_builtin_proc::builtin;
|
||||||
|
|
||||||
use crate::Shell;
|
use crate::Shell;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -9,8 +11,8 @@ pub struct Builtin {
|
||||||
pub fun: fn(shell: &mut Shell, args: &[Cow<str>]),
|
pub fun: fn(shell: &mut Shell, args: &[Cow<str>]),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static HELP: Builtin = {
|
#[builtin(description = "prints help for different builtins")]
|
||||||
pub fn help(shell: &mut Shell, _args: &[Cow<str>]) {
|
fn help(shell: &mut Shell, _args: &[Cow<str>]) {
|
||||||
println!(
|
println!(
|
||||||
r"oyster help:
|
r"oyster help:
|
||||||
|
|
||||||
|
@ -20,14 +22,7 @@ These are the loaded builtins:"
|
||||||
for builtin in shell.builtins().iter() {
|
for builtin in shell.builtins().iter() {
|
||||||
println!(" {: <8} {}", builtin.name, builtin.description);
|
println!(" {: <8} {}", builtin.name, builtin.description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Builtin {
|
|
||||||
name: "help",
|
|
||||||
description: "prints help for different builtins",
|
|
||||||
fun: help,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Used to register and retrieve builtins.
|
/// Used to register and retrieve builtins.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -51,7 +46,7 @@ impl BuiltinMap {
|
||||||
|
|
||||||
/// Add default builtins.
|
/// Add default builtins.
|
||||||
pub fn add_defaults(&mut self) {
|
pub fn add_defaults(&mut self) {
|
||||||
self.add(HELP);
|
self.add(help);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
env,
|
env,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{BufRead, BufReader, Write},
|
io::{BufRead, BufReader, Write},
|
||||||
|
@ -13,12 +14,19 @@ use nix::{
|
||||||
sys,
|
sys,
|
||||||
unistd::{self, ForkResult},
|
unistd::{self, ForkResult},
|
||||||
};
|
};
|
||||||
use oyster_runtime::{builtins::Builtin, Shell};
|
use oyster_builtin_proc::builtin;
|
||||||
|
use oyster_runtime::Shell;
|
||||||
|
|
||||||
// TODO: test signal return codes
|
// TODO: test signal return codes
|
||||||
|
|
||||||
ioctl_write_int_bad!(tiocsctty, libc::TIOCSCTTY);
|
ioctl_write_int_bad!(tiocsctty, libc::TIOCSCTTY);
|
||||||
|
|
||||||
|
#[builtin(description = "test builtin")]
|
||||||
|
fn test_builtin(_: &mut Shell, _: &[Cow<str>]) {
|
||||||
|
// XXX: this is a workaround around libtest's use of io::set_output_capture
|
||||||
|
let _ = write!(std::io::stdout(), "this is a test\n");
|
||||||
|
}
|
||||||
|
|
||||||
/// Forks to redirect stdin, stderr, stdout, then run the commands.
|
/// Forks to redirect stdin, stderr, stdout, then run the commands.
|
||||||
/// Relies on inserting a NUL byte in the end, so there shouldn't be NUL in the output.
|
/// Relies on inserting a NUL byte in the end, so there shouldn't be NUL in the output.
|
||||||
fn collect_output<F>(mut f: F) -> String
|
fn collect_output<F>(mut f: F) -> String
|
||||||
|
@ -184,14 +192,7 @@ fn simple_builtin() {
|
||||||
|
|
||||||
let actual = collect_output(|| {
|
let actual = collect_output(|| {
|
||||||
let mut shell = Shell::new().unwrap();
|
let mut shell = Shell::new().unwrap();
|
||||||
shell.builtins_mut().add(Builtin {
|
shell.builtins_mut().add(test_builtin);
|
||||||
name: "test_builtin",
|
|
||||||
description: "test",
|
|
||||||
fun: |_, _| {
|
|
||||||
// XXX: this is a workaround around libtest's use of io::set_output_capture
|
|
||||||
let _ = write!(std::io::stdout(), "this is a test\n");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
shell.run(&ast).unwrap();
|
shell.run(&ast).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -220,14 +221,7 @@ fn builtin_redirection() {
|
||||||
|
|
||||||
let actual = collect_output(|| {
|
let actual = collect_output(|| {
|
||||||
let mut shell = Shell::new().unwrap();
|
let mut shell = Shell::new().unwrap();
|
||||||
shell.builtins_mut().add(Builtin {
|
shell.builtins_mut().add(test_builtin);
|
||||||
name: "test_builtin",
|
|
||||||
description: "test",
|
|
||||||
fun: |_, _| {
|
|
||||||
// XXX: this is a workaround around libtest's use of io::set_output_capture
|
|
||||||
let _ = write!(std::io::stdout(), "this is a test\n");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
shell.run(&ast).unwrap();
|
shell.run(&ast).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue