craby_codegen 0.1.0-rc.2

Craby code generator
Documentation
use std::collections::{BTreeMap, BTreeSet};

use crate::{
    common::IntoCode,
    parser::types::{EnumTypeAnnotation, ObjectTypeAnnotation, TypeAnnotation},
    types::Schema,
};

pub fn indent_str(str: &str, indent_size: usize) -> String {
    let indent_str = " ".repeat(indent_size);
    str.lines()
        .map(|line| {
            if line.trim().is_empty() {
                line.to_string()
            } else {
                format!("{indent_str}{line}")
            }
        })
        .collect::<Vec<_>>()
        .join("\n")
}

pub fn calc_deps_order(schema: &Schema) -> Result<Vec<String>, anyhow::Error> {
    let mut dependencies = BTreeMap::new();
    let mut visited = BTreeSet::new();
    let mut in_progress = BTreeSet::new();
    let mut result = vec![];

    for type_annotation in &schema.aliases {
        let alias_spec = type_annotation.as_object().unwrap();

        dependencies.insert(alias_spec.name.clone(), vec![]);

        for prop in &alias_spec.props {
            match &prop.type_annotation {
                TypeAnnotation::Object(ObjectTypeAnnotation {
                    name: alias_name, ..
                }) => {
                    dependencies
                        .get_mut(&alias_spec.name)
                        .unwrap()
                        .push(alias_name.clone());
                }
                TypeAnnotation::Enum(EnumTypeAnnotation {
                    name: enum_name, ..
                }) => {
                    dependencies
                        .get_mut(&alias_spec.name)
                        .unwrap()
                        .push(enum_name.clone());
                }
                nullable @ TypeAnnotation::Nullable(type_annotation) => {
                    let rs_type = nullable.as_rs_bridge_type()?.into_code();
                    dependencies.entry(rs_type.clone()).or_insert(vec![]);

                    match &**type_annotation {
                        TypeAnnotation::Object(ObjectTypeAnnotation {
                            name: alias_name, ..
                        }) => {
                            dependencies
                                .get_mut(&rs_type)
                                .unwrap()
                                .push(alias_name.clone());
                        }
                        TypeAnnotation::Enum(EnumTypeAnnotation {
                            name: enum_name, ..
                        }) => {
                            dependencies
                                .get_mut(&rs_type)
                                .unwrap()
                                .push(enum_name.clone());
                        }
                        _ => (),
                    }
                }
                _ => (),
            }
        }
    }

    fn visit(
        node: &str,
        dependencies: &BTreeMap<String, Vec<String>>,
        visited: &mut BTreeSet<String>,
        in_progress: &mut BTreeSet<String>,
        result: &mut Vec<String>,
    ) -> Result<(), anyhow::Error> {
        if in_progress.contains(node) {
            return Err(anyhow::anyhow!(
                "Circular dependency detected involving: {}",
                node
            ));
        }

        if visited.contains(node) {
            return Ok(());
        }

        in_progress.insert(node.to_string());

        if let Some(deps) = dependencies.get(node) {
            for dep in deps {
                visit(dep, dependencies, visited, in_progress, result)?;
            }
        }

        in_progress.remove(node);
        visited.insert(node.to_string());
        result.push(node.to_string());

        Ok(())
    }

    for node in dependencies.keys() {
        if !visited.contains(node) {
            visit(
                node,
                &dependencies,
                &mut visited,
                &mut in_progress,
                &mut result,
            )?;
        }
    }

    Ok(result)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_indent_str() {
        assert_eq!(indent_str("Hello\nWorld", 2), "  Hello\n  World");
        assert_eq!(indent_str("Hello\nWorld", 4), "    Hello\n    World");
    }
}