#proc-macro #tui #macro-derive #apps #productivity #struct #syntax

macro r3bl_rs_utils_macro

Internal support for a proc-macro library for reading attributes into structs when implementing custom derives. Used by workspace in https://crates.io/crates/r3bl_rs_utils.

26 releases

new 0.9.9 Apr 16, 2024
0.9.8 Dec 23, 2023
0.9.7 Oct 22, 2023
0.9.2 Apr 21, 2023
0.7.8 Jul 7, 2022

#488 in Procedural macros

Download history 16/week @ 2023-12-23 58/week @ 2023-12-30 76/week @ 2024-01-06 184/week @ 2024-01-13 1344/week @ 2024-01-20 2128/week @ 2024-01-27 2453/week @ 2024-02-03 2450/week @ 2024-02-10 3104/week @ 2024-02-17 3362/week @ 2024-02-24 3203/week @ 2024-03-02 3266/week @ 2024-03-09 2733/week @ 2024-03-16 2575/week @ 2024-03-23 2666/week @ 2024-03-30 2304/week @ 2024-04-06

10,730 downloads per month
Used in 7 crates (3 directly)

Apache-2.0

83KB
1K SLoC

Context

R3BL TUI library & suite of apps focused on developer productivity

We are working on building command line apps in Rust which have rich text user interfaces (TUI). We want to lean into the terminal as a place of productivity, and build all kinds of awesome apps for it.

  1. 🔮 Instead of just building one app, we are building a library to enable any kind of rich TUI development w/ a twist: taking concepts that work really well for the frontend mobile and web development world and re-imagining them for TUI & Rust.

    • Taking things like React, JSX, CSS, and Redux, but making everything async (they can be run in parallel & concurrent via Tokio).
    • Even the thread running the main event loop doesn't block since it is async.
    • Using proc macros to create DSLs to implement CSS & JSX.
  2. 🌎 We are building apps to enhance developer productivity & workflows.

    • The idea here is not to rebuild tmux in Rust (separate processes mux'd onto a single terminal window). Rather it is to build a set of integrated "apps" (or "tasks") that run in the same process that renders to one terminal window.
    • Inside of this terminal window, we can implement things like "app" switching, routing, tiling layout, stacking layout, etc. so that we can manage a lot of TUI apps (which are tightly integrated) that are running in the same process, in the same window. So you can imagine that all these "app"s have shared application state (that is in a Redux store). Each "app" may also have its own Redux store.
    • Here are some examples of the types of "app"s we want to build:
      1. multi user text editors w/ syntax highlighting
      2. integrations w/ github issues
      3. integrations w/ calendar, email, contacts APIs

r3bl_rs_utils_macro

This crate is related to the first thing that's described above. It provides lots of useful functionality to help you build TUI (text user interface) apps, along w/ general niceties & ergonomics that all Rustaceans 🦀 can enjoy 🎉:

Macros

Procedural

All the procedural macros are organized in 3 crates using an internal or core crate: the public crate, an internal or core crate, and the proc macro crate.

style! macro

Here's an example of the style! macro:

style! {
  id: "my_style",          /* Optional. */
  attrib: [dim, bold]      /* Optional. */
  padding: 10,             /* Optional. */
  color_fg: TuiColor::Blue, /* Optional. */
  color_bg: TuiColor::Red,  /* Optional. */
}

color_fg and color_bg can take any of the following:

  1. Color enum value.
  2. Rgb value.
  3. Variable holding either of the above.

Builder derive macro

This derive macro makes it easy to generate builders when annotating a struct or enum. It generates It has full support for generics. It can be used like this.

#[derive(Builder)]
struct Point<X, Y>
where
  X: std::fmt::Display + Clone,
  Y: std::fmt::Display + Clone,
{
  x: X,
  y: Y,
}

let my_pt: Point<i32, i32> = PointBuilder::new()
  .set_x(1 as i32)
  .set_y(2 as i32)
  .build();

assert_eq!(my_pt.x, 1);
assert_eq!(my_pt.y, 2);

make_struct_safe_to_share_and_mutate!

This function like macro (with custom syntax) makes it easy to manage shareability and interior mutability of a struct. We call this pattern the "manager" of "things").

🪄 You can read all about it here.

  1. This struct gets wrapped in a RwLock for thread safety.
  2. That is then wrapped inside an Arc so we can share it across threads.
  3. Additionally it works w/ Tokio so that it is totally async. It also fully supports generics and trait bounds w/ an optional where clause.

Here's a very simple usage:

make_struct_safe_to_share_and_mutate! {
  named MyMapManager<K, V>
  where K: Default + Send + Sync + 'static, V: Default + Send + Sync + 'static
  containing my_map
  of_type std::collections::HashMap<K, V>
}

Here's an async example.

#[tokio::test]
async fn test_custom_syntax_no_where_clause() {
  make_struct_safe_to_share_and_mutate! {
    named StringMap<K, V>
    // where is optional and is missing here.
    containing my_map
    of_type std::collections::HashMap<K, V>
  }

  let my_manager: StringMap<String, String> = StringMap::default();
  let locked_map = my_manager.my_map.read().await;
  assert_eq!(locked_map.len(), 0);
  drop(locked_map);
}

make_safe_async_fn_wrapper!

This function like macro (with custom syntax) makes it easy to share functions and lambdas that are async. They should be safe to share between threads and they should support either being invoked or spawned.

🪄 You can read all about how to write proc macros here.

  1. A struct is generated that wraps the given function or lambda in an Arc<RwLock<>> for thread safety and interior mutability.
  2. A get() method is generated which makes it possible to share this struct across threads.
  3. A from() method is generated which makes it easy to create this struct from a function or lambda.
  4. A spawn() method is generated which makes it possible to spawn the enclosed function or lambda asynchronously using Tokio.
  5. An invoke() method is generated which makes it possible to invoke the enclosed function or lambda synchronously.

Here's an example of how to use this macro.

use r3bl_rs_utils::make_safe_async_fn_wrapper;

make_safe_async_fn_wrapper! {
  named SafeMiddlewareFnWrapper<A>
  containing fn_mut
  of_type FnMut(A) -> Option<A>
}

Here's another example.

use r3bl_rs_utils::make_safe_async_fn_wrapper;

make_safe_async_fn_wrapper! {
  named SafeSubscriberFnWrapper<S>
  containing fn_mut
  of_type FnMut(S) -> ()
}

Other crates that depend on this

This crate is a dependency of r3bl_rs_utils crate (the "main" library).

Issues, comments, feedback, and PRs

Please report any issues to the issue tracker. And if you have any feature requests, feel free to add them there too 👍.

Dependencies

~10–20MB
~155K SLoC