feat: export package docs to json format and render to html (#1809)
Some checks failed
tinymist::auto_tag / auto-tag (push) Has been cancelled
tinymist::ci / Duplicate Actions Detection (push) Has been cancelled
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Has been cancelled
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Has been cancelled
tinymist::ci / prepare-build (push) Has been cancelled
tinymist::gh_pages / build-gh-pages (push) Has been cancelled
tinymist::ci / announce (push) Has been cancelled
tinymist::ci / build (push) Has been cancelled

It provides a `package-doc.json` and HTML is rendered using a
`package-doc` function.

```typ
#let package-doc(info, path: none) [
  #metadata((
    // more sub paths
  )) <static-paths>
  #render-page(info, path) // the content of $path/.html
]
#package-doc(json("package-doc.json"))
```
This commit is contained in:
Myriad-Dreamin 2025-08-13 12:12:08 +08:00 committed by GitHub
parent 2c552ce985
commit bf081ec347
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 2544 additions and 133 deletions

View file

@ -66,6 +66,8 @@ pub fn module_docs(ctx: &mut LocalContext, entry_point: FileId) -> StrResult<Pac
/// Information about a definition.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DefInfo {
/// The raw documentation of the definition.
pub id: EcoString,
/// The name of the definition.
pub name: EcoString,
/// The kind of the definition.
@ -74,6 +76,10 @@ pub struct DefInfo {
pub loc: Option<(usize, usize, usize)>,
/// Whether the definition external to the module.
pub is_external: bool,
/// The module link to the definition
pub module_link: Option<String>,
/// The symbol link to the definition
pub symbol_link: Option<String>,
/// The link to the definition if it is external.
pub external_link: Option<String>,
/// The one-line documentation of the definition.
@ -90,7 +96,7 @@ pub struct DefInfo {
#[serde(skip)]
pub decl: Option<Interned<Decl>>,
/// The children of the definition.
pub children: EcoVec<DefInfo>,
pub children: Vec<DefInfo>,
}
/// Information about the definitions in a package.
@ -206,6 +212,7 @@ impl ScanDefCtx<'_> {
};
let mut head = DefInfo {
id: EcoString::new(),
name: key.to_string().into(),
kind: decl.kind(),
constant: expr.map(|expr| expr.repr()),
@ -215,6 +222,8 @@ impl ScanDefCtx<'_> {
children: children.unwrap_or_default(),
loc: None,
is_external: false,
module_link: None,
symbol_link: None,
external_link: None,
oneliner: None,
};

View file

@ -0,0 +1,299 @@
#import "@preview/cmarker:0.1.6": render
// re-export page template
#import "/typ/packages/package-docs/template.typ": project
#let module-divider = html.elem("hr", attrs: (class: "module-divider"));
#show link: it => if type(it.dest) == label {
html.elem("a", attrs: (href: "#" + str(it.dest), class: "symbol-link"), it.body)
} else {
it
}
#let heading-label(name) = {
let it = name.replace(regex("[\s\:]"), "-").replace(regex("[.()]"), "").replace(regex("-+"), "-").replace("M", "m")
label(it)
}
#let labelled-heading(depth, it) = {
heading(depth: depth, html.elem("span", attrs: (id: str(heading-label(it))), it))
}
#let markdown-docs = render.with(
scope: (
image: (src, alt: none) => {
html.elem("img", attrs: (src: src, alt: alt, class: "code-image"))
},
),
)
#let display-package-spec(pkg-spec) = {
"@"
pkg-spec.meta.namespace
"/"
pkg-spec.meta.name
":"
pkg-spec.meta.version
}
#let span = html.elem.with("span")
#let code = html.elem.with("code")
#let keyword = code.with(attrs: (class: "code-kw"))
#let builtin-ty = code.with(attrs: (class: "type-builtin"))
#let symbol-doc(symbol-ctx, info) = {
// let ident = if !primary.is_empty() {
// eco_format!("symbol-{}-{primary}.{}", child.kind, child.name)
// } else {
// eco_format!("symbol-{}-{}", child.kind, child.name)
// };
let symlink(body) = if info.symbol_link != none {
// let _ = writeln!(out, "#link({})[Symbol Docs]\n", TypstLink(lnk));
html.elem("a", attrs: (href: info.symbol_link, class: "symbol-link"), body)
} else {
body
}
if info.is_external {
let fid = info.loc.at(0, default: none)
let file = symbol-ctx.files.at(fid, default: none)
let package = symbol-ctx.packages.at(file.package, default: none)
let title = if info.kind == "module" {
let title = if package != none and file.package > 0 {
span(attrs: (title: display-package-spec(package)), "external")
code(" ")
code(file.path)
} else {
code(file.path)
}
symlink(code(title))
} else {
// keyword("extern")
// code(" ")
symlink(code(info.name))
if info.kind == "function" {
code("()")
} else {
code(": ")
builtin-ty[any]
}
}
html.elem("div", attrs: (class: "detail-header doc-symbol-" + info.kind), [=== #title])
if info.oneliner != none {
markdown-docs(info.oneliner)
}
return
}
labelled-heading(3, info.kind + ": " + info.name + " in " + symbol-ctx.in-module)
// if info.symbol_link != none {
// // let _ = writeln!(out, "#link({})[Symbol Docs]\n", TypstLink(lnk));
// par(symlink("Symbol Docs"))
// }
// let convert_err = None::<EcoString>;
if info.parsed_docs != none {
if info.parsed_docs.kind == "func" {
// if let Some(DefDocs::Function(sig)) = &info.parsed_docs {
// // let _ = writeln!(out, "<!-- begin:sig -->");
// let _ = writeln!(out, "```typc");
// let _ = write!(out, "let {}", info.name);
// let _ = sig.print(&mut out);
// let _ = writeln!(out, ";");
// let _ = writeln!(out, "```");
// // let _ = writeln!(out, "<!-- end:sig -->");
// }
// repr(info.parsed_docs)
}
}
let printed_docs = false
if not info.is_external {
// let convert_err = None::<EcoString>;
if info.parsed_docs != none {
let docs = info.parsed_docs
if docs.docs != none and docs.docs.len() > 0 {
// remove_list_annotations(docs.docs())
printed_docs = true
markdown-docs(docs.docs)
}
if docs.kind == "func" {
labelled-heading(4, "Resultant")
docs.ret_ty.at(0)
labelled-heading(4, "Parameters")
let pos = docs.at("pos", default: ())
let named = docs.at("named", default: (:)).values()
let rest = docs.at("rest", default: none)
let params = pos + named
if rest != none {
params.push(rest)
}
for param in params {
labelled-heading(5, param.name)
let param-head = (param.name, ": ", param.cano_type.at(0))
if param.positional {
param-head.push(" (Positional)")
}
if param.variadic {
param-head.push(" (Variadic)")
}
if param.settable {
param-head.push(" (Settable)")
}
if param.named {
param-head.push(" (Named, default: ")
param-head.push(param.default)
param-head.push(")")
}
raw(block: true, param-head.join())
markdown-docs(param.docs)
}
}
}
}
if not printed_docs {
let plain_docs = info.docs
if plain_docs == none {
plain_docs = info.oneliner
}
// todo: eval with error tolerance?
// if plain_docs != none {
// eval(plain_docs, mode: "markup")
// }
// if let Some(lnk) = &child.module_link {
// match lnk.as_str() {
// "builtin" => {
// let _ = writeln!(out, "A Builtin Module");
// }
// _lnk => {
// // let _ = writeln!(out, "#link({})[Module Docs]\n",
// // TypstLink(lnk));
// }
// }
// }
// // let _ = writeln!(out, "<!-- end:symbol {ident} -->");
// let _ = writeln!(out, "]),");
}
}
#let analyze-package(p) = {
(
packages: p.packages,
files: p.files,
)
}
#let analyze-module(m) = {
let modules = ()
let functions = ()
let constants = ()
let unknowns = ()
for child in m.children {
if child.kind == "module" {
modules.push(child)
} else if child.kind == "function" {
functions.push(child)
} else if child.kind == "variable" {
constants.push(child)
} else {
unknowns.push(child)
}
}
(
modules: modules.sorted(key: it => it.id),
functions: functions,
constants: constants,
unknowns: unknowns,
)
}
#let module-doc(info: none, name: none, symbol-ctx, m) = {
let m = analyze-module(m)
if info.prefix.len() > 0 {
let primary = info.prefix
let title = "Module: " + primary + " in " + info.prefix
module-divider
labelled-heading(1, title)
} else {
labelled-heading(1, "Package Exports")
}
let symbol-ctx = (
in-module: info.prefix,
..symbol-ctx,
)
if m.modules.len() > 0 {
[== Modules]
for child in m.modules {
symbol-doc(symbol-ctx, child)
}
}
if m.constants.len() > 0 {
[== Constants]
for child in m.constants {
symbol-doc(symbol-ctx, child)
}
}
if m.functions.len() > 0 {
[== Functions]
for child in m.functions {
symbol-doc(symbol-ctx, child)
}
}
if m.unknowns.len() > 0 {
[== Unknowns]
for child in m.unknowns {
symbol-doc(symbol-ctx, child)
}
}
}
#let package-doc(info) = {
let info = json(info)
let title = "@" + info.meta.namespace + "/" + info.meta.name + " " + info.meta.version
show: project.with(title: title)
html.elem("style", read("/typ/packages/package-docs/global.css"))
show: html.elem.with("main")
strong[
This documentation is generated locally. Please submit issues to #link("https://github.com/Myriad-Dreamin/tinymist")[tinymist] if you see incorrect information in it.
]
html.elem("h1", attrs: (id: "package-doc-title"), title)
let repo = info.meta.manifest.package.at("repository", default: none)
if repo != none {
let repo_link = html.elem("a", attrs: (href: repo, class: "package-repo-link"), "Repository")
html.elem("p", repo_link)
}
let description = info.meta.manifest.package.at("description", default: none)
if description != none {
description
}
let symbol-ctx = analyze-package(info)
for (name, m, info) in info.modules {
module-doc(info: info, name: name, symbol-ctx, m)
}
}

