Skip to content

Commit f7e6bf6

Browse files
generate-copyright: use rinja to format the output
I can't find a way to derive rinja::Template for Node - I think because it is a recursive type. So I rendered it manually using html_escape.
1 parent dbab595 commit f7e6bf6

File tree

4 files changed

+146
-152
lines changed

4 files changed

+146
-152
lines changed

src/tools/generate-copyright/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ description = "Produces a manifest of all the copyrighted materials in the Rust
88

99
[dependencies]
1010
anyhow = "1.0.65"
11+
cargo_metadata = "0.18.1"
12+
html-escape = "0.2.13"
13+
rinja = "0.2.0"
1114
serde = { version = "1.0.147", features = ["derive"] }
1215
serde_json = "1.0.85"
13-
thiserror = "1"
1416
tempfile = "3"
15-
cargo_metadata = "0.18.1"
17+
thiserror = "1"

src/tools/generate-copyright/src/cargo_metadata.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Gets metadata about a workspace from Cargo
22
33
use std::collections::BTreeMap;
4-
use std::ffi::{OsStr, OsString};
4+
use std::ffi::OsStr;
55
use std::path::Path;
66

77
/// Describes how this module can fail
@@ -36,7 +36,9 @@ pub struct PackageMetadata {
3636
/// A list of important files from the package, with their contents.
3737
///
3838
/// This includes *COPYRIGHT*, *NOTICE*, *AUTHOR*, *LICENSE*, and *LICENCE* files, case-insensitive.
39-
pub notices: BTreeMap<OsString, String>,
39+
pub notices: BTreeMap<String, String>,
40+
/// If this is true, this dep is in the Rust Standard Library
41+
pub is_in_libstd: Option<bool>,
4042
}
4143

4244
/// Use `cargo metadata` and `cargo vendor` to get a list of dependencies and their license data.
@@ -101,6 +103,7 @@ pub fn get_metadata(
101103
license: package.license.unwrap_or_else(|| String::from("Unspecified")),
102104
authors: package.authors,
103105
notices: BTreeMap::new(),
106+
is_in_libstd: None,
104107
},
105108
);
106109
}
@@ -161,8 +164,9 @@ fn load_important_files(
161164
if metadata.is_dir() {
162165
// scoop up whole directory
163166
} else if metadata.is_file() {
164-
println!("Scraping {}", filename.to_string_lossy());
165-
dep.notices.insert(filename.to_owned(), std::fs::read_to_string(path)?);
167+
let filename = filename.to_string_lossy();
168+
println!("Scraping {}", filename);
169+
dep.notices.insert(filename.to_string(), std::fs::read_to_string(path)?);
166170
}
167171
}
168172
}
Lines changed: 80 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,17 @@
11
use std::collections::BTreeMap;
2-
use std::io::Write;
32
use std::path::{Path, PathBuf};
43

54
use anyhow::Error;
5+
use rinja::Template;
66

77
mod cargo_metadata;
88

9-
static TOP_BOILERPLATE: &str = r##"
10-
<!DOCTYPE html>
11-
<html>
12-
<head>
13-
<meta charset="UTF-8">
14-
<title>Copyright notices for The Rust Toolchain</title>
15-
</head>
16-
<body>
17-
18-
<h1>Copyright notices for The Rust Toolchain</h1>
19-
20-
<p>This file describes the copyright and licensing information for the source
21-
code within The Rust Project git tree, and the third-party dependencies used
22-
when building the Rust toolchain (including the Rust Standard Library).</p>
23-
24-
<h2>Table of Contents</h2>
25-
<ul>
26-
<li><a href="#in-tree-files">In-tree files</a></li>
27-
<li><a href="#out-of-tree-dependencies">Out-of-tree dependencies</a></li>
28-
</ul>
29-
"##;
30-
31-
static BOTTOM_BOILERPLATE: &str = r#"
32-
</body>
33-
</html>
34-
"#;
9+
#[derive(Template)]
10+
#[template(path = "COPYRIGHT.html")]
11+
struct CopyrightTemplate {
12+
in_tree: Node,
13+
dependencies: BTreeMap<cargo_metadata::Package, cargo_metadata::PackageMetadata>,
14+
}
3515

