Support Typst subdirectories
Dependencies
- [2]
4MMVEN5YMove generated docs inside of parent `docs` module - [3]
BMG4FSHNAdd basic `clap` support - [4]
C73UJ7ZYCreate simple `xilem_html` demo - [5]
2N3KOCP7Create MVP Pandoc->Rust compiler - [6]
BSJYWOYSImplement MVP Typst embedding - [7]
BA5Y6VSEOutput Rust code using `syn` - [8]
ZYNEMGAZUse generated Typst code from Rust - [9]
HEUMSBESOuput use statements in generated file
Change contents
- edit in Cargo.lock at line 61
version = "1.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"[[package]]name = "bitflags" - edit in Cargo.lock at line 261
][[package]]name = "patricia_tree"version = "0.6.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1c8e0b346244f1606d39ec7c47046286cbdcc6553dbdf25b3b9550d5e026f2ed"dependencies = ["bitflags 1.3.2", - edit in Cargo.lock at line 437
"patricia_tree", - replacement in Cargo.lock at line 634
"bitflags","bitflags 2.4.1", - replacement in build.rs at line 3
use typst_rust_gen::transform_pandoc;use typst_rust_gen::output_rust; - replacement in build.rs at line 23
// Set up the staging directory// Set up the output directory - replacement in build.rs at line 27
// Build all the fileslet prefix = PathBuf::from(DOCS_SOURCE);for file in typst_files {let parent = file.path().parent().unwrap();// TODO: Handle nested directories correctly// let remainder = if parent != prefix {// assert!(parent.starts_with(&prefix));// parent.strip_prefix(&prefix).unwrap().to_path_buf()// } else {// PathBuf::new()// };// Keep track of all the filenames to later construct modules// Each folder is a module, each file is a function// For example: docs/test/hello.typ is docs::test::hello()let filenames: Vec<String> = typst_files.map(|entry| entry.path().to_string_lossy().to_string()).collect(); - replacement in build.rs at line 34
// Store everything inside one large autogenerated Rust filelet output_path = out_dir.join("docs.rs");// Store everything inside one large autogenerated Rust filelet output_path = out_dir.join("docs.rs"); - replacement in build.rs at line 37[3.1820]→[3.1820:1893](∅→∅),[3.1893]→[3.4289:4485](∅→∅),[3.4485]→[3.2167:2173](∅→∅),[3.2167]→[3.2167:2173](∅→∅)
std::fs::create_dir_all(output_path.parent().unwrap()).unwrap();// TODO: this will only work for a single file, in the top-level directorylet rust_code = transform_pandoc(file.path());std::fs::write(&output_path, rust_code).unwrap();}let rust_code = output_rust(filenames);std::fs::write(&output_path, rust_code).unwrap(); - replacement in crates/typst_rust_gen/src/lib.rs at line 1
use std::path::Path;use std::path::PathBuf; - edit in crates/typst_rust_gen/src/lib.rs at line 4
use patricia_tree::StringPatriciaSet; - edit in crates/typst_rust_gen/src/lib.rs at line 7
const DOCS_DIR: &str = "docs"; - edit in crates/typst_rust_gen/src/lib.rs at line 68
}}pub fn output_rust(files: Vec<String>) -> String {let file = syn::File {shebang: None,attrs: Vec::new(),items: vec![files_to_modules(files)],};let output = quote!(#file).to_string();output}fn nth_deepest_module(global_module: &mut syn::ItemMod, limit: usize) -> &mut syn::ItemMod {dbg!(&global_module, limit);let mut prev_deepest = global_module;let mut counter = 0;while counter < limit {prev_deepest = match prev_deepest.content.as_mut().unwrap().1.last_mut().unwrap() {syn::Item::Mod(next) => next,_ => unreachable!(),};counter += 1;}prev_deepest}fn new_module(name: &str) -> syn::ItemMod {syn::ItemMod {attrs: Vec::new(),vis: syn::Visibility::Public(syn::token::Pub::default()),unsafety: None,mod_token: syn::token::Mod::default(),ident: syn::Ident::new(name, proc_macro2::Span::call_site()),content: Some((syn::token::Brace::default(),vec![syn::Item::Use(syn::ItemUse {attrs: Vec::new(),vis: syn::Visibility::Inherited,use_token: syn::token::Use::default(),leading_colon: None,tree: syn::UseTree::Path(syn::UsePath {ident: syn::Ident::new("xilem_html", proc_macro2::Span::call_site()),colon2_token: syn::token::PathSep::default(),tree: Box::new(syn::UseTree::Group(syn::UseGroup {brace_token: syn::token::Brace::default(),items: Punctuated::<syn::UseTree, syn::token::Comma>::from_iter(vec![syn::UseTree::Name(syn::UseName {ident: syn::Ident::new("elements",proc_macro2::Span::call_site(),),}),syn::UseTree::Name(syn::UseName {ident: syn::Ident::new("ViewSequence",proc_macro2::Span::call_site(),),}),].into_iter(),),})),}),semi_token: syn::token::Semi::default(),})],)),semi: None, - edit in crates/typst_rust_gen/src/lib.rs at line 142
}fn fs_path_to_module_ident(path: &str) -> String {let file_path = PathBuf::from(path);file_path.file_name().unwrap().to_string_lossy().split('.').next().unwrap().to_string() - replacement in crates/typst_rust_gen/src/lib.rs at line 156
pub fn transform_pandoc(path: &Path) -> String {let mut pandoc = pandoc::new();pandoc.add_input(path);fn files_to_modules(mut files: Vec<String>) -> syn::Item {let paths = StringPatriciaSet::from_iter(files.iter());// Sorting the list of filenames allows for easier module creation laterfiles.sort();let mut global = new_module(DOCS_DIR);let mut parent = &mut global;let mut depth = 0;for file in &files {// Get the longest shared prefix by getting the parent directorylet file_path = PathBuf::from(&file);let file_parent = file_path.parent().unwrap().to_string_lossy();dbg!(&file_parent);let prefix = paths.get_longest_common_prefix(&file_parent).unwrap_or(&file_parent);dbg!(prefix);let mut common_prefixed = paths.iter_prefix(prefix);let first_common = &common_prefixed.next().unwrap();let last_common = &common_prefixed.last().unwrap_or(file.clone());// Create Rust ASTlet pandoc = typst_to_pandoc(&file).iter().map(pandoc_to_xilem).collect();let function = blocks_to_function(&fs_path_to_module_ident(&file), pandoc);// Increase depth if first item in prefix// Make sure not to immediately jump 1 level too deep if there's only 1 fileif first_common == file && prefix != DOCS_DIR {depth += 1;parent.content.as_mut().unwrap().1.push(syn::Item::Mod(new_module(&file_path.parent().unwrap().file_name().unwrap().to_string_lossy(),)));parent = nth_deepest_module(&mut global, depth);}// Get a mutable reference to the module childrenlet content = &mut parent.content.as_mut();// Append the function corresponding to our Typst filecontent.get_or_insert(&mut ((syn::token::Brace::default()), Vec::new())).1.push(function);// Decrease depth if last item in prefixdbg!(&first_common, &last_common);if last_common == file && depth > 0 {depth -= 1;parent = nth_deepest_module(&mut global, depth);}}syn::Item::Mod(global)} - edit in crates/typst_rust_gen/src/lib.rs at line 222
fn typst_to_pandoc(filename: &str) -> Vec<pandoc_ast::Block> {let mut pandoc = pandoc::new();pandoc.add_input(filename); - replacement in crates/typst_rust_gen/src/lib.rs at line 233
let ast = pandoc_ast::Pandoc::from_json(&output);dbg!(&ast);let blocks: Vec<syn::Expr> = ast.blocks.iter().map(transform_block).collect();dbg!(&blocks, blocks.len());pandoc_ast::Pandoc::from_json(&output).blocks} - replacement in crates/typst_rust_gen/src/lib.rs at line 236
let file = syn::File {shebang: None,fn blocks_to_function(name: &str, blocks: Vec<syn::Expr>) -> syn::Item {syn::Item::Fn(syn::ItemFn { - replacement in crates/typst_rust_gen/src/lib.rs at line 239[3.1898]→[3.0:21](∅→∅),[3.21]→[2.0:42](∅→∅),[2.42]→[3.63:147](∅→∅),[3.63]→[3.63:147](∅→∅),[3.147]→[2.43:1852](∅→∅)
items: vec![syn::Item::Mod(syn::ItemMod {attrs: Vec::new(),vis: syn::Visibility::Inherited,unsafety: None,mod_token: syn::token::Mod::default(),ident: syn::Ident::new("docs", proc_macro2::Span::call_site()),content: Some((syn::token::Brace::default(), vec![syn::Item::Use(syn::ItemUse {attrs: Vec::new(),vis: syn::Visibility::Inherited,use_token: syn::token::Use::default(),leading_colon: None,tree: syn::UseTree::Path(syn::UsePath {ident: syn::Ident::new("xilem_html", proc_macro2::Span::call_site()),colon2_token: syn::token::PathSep::default(),tree: Box::new(syn::UseTree::Group(syn::UseGroup {brace_token: syn::token::Brace::default(),items: Punctuated::<syn::UseTree, syn::token::Comma>::from_iter(vec![syn::UseTree::Name(syn::UseName {ident: syn::Ident::new("elements",proc_macro2::Span::call_site(),),}),syn::UseTree::Name(syn::UseName {ident: syn::Ident::new("ViewSequence",proc_macro2::Span::call_site(),),}),]vis: syn::Visibility::Public(syn::token::Pub::default()),sig: syn::Signature {constness: None,asyncness: None,unsafety: None,abi: None,fn_token: syn::token::Fn::default(),ident: syn::Ident::new(name, proc_macro2::Span::call_site()),generics: syn::Generics { lt_token: None, params: Punctuated::from_iter(vec![syn::GenericParam::Type(syn::TypeParam { attrs: Vec::new(), ident: syn::Ident::new("T", proc_macro2::Span::call_site()), colon_token: None, bounds: Punctuated::new(), eq_token: None, default: None })].into_iter()), gt_token: None, where_clause: None },paren_token: syn::token::Paren::default(),inputs: Punctuated::new(),variadic: None,output: syn::ReturnType::Type(syn::token::RArrow::default(),Box::new(syn::Type::ImplTrait(syn::TypeImplTrait {impl_token: syn::token::Impl::default(),bounds: Punctuated::from_iter(vec![syn::TypeParamBound::Trait(syn::TraitBound {paren_token: None,modifier: syn::TraitBoundModifier::None,lifetimes: None,path: syn::Path {leading_colon: None,segments: Punctuated::from_iter(vec![syn::PathSegment {ident: syn::Ident::new("ViewSequence",proc_macro2::Span::call_site(),),arguments: syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {colon2_token: None,lt_token: syn::token::Lt::default(),args: Punctuated::from_iter(vec![syn::GenericArgument::Type(syn::Type::Path(syn::TypePath {qself: None,path: syn::Path {leading_colon: None,segments: Punctuated::from_iter(vec![syn::PathSegment { ident: syn::Ident::new("T", proc_macro2::Span::call_site()), arguments: syn::PathArguments::None }].into_iter()),},}),)].into_iter(),),gt_token: syn::token::Gt::default(),},),}] - replacement in crates/typst_rust_gen/src/lib.rs at line 290
})),}),semi_token: syn::token::Semi::default(),}),syn::Item::Fn(syn::ItemFn {},})].into_iter(),),})),),},block: Box::new(syn::Block {brace_token: syn::token::Brace::default(),stmts: vec![syn::Stmt::Expr(syn::Expr::Tuple(syn::ExprTuple { - replacement in crates/typst_rust_gen/src/lib.rs at line 302[2.2174]→[2.2174:6487](∅→∅),[2.6487]→[3.522:589](∅→∅),[3.522]→[3.522:589](∅→∅),[3.589]→[2.6488:6981](∅→∅),[2.6981]→[3.1894:1910](∅→∅),[3.1894]→[3.1894:1910](∅→∅),[3.3059]→[3.3168:3179](∅→∅),[3.3179]→[3.3072:3080](∅→∅),[3.3072]→[3.3072:3080](∅→∅),[3.3080]→[3.3180:3254](∅→∅)
vis: syn::Visibility::Public(syn::token::Pub::default()),sig: syn::Signature {constness: None,asyncness: None,unsafety: None,abi: None,fn_token: syn::token::Fn::default(),ident: syn::Ident::new("test", proc_macro2::Span::call_site()),generics: syn::Generics { lt_token: None, params: Punctuated::from_iter(vec![syn::GenericParam::Type(syn::TypeParam { attrs: Vec::new(), ident: syn::Ident::new("T", proc_macro2::Span::call_site()), colon_token: None, bounds: Punctuated::new(), eq_token: None, default: None })].into_iter()), gt_token: None, where_clause: None },paren_token: syn::token::Paren::default(),inputs: Punctuated::new(),variadic: None,output: syn::ReturnType::Type(syn::token::RArrow::default(),Box::new(syn::Type::ImplTrait(syn::TypeImplTrait {impl_token: syn::token::Impl::default(),bounds: Punctuated::from_iter(vec![syn::TypeParamBound::Trait(syn::TraitBound {paren_token: None,modifier: syn::TraitBoundModifier::None,lifetimes: None,path: syn::Path {leading_colon: None,segments: Punctuated::from_iter(vec![syn::PathSegment {ident: syn::Ident::new("ViewSequence",proc_macro2::Span::call_site(),),arguments: syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {colon2_token: None,lt_token: syn::token::Lt::default(),args: Punctuated::from_iter(vec![syn::GenericArgument::Type(syn::Type::Path(syn::TypePath {qself: None,path: syn::Path {leading_colon: None,segments: Punctuated::from_iter(vec![syn::PathSegment { ident: syn::Ident::new("T", proc_macro2::Span::call_site()), arguments: syn::PathArguments::None }].into_iter()),},}),)].into_iter(),),gt_token: syn::token::Gt::default(),},),}].into_iter(),),},})].into_iter(),),})),),},block: Box::new(syn::Block {brace_token: syn::token::Brace::default(),stmts: vec![syn::Stmt::Expr(syn::Expr::Tuple(syn::ExprTuple {attrs: Vec::new(),paren_token: syn::token::Paren::default(),elems: Punctuated::from_iter(blocks.into_iter()),}),None,)],}),}),])),semi: None,}),],};let output = quote!(#file).to_string();dbg!(&output);outputparen_token: syn::token::Paren::default(),elems: Punctuated::from_iter(blocks.into_iter()),}),None,)],}),}) - replacement in crates/typst_rust_gen/src/lib.rs at line 338
fn transform_block(block: &pandoc_ast::Block) -> syn::Expr {fn pandoc_to_xilem(block: &pandoc_ast::Block) -> syn::Expr { - edit in crates/typst_rust_gen/src/lib.rs at line 374
dbg!(&full_literal); - replacement in crates/typst_rust_gen/examples/basic.rs at line 2
use typst_rust_gen::transform_pandoc;use typst_rust_gen::output_rust; - replacement in crates/typst_rust_gen/examples/basic.rs at line 5
dbg!(transform_pandoc(Path::new("docs/test.typ")));dbg!(output_rust(Path::new("docs/test.typ"))); - edit in crates/typst_rust_gen/Cargo.toml at line 9
patricia_tree = "0.6.3"