Detailed changes
@@ -12273,6 +12273,7 @@ dependencies = [
"story",
"strum 0.25.0",
"theme",
+ "ui_macros",
"windows 0.58.0",
]
@@ -12287,6 +12288,16 @@ dependencies = [
"ui",
]
+[[package]]
+name = "ui_macros"
+version = "0.1.0"
+dependencies = [
+ "convert_case 0.6.0",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "unicase"
version = "2.7.0"
@@ -118,6 +118,7 @@ members = [
"crates/title_bar",
"crates/ui",
"crates/ui_input",
+ "crates/ui_macros",
"crates/util",
"crates/vcs_menu",
"crates/vim",
@@ -292,6 +293,7 @@ time_format = { path = "crates/time_format" }
title_bar = { path = "crates/title_bar" }
ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" }
+ui_macros = { path = "crates/ui_macros" }
util = { path = "crates/util" }
vcs_menu = { path = "crates/vcs_menu" }
vim = { path = "crates/vim" }
@@ -333,6 +335,7 @@ chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
clickhouse = "0.11.6"
cocoa = "0.26"
+convert_case = "0.6.0"
core-foundation = "0.9.3"
core-foundation-sys = "0.8.6"
ctor = "0.2.6"
@@ -35,7 +35,7 @@ chrono.workspace = true
client.workspace = true
clock.workspace = true
collections.workspace = true
-convert_case = "0.6.0"
+convert_case.workspace = true
db.workspace = true
emojis.workspace = true
file_icons.workspace = true
@@ -23,6 +23,7 @@ smallvec.workspace = true
story = { workspace = true, optional = true }
strum = { workspace = true, features = ["derive"] }
theme.workspace = true
+ui_macros.workspace = true
[target.'cfg(windows)'.dependencies]
windows.workspace = true
@@ -0,0 +1,33 @@
+#[cfg(test)]
+mod tests {
+ use strum::EnumString;
+ use ui_macros::{path_str, DerivePathStr};
+
+ #[test]
+ fn test_derive_path_str_with_prefix() {
+ #[derive(Debug, EnumString, DerivePathStr)]
+ #[strum(serialize_all = "snake_case")]
+ #[path_str(prefix = "test_prefix")]
+ enum MyEnum {
+ FooBar,
+ Baz,
+ }
+
+ assert_eq!(MyEnum::FooBar.path(), "test_prefix/foo_bar");
+ assert_eq!(MyEnum::Baz.path(), "test_prefix/baz");
+ }
+
+ #[test]
+ fn test_derive_path_str_with_prefix_and_suffix() {
+ #[derive(Debug, EnumString, DerivePathStr)]
+ #[strum(serialize_all = "snake_case")]
+ #[path_str(prefix = "test_prefix", suffix = ".txt")]
+ enum MyEnum {
+ FooBar,
+ Baz,
+ }
+
+ assert_eq!(MyEnum::FooBar.path(), "test_prefix/foo_bar.txt");
+ assert_eq!(MyEnum::Baz.path(), "test_prefix/baz.txt");
+ }
+}
@@ -8,6 +8,7 @@ mod components;
mod disableable;
mod fixed;
mod key_bindings;
+mod path_str;
pub mod prelude;
mod selectable;
mod styled_ext;
@@ -0,0 +1,19 @@
+[package]
+name = "ui_macros"
+version = "0.1.0"
+edition = "2021"
+publish = false
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/ui_macros.rs"
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1.0.66"
+quote = "1.0.9"
+syn = { version = "1.0.72", features = ["full", "extra-traits"] }
+convert_case.workspace = true
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -0,0 +1,105 @@
+use convert_case::{Case, Casing};
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, Attribute, Data, DeriveInput, Lit, Meta, NestedMeta};
+
+pub fn derive_path_str(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ let name = &input.ident;
+
+ let prefix = get_attr_value(&input.attrs, "prefix").expect("prefix attribute is required");
+ let suffix = get_attr_value(&input.attrs, "suffix").unwrap_or_else(|| "".to_string());
+
+ let serialize_all = get_strum_serialize_all(&input.attrs);
+ let path_str_impl = impl_path_str(name, &input.data, &prefix, &suffix, serialize_all);
+
+ let expanded = quote! {
+ impl #name {
+ pub fn path(&self) -> &'static str {
+ #path_str_impl
+ }
+ }
+ };
+
+ TokenStream::from(expanded)
+}
+
+fn impl_path_str(
+ name: &syn::Ident,
+ data: &Data,
+ prefix: &str,
+ suffix: &str,
+ serialize_all: Option<String>,
+) -> proc_macro2::TokenStream {
+ match *data {
+ Data::Enum(ref data) => {
+ let match_arms = data.variants.iter().map(|variant| {
+ let ident = &variant.ident;
+ let variant_name = if let Some(ref case) = serialize_all {
+ match case.as_str() {
+ "snake_case" => ident.to_string().to_case(Case::Snake),
+ "lowercase" => ident.to_string().to_lowercase(),
+ _ => ident.to_string(),
+ }
+ } else {
+ ident.to_string()
+ };
+ let path = format!("{}/{}{}", prefix, variant_name, suffix);
+ quote! {
+ #name::#ident => #path,
+ }
+ });
+
+ quote! {
+ match self {
+ #(#match_arms)*
+ }
+ }
+ }
+ _ => panic!("DerivePathStr only supports enums"),
+ }
+}
+
+fn get_strum_serialize_all(attrs: &[Attribute]) -> Option<String> {
+ attrs
+ .iter()
+ .filter(|attr| attr.path.is_ident("strum"))
+ .find_map(|attr| {
+ if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
+ meta_list.nested.iter().find_map(|nested_meta| {
+ if let NestedMeta::Meta(Meta::NameValue(name_value)) = nested_meta {
+ if name_value.path.is_ident("serialize_all") {
+ if let Lit::Str(lit_str) = &name_value.lit {
+ return Some(lit_str.value());
+ }
+ }
+ }
+ None
+ })
+ } else {
+ None
+ }
+ })
+}
+
+fn get_attr_value(attrs: &[Attribute], key: &str) -> Option<String> {
+ attrs
+ .iter()
+ .filter(|attr| attr.path.is_ident("path_str"))
+ .find_map(|attr| {
+ if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
+ meta_list.nested.iter().find_map(|nested_meta| {
+ if let NestedMeta::Meta(Meta::NameValue(name_value)) = nested_meta {
+ if name_value.path.is_ident(key) {
+ if let Lit::Str(lit_str) = &name_value.lit {
+ return Some(lit_str.value());
+ }
+ }
+ }
+ None
+ })
+ } else {
+ None
+ }
+ })
+}
@@ -0,0 +1,53 @@
+mod derive_path_str;
+
+use proc_macro::TokenStream;
+
+/// Derives the `path` method for an enum.
+///
+/// This macro generates a `path` method for each variant of the enum, which returns a string
+/// representation of the enum variant's path. The path is constructed using a prefix and
+/// optionally a suffix, which are specified using attributes.
+///
+/// # Attributes
+///
+/// - `#[path_str(prefix = "...")]`: Required. Specifies the prefix for all paths.
+/// - `#[path_str(suffix = "...")]`: Optional. Specifies a suffix for all paths.
+/// - `#[strum(serialize_all = "...")]`: Optional. Specifies the case conversion for variant names.
+///
+/// # Example
+///
+/// ```
+/// use strum::EnumString;
+/// use ui_macros::{path_str, DerivePathStr};
+///
+/// #[derive(EnumString, DerivePathStr)]
+/// #[path_str(prefix = "my_prefix", suffix = ".txt")]
+/// #[strum(serialize_all = "snake_case")]
+/// enum MyEnum {
+/// VariantOne,
+/// VariantTwo,
+/// }
+///
+/// // These assertions would work if we could instantiate the enum
+/// // assert_eq!(MyEnum::VariantOne.path(), "my_prefix/variant_one.txt");
+/// // assert_eq!(MyEnum::VariantTwo.path(), "my_prefix/variant_two.txt");
+/// ```
+///
+/// # Panics
+///
+/// This macro will panic if used on anything other than an enum.
+#[proc_macro_derive(DerivePathStr, attributes(path_str))]
+pub fn derive_path_str(input: TokenStream) -> TokenStream {
+ derive_path_str::derive_path_str(input)
+}
+
+/// A marker attribute for use with `DerivePathStr`.
+///
+/// This attribute is used to specify the prefix and suffix for the `path` method
+/// generated by `DerivePathStr`. It doesn't modify the input and is only used as a
+/// marker for the derive macro.
+#[proc_macro_attribute]
+pub fn path_str(_args: TokenStream, input: TokenStream) -> TokenStream {
+ // This attribute doesn't modify the input, it's just a marker
+ input
+}