summaryrefslogtreecommitdiff
path: root/crates/typst-macros/src/util.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-07-02 19:59:52 +0200
committerLaurenz <laurmaedje@gmail.com>2023-07-02 20:07:43 +0200
commitebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch)
tree2bbc24ddb4124c4bb14dec0e536129d4de37b056 /crates/typst-macros/src/util.rs
parent3ab19185093d7709f824b95b979060ce125389d8 (diff)
Move everything into `crates/` directory
Diffstat (limited to 'crates/typst-macros/src/util.rs')
-rw-r--r--crates/typst-macros/src/util.rs145
1 files changed, 145 insertions, 0 deletions
diff --git a/crates/typst-macros/src/util.rs b/crates/typst-macros/src/util.rs
new file mode 100644
index 00000000..389fed06
--- /dev/null
+++ b/crates/typst-macros/src/util.rs
@@ -0,0 +1,145 @@
+use heck::ToKebabCase;
+use quote::ToTokens;
+
+use super::*;
+
+/// Return an error at the given item.
+macro_rules! bail {
+ (callsite, $($tts:tt)*) => {
+ return Err(syn::Error::new(
+ proc_macro2::Span::call_site(),
+ format!("typst: {}", format!($($tts)*))
+ ))
+ };
+ ($item:expr, $($tts:tt)*) => {
+ return Err(syn::Error::new_spanned(
+ &$item,
+ format!("typst: {}", format!($($tts)*))
+ ))
+ };
+}
+
+/// For parsing attributes of the form:
+/// #[attr(
+/// statement;
+/// statement;
+/// returned_expression
+/// )]
+pub struct BlockWithReturn {
+ pub prefix: Vec<syn::Stmt>,
+ pub expr: syn::Stmt,
+}
+
+impl Parse for BlockWithReturn {
+ fn parse(input: ParseStream) -> Result<Self> {
+ let mut stmts = syn::Block::parse_within(input)?;
+ let Some(expr) = stmts.pop() else {
+ return Err(input.error("expected at least one expression"));
+ };
+ Ok(Self { prefix: stmts, expr })
+ }
+}
+
+/// Whether an attribute list has a specified attribute.
+pub fn has_attr(attrs: &mut Vec<syn::Attribute>, target: &str) -> bool {
+ take_attr(attrs, target).is_some()
+}
+
+/// Whether an attribute list has a specified attribute.
+pub fn parse_attr<T: Parse>(
+ attrs: &mut Vec<syn::Attribute>,
+ target: &str,
+) -> Result<Option<Option<T>>> {
+ take_attr(attrs, target)
+ .map(|attr| {
+ Ok(match attr.meta {
+ syn::Meta::Path(_) => None,
+ syn::Meta::List(list) => Some(list.parse_args()?),
+ syn::Meta::NameValue(meta) => bail!(meta, "not valid here"),
+ })
+ })
+ .transpose()
+}
+
+/// Whether an attribute list has a specified attribute.
+pub fn take_attr(
+ attrs: &mut Vec<syn::Attribute>,
+ target: &str,
+) -> Option<syn::Attribute> {
+ attrs
+ .iter()
+ .position(|attr| attr.path().is_ident(target))
+ .map(|i| attrs.remove(i))
+}
+
+/// Ensure that no unrecognized attributes remain.
+pub fn validate_attrs(attrs: &[syn::Attribute]) -> Result<()> {
+ for attr in attrs {
+ if !attr.path().is_ident("doc") && !attr.path().is_ident("derive") {
+ let ident = attr.path().get_ident().unwrap();
+ bail!(ident, "unrecognized attribute: {ident}");
+ }
+ }
+ Ok(())
+}
+
+/// Convert an identifier to a kebab-case string.
+pub fn kebab_case(name: &Ident) -> String {
+ name.to_string().to_kebab_case()
+}
+
+/// Extract documentation comments from an attribute list.
+pub fn documentation(attrs: &[syn::Attribute]) -> String {
+ let mut doc = String::new();
+
+ // Parse doc comments.
+ for attr in attrs {
+ if let syn::Meta::NameValue(meta) = &attr.meta {
+ if meta.path.is_ident("doc") {
+ if let syn::Expr::Lit(lit) = &meta.value {
+ if let syn::Lit::Str(string) = &lit.lit {
+ let full = string.value();
+ let line = full.strip_prefix(' ').unwrap_or(&full);
+ doc.push_str(line);
+ doc.push('\n');
+ }
+ }
+ }
+ }
+ }
+
+ doc.trim().into()
+}
+
+/// Extract a line of metadata from documentation.
+pub fn meta_line<'a>(lines: &mut Vec<&'a str>, key: &str) -> Result<&'a str> {
+ match lines.last().and_then(|line| line.strip_prefix(&format!("{key}:"))) {
+ Some(value) => {
+ lines.pop();
+ Ok(value.trim())
+ }
+ None => bail!(callsite, "missing metadata key: {key}"),
+ }
+}
+
+/// Creates a block responsible for building a `Scope`.
+pub fn create_scope_builder(scope_block: Option<&BlockWithReturn>) -> TokenStream {
+ if let Some(BlockWithReturn { prefix, expr }) = scope_block {
+ quote! { {
+ let mut scope = ::typst::eval::Scope::deduplicating();
+ #(#prefix);*
+ #expr
+ } }
+ } else {
+ quote! { ::typst::eval::Scope::new() }
+ }
+}
+
+/// Quotes an option literally.
+pub fn quote_option<T: ToTokens>(option: &Option<T>) -> TokenStream {
+ if let Some(value) = option {
+ quote! { Some(#value) }
+ } else {
+ quote! { None }
+ }
+}