Repository: zhiburt/tabled (rust)

Issue:
Is it valuable to support ordering in derive macro?
We could support something like the following.

```rust
#[derive(Tabled)]
struct Picture {
  #[tabled(order = 2)];
  author: String,
  title: String,
  created_at_year: u32,
}
```

Which would result in a table where the author column would be the 2nd not the first like by default.

A use case I can picture is the type which is marked by `#[repl(C)]` probably.
But that's it.

Is it valuable?

Pull request:
tabled_derive/ Add order argument
close #67

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index a66c65a..ed218cf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,7 +29,7 @@ maintenance = { status = "actively-developed" }
 color = ["papergrid/color", "ansi-str"]
 
 [dependencies]
-tabled_derive = "0.3.0"
+tabled_derive = { path = "./tabled_derive" }
 papergrid = { path = "./papergrid" }
 ansi-str = { version = "0.1.1", optional = true }
 
diff --git a/tabled_derive/src/lib.rs b/tabled_derive/src/lib.rs
index 42a2485..609a43c 100644
--- a/tabled_derive/src/lib.rs
+++ b/tabled_derive/src/lib.rs
@@ -2,7 +2,7 @@ extern crate proc_macro;
 
 use proc_macro2::TokenStream;
 use quote::*;
-use std::str;
+use std::{collections::HashMap, str};
 use syn::{
     parse_macro_input, token, Attribute, Data, DataEnum, DataStruct, DeriveInput, Field, Fields,
     Ident, Index, Lit, Meta, NestedMeta, Type, Variant,
@@ -49,7 +49,7 @@ fn get_tabled_length(ast: &DeriveInput) -> Result<TokenStream, String> {
 }
 
 fn get_fields_length(fields: &Fields) -> TokenStream {
-    let size_compontents = fields
+    let size_components = fields
         .iter()
         .map(|field| {
             let attributes = Attributes::parse(&field.attrs);
@@ -65,10 +65,10 @@ fn get_fields_length(fields: &Fields) -> TokenStream {
             }
         });
 
-    let size_compontents = std::iter::once(quote!(0)).chain(size_compontents);
+    let size_components = std::iter::once(quote!(0)).chain(size_components);
 
     let mut stream = TokenStream::new();
-    stream.append_separated(size_compontents, syn::token::Add::default());
+    stream.append_separated(size_components, syn::token::Add::default());
 
     stream
 }
@@ -121,6 +121,8 @@ fn info_from_fields(
     field_name: impl Fn(usize, &Field) -> TokenStream,
     header_prefix: &str,
 ) -> Result<Impl, String> {
+    let count_fields = fields.len();
+
     let fields = fields.into_iter().enumerate().map(|(i, field)| {
         let attributes = Attributes::parse(&field.attrs);
         (i, field, attributes)
@@ -128,12 +130,21 @@ fn info_from_fields(
 
     let mut headers = Vec::new();
     let mut values = Vec::new();
+    let mut reorder = HashMap::new();
 
     for (i, field, attributes) in fields {
         if attributes.is_ignored() {
             continue;
         }
 
+        if let Some(order) = attributes.order {
+            if order >= count_fields {
+                panic!("An order index '{}' is out of fields scope", order);
+            }
+
+            reorder.insert(order, i);
+        }
+
         let header = field_headers(field, i, &attributes, header_prefix);
 
         headers.push(header);
@@ -144,6 +155,11 @@ fn info_from_fields(
         values.push(value);
     }
 
+    if !reorder.is_empty() {
+        values = reorder_fields(&reorder, values);
+        headers = reorder_fields(&reorder, headers);
+    }
+
     let headers = quote!({
         let mut out = Vec::new();
         #(out.extend(#headers);)*
@@ -159,6 +175,34 @@ fn info_from_fields(
     Ok(Impl { headers, values })
 }
 
+fn reorder_fields<T: Clone>(order: &HashMap<usize, usize>, elements: Vec<T>) -> Vec<T> {
+    let mut out: Vec<Option<T>> = Vec::with_capacity(elements.len());
+    out.resize(elements.len(), None);
+
+    for (pos, index) in order {
+        let value = elements[*index].clone();
+        out[*pos] = Some(value);
+    }
+
+    let mut j = 0;
+    for el in out.iter_mut() {
+        if el.is_some() {
+            continue;
+        }
+
+        while order.values().any(|&pos| j == pos) {
+            j += 1;
+        }
+
+        let v = elements[j].clone();
+        *el = Some(v);
+
+        j += 1;
+    }
+
+    out.into_iter().flatten().collect()
+}
+
 fn field_headers(
     field: &Field,
     index: usize,
@@ -401,6 +445,7 @@ struct Attributes {
     inline_prefix: Option<String>,
     name: Option<String>,
     display_with: Option<String>,
+    order: Option<usize>,
 }
 
 impl Attributes {
@@ -410,12 +455,14 @@ impl Attributes {
         let inline_prefix = look_for_inline_prefix(attrs);
         let display_with = check_display_with_func(attrs);
         let override_header_name = override_header_name(attrs);
+        let order = override_header_order(attrs);
 
         Self {
             display_with,
             is_ignored,
-            inline: should_be_inlined,
+            order,
             inline_prefix,
+            inline: should_be_inlined,
             name: override_header_name,
         }
     }
@@ -429,6 +476,10 @@ fn override_header_name(attrs: &[Attribute]) -> Option<String> {
     find_name_attribute(attrs, "tabled", "rename", look_up_nested_meta_str)
 }
 
+fn override_header_order(attrs: &[Attribute]) -> Option<usize> {
+    find_name_attribute(attrs, "tabled", "order", look_up_nested_meta_usize)
+}
+
 fn check_display_with_func(attrs: &[Attribute]) -> Option<String> {
     find_name_attribute(attrs, "tabled", "display_with", look_up_nested_meta_str)
 }
@@ -548,6 +599,31 @@ fn look_up_nested_flag_str_in_attr(
     }
 }
 
+fn look_up_nested_meta_usize(meta: &NestedMeta, name: &str) -> Result<Option<usize>, String> {
+    match meta {
+        NestedMeta::Meta(Meta::NameValue(value)) => {
+            if value.path.is_ident(name) {
+                check_usize_literal(&value.lit)
+            } else {
+                Ok(None)
+            }
+        }
+        _ => Ok(None),
+    }
+}
+
+fn check_usize_literal(lit: &Lit) -> Result<Option<usize>, String> {
+    match lit {
+        Lit::Int(value) => {
+            let value = value
+                .base10_parse::<usize>()
+                .map_err(|e| format!("Failed to parse int {:?}; {}", value.to_string(), e))?;
+            Ok(Some(value))
+        }
+        _ => Ok(None),
+    }
+}
+
 fn find_name_attribute<R, F>(
     attributes: &[Attribute],
     method: &str,


Test patch:
diff --git a/tests/derive_test.rs b/tests/derive_test.rs
index 0087375..54aad5a 100644
--- a/tests/derive_test.rs
+++ b/tests/derive_test.rs
@@ -126,6 +126,170 @@ mod tupple_structure {
 
         assert_eq!(St::<String>::LENGTH, 1);
     }
+
+    #[test]
+    fn unit_order_tabled() {
+        {
+            #[derive(Tabled)]
+            struct St(#[tabled(order = 0)] u8, u8, u8);
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["0", "1", "2"], st.fields());
+            assert_eq!(vec!["0", "1", "2"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St(#[tabled(order = 1)] u8, u8, u8);
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["1", "0", "2"], st.fields());
+            assert_eq!(vec!["1", "0", "2"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St(#[tabled(order = 2)] u8, u8, u8);
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["1", "2", "0"], st.fields());
+            assert_eq!(vec!["1", "2", "0"], St::headers());
+        }
+
+        {
+            #[derive(Tabled)]
+            struct St(u8, #[tabled(order = 0)] u8, u8);
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["1", "0", "2"], st.fields());
+            assert_eq!(vec!["1", "0", "2"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St(u8, #[tabled(order = 1)] u8, u8);
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["0", "1", "2"], st.fields());
+            assert_eq!(vec!["0", "1", "2"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St(u8, #[tabled(order = 2)] u8, u8);
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["0", "2", "1"], st.fields());
+            assert_eq!(vec!["0", "2", "1"], St::headers());
+        }
+
+        {
+            #[derive(Tabled)]
+            struct St(u8, u8, #[tabled(order = 0)] u8);
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["2", "0", "1"], st.fields());
+            assert_eq!(vec!["2", "0", "1"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St(u8, u8, #[tabled(order = 1)] u8);
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["0", "2", "1"], st.fields());
+            assert_eq!(vec!["0", "2", "1"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St(u8, u8, #[tabled(order = 2)] u8);
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["0", "1", "2"], st.fields());
+            assert_eq!(vec!["0", "1", "2"], St::headers());
+        }
+
+        {
+            #[derive(Tabled)]
+            struct St(#[tabled(order = 2)] u8, u8, #[tabled(order = 0)] u8);
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["2", "1", "0"], st.fields());
+            assert_eq!(vec!["2", "1", "0"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St(#[tabled(order = 2)] u8, #[tabled(order = 1)] u8, u8);
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["2", "1", "0"], st.fields());
+            assert_eq!(vec!["2", "1", "0"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St(
+                #[tabled(order = 2)] u8,
+                #[tabled(order = 2)] u8,
+                #[tabled(order = 1)] u8,
+            );
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["0", "2", "1"], st.fields());
+            assert_eq!(vec!["0", "2", "1"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St(
+                #[tabled(order = 2)] u8,
+                #[tabled(order = 2)] u8,
+                #[tabled(order = 2)] u8,
+            );
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["0", "1", "2"], st.fields());
+            assert_eq!(vec!["0", "1", "2"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St(
+                #[tabled(order = 1)] u8,
+                #[tabled(order = 1)] u8,
+                #[tabled(order = 1)] u8,
+            );
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["0", "2", "1"], st.fields());
+            assert_eq!(vec!["0", "2", "1"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St(
+                #[tabled(order = 2)] u8,
+                #[tabled(order = 1)] u8,
+                #[tabled(order = 0)] u8,
+            );
+
+            let st = St(0, 1, 2);
+
+            assert_eq!(vec!["2", "1", "0"], st.fields());
+            assert_eq!(vec!["2", "1", "0"], St::headers());
+        }
+    }
+
+    // #[test]
+    // fn order_compile_fail_when_order_is_bigger_then_count_fields() {
+    //     #[derive(Tabled)]
+    //     struct St(#[tabled(order = 3)] u8, u8, u8);
+    // }
 }
 
 mod enum_ {
@@ -631,6 +795,284 @@ mod structure {
         assert_eq!(vec!["0".to_owned(), "some v2".to_owned()], st.fields());
         assert_eq!(vec!["f1".to_owned(), "f2".to_owned()], St::headers());
     }
+
+    #[test]
+    fn order_tabled() {
+        {
+            #[derive(Tabled)]
+            struct St {
+                #[tabled(order = 0)]
+                f0: u8,
+                f1: u8,
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["0", "1", "2"], st.fields());
+            assert_eq!(vec!["f0", "f1", "f2"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St {
+                #[tabled(order = 1)]
+                f0: u8,
+                f1: u8,
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["1", "0", "2"], st.fields());
+            assert_eq!(vec!["f1", "f0", "f2"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St {
+                #[tabled(order = 2)]
+                f0: u8,
+                f1: u8,
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["1", "2", "0"], st.fields());
+            assert_eq!(vec!["f1", "f2", "f0"], St::headers());
+        }
+
+        {
+            #[derive(Tabled)]
+            struct St {
+                f0: u8,
+                #[tabled(order = 0)]
+                f1: u8,
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["1", "0", "2"], st.fields());
+            assert_eq!(vec!["f1", "f0", "f2"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St {
+                f0: u8,
+                #[tabled(order = 1)]
+                f1: u8,
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["0", "1", "2"], st.fields());
+            assert_eq!(vec!["f0", "f1", "f2"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St {
+                f0: u8,
+                #[tabled(order = 2)]
+                f1: u8,
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["0", "2", "1"], st.fields());
+            assert_eq!(vec!["f0", "f2", "f1"], St::headers());
+        }
+
+        {
+            #[derive(Tabled)]
+            struct St {
+                f0: u8,
+                f1: u8,
+                #[tabled(order = 0)]
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["2", "0", "1"], st.fields());
+            assert_eq!(vec!["f2", "f0", "f1"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St {
+                f0: u8,
+                f1: u8,
+                #[tabled(order = 1)]
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["0", "2", "1"], st.fields());
+            assert_eq!(vec!["f0", "f2", "f1"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St {
+                f0: u8,
+                f1: u8,
+                #[tabled(order = 2)]
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["0", "1", "2"], st.fields());
+            assert_eq!(vec!["f0", "f1", "f2"], St::headers());
+        }
+
+        {
+            #[derive(Tabled)]
+            struct St {
+                #[tabled(order = 2)]
+                f0: u8,
+                f1: u8,
+                #[tabled(order = 0)]
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["2", "1", "0"], st.fields());
+            assert_eq!(vec!["f2", "f1", "f0"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St {
+                #[tabled(order = 2)]
+                f0: u8,
+                #[tabled(order = 1)]
+                f1: u8,
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["2", "1", "0"], st.fields());
+            assert_eq!(vec!["f2", "f1", "f0"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St {
+                #[tabled(order = 2)]
+                f0: u8,
+                #[tabled(order = 2)]
+                f1: u8,
+                #[tabled(order = 1)]
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["0", "2", "1"], st.fields());
+            assert_eq!(vec!["f0", "f2", "f1"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St {
+                #[tabled(order = 2)]
+                f0: u8,
+                #[tabled(order = 2)]
+                f1: u8,
+                #[tabled(order = 2)]
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["0", "1", "2"], st.fields());
+            assert_eq!(vec!["f0", "f1", "f2"], St::headers());
+        }
+        {
+            #[derive(Tabled)]
+            struct St {
+                #[tabled(order = 2)]
+                f0: u8,
+                #[tabled(order = 1)]
+                f1: u8,
+                #[tabled(order = 0)]
+                f2: u8,
+            }
+
+            let st = St {
+                f0: 0,
+                f1: 1,
+                f2: 2,
+            };
+
+            assert_eq!(vec!["2", "1", "0"], st.fields());
+            assert_eq!(vec!["f2", "f1", "f0"], St::headers());
+        }
+    }
+
+    // #[test]
+    // fn order_compile_fail_when_order_is_bigger_then_count_fields() {
+    //     #[derive(Tabled)]
+    //     struct St {
+    //         #[tabled(order = 3)]
+    //         f0: u8,
+    //         f1: u8,
+    //         f2: u8,
+    //     }
+    // }
 }
 
 #[test]