alethea 0.1.0

A Rust-native templating approach using normal Rust syntax.
Documentation
//! # alethea
//!
//! ## Overview
//!
//! `alethea` is a Rust templating approach designed to keep templates simple,
//! expressive, and fully aligned with the Rust language itself.
//!
//! Instead of introducing a new syntax, it allows you to write templates using
//! native Rust constructs, reducing cognitive overhead and increasing flexibility.
//!
//! ### Goals
//!
//! - Eliminate `std::string`-style complexity:
//!     - Avoid manual string building (`push_str`, etc.) in most cases.
//!     - Keep templates focused on structure rather than concatenation.
//!
//! - Use Rust directly inside templates:
//!     - Write `if`, `for`, `match`, and other Rust constructs naturally.
//!     - Generate dynamic output without learning a separate templating language.
//!
//! - Reduce the learning curve:
//!     - No new syntax to learn—just Rust.
//!     - Avoid limitations commonly found in traditional template engines.
//!     - Reuse existing powerful Rust features like `format!` and standard library utilities,
//!       including padding and alignment
//!     - Behavior should be predictable if you already know Rust.
//!
//! - Ensure compile-time template safety:
//!     - Catch errors early during compilation.
//!     - Enable more reliable and maintainable templates.
//!
//! - Treat input data as read-only:
//!     - Templates should not modify their inputs
//!     - Any attempt to mutate inputs will fail at compile time.
//!
//! - Keep composition simple:
//!     - Provide clear and straightforward template inheritance.
//!     - Support nested templates for better structure and reuse.
//!
//!
//! ## Architecture and Philosophy
//!
//! - Define a template using the following syntax:
//! ```ignore
//! new_template! {
//!     template_name(var1, var2, ...) {
//!         /*
//!             Write regular Rust code inside the template.
//!
//!             Parameters (`var1`, `var2`, ...) must be valid Rust identifiers (`Ident`).
//!
//!             To access variables passed in `template_name(...)`,
//!             use `$var_name`.
//!
//!             Variables are read-only: any attempt to mutate them
//!             will result in a compile-time error.
//!
//!             Use `append_to_template!` to build the output.
//!             The appended value must be coercible to `&str`
//!             (e.g. `&str`, `String`).
//!         */
//!     }
//! }
//! ```
//!
//! - Use the template by passing parameters to produce a `String`:
//! ```ignore
//! let result = template_name!(var1: value1, var2: value2, ...);
//! ```
//!
//!   where:
//!   - `var1`, `var2`, ... are valid identifiers (`Ident`)
//!   - `value1`, `value2`, ... are Rust expressions (`Expr`)
//!
//! ## Quick Start
//!
//! ### Example 01
//!
//! ```rust
//!# use alethea_macros::{append_to_template, new_template};
//!#
//! new_template! {
//!     template(name, is_doctor) {
//!         append_to_template!("Hello ");
//!
//!         if $is_doctor {
//!             append_to_template!("Dr. ");
//!         }
//!
//!         append_to_template!($name);
//!     }
//! }
//!
//! assert_eq!(template!(name:"Alethea", is_doctor:true), "Hello Dr. Alethea");
//! assert_eq!(template!(name:"Alethea", is_doctor:false), "Hello Alethea");
//!
//! let s = String::from("Alethea");
//! assert_eq!(template!(name:&s, is_doctor:true), "Hello Dr. Alethea");
//! ```
//!
//! ### Example 02
//!
//! ```rust
//!# pub use alethea::{append_to_template, new_template};
//!#
//! new_template! {
//!     animals_html(animals) {
//!         append_to_template!{
//! r#"
//! <!DOCTYPE html>
//! <html lang="en">
//!     <head>
//!         <meta charset="UTF-8">
//!         <meta name="viewport" content="width=device-width, initial-scale=1.0">
//!         <title>Pet Cards</title>
//!         <script src="https://cdn.tailwindcss.com"></script>
//!     </head>
//!     <body class="bg-gray-100 p-10">
//!         <h1 class="text-3xl font-bold text-center mb-10">Our Pets</h1>
//!         <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
//! "#
//!         }
//!
//!         for animal in $animals.iter() {
//!             append_to_template!{
//!                 format!(
//! r#"
//!             <!-- Card -->
//!             <div class="bg-white rounded-xl shadow-md overflow-hidden hover:shadow-xl transition">
//!                 <img class="w-full h-40 object-cover" src="{}" alt="Dog">
//!                 <div class="p-4">
//!                     <h2 class="text-lg font-semibold">{}</h2>
//!                     <p class="text-gray-500 text-sm mb-2">{}</p>
//!                     <p class="text-gray-600 text-sm mb-4">{}</p>
//!                     <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600">
//!                     Adopt
//!                     </button>
//!                 </div>
//!             </div>
//! "#
//!                 , animal.image_url, animal.species, animal.description, animal.name)
//!             }
//!         }
//!
//!         append_to_template!{
//! r#"
//!         </div>
//!     </body>
//! </html>
//! "#        
//!         }
//!
//!     }
//! }
//!
//!/*
//!    Usage example :
//!    ...
//!
//!    let animal_html = animals_html!(animals: animals);
//!
//!    // Write the string directly to the file, panic on error
//!    fs::write(
//!        "examples/htmlgen_basic/output_html/animals.html",
//!        &animal_html,
//!    )
//!    .unwrap();
//!   ...
//!    
//!*/
//! ```
//!
//! ## Advanced Examples
//!
//! For more complete examples with usecases like inheritence
//! , check the repository.
//!
//! The `examples/` directory contains small projects using
//! frameworks like Axum, Actix, and Rocket, along with
//! standalone HTML and file generation examples.
//!
//! To run an example:
//! ```bash
//! cargo run --manifest-path examples/<example>/Cargo.toml
//! ```
//!
//! ## Template inheritance
//! Check `examples/htmlgen_template_inheritance_indent` for usage examples.
//!
//! TODO: document inheritance behavior
//!
//! ## Nested templates
//! See `examples/htmlgen_inner_templates` for usage examples.
//!
//! TODO: document nested templates
//!
//! ## Template readability guidelines
//!
//! - Keep appended or formatted strings aligned to the left (no indentation).
//!   This helps the reader see the output structure directly, almost as if
//!   they were reading the final rendered document, with control flow
//!   (`if`, `for`, etc.) appearing around it.
//!
//! - Prefer using "\ and type content in next line for paragraphs when possible,
//!   as it provides a more natural and readable flow for multi-line content.
//!
//! > Note: these are guidelines only. There are no strict limitations imposed
//! > by the library—you are free to structure templates as you see fit.
//!
//! ## HTML templates readability guidelines
//!
//! When working with HTML, there is more flexibility since the generated
//! output is interpreted as XML-like content and does not require strict
//! indentation or line breaks for correct rendering.
//!
//! To keep templates readable:
//! - Prefer using "\ when possible.
//! - Otherwise, use raw strings (`r#"..."#`) for multiline content.
//!   In that case, content typically begins on the line after `r#"`,
//!   which introduces a newline in the template, but this does not
//!   affect HTML rendering and can improve readability.
//!
//! Another important point is indentation in inheritance scenarios.
//! When composing templates (parent/child), indentation can be applied
//! in the parent when appending the child template (see
//! `examples/htmlgen_template_inheritance_indent`).
//!
//! However, this indentation may introduce a small runtime cost.
//! For HTML use cases, it is recommended to:
//! - Enable indentation in debug mode for readable output.
//! - Disable it in release mode for better performance,
//!   especially in web contexts where performance matters.
//!
//!
//! > Note: these are guidelines only. There are no strict limitations imposed
//! > by the library—you are free to structure templates as you see fit.
//!

