Does not include support for directories without direct children, but that should be fixable. Apart from that, mostly robustness work then it should be a good time to start adding support for more Typst functionality.
GYTRFADRDO4SYXV6V3PEPGGFIRDHQH5YBTKEJCWFAIZ5CX4P46NAC
4MMVEN5YUVUQ5RO3X2XKZVBMBMHOEJIYUCLKL63EPRUVQQRQXUAAC
C73UJ7ZYG4EE3YTK3N66GXPNWJHEBSRE4PDQBWMN6SKQ3U6ZYKXAC
2N3KOCP74PCK2ETO5PCWBDR5PA57DDNT2KR4JLBPZPQPA56SAR4QC
BA5Y6VSEHJQBOYBS6R6FE6IZDRNAPNIN5ITJXWK7L46RJVHNI7JAC
BSJYWOYSJRERQ45AD7RN3364RYQ5P3IM76S67262VLFZPFO3B5JQC
ZYNEMGAZXWHIWGNPB2RTYG3JWTH5Y5XY4JWJ3TTPANIOORTCLISAC
HEUMSBESSTWA6G7ZG5OJP3ZW3FLXHYUCODGDHMZSLE4O777JSDJQC
// Build all the files
let 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();
std::fs::create_dir_all(output_path.parent().unwrap()).unwrap();
// TODO: this will only work for a single file, in the top-level directory
let 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();
}
}
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,
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 later
files.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 directory
let 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 AST
let 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 file
if 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 children
let content = &mut parent.content.as_mut();
// Append the function corresponding to our Typst file
content
.get_or_insert(&mut ((syn::token::Brace::default()), Vec::new()))
.1
.push(function);
// Decrease depth if last item in prefix
dbg!(&first_common, &last_common);
if last_common == file && depth > 0 {
depth -= 1;
parent = nth_deepest_module(&mut global, depth);
}
}
syn::Item::Mod(global)
}
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(),
},
),
}]
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);
output
paren_token: syn::token::Paren::default(),
elems: Punctuated::from_iter(blocks.into_iter()),
}),
None,
)],
}),
})