Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wasm/html): add callback to generate the default path resolver in resolvePath #659

Merged
merged 4 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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