gemrendr 0.3.1

Turns Gemtext into idiomatic HTML
Documentation
//! Turns Gemtext into idiomatic HTML.
//!
//! ```rust
//! let document = "# Hello, world!";
//! let output = gemrendr::html_from_gemtext(document, Default::default());
//! assert!(output.ends_with("<h1>Hello, world!</h1>"));
//! ```
//!
//! By default, the output includes a comment that points to gemrendr's
//! public source code repository. To omit this line, configure render
//! options like so:
//! ```rust
//! use gemrendr::{RenderOptions, html_from_gemtext};
//!
//! let options = RenderOptions {
//! 	preamble: false,
//! 	..Default::default()
//! };
//! let document = "# Hello, world!";
//! let output = html_from_gemtext(document, options);
//! assert_eq!(output, "<h1>Hello, world!</h1>");
//! ```
//!
//! See [`RenderOptions`] for more available options.
//!
//! To only parse the gemtext, without transforming it right away into an HTML string,
//! use the various `parse` methods on [`gemtext::Document`]:
//! ```rust
//! use gemrendr::gemtext::Document;
//! # use gemrendr::gemtext::{GemtextContentBlock, HeadingLevel};
//!
//! let parsed = Document::parse_from_gemtext("# Hello, world!");
//! # assert_eq!(parsed.contents.len(), 1);
//! # assert_eq!(*parsed.contents.first().unwrap(), GemtextContentBlock::Heading { level: HeadingLevel::One, content: String::from("Hello, world!") });
//! let also_parsed = "# Hello, world!".parse::<Document>().unwrap();
//! # assert_eq!(also_parsed, parsed);
//! ```
//! With this representation you can, for example, do additional processing to determine
//! the page title (e.g. by considering first headings). To convert the parsed document
//! data into HTML, use [`Document::into_html`]:
//! ```rust
//! use gemrendr::gemtext::Document;
//!
//! let parsed = Document::parse_from_gemtext("# Hello, world!");
//! /* do something with parsed */
//! let output = parsed.into_html(Default::default());
//! assert!(output.into_string().ends_with("<h1>Hello, world!</h1>"));
//! ```
//!
//! ## Crate Features

// Copyright (C) 2025  AverageHelper
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

#![doc = document_features::document_features!()]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![allow(clippy::tabs_in_doc_comments)]

extern crate alloc;
extern crate core;

mod constants;
pub mod gemtext;
mod markup;
mod parse;

use alloc::string::String;
use constants::{PKG_NAME, PKG_REPO};
use gemtext::Document;
pub use markup::{CopyButtonStyle, EmptyLineTag};
use maud::{Markup, PreEscaped, html};

/// Options to use when rendering Gemtext as HTML.
///
/// Because the addition of new future options is NOT considered a semver-major
/// breaking change, constructing this value directly may result in unexpected
/// compilation errors unless you use [`Default::default`] and the
/// "spread" syntax.
///
/// # Example
/// ```rust
/// # use gemrendr::{CopyButtonStyle, EmptyLineTag, RenderOptions};
/// let options = RenderOptions {
/// 	empty_line_tag: EmptyLineTag::P,
/// 	..Default::default()
/// };
///
/// assert!(matches!(options.copy_button_style, CopyButtonStyle::None));
/// ```
#[derive(Clone, Copy)]
pub struct RenderOptions {
	/// Whether and how a Copy Text button should be generated in preformatted blocks.
	pub copy_button_style: CopyButtonStyle,

	/// The tag that should be used to represent empty Text lines.
	pub empty_line_tag: EmptyLineTag,

	/// Whether to include the generated-by notice comment in output.
	pub preamble: bool,
}

impl Default for RenderOptions {
	fn default() -> Self {
		Self {
			copy_button_style: Default::default(),
			empty_line_tag: Default::default(),
			preamble: true,
		}
	}
}

impl Document {
	/// Transforms the given Gemtext document data into a representation of HTML markup.
	// TODO: Rename this to `into_html_fragment` maybe?
	pub fn into_html(self, options: RenderOptions) -> Markup {
		html! {
			@if options.preamble {
				(PreEscaped("<!-- Content generated using "))(PKG_NAME)(PreEscaped("; see <"))(PKG_REPO)(PreEscaped("> for details. -->"))
			}
			@for block in self.contents {
				(block.as_markup(options.into()))
			}
		}
	}
}

/// Transforms the given Gemtext document string into an HTML document string.
pub fn html_from_gemtext(document: &str, options: RenderOptions) -> String {
	Document::parse_from_gemtext(document)
		.into_html(options)
		.into_string()
}