interoptopus_backend_csharp 0.15.0-alpha.18

Generates C# bindings.
use crate::Interop;
use crate::converter::{field_name, function_name};
use crate::interop::FunctionNameFlavor;
use crate::interop::functions::write_function;
use crate::interop::patterns::services::{MethodType, write_pattern_service_method, write_service_method_overload};
use crate::interop::types::composite::write_type_definition_composite_body;
use crate::interop::types::enums::write_type_definition_enum;
use interoptopus::inventory::non_service_functions;
use interoptopus::lang::{Composite, Function, Type};
use interoptopus::pattern::{LibraryPattern, TypePattern};
use interoptopus_backend_utils::{Error, IndentWriter, WriteFor, indented};
use std::fs::File;
use std::path::Path;

/// Configures C# documentation generation.
#[derive(Clone, Debug, Default)]
pub struct MarkdownConfig {
    /// Header to append to the generated documentation.
    pub header: String,
}

/// Produces C# API documentation.
pub struct Markdown<'a> {
    interop: &'a Interop,
    config: MarkdownConfig,
}

impl<'a> Markdown<'a> {
    #[must_use]
    pub const fn new(interop: &'a Interop, config: MarkdownConfig) -> Self {
        Self { interop, config }
    }

    fn write_toc(&self, w: &mut IndentWriter) -> Result<(), Error> {
        indented!(w, r"## API Overview")?;

        w.newline()?;
        indented!(w, r"### Functions")?;
        indented!(w, r"Freestanding callables inside the module.")?;

        for the_type in non_service_functions(&self.interop.inventory) {
            let doc = the_type.meta().docs().lines().first().cloned().unwrap_or_default();

            indented!(w, r" - **[{}](#{})** - {}", the_type.name(), the_type.name(), doc)?;
        }

        w.newline()?;
        indented!(w, r"### Classes")?;
        indented!(w, r"Methods operating on common state.")?;

        for pattern in self.interop.inventory.patterns().iter().map(|x| match x {
            LibraryPattern::Service(s) => s,
            _ => panic!("Pattern not explicitly handled"),
        }) {
            let prefix = pattern.common_prefix();
            let doc = pattern.the_type().meta().docs().lines().first().cloned().unwrap_or_default();
            let name = pattern.the_type().rust_name();

            indented!(w, r" - **[{}](#{})** - {}", name, name, doc)?;

            for x in pattern.constructors() {
                let func_name = function_name(x, FunctionNameFlavor::CSharpMethodWithoutClass(&prefix));
                let target = format!("{name}.{func_name}");
                let doc = x.meta().docs().lines().first().cloned().unwrap_or_default();
                indented!(w, r"     - **[{}](#{})** <sup>**ctor**</sup> - {}", func_name, target, doc)?;
            }
            for x in pattern.methods() {
                let func_name = function_name(x, FunctionNameFlavor::CSharpMethodWithoutClass(&prefix));
                let target = format!("{name}.{func_name}");
                let doc = x.meta().docs().lines().first().cloned().unwrap_or_default();
                indented!(w, r"     - **[{}](#{})** - {}", func_name, target, doc)?;
            }
        }

        w.newline()?;
        indented!(w, r"### Enums")?;
        indented!(w, r"Groups of related constants.")?;

        for the_type in self.interop.inventory.c_types().iter().filter_map(|x| match x {
            Type::Enum(e) => Some(e),
            _ => None,
        }) {
            let doc = the_type.meta().docs().lines().first().cloned().unwrap_or_default();
            indented!(w, r" - **[{}](#{})** - {}", the_type.rust_name(), the_type.rust_name(), doc)?;
        }

        w.newline()?;
        indented!(w, r"### Data Structs")?;
        indented!(w, r"Composite data used by functions and methods.")?;

        for the_type in self.interop.inventory.c_types() {
            match the_type {
                Type::Composite(c) => {
                    let doc = c.meta().docs().lines().first().cloned().unwrap_or_default();
                    indented!(w, r" - **[{}](#{})** - {}", c.rust_name(), c.rust_name(), doc)?;
                }
                Type::Pattern(p @ TypePattern::Option(_)) => {
                    let c = p.fallback_type().as_composite_type().cloned().unwrap();
                    indented!(w, r" - **[{}](#{})** - A boolean flag and optionally data.", c.rust_name(), c.rust_name())?;
                }
                Type::Pattern(p @ TypePattern::Slice(_)) => {
                    let c = p.fallback_type().as_composite_type().cloned().unwrap();
                    indented!(w, r" - **[{}](#{})** - A pointer and length of un-owned elements.", c.rust_name(), c.rust_name())?;
                }
                _ => {}
            }
        }

        w.newline()?;
        indented!(w, r"---")?;
        w.newline()?;

        Ok(())
    }

