Skip to content

Commit

Permalink
feat(build): write top level metadata.json
Browse files Browse the repository at this point in the history
  • Loading branch information
fiji-flo committed Dec 11, 2024
1 parent 8215b2b commit bbb1112
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 69 deletions.
5 changes: 3 additions & 2 deletions crates/rari-cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use clap_verbosity_flag::Verbosity;
use dashmap::DashMap;
use rari_doc::build::{
build_blog_pages, build_contributor_spotlight_pages, build_curriculum_pages, build_docs,
build_generic_pages, build_spas,
build_generic_pages, build_spas, build_top_level_meta,
};
use rari_doc::cached_readers::{read_and_cache_doc_pages, CACHED_DOC_PAGE_FILES};
use rari_doc::issues::{issues_by, InMemoryLayer};
Expand Down Expand Up @@ -301,7 +301,8 @@ fn main() -> Result<(), Error> {
}
if args.all || !args.no_basic || args.content || !args.files.is_empty() {
let start = std::time::Instant::now();
let docs = build_docs(&docs)?;
let (docs, meta) = build_docs(&docs)?;
build_top_level_meta(meta)?;
let num = docs.len();
urls.extend(docs);
println!(
Expand Down
85 changes: 60 additions & 25 deletions crates/rari-doc/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! processing for documentzation pages to improve the efficiency of building large sets files.
use std::borrow::Cow;
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::{BufWriter, Write};
use std::iter::once;
Expand All @@ -23,7 +24,7 @@ use crate::cached_readers::{
use crate::contributors::contributors_txt;
use crate::error::DocError;
use crate::pages::build::copy_additional_files;
use crate::pages::json::BuiltPage;
use crate::pages::json::{BuiltPage, JsonDocMetadata};
use crate::pages::page::{Page, PageBuilder, PageLike};
use crate::pages::types::spa::SPA;
use crate::resolve::url_to_folder_path;
Expand All @@ -46,14 +47,12 @@ pub struct SitemapMeta<'a> {
///
/// * `page` - A reference to the `Page` object to be built.
///
/// # Panics
/// # Returns
///
/// * `Result<(BuiltPage, String), DocError> ` - Returns the `BuiltPage` and according hash
/// if successful, or a `DocError` if an error occurs during the process.
///
/// This function will panic if:
/// - The `BUILD_OUT_ROOT` environment variable is not set.
/// - An error occurs while creating the output directory or file.
/// - An error occurs while writing the JSON content to the file.
/// - An error occurs while copying additional files.
pub fn build_single_page(page: &Page) -> Result<(), DocError> {
pub fn build_single_page(page: &Page) -> Result<(BuiltPage, String), DocError> {
let file = page.full_path().to_string_lossy();
let span = span!(
Level::ERROR,
Expand All @@ -71,33 +70,42 @@ pub fn build_single_page(page: &Page) -> Result<(), DocError> {
let out_file = out_path.join("index.json");
let file = File::create(out_file).unwrap();
let mut buffed = BufWriter::new(file);
let json_str = serde_json::to_string(&built_page)?;
buffed.write_all(json_str.as_bytes())?;
let hash = format!("{:x}", Sha256::digest(json_str.as_bytes()));
if let Some(in_path) = page.full_path().parent() {
copy_additional_files(in_path, &out_path, page.full_path())?;
}
Ok((built_page, hash))
}

if let BuiltPage::Doc(json) = built_page {
let json_str = serde_json::to_string(&json)?;
buffed.write_all(json_str.as_bytes())?;
let hash = Sha256::digest(json_str.as_bytes());
pub fn build_single_doc(page: &Page) -> Result<JsonDocMetadata, DocError> {
let (built_doc, hash) = build_single_page(page)?;
if let BuiltPage::Doc(json) = built_doc {
let meta = JsonDocMetadata::from_json_doc(json.doc, hash);

let out_path = build_out_root()
.expect("No BUILD_OUT_ROOT")
.join(url_to_folder_path(page.url().trim_start_matches('/')));
let meta_out_file = out_path.join("metadata.json");
let meta_file = File::create(meta_out_file).unwrap();
let meta_buffed = BufWriter::new(meta_file);
serde_json::to_writer(meta_buffed, &json.doc.as_meta(format!("{hash:x}")))?;
serde_json::to_writer(meta_buffed, &meta)?;
let wiki_histories = wiki_histories();
let wiki_history = wiki_histories
.get(&page.locale())
.and_then(|wh| wh.get(page.slug()));
let github_file_url = json.doc.source.github_url.as_str();
let github_file_url = meta.source.github_url.as_str();
let contributors_txt_str = contributors_txt(wiki_history, github_file_url);
let contributors_out_file = out_path.join("contributors.txt");
let contributors_file = File::create(contributors_out_file).unwrap();
let mut contributors_buffed = BufWriter::new(contributors_file);
contributors_buffed.write_all(contributors_txt_str.as_bytes())?;
} else {
serde_json::to_writer(buffed, &built_page)?;
}

if let Some(in_path) = page.full_path().parent() {
copy_additional_files(in_path, &out_path, page.full_path())?;
Ok(meta)
} else {
Err(DocError::NotADoc)
}
Ok(())
}

/// Builds a collection of documentation pages and returns their URLs.
Expand All @@ -120,20 +128,47 @@ pub fn build_single_page(page: &Page) -> Result<(), DocError> {
///
/// This function will return an error if:
/// - An error occurs while building any of the documentation pages.
pub fn build_docs<'a, 'b: 'a>(docs: &'b [Page]) -> Result<Vec<SitemapMeta<'a>>, DocError> {
pub fn build_docs<'a, 'b: 'a>(
docs: &'b [Page],
) -> Result<(Vec<SitemapMeta<'a>>, Vec<JsonDocMetadata>), DocError> {
docs.into_par_iter()
.map(|page| {
let history = git_history().get(page.path());
let modified = history.map(|entry| entry.modified);
build_single_page(page).map(|_| SitemapMeta {
url: Cow::Borrowed(page.url()),
locale: page.locale(),
modified,
build_single_doc(page).map(|meta| {
(
SitemapMeta {
url: Cow::Borrowed(page.url()),
locale: page.locale(),
modified,
},
meta,
)
})
})
.collect()
}

pub fn build_top_level_meta(locale_meta: Vec<JsonDocMetadata>) -> Result<(), DocError> {
let locale_meta_map =
locale_meta
.into_iter()
.fold(HashMap::<Locale, Vec<_>>::new(), |mut map, meta| {
map.entry(meta.locale).or_default().push(meta);
map
});

for (locale, meta) in locale_meta_map {
let meta_out_file = build_out_root()?
.join(locale.as_folder_str())
.join("metadata.json");
let meta_file = File::create(meta_out_file).unwrap();
let meta_buffed = BufWriter::new(meta_file);
serde_json::to_writer(meta_buffed, &meta)?;
}
Ok(())
}

/// Builds curriculum pages and returns their URLs.
///
/// This function retrieves the (cached) curriculum pages, builds each page using the `build_single_page` function,
Expand Down
2 changes: 2 additions & 0 deletions crates/rari-doc/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use crate::pages::page::PageCategory;
/// error messages and associated data.
#[derive(Debug, Error)]
pub enum DocError {
#[error("Not a Doc")]
NotADoc,
#[error("Cannot parse templ index")]
TemplIndexParseError(#[from] std::num::ParseIntError),
#[error("Invalid templ index {0}")]
Expand Down
106 changes: 64 additions & 42 deletions crates/rari-doc/src/pages/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,65 +273,87 @@ pub struct JsonDoc {
pub flaws: Option<DisplayIssues>,
}

impl JsonDoc {
pub fn as_meta(&self, hash: String) -> JsonDocMetadata {
JsonDocMetadata {
is_active: &self.is_active,
is_markdown: &self.is_markdown,
is_translated: &self.is_translated,
locale: &self.locale,
mdn_url: &self.mdn_url,
modified: &self.modified,
native: &self.native,
no_indexing: &self.no_indexing,
other_translations: &self.other_translations,
page_title: &self.page_title,
parents: &self.parents,
popularity: &self.popularity,
short_title: &self.short_title,
source: &self.source,
summary: &self.summary,
title: &self.title,
baseline: &self.baseline,
browser_compat: &self.browser_compat,
page_type: &self.page_type,
impl JsonDocMetadata {
pub fn from_json_doc(value: JsonDoc, hash: String) -> Self {
let JsonDoc {
is_active,
is_markdown,
is_translated,
locale,
mdn_url,
modified,
native,
no_indexing,
other_translations,
page_title,
parents,
popularity,
short_title,
source,
summary,
title,
baseline,
browser_compat,
page_type,
..
} = value;
Self {
is_active,
is_markdown,
is_translated,
locale,
mdn_url,
modified,
native,
no_indexing,
other_translations,
page_title,
parents,
popularity,
short_title,
source,
summary,
title,
baseline,
browser_compat,
page_type,
hash,
}
}
}

#[derive(Debug, Clone, Serialize)]
pub struct JsonDocMetadata<'a> {
pub struct JsonDocMetadata {
#[serde(rename = "isActive")]
pub is_active: &'a bool,
pub is_active: bool,
#[serde(rename = "isMarkdown")]
pub is_markdown: &'a bool,
pub is_markdown: bool,
#[serde(rename = "isTranslated")]
pub is_translated: &'a bool,
pub locale: &'a Locale,
pub mdn_url: &'a String,
pub is_translated: bool,
pub locale: Locale,
pub mdn_url: String,
#[serde(serialize_with = "modified_dt")]
pub modified: &'a NaiveDateTime,
pub native: &'a Native,
pub modified: NaiveDateTime,
pub native: Native,
#[serde(rename = "noIndexing")]
pub no_indexing: &'a bool,
pub other_translations: &'a Vec<Translation>,
pub no_indexing: bool,
pub other_translations: Vec<Translation>,
#[serde(rename = "pageTitle")]
pub page_title: &'a String,
pub page_title: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub parents: &'a Vec<Parent>,
pub popularity: &'a Option<f64>,
pub short_title: &'a String,
pub source: &'a Source,
pub parents: Vec<Parent>,
pub popularity: Option<f64>,
pub short_title: String,
pub source: Source,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: &'a Option<String>,
pub title: &'a String,
pub summary: Option<String>,
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub baseline: &'a Option<&'static SupportStatusWithByKey>,
pub baseline: Option<&'static SupportStatusWithByKey>,
#[serde(rename = "browserCompat", skip_serializing_if = "Vec::is_empty")]
pub browser_compat: &'a Vec<String>,
pub browser_compat: Vec<String>,
#[serde(rename = "pageType")]
pub page_type: &'a PageType,
pub page_type: PageType,
pub hash: String,
}

Expand Down

0 comments on commit bbb1112

Please sign in to comment.