pub use alethea_macros::{append_to_template, new_template};

/// Indents each line of the given string with the provided prefix.
///
/// This function prepends `prefix` to every line in `s`, including empty lines.
/// Lines are split on `\n`, and the function preserves trailing newlines by
/// treating them as additional empty lines.
///
/// # Examples
///
/// ```
/// # use alethea::indent;
/// assert_eq!(indent("prefix", ""), "prefix");
/// ```
///
/// ```
/// # use alethea::indent;
/// assert_eq!(indent("prefix", "\n"), "prefix\nprefix");
/// ```
///
/// ```
/// # use alethea::indent;
/// assert_eq!(indent("  ", "line_1\nline_2\n"), "  line_1\n  line_2\n  ");
/// ```
///
/// ```
/// # use alethea::indent;
/// assert_eq!(indent("  ", "line_1\nline_2"), "  line_1\n  line_2");
/// ```
///
/// ```
/// # use alethea::indent;
/// assert_eq!(indent("  ", "\nline_1\nline_2\n\n"), "  \n  line_1\n  line_2\n  \n  ");
/// ```
///
/// ```
/// # use alethea::indent;
/// assert_eq!(indent("  ", "\n\n\n"), "  \n  \n  \n  ");
/// ```
pub fn indent(prefix: &str, s: &str) -> String {
    let mut result = String::with_capacity(
        s.len() + prefix.len() * (1 + s.as_bytes().iter().filter(|&&b| b == b'\n').count()),
    );

    if s.is_empty() {
        result.push_str(prefix);
        return result;
    }

    for (idx, line) in s.split_terminator('\n').enumerate() {
        if idx > 0 {
            result.push('\n');
        }
        result.push_str(prefix);
        result.push_str(line);
    }

    if s.ends_with('\n') {
        result.push('\n');
        result.push_str(prefix);
    }
    result
}

#[cfg(test)]
mod tests {

    use super::indent;

    #[test]
    fn test_indent_scenario_01() {
        assert_eq!(indent("prefix", ""), "prefix");
    }

    #[test]
    fn test_indent_scenario_02() {
        assert_eq!(indent("prefix", "\n"), "prefix\nprefix");
    }

    #[test]
    fn test_indent_scenario_03() {
        assert_eq!(indent("  ", "line_1\nline_2\n"), "  line_1\n  line_2\n  ");
    }

    #[test]
    fn test_indent_scenario_04() {
        assert_eq!(indent("  ", "line_1\nline_2"), "  line_1\n  line_2");
    }

    #[test]
    fn test_indent_scenario_05() {
        assert_eq!(
            indent("  ", "\nline_1\nline_2\n\n"),
            "  \n  line_1\n  line_2\n  \n  "
        );
    }

    #[test]
    fn test_indent_scenario_06() {
        assert_eq!(indent("  ", "\n\n\n"), "  \n  \n  \n  ");
    }
}