neutron-engine 0.1.3

A lightweight markup parser for the Neutron styling language (.nt)
Documentation
//! # Neutron Engine
//!
//! A lightweight markup parser for the Neutron styling language (.nt).
//! Provides fast, zero-dependency parsing of XML-like tags with inline style declarations.
//!
//! ## Example
//!
//! ```rust
//! use neutron_engine::{parse_tag, parse_attributes, parse_style, normalize_decl, Attribute, StyleDecl};
//!
//! let tag = r#"<Container style="b:#000; w:fill" id="Main">"#;
//! if let Some((name, attrs_text)) = parse_tag(tag) {
//!     println!("Tag: {}", name);
//!
//!     let mut attrs = [Attribute { name: "", value: "" }; 12];
//!     let attr_count = parse_attributes(attrs_text, &mut attrs);
//!
//!     for attr in &attrs[..attr_count] {
//!         if attr.name == "style" {
//!             let mut styles = [StyleDecl { name: "", value: "" }; 16];
//!             let style_count = parse_style(attr.value, &mut styles);
//!
//!             for style in &styles[..style_count] {
//!                 let prop = normalize_decl(*style);
//!                 println!("Style: {:?}", prop);
//!             }
//!         }
//!     }
//! }
//! ```

#[path = "iris/mod.rs"]
pub mod iris;















use std::fs;

/// Represents a style declaration with name and value
#[derive(Debug, Clone, Copy)]
pub struct StyleDecl<'a> {
    pub name: &'a str,
    pub value: &'a str,
}

/// Represents an attribute with name and value
#[derive(Debug, Clone, Copy)]
pub struct Attribute<'a> {
    pub name: &'a str,
    pub value: &'a str,
}

