open_plc_schema 0.1.0

Types from openPLC XML schema
Documentation
use std::fmt::Debug;
fn main() -> Result<(), impl Debug> {
    #[cfg(feature = "generate")]
    {
        // this is needed because the generator is so complex that it will
        // overflow its stack
        let child = std::thread::Builder::new()
            .stack_size(32 * 1024 * 1024)
            .spawn(generate_code)?;
        child.join().expect("did not panic")?;
        Result::<_, xsd_parser::Error>::Ok(())
    }
    #[cfg(not(feature = "generate"))]
    Result::<_, std::convert::Infallible>::Ok(())
}

#[cfg(feature = "generate")]
fn generate_code() -> Result<(), xsd_parser::Error> {
    use xsd_parser::config::{Namespace, RendererFlags};
    use xsd_parser::{
        Config,
        config::{GeneratorFlags, InterpreterFlags, OptimizerFlags, RenderStep, Schema},
        generate,
    };
    let schema_path =
        std::env::var("OPEN_PLC_SCHEMA_PATH").expect("you must provide the path to the schema");
    let mut config = Config::default();
    config.parser.schemas = vec![Schema::File(schema_path.into())];
    config.interpreter.flags = InterpreterFlags::all();
    config.optimizer.flags = OptimizerFlags::all();
    config.generator.flags = GeneratorFlags::all();
    config.renderer.flags = RendererFlags::all();

    config.generator.any_type = Some("xsd_parser::xml::AnyElement".into());
    config.generator.any_attribute_type = Some("xsd_parser::xml::AnyAttributes".into());
    // Add renderers for `quick-xml` serializer and deserializer.
    let config = config.with_render_steps([
        RenderStep::Types,
        RenderStep::Defaults,
        RenderStep::NamespaceConstants,
        RenderStep::QuickXmlDeserialize {
            boxed_deserializer: true,
        },
        RenderStep::QuickXmlSerialize {
            with_namespaces: true,
            default_namespace: Some(Namespace::new_const(b"http://www.plcopen.org/xml/tc6_0201")),
        },
    ]);

    // Generate the code based on the configuration above.
    let code = generate(config)?;
    let code = code.to_string();

    // Use a small helper to pretty-print the code (it uses `RUSTFMT`).
    // Actually, this is easier to use, if one has to compare the result of
    // 2 versions of `my-schema.xsd`.
    let code = rustfmt_pretty_print(code).unwrap();

    // Generate my_schema.rs, containing all structures and implementations defined from
    // `my-schema.xsd` and the configuration above.
    let mut file = std::fs::File::create("src/schema.rs")?;
    use std::io::Write;
    file.write_all(code.to_string().as_bytes())?;

    Ok(())
}

// A small helper to call `rustfmt` when generating file(s).
// This may be useful to compare different versions of generated files.
#[cfg(feature = "generate")]
pub fn rustfmt_pretty_print(code: String) -> Result<String, std::io::Error> {
    use std::io::Write;
    use std::process::{Command, Output, Stdio};

    let mut child = Command::new("rustfmt")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;

    let mut stdin = child.stdin.take().unwrap();

    write!(stdin, "{code}")?;
    stdin.flush()?;
    drop(stdin);

    let Output {
        status,
        stdout,
        stderr,
    } = child.wait_with_output()?;

    let stdout = String::from_utf8_lossy(&stdout);
    let stderr = String::from_utf8_lossy(&stderr);

    if !status.success() {
        let code = status.code();
        match code {
            Some(code) => {
                if code != 0 {
                    panic!("The `rustfmt` command failed with return code {code}!\n{stderr}");
                }
            }
            None => {
                panic!("The `rustfmt` command failed!\n{stderr}")
            }
        }
    }

    Ok(stdout.into())
}