The`document_features!()`macroanalyzesthecontentsof`Cargo.toml`. SimilartoRust'sdocumentationcomments`///` and `//!`, the macro understands commentsthatstartwith`##`and`#!`.Notetherequiredtrailingspace. Linesstartingwith`###`willnotbeunderstoodasdoccomment.
#[cfg(not(feature = "default"))]
compile_error!( "The feature `default` must be enabled to ensure \
forward compatibility with future version of this crate"
);
externcrate proc_macro;
use proc_macro::{TokenStream, TokenTree}; use std::borrow::Cow; use std::collections::HashSet; use std::convert::TryFrom; use std::fmt::Write; use std::path::Path; use std::str::FromStr;
// parse the key, ensuring that it is the identifier `feature_label` match token_trees.next() {
None => return Ok(Args::default()),
Some(TokenTree::Ident(ident)) if ident.to_string() == "feature_label" => (),
tt => return Err(compile_error("expected `feature_label`", tt)),
}
// parse a single equal sign `=` match token_trees.next() {
Some(TokenTree::Punct(p)) if p.as_char() == '=' => (),
tt => return Err(compile_error("expected `=`", tt)),
}
// parse the value, ensuring that it is a string literal containing the substring `"{feature}"` let feature_label; iflet Some(tt) = token_trees.next() { match litrs::StringLit::<String>::try_from(&tt) {
Ok(string_lit) if string_lit.value().contains("{feature}") => {
feature_label = string_lit.value().to_string()
}
_ => { return Err(compile_error( "expected a string literal containing the substring \"{feature}\"",
Some(tt),
))
}
}
} else { return Err(compile_error( "expected a string literal containing the substring \"{feature}\"",
None,
));
}
// ensure there is nothing left after the format string iflet tt @ Some(_) = token_trees.next() { return Err(compile_error("unexpected token after the format string", tt));
}
Ok(Args { feature_label: Some(feature_label) })
}
/// Produce a literal string containing documentation extracted from Cargo.toml /// /// See the [crate] documentation for details #[proc_macro] pubfn document_features(tokens: TokenStream) -> TokenStream {
parse_args(tokens)
.and_then(|args| document_features_impl(&args))
.unwrap_or_else(std::convert::identity)
}
if !has_doc_comments(&cargo_toml) { // On crates.io, Cargo.toml is usually "normalized" and stripped of all comments. // The original Cargo.toml has been renamed Cargo.toml.orig iflet Ok(orig) = std::fs::read_to_string(Path::new(&path).join("Cargo.toml.orig")) { if has_doc_comments(&orig) {
cargo_toml = orig;
}
}
}
let result = process_toml(&cargo_toml, args).map_err(|e| error(&e))?;
Ok(std::iter::once(proc_macro::TokenTree::from(proc_macro::Literal::string(&result))).collect())
}
assert!(!has_doc_comments(
r#"
[[package.metadata.release.pre-release-replacements]]
value = """" string \""" ## within the string
\""""
another_string = """"" # """ ## also within""" "#
));
assert!(has_doc_comments(
r#"
[[package.metadata.release.pre-release-replacements]]
value = """" string \""" ## within the string
\""""
another_string = """"" # """ ## also within""" ## out of the string
foo = bar "#
));
}
fn process_toml(cargo_toml: &str, args: &Args) -> Result<String, String> { // Get all lines between the "[features]" and the next block letmut lines = cargo_toml
.lines()
.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("#!"))
}); letmut top_comment = String::new(); letmut current_comment = String::new(); letmut features = vec![]; letmut default_features = HashSet::new(); letmut current_table = ""; whilelet Some(line) = lines.next() { iflet Some(x) = line.strip_prefix("#!") { 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();
} elseiflet Some(x) = line.strip_prefix("##") { if !x.is_empty() && !x.starts_with(' ') { continue; // it's not a doc comment
}
writeln!(current_comment, " {}", x).unwrap();
} elseiflet Some(table) = line.strip_prefix('[') {
current_table = table
.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('.')
.and_then(|(table, dep)| table.trim().ends_with("dependencies").then(|| dep))
.ok_or_else(|| format!("Not a feature: `{}`", line))?;
features.push((
dep.trim(),
std::mem::take(&mut top_comment),
std::mem::take(&mut current_comment),
));
}
} elseiflet 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(']'))
.ok_or_else(|| format!("Parse error while parsing dependency {}", dep))?
.split(',')
.map(|d| d.trim().trim_matches(|c| c == '"' || c == '\'').trim().to_string())
.filter(|d| !d.is_empty());
default_features.extend(defaults);
} if !current_comment.is_empty() { if current_table.ends_with("dependencies") { if !rest
.split_once("optional")
.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));
}
} elseif current_table != "features" { return Err(format!(
r#"Comment cannot be associated with a feature: "{}""#,
current_comment.trim()
));
}
features.push((
dep,
std::mem::take(&mut top_comment),
std::mem::take(&mut current_comment),
));
}
}
} if !current_comment.is_empty() { return Err("Found comment not associated with a feature".into());
} if features.is_empty() { return Ok("*No documented features in Cargo.toml*".into());
} letmut result = String::new(); for (f, top, comment) in features { let default = if default_features.contains(f) { " *(enabled by default)*" } else { "" }; if !comment.trim().is_empty() { iflet Some(feature_label) = &args.feature_label {
writeln!(
result, "{}* {}{} —{}",
top,
feature_label.replace("{feature}", f),
default,
comment.trim_end(),
)
.unwrap();
} else {
writeln!(result, "{}* **`{}`**{} —{}", top, f, default, comment.trim_end())
.unwrap();
}
} elseiflet Some(feature_label) = &args.feature_label {
writeln!(result, "{}* {}{}", top, feature_label.replace("{feature}", f), default,)
.unwrap();
} else {
writeln!(result, "{}* **`{}`**{}", top, f, default).unwrap();
}
}
result += &top_comment;
Ok(result)
}
#[cfg(feature = "self-test")] #[proc_macro] #[doc(hidden)] /// Helper macro for the tests. Do not use pubfn self_test_helper(input: TokenStream) -> TokenStream { letmut code = String::new(); for line in (&input).to_string().trim_matches(|c| c == '"' || c == '#').lines() { // Rustdoc removes the lines that starts with `# ` and removes one `#` from lines that starts with # followed by space. // We need to re-add the `#` that was removed by rustdoc to get the original. if line.strip_prefix('#').map_or(false, |x| x.is_empty() || x.starts_with(' ')) {
code += "#";
}
code += line;
code += "\n";
}
process_toml(&code, &Args::default()).map_or_else(
|e| error(&e),
|r| std::iter::once(proc_macro::TokenTree::from(proc_macro::Literal::string(&r))).collect(),
)
}
#[test] fn dots_in_feature() { let toml = r#"
[features] ## This is a test "teßt." = []
default = ["teßt."]
[dependencies] ## A dep "dep" = { version = "123", optional = true } "#; let parsed = process_toml(toml, &Args::default()).unwrap();
assert_eq!(
parsed, "* **`teßt.`** *(enabled by default)* — This is a test\n* **`dep`** — A dep\n"
); let parsed = process_toml(
toml,
&Args {
feature_label: Some( "<span class=\"stab portability\"><code>{feature}</code></span>".into(),
),
},
)
.unwrap();
assert_eq!(
parsed, "* <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"
);
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 2026-06-19)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.