trans-gen 0.4.0

Generating trans code from Rust into other languages
Documentation
pub use trans;

use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use std::path::Path;
use trans::*;

pub mod gens;

#[derive(Debug)]
pub struct File {
    pub path: String,
    pub content: String,
}

#[derive(Debug)]
pub struct GenResult {
    pub files: Vec<File>,
}

impl GenResult {
    pub fn write_to<P: AsRef<Path>>(&self, target_dir: P) -> std::io::Result<()> {
        let target_dir = target_dir.as_ref();
        for file in &self.files {
            if let Some(parent) = Path::new(&file.path).parent() {
                std::fs::create_dir_all(target_dir.join(parent))?;
            }
            use std::io::Write;
            std::fs::File::create(target_dir.join(&file.path))?
                .write_all(file.content.as_bytes())?;
        }
        Ok(())
    }
}

impl From<HashMap<String, String>> for GenResult {
    fn from(file_map: HashMap<String, String>) -> Self {
        Self {
            files: file_map
                .into_iter()
                .map(|(path, content)| File { path, content })
                .collect(),
        }
    }
}

impl From<Vec<File>> for GenResult {
    fn from(files: Vec<File>) -> Self {
        Self { files }
    }
}

pub trait Generator {
    type Options: Default;
    fn new(name: &str, version: &str, options: Self::Options) -> Self;
    fn add_only(&mut self, schema: &trans::Schema);
    fn result(self) -> GenResult;
}

pub struct GeneratorImpl<T: Generator> {
    inner: T,
    added: HashMap<trans::Name, trans::Schema>,
}

impl<T: Generator> GeneratorImpl<T> {
    pub fn new(name: &str, version: &str, options: T::Options) -> Self {
        Self {
            inner: T::new(name, version, options),
            added: HashMap::new(),
        }
    }
    pub fn add(&mut self, schema: &Schema) {
        let schema_name = schema.full_name();
        let current = self.added.get(&schema_name);
        if let Some(current) = current {
            assert_eq!(
                current, schema,
                "Two schemas with same name but different structure"
            );
            return;
        }
        self.added.insert(schema_name, schema.clone());
        match schema {
            Schema::Struct(Struct { fields, .. }) => {
                for field in fields {
                    self.add(&field.schema);
                }
            }
            Schema::OneOf { variants, .. } => {
                for variant in variants {
                    for field in &variant.fields {
                        self.add(&field.schema);
                    }
                }
            }
            Schema::Option(inner) => {
                self.add(inner);
            }
            Schema::Vec(inner) => {
                self.add(inner);
            }
            Schema::Map(key_type, value_type) => {
                self.add(key_type);
                self.add(value_type);
            }
            Schema::Bool
            | Schema::Int32
            | Schema::Int64
            | Schema::Float32
            | Schema::Float64
            | Schema::String
            | Schema::Enum { .. } => {}
        }
        self.inner.add_only(schema);
    }
    pub fn result(self) -> GenResult {
        self.inner.result()
    }
}

pub struct Writer {
    content: String,
    ident_level: usize,
}

impl Writer {
    pub fn new() -> Self {
        Self {
            content: String::new(),
            ident_level: 0,
        }
    }
    pub fn inc_ident(&mut self) {
        self.ident_level += 1;
    }
    pub fn dec_ident(&mut self) {
        self.ident_level -= 1;
    }
    pub fn get(self) -> String {
        assert_eq!(self.ident_level, 0, "Incorrect indentation");
        self.content
    }
}

impl std::fmt::Write for Writer {
    fn write_str(&mut self, s: &str) -> std::fmt::Result {
        for c in s.chars() {
            if c != '\n' && self.content.chars().last().unwrap_or('\n') == '\n' {
                for _ in 0..self.ident_level * 4 {
                    self.content.push(' ');
                }
            }
            self.content.push(c);
        }
        Ok(())
    }
}