    fn write_types(&self, w: &mut IndentWriter) -> Result<(), Error> {
        indented!(w, r"# Types ")?;

        for the_type in self.interop.inventory.c_types() {
            match the_type {
                Type::Composite(e) => self.write_composite(w, e)?,
                Type::Pattern(p @ TypePattern::Option(_)) => self.write_composite(w, p.fallback_type().as_composite_type().unwrap())?,
                Type::Pattern(p @ TypePattern::Slice(_)) => self.write_composite(w, p.fallback_type().as_composite_type().unwrap())?,
                _ => continue,
            }

            w.newline()?;
            indented!(w, r"---")?;
            w.newline()?;
        }

        Ok(())
    }

    fn write_composite(&self, w: &mut IndentWriter, composite: &Composite) -> Result<(), Error> {
        let meta = composite.meta();

        w.newline()?;
        w.newline()?;

        indented!(w, r#" ### <a name="{}">**{}**</a>"#, composite.rust_name(), composite.rust_name())?;
        w.newline()?;

        for line in meta.docs().lines() {
            indented!(w, r"{}", line.trim())?;
        }

        w.newline()?;

        indented!(w, r"#### Fields ")?;
        for f in composite.fields() {
            let doc = f.docs().lines().join("\n");
            let name = field_name(f);
            indented!(w, r"- **{}** - {} ", name, doc)?;
        }

        indented!(w, r"#### Definition ")?;
        indented!(w, r"```csharp")?;
        write_type_definition_composite_body(self.interop, w, composite, WriteFor::Docs)?;
        indented!(w, r"```")?;

        Ok(())
    }

    fn write_enums(&self, w: &mut IndentWriter) -> Result<(), Error> {
        indented!(w, r"# Enums ")?;

        for the_type in self.interop.inventory.c_types() {
            let Type::Enum(the_enum) = the_type else { continue };
            let meta = the_enum.meta();

            w.newline()?;
            w.newline()?;

            indented!(w, r#" ### <a name="{}">**{}**</a>"#, the_type.name_within_lib(), the_type.name_within_lib())?;
            w.newline()?;

            for line in meta.docs().lines() {
                indented!(w, r"{}", line.trim())?;
            }
            w.newline()?;

            indented!(w, r"#### Variants ")?;
            for v in the_enum.variants() {
                let doc = v.docs().lines().join("\n");
                indented!(w, r"- **{}** - {} ", v.name(), doc)?;
            }

            indented!(w, r"#### Definition ")?;
            indented!(w, r"```csharp")?;
            write_type_definition_enum(self.interop, w, the_enum)?;
            indented!(w, r"```")?;
            w.newline()?;
            indented!(w, r"---")?;
            w.newline()?;
        }

        Ok(())
    }

    fn write_functions(&self, w: &mut IndentWriter) -> Result<(), Error> {
        indented!(w, r"# Functions")?;

        for the_type in non_service_functions(&self.interop.inventory) {
            self.write_function(w, the_type)?;
        }

        Ok(())
    }

    fn write_function(&self, w: &mut IndentWriter, function: &Function) -> Result<(), Error> {
        indented!(w, r#"### <a name="{}">**{}**</a>"#, function.name(), function.name())?;

        for line in function.meta().docs().lines() {
            if line.trim().starts_with('#') {
                write!(w.writer(), "##")?;
            }
            indented!(w, r"{}", line.trim())?;
        }

        indented!(w, r"#### Definition ")?;
        indented!(w, r"```csharp")?;
        write_function(self.interop, w, function, WriteFor::Docs)?;
        indented!(w, r"```")?;
        w.newline()?;
        indented!(w, r"---")?;
        w.newline()?;

        Ok(())
    }

    fn write_services(&self, w: &mut IndentWriter) -> Result<(), Error> {
        indented!(w, r"# Classes")?;

        for pattern in self.interop.inventory.patterns().iter().map(|x| match x {
            LibraryPattern::Service(s) => s,
            _ => panic!("Pattern not explicitly handled"),
        }) {
            let prefix = pattern.common_prefix();
            let doc = pattern.the_type().meta().docs().lines();
            let class_name = pattern.the_type().rust_name();

            indented!(w, r#"## <a name="{}">**{}**</a>"#, class_name, class_name)?;

            for line in doc {
                let line = line.replace(" # ", " #### ");
                let line = line.replace(" ## ", " ##### ");
                let line = line.replace(" ### ", " ###### ");

                indented!(w, r"{}", line)?;
            }

            for x in pattern.constructors() {
                let fname = function_name(x, FunctionNameFlavor::CSharpMethodWithoutClass(&prefix));
                let target = fname.to_string();
                indented!(w, r#"### <a name="{}">**{}**</a> <sup>ctor</sup>"#, target, target)?;

                let doc = x.meta().docs().lines();
                for line in doc {
                    let line = line.replace(" # ", " #### ");
                    let line = line.replace(" ## ", " ##### ");
                    let line = line.replace(" ### ", " ###### ");
                    indented!(w, r"{}", line)?;
                }
                w.newline()?;
                indented!(w, r"#### Definition ")?;
                indented!(w, r"```csharp")?;
                write_pattern_service_method(self.interop, w, pattern, x, MethodType::Ctor, WriteFor::Docs)?;
                indented!(w, r"```")?;
                w.newline()?;
                indented!(w, r"---")?;
                w.newline()?;
            }

            for x in pattern.methods() {
                let fname = function_name(x, FunctionNameFlavor::CSharpMethodWithoutClass(&prefix));
                let target = fname.to_string();

                indented!(w, r#"### <a name="{}">**{}**</a>"#, target, target)?;

                let doc = x.meta().docs().lines();
                for line in doc {
                    let line = line.replace(" # ", " #### ");
                    let line = line.replace(" ## ", " ##### ");
                    let line = line.replace(" ### ", " ###### ");
                    indented!(w, r"{}", line)?;
                }

                w.newline()?;
                indented!(w, r"#### Definition ")?;
                indented!(w, r"```csharp")?;
                indented!(w, r"{} class {} {{", self.interop.visibility_types.to_access_modifier(), class_name)?;
                w.indent();
                write_pattern_service_method(self.interop, w, pattern, x, MethodType::Regular, WriteFor::Docs)?;
                write_service_method_overload(self.interop, w, pattern, x, WriteFor::Docs)?;
                w.unindent();
                indented!(w, r"}}")?;
                indented!(w, r"```")?;
                w.newline()?;
                indented!(w, r"---")?;
                w.newline()?;
            }

            w.newline()?;
            w.newline()?;
        }

        Ok(())
    }

    /// Generates FFI binding code and writes them to the [`IndentWriter`].
    ///
    /// # Errors
    /// Can result in an error if I/O failed.
    pub fn write_to(&self, w: &mut IndentWriter) -> Result<(), Error> {
        writeln!(w.writer(), "{}", self.config.header)?;

        self.write_toc(w)?;
        self.write_types(w)?;
        self.write_enums(w)?;
        self.write_functions(w)?;
        self.write_services(w)?;

        w.newline()?;

        Ok(())
    }

    /// Convenience method to write FFI bindings to the specified file with default indentation.
    ///
    /// # Errors
    /// Can result in an error if I/O failed.
    pub fn write_file<P: AsRef<Path>>(&self, file_name: P) -> Result<(), Error> {
        let mut file = File::create(file_name)?;
        let mut writer = IndentWriter::new(&mut file);

        self.write_to(&mut writer)
    }

    /// Convenience method to write FFI bindings to a string.
    ///
    /// # Errors
    /// Can result in an error if I/O failed.
    pub fn to_string(&self) -> Result<String, Error> {
        let mut vec = Vec::new();
        let mut writer = IndentWriter::new(&mut vec);
        self.write_to(&mut writer)?;
        Ok(String::from_utf8(vec)?)
    }
}