3616
/// The entry point to the binary.
3717
///
@@ -53,150 +33,114 @@ fn main() -> Result<(), Error> {
5333
Path::new("./src/tools/cargo/Cargo.toml"),
5434
Path::new("./library/std/Cargo.toml"),
5535
];
56-
let collected_cargo_metadata =
36+
let mut collected_cargo_metadata =
5737
cargo_metadata::get_metadata_and_notices(&cargo, &out_dir, &root_path, &workspace_paths)?;
5838

5939
let stdlib_set =
6040
cargo_metadata::get_metadata(&cargo, &root_path, &[Path::new("./library/std/Cargo.toml")])?;
6141

62-
let mut buffer = Vec::new();
42+
for (key, value) in collected_cargo_metadata.iter_mut() {
43+
value.is_in_libstd = Some(stdlib_set.contains_key(key));
44+
}
6345

64-
writeln!(buffer, "{}", TOP_BOILERPLATE)?;
46+
let template = CopyrightTemplate {
47+
in_tree: collected_tree_metadata.files,
48+
dependencies: collected_cargo_metadata,
49+
};
6550

66-
writeln!(
67-
buffer,
68-
r#"<h2 id="in-tree-files">In-tree files</h2><p>The following licenses cover the in-tree source files that were used in this release:</p>"#
69-
)?;
70-
render_tree_recursive(&collected_tree_metadata.files, &mut buffer)?;
51+
let output = template.render()?;
7152

72-
writeln!(
73-
buffer,
74-
r#"<h2 id="out-of-tree-dependencies">Out-of-tree dependencies</h2><p>The following licenses cover the out-of-tree crates that were used in this release:</p>"#
75-
)?;
76-
render_deps(&collected_cargo_metadata, &stdlib_set, &mut buffer)?;
53+
std::fs::write(&dest_file, output)?;
7754

78-
writeln!(buffer, "{}", BOTTOM_BOILERPLATE)?;
55+
Ok(())
56+
}
7957

80-
std::fs::write(&dest_file, &buffer)?;
58+
/// Describes a tree of metadata for our filesystem tree
59+
#[derive(serde::Deserialize)]
60+
struct Metadata {
61+
files: Node,
62+
}
8163

