Upgrade document-features to 0.2.8

This project was upgraded with external_updater.
Usage: tools/external_updater/updater.sh update external/rust/crates/document-features
For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md

Test: TreeHugger
Change-Id: I1ed921aa265598b26cc9d63d6372b090b9d022e1
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index 2cc8384..7feaa35 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
 {
   "git": {
-    "sha1": "2f77c4e102d4546eb4a2291b396ed39c4533df1d"
+    "sha1": "0ed8e43fed1eb791de0651b520e4c7a35bfdf8d7"
   },
   "path_in_vcs": ""
 }
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
index 05a67ac..f066a2a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -43,7 +43,7 @@
     name: "libdocument_features",
     crate_name: "document_features",
     cargo_env_compat: true,
-    cargo_pkg_version: "0.2.7",
+    cargo_pkg_version: "0.2.8",
     srcs: ["lib.rs"],
     edition: "2018",
     features: ["default"],
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa4f453..f94ee19 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,12 @@
 # Changelog
 
-## 0.2.7 - 2002-12-21
+
+## 0.2.7 - 2023-12-29
+
+* Remove `\n` between features (#17)
+* Don't throw an error when there is no features in Cargo.toml (#20)
+
+## 0.2.7 - 2022-12-21
 
 * Fix parsing of Cargo.toml with multi-line array of array (#16)
 
diff --git a/Cargo.toml b/Cargo.toml
index e7d6d33..bae5ff2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@
 [package]
 edition = "2018"
 name = "document-features"
-version = "0.2.7"
+version = "0.2.8"
 authors = ["Slint Developers <info@slint-ui.com>"]
 description = "Extract documentation for the feature flags from comments in Cargo.toml"
 homepage = "https://slint-ui.com"
@@ -32,7 +32,7 @@
 proc-macro = true
 
 [dependencies.litrs]
-version = "0.2.3"
+version = "0.4.1"
 default-features = false
 
 [features]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index c4ba7a9..471921a 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -3,7 +3,7 @@
 
 [package]
 name = "document-features"
-version = "0.2.7"
+version = "0.2.8"
 authors = ["Slint Developers <info@slint-ui.com>"]
 edition = "2018"
 license = "MIT OR Apache-2.0"
@@ -24,4 +24,4 @@
 self-test = []
 
 [dependencies]
-litrs = { version = "0.2.3", default-features = false }
+litrs = { version = "0.4.1", default-features = false }
diff --git a/METADATA b/METADATA
index 17e591a..6886418 100644
--- a/METADATA
+++ b/METADATA
@@ -1,23 +1,20 @@
 # This project was upgraded with external_updater.
-# Usage: tools/external_updater/updater.sh update rust/crates/document-features
-# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+# Usage: tools/external_updater/updater.sh update external/rust/crates/document-features
+# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
 
 name: "document-features"
 description: "Extract documentation for the feature flags from comments in Cargo.toml"
 third_party {
-  url {
-    type: HOMEPAGE
-    value: "https://crates.io/crates/document-features"
-  }
-  url {
-    type: ARCHIVE
-    value: "https://static.crates.io/crates/document-features/document-features-0.2.7.crate"
-  }
-  version: "0.2.7"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2023
+    year: 2024
     month: 2
-    day: 1
+    day: 8
+  }
+  homepage: "https://crates.io/crates/document-features"
+  identifier {
+    type: "Archive"
+    value: "https://static.crates.io/crates/document-features/document-features-0.2.8.crate"
+    version: "0.2.8"
   }
 }
diff --git a/lib.rs b/lib.rs
index 5ce0b50..b30ebe7 100644
--- a/lib.rs
+++ b/lib.rs
@@ -83,7 +83,6 @@
     /**
 This comments goes on top
 * **`foo`** *(enabled by default)* —  The foo feature enables the `foo` functions
-
 * **`bar`** —  The bar feature enables the bar module
 
 #### Experimental features
@@ -94,9 +93,7 @@
 
 #### Optional dependencies
 * **`genial`** —  Enable this feature to implement the trait for the types from the genial crate
-
 * **`awesome`** —  This awesome dependency is specified in its own table
-
 */
 )]
 /*!
@@ -280,7 +277,7 @@
         .map(str::trim)
         // and skip empty lines and comments that are not docs comments
         .filter(|l| {
-            !l.is_empty() && (!l.starts_with("#") || l.starts_with("##") || l.starts_with("#!"))
+            !l.is_empty() && (!l.starts_with('#') || l.starts_with("##") || l.starts_with("#!"))
         });
     let mut top_comment = String::new();
     let mut current_comment = String::new();
@@ -289,26 +286,29 @@
     let mut current_table = "";
     while let Some(line) = lines.next() {
         if let Some(x) = line.strip_prefix("#!") {
-            if !x.is_empty() && !x.starts_with(" ") {
+            if !x.is_empty() && !x.starts_with(' ') {
                 continue; // it's not a doc comment
             }
             if !current_comment.is_empty() {
                 return Err("Cannot mix ## and #! comments between features.".into());
             }
+            if top_comment.is_empty() && !features.is_empty() {
+                top_comment = "\n".into();
+            }
             writeln!(top_comment, "{}", x).unwrap();
         } else if let Some(x) = line.strip_prefix("##") {
-            if !x.is_empty() && !x.starts_with(" ") {
+            if !x.is_empty() && !x.starts_with(' ') {
                 continue; // it's not a doc comment
             }
             writeln!(current_comment, " {}", x).unwrap();
-        } else if let Some(table) = line.strip_prefix("[") {
+        } else if let Some(table) = line.strip_prefix('[') {
             current_table = table
-                .split_once("]")
+                .split_once(']')
                 .map(|(t, _)| t.trim())
                 .ok_or_else(|| format!("Parse error while parsing line: {}", line))?;
             if !current_comment.is_empty() {
                 let dep = current_table
-                    .rsplit_once(".")
+                    .rsplit_once('.')
                     .and_then(|(table, dep)| table.trim().ends_with("dependencies").then(|| dep))
                     .ok_or_else(|| format!("Not a feature: `{}`", line))?;
                 features.push((
@@ -317,17 +317,17 @@
                     std::mem::take(&mut current_comment),
                 ));
             }
-        } else if let Some((dep, rest)) = line.split_once("=") {
+        } else if let Some((dep, rest)) = line.split_once('=') {
             let dep = dep.trim().trim_matches('"');
             let rest = get_balanced(rest, &mut lines)
                 .map_err(|e| format!("Parse error while parsing value {}: {}", dep, e))?;
             if current_table == "features" && dep == "default" {
                 let defaults = rest
                     .trim()
-                    .strip_prefix("[")
-                    .and_then(|r| r.strip_suffix("]"))
+                    .strip_prefix('[')
+                    .and_then(|r| r.strip_suffix(']'))
                     .ok_or_else(|| format!("Parse error while parsing dependency {}", dep))?
-                    .split(",")
+                    .split(',')
                     .map(|d| d.trim().trim_matches(|c| c == '"' || c == '\'').trim().to_string())
                     .filter(|d| !d.is_empty());
                 default_features.extend(defaults);
@@ -336,15 +336,15 @@
                 if current_table.ends_with("dependencies") {
                     if !rest
                         .split_once("optional")
-                        .and_then(|(_, r)| r.trim().strip_prefix("="))
+                        .and_then(|(_, r)| r.trim().strip_prefix('='))
                         .map_or(false, |r| r.trim().starts_with("true"))
                     {
                         return Err(format!("Dependency {} is not an optional dependency", dep));
                     }
                 } else if current_table != "features" {
                     return Err(format!(
-                        "Comment cannot be associated with a feature:{}",
-                        current_comment
+                        r#"Comment cannot be associated with a feature: "{}""#,
+                        current_comment.trim()
                     ));
                 }
                 features.push((
@@ -359,7 +359,7 @@
         return Err("Found comment not associated with a feature".into());
     }
     if features.is_empty() {
-        return Err("Could not find documented features in Cargo.toml".into());
+        return Ok("*No documented features in Cargo.toml*".into());
     }
     let mut result = String::new();
     for (f, top, comment) in features {
@@ -372,25 +372,18 @@
                     top,
                     feature_label.replace("{feature}", f),
                     default,
-                    comment
+                    comment.trim_end(),
                 )
                 .unwrap();
             } else {
-                writeln!(result, "{}* **`{}`**{} —{}", top, f, default, comment).unwrap();
+                writeln!(result, "{}* **`{}`**{} —{}", top, f, default, comment.trim_end())
+                    .unwrap();
             }
+        } else if let Some(feature_label) = &args.feature_label {
+            writeln!(result, "{}* {}{}", top, feature_label.replace("{feature}", f), default,)
+                .unwrap();
         } else {
-            if let Some(feature_label) = &args.feature_label {
-                writeln!(
-                    result,
-                    "{}* {}{}\n",
-                    top,
-                    feature_label.replace("{feature}", f),
-                    default,
-                )
-                .unwrap();
-            } else {
-                writeln!(result, "{}* **`{}`**{}\n", top, f, default).unwrap();
-            }
+            writeln!(result, "{}* **`{}`**{}", top, f, default).unwrap();
         }
     }
     result += &top_comment;
@@ -408,7 +401,7 @@
     let mut level = 0;
     loop {
         let mut last_slash = false;
-        for (idx, b) in line.as_bytes().into_iter().enumerate() {
+        for (idx, b) in line.as_bytes().iter().enumerate() {
             if last_slash {
                 last_slash = false
             } else if in_quote {
@@ -590,26 +583,30 @@
     }
 
     #[test]
-    fn parse_error1() {
-        test_error(
+    fn no_features() {
+        let r = process_toml(
             r#"
 [features]
 [dependencies]
 foo = 4;
 "#,
-            "Could not find documented features",
-        );
+            &Args::default(),
+        )
+        .unwrap();
+        assert_eq!(r, "*No documented features in Cargo.toml*");
     }
 
     #[test]
-    fn parse_error2() {
-        test_error(
+    fn no_features2() {
+        let r = process_toml(
             r#"
 [packages]
 [dependencies]
 "#,
-            "Could not find documented features",
-        );
+            &Args::default(),
+        )
+        .unwrap();
+        assert_eq!(r, "*No documented features in Cargo.toml*");
     }
 
     #[test]
@@ -698,7 +695,7 @@
 ## hallo
 foo = []
 "#,
-            "Comment cannot be associated with a feature:  hallo",
+            "Comment cannot be associated with a feature: \"hallo\"",
         );
     }
 
@@ -782,7 +779,7 @@
 optional = true
         "#;
         let parsed = process_toml(toml, &Args::default()).unwrap();
-        assert_eq!(parsed, " top\n* **`dep1`** —  dep1\n\n yo\n* **`dep3`** —  dep3\n\n");
+        assert_eq!(parsed, " top\n* **`dep1`** —  dep1\n\n yo\n* **`dep3`** —  dep3\n");
         let parsed = process_toml(
             toml,
             &Args {
@@ -792,7 +789,7 @@
             },
         )
         .unwrap();
-        assert_eq!(parsed, " top\n* <span class=\"stab portability\"><code>dep1</code></span> —  dep1\n\n yo\n* <span class=\"stab portability\"><code>dep3</code></span> —  dep3\n\n");
+        assert_eq!(parsed, " top\n* <span class=\"stab portability\"><code>dep1</code></span> —  dep1\n\n yo\n* <span class=\"stab portability\"><code>dep3</code></span> —  dep3\n");
     }
 
     #[test]
@@ -830,7 +827,7 @@
         let parsed = process_toml(toml, &Args::default()).unwrap();
         assert_eq!(
             parsed,
-            "* **`dep1`** —  dep1\n\n* **`foo`** —  foo\n\n* **`bar`** *(enabled by default)* —  bar\n\n"
+            "* **`dep1`** —  dep1\n* **`foo`** —  foo\n* **`bar`** *(enabled by default)* —  bar\n"
         );
         let parsed = process_toml(
             toml,
@@ -843,7 +840,7 @@
         .unwrap();
         assert_eq!(
             parsed,
-            "* <span class=\"stab portability\"><code>dep1</code></span> —  dep1\n\n* <span class=\"stab portability\"><code>foo</code></span> —  foo\n\n* <span class=\"stab portability\"><code>bar</code></span> *(enabled by default)* —  bar\n\n"
+            "* <span class=\"stab portability\"><code>dep1</code></span> —  dep1\n* <span class=\"stab portability\"><code>foo</code></span> —  foo\n* <span class=\"stab portability\"><code>bar</code></span> *(enabled by default)* —  bar\n"
         );
     }
 
@@ -861,7 +858,7 @@
         let parsed = process_toml(toml, &Args::default()).unwrap();
         assert_eq!(
             parsed,
-            "* **`teßt.`** *(enabled by default)* —  This is a test\n\n* **`dep`** —  A dep\n\n"
+            "* **`teßt.`** *(enabled by default)* —  This is a test\n* **`dep`** —  A dep\n"
         );
         let parsed = process_toml(
             toml,
@@ -874,7 +871,7 @@
         .unwrap();
         assert_eq!(
             parsed,
-            "* <span class=\"stab portability\"><code>teßt.</code></span> *(enabled by default)* —  This is a test\n\n* <span class=\"stab portability\"><code>dep</code></span> —  A dep\n\n"
+            "* <span class=\"stab portability\"><code>teßt.</code></span> *(enabled by default)* —  This is a test\n* <span class=\"stab portability\"><code>dep</code></span> —  A dep\n"
         );
     }
 }
diff --git a/tests/self-doc.rs b/tests/self-doc.rs
index 9364566..4e27113 100644
--- a/tests/self-doc.rs
+++ b/tests/self-doc.rs
@@ -18,8 +18,7 @@
 #[test]
 fn self_doc() {
     let actual = document_features::document_features!();
-    let expected =
-        "* **`self-test`** —  Internal feature used only for the tests, don't enable\n\n";
+    let expected = "* **`self-test`** —  Internal feature used only for the tests, don't enable\n";
     assert_eq!(actual, expected);
 }
 
@@ -29,7 +28,7 @@
         feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#
     );
     let expected =
-        "* <span class=\"stab portability\"><code>self-test</code></span> —  Internal feature used only for the tests, don't enable\n\n";
+        "* <span class=\"stab portability\"><code>self-test</code></span> —  Internal feature used only for the tests, don't enable\n";
     assert_eq!(actual, expected);
     let actual2 = document_features::document_features!(
         feature_label = "<span class=\"stab\u{0020}portability\"><code>{feature}</code></span>"