evfmt/formatter.rs
1//! Core formatting engine.
2//!
3//! This module provides whole-text formatting.
4//!
5//! # Examples
6//!
7//! ```rust
8//! use evfmt::{FormatResult, Policy, format_text};
9//!
10//! let policy = Policy::default();
11//!
12//! assert_eq!(
13//! format_text("#\u{FE0E}", &policy),
14//! FormatResult::Changed("#".into())
15//! );
16//! assert_eq!(format_text("#", &policy), FormatResult::Unchanged);
17//! ```
18//
19// formatter.rs — The core formatting engine.
20//
21// Uses the sequence-aware scanner to process text, then asks findings analysis
22// for each policy-aware finding and applies the default replacement decision.
23//
24// AUDIT NOTE — Key properties maintained by this module:
25//
26// 1. IDEMPOTENCY: format(format(x)) == format(x). Verified by prop_idempotent.
27// 2. LOSSLESSNESS: only FE0E/FE0F are inserted/removed; all other content is
28// preserved. Verified by prop_only_modifies_selectors.
29// 3. NO VIOLATIONS: re-scanning the output produces zero violations under
30// the same policy. Verified by prop_no_violations_in_output.
31// 4. CURRENT IMPLEMENTATION SHAPE: format_text scans the input once, analyzes
32// each item, and applies the default replacement decision. This is this
33// module's chosen boundary, not a spec requirement; output canonicality is
34// verified by prop_no_violations_in_output.
35
36use crate::findings;
37use crate::policy::Policy;
38use crate::scanner;
39
40/// The result of formatting a text string.
41///
42/// # Examples
43///
44/// ```rust
45/// use evfmt::{FormatResult, Policy, format_text};
46///
47/// match format_text("#\u{FE0E}", &Policy::default()) {
48/// FormatResult::Changed(text) => assert_eq!(text, "#"),
49/// FormatResult::Unchanged => panic!("the selector should be removed"),
50/// }
51/// ```
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum FormatResult {
54 /// The input was already canonical; no changes needed.
55 Unchanged,
56 /// The input was modified; contains the new text.
57 Changed(String),
58}
59
60/// Format the input text according to the given policy.
61///
62/// # Examples
63///
64/// ```rust
65/// use evfmt::{FormatResult, Policy, format_text};
66///
67/// let policy = Policy::default();
68///
69/// assert_eq!(
70/// format_text("#\u{FE0E}", &policy),
71/// FormatResult::Changed("#".to_owned())
72/// );
73/// assert_eq!(
74/// format_text("plain text", &policy),
75/// FormatResult::Unchanged
76/// );
77/// ```
78#[must_use]
79pub fn format_text(input: &str, policy: &Policy) -> FormatResult {
80 let mut output = String::with_capacity(input.len());
81
82 for item in scanner::scan(input) {
83 if let Some(finding) = findings::analyze_scan_item(&item, policy) {
84 output.push_str(finding.default_replacement());
85 } else {
86 output.push_str(item.raw);
87 }
88 }
89
90 if output == input {
91 FormatResult::Unchanged
92 } else {
93 FormatResult::Changed(output)
94 }
95}
96
97#[cfg(test)]
98mod tests;