64+
/// Describes one node in our metadata tree
65+
#[derive(serde::Deserialize)]
66+
#[serde(rename_all = "kebab-case", tag = "type")]
67+
pub(crate) enum Node {
68+
Root { children: Vec<Node> },
69+
Directory { name: String, children: Vec<Node>, license: Option<License> },
70+
File { name: String, license: License },
71+
Group { files: Vec<String>, directories: Vec<String>, license: License },
72+
}
73+
74+
fn with_box<F>(fmt: &mut std::fmt::Formatter<'_>, inner: F) -> std::fmt::Result
75+
where
76+
F: FnOnce(&mut std::fmt::Formatter<'_>) -> std::fmt::Result,
77+
{
78+
writeln!(fmt, r#"<div style="border:1px solid black; padding: 5px;">"#)?;
79+
inner(fmt)?;
80+
writeln!(fmt, "</div>")?;
8281
Ok(())
8382
}
8483

85-
/// Recursively draw the tree of files/folders we found on disk and their licenses, as
86-
/// markdown, into the given Vec.
87-
fn render_tree_recursive(node: &Node, buffer: &mut Vec<u8>) -> Result<(), Error> {
88-
writeln!(buffer, r#"<div style="border:1px solid black; padding: 5px;">"#)?;
89-
match node {
90-
Node::Root { children } => {
91-
for child in children {
92-
render_tree_recursive(child, buffer)?;
84+
impl std::fmt::Display for Node {
85+
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86+
match self {
87+
Node::Root { children } => {
88+
if children.len() > 1 {
89+
with_box(fmt, |f| {
90+
for child in children {
91+
writeln!(f, "{child}")?;
92+
}
93+
Ok(())
94+
})
95+
} else {
96+
for child in children {
97+
writeln!(fmt, "{child}")?;
98+
}
99+
Ok(())
100+
}
93101
}
94-
}
95-
Node::Directory { name, children, license } => {
96-
render_tree_license(std::iter::once(name), license.as_ref(), buffer)?;
97-
if !children.is_empty() {
98-
writeln!(buffer, "<p><b>Exceptions:</b></p>")?;
99-
for child in children {
100-
render_tree_recursive(child, buffer)?;
102+
Node::Directory { name, children, license } => with_box(fmt, |f| {
103+
render_tree_license(std::iter::once(name), license.as_ref(), f)?;
104+
if !children.is_empty() {
105+
writeln!(f, "<p><b>Exceptions:</b></p>")?;
106+
for child in children {
107+
writeln!(f, "{child}")?;
108+
}
101109
}
110+
Ok(())
111+
}),
112+
Node::Group { files, directories, license } => with_box(fmt, |f| {
113+
render_tree_license(directories.iter().chain(files.iter()), Some(license), f)
114+
}),
115+
Node::File { name, license } => {
116+
with_box(fmt, |f| render_tree_license(std::iter::once(name), Some(license), f))
102117
}
103118
}
104-
Node::Group { files, directories, license } => {
105-
render_tree_license(directories.iter().chain(files.iter()), Some(license), buffer)?;
106-
}
107-
Node::File { name, license } => {
108-
render_tree_license(std::iter::once(name), Some(license), buffer)?;
109-
}
110119
}
111-
writeln!(buffer, "</div>")?;
112-
113-
Ok(())
114120
}
115121

116-
/// Draw a series of sibling files/folders, as markdown, into the given Vec.
122+
/// Draw a series of sibling files/folders, as HTML, into the given formatter.
117123
fn render_tree_license<'a>(
118124
names: impl Iterator<Item = &'a String>,
119125
license: Option<&License>,
120-
buffer: &mut Vec<u8>,
121-
) -> Result<(), Error> {
122-
writeln!(buffer, "<p><b>File/Directory:</b> ")?;
126+
f: &mut std::fmt::Formatter<'_>,
127+
) -> std::fmt::Result {
128+
writeln!(f, "<p><b>File/Directory:</b> ")?;
123129
for name in names {
124-
writeln!(buffer, "<code>{name}</code>")?;
130+
writeln!(f, "<code>{}</code>", html_escape::encode_text(&name))?;
125131
}
126-
writeln!(buffer, "</p>")?;
132+
writeln!(f, "</p>")?;
127133

128134
if let Some(license) = license {
129-
writeln!(buffer, "<p><b>License:</b> {}</p>", license.spdx)?;
135+
writeln!(f, "<p><b>License:</b> {}</p>", html_escape::encode_text(&license.spdx))?;
130136
for copyright in license.copyright.iter() {
131-
writeln!(buffer, "<p><b>Copyright:</b> {copyright}</p>")?;
137+
writeln!(f, "<p><b>Copyright:</b> {}</p>", html_escape::encode_text(&copyright))?;
132138
}
133139
}
134140

135141
Ok(())
136142
}
137143

138-
/// Render a list of out-of-tree dependencies as markdown into the given Vec.
139-
fn render_deps(
140-
all_deps: &BTreeMap<cargo_metadata::Package, cargo_metadata::PackageMetadata>,
141-
stdlib_set: &BTreeMap<cargo_metadata::Package, cargo_metadata::PackageMetadata>,
142-
buffer: &mut Vec<u8>,
143-
) -> Result<(), Error> {
144-
for (package, metadata) in all_deps {
145-
let authors_list = if metadata.authors.is_empty() {
146-
"None Specified".to_owned()
147-
} else {
148-
metadata.authors.join(", ")
149-
};
150-
let url = format!("https://crates.io/crates/{}/{}", package.name, package.version);
151-
writeln!(buffer)?;
152-
writeln!(
153-
buffer,
154-
r#"<h3>📦 {name}-{version}</h3>"#,
155-
name = package.name,
156-
version = package.version,
157-
)?;
158-
writeln!(buffer, r#"<p><b>URL:</b> <a href="{url}">{url}</a></p>"#,)?;
159-
writeln!(
160-
buffer,
161-
"<p><b>In libstd:</b> {}</p>",
162-
if stdlib_set.contains_key(package) { "Yes" } else { "No" }
163-
)?;
164-
writeln!(buffer, "<p><b>Authors:</b> {}</p>", escape_html(&authors_list))?;
165-
writeln!(buffer, "<p><b>License:</b> {}</p>", escape_html(&metadata.license))?;
166-
writeln!(buffer, "<p><b>Notices:</b> ")?;
167-
if metadata.notices.is_empty() {
168-
writeln!(buffer, "None")?;
169-
} else {
170-
for (name, contents) in &metadata.notices {
171-
writeln!(
172-
buffer,
173-
"<details><summary><code>{}</code></summary>",
174-
name.to_string_lossy()
175-
)?;
176-
writeln!(buffer, "<pre>\n{}\n</pre>", contents)?;
177-
writeln!(buffer, "</details>")?;
178-
}
179-
}
180-
writeln!(buffer, "</p>")?;
181-
}
182-
Ok(())
183-
}
184-
/// Describes a tree of metadata for our filesystem tree
185-
#[derive(serde::Deserialize)]
186-
struct Metadata {
187-
files: Node,
188-
}
189-
190-
/// Describes one node in our metadata tree
191-
#[derive(serde::Deserialize)]
192-
#[serde(rename_all = "kebab-case", tag = "type")]
193-
pub(crate) enum Node {
194-
Root { children: Vec<Node> },
195-
Directory { name: String, children: Vec<Node>, license: Option<License> },
196-
File { name: String, license: License },
197-
Group { files: Vec<String>, directories: Vec<String>, license: License },
198-
}
199-
200144
/// A License has an SPDX license name and a list of copyright holders.
201145
#[derive(serde::Deserialize)]
202146
struct License {
@@ -212,13 +156,3 @@ fn env_path(var: &str) -> Result<PathBuf, Error> {
212156
anyhow::bail!("missing environment variable {var}")
213157
}
214158
}
215-
216-
/// Escapes any invalid HTML characters
217-
fn escape_html(input: &str) -> String {
218-
static MAPPING: [(char, &str); 3] = [('&', "&amp;"), ('<', "&lt;"), ('>', "&gt;")];
219-
let mut output = input.to_owned();
220-
for (ch, s) in &MAPPING {
221-
output = output.replace(*ch, s);
222-
}
223-
output
224-
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Copyright notices for The Rust Toolchain</title>
6+
</head>
7+
<body>
8+
9+
<h1>Copyright notices for The Rust Toolchain</h1>
10+
11+
<p>This file describes the copyright and licensing information for the source
12+
code within The Rust Project git tree, and the third-party dependencies used
13+
when building the Rust toolchain (including the Rust Standard Library).</p>
14+
15+
<h2>Table of Contents</h2>
16+
<ul>
17+
<li><a href="#in-tree-files">In-tree files</a></li>
18+
<li><a href="#out-of-tree-dependencies">Out-of-tree dependencies</a></li>
19+
</ul>
20+
21+
<h2 id="in-tree-files">In-tree files</h2>
22+
23+
<p>The following licenses cover the in-tree source files that were used in this
24+
release:</p>
25+
26+
{{ in_tree|safe }}
27+
28+
<h2 id="out-of-tree-dependencies">Out-of-tree dependencies</h2>
29+
30+
<p>The following licenses cover the out-of-tree crates that were used in this
31+
release:</p>
32+
33+
{% for (key, value) in dependencies %}
34+
<h3>📦 {{key.name}}-{{key.version}}</h3>
35+
<p><b>URL:</b> <a href="https://crates.io/crates/{{ key.name }}/{{ key.version }}">https://crates.io/crates/{{ key.name }}/{{ key.version }}</a></p>
36+
<p><b>In libstd:</b> {% if value.is_in_libstd.unwrap() %} Yes {% else %} No {% endif %}</p>
37+
<p><b>Authors:</b> {{ value.authors|join(", ") }}</p>
38+
<p><b>License:</b> {{ value.license }}</p>
39+
{% let len = value.notices.len() %}
40+
{% if len > 0 %}
41+
<p><b>Notices:</b>
42+
{% for (notice_name, notice_text) in value.notices %}
43+
<details>
44+
<summary><code>{{ notice_name }}</code></summary>
45+
<pre>
46+
{{ notice_text }}
47+
</pre>
48+
</details>
49+
{% endfor %}
50+
</p>
51+
{% endif %}
52+
{% endfor %}
53+
</body>
54+
</html>

0 commit comments

Comments
 (0)