View file

@ -5,6 +5,8 @@ use std::path::PathBuf;
use ecow::{EcoString, EcoVec};
use indexmap::IndexSet;
use serde::{Deserialize, Serialize};
use tinymist_analysis::docs::tidy::remove_list_annotations;
use tinymist_world::package::PackageSpec;
use typst::diag::{eco_format, StrResult};
use typst::syntax::package::PackageManifest;
use typst::syntax::{FileId, Span};
@ -13,13 +15,29 @@ use crate::docs::{file_id_repr, module_docs, DefDocs, PackageDefInfo};
use crate::package::{get_manifest_id, PackageInfo};
use crate::LocalContext;
use tinymist_analysis::docs::tidy::remove_list_annotations;
/// Documentation Information about a package.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackageDoc {
meta: PackageMeta,
packages: Vec<PackageMeta>,
files: Vec<FileMeta>,
modules: Vec<(EcoString, crate::docs::DefInfo, ModuleInfo)>,
}
/// Documentation Information about a package module.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ModuleInfo {
prefix: EcoString,
name: EcoString,
loc: Option<usize>,
parent_ident: EcoString,
aka: EcoVec<String>,
}
/// Generate full documents in markdown format
pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<String> {
pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<PackageDoc> {
log::info!("generate_md_docs {spec:?}");
let mut md = String::new();
let toml_id = get_manifest_id(spec)?;
let manifest = ctx.get_manifest(toml_id)?;
@ -32,16 +50,6 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
crate::log_debug_ct!("module_uses: {module_uses:#?}");
let title = for_spec.to_string();
let mut errors = vec![];
writeln!(md, "# {title}").unwrap();
md.push('\n');
writeln!(md, "This documentation is generated locally. Please submit issues to [tinymist](https://github.com/Myriad-Dreamin/tinymist/issues) if you see **incorrect** information in it.").unwrap();
md.push('\n');
md.push('\n');
let manifest = ctx.get_manifest(toml_id)?;
let meta = PackageMeta {
@ -50,8 +58,6 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
version: spec.version.to_string(),
manifest: Some(manifest),
};
let package_meta = jbase64(&meta);
let _ = writeln!(md, "<!-- begin:package {package_meta} -->");
let mut modules_to_generate = vec![(root.name.clone(), root)];
let mut generated_modules = HashSet::new();
@ -72,10 +78,11 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
.clone()
};
let mut modules = vec![];
while !modules_to_generate.is_empty() {
for (parent_ident, def) in std::mem::take(&mut modules_to_generate) {
for (parent_ident, mut def) in std::mem::take(&mut modules_to_generate) {
// parent_ident, symbols
let children = def.children;
let module_val = def.decl.as_ref().unwrap();
let fid = module_val.file_id();
@ -84,32 +91,18 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
// It is (primary) known to safe as a part of HTML string, so we don't have to
// do sanitization here.
let primary = aka.first().cloned().unwrap_or_default();
if !primary.is_empty() {
let _ = writeln!(md, "---\n## Module: {primary}");
}
crate::log_debug_ct!("module: {primary} -- {parent_ident}");
let persist_fid = fid.map(|fid| file_ids.insert_full(fid).0);
#[derive(Serialize)]
struct ModuleInfo {
prefix: EcoString,
name: EcoString,
loc: Option<usize>,
parent_ident: EcoString,
aka: EcoVec<String>,
}
let module_info = jbase64(&ModuleInfo {
let module_info = ModuleInfo {
prefix: primary.as_str().into(),
name: def.name.clone(),
loc: persist_fid,
parent_ident: parent_ident.clone(),
aka,
});
let _ = writeln!(md, "<!-- begin:module {primary} {module_info} -->");
};
for mut child in children {
for child in def.children.iter_mut() {
let span = child.decl.as_ref().map(|decl| decl.span());
let fid_range = span.and_then(|v| {
v.id().and_then(|fid| {
@ -127,9 +120,7 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
});
child.loc = span;
let convert_err = None::<EcoString>;
if let Some(docs) = &child.parsed_docs {
child.parsed_docs = Some(docs.clone());
if child.parsed_docs.is_some() {
child.docs = None;
}
@ -138,7 +129,6 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
} else {
eco_format!("symbol-{}-{}", child.kind, child.name)
};
let _ = writeln!(md, "### {}: {} in {primary}", child.kind, child.name);
if child.is_external {
if let Some(fid) = child_fid {
@ -163,112 +153,40 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
child.external_link = Some(lnk.clone());
lnk
};
let _ = writeln!(md, "[Symbol Docs]({lnk})\n");
child.symbol_link = Some(lnk);
}
}
let child_children = std::mem::take(&mut child.children);
let head = jbase64(&child);
let _ = writeln!(md, "<!-- begin:symbol {ident} {head} -->");
if let Some(DefDocs::Function(sig)) = &child.parsed_docs {
let _ = writeln!(md, "<!-- begin:sig -->");
let _ = writeln!(md, "```typc");
let _ = write!(md, "let {}", child.name);
let _ = sig.print(&mut md);
let _ = writeln!(md, ";");
let _ = writeln!(md, "```");
let _ = writeln!(md, "<!-- end:sig -->");
}
let mut printed_docs = false;
match (&child.parsed_docs, convert_err) {
(_, Some(err)) => {
let err = format!("failed to convert docs in {title}: {err}").replace(
"-->", "—>", // avoid markdown comment
);
let _ = writeln!(md, "<!-- convert-error: {err} -->");
errors.push(err);
}
(Some(docs), _) if !child.is_external => {
let _ = writeln!(md, "{}", remove_list_annotations(docs.docs()));
printed_docs = true;
if let DefDocs::Function(docs) = docs {
for param in docs
.pos
.iter()
.chain(docs.named.values())
.chain(docs.rest.as_ref())
{
let _ = writeln!(md, "<!-- begin:param {} -->", param.name);
let ty = match &param.cano_type {
Some((short, _, _)) => short,
None => "unknown",
};
let _ = writeln!(
md,
"#### {} ({ty:?})\n<!-- begin:param-doc {} -->\n{}\n<!-- end:param-doc {} -->",
param.name, param.name, param.docs, param.name
);
let _ = writeln!(md, "<!-- end:param -->");
}
}
}
(_, None) => {}
}
if !printed_docs {
let plain_docs = child.docs.as_deref();
let plain_docs = plain_docs.or(child.oneliner.as_deref());
if let Some(docs) = plain_docs {
let contains_code = docs.contains("```");
if contains_code {
let _ = writeln!(md, "`````typ");
}
let _ = writeln!(md, "{docs}");
if contains_code {
let _ = writeln!(md, "`````");
}
}
}
if !child_children.is_empty() {
crate::log_debug_ct!("sub_fid: {child_fid:?}");
match child_fid {
let lnk = match child_fid {
Some(fid) => {
let aka = akas(fid);
let primary = aka.first().cloned().unwrap_or_default();
let link = format!("module-{primary}").replace(".", "");
let _ = writeln!(md, "[Module Docs](#{link})\n");
if generated_modules.insert(fid) {
let mut child = child.clone();
child.children = child_children;
modules_to_generate.push((ident.clone(), child));
}
let link = format!("module-{primary}").replace(".", "");
format!("#{link}")
}
None => {
let _ = writeln!(md, "A Builtin Module");
}
}
None => "builtin".to_owned(),
};
child.module_link = Some(lnk);
}
let _ = writeln!(md, "<!-- end:symbol {ident} -->");
child.id = ident;
}
let _ = writeln!(md, "<!-- end:module {primary} -->");
modules.push((parent_ident, def, module_info));
}
}
let res = ConvertResult { errors };
let err = jbase64(&res);
let _ = writeln!(md, "<!-- begin:errors {err} -->");
let _ = writeln!(md, "## Errors");
for errs in res.errors {
let _ = writeln!(md, "- {errs}");
}
let _ = writeln!(md, "<!-- end:errors -->");
let mut packages = IndexSet::new();
let files = file_ids
@ -295,11 +213,172 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
})
.collect();
let meta = PackageMetaEnd { packages, files };
let package_meta = jbase64(&meta);
let _ = writeln!(md, "<!-- end:package {package_meta} -->");
let doc = PackageDoc {
meta,
packages,
files,
modules,
};
Ok(md)
Ok(doc)
}
/// Generate full documents in markdown format
pub fn package_docs_typ(doc: &PackageDoc) -> StrResult<String> {
let mut out = String::new();
let _ = writeln!(out, "{}", include_str!("package-doc.typ"));
let pi = &doc.meta;
let _ = writeln!(
out,
"#package-doc(bytes(read(\"{}-{}-{}.json\")))",
pi.namespace, pi.name, pi.version,
);
Ok(out)
}
/// Generate full documents in markdown format
pub fn package_docs_md(doc: &PackageDoc) -> StrResult<String> {
let mut out = String::new();
let title = doc.meta.spec().to_string();
writeln!(out, "# {title}").unwrap();
out.push('\n');
writeln!(out, "This documentation is generated locally. Please submit issues to [tinymist](https://github.com/Myriad-Dreamin/tinymist/issues) if you see **incorrect** information in it.").unwrap();
out.push('\n');
out.push('\n');
let package_meta = jbase64(&doc.meta);
let _ = writeln!(out, "<!-- begin:package {package_meta} -->");
let mut errors = vec![];
for (parent_ident, def, module_info) in &doc.modules {
// parent_ident, symbols
let primary = &module_info.prefix;
if !module_info.prefix.is_empty() {
let _ = writeln!(out, "---\n## Module: {primary}");
}
crate::log_debug_ct!("module: {primary} -- {parent_ident}");
let module_info = jbase64(&module_info);
let _ = writeln!(out, "<!-- begin:module {primary} {module_info} -->");
for child in &def.children {
let convert_err = None::<EcoString>;
let ident = if !primary.is_empty() {
eco_format!("symbol-{}-{primary}.{}", child.kind, child.name)
} else {
eco_format!("symbol-{}-{}", child.kind, child.name)
};
let _ = writeln!(out, "### {}: {} in {primary}", child.kind, child.name);
if let Some(lnk) = &child.symbol_link {
let _ = writeln!(out, "[Symbol Docs]({lnk})\n");
}
let head = jbase64(&child);
let _ = writeln!(out, "<!-- begin:symbol {ident} {head} -->");
if let Some(DefDocs::Function(sig)) = &child.parsed_docs {
let _ = writeln!(out, "<!-- begin:sig -->");
let _ = writeln!(out, "```typc");
let _ = write!(out, "let {}", child.name);
let _ = sig.print(&mut out);
let _ = writeln!(out, ";");
let _ = writeln!(out, "```");
let _ = writeln!(out, "<!-- end:sig -->");
}
let mut printed_docs = false;
match (&child.parsed_docs, convert_err) {
(_, Some(err)) => {
let err = format!("failed to convert docs in {title}: {err}").replace(
"-->", "—>", // avoid markdown comment
);
let _ = writeln!(out, "<!-- convert-error: {err} -->");
errors.push(err);
}
(Some(docs), _) if !child.is_external => {
let _ = writeln!(out, "{}", remove_list_annotations(docs.docs()));
printed_docs = true;
if let DefDocs::Function(docs) = docs {
for param in docs
.pos
.iter()
.chain(docs.named.values())
.chain(docs.rest.as_ref())
{
let _ = writeln!(out, "<!-- begin:param {} -->", param.name);
let ty = match &param.cano_type {
Some((short, _, _)) => short,
None => "unknown",
};
let _ = writeln!(
out,
"#### {} ({ty:?})\n<!-- begin:param-doc {} -->\n{}\n<!-- end:param-doc {} -->",
param.name, param.name, param.docs, param.name
);
let _ = writeln!(out, "<!-- end:param -->");
}
}
}
(_, None) => {}
}
if !printed_docs {
let plain_docs = child.docs.as_deref();
let plain_docs = plain_docs.or(child.oneliner.as_deref());
if let Some(docs) = plain_docs {
let contains_code = docs.contains("```");
if contains_code {
let _ = writeln!(out, "`````typ");
}
let _ = writeln!(out, "{docs}");
if contains_code {
let _ = writeln!(out, "`````");
}
}
}
if let Some(lnk) = &child.module_link {
match lnk.as_str() {
"builtin" => {
let _ = writeln!(out, "A Builtin Module");
}
lnk => {
let _ = writeln!(out, "[Module Docs]({lnk})\n");
}
}
}
let _ = writeln!(out, "<!-- end:symbol {ident} -->");
}
let _ = writeln!(out, "<!-- end:module {primary} -->");
}
let res = ConvertResult { errors };
let err = jbase64(&res);
let _ = writeln!(out, "<!-- begin:errors {err} -->");
let _ = writeln!(out, "## Errors");
for errs in res.errors {
let _ = writeln!(out, "- {errs}");
}
let _ = writeln!(out, "<!-- end:errors -->");
let meta = PackageMetaEnd {
packages: doc.packages.clone(),
files: doc.files.clone(),
};
let package_meta = jbase64(&meta);
let _ = writeln!(out, "<!-- end:package {package_meta} -->");
Ok(out)
}
fn jbase64<T: Serialize>(s: &T) -> String {
@ -309,7 +388,7 @@ fn jbase64<T: Serialize>(s: &T) -> String {
}
/// Information about a package.
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackageMeta {
/// The namespace the package lives in.
pub namespace: EcoString,
@ -321,6 +400,17 @@ pub struct PackageMeta {
pub manifest: Option<PackageManifest>,
}
impl PackageMeta {
/// Returns the package's full name, including namespace and version.
pub fn spec(&self) -> PackageSpec {
PackageSpec {
namespace: self.namespace.clone(),
name: self.name.clone(),
version: self.version.parse().expect("Invalid version format"),
}
}
}
/// Information about a package.
#[derive(Debug, Serialize, Deserialize)]
pub struct PackageMetaEnd {
@ -329,7 +419,7 @@ pub struct PackageMetaEnd {
}
/// Information about a package.
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileMeta {
package: Option<usize>,
path: PathBuf,
@ -344,7 +434,7 @@ struct ConvertResult {
mod tests {
use tinymist_world::package::{PackageRegistry, PackageSpec};
use super::{package_docs, PackageInfo};
use super::{package_docs, package_docs_md, package_docs_typ, PackageInfo};
use crate::tests::*;
fn test(pkg: PackageSpec) {
@ -358,11 +448,23 @@ mod tests {
};
run_with_ctx(verse, path, &|a, _p| {
let docs = package_docs(a, &pi).unwrap();
let dest = format!(
"../../target/{}-{}-{}.json",
pi.namespace, pi.name, pi.version
);
std::fs::write(dest, serde_json::to_string_pretty(&docs).unwrap()).unwrap();
let typ = package_docs_typ(&docs).unwrap();
let dest = format!(
"../../target/{}-{}-{}.typ",
pi.namespace, pi.name, pi.version
);
std::fs::write(dest, typ).unwrap();
let md = package_docs_md(&docs).unwrap();
let dest = format!(
"../../target/{}-{}-{}.md",
pi.namespace, pi.name, pi.version
);
std::fs::write(dest, docs).unwrap();
std::fs::write(dest, md).unwrap();
})
})
}
@ -381,7 +483,7 @@ mod tests {
test(PackageSpec {
namespace: "preview".into(),
name: "touying".into(),
version: "0.5.2".parse().unwrap(),
version: "0.6.0".parse().unwrap(),
});
}