diff --git a/resources/select.html.jinja b/resources/select.html.jinja
new file mode 100644
index 0000000000..8d6dfd0bed
--- /dev/null
+++ b/resources/select.html.jinja
@@ -0,0 +1,48 @@
+
+
+
+
+
+
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index d75178608e..4831d40390 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -8,16 +8,18 @@ publish = false
anyhow = "1.0.93"
basic-toml = "0.1.9"
chrono = "0.4.38"
-clap = { version = "4.5.20", features = ["derive", "wrap_help"] }
+clap = { version = "4.5.20", features = ["derive", "wrap_help"] }
console = "0.15.10"
csv = "1.3.1"
env_logger = "0.11.5"
esp-metadata = { path = "../esp-metadata", features = ["clap"] }
+kuchikiki = "0.8.2"
log = "0.4.22"
minijinja = "2.5.0"
-semver = { version = "1.0.23", features = ["serde"] }
+reqwest = { version = "0.12.12", features = ["blocking", "json", "native-tls-vendored"] }
+semver = { version = "1.0.23", features = ["serde"] }
serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.70"
-strum = { version = "0.26.3", features = ["derive"] }
+strum = { version = "0.26.3", features = ["derive"] }
toml_edit = "0.22.22"
walkdir = "2.5.0"
diff --git a/xtask/src/documentation.rs b/xtask/src/documentation.rs
index 2cb403a358..9b13de692d 100644
--- a/xtask/src/documentation.rs
+++ b/xtask/src/documentation.rs
@@ -1,4 +1,5 @@
use std::{
+ collections::HashSet,
fs,
path::{Path, PathBuf},
};
@@ -6,7 +7,9 @@ use std::{
use anyhow::{ensure, Context as _, Result};
use clap::ValueEnum;
use esp_metadata::Config;
+use kuchikiki::traits::*;
use minijinja::Value;
+use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use crate::{cargo::CargoArgsBuilder, Chip, Package};
@@ -14,10 +17,16 @@ use crate::{cargo::CargoArgsBuilder, Chip, Package};
// ----------------------------------------------------------------------------
// Build Documentation
+#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
+struct Manifest {
+ versions: HashSet,
+}
+
pub fn build_documentation(
workspace: &Path,
packages: &mut [Package],
chips: &mut [Chip],
+ base_url: Option,
) -> Result<()> {
let output_path = workspace.join("docs");
@@ -32,6 +41,23 @@ pub fn build_documentation(
continue;
}
+ // Download the manifest from the documentation server if able,
+ // otherwise just create a default (empty) manifest:
+ let mut manifest_url = base_url
+ .clone()
+ .unwrap_or_default()
+ .trim_end_matches('/')
+ .to_string();
+ manifest_url.push_str(&format!("/{package}/manifest.json"));
+
+ let mut manifest = match reqwest::blocking::get(manifest_url) {
+ Ok(resp) => resp.json::()?,
+ Err(err) => {
+ log::warn!("Unable to fetch package manifest: {err}");
+ Manifest::default()
+ }
+ };
+
// If the package does not have chip features, then just ignore
// whichever chip(s) were specified as arguments:
let chips = if package.has_chip_features() {
@@ -48,6 +74,11 @@ pub fn build_documentation(
vec![]
};
+ // Update the package manifest to include the latest version:
+ let version = crate::package_version(workspace, *package)?;
+ manifest.versions.insert(version.clone());
+
+ // Build the documentation for the package:
if chips.is_empty() {
build_documentation_for_package(workspace, package, None)?;
} else {
@@ -55,6 +86,15 @@ pub fn build_documentation(
build_documentation_for_package(workspace, package, Some(chip))?;
}
}
+
+ // Write out the package manifest JSON file:
+ fs::write(
+ output_path.join(package.to_string()).join("manifest.json"),
+ serde_json::to_string(&manifest)?,
+ )?;
+
+ // Patch the generated documentation to include a select box for the version:
+ patch_documentation_index_for_package(workspace, package, &version, &base_url)?;
}
Ok(())
@@ -230,6 +270,56 @@ fn apply_feature_rules(package: &Package, config: &Config) -> Vec {
features
}
+fn patch_documentation_index_for_package(
+ workspace: &Path,
+ package: &Package,
+ version: &semver::Version,
+ base_url: &Option,
+) -> Result<()> {
+ let package_name = package.to_string().replace('-', "_");
+ let package_path = workspace.join("docs").join(package.to_string());
+ let version_path = package_path.join(version.to_string());
+
+ let mut index_paths = Vec::new();
+
+ if package.chip_features_matter() {
+ for chip_path in fs::read_dir(version_path)? {
+ let chip_path = chip_path?.path();
+ if chip_path.is_dir() {
+ let path = chip_path.join(&package_name).join("index.html");
+ index_paths.push((version.clone(), path));
+ }
+ }
+ } else {
+ let path = version_path.join(&package_name).join("index.html");
+ index_paths.push((version.clone(), path));
+ }
+
+ for (version, index_path) in index_paths {
+ let html = fs::read_to_string(&index_path)?;
+ let document = kuchikiki::parse_html().one(html);
+
+ let elem = document
+ .select_first(".sidebar-crate")
+ .expect("Unable to select '.sidebar-crate' element in HTML");
+
+ let base_url = base_url.clone().unwrap_or_default();
+ let resources_path = workspace.join("resources");
+ let html = render_template(
+ &resources_path,
+ "select.html.jinja",
+ minijinja::context! { base_url => base_url, package => package, version => version },
+ )?;
+
+ let node = elem.as_node();
+ node.append(kuchikiki::parse_html().one(html));
+
+ fs::write(&index_path, document.to_string())?;
+ }
+
+ Ok(())
+}
+
// ----------------------------------------------------------------------------
// Build Documentation Index
@@ -293,13 +383,16 @@ pub fn build_documentation_index(workspace: &Path, packages: &mut [Package]) ->
chips.sort();
let meta = generate_documentation_meta_for_package(workspace, *package, &chips)?;
- render_template(
- "package_index.html.jinja",
- "index.html",
- &version_path,
+
+ // Render the template to HTML and write it out to the desired path:
+ let html = render_template(
&resources_path,
+ "package_index.html.jinja",
minijinja::context! { metadata => meta },
)?;
+ let path = version_path.join("index.html");
+ fs::write(&path, html).context(format!("Failed to write index.html"))?;
+ log::info!("Created {}", path.display());
}
}
@@ -312,13 +405,15 @@ pub fn build_documentation_index(workspace: &Path, packages: &mut [Package]) ->
let meta = generate_documentation_meta_for_index(&workspace)?;
- render_template(
- "index.html.jinja",
- "index.html",
- &docs_path,
+ // Render the template to HTML and write it out to the desired path:
+ let html = render_template(
&resources_path,
+ "index.html.jinja",
minijinja::context! { metadata => meta },
)?;
+ let path = docs_path.join("index.html");
+ fs::write(&path, html).context(format!("Failed to write index.html"))?;
+ log::info!("Created {}", path.display());
Ok(())
}
@@ -381,13 +476,7 @@ fn generate_documentation_meta_for_index(workspace: &Path) -> Result>
// ----------------------------------------------------------------------------
// Helper Functions
-fn render_template(
- template: &str,
- name: &str,
- path: &Path,
- resources: &Path,
- ctx: C,
-) -> Result<()>
+fn render_template(resources: &Path, template: &str, ctx: C) -> Result
where
C: serde::Serialize,
{
@@ -400,10 +489,5 @@ where
let tmpl = env.get_template(template)?;
let html = tmpl.render(ctx)?;
- // Write out the rendered HTML to the desired path:
- let path = path.join(name);
- fs::write(&path, html).context(format!("Failed to write {name}"))?;
- log::info!("Created {}", path.display());
-
- Ok(())
+ Ok(html)
}
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs
index b47f830da9..1d485f0bfa 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -30,6 +30,7 @@ pub mod firmware;
Display,
EnumIter,
ValueEnum,
+ serde::Deserialize,
serde::Serialize,
)]
#[serde(rename_all = "kebab-case")]
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 93eecca4f8..f24be0100d 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -89,6 +89,9 @@ struct BuildDocumentationArgs {
/// Chip(s) to build documentation for.
#[arg(long, value_enum, value_delimiter = ',', default_values_t = Chip::iter())]
chips: Vec,
+ /// Base URL of the deployed documentation.
+ #[arg(long)]
+ base_url: Option,
}
#[derive(Debug, Args)]
@@ -474,7 +477,12 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> {
}
fn build_documentation(workspace: &Path, mut args: BuildDocumentationArgs) -> Result<()> {
- xtask::documentation::build_documentation(workspace, &mut args.packages, &mut args.chips)
+ xtask::documentation::build_documentation(
+ workspace,
+ &mut args.packages,
+ &mut args.chips,
+ args.base_url,
+ )
}
fn build_documentation_index(