/// Normalized style property enum
#[derive(Debug)]
pub enum StyleProp<'a> {
    Background(&'a str),
    Color(&'a str),
    Width(&'a str),
    Height(&'a str),
    FontSize(&'a str),
    Raw(&'a str, &'a str),
}

/// Parse a tag string into tag name and attributes text
///
/// # Examples
///
/// ```
/// use neutron_engine::parse_tag;
///
/// let result = parse_tag(r#"<Container style="b:#000">"#);
/// assert_eq!(result, Some(("Container", r#"style="b:#000""#)));
/// ```
pub fn parse_tag<'a>(input: &'a str) -> Option<(&'a str, &'a str)> {
    let input = input.trim();
    if !input.starts_with('<') || !input.ends_with('>') {
        return None;
    }
    let inner = &input[1..input.len() - 1];
    let mut parts = inner.splitn(2, char::is_whitespace);
    let tag_name = parts.next()?.trim();
    let attrs = parts.next().unwrap_or("").trim();
    Some((tag_name, attrs))
}

/// Parse attributes from a string into an array of Attribute structs
///
/// # Examples
///
/// ```
/// use neutron_engine::{parse_attributes, Attribute};
///
/// let mut attrs = [Attribute { name: "", value: "" }; 12];
/// let count = parse_attributes(r#"style="b:#000" id="Main""#, &mut attrs);
/// assert_eq!(count, 2);
/// assert_eq!(attrs[0].name, "style");
/// assert_eq!(attrs[0].value, "b:#000");
/// ```
pub fn parse_attributes<'a>(input: &'a str, out: &mut [Attribute<'a>]) -> usize {
    let mut count = 0;
    let mut i = 0;
    let bytes = input.as_bytes();

    while i < bytes.len() && count < out.len() {
        while i < bytes.len() && bytes[i].is_ascii_whitespace() {
            i += 1;
        }
        if i >= bytes.len() {
            break;
        }

        let start = i;
        while i < bytes.len() && bytes[i] != b'=' && !bytes[i].is_ascii_whitespace() {
            i += 1;
        }
        let name = &input[start..i].trim();

        while i < bytes.len() && bytes[i].is_ascii_whitespace() {
            i += 1;
        }
        if i >= bytes.len() || bytes[i] != b'=' {
            break;
        }
        i += 1;

        while i < bytes.len() && bytes[i].is_ascii_whitespace() {
            i += 1;
        }
        if i >= bytes.len() || bytes[i] != b'"' {
            break;
        }
        i += 1;
        let value_start = i;

        while i < bytes.len() && bytes[i] != b'"' {
            i += 1;
        }
        if i > bytes.len() {
            break;
        }
        let value = &input[value_start..i];
        i += 1;

        out[count] = Attribute { name, value };
        count += 1;
    }

    count
}

/// Parse style declarations from a string
///
/// # Examples
///
/// ```
/// use neutron_engine::{parse_style, StyleDecl};
///
/// let mut styles = [StyleDecl { name: "", value: "" }; 16];
/// let count = parse_style("b:#000; c:#fff", &mut styles);
/// assert_eq!(count, 2);
/// assert_eq!(styles[0].name, "b");
/// assert_eq!(styles[0].value, "#000");
/// ```
pub fn parse_style<'a>(input: &'a str, out: &mut [StyleDecl<'a>]) -> usize {
    let mut count = 0;
    let mut rest = input;

    while !rest.is_empty() && count < out.len() {
        let semicolon = rest.find(';').unwrap_or(rest.len());
        let decl = rest[..semicolon].trim();

        if !decl.is_empty() {
            if let Some((name, value)) = decl.split_once(':') {
                let name = name.trim();
                let value = value.trim();
                if !name.is_empty() && !value.is_empty() {
                    out[count] = StyleDecl { name, value };
                    count += 1;
                }
            }
        }

        rest = if semicolon < rest.len() {
            &rest[semicolon + 1..]
        } else {
            ""
        };
    }

    count
}

/// Normalize a style declaration into a typed StyleProp
///
/// # Examples
///
/// ```
/// use neutron_engine::{normalize_decl, StyleDecl, StyleProp};
///
/// let decl = StyleDecl { name: "b", value: "#000" };
/// let prop = normalize_decl(decl);
///
/// match prop {
///     StyleProp::Background(color) => assert_eq!(color, "#000"),
///     _ => panic!("Expected Background"),
/// }
/// ```
pub fn normalize_decl<'a>(decl: StyleDecl<'a>) -> StyleProp<'a> {
    match decl.name {
        "b" => StyleProp::Background(decl.value),
        "c" => StyleProp::Color(decl.value),
        "w" => StyleProp::Width(decl.value),
        "h" => StyleProp::Height(decl.value),
        "f-sz" => StyleProp::FontSize(decl.value),
        _ => StyleProp::Raw(decl.name, decl.value),
    }
}

/// Parse a complete Neutron document and print the parsed structure
pub fn parse_neutron(input: &str) {
    let mut attrs = [Attribute { name: "", value: "" }; 12];
    let mut styles = [StyleDecl { name: "", value: "" }; 16];

    let mut i = 0;
    while let Some(start) = input[i..].find('<') {
        let start = i + start;
        if let Some(end) = input[start..].find('>') {
            let end = start + end + 1;
            let tag_content = &input[start..end];

            if let Some((tag_name, attrs_text)) = parse_tag(tag_content) {
                println!("[Tag] {}", tag_name);

                let attr_count = parse_attributes(attrs_text, &mut attrs);
                for attr in &attrs[..attr_count] {
                    println!("  [Attr] {} = \"{}\"", attr.name, attr.value);

                    if attr.name == "style" {
                        let style_count = parse_style(attr.value, &mut styles);
                        for style in &styles[..style_count] {
                            let prop = normalize_decl(*style);
                            println!("    [Style] {:?}", prop);
                        }
                    }
                }
            }

            i = end;
        } else {
            break;
        }
    }
}

/// Read a Neutron file and parse it
pub fn parse_neutron_file(path: &str) {
    let neutron_code = match fs::read_to_string(path) {
        Ok(content) => content,
        Err(_) => r#"
        <Neutron>
            <Body>
                <Container style="b:#000; w:fill; h:fill" id="Main">
                    <Text style="c:#fff; f-sz:20pt">Iris Neo di Laptop</Text>
                    <Text style="c:#0f0; f-sz:20pt">Neutron Demo</Text>
                </Container>
            </Body>
        </Neutron>
    "#
        .to_string(),
    };

    println!("--- Neutron Engine Booting ---");
    parse_neutron(&neutron_code);
}