use std::{
borrow::{Borrow, Cow},
cmp::Ord,
ffi::OsStr,
fmt::{self, Display},
path::{Component, Path},
};
use crate::decorator::DecoratorSet;
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct CrateName(String);
impl Display for CrateName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl CrateName {
pub const fn new(name: String) -> Self {
Self(name)
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn find_crate_name(path: &Path) -> Option<Self> {
path.ancestors()
.take_while(|path| {
matches!(
path.components().next_back(),
Some(Component::Normal(_) | Component::CurDir)
)
})
.find(|path| path.file_name() == Some(OsStr::new("src")))?
.parent()?
.file_name()?
.to_str()
.map(|name| name.replace("-", "_"))
.map(CrateName)
}
}
impl PartialEq<str> for CrateName {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<&str> for CrateName {
fn eq(&self, other: &&str) -> bool {
self == *other
}
}
#[derive(Debug, Clone)]
pub struct Id {
pub original: TypeName,
pub renamed: TypeName,
}
impl std::fmt::Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.original == self.renamed {
write!(f, "({})", self.original)
} else {
write!(f, "({}, {})", self.original, self.renamed)
}
}
}
#[derive(Debug, Clone)]
pub struct RustStruct {
pub id: Id,
pub generic_types: Vec<TypeName>,
pub fields: Vec<RustField>,
pub comments: Vec<String>,
pub decorators: DecoratorSet,
}
#[derive(Debug, Clone)]
pub struct RustTypeAlias {
pub id: Id,
pub generic_types: Vec<TypeName>,
pub ty: RustType,
pub comments: Vec<String>,
pub decorators: DecoratorSet,
}
#[derive(Debug, Clone)]
pub struct RustField {
pub id: Id,
pub ty: RustType,
pub comments: Vec<String>,
pub has_default: bool,
pub decorators: DecoratorSet,
}
#[derive(Debug, Clone)]
pub enum RustType {
Generic {
#[allow(missing_docs)]
id: TypeName,
#[allow(missing_docs)]
parameters: Vec<RustType>,
},
Special(SpecialRustType),
Simple {
#[allow(missing_docs)]
id: TypeName,
},
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum SpecialRustType {
Vec(Box<RustType>),
Array(Box<RustType>, usize),
Slice(Box<RustType>),
HashMap(Box<RustType>, Box<RustType>),
Option(Box<RustType>),
Unit,
String,
Char,
I8,
I16,
I32,
I64,
U8,
U16,
U32,
U64,
ISize,
USize,
Bool,
F32,
F64,
I54,
U53,
}
impl RustType {
pub fn contains_type(&self, ty: &TypeName) -> bool {
match &self {
Self::Simple { id } => id == ty,
Self::Generic { id, parameters } => {
id == ty || parameters.iter().any(|p| p.contains_type(ty))
}
Self::Special(special) => special.contains_type(ty),
}
}
pub fn id(&self) -> Option<&TypeName> {
match &self {
Self::Simple { id } | Self::Generic { id, .. } => Some(id),
Self::Special(_) => None,
}
}
pub fn is_optional(&self) -> bool {
matches!(self, Self::Special(SpecialRustType::Option(_)))
}
pub fn is_double_optional(&self) -> bool {
matches!(self, Self::Special(SpecialRustType::Option(inner)) if inner.is_optional())
}
pub fn is_vec(&self) -> bool {
matches!(self, Self::Special(SpecialRustType::Vec(_)))
}
pub fn is_hash_map(&self) -> bool {
matches!(self, Self::Special(SpecialRustType::HashMap(_, _)))
}
pub fn parameters(&self) -> Box<dyn Iterator<Item = &Self> + '_> {
match &self {
Self::Simple { .. } => Box::new(std::iter::empty()),
Self::Generic { parameters, .. } => Box::new(parameters.iter()),
Self::Special(special) => special.parameters(),
}
}
}
impl SpecialRustType {
pub fn contains_type(&self, ty: &TypeName) -> bool {
match self {
Self::Vec(rty) | Self::Array(rty, _) | Self::Slice(rty) | Self::Option(rty) => {
rty.contains_type(ty)
}
Self::HashMap(rty1, rty2) => rty1.contains_type(ty) || rty2.contains_type(ty),
Self::Unit
| Self::String
| Self::Char
| Self::I8
| Self::I16
| Self::I32
| Self::I64
| Self::U8
| Self::U16
| Self::U32
| Self::U64
| Self::ISize
| Self::USize
| Self::Bool
| Self::F32
| Self::F64
| Self::I54
| Self::U53 => false,
}
}
pub fn parameters(&self) -> Box<dyn Iterator<Item = &RustType> + '_> {
match &self {
Self::Vec(rtype) | Self::Array(rtype, _) | Self::Slice(rtype) | Self::Option(rtype) => {
Box::new(std::iter::once(rtype.as_ref()))
}
Self::HashMap(rtype1, rtype2) => {
Box::new([rtype1.as_ref(), rtype2.as_ref()].into_iter())
}
Self::Unit
| Self::String
| Self::Char
| Self::I8
| Self::I16
| Self::I32
| Self::I64
| Self::U8
| Self::U16
| Self::U32
| Self::U64
| Self::ISize
| Self::USize
| Self::Bool
| Self::F32
| Self::F64
| Self::I54
| Self::U53 => Box::new(std::iter::empty()),
}
}
}
#[derive(Debug, Clone)]
pub enum RustEnum {
Unit {
shared: RustEnumShared,
unit_variants: Vec<RustEnumVariantShared>,
},
Algebraic {
tag_key: String,
content_key: String,
shared: RustEnumShared,
variants: Vec<RustEnumVariant>,
},
}
impl RustEnum {
pub fn shared(&self) -> &RustEnumShared {
match self {
Self::Unit { shared, .. } | Self::Algebraic { shared, .. } => shared,
}
}
}
#[derive(Debug, Clone)]
pub struct RustEnumShared {
pub id: Id,
pub generic_types: Vec<TypeName>,
pub comments: Vec<String>,
pub decorators: DecoratorSet,
pub is_recursive: bool,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum RustEnumVariant {
Unit(RustEnumVariantShared),
Tuple {
ty: RustType,
shared: RustEnumVariantShared,
},
AnonymousStruct {
fields: Vec<RustField>,
shared: RustEnumVariantShared,
},
}
impl RustEnumVariant {
pub fn shared(&self) -> &RustEnumVariantShared {
match self {
Self::Unit(shared)
| Self::Tuple { shared, .. }
| Self::AnonymousStruct { shared, .. } => shared,
}
}
}
#[derive(Debug, Clone)]
pub struct RustEnumVariantShared {
pub id: Id,
pub comments: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct RustConst {
pub id: Id,
pub ty: RustType,
pub expr: RustConstExpr,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum RustConstExpr {
Int(i128),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ImportedType {
pub base_crate: CrateName,
pub type_name: TypeName,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TypeName(Cow<'static, str>);
impl TypeName {
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_ref()
}
#[inline]
#[must_use]
pub fn new_string(ident: String) -> Self {
Self(Cow::Owned(ident))
}
#[inline]
#[must_use]
pub const fn new_static(ident: &'static str) -> Self {
Self(Cow::Borrowed(ident))
}
}
impl AsRef<str> for TypeName {
#[inline]
#[must_use]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Borrow<str> for TypeName {
#[inline]
#[must_use]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for TypeName {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl PartialEq<str> for TypeName {
#[inline]
#[must_use]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<&str> for TypeName {
#[inline]
#[must_use]
fn eq(&self, other: &&str) -> bool {
self == *other
}
}
#[cfg(test)]
mod test {
use super::CrateName;
use std::path::Path;
#[test]
fn test_crate_name() {
let path = Path::new("/some/path/to/projects/core/foundation/op-proxy/src/android.rs");
assert_eq!(CrateName::find_crate_name(path).unwrap(), "op_proxy",);
}
#[test]
fn skip_curdir() {
let path = Path::new("/path/to/crate-name/./src/main.rs");
assert_eq!(CrateName::find_crate_name(path).unwrap(), "crate_name")
}
#[test]
fn bail_on_parent_dir() {
let path = Path::new("/path/to/crate-name/src/foo/../stuff.rs");
assert!(CrateName::find_crate_name(path).is_none());
}
#[test]
fn accept_parent_dir_before_crate() {
let path = Path::new("/path/to/../crate/src/foo/bar/stuff.rs");
assert_eq!(CrateName::find_crate_name(path).unwrap(), "crate");
}
#[test]
fn reject_rooted_src() {
let path = Path::new("/src/foo.rs");
assert!(CrateName::find_crate_name(path).is_none());
}
}
#[cfg(test)]
mod rust_type_api {
use super::*;
const INT: RustType = RustType::Special(SpecialRustType::I32);
fn make_option(inner: RustType) -> RustType {
RustType::Special(SpecialRustType::Option(Box::new(inner)))
}
#[test]
fn test_optional() {
let ty = make_option(INT);
assert!(ty.is_optional());
assert!(!ty.is_double_optional());
assert!(!ty.is_hash_map());
}
#[test]
fn test_double_optional() {
let ty = make_option(make_option(INT));
assert!(ty.is_optional());
assert!(ty.is_double_optional());
assert!(!ty.is_vec());
}
}