Skip to content

Commit

Permalink
feat(wasm/html): add callback to generate the default path resolver i…
Browse files Browse the repository at this point in the history
…n resolvePath (#659)
  • Loading branch information
crowlKats authored Nov 15, 2024
1 parent c8b849b commit 751e6e4
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 41 deletions.
21 changes: 12 additions & 9 deletions js/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,13 @@ export type UrlResolveKind =
| UrlResolveKindFile
| UrlResolveKindSymbol;

interface HrefResolver {
/** Resolver for how files should link to eachother. */
resolvePath?(current: UrlResolveKind, target: UrlResolveKind): string;
export interface HrefResolver {
/** Resolver for how files should link to each other. */
resolvePath?(
current: UrlResolveKind,
target: UrlResolveKind,
defaultResolve: () => string,
): string;
/** Resolver for global symbols, like the Deno namespace or other built-ins */
resolveGlobalSymbol?(symbol: string[]): string | undefined;
/** Resolver for symbols from non-relative imports */
Expand Down Expand Up @@ -212,7 +216,7 @@ export interface UsageComposer {
): Map<UsageComposerEntry, string>;
}

interface GenerateOptions {
export interface GenerateOptions {
/** The name of the package to use in the breadcrumbs. */
packageName?: string;
/** The main entrypoint if one is present. */
Expand All @@ -236,10 +240,9 @@ interface GenerateOptions {
*/
symbolRedirectMap?: Record<string, Record<string, string>>;
/**
* Map of modules, where the value is a link to where the default symbol
* should redirect to.
* Map of modules, where the value is what the name of the default symbol should be.
*/
defaultRedirectMap?: Record<string, string>;
defaultSymbolMap?: Record<string, string>;
/**
* Hook to inject content in the `head` tag.
*
Expand Down Expand Up @@ -300,8 +303,8 @@ const defaultUsageComposer: UsageComposer = {
* @param docNodesByUrl DocNodes keyed by their absolute URL.
*/
export async function generateHtml(
options: GenerateOptions,
docNodesByUrl: Record<string, Array<DocNode>>,
options: GenerateOptions,
): Promise<Record<string, string>> {
const {
usageComposer = defaultUsageComposer,
Expand All @@ -317,7 +320,7 @@ export async function generateHtml(
options.categoryDocs,
options.disableSearch ?? false,
options.symbolRedirectMap,
options.defaultRedirectMap,
options.defaultSymbolMap,
options.hrefResolver?.resolvePath,
options.hrefResolver?.resolveGlobalSymbol || (() => undefined),
options.hrefResolver?.resolveImportHref || (() => undefined),
Expand Down
4 changes: 2 additions & 2 deletions js/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Deno.test({
"https://deno.land/[email protected]/fmt/colors.ts",
);

const files = await generateHtml({
const files = await generateHtml({ ["file:///colors.ts"]: entries }, {
markdownRenderer(
md,
_titleOnly,
Expand All @@ -143,7 +143,7 @@ Deno.test({
markdownStripper(md: string) {
return md;
},
}, { ["file:///colors.ts"]: entries });
});

assertEquals(Object.keys(files).length, 61);
},
Expand Down
66 changes: 65 additions & 1 deletion lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use import_map::ImportMap;
use import_map::ImportMapOptions;
use indexmap::IndexMap;
use serde::Serialize;
use std::ffi::c_void;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
Expand All @@ -37,6 +38,11 @@ macro_rules! console_warn {
($($t:tt)*) => (warn(&format_args!($($t)*).to_string()))
}

thread_local! {
static CURRENT: std::cell::RefCell<*const c_void> = const { std::cell::RefCell::new(std::ptr::null()) };
static TARGET: std::cell::RefCell<*const c_void> = const { std::cell::RefCell::new(std::ptr::null()) };
}

struct JsLoader {
load: js_sys::Function,
}
Expand Down Expand Up @@ -318,13 +324,71 @@ impl deno_doc::html::HrefResolver for JsHrefResolver {
if let Some(resolve_path) = &self.resolve_path {
let this = JsValue::null();

let new_current = current.clone();
let new_target = target.clone();

{
let current_ptr =
&new_current as *const UrlResolveKind as *const c_void;
CURRENT.set(current_ptr);
let target = &new_target as *const UrlResolveKind as *const c_void;
TARGET.set(target);
}

let default_closure = Box::new(move || {
CURRENT.with(|current| {
let current_ptr = *current.borrow() as *const UrlResolveKind;
assert!(!current_ptr.is_null());
// SAFETY: this pointer is valid until destroyed, which is done
// after compose is called
let current_val = unsafe { &*current_ptr };

let path = TARGET.with(|target| {
let target_ptr = *target.borrow() as *const UrlResolveKind;
assert!(!target_ptr.is_null());
// SAFETY: this pointer is valid until destroyed, which is done
// after compose is called
let target_val = unsafe { &*target_ptr };

let path =
deno_doc::html::href_path_resolve(*current_val, *target_val);

*target.borrow_mut() =
target_val as *const UrlResolveKind as *const c_void;

path
});

*current.borrow_mut() =
current_val as *const UrlResolveKind as *const c_void;

path
})
});

let default_closure =
Closure::wrap(Box::new(default_closure) as Box<dyn Fn() -> String>);
let default_closure =
JsCast::unchecked_ref::<js_sys::Function>(default_closure.as_ref());

let current = serde_wasm_bindgen::to_value(&current).unwrap();
let target = serde_wasm_bindgen::to_value(&target).unwrap();

let global_symbol = resolve_path
.call2(&this, &current, &target)
.call3(&this, &current, &target, default_closure)
.expect("resolve_path errored");

{
let current =
CURRENT.replace(std::ptr::null()) as *const UrlResolveKind;
// SAFETY: take the pointer and drop it
let _ = unsafe { &*current };

let target = TARGET.replace(std::ptr::null()) as *const UrlResolveKind;
// SAFETY: take the pointer and drop it
let _ = unsafe { &*target };
}

serde_wasm_bindgen::from_value(global_symbol)
.expect("resolve_path returned an invalid value")
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/html/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ impl ShortPath {
if self.is_main {
UrlResolveKind::Root
} else {
UrlResolveKind::File(self)
UrlResolveKind::File { file: self }
}
}
}
Expand Down
26 changes: 17 additions & 9 deletions src/html/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ impl CategoriesPanelCtx {
name: short_path.display_name().to_string(),
href: ctx.ctx.resolve_path(
ctx.get_current_resolve(),
UrlResolveKind::File(short_path),
UrlResolveKind::File { file: short_path },
),
active: current_path.is_some_and(|current_path| {
current_path == short_path.display_name()
Expand Down Expand Up @@ -157,7 +157,7 @@ impl CategoriesPanelCtx {
.map(|title| CategoriesPanelCategoryCtx {
href: ctx.ctx.resolve_path(
ctx.get_current_resolve(),
UrlResolveKind::Category(&title),
UrlResolveKind::Category { category: &title },
),
active: current_path
.is_some_and(|current_path| current_path == title),
Expand Down Expand Up @@ -320,7 +320,7 @@ impl IndexCtx {
header: SectionHeaderCtx {
href: Some(render_ctx.ctx.resolve_path(
render_ctx.get_current_resolve(),
UrlResolveKind::Category(&title),
UrlResolveKind::Category { category: &title },
)),
title,
anchor: AnchorCtx { id: anchor },
Expand Down Expand Up @@ -376,8 +376,11 @@ impl IndexCtx {
partitions: partition::Partitions<String>,
all_doc_nodes: &[DocNodeWithContext],
) -> Self {
let render_ctx =
RenderContext::new(ctx, all_doc_nodes, UrlResolveKind::Category(name));
let render_ctx = RenderContext::new(
ctx,
all_doc_nodes,
UrlResolveKind::Category { category: name },
);

let sections = super::namespace::render_namespace(
partitions.into_iter().map(|(title, nodes)| {
Expand All @@ -398,8 +401,10 @@ impl IndexCtx {
}),
);

let root =
ctx.resolve_path(UrlResolveKind::Category(name), UrlResolveKind::Root);
let root = ctx.resolve_path(
UrlResolveKind::Category { category: name },
UrlResolveKind::Root,
);

let html_head_ctx = HtmlHeadCtx::new(ctx, &root, Some(name), None);

Expand Down Expand Up @@ -515,8 +520,11 @@ pub fn generate_symbol_pages_for_module(

let mut generated_pages = Vec::with_capacity(name_partitions.values().len());

let render_ctx =
RenderContext::new(ctx, module_doc_nodes, UrlResolveKind::File(short_path));
let render_ctx = RenderContext::new(
ctx,
module_doc_nodes,
UrlResolveKind::File { file: short_path },
);

for (name, doc_nodes) in name_partitions {
let (breadcrumbs_ctx, symbol_group_ctx, toc_ctx, categories_panel) =
Expand Down
20 changes: 12 additions & 8 deletions src/html/render_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ impl<'ctx> RenderContext<'ctx> {
},
]
}
UrlResolveKind::Category(category) => {
UrlResolveKind::Category { category } => {
vec![
BreadcrumbCtx {
name: index_name,
Expand All @@ -217,7 +217,7 @@ impl<'ctx> RenderContext<'ctx> {
},
]
}
UrlResolveKind::File(file) => {
UrlResolveKind::File { file } => {
if file.is_main {
vec![BreadcrumbCtx {
name: index_name,
Expand Down Expand Up @@ -257,9 +257,10 @@ impl<'ctx> RenderContext<'ctx> {
if !file.is_main {
parts.push(BreadcrumbCtx {
name: file.display_name().to_string(),
href: self
.ctx
.resolve_path(self.current_resolve, UrlResolveKind::File(file)),
href: self.ctx.resolve_path(
self.current_resolve,
UrlResolveKind::File { file },
),
is_symbol: false,
is_first_symbol: false,
});
Expand All @@ -268,7 +269,7 @@ impl<'ctx> RenderContext<'ctx> {
name: category.to_string(),
href: self.ctx.resolve_path(
self.current_resolve,
UrlResolveKind::Category(category),
UrlResolveKind::Category { category },
),
is_symbol: false,
is_first_symbol: false,
Expand Down Expand Up @@ -618,8 +619,11 @@ mod test {
let render_ctx = RenderContext::new(&ctx, doc_nodes, UrlResolveKind::Root);
assert_eq!(render_ctx.lookup_symbol_href("foo").unwrap(), "b/foo");

let render_ctx =
RenderContext::new(&ctx, doc_nodes, UrlResolveKind::File(short_path));
let render_ctx = RenderContext::new(
&ctx,
doc_nodes,
UrlResolveKind::File { file: short_path },
);
assert_eq!(render_ctx.lookup_symbol_href("foo").unwrap(), "b/foo");
}
}
2 changes: 2 additions & 0 deletions src/html/usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,14 @@ impl UsagesCtx {
move |url: String, custom_file_identifier: Option<String>| {
RENDER_CONTEXT.with(|ctx| {
let render_ctx_ptr = *ctx.borrow() as *const RenderContext;
assert!(!render_ctx_ptr.is_null());
// SAFETY: this pointer is valid until destroyed, which is done
// after compose is called
let render_ctx = unsafe { &*render_ctx_ptr };

let usage = DOC_NODES.with(|nodes| {
let (nodes_ptr, nodes_ptr_len) = *nodes.borrow();
assert!(!nodes_ptr.is_null());
// SAFETY: the pointers are valid until destroyed, which is done
// after compose is called
let doc_nodes = unsafe {
Expand Down
22 changes: 13 additions & 9 deletions src/html/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,12 @@ impl NamespacedGlobalSymbols {
pub enum UrlResolveKind<'a> {
Root,
AllSymbols,
Category(&'a str),
File(&'a ShortPath),
Category {
category: &'a str,
},
File {
file: &'a ShortPath,
},
Symbol {
file: &'a ShortPath,
symbol: &'a str,
Expand All @@ -245,8 +249,8 @@ impl UrlResolveKind<'_> {
match self {
UrlResolveKind::Root => None,
UrlResolveKind::AllSymbols => None,
UrlResolveKind::Category(_) => None,
UrlResolveKind::File(file) => Some(file),
UrlResolveKind::Category { .. } => None,
UrlResolveKind::File { file } => Some(file),
UrlResolveKind::Symbol { file, .. } => Some(file),
}
}
Expand All @@ -257,7 +261,7 @@ pub fn href_path_resolve(
target: UrlResolveKind,
) -> String {
let backs = match current {
UrlResolveKind::File(file) => "../".repeat(if file.is_main {
UrlResolveKind::File { file } => "../".repeat(if file.is_main {
1
} else {
file.path.split('/').count()
Expand All @@ -269,12 +273,12 @@ pub fn href_path_resolve(
}),
UrlResolveKind::Root => String::new(),
UrlResolveKind::AllSymbols => String::from("./"),
UrlResolveKind::Category(_) => String::from("./"),
UrlResolveKind::Category { .. } => String::from("./"),
};

match target {
UrlResolveKind::Root => backs,
UrlResolveKind::File(target_file) if target_file.is_main => backs,
UrlResolveKind::File { file: target_file } if target_file.is_main => backs,
UrlResolveKind::AllSymbols => format!("{backs}./all_symbols.html"),
UrlResolveKind::Symbol {
file: target_file,
Expand All @@ -283,10 +287,10 @@ pub fn href_path_resolve(
} => {
format!("{backs}./{}/~/{target_symbol}.html", target_file.path)
}
UrlResolveKind::File(target_file) => {
UrlResolveKind::File { file: target_file } => {
format!("{backs}./{}/index.html", target_file.path)
}
UrlResolveKind::Category(category) => {
UrlResolveKind::Category { category } => {
format!("{backs}./{}.html", slugify(category))
}
}
Expand Down
7 changes: 5 additions & 2 deletions tests/html_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,8 +511,11 @@ async fn module_doc() {
let mut module_docs = vec![];

for (short_path, doc_nodes) in &ctx.doc_nodes {
let render_ctx =
RenderContext::new(&ctx, doc_nodes, UrlResolveKind::File(short_path));
let render_ctx = RenderContext::new(
&ctx,
doc_nodes,
UrlResolveKind::File { file: short_path },
);
let module_doc = jsdoc::ModuleDocCtx::new(&render_ctx, short_path);

module_docs.push(module_doc);
Expand Down

0 comments on commit 751e6e4

Please sign in to comment.