use std::iter;
use openusd::usd;
use proc_macro2::TokenStream;
use quote::quote;
use super::{ident, items};
use super::lower::{
AccessorTrait, Constant, Instancing, MetadataAccessor, MetadataTrait, PropertyKind, Reflected, RustAccessor,
RustClass, RustEnum, RustLibrary, TypedRead, kind_constant, schema_root,
};
use crate::doc;
pub fn library(library: &RustLibrary, declarations: &TokenStream) -> TokenStream {
let name = library.library.as_str();
let tokens = tokens_module(&library.tokens);
let classes = library.classes.iter().map(|class| {
let accessors = accessor_trait(class);
let view = view(class);
quote! {
#accessors
#view
}
});
let enums = library.enums.iter().map(token_enum);
let metadata = library.metadata.iter().map(metadata_trait);
let library_name = ident(items::LIBRARY_NAME);
quote! {
#tokens
#(#enums)*
pub const #library_name: &str = #name;
#declarations
#(#classes)*
#(#metadata)*
}
}
pub(super) fn tokens_module(constants: &[Constant]) -> TokenStream {
let constants = constants.iter().map(|constant| {
let Constant {
name,
value,
documentation,
} = constant;
let documentation = super::doc_lines(documentation);
quote! {
#(#[doc = #documentation])*
pub const #name: &str = #value;
}
});
let tokens = ident(items::TOKENS);
quote! {
pub mod #tokens {
#(#constants)*
}
}
}
fn metadata_trait(generated: &MetadataTrait) -> TokenStream {
let MetadataTrait {
name,
documentation,
handle,
stage,
fields,
} = generated;
let documentation = super::doc_lines(documentation);
let (declarations, bodies): (Vec<TokenStream>, Vec<TokenStream>) = fields
.iter()
.map(|field| {
let MetadataAccessor {
getter,
setter,
token,
rust_type,
documentation,
} = field;
let documentation = super::doc_lines(documentation);
let read = quote! { fn #getter(&self) -> ::openusd::Result<::std::option::Option<#rust_type>> };
let write: Vec<TokenStream> = setter
.iter()
.map(|setter| {
quote! {
fn #setter(self, value: #rust_type) -> ::std::result::Result<Self, ::openusd::usd::StageAuthoringError>
}
})
.collect();
let declaration = quote! {
#(#[doc = #documentation])*
#read;
#(
#write
where
Self: ::std::marker::Sized;
)*
};
let reads = if *stage {
quote! {
self.stage_metadata(#token)?
.map(<#rust_type as ::std::convert::TryFrom<::openusd::sdf::Value>>::try_from)
.transpose()
.map_err(::std::convert::Into::into)
}
} else {
quote! { self.get_metadata::<#rust_type>(#token) }
};
let body = quote! {
#read {
#reads
}
#(
#write {
self.set_metadata(#token, value)
}
)*
};
(declaration, body)
})
.unzip();
quote! {
#(#[doc = #documentation])*
pub trait #name {
#(#declarations)*
}
impl #name for #handle {
#(#bodies)*
}
}
}
fn token_enum(generated: &RustEnum) -> TokenStream {
let RustEnum {
name,
documentation,
variants,
default,
..
} = generated;
let documentation = super::doc_lines(documentation);
let declared = variants.iter().map(|variant| {
let name = &variant.name;
let documentation = format!(" `{:?}`.", variant.value);
let default = (default.as_ref() == Some(name)).then(|| quote! { #[default] });
quote! {
#[doc = #documentation]
#default
#name
}
});
let written = variants.iter().map(|variant| {
let (name, constant) = (&variant.name, &variant.constant);
quote! { Self::#name => #constant }
});
let read = variants.iter().map(|variant| {
let (name, constant) = (&variant.name, &variant.constant);
quote! { #constant => ::std::option::Option::Some(Self::#name) }
});
let derive_default = default.as_ref().map(|_| quote! { , ::std::default::Default });
quote! {
#(#[doc = #documentation])*
#[derive(
::std::clone::Clone, ::std::marker::Copy, ::std::fmt::Debug,
::std::cmp::PartialEq, ::std::cmp::Eq, ::std::hash::Hash #derive_default
)]
pub enum #name {
#(#declared),*
}
impl #name {
pub const fn as_token(self) -> &'static str {
match self {
#(#written),*
}
}
pub fn from_token(token: impl ::std::convert::AsRef<str>) -> ::std::option::Option<Self> {
match token.as_ref() {
#(#read,)*
_ => ::std::option::Option::None,
}
}
}
::openusd::sdf::impl_token_value!(#name);
}
}
fn documented(class: &RustClass, note: &str) -> TokenStream {
let own = class.documentation.as_deref().unwrap_or_default();
let separator = (!own.is_empty() && !note.is_empty()).then_some("");
let lines = own
.lines()
.chain(separator)
.chain(note.lines())
.map(|line| format!(" {line}"));
quote! { #(#[doc = #lines])* }
}
fn accessor_note(class: &RustClass) -> String {
let Some(own) = &class.accessor_trait else {
return String::new();
};
let own = doc::link(&own.name.to_string(), None);
let inherited: Vec<String> = class.inherited.iter().map(linked).collect();
let text = match (class.accessors.is_empty(), inherited.is_empty()) {
(true, true) => return String::new(),
(true, false) => format!(
"Its accessors are those of the classes behind it, on {}, which a caller has to have in \
scope to reach one.",
listed(&inherited)
),
(false, true) => format!("Its accessors live on {own}, which a caller has to have in scope to reach one."),
(false, false) => format!(
"A property is reached through the trait that declares it, which a caller has to have in \
scope. This schema's own are on {own}; the rest come from the classes behind it, on {}.",
listed(&inherited)
),
};
format!("# Accessors\n\n{}", doc::wrap(&text))
}
fn trait_note(class: &RustClass) -> String {
if class.accessors.is_empty() {
return String::new();
}
let name = doc::link(&class.name.to_string(), None);
doc::wrap(&format!(
"The accessors {name} carries. They live on a trait so that a schema deriving from this one \
carries them too."
))
}
fn linked(path: &syn::Path) -> String {
let name = match path.segments.last() {
Some(segment) => segment.ident.to_string(),
None => return String::new(),
};
if path.segments.len() == 1 && path.leading_colon.is_none() {
return doc::link(&name, None);
}
doc::link(&name, Some(&super::path_text(path)))
}
fn listed(names: &[String]) -> String {
match names.split_last() {
Some((last, [])) => last.clone(),
Some((last, rest)) => format!("{} and {last}", rest.join(", ")),
None => String::new(),
}
}
fn accessor_trait(class: &RustClass) -> TokenStream {
let Some(AccessorTrait { name, parent }) = &class.accessor_trait else {
return TokenStream::new();
};
let documentation = documented(class, &trait_note(class));
let allow = allow_non_camel_case(class);
let methods = class.accessors.iter().map(|accessor| method(accessor, false));
let reflected = class
.reflected
.iter()
.map(|reflected| reflected_method(reflected, false));
quote! {
#documentation
#allow
pub trait #name: #parent {
#(#reflected)*
#(#methods)*
}
}
}
fn reflected_method(reflected: &Reflected, inherent: bool) -> TokenStream {
let Reflected { accessor, view } = reflected;
let documentation = doc::wrap(&format!(
"Views the prim through {}, whose properties this schema carries.",
linked(view)
));
let documentation = super::doc_lines(&documentation);
let (visibility, prim) = reach(inherent);
quote! {
#(#[doc = #documentation])*
#visibility fn #accessor(&self) -> #view {
#view::from_prim_unchecked(::openusd::usd::Prim::clone(#prim))
}
}
}
fn reach(inherent: bool) -> (TokenStream, TokenStream) {
match inherent {
true => (quote! { pub }, quote! { self }),
false => (TokenStream::new(), quote! { self.prim() }),
}
}
fn allow_non_camel_case(class: &RustClass) -> TokenStream {
match class.name.to_string().contains('_') {
true => quote! {
#[allow(
non_camel_case_types,
reason = "the schema's identifier carries its version, and the view is named after it"
)]
},
false => TokenStream::new(),
}
}
fn view(class: &RustClass) -> TokenStream {
let name = &class.name;
let documentation = documented(class, &accessor_note(class));
let allow = allow_non_camel_case(class);
let kind = kind_constant(class.kind);
let constructors = constructors(class);
let (declaration, prim) = match class.kind.is_multiple_apply_api_schema() {
true => (
quote! {
pub struct #name {
prim: ::openusd::usd::Prim,
name: ::openusd::tf::Token,
}
},
quote! { &self.prim },
),
false => (quote! { pub struct #name(::openusd::usd::Prim); }, quote! { &self.0 }),
};
let inherent = class
.accessor_trait
.is_none()
.then(|| {
class
.reflected
.iter()
.map(|reflected| reflected_method(reflected, true))
.chain(class.accessors.iter().map(|accessor| method(accessor, true)))
})
.into_iter()
.flatten();
let own = class.accessor_trait.iter().map(|own| {
let own = &own.name;
quote! { impl #own for #name {} }
});
let root = schema_root(class.kind);
let answers_to = iter::once(&root)
.chain(&class.inherited)
.map(|path| quote! { impl #path for #name {} });
quote! {
#documentation
#allow
#[derive(::std::clone::Clone, ::std::fmt::Debug)]
#declaration
impl #name {
#constructors
#(#inherent)*
}
impl ::openusd::usd::SchemaBase for #name {
const KIND: ::openusd::usd::SchemaKind = #kind;
fn prim(&self) -> &::openusd::usd::Prim {
#prim
}
}
impl ::std::ops::Deref for #name {
type Target = ::openusd::usd::Prim;
fn deref(&self) -> &Self::Target {
#prim
}
}
#(#own)*
#(#answers_to)*
}
}
fn constructors(class: &RustClass) -> TokenStream {
let constant = &class.constant;
match class.kind {
usd::SchemaKind::ConcreteTyped | usd::SchemaKind::AbstractTyped => {
let define = class.kind.is_concrete().then(|| {
quote! {
pub fn define(
stage: &::openusd::usd::Stage,
path: impl ::openusd::sdf::IntoPath,
) -> ::openusd::Result<Self> {
::std::result::Result::Ok(Self(stage.define_typed_prim(path, #constant)?))
}
}
});
quote! {
pub fn from_prim_unchecked(prim: ::openusd::usd::Prim) -> Self {
Self(prim)
}
#define
pub fn from_prim(
prim: ::openusd::usd::Prim,
) -> ::openusd::Result<::std::option::Option<Self>> {
::std::result::Result::Ok(prim.is_a(#constant)?.then_some(Self(prim)))
}
pub fn get(
stage: &::openusd::usd::Stage,
path: impl ::openusd::sdf::IntoPath,
) -> ::openusd::Result<::std::option::Option<Self>> {
Self::from_prim(stage.prim(path)?)
}
}
}
usd::SchemaKind::SingleApplyApi => quote! {
pub fn from_prim_unchecked(prim: ::openusd::usd::Prim) -> Self {
Self(prim)
}
pub fn apply(prim: &::openusd::usd::Prim) -> ::openusd::Result<Self> {
::std::result::Result::Ok(Self(prim.clone().apply_api(#constant)?))
}
pub fn can_apply(
prim: &::openusd::usd::Prim,
) -> ::std::result::Result<(), ::openusd::usd::ApplyApiError> {
prim.can_apply_api(#constant)
}
pub fn from_prim(
prim: ::openusd::usd::Prim,
) -> ::openusd::Result<::std::option::Option<Self>> {
::std::result::Result::Ok(prim.has_api_schema(#constant)?.then_some(Self(prim)))
}
pub fn get(
stage: &::openusd::usd::Stage,
path: impl ::openusd::sdf::IntoPath,
) -> ::openusd::Result<::std::option::Option<Self>> {
Self::from_prim(stage.prim(path)?)
}
},
usd::SchemaKind::MultipleApplyApi => {
let decoding = class.instancing.as_ref().map(decoding);
quote! {
pub fn from_prim_unchecked(
prim: ::openusd::usd::Prim,
name: impl ::std::convert::Into<::openusd::tf::Token>,
) -> Self {
Self { prim, name: name.into() }
}
pub fn name(&self) -> &::openusd::tf::Token {
&self.name
}
pub fn apply(
prim: &::openusd::usd::Prim,
name: impl ::std::convert::Into<::openusd::tf::Token>,
) -> ::openusd::Result<Self> {
let name = name.into();
let applied =
::openusd::usd::SchemaRegistry::make_applied_name(#constant, name.as_str());
::std::result::Result::Ok(Self::from_prim_unchecked(prim.clone().apply_api(applied)?, name))
}
pub fn get_instance(
prim: &::openusd::usd::Prim,
name: impl ::std::convert::Into<::openusd::tf::Token>,
) -> ::openusd::Result<::std::option::Option<Self>> {
let name = name.into();
let applied =
::openusd::usd::SchemaRegistry::make_applied_name(#constant, name.as_str());
let carried = prim.has_api_schema(applied)?;
::std::result::Result::Ok(carried.then(|| Self::from_prim_unchecked(prim.clone(), name)))
}
pub fn can_apply(
prim: &::openusd::usd::Prim,
name: &str,
) -> ::std::result::Result<(), ::openusd::usd::ApplyApiError> {
prim.can_apply_api(
::openusd::usd::SchemaRegistry::make_applied_name(#constant, name),
)
}
pub fn get_all(prim: &::openusd::usd::Prim) -> ::openusd::Result<::std::vec::Vec<Self>> {
prim.api_schema_instance_names(#constant).map(|names| {
names
.into_iter()
.map(|name| Self::from_prim_unchecked(prim.clone(), name))
.collect()
})
}
#decoding
}
}
usd::SchemaKind::AbstractBase | usd::SchemaKind::NonAppliedApi => quote! {
pub fn from_prim_unchecked(prim: ::openusd::usd::Prim) -> Self {
Self(prim)
}
},
}
}
fn decoding(instancing: &Instancing) -> TokenStream {
let Instancing { prefix, base_names } = instancing;
quote! {
pub fn is_schema_property_base_name(base_name: &str) -> bool {
::std::matches!(base_name, #(#base_names)|*)
}
pub fn instance_at_path(
path: &::openusd::sdf::Path,
) -> ::std::option::Option<(::openusd::sdf::Path, ::openusd::tf::Token)> {
let (prim, property) = path.split_property()?;
if !property.split(':').all(::openusd::sdf::Path::is_valid_identifier) {
return ::std::option::Option::None;
}
let instance = property.strip_prefix(#prefix)?.strip_prefix(':')?;
let base_name = instance.rsplit(':').next()?;
match Self::is_schema_property_base_name(base_name) {
true => ::std::option::Option::None,
false => ::std::option::Option::Some((prim, ::openusd::tf::Token::from(instance))),
}
}
}
}
fn method(accessor: &RustAccessor, inherent: bool) -> TokenStream {
let RustAccessor {
getter,
creator,
builder,
token,
instanced,
documentation,
..
} = accessor;
let token = match instanced {
true => quote! {
::openusd::usd::SchemaRegistry::make_multiple_apply_name_instance(#token, self.name.as_str())
},
false => token.clone(),
};
let (visibility, prim) = reach(inherent);
let custom = (!accessor.custom).then(|| quote! { .custom(false) });
let uniform = accessor
.uniform
.then(|| quote! { .variability(::openusd::sdf::Variability::Uniform) });
let (kind, reader, builder_kind, declared) = match &accessor.kind {
PropertyKind::Relationship => (
quote! { ::openusd::usd::Relationship },
quote! { relationship },
quote! { ::openusd::usd::RelationshipBuilder<'static> },
quote! { #prim.relationship_builder(#token) #custom },
),
PropertyKind::Attribute { type_constant, .. } => (
quote! { ::openusd::usd::Attribute },
quote! { attribute },
quote! { ::openusd::usd::AttributeBuilder<'static> },
quote! { #prim.attribute_builder(#token, #type_constant) #custom #uniform },
),
};
let documentation = super::doc_lines(documentation);
let read = if accessor.custom_get {
TokenStream::new()
} else {
quote! {
#(#[doc = #documentation])*
#visibility fn #getter(&self) -> #kind {
#prim.#reader(#token)
}
}
};
let typed = accessor.typed.as_ref().map(|TypedRead { value, value_at, ty }| {
let at = value_at.as_ref().map(|value_at| {
quote! {
#visibility fn #value_at(
&self,
time: impl ::std::convert::Into<::std::option::Option<::openusd::usd::TimeCode>>,
) -> ::openusd::Result<::std::option::Option<#ty>> {
self.#getter().get_at::<#ty>(time)
}
}
});
quote! {
#visibility fn #value(&self) -> ::openusd::Result<::std::option::Option<#ty>> {
self.#getter().get::<#ty>()
}
#at
}
});
quote! {
#read
#typed
#visibility fn #builder(&self) -> #builder_kind {
#declared
}
#visibility fn #creator(&self) -> ::openusd::Result<#kind> {
::std::result::Result::Ok(self.#builder().build()?)
}
}
}