use alux_shape::{FieldAlg, ShapeAlg, Sorts, Spelling, Words};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Layout {
ty: String,
declarations: BTreeMap<String, String>,
product: Option<Product>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct Product {
members: Vec<RustMember>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RustMember {
Field { words: Vec<String>, ty: String, declarations: BTreeMap<String, String> },
Merge { ty: String, declarations: BTreeMap<String, String> },
}
impl RustMember {
fn declarations(&self) -> &BTreeMap<String, String> {
match self {
Self::Field { declarations, .. } | Self::Merge { declarations, .. } => declarations,
}
}
}
impl Layout {
#[must_use]
pub fn ty(&self) -> &str {
&self.ty
}
#[must_use]
pub fn module(&self) -> String {
let declarations: Vec<&str> = self.declarations.values().map(String::as_str).collect();
declarations.join("\n\n")
}
fn of(ty: impl Into<String>, parts: &[&Self]) -> Self {
let mut declarations = BTreeMap::new();
for part in parts {
declarations.extend(part.declarations.clone());
}
Self { ty: ty.into(), declarations, product: None }
}
}
#[derive(Debug, Clone)]
pub struct RustShape {
wire: Spelling,
known: Vec<(Vec<String>, String)>,
}
impl RustShape {
#[must_use]
pub fn new(wire: Spelling) -> Self {
Self { wire, known: Vec::new() }
}
#[must_use]
pub fn known(mut self, words: Words<'_>, path: impl Into<String>) -> Self {
self.known.push((words.iter().map(|word| (*word).to_owned()).collect(), path.into()));
self
}
fn known_type(&self, words: Words<'_>) -> Option<&str> {
self.known
.iter()
.find(|(known, _)| known.iter().map(String::as_str).eq(words.iter().copied()))
.map(|(_, path)| path.as_str())
}
fn rename_all(&self) -> Option<&'static str> {
match self.wire {
Spelling::Snake => None,
Spelling::LowerCamel => Some("camelCase"),
Spelling::UpperCamel => Some("PascalCase"),
Spelling::Kebab => Some("kebab-case"),
Spelling::Screaming => Some("SCREAMING_SNAKE_CASE"),
}
}
fn attributes(&self) -> String {
let derive = "#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]";
match self.rename_all() {
Some(rename) => format!("{derive}\n#[serde(rename_all = \"{rename}\")]"),
None => derive.to_owned(),
}
}
}
impl Sorts for RustShape {
type Ty = Layout;
type Field = RustMember;
}
impl ShapeAlg for RustShape {
fn truth(&self) -> Layout {
Layout::of("bool", &[])
}
fn unit(&self) -> Layout {
Layout::of("()", &[])
}
fn text(&self) -> Layout {
Layout::of("String", &[])
}
fn literal(&self, _text: &str) -> Layout {
Layout::of("String", &[])
}
fn name_word(&self, _words: Words<'_>) -> Layout {
Layout::of("String", &[])
}
fn int(&self, signed: bool, bits: u16) -> Layout {
let sign = if signed { 'i' } else { 'u' };
Layout::of(format!("{sign}{bits}"), &[])
}
fn float(&self, bits: u16) -> Layout {
Layout::of(format!("f{bits}"), &[])
}
fn bytes(&self, len: Option<usize>) -> Layout {
match len {
Some(len) => Layout::of(format!("[u8; {len}]"), &[]),
None => Layout::of("Vec<u8>", &[]),
}
}
fn hex(&self, item: Layout) -> Layout {
Layout::of(format!("Hex<{}>", item.ty), &[&item])
}
fn decimal(&self, item: Layout) -> Layout {
Layout::of(format!("Decimal<{}>", item.ty), &[&item])
}
fn base64(&self, item: Layout) -> Layout {
Layout::of(format!("Base64<{}>", item.ty), &[&item])
}
fn opt(&self, item: Layout) -> Layout {
Layout::of(format!("Option<{}>", item.ty), &[&item])
}
fn seq(&self, item: Layout) -> Layout {
Layout::of(format!("Vec<{}>", item.ty), &[&item])
}
fn map(&self, key: Layout, value: Layout) -> Layout {
Layout::of(format!("std::collections::BTreeMap<{}, {}>", key.ty, value.ty), &[&key, &value])
}
fn product(&self, fields: Vec<RustMember>) -> Layout {
let mut declarations = BTreeMap::new();
for field in &fields {
declarations.extend(field.declarations().clone());
}
Layout { ty: "()".to_owned(), declarations, product: Some(Product { members: fields }) }
}
fn choice(&self, alternatives: Vec<Layout>) -> Layout {
let parts: Vec<&Layout> = alternatives.iter().collect();
let mut layout = Layout::of("()", &parts);
layout.product = None;
layout.ty = alternatives.iter().map(|alternative| alternative.ty.clone()).collect::<Vec<_>>().join(" | ");
layout
}
fn named(&self, words: Words<'_>, body: Layout) -> Layout {
if let Some(path) = self.known_type(words) {
return Layout::of(path, &[&body]);
}
let name = Spelling::UpperCamel.spell(words);
let declaration = self.declare(&name, &body);
let mut layout = Layout::of(name.clone(), &[&body]);
layout.declarations.insert(name, declaration);
layout.product = body.product;
layout
}
fn reference(&self, words: Words<'_>) -> Layout {
Layout::of(Spelling::UpperCamel.spell(words), &[])
}
}
impl RustShape {
fn declare(&self, name: &str, body: &Layout) -> String {
let attributes = self.attributes();
match &body.product {
Some(product) => {
let fields = product
.members
.iter()
.map(|member| match member {
RustMember::Field { words, ty, .. } => {
let field: Vec<&str> = words.iter().map(String::as_str).collect();
format!(" pub {}: {ty},", Spelling::Snake.spell(&field))
}
RustMember::Merge { ty, .. } => format!(
" #[serde(flatten)]\n pub {}: {ty},",
Spelling::Snake.spell(&[&camel_to_snake(ty)]),
),
})
.collect::<Vec<_>>()
.join("\n");
format!("{attributes}\npub struct {name} {{\n{fields}\n}}")
}
None => format!("pub type {name} = {};", body.ty),
}
}
}
fn camel_to_snake(ty: &str) -> String {
let mut out = String::new();
for (index, character) in ty.chars().enumerate() {
if character.is_uppercase() && index > 0 {
out.push('_');
}
out.extend(character.to_lowercase());
}
out
}
impl FieldAlg for RustShape {
fn field(&self, words: Words<'_>, shape: Layout) -> RustMember {
RustMember::Field {
words: words.iter().map(|word| (*word).to_owned()).collect(),
ty: shape.ty,
declarations: shape.declarations,
}
}
fn merge(&self, shape: Layout) -> RustMember {
RustMember::Merge { ty: shape.ty, declarations: shape.declarations }
}
}