Repository: lambda-fairy/maud
Pull request #398 (2023-11-14 21:35:20)

Parse many literals along side idents in names
This is a fix for the bug mentioned in https://github.com/lambda-fairy/maud/pull/396#issuecomment-1811008846. 

This is similar to #336 and also addresses #335, se the test for examples of what is now possible

The implications mentioned in https://github.com/lambda-fairy/maud/issues/335#issuecomment-1094247196 are not addressed here, but for now this fixes the bug that #396 created


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 60f636d..69b4df9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,10 @@
   [#380](https://github.com/lambda-fairy/maud/pull/380)
 - Accept literals in attribute names
   [#396](https://github.com/lambda-fairy/maud/pull/396)
+- Support `axum` v0.7 through `axum-core` v0.4 and `http` v1
+  [#401](https://github.com/lambda-fairy/maud/pull/401)
+- Support `rocket` v0.5
+  [#406](https://github.com/lambda-fairy/maud/pull/406)
 
 ## [0.25.0] - 2023-04-16
 
diff --git a/docs/Cargo.lock b/docs/Cargo.lock
index 63163a9..780c7d0 100644
--- a/docs/Cargo.lock
+++ b/docs/Cargo.lock
@@ -182,7 +182,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn",
 ]
 
 [[package]]
@@ -257,7 +257,6 @@ dependencies = [
  "proc-macro-error-attr",
  "proc-macro2",
  "quote",
- "syn 1.0.109",
  "version_check",
 ]
 
@@ -357,7 +356,7 @@ checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.22",
+ "syn",
 ]
 
 [[package]]
@@ -380,17 +379,6 @@ dependencies = [
  "deunicode",
 ]
 
-[[package]]
-name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
 [[package]]
 name = "syn"
 version = "2.0.22"
@@ -442,7 +430,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.22",
+ "syn",
 ]
 
 [[package]]
diff --git a/docs/content/web-frameworks.md b/docs/content/web-frameworks.md
index 3579f1a..b5442a5 100644
--- a/docs/content/web-frameworks.md
+++ b/docs/content/web-frameworks.md
@@ -60,22 +60,21 @@ maud = { version = "*", features = ["rocket"] }
 This adds a `Responder` implementation for the `Markup` type, so you can return the result directly:
 
 ```rust,no_run
-#![feature(decl_macro)]
-
 use maud::{html, Markup};
 use rocket::{get, routes};
 use std::borrow::Cow;
 
 #[get("/<name>")]
-fn hello<'a>(name: Cow<'a, str>) -> Markup {
+fn hello(name: &str) -> Markup {
     html! {
         h1 { "Hello, " (name) "!" }
         p { "Nice to meet you!" }
     }
 }
 
-fn main() {
-    rocket::ignite().mount("/", routes![hello]).launch();
+#[rocket::launch]
+fn launch() -> _ {
+    rocket::build().mount("/", routes![hello])
 }
 ```
 
@@ -167,9 +166,8 @@ async fn main() {
     let app = Router::new().route("/", get(hello_world));
 
     // run it with hyper on localhost:3000
-    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
-        .serve(app.into_make_service())
-        .await
-        .unwrap();
+    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
+
+    axum::serve(listener, app.into_make_service()).await.unwrap();
 }
 ```
diff --git a/maud/Cargo.toml b/maud/Cargo.toml
index d908853..d4b61ca 100644
--- a/maud/Cargo.toml
+++ b/maud/Cargo.toml
@@ -21,12 +21,12 @@ axum = ["axum-core", "http"]
 [dependencies]
 maud_macros = { version = "0.25.0", path = "../maud_macros" }
 itoa = "1"
-rocket = { version = ">= 0.3, < 0.5", optional = true }
+rocket = { version = "0.5", optional = true }
 futures-util = { version = "0.3.0", optional = true, default-features = false }
 actix-web-dep = { package = "actix-web", version = "4", optional = true, default-features = false }
 tide = { version = "0.16.0", optional = true, default-features = false }
-axum-core = { version = "0.3", optional = true }
-http = { version = "0.2", optional = true }
+axum-core = { version = "0.4", optional = true }
+http = { version = "1", optional = true }
 
 [dev-dependencies]
 trybuild = { version = "1.0.33", features = ["diff"] }
diff --git a/maud/src/lib.rs b/maud/src/lib.rs
index 53aa3fa..d6b45b7 100644
--- a/maud/src/lib.rs
+++ b/maud/src/lib.rs
@@ -284,17 +284,17 @@ mod rocket_support {
     use crate::PreEscaped;
     use alloc::string::String;
     use rocket::{
-        http::{ContentType, Status},
+        http::ContentType,
         request::Request,
         response::{Responder, Response},
     };
     use std::io::Cursor;
 
-    impl Responder<'static> for PreEscaped<String> {
-        fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
+    impl<'r> Responder<'r, 'static> for PreEscaped<String> {
+        fn respond_to(self, _: &Request) -> rocket::response::Result<'static> {
             Response::build()
                 .header(ContentType::HTML)
-                .sized_body(Cursor::new(self.0))
+                .sized_body(self.0.len(), Cursor::new(self.0))
                 .ok()
         }
     }
@@ -338,11 +338,11 @@ mod tide_support {
 mod axum_support {
     use crate::PreEscaped;
     use alloc::string::String;
-    use axum_core::{body::BoxBody, response::IntoResponse};
-    use http::{header, HeaderMap, HeaderValue, Response};
+    use axum_core::response::{IntoResponse, Response};
+    use http::{header, HeaderMap, HeaderValue};
 
     impl IntoResponse for PreEscaped<String> {
-        fn into_response(self) -> Response<BoxBody> {
+        fn into_response(self) -> Response {
             let mut headers = HeaderMap::new();
             headers.insert(
                 header::CONTENT_TYPE,
diff --git a/maud_macros/Cargo.toml b/maud_macros/Cargo.toml
index 1c5c4e8..2f101fb 100644
--- a/maud_macros/Cargo.toml
+++ b/maud_macros/Cargo.toml
@@ -11,10 +11,10 @@ description = "Compile-time HTML templates."
 edition = "2021"
 
 [dependencies]
-syn = "1.0.8"
+syn = "2"
 quote = "1.0.7"
 proc-macro2 = "1.0.23"
-proc-macro-error = "1.0.0"
+proc-macro-error = { version = "1.0.0", default-features = false }
 
 [lib]
 name = "maud_macros"
diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index 662b830..1b85d1f 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -204,7 +204,13 @@ impl Parser {
             }
             // Boolean literals are idents, so `Lit::Bool` is handled in
             // `markup`, not here.
-            Lit::Int(..) | Lit::Float(..) => {
+            Lit::Int(lit_int) => {
+                return ast::Markup::Literal {
+                    content: lit_int.to_string(),
+                    span: SpanRange::single_span(literal.span()),
+                };
+            }
+            Lit::Float(..) => {
                 emit_error!(literal, r#"literal must be double-quoted: `"{}"`"#, literal);
             }
             Lit::Char(lit_char) => {
@@ -702,27 +708,32 @@ impl Parser {
     /// Parses an identifier, without dealing with namespaces.
     fn try_name(&mut self) -> Option<TokenStream> {
         let mut result = Vec::new();
-        match self.peek() {
-            Some(token @ TokenTree::Ident(_)) | Some(token @ TokenTree::Literal(_)) => {
-                self.advance();
-                result.push(token);
-            }
-            _ => return None,
-        };
-        let mut expect_ident = false;
+        let mut expect_ident_or_literal = true;
         loop {
-            expect_ident = match self.peek() {
+            expect_ident_or_literal = match self.peek() {
                 Some(TokenTree::Punct(ref punct)) if punct.as_char() == '-' => {
                     self.advance();
                     result.push(TokenTree::Punct(punct.clone()));
                     true
                 }
-                Some(TokenTree::Ident(ref ident)) if expect_ident => {
+                Some(token @ TokenTree::Ident(_)) if expect_ident_or_literal => {
                     self.advance();
-                    result.push(TokenTree::Ident(ident.clone()));
+                    result.push(token);
                     false
                 }
-                _ => break,
+                Some(TokenTree::Literal(ref literal)) if expect_ident_or_literal => {
+                    self.literal(literal.clone());
+                    self.advance();
+                    result.push(TokenTree::Literal(literal.clone()));
+                    false
+                }
+                _ => {
+                    if result.is_empty() {
+                        return None;
+                    } else {
+                        break;
+                    }
+                }
             };
         }
         Some(result.into_iter().collect())


Test patch:
diff --git a/doctest/Cargo.toml b/doctest/Cargo.toml
index 35df0a4..9f47943 100644
--- a/doctest/Cargo.toml
+++ b/doctest/Cargo.toml
@@ -9,11 +9,11 @@ actix-web = { version = "4.0.0-rc.2", default-features = false, features = ["mac
 ammonia = "3"
 maud = { path = "../maud", features = ["actix-web", "rocket", "tide", "axum"] }
 pulldown-cmark = "0.8"
-rocket = "0.4"
+rocket = "0.5"
 rouille = "3"
 tide = "0.16"
 tokio = { version = "1.9.0", features = ["rt", "macros", "rt-multi-thread"] }
-axum = "0.6"
+axum = "0.7"
 
 [dependencies.async-std]
 version = "1.9.0"
diff --git a/maud/tests/basic_syntax.rs b/maud/tests/basic_syntax.rs
index 96e0235..c2c5dc5 100644
--- a/maud/tests/basic_syntax.rs
+++ b/maud/tests/basic_syntax.rs
@@ -201,11 +201,19 @@ fn raw_string_literals_in_attribute_names() {
 
 #[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 {} };
+    let result = html! { this r#"raw_string"#="false" 123="123" 123usize "2.5" true of-course {} };
     assert_eq!(
         result.into_string(),
-        r#"<this byte_string="false" 123="123" 2.5 true a="a" b="b" of-course></this>"#
+        r#"<this raw_string="false" 123="123" 123usize 2.5 true of-course></this>"#
+    );
+}
+
+#[test]
+fn idents_and_literals_in_names() {
+    let result = html! { custom:element-001 test:123-"test"="123" .m-2.p-2 {} };
+    assert_eq!(
+        result.into_string(),
+        r#"<custom:element-001 class="m-2 p-2" test:123-test="123"></custom:element-001>"#
     );
 }
 
diff --git a/maud/tests/warnings/non-string-literal.stderr b/maud/tests/warnings/non-string-literal.stderr
index 635617b..7755e3f 100644
--- a/maud/tests/warnings/non-string-literal.stderr
+++ b/maud/tests/warnings/non-string-literal.stderr
@@ -1,41 +1,29 @@
-error: literal must be double-quoted: `"42"`
- --> $DIR/non-string-literal.rs:5:9
-  |
-5 |         42
-  |         ^^
-
-error: literal must be double-quoted: `"42usize"`
- --> $DIR/non-string-literal.rs:6:9
-  |
-6 |         42usize
-  |         ^^^^^^^
-
 error: literal must be double-quoted: `"42.0"`
- --> $DIR/non-string-literal.rs:7:9
+ --> tests/warnings/non-string-literal.rs:7:9
   |
 7 |         42.0
   |         ^^^^
 
 error: literal must be double-quoted: `"a"`
- --> $DIR/non-string-literal.rs:8:9
+ --> tests/warnings/non-string-literal.rs:8:9
   |
 8 |         'a'
   |         ^^^
 
 error: expected string
- --> $DIR/non-string-literal.rs:9:9
+ --> tests/warnings/non-string-literal.rs:9:9
   |
 9 |         b"a"
   |         ^^^^
 
 error: expected string
-  --> $DIR/non-string-literal.rs:10:9
+  --> tests/warnings/non-string-literal.rs:10:9
    |
 10 |         b'a'
    |         ^^^^
 
 error: attribute value must be a string
-  --> $DIR/non-string-literal.rs:13:24
+  --> tests/warnings/non-string-literal.rs:13:24
    |
 13 |         input disabled=true;
    |                        ^^^^
@@ -44,7 +32,7 @@ error: attribute value must be a string
    = help: to toggle the attribute, use square brackets: `disabled[some_boolean_flag]`
 
 error: attribute value must be a string
-  --> $DIR/non-string-literal.rs:14:24
+  --> tests/warnings/non-string-literal.rs:14:24
    |
 14 |         input disabled=false;
    |                        ^^^^^