I'm trying to create a Rust macro that generates endpoints like "/api/v2/stats/<token>".
I thought it would be cool to make it like warp path does it: warp::path!("sum" / u32 / u32).
But in warp, they do not need to support token trees with dots, i.e. expressions, and retrieve their values...
What I got so far is this:
macro_rules! path {
() => {};
($next:tt $($tail:tt)*) => {{
println!(stringify!($next));
path!($($tail)*);
}};
}
fn main() {
struct Data {
event: String,
token: String,
}
let data = Data {
event: String::from("stats"),
token: String::from("a1b2c3d4"),
};
path!("/api/v2" / data.event / data.token)
}
playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=babf87265cc6060fc1695019e30e38bf
This shows what the macro is seeing:
"/api/v2"
/
data
.
event
/
data
.
token
I know token trees can be reinterpreted as expressions later, so there should be a way to keep tt's in tail, split slashes from "anything else", and get those as expressions to retrieve their values, but I'm not seeing how...
How could I make it return the String "/api/v2/stats/a1b2c3d4"?
More examples of inputs and expected outputs:
struct Conf<'a> {env: &'a str};
let conf = Conf { env: "dev" };
let subsystem = "stats";
path!("/"); // root: "/"
path!("/api/v1" / data.event / "results"); // "/api/v1/stats/results"
path!("/api/v2/errors" / conf.env / subsystem); // "/api/v2/errors/dev/stats"
EDIT: I kinda did it with expressions, which it not that expressive, more of a workaround, but it works:
macro_rules! path {
($($path:expr),+) => {{
let mut s = [$($path),+].into_iter().flat_map(|p| [p, "/"]).collect::<String>();
s.pop();
s
}}
}
Limitations: it only accepts commas, the path parts have to be &str so I have to manually reference them, and most of all, this could be better represented as a function, but is something to start with:
let result_url = path!("/api/v2", &data.event, &data.token);
Thank you!
You can use a tt muncher to achieve this:
macro_rules! path {
(#munch / ) => {
String::from("/")
};
(#munch / $part:literal $(/)* ) => {
format!("/{}", $part)
};
(#munch / $part:literal / $($tail:tt)* ) => {
format!("/{}{}", $part, path!(#munch / $($tail)*))
};
(#munch / $($parts:ident).+ $(/)* ) => {
format!("/{}", & $($parts).+)
};
(#munch / $($parts:ident).+ / $($tail:tt)* ) => {
format!("/{}{}", & $($parts).+, path!(#munch / $($tail)*))
};
(/ $($input:tt)*) => {
path!(#munch / $($input)*)
};
}
playground
Currently this produces nested format! calls. In order to avoid that you'll probably also need to use an accumulator. This kinda stuff interests me so I'm working on a version with an accumulator.
Edit: And here's the accumulator version
macro_rules! path {
(/) => {
String::from("/")
};
(/ $($input:tt)*) => {
path!(#munch { / $($input)* } => ())
};
(#munch { / $part:literal $(/)* } => ($($accum:expr),*)) => {
path!(#done ($( $accum, )* $part))
};
(#munch { / $part:literal / $($tail:tt)* } => ($($accum:expr),*)) => {
path!(#munch { / $($tail)* } => ($( $accum, )* $part ))
};
(#munch { / $($parts:ident).+ $(/)* } => ($($accum:expr),*)) => {
path!(#done ($( $accum, )* & $($parts).+ ))
};
(#munch { / $($parts:ident).+ / $($tail:tt)* } => ($($accum:expr),*)) => {
path!(#munch { / $($tail)* } => ($( $accum, )* & $($parts).+ ))
};
(#replace_expr $_t:tt => $sub:expr) => { $sub };
(#done ($($accum:expr),*)) => {
format!(
concat!($( path!(#replace_expr ($accum) => "/{}"), )*),
$( $accum, )*
)
};
}
playground
Edit2: per your request, another version which uses two accumulators to support a leading literal
macro_rules! path {
(/) => {
String::from("/")
};
(/ $($input:tt)*) => {
path!(#munch { / $($input)* } -> () : ())
};
($part:literal $(/)*) => {
String::from($part)
};
($part:literal $($input:tt)*) => {
path!(#munch { $($input)* } -> ("{}") : ($part))
};
(#munch { / $part:literal $(/)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
path!(#done ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* $part))
};
(#munch { / $part:literal / $($tail:tt)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
path!(#munch { / $($tail)* } -> ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* $part ))
};
(#munch { / $($parts:ident).+ $(/)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
path!(#done ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* & $($parts).+ ))
};
(#munch { / $($parts:ident).+ / $($tail:tt)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
path!(#munch { / $($tail)* } -> ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* & $($parts).+ ))
};
(#done ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
format!(
concat!($( $fmt_accum, )*),
$( $args_accum, )*
)
};
}
playground
Related
Here is the classic situation :
macro_rules! foo {
($a:tt $(b:tt)?) => {
do_something_with_a($a);
$(do_something_with_b($b);)?
}
}
But what if you want this:
macro_rules! foo {
($a:tt) => {
do_something_with_a($a);
do_something_when_b_is_absent();
}
($a:tt b:tt) => {
do_something_with_a($a);
do_something_with_b($b);
}
}
How to avoid code repetition here ?
Is there a way to do something like:
macro_rules! foo {
($a:tt $(b:tt)?) => {
do_something_with_a($a);
$(do_something_with_b($b);)?
${if(
eq(count(b), 0),
{do_something_when_b_is_absent();}
)}
}
}
My guess is no, but I could be surprised :)
You could do this using #internal to differentiate between the 2 cases,
or use a different macro for the other cases:
macro_rules! foo {
(#internal ) => {
do_something_when_b_is_absent();
};
(#internal $b:tt) => {
do_something_with_b($b);
};
($a:tt $($b:tt)?) => {
do_something_with_a($a);
foo!(#internal $($b)?);
};
}
It turns out, it is possible to write a macro that does the magic:
macro_rules! if_not {
(() $code:block) => {
$code
};
(($($target:tt)+) $code:block) => {};
}
Then you use it this way:
macro_rules! foo {
($a:tt $(b:tt)?) => {
do_something_with_a($a);
$(do_something_with_b($b);)?
if_not!(($(b)?) {
do_something_when_b_is_absent();
});
}
}
Currently, my code looks like this:
interface TestInterface<T extends string> {
data: {
[key in T]: () => void;
}
}
const testCommand: TestInterface<"fun1"> = {
data: {
fun1: () => {
// do something
},
}
};
testCommand.data.fun1();
I pass a string (in this example "fun1") as a generic to the testCommand function. In the data object, I can access this string/function "fun1".
My Question: Can I pass an array of strings as a generic to have a custom amount of functions in the data object (Maybe convert the data obj to an array)?
Example:
const testCommand: TestInterface<["fun1", "fun2", "fun3"]> = {
data: {
fun1: () => {
// do something
},
fun2: () => {
// do something
},
fun3: () => {
// do something
},
}
};
or:
const testCommand: TestInterface<["fun1", "fun2", "fun3"]> = {
data: [
{ name: "fun1", exec: () => {} },
{ name: "fun2", exec: () => {} },
{ name: "fun3", exec: () => {} },
]
};
Instead of an array, you can just pass in a union of the string literal types you want to support; the resulting mapped type will contain all of the keys:
const testCommand: TestInterface<"fun1" | "fun2" | "fun3"> = {
data: {
fun1: () => {
// do something
},
fun2: () => {
// do something
},
fun3: () => {
// do something
},
}
};
Indeed, this is very much like using the Record<K, V> utility type, where you pass in a union of key types for K and a value type for V, and get a type with properties of type V at all keys in K.
Playground link to code
Looking for help populating and using structs within a polars Dataframe/Series.
I know there is a JSON workaround to get structs into the df... but there should be a more straight forward solution rather than this "awkward" workaround?!
Once the structs made it into a df ... how do I get them out?
Here is some code I use to better understand how polars handles various data types:
use polars::prelude::*;
enum FriendType {
Goodfriend,
Badfriend,
}
struct MyStruct {
pub person: Option<String>,
pub relation: FriendType,
}
impl MyStruct {
fn new(person: &str, relation: FriendType) -> Self {
MyStruct {
person: Some(person.to_string()),
relation,
}
}
}
fn main() {
// HOW to get the MyStruct_col into a polars series/dataframe
let df = df![
"Integer_col" => [1,2,3,4],
"Float_col" => [1.1,0.3,9.6,4.2],
"String_col" => ["apple", "pear", "lemon", "plum"],
"values_nulls" => [Some(1), None, Some(3), Some(8)],
// "MyStruct_col" => [MyStruct::new("Peter",Badfriend),
// MyStruct::new("Mary",Goodfriend),
// MyStruct::new("Elon",Goodfriend),
// MyStruct::new("Joe",Badfriend),
// ],
]
.unwrap();
// HOW do I get them out again?
for each_column_name in df.get_column_names() {
let df_col = df.column(each_column_name).unwrap();
println!("\nFound TYPE: {}", df_col.dtype());
/*----------------------------INTEGER_COLUMN----------------------------------------------*/
if df_col.dtype() == &DataType::Int32 {
let df_col_chunked = df_col.i32().unwrap();
for each_element in df_col_chunked {
match each_element {
Some(_) => each_element,
None => continue,
};
let single_element = each_element.unwrap();
println!(
"Element: {:?} as String: {}",
single_element,
single_element.to_string()
)
}
}
/*----------------------------FLOAT64_COLUMN----------------------------------------------*/
if df_col.dtype() == &DataType::Float64 {
let df_col_chunked = df_col.f64().unwrap();
for each_element in df_col_chunked {
match each_element {
Some(_) => each_element,
None => continue,
};
let single_element = each_element.unwrap();
println!(
"Element: {} as String: {}",
single_element,
single_element.to_string()
)
}
};
/*----------------------------STRING_COLUMN----------------------------------------------*/
if df_col.dtype() == &DataType::Utf8 {
let df_col_chunked = df_col.utf8().unwrap();
for each_element in df_col_chunked {
match each_element {
Some(_) => each_element,
None => continue,
};
let single_element = each_element.unwrap();
println!(
"Element: {} as String: {}",
single_element,
single_element.to_string()
)
}
};
/*----------------------------STRUCT_COLUMN----------------------------------------------*/
// if (df_col.dtype() == &MyStruct) {
// let df_col_chunked = df_col.struct_().unwrap();
// for each_element in df_col_chunked {
// match each_element {
// Some(_) => each_element,
// None => continue,
// };
//
// let single_element = each_element.unwrap();
//
// println!(
// "Element: {} as String: {}",
// single_element,
// single_element.to_string()
// )
// }
// };
}
}
i need to fetch some data before render my template and i tryied 2 differents methods, but the both failed.
this is the first one with sync transaction:
if (loginData) {
loginGet(userData!.cpf, loginData.access_token)
.then((data: UserAzureInterface) => {
console.log(JSON.stringify(data));
dispatchUserAzureSuccess(data, dispatch);
})
.catch(_error => {});
} else {
navigation.navigate('Login');
}
return (
<>
<ProfileIndexTemplate onSubmit={onSubmit} userData={userData} />
</>
);
and this is the async transaction:
const getLoginAzure = async () => {
if (loginData) {
const loginAzureData = await loginGet(
userData!.cpf,
loginData.access_token,
);
console.log(JSON.stringify(loginAzureData));
dispatchUserAzureSuccess(loginAzureData, dispatch);
}
};
getLoginAzure();
return (
<>
<ProfileIndexTemplate onSubmit={onSubmit} userData={userData} />
</>
);
From profileIndexTemplate im trying to get my UserAzureInterface but i got undefined data, because my loginGet at the screen are not finish yet.
const loginAzure = useSelector(({ userAzure }: AppState) => userAzure.data);
console.log(`meu login azure: ${JSON.stringify(loginAzure)}`);
Can i see the error, but i cant figure how to solve that. any one can helps me?
i got it! to solve this problem this is what i did:
useEffect(() => {
console.log(`gettingAzure: ${userData!.cpf}`);
if (!loginData) {
navigation.navigate('Login');
} else {
loginGet(userData!.cpf, loginData!.access_token)
.then((data: UserAzureInterface) => {
console.log(JSON.stringify(data));
dispatchUserAzureSuccess(data, dispatch);
})
.catch(_error => {});
}
}, []);
if (azurePending || !azureData) {
return <LoadingModal />;
}
return (
<ProfileIndexTemplate
onSubmit={onSubmit}
userData={userData}
loginAzure={azureData}
/>
);
Can anyone help me in the implementation of nested set behavior https://github.com/creocoder/yii2-nested-sets with Yii2 Menu Widget?
Basically I want the hierarchical sidebar navigation menu like this http://www.surtex-instruments.co.uk/surgical/general-surgical-instruments
Thanks.
In your Model class define this functions:
public static function getTree($selected, $search)
{
$categories = Categories::find()->orderBy('lft')->asArray()->all();
$tree = [];
$index = 0;
foreach ($categories as $category) {
if ($category['parent_category_id'] === NULL) {
$tree[$index]['text'] = $category['category_' . Yii::$app->language];
if ($search) {
$tree[$index]['href'] = Url::to(['products', 'category' => $category['category_id'], 'search' => $search, 'string' => $category['category_' . Yii::$app->language]]);
} else {
$tree[$index]['href'] = Url::to(['products', 'category' => $category['category_id'], 'string' => $category['category_' . Yii::$app->language] ]);
}
$tree[$index]['id'] = $category['category_id'];
if ($selected) {
if ($selected['category_id'] == $category['category_id']) {
$tree[$index]['state']['selected'] = true;
}
if ($selected['lft'] >= $category['lft'] && $selected['rgt'] <= $category['rgt']) {
$tree[$index]['state']['expanded'] = true;
}
}
if ($category['lft'] + 1 != $category['rgt']) {
Categories::getNodes($tree[$index], $categories, $selected, $search);
}
$index++;
}
}
return $tree;
}
private static function getNodes(&$tree, $categories, $selected, $search)
{
$index = 0;
foreach ($categories as $category) {
if ($tree['id'] == $category['parent_category_id']) {
$tree['nodes'][$index]['text'] = $category['category_' . Yii::$app->language];
if ($search) {
$tree['nodes'][$index]['href'] = Url::to(['products', 'category' => $category['category_id'], 'search' => $search, 'string' => $category['category_' . Yii::$app->language]]);
} else {
$tree['nodes'][$index]['href'] = Url::to(['products', 'category' => $category['category_id'], 'string' => $category['category_' . Yii::$app->language]]);
}
$tree['nodes'][$index]['id'] = $category['category_id'];
if ($selected) {
if ($selected['category_id'] == $category['category_id']) {
$tree['nodes'][$index]['state']['selected'] = true;
}
if ($selected['lft'] >= $category['lft'] && $selected['rgt'] <= $category['rgt']) {
$tree['nodes'][$index]['state']['expanded'] = true;
}
}
if ($category['lft'] + 1 != $category['rgt']) {
Categories::getNodes($tree['nodes'][$index], $categories, $selected, $search);
}
$index++;
}
}
}
and use this extension execut/yii2-widget-bootstraptreeview
In the controller file get the menu like this:
public function actionProducts($category = false)
{
...
$data = Categories::getTree($category,'');
In your view file
<?php
...
use execut\widget\TreeView;
...
$onSelect = new JsExpression(<<<JS
function (undefined, item) {
window.location.href = item.href;
}
JS
);
?>
<?= TreeView::widget([
'data' => $data,
'template' => TreeView::TEMPLATE_SIMPLE,
'clientOptions' => [
'onNodeSelected' => $onSelect,
'onhoverColor' => "#fff",
'enableLinks' => true,
'borderColor' => '#fff',
'collapseIcon' => 'fa fa-angle-down',
'expandIcon' => 'fa fa-angle-right',
'levels' => 1,
'selectedBackColor' => '#fff',
'selectedColor' => '#2eaadc',
],
]); ?>