From 8c472085686d2d8edc56985e91a7dec9e0eeee8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Teodor=20K=C3=A4llman?=
 <94024065+RedPhoenixQ@users.noreply.github.com>
Date: Tue, 14 Nov 2023 05:05:48 +0100
Subject: [PATCH] Accept literals in attribute names (#396)

* Accept literals in attribute names

* Accept literals in attribute names

* Propper handling of literals in name_to_string()
---
 CHANGELOG.md               |  2 ++
 maud/tests/basic_syntax.rs | 28 ++++++++++++++++++++++++++++
 maud_macros/src/ast.rs     | 19 ++++++++++++++++++-
 maud_macros/src/parse.rs   | 13 +++++++------
 4 files changed, 55 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c1d9102..60f636d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@
   [#377](https://github.com/lambda-fairy/maud/pull/377)
 - Implement `Render` for `Arc<T>`
   [#380](https://github.com/lambda-fairy/maud/pull/380)
+- Accept literals in attribute names
+  [#396](https://github.com/lambda-fairy/maud/pull/396)
 
 ## [0.25.0] - 2023-04-16
 
diff --git a/maud/tests/basic_syntax.rs b/maud/tests/basic_syntax.rs
index b84f795..96e0235 100644
--- a/maud/tests/basic_syntax.rs
+++ b/maud/tests/basic_syntax.rs
@@ -181,6 +181,34 @@ fn hyphens_in_attribute_names() {
     );
 }
 
+#[test]
+fn string_literals_in_attribute_names() {
+    let result = html! { this "@sentence:-is.not"="false" of-course {} };
+    assert_eq!(
+        result.into_string(),
+        r#"<this @sentence:-is.not="false" of-course></this>"#
+    );
+}
+
+#[test]
+fn raw_string_literals_in_attribute_names() {
+    let result = html! { this r#"@sentence:-is.not"#="false" of-course {} };
+    assert_eq!(
+        result.into_string(),
+        r#"<this @sentence:-is.not="false" of-course></this>"#
+    );
+}
+
+#[test]
+fn other_literals_in_attribute_names() {
+    let result =
+        html! { this b"byte_string"="false" 123="123" 2.5 true 'a'="a" b'b'="b" of-course {} };
+    assert_eq!(
+        result.into_string(),
+        r#"<this byte_string="false" 123="123" 2.5 true a="a" b="b" of-course></this>"#
+    );
+}
+
 #[test]
 fn class_shorthand() {
     let result = html! { p { "Hi, " span.name { "Lyra" } "!" } };
diff --git a/maud_macros/src/ast.rs b/maud_macros/src/ast.rs
index cd8a2ce..16d228e 100644
--- a/maud_macros/src/ast.rs
+++ b/maud_macros/src/ast.rs
@@ -1,5 +1,6 @@
 use proc_macro2::{TokenStream, TokenTree};
 use proc_macro_error::SpanRange;
+use syn::Lit;
 
 #[derive(Debug)]
 pub enum Markup {
@@ -217,5 +218,21 @@ pub fn join_ranges<I: IntoIterator<Item = SpanRange>>(ranges: I) -> SpanRange {
 }
 
 pub fn name_to_string(name: TokenStream) -> String {
-    name.into_iter().map(|token| token.to_string()).collect()
+    name.into_iter()
+        .map(|token| {
+            if let TokenTree::Literal(literal) = token {
+                match Lit::new(literal.clone()) {
+                    Lit::Str(str) => str.value(),
+                    Lit::Char(char) => char.value().to_string(),
+                    Lit::ByteStr(byte) => {
+                        String::from_utf8(byte.value()).expect("Invalid utf8 byte")
+                    }
+                    Lit::Byte(byte) => (byte.value() as char).to_string(),
+                    _ => literal.to_string(),
+                }
+            } else {
+                token.to_string()
+            }
+        })
+        .collect()
 }
diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index 3521ce4..662b830 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -702,12 +702,13 @@ impl Parser {
     /// Parses an identifier, without dealing with namespaces.
     fn try_name(&mut self) -> Option<TokenStream> {
         let mut result = Vec::new();
-        if let Some(token @ TokenTree::Ident(_)) = self.peek() {
-            self.advance();
-            result.push(token);
-        } else {
-            return None;
-        }
+        match self.peek() {
+            Some(token @ TokenTree::Ident(_)) | Some(token @ TokenTree::Literal(_)) => {
+                self.advance();
+                result.push(token);
+            }
+            _ => return None,
+        };
         let mut expect_ident = false;
         loop {
             expect_ident = match self.peek() {