mirror of
https://github.com/typst/typst
synced 2025-05-20 12:05:27 +08:00
Add basic typst-docs CLI that spits out json (#3429)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
parent
85db05727b
commit
7ed257a3c7
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -2660,6 +2660,7 @@ dependencies = [
|
|||||||
name = "typst-docs"
|
name = "typst-docs"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
"comemo",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
"heck",
|
"heck",
|
||||||
@ -2667,10 +2668,12 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"serde_yaml 0.9.27",
|
"serde_yaml 0.9.27",
|
||||||
"syntect",
|
"syntect",
|
||||||
"typed-arena",
|
"typed-arena",
|
||||||
"typst",
|
"typst",
|
||||||
|
"typst-render",
|
||||||
"unicode_names2",
|
"unicode_names2",
|
||||||
"unscanny",
|
"unscanny",
|
||||||
"yaml-front-matter",
|
"yaml-front-matter",
|
||||||
|
@ -10,6 +10,13 @@ publish = false
|
|||||||
doctest = false
|
doctest = false
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "typst-docs"
|
||||||
|
required-features = ["cli"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
cli = ["clap", "typst-render", "serde_json"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
typst = { workspace = true }
|
typst = { workspace = true }
|
||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
@ -25,6 +32,9 @@ typed-arena = { workspace = true }
|
|||||||
unicode_names2 = { workspace = true }
|
unicode_names2 = { workspace = true }
|
||||||
unscanny = { workspace = true }
|
unscanny = { workspace = true }
|
||||||
yaml-front-matter = { workspace = true }
|
yaml-front-matter = { workspace = true }
|
||||||
|
clap = { workspace = true, optional = true }
|
||||||
|
typst-render = { workspace = true, optional = true }
|
||||||
|
serde_json = { workspace = true, optional = true }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
148
crates/typst-docs/src/main.rs
Normal file
148
crates/typst-docs/src/main.rs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use typst::model::Document;
|
||||||
|
use typst::visualize::Color;
|
||||||
|
use typst_docs::{provide, Html, Resolver};
|
||||||
|
use typst_render::render;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct CliResolver<'a> {
|
||||||
|
assets_dir: &'a Path,
|
||||||
|
verbose: bool,
|
||||||
|
base: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Resolver for CliResolver<'a> {
|
||||||
|
fn commits(&self, from: &str, to: &str) -> Vec<typst_docs::Commit> {
|
||||||
|
if self.verbose {
|
||||||
|
eprintln!("commits({from}, {to})");
|
||||||
|
}
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn example(
|
||||||
|
&self,
|
||||||
|
hash: u128,
|
||||||
|
source: Option<Html>,
|
||||||
|
document: &Document,
|
||||||
|
) -> typst_docs::Html {
|
||||||
|
if self.verbose {
|
||||||
|
eprintln!(
|
||||||
|
"example(0x{hash:x}, {:?} chars, Document)",
|
||||||
|
source.as_ref().map(|s| s.as_str().len())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let frame = &document.pages.first().expect("page 0").frame;
|
||||||
|
let pixmap = render(frame, 2.0, Color::WHITE);
|
||||||
|
let filename = format!("{hash:x}.png");
|
||||||
|
let path = self.assets_dir.join(&filename);
|
||||||
|
fs::create_dir_all(path.parent().expect("parent")).expect("create dir");
|
||||||
|
pixmap.save_png(path.as_path()).expect("save png");
|
||||||
|
let src = format!("{}assets/{filename}", self.base);
|
||||||
|
eprintln!("Generated example image {path:?}");
|
||||||
|
|
||||||
|
if let Some(code) = source {
|
||||||
|
let code_safe = code.as_str();
|
||||||
|
Html::new(format!(
|
||||||
|
r#"<div class="previewed-code"><pre>{code_safe}</pre><div class="preview"><img src="{src}" alt="Preview" /></div></div>"#
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Html::new(format!(
|
||||||
|
r#"<div class="preview"><img src="{src}" alt="Preview" /></div>"#
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn image(&self, filename: &str, data: &[u8]) -> String {
|
||||||
|
if self.verbose {
|
||||||
|
eprintln!("image({filename}, {} bytes)", data.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = self.assets_dir.join(filename);
|
||||||
|
fs::create_dir_all(path.parent().expect("parent")).expect("create dir");
|
||||||
|
fs::write(&path, data).expect("write image");
|
||||||
|
eprintln!("Created {} byte image at {path:?}", data.len());
|
||||||
|
|
||||||
|
format!("{}assets/{filename}", self.base)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn link(&self, link: &str) -> Option<String> {
|
||||||
|
if self.verbose {
|
||||||
|
eprintln!("link({link})");
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base(&self) -> &str {
|
||||||
|
self.base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the JSON representation of the documentation. This can be used to
|
||||||
|
/// generate the HTML yourself. Be warned: the JSON structure is not stable and
|
||||||
|
/// may change at any time.
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// The generation process can produce additional assets. Namely images.
|
||||||
|
/// This option controls where to spit them out. The HTML generation will
|
||||||
|
/// assume that this output directory is served at `${base_url}/assets/*`.
|
||||||
|
/// The default is `assets`. For example, if the base URL is `/docs/` then
|
||||||
|
/// the gemerated HTML might look like `<img src="/docs/assets/foo.png">`
|
||||||
|
/// even though the `--assets-dir` was set to `/tmp/images` or something.
|
||||||
|
#[arg(long, default_value = "assets")]
|
||||||
|
assets_dir: PathBuf,
|
||||||
|
|
||||||
|
/// Write the JSON output to this file. The default is `-` which is a
|
||||||
|
/// special value that means "write to standard output". If you want to
|
||||||
|
/// write to a file named `-` then use `./-`.
|
||||||
|
#[arg(long, default_value = "-")]
|
||||||
|
out_file: PathBuf,
|
||||||
|
|
||||||
|
/// The base URL for the documentation. This can be an absolute URL like
|
||||||
|
/// `https://example.com/docs/` or a relative URL like `/docs/`. This is
|
||||||
|
/// used as the base URL for the generated page's `.route` properties as
|
||||||
|
/// well as cross-page links. The default is `/`. If a `/` trailing slash is
|
||||||
|
/// not present then it will be added. This option also affects the HTML
|
||||||
|
/// asset references. For example: `--base /docs/` will generate
|
||||||
|
/// `<img src="/docs/assets/foo.png">`.
|
||||||
|
#[arg(long, default_value = "/")]
|
||||||
|
base: String,
|
||||||
|
|
||||||
|
/// Enable verbose logging. This will print out all the calls to the
|
||||||
|
/// resolver and the paths of the generated assets.
|
||||||
|
#[arg(long)]
|
||||||
|
verbose: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let args = Args::parse();
|
||||||
|
let mut base = args.base.clone();
|
||||||
|
if !base.ends_with('/') {
|
||||||
|
base.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolver = CliResolver {
|
||||||
|
assets_dir: &args.assets_dir,
|
||||||
|
verbose: args.verbose,
|
||||||
|
base: &base,
|
||||||
|
};
|
||||||
|
if args.verbose {
|
||||||
|
eprintln!("resolver: {resolver:?}");
|
||||||
|
}
|
||||||
|
let pages = provide(&resolver);
|
||||||
|
|
||||||
|
eprintln!("Be warned: the JSON structure is not stable and may change at any time.");
|
||||||
|
let json = serde_json::to_string_pretty(&pages)?;
|
||||||
|
|
||||||
|
if args.out_file.to_string_lossy() == "-" {
|
||||||
|
println!("{json}");
|
||||||
|
} else {
|
||||||
|
fs::write(&args.out_file, &*json)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user