Repository: zhiburt/tabled (language: rust)

Issue:
Feature request: add `#[tabled(crate = ...)]` to allow re-exporting the library
# Problem 
I've run into the following issue while developing a `macro_rules!` macro that auto-implements `Tabled` for a struct:
![image](https://github.com/zhiburt/tabled/assets/98741738/ff699fbd-443b-410e-ae7a-590c55b71a88)
`cargo clippy` output:
```
 1  error[E0433]: failed to resolve: could not find `tabled` in the list of imported crates
```

This happens because there are lines like this in the `derive` implementation: https://github.com/zhiburt/tabled/blob/master/tabled_derive/src/lib.rs#L48

Hence, crates that use my macro will be forced to import both my crate and the `tabled` crate, which is not great ergonomics.

# Request
Add a `#[tabled(crate = ...)]` that takes as argument the path of the `tabled` crate. This will allow crates to re-export `tabled` and let users use their macros without explicitly importing `tabled`.

Basically the same as serde's `#[serde(crate = "...")]` [attribute](https://serde.rs/container-attrs.html#crate).

I have some experience implementing macros like this and can do so for `tabled` if the community/maintainers agree with the addition.


Discussion / hints:
Hi there

Interesting issue.

If you wanna try you can do it;
But I can do it myself if you wish, let me know if so.

________________

I guess you'll need to update the struct and use it in the trait emit.

https://github.com/zhiburt/tabled/blob/fa06ecfb180649fef55141cae8dfd8db11a8a2c5/tabled_derive/src/attributes.rs#L77

Take care



> Hi there
> 
> Interesting issue.
> 
> If you wanna try you can do it; But I can do it myself if you wish, let me know if so.
> 
> I guess you'll need to update the struct and use it in the trait emit.
> 
> https://github.com/zhiburt/tabled/blob/fa06ecfb180649fef55141cae8dfd8db11a8a2c5/tabled_derive/src/attributes.rs#L77
> 
> Take care

@zhiburt If you wanna have a stab at it first, I think I'll only have time next week for it. Thanks for the tip btw!

Fix patch:
diff --git a/tabled/Cargo.toml b/tabled/Cargo.toml
index d39a0eea..e8addc24 100644
--- a/tabled/Cargo.toml
+++ b/tabled/Cargo.toml
@@ -69,6 +69,11 @@ name = "display_with"
 path = "examples/derive/display_with.rs"
 required-features = ["derive"]
 
+[[example]]
+name = "crate_override"
+path = "examples/derive/crate_override.rs"
+required-features = ["derive"]
+
 [[example]]
 name = "table"
 path = "examples/table.rs"
diff --git a/tabled/examples/derive/crate_override.rs b/tabled/examples/derive/crate_override.rs
new file mode 100644
index 00000000..ca37528e
--- /dev/null
+++ b/tabled/examples/derive/crate_override.rs
@@ -0,0 +1,39 @@
+//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros)
+//! [`skip`] to omit specific fields from becoming columns in a [`Table`] display.
+//!
+//! * Note how [`skip`] annoys [clippy](https://doc.rust-lang.org/clippy/) with `dead_code`
+//! warnings. This can be addressed with compiler overrides like `#[allow(dead_code)]`.
+
+pub mod unknown_crate {
+    pub use ::tabled::{Table, Tabled};
+}
+
+#[allow(non_camel_case_types, dead_code)]
+type tabled = usize;
+
+use unknown_crate::{Table, Tabled};
+
+#[derive(Tabled)]
+#[tabled(crate = "unknown_crate")]
+struct Country<'a> {
+    name: &'a str,
+    capital_city: &'a str,
+}
+
+impl<'a> Country<'a> {
+    fn new(name: &'a str, capital_city: &'a str) -> Self {
+        Self { name, capital_city }
+    }
+}
+
+fn main() {
+    let data = [
+        Country::new("Afghanistan", "Kabul"),
+        Country::new("Angola", "Luanda"),
+        Country::new("Canada", "Ottawa"),
+    ];
+
+    let table = Table::new(data);
+
+    println!("{table}");
+}
diff --git a/tabled_derive/src/attributes.rs b/tabled_derive/src/attributes.rs
index 2cf5dc43..0e415ea6 100644
--- a/tabled_derive/src/attributes.rs
+++ b/tabled_derive/src/attributes.rs
@@ -1,9 +1,16 @@
 use syn::{Attribute, Lit, LitInt};
 
