use super::*;
#[derive(Default)]
pub struct Remapper {
input: Vec<PathBuf>,
output: PathBuf,
routes: HashMap<String, String>,
sources: Vec<String>,
fallback: String,
}
impl Remapper {
pub fn new() -> Self {
Self::default()
}
pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
self.input.push(input.as_ref().to_path_buf());
self
}
pub fn inputs<I, S>(&mut self, inputs: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<Path>,
{
for input in inputs {
self.input(input);
}
self
}
pub fn source(&mut self, namespace: &str) -> &mut Self {
self.sources.push(namespace.to_string());
self
}
pub fn sources<I, S>(&mut self, namespaces: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
for namespace in namespaces {
self.source(namespace.as_ref());
}
self
}
pub fn fallback(&mut self, namespace: &str) -> &mut Self {
self.fallback = namespace.to_string();
self
}
pub fn route(&mut self, name: impl Into<String>, namespace: impl Into<String>) -> &mut Self {
self.routes.insert(name.into(), namespace.into());
self
}
pub fn routes<I, K, V>(&mut self, routes: I) -> &mut Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
for (name, namespace) in routes {
self.route(name, namespace);
}
self
}
pub fn output(&mut self, output: impl AsRef<Path>) -> &mut Self {
self.output = output.as_ref().to_path_buf();
self
}
pub fn remap(&self) -> Result<(), Error> {
if self.output.as_os_str().is_empty() {
return Err(Error::new("output is required"));
}
let name = self
.output
.file_stem()
.and_then(|s| s.to_str())
.ok_or_else(|| {
Error::new(format!("invalid output path `{}`", self.output.display()))
})?;
let files = read_inputs(&self.input)?;
let index = reader::Index::new(files);
let mut file = writer::File::new(name);
let mut apis: Vec<reader::TypeDef> = Vec::new();
let mut types: Vec<reader::TypeDef> = index.types().collect();
types.sort_by(|a, b| (a.namespace(), a.name()).cmp(&(b.namespace(), b.name())));
for ty in types {
if self.is_source_apis(ty) {
apis.push(ty);
} else {
self.write_type(&mut file, &index, ty, None);
}
}
self.split_apis(&mut file, &apis);
let bytes = file.into_stream();
std::fs::write(&self.output, bytes)
.map_err(|e| Error::new(format!("failed to write `{}`: {e}", self.output.display())))
}
fn is_source_apis(&self, ty: reader::TypeDef) -> bool {
!ty.flags().contains(TypeAttributes::WindowsRuntime)
&& ty.category() == reader::TypeCategory::Class
&& ty.name() == "Apis"
&& self.sources.iter().any(|s| s == ty.namespace())
}
fn target(&self, namespace: &str, name: &str) -> String {
if self.sources.iter().any(|s| s == namespace) {
self.routes
.get(trim_tick(name))
.cloned()
.unwrap_or_else(|| self.fallback.clone())
} else {
namespace.to_string()
}
}
fn remap_type(&self, ty: &Type) -> Type {
match ty {
Type::ClassName(tn) => Type::ClassName(self.remap_type_name(tn)),
Type::ValueName(tn) => Type::ValueName(self.remap_type_name(tn)),
Type::Array(inner) => Type::Array(Box::new(self.remap_type(inner))),
Type::RefMut(inner) => Type::RefMut(Box::new(self.remap_type(inner))),
Type::RefConst(inner) => Type::RefConst(Box::new(self.remap_type(inner))),
Type::PtrMut(inner, n) => Type::PtrMut(Box::new(self.remap_type(inner)), *n),
Type::PtrConst(inner, n) => Type::PtrConst(Box::new(self.remap_type(inner)), *n),
Type::ArrayFixed(inner, n) => Type::ArrayFixed(Box::new(self.remap_type(inner)), *n),
other => other.clone(),
}
}
fn remap_type_name(&self, tn: &TypeName) -> TypeName {
TypeName {
namespace: self.target(&tn.namespace, &tn.name),
name: tn.name.clone(),
generics: tn.generics.iter().map(|g| self.remap_type(g)).collect(),
}
}
fn remap_signature(&self, sig: &Signature) -> Signature {
Signature {
flags: sig.flags,
return_type: self.remap_type(&sig.return_type),
types: sig.types.iter().map(|t| self.remap_type(t)).collect(),
}
}
fn remap_extends(&self, file: &mut writer::File, def: reader::TypeDef) -> writer::TypeDefOrRef {
def.extends()
.map(|extends| {
let namespace = self.target(extends.namespace(), extends.name());
writer::TypeDefOrRef::TypeRef(file.TypeRef(&namespace, extends.name()))
})
.unwrap_or_default()
}
fn write_type(
&self,
file: &mut writer::File,
index: &reader::Index,
def: reader::TypeDef,
outer: Option<writer::TypeDef>,
) {
let extends = self.remap_extends(file, def);
let namespace = if def.flags().is_nested() {
String::new()
} else {
self.target(def.namespace(), def.name())
};
let type_def = file.TypeDef(&namespace, def.name(), extends, def.flags());
if let Some(outer) = outer {
file.NestedClass(type_def, outer);
}
for field in def.fields() {
let field_def = file.Field(field.name(), &self.remap_type(&field.ty()), field.flags());
if let Some(constant) = field.constant() {
file.Constant(writer::HasConstant::Field(field_def), &constant.value());
}
write_attributes(file, writer::HasAttribute::Field(field_def), field);
}
let generics: Vec<_> = def
.generic_params()
.map(|param| Type::Generic(param.name().to_string(), param.sequence()))
.collect();
write_attributes(file, writer::HasAttribute::TypeDef(type_def), def);
for map in def.interface_impls() {
let interface = self.remap_type(&map.interface(&generics));
let interface_impl = file.InterfaceImpl(type_def, &interface);
write_attributes(
file,
writer::HasAttribute::InterfaceImpl(interface_impl),
map,
);
}
for generic in def.generic_params() {
file.GenericParam(
generic.name(),
writer::TypeOrMethodDef::TypeDef(type_def),
generic.sequence(),
generic.flags(),
);
}
let is_winrt_class = def.category() == reader::TypeCategory::Class
&& def.flags().contains(TypeAttributes::WindowsRuntime);
if !is_winrt_class {
for method in def.methods() {
self.write_method(file, method, &generics);
}
}
if let Some(class_layout) = def.class_layout() {
file.ClassLayout(
type_def,
class_layout.packing_size(),
class_layout.class_size(),
);
}
for inner_def in index.nested(def) {
self.write_type(file, index, inner_def, Some(type_def));
}
}
fn write_method(&self, file: &mut writer::File, method: reader::MethodDef, generics: &[Type]) {
let signature = self.remap_signature(&method.signature(generics));
let method_def = file.MethodDef(
method.name(),
&signature,
method.flags(),
method.impl_flags(),
);
for param_def in method.params() {
let param = file.Param(param_def.name(), param_def.sequence(), param_def.flags());
write_attributes(file, writer::HasAttribute::Param(param), param_def);
}
write_attributes(file, writer::HasAttribute::MethodDef(method_def), method);
if let Some(impl_map) = method.impl_map() {
file.ImplMap(
method_def,
impl_map.flags(),
impl_map.import_name(),
impl_map.import_scope().name(),
);
}
}
fn split_apis(&self, file: &mut writer::File, apis: &[reader::TypeDef]) {
let mut namespaces: Vec<String> = Vec::new();
let mut fields: HashMap<String, Vec<reader::Field>> = HashMap::new();
let mut methods: HashMap<String, Vec<reader::MethodDef>> = HashMap::new();
let record = |namespaces: &mut Vec<String>, namespace: String| {
if !namespaces.contains(&namespace) {
namespaces.push(namespace);
}
};
let template = apis.first().copied();
for &container in apis {
for field in container.fields() {
let namespace = self.target(container.namespace(), field.name());
record(&mut namespaces, namespace.clone());
fields.entry(namespace).or_default().push(field);
}
for method in container.methods() {
let namespace = self.target(container.namespace(), method.name());
record(&mut namespaces, namespace.clone());
methods.entry(namespace).or_default().push(method);
}
}
namespaces.sort();
let Some(template) = template else { return };
for namespace in &namespaces {
let extends = self.remap_extends(file, template);
let type_def = file.TypeDef(namespace, "Apis", extends, template.flags());
for field in fields.get(namespace).into_iter().flatten() {
let field_def =
file.Field(field.name(), &self.remap_type(&field.ty()), field.flags());
if let Some(constant) = field.constant() {
file.Constant(writer::HasConstant::Field(field_def), &constant.value());
}
write_attributes(file, writer::HasAttribute::Field(field_def), *field);
}
for method in methods.get(namespace).into_iter().flatten() {
self.write_method(file, *method, &[]);
}
write_attributes(file, writer::HasAttribute::TypeDef(type_def), template);
}
}
}