-use crate::{casing_style::CasingStyle, error::Error, parse};
+use crate::{
+    casing_style::CasingStyle,
+    error::Error,
+    parse::{
+        field_attr::{parse_field_attributes, FieldAttr, FieldAttrKind},
+        type_attr::{parse_type_attributes, TypeAttr, TypeAttrKind},
+    },
+};
 
 #[derive(Debug, Default)]
-pub struct Attributes {
+pub struct FieldAttributes {
     pub is_ignored: bool,
     pub inline: bool,
     pub inline_prefix: Option<String>,
@@ -14,7 +21,7 @@ pub struct Attributes {
     pub order: Option<usize>,
 }
 
-impl Attributes {
+impl FieldAttributes {
     pub fn parse(attrs: &[Attribute]) -> Result<Self, Error> {
         let mut attributes = Self::default();
         attributes.fill_attributes(attrs)?;
@@ -23,7 +30,7 @@ impl Attributes {
     }
 
     fn fill_attributes(&mut self, attrs: &[Attribute]) -> Result<(), Error> {
-        for attrs in parse::parse_attributes(attrs) {
+        for attrs in parse_field_attributes(attrs) {
             let attrs = attrs?;
             for attr in attrs {
                 self.insert_attribute(attr)?;
@@ -33,14 +40,14 @@ impl Attributes {
         Ok(())
     }
 
-    fn insert_attribute(&mut self, attr: parse::TabledAttr) -> Result<(), Error> {
+    fn insert_attribute(&mut self, attr: FieldAttr) -> Result<(), Error> {
         match attr.kind {
-            parse::TabledAttrKind::Skip(b) => {
+            FieldAttrKind::Skip(b) => {
                 if b.value {
                     self.is_ignored = true;
                 }
             }
-            parse::TabledAttrKind::Inline(b, prefix) => {
+            FieldAttrKind::Inline(b, prefix) => {
                 if b.value {
                     self.inline = true;
                 }
@@ -49,11 +56,11 @@ impl Attributes {
                     self.inline_prefix = Some(prefix.value());
                 }
             }
-            parse::TabledAttrKind::Rename(value) => self.rename = Some(value.value()),
-            parse::TabledAttrKind::RenameAll(lit) => {
+            FieldAttrKind::Rename(value) => self.rename = Some(value.value()),
+            FieldAttrKind::RenameAll(lit) => {
                 self.rename_all = Some(CasingStyle::from_lit(&lit)?);
             }
-            parse::TabledAttrKind::DisplayWith(path, comma, args) => {
+            FieldAttrKind::DisplayWith(path, comma, args) => {
                 self.display_with = Some(path.value());
                 if comma.is_some() {
                     let args = args
@@ -63,31 +70,63 @@ impl Attributes {
                     self.display_with_args = Some(args);
                 }
             }
-            parse::TabledAttrKind::Order(value) => self.order = Some(lit_int_to_usize(&value)?),
+            FieldAttrKind::Order(value) => self.order = Some(lit_int_to_usize(&value)?),
         }
 
         Ok(())
     }
-
-    pub fn is_ignored(&self) -> bool {
-        self.is_ignored
-    }
 }
 
-pub struct StructAttributes {
+#[derive(Debug, Default)]
+pub struct TypeAttributes {
     pub rename_all: Option<CasingStyle>,
     pub inline: bool,
     pub inline_value: Option<String>,
+    pub crate_name: Option<String>,
 }
 
-impl StructAttributes {
+impl TypeAttributes {
     pub fn parse(attrs: &[Attribute]) -> Result<Self, Error> {
-        let attrs = Attributes::parse(attrs)?;
-        Ok(Self {
-            rename_all: attrs.rename_all,
-            inline: attrs.inline,
-            inline_value: attrs.inline_prefix,
-        })
+        let mut attributes = Self::default();
+        attributes.fill_attributes(attrs)?;
+
+        Ok(attributes)
+    }
+
+    fn fill_attributes(&mut self, attrs: &[Attribute]) -> Result<(), Error> {
+        for attrs in parse_type_attributes(attrs) {
+            let attrs = attrs?;
+            for attr in attrs {
+                self.insert_attribute(attr)?;
+            }
+        }
+
+        Ok(())
+    }
+
+    fn insert_attribute(&mut self, attr: TypeAttr) -> Result<(), Error> {
+        match attr.kind {
+            TypeAttrKind::Crate(crate_name) => {
+                let name = crate_name.value();
+                if !name.is_empty() {
+                    self.crate_name = Some(name);
+                }
+            }
+            TypeAttrKind::Inline(b, prefix) => {
+                if b.value {
+                    self.inline = true;
+                }
+
+                if let Some(prefix) = prefix {
+                    self.inline_value = Some(prefix.value());
+                }
+            }
+            TypeAttrKind::RenameAll(lit) => {
+                self.rename_all = Some(CasingStyle::from_lit(&lit)?);
+            }
+        }
+
+        Ok(())
     }
 }
 
diff --git a/tabled_derive/src/lib.rs b/tabled_derive/src/lib.rs
index 26a1f355..58f2525b 100644
--- a/tabled_derive/src/lib.rs
+++ b/tabled_derive/src/lib.rs
@@ -15,11 +15,11 @@ use proc_macro_error::proc_macro_error;
 use quote::{quote, ToTokens, TokenStreamExt};
 use std::{collections::HashMap, str};
 use syn::{
-    parse_macro_input, token, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, Ident, Index,
-    Type, Variant,
+    parse_macro_input, token, Data, DataEnum, DataStruct, DeriveInput, ExprPath, Field, Fields,
+    Ident, Index, PathSegment, Type, Variant,
 };
 
-use attributes::{Attributes, FuncArg, StructAttributes};
+use attributes::{FieldAttributes, FuncArg, TypeAttributes};
 use error::Error;
 
 #[proc_macro_derive(Tabled, attributes(tabled))]
@@ -31,21 +31,26 @@ pub fn tabled(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
 }
 
 fn impl_tabled(ast: &DeriveInput) -> TokenStream {
-    let attrs = StructAttributes::parse(&ast.attrs)
+    let attrs = TypeAttributes::parse(&ast.attrs)
         .map_err(error::abort)
         .unwrap();
 
-    let length = get_tabled_length(ast, &attrs)
+    let tabled_trait_path = get_crate_name_expr(&attrs).map_err(error::abort).unwrap();
+
+    let length = get_tabled_length(ast, &attrs, &tabled_trait_path)
+        .map_err(error::abort)
+        .unwrap();
+    let info = collect_info(ast, &attrs, &tabled_trait_path)
         .map_err(error::abort)
         .unwrap();
-    let info = collect_info(ast, &attrs).map_err(error::abort).unwrap();
     let fields = info.values;
     let headers = info.headers;
 
     let name = &ast.ident;
     let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+
     let expanded = quote! {
-        impl #impl_generics ::tabled::Tabled for #name #ty_generics #where_clause {
+        impl #impl_generics #tabled_trait_path for #name #ty_generics #where_clause {
             const LENGTH: usize = #length;
 
             fn fields(&self) -> Vec<::std::borrow::Cow<'_, str>> {
@@ -61,34 +66,38 @@ fn impl_tabled(ast: &DeriveInput) -> TokenStream {
     expanded
 }
 
-fn get_tabled_length(ast: &DeriveInput, attrs: &StructAttributes) -> Result<TokenStream, Error> {
+fn get_tabled_length(
+    ast: &DeriveInput,
+    attrs: &TypeAttributes,
+    trait_path: &ExprPath,
+) -> Result<TokenStream, Error> {
     match &ast.data {
-        Data::Struct(data) => get_fields_length(&data.fields),
+        Data::Struct(data) => get_fields_length(&data.fields, trait_path),
         Data::Enum(data) => {
             if attrs.inline {
                 Ok(quote! { 1 })
             } else {
-                get_enum_length(data)
+                get_enum_length(data, trait_path)
             }
         }
         Data::Union(_) => Err(Error::message("Union type isn't supported")),
     }
 }
 
-fn get_fields_length(fields: &Fields) -> Result<TokenStream, Error> {
+fn get_fields_length(fields: &Fields, tabled_trait: &ExprPath) -> Result<TokenStream, Error> {
     let size_components = fields
         .iter()
         .map(|field| {
-            let attributes = Attributes::parse(&field.attrs)?;
+            let attributes = FieldAttributes::parse(&field.attrs)?;
             Ok((field, attributes))
         })
         .collect::<Result<Vec<_>, Error>>()?
         .into_iter()
-        .filter(|(_, attr)| !attr.is_ignored())
+        .filter(|(_, attr)| !attr.is_ignored)
         .map(|(field, attr)| {
             if attr.inline {
                 let field_type = &field.ty;
-                quote!({<#field_type as tabled::Tabled>::LENGTH})
+                quote!({<#field_type as #tabled_trait>::LENGTH})
             } else {
                 quote!({ 1 })
             }
@@ -102,8 +111,8 @@ fn get_fields_length(fields: &Fields) -> Result<TokenStream, Error> {
     Ok(stream)
 }
 
-fn get_enum_length(enum_ast: &DataEnum) -> Result<TokenStream, Error> {
-    let variant_sizes = get_enum_variant_length(enum_ast);
+fn get_enum_length(enum_ast: &DataEnum, trait_path: &ExprPath) -> Result<TokenStream, Error> {
+    let variant_sizes = get_enum_variant_length(enum_ast, trait_path);
 
     let mut stream = TokenStream::new();
     for (i, size) in variant_sizes.enumerate() {
@@ -119,38 +128,47 @@ fn get_enum_length(enum_ast: &DataEnum) -> Result<TokenStream, Error> {
     Ok(stream)
 }
 
-fn get_enum_variant_length(
-    enum_ast: &DataEnum,
-) -> impl Iterator<Item = Result<TokenStream, Error>> + '_ {
+fn get_enum_variant_length<'a>(
+    enum_ast: &'a DataEnum,
+    trait_path: &'a ExprPath,
+) -> impl Iterator<Item = Result<TokenStream, Error>> + 'a {
     enum_ast
         .variants
         .iter()
         .map(|variant| -> Result<_, Error> {
-            let attributes = Attributes::parse(&variant.attrs)?;
+            let attributes = FieldAttributes::parse(&variant.attrs)?;
             Ok((variant, attributes))
         })
-        .filter(|result| result.is_err() || matches!(result, Ok((_, attr)) if !attr.is_ignored()))
-        .map(|result| {
+        .filter(|result| result.is_err() || matches!(result, Ok((_, attr)) if !attr.is_ignored))
+        .map(move |result| {
             let (variant, attr) = result?;
 
             if attr.inline {
-                get_fields_length(&variant.fields)
+                get_fields_length(&variant.fields, trait_path)
             } else {
                 Ok(quote!(1))
             }
         })
 }
 
-fn collect_info(ast: &DeriveInput, attrs: &StructAttributes) -> Result<Impl, Error> {
+fn collect_info(
+    ast: &DeriveInput,
+    attrs: &TypeAttributes,
+    trait_path: &ExprPath,
+) -> Result<Impl, Error> {
     match &ast.data {
-        Data::Struct(data) => collect_info_struct(data, attrs),
-        Data::Enum(data) => collect_info_enum(data, attrs, &ast.ident),
+        Data::Struct(data) => collect_info_struct(data, attrs, trait_path),
+        Data::Enum(data) => collect_info_enum(data, attrs, &ast.ident, trait_path),
         Data::Union(_) => Err(Error::message("Union type isn't supported")),
     }
 }
 
-fn collect_info_struct(ast: &DataStruct, attrs: &StructAttributes) -> Result<Impl, Error> {
-    info_from_fields(&ast.fields, attrs, field_var_name, "")
+fn collect_info_struct(
+    ast: &DataStruct,
+    attrs: &TypeAttributes,
+    trait_path: &ExprPath,
+) -> Result<Impl, Error> {
+    info_from_fields(&ast.fields, attrs, field_var_name, "", trait_path)
 }
 
 // todo: refactoring. instead of using a lambda + prefix
@@ -158,9 +176,10 @@ fn collect_info_struct(ast: &DataStruct, attrs: &StructAttributes) -> Result<Imp
 // So the called would prefix it on its own
 fn info_from_fields(
     fields: &Fields,
-    attrs: &StructAttributes,
+    attrs: &TypeAttributes,
     field_name: impl Fn(usize, &Field) -> TokenStream,
     header_prefix: &str,
+    trait_path: &ExprPath,
 ) -> Result<Impl, Error> {
     let count_fields = fields.len();
 
@@ -168,7 +187,7 @@ fn info_from_fields(
         .into_iter()
         .enumerate()
         .map(|(i, field)| -> Result<_, Error> {
-            let mut attributes = Attributes::parse(&field.attrs)?;
+            let mut attributes = FieldAttributes::parse(&field.attrs)?;
             merge_attributes(&mut attributes, attrs);
 
             Ok((i, field, attributes))
@@ -181,7 +200,7 @@ fn info_from_fields(
     let mut skipped = 0;
     for result in fields {
         let (i, field, attributes) = result?;
-        if attributes.is_ignored() {
+        if attributes.is_ignored {
             skipped += 1;
             continue;
         }
@@ -196,7 +215,7 @@ fn info_from_fields(
             reorder.insert(order, i - skipped);
         }
 
-        let header = field_headers(field, i, &attributes, header_prefix);
+        let header = field_headers(field, i, &attributes, header_prefix, trait_path);
         headers.push(header);
 
         let field_name = field_name(i, field);
@@ -255,15 +274,16 @@ fn reorder_fields<T: Clone>(order: &HashMap<usize, usize>, elements: &[T]) -> Ve
 fn field_headers(
     field: &Field,
     index: usize,
-    attributes: &Attributes,
+    attributes: &FieldAttributes,
     prefix: &str,
+    trait_path: &ExprPath,
 ) -> TokenStream {
     if attributes.inline {
         let prefix = attributes
             .inline_prefix
             .as_ref()
             .map_or_else(|| "", |s| s.as_str());
-        return get_type_headers(&field.ty, prefix, "");
+        return get_type_headers(&field.ty, prefix, "", trait_path);
     }
 
     let header_name = field_header_name(field, attributes, index);
@@ -277,8 +297,9 @@ fn field_headers(
 
 fn collect_info_enum(
     ast: &DataEnum,
-    attrs: &StructAttributes,
+    attrs: &TypeAttributes,
     name: &Ident,
+    trait_path: &ExprPath,
 ) -> Result<Impl, Error> {
     match &attrs.inline {
         true => {
@@ -289,32 +310,36 @@ fn collect_info_enum(
 
             collect_info_enum_inlined(ast, attrs, enum_name)
         }
-        false => _collect_info_enum(ast, attrs),
+        false => _collect_info_enum(ast, attrs, trait_path),
     }
 }
 
-fn _collect_info_enum(ast: &DataEnum, attrs: &StructAttributes) -> Result<Impl, Error> {
+fn _collect_info_enum(
+    ast: &DataEnum,
+    attrs: &TypeAttributes,
+    trait_path: &ExprPath,
+) -> Result<Impl, Error> {
     // reorder variants according to order (if set)
     let orderedvariants = reodered_variants(ast)?;
 
     let mut headers_list = Vec::new();
     let mut variants = Vec::new();
     for v in orderedvariants {
-        let mut attributes = Attributes::parse(&v.attrs)?;
+        let mut attributes = FieldAttributes::parse(&v.attrs)?;
         merge_attributes(&mut attributes, attrs);
-        if attributes.is_ignored() {
+        if attributes.is_ignored {
             continue;
         }
 
-        let info = info_from_variant(v, &attributes, attrs)?;
+        let info = info_from_variant(v, &attributes, attrs, trait_path)?;
         variants.push((v, info.values));
         headers_list.push(info.headers);
     }
 
-    let variant_sizes = get_enum_variant_length(ast)
+    let variant_sizes = get_enum_variant_length(ast, trait_path)
         .collect::<Result<Vec<_>, Error>>()?
         .into_iter();
-    let values = values_for_enum(variant_sizes, &variants);
+    let values = values_for_enum(variant_sizes, &variants, trait_path);
 
     let headers = quote! {
         [
@@ -328,7 +353,7 @@ fn _collect_info_enum(ast: &DataEnum, attrs: &StructAttributes) -> Result<Impl,
 
 fn collect_info_enum_inlined(
     ast: &DataEnum,
-    attrs: &StructAttributes,
+    attrs: &TypeAttributes,
     enum_name: String,
 ) -> Result<Impl, Error> {
     let orderedvariants = reodered_variants(ast)?;
@@ -336,10 +361,10 @@ fn collect_info_enum_inlined(
     let mut variants = Vec::new();
     let mut names = Vec::new();
     for variant in orderedvariants {
-        let mut attributes = Attributes::parse(&variant.attrs)?;
+        let mut attributes = FieldAttributes::parse(&variant.attrs)?;
         merge_attributes(&mut attributes, attrs);
         let mut name = String::new();
-        if !attributes.is_ignored() {
+        if !attributes.is_ignored {
             name = variant_name(variant, &attributes);
         }
 
@@ -360,15 +385,16 @@ fn collect_info_enum_inlined(
 
 fn info_from_variant(
     variant: &Variant,
-    attr: &Attributes,
-    attrs: &StructAttributes,
+    attr: &FieldAttributes,
+    attrs: &TypeAttributes,
+    trait_path: &ExprPath,
 ) -> Result<Impl, Error> {
     if attr.inline {
         let prefix = attr
             .inline_prefix
             .as_ref()
             .map_or_else(|| "", |s| s.as_str());
-        return info_from_fields(&variant.fields, attrs, variant_var_name, prefix);
+        return info_from_fields(&variant.fields, attrs, variant_var_name, prefix, trait_path);
     }
 
     let variant_name = variant_name(variant, attr);
@@ -408,12 +434,17 @@ struct Impl {
     values: TokenStream,
 }
 
-fn get_type_headers(field_type: &Type, inline_prefix: &str, prefix: &str) -> TokenStream {
+fn get_type_headers(
+    field_type: &Type,
+    inline_prefix: &str,
+    prefix: &str,
+    tabled_trait: &ExprPath,
+) -> TokenStream {
     if prefix.is_empty() && inline_prefix.is_empty() {
-        quote! { <#field_type as tabled::Tabled>::headers() }
+        quote! { <#field_type as #tabled_trait>::headers() }
     } else {
         quote! {
-            <#field_type as tabled::Tabled>::headers().into_iter()
+            <#field_type as #tabled_trait>::headers().into_iter()
                 .map(|header| {
                     let header = format!("{}{}{}", #prefix, #inline_prefix, header);
                     ::std::borrow::Cow::Owned(header)
@@ -423,7 +454,7 @@ fn get_type_headers(field_type: &Type, inline_prefix: &str, prefix: &str) -> Tok
     }
 }
 
-fn get_field_fields(field: &TokenStream, attr: &Attributes) -> TokenStream {
+fn get_field_fields(field: &TokenStream, attr: &FieldAttributes) -> TokenStream {
     if attr.inline {
         return quote! { #field.fields() };
     }
@@ -499,6 +530,7 @@ fn variant_var_name(index: usize, field: &Field) -> TokenStream {
 fn values_for_enum(
     variant_sizes: impl Iterator<Item = TokenStream>,
     variants: &[(&Variant, TokenStream)],
+    tabled_trait: &ExprPath,
 ) -> TokenStream {
     let branches = variants.iter().map(|(variant, _)| match_variant(variant));
 
@@ -536,7 +568,7 @@ fn values_for_enum(
             offsets[i] += offsets[i-1]
         }
 
-        let size = <Self as tabled::Tabled>::LENGTH;
+        let size = <Self as #tabled_trait>::LENGTH;
         let mut out_vec = vec![::std::borrow::Cow::Borrowed(""); size];
 
         #[allow(unused_variables)]
@@ -582,7 +614,7 @@ fn match_variant(v: &Variant) -> TokenStream {
     token
 }
 
-fn variant_name(variant: &Variant, attributes: &Attributes) -> String {
+fn variant_name(variant: &Variant, attributes: &FieldAttributes) -> String {
     attributes
         .rename
         .clone()
@@ -595,7 +627,7 @@ fn variant_name(variant: &Variant, attributes: &Attributes) -> String {
         .unwrap_or_else(|| variant.ident.to_string())
 }
 
-fn field_header_name(f: &Field, attr: &Attributes, index: usize) -> String {
+fn field_header_name(f: &Field, attr: &FieldAttributes, index: usize) -> String {
     if let Some(name) = &attr.rename {
         return name.to_string();
     }
@@ -612,7 +644,7 @@ fn field_header_name(f: &Field, attr: &Attributes, index: usize) -> String {
     }
 }
 
-fn merge_attributes(attr: &mut Attributes, global_attr: &StructAttributes) {
+fn merge_attributes(attr: &mut FieldAttributes, global_attr: &TypeAttributes) {
     if attr.rename_all.is_none() {
         attr.rename_all = global_attr.rename_all;
     }
@@ -642,7 +674,7 @@ fn reodered_variants(ast: &DataEnum) -> Result<Vec<&Variant>, Error> {
     for (i, attr) in ast
         .variants
         .iter()
-        .map(|v| Attributes::parse(&v.attrs).unwrap_or_default())
+        .map(|v| FieldAttributes::parse(&v.attrs).unwrap_or_default())
         .enumerate()
     {
         if attr.is_ignored {
@@ -668,3 +700,24 @@ fn reodered_variants(ast: &DataEnum) -> Result<Vec<&Variant>, Error> {
 
     Ok(orderedvariants)
 }
+
+fn get_crate_name_expr(attrs: &TypeAttributes) -> Result<ExprPath, Error> {
+    let crate_name = attrs
+        .crate_name
+        .clone()
+        .unwrap_or_else(|| String::from("::tabled"));
+    let crate_name = parse_crate_name(&crate_name)?;
+    Ok(create_tabled_trait_path(crate_name))
+}
+
+fn parse_crate_name(name: &str) -> Result<ExprPath, Error> {
+    syn::parse_str(name).map_err(|_| Error::message("unexpected crate attribute type"))
+}
+
+fn create_tabled_trait_path(mut p: ExprPath) -> ExprPath {
+    p.path.segments.push(PathSegment {
+        ident: Ident::new("Tabled", proc_macro2::Span::call_site()),
+        arguments: syn::PathArguments::None,
+    });
+    p
+}
diff --git a/tabled_derive/src/parse.rs b/tabled_derive/src/parse/field_attr.rs
similarity index 90%
rename from tabled_derive/src/parse.rs
rename to tabled_derive/src/parse/field_attr.rs
index 001f38c4..396b5bb3 100644
--- a/tabled_derive/src/parse.rs
+++ b/tabled_derive/src/parse/field_attr.rs
@@ -4,29 +4,29 @@ use syn::{
     Token,
 };
 
-pub fn parse_attributes(
+pub fn parse_field_attributes(
     attributes: &[Attribute],
-) -> impl Iterator<Item = syn::Result<impl Iterator<Item = TabledAttr>>> + '_ {
+) -> impl Iterator<Item = syn::Result<impl Iterator<Item = FieldAttr>>> + '_ {
     attributes
         .iter()
         .filter(|attr| attr.path.is_ident("tabled"))
-        .map(|attr| attr.parse_args_with(Punctuated::<TabledAttr, Token![,]>::parse_terminated))
+        .map(|attr| attr.parse_args_with(Punctuated::<FieldAttr, Token![,]>::parse_terminated))
         .map(|result| result.map(IntoIterator::into_iter))
 }
 
-pub struct TabledAttr {
+pub struct FieldAttr {
     pub ident: Ident,
-    pub kind: TabledAttrKind,
+    pub kind: FieldAttrKind,
 }
 
-impl TabledAttr {
-    pub fn new(ident: Ident, kind: TabledAttrKind) -> Self {
+impl FieldAttr {
+    pub fn new(ident: Ident, kind: FieldAttrKind) -> Self {
         Self { ident, kind }
     }
 }
 
 #[derive(Clone)]
-pub enum TabledAttrKind {
+pub enum FieldAttrKind {
     Skip(LitBool),
     Inline(LitBool, Option<LitStr>),
     Rename(LitStr),
@@ -35,9 +35,9 @@ pub enum TabledAttrKind {
     Order(LitInt),
 }
 
-impl Parse for TabledAttr {
+impl Parse for FieldAttr {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        use TabledAttrKind::*;
+        use FieldAttrKind::*;
 
         let name: Ident = input.parse()?;
         let name_str = name.to_string();
diff --git a/tabled_derive/src/parse/mod.rs b/tabled_derive/src/parse/mod.rs
new file mode 100644
index 00000000..b23ed6ea
--- /dev/null
+++ b/tabled_derive/src/parse/mod.rs
@@ -0,0 +1,2 @@
+pub mod field_attr;
+pub mod type_attr;
diff --git a/tabled_derive/src/parse/type_attr.rs b/tabled_derive/src/parse/type_attr.rs
new file mode 100644
index 00000000..6726b1e0
--- /dev/null
+++ b/tabled_derive/src/parse/type_attr.rs
@@ -0,0 +1,110 @@
+use proc_macro2::{Ident, Span};
+use syn::{
+    parenthesized, parse::Parse, punctuated::Punctuated, token, Attribute, LitBool, LitStr, Token,
+};
+
+pub fn parse_type_attributes(
+    attributes: &[Attribute],
+) -> impl Iterator<Item = syn::Result<impl Iterator<Item = TypeAttr>>> + '_ {
+    attributes
+        .iter()
+        .filter(|attr| attr.path.is_ident("tabled"))
+        .map(|attr| attr.parse_args_with(Punctuated::<TypeAttr, Token![,]>::parse_terminated))
+        .map(|result| result.map(IntoIterator::into_iter))
+}
+
+pub struct TypeAttr {
+    pub ident: Ident,
+    pub kind: TypeAttrKind,
+}
+
+impl TypeAttr {
+    pub fn new(ident: Ident, kind: TypeAttrKind) -> Self {
+        Self { ident, kind }
+    }
+}
+
+#[derive(Clone)]
+pub enum TypeAttrKind {
+    Inline(LitBool, Option<LitStr>),
+    RenameAll(LitStr),
+    Crate(LitStr),
+}
+
+impl Parse for TypeAttr {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        use TypeAttrKind::*;
+
+        if input.peek(syn::token::Crate) {
+            let crate_token: syn::token::Crate = input.parse()?;
+            let _ = input.parse::<Token![=]>()?;
+            let value = input.parse::<LitStr>()?;
+
+            return Ok(Self::new(
+                Ident::new("crate", crate_token.span),
+                Crate(value),
+            ));
+        }
+
+        let name: Ident = input.parse()?;
+        let name_str = name.to_string();
+
+        if input.peek(Token![=]) {
+            let assign_token = input.parse::<Token![=]>()?;
+
+            if input.peek(LitStr) {
+                let lit = input.parse::<LitStr>()?;
+
+                if let "rename_all" = name_str.as_str() {
+                    return Ok(Self::new(name, RenameAll(lit)));
+                }
+            }
+
+            if input.peek(LitBool) {
+                let lit = input.parse::<LitBool>()?;
+
+                if let "inline" = name_str.as_str() {
+                    return Ok(Self::new(name, Inline(lit, None)));
+                }
+            }
+
+            return Err(syn::Error::new(
+                assign_token.span,
+                "expected `string literal` or `expression` after `=`",
+            ));
+        }
+
+        if input.peek(token::Paren) {
+            let nested;
+            let _paren = parenthesized!(nested in input);
+
+            if nested.peek(LitStr) {
+                let lit = nested.parse::<LitStr>()?;
+
+                if let "inline" = name_str.as_str() {
+                    return Ok(Self::new(
+                        name,
+                        Inline(LitBool::new(true, Span::call_site()), Some(lit)),
+                    ));
+                }
+            }
+
+            return Err(syn::Error::new(
+                _paren.span,
+                "expected a `string literal` in parenthesis",
+            ));
+        }
+
+        if let "inline" = name_str.as_str() {
+            return Ok(Self::new(
+                name,
+                Inline(LitBool::new(true, Span::call_site()), None),
+            ));
+        }
+
+        Err(syn::Error::new(
+            name.span(),
+            format!("unexpected attribute: {name_str}"),
+        ))
+    }
+}


Test patch:
diff --git a/tabled/tests/derive/derive_test.rs b/tabled/tests/derive/derive_test.rs
index e742583f..c8dfe53b 100644
--- a/tabled/tests/derive/derive_test.rs
+++ b/tabled/tests/derive/derive_test.rs
@@ -1081,6 +1081,46 @@ fn test_skip_enum_0() {
     assert_eq!(Letters::Digit.fields(), vec!["", ""]);
 }
 
+#[test]
+fn test_reimport_trait_by_crate_attribute() {
+    pub mod new_module {
+        pub trait Tabled {
+            const LENGTH: usize;
+
+            fn fields(&self) -> Vec<std::borrow::Cow<'_, str>>;
+            fn headers() -> Vec<std::borrow::Cow<'static, str>>;
+        }
+    }
+
+    mod tabled {}
+
+    #[allow(dead_code)]
+    #[derive(Tabled)]
+    #[tabled(crate = "new_module")]
+    enum Letters {
+        Vowels {
+            character: char,
+            lang: u8,
+        },
+        Consonant(char),
+        #[tabled(skip)]
+        Digit,
+    }
+
+    assert_eq!(
+        <Letters as new_module::Tabled>::headers(),
+        vec!["Vowels", "Consonant"]
+    );
+    assert_eq!(
+        <Letters as new_module::Tabled>::fields(&Letters::Consonant('c')),
+        vec!["", "+"]
+    );
+    assert_eq!(
+        <Letters as new_module::Tabled>::fields(&Letters::Digit),
+        vec!["", ""]
+    );
+}
+
 #[test]
 fn test_display_with_2() {
     #[derive(tabled::Tabled)]