Skip to main content

shuck_format/
lib.rs

1#![warn(missing_docs)]
2#![cfg_attr(not(test), warn(clippy::unwrap_used))]
3
4//! Generic document and pretty-printing primitives used by `shuck-formatter`.
5//!
6//! This crate is shell-agnostic. It provides the document tree, formatter traits, and printer
7//! implementation that higher-level crates use to build language-specific formatting rules.
8#[allow(missing_docs)]
9mod buffer;
10#[allow(missing_docs)]
11mod format_element;
12#[allow(missing_docs)]
13mod formatter;
14#[allow(missing_docs)]
15mod macros;
16/// Re-exports commonly used when implementing [`crate::Format`] values.
17#[allow(missing_docs)]
18pub mod prelude;
19#[allow(missing_docs)]
20mod printer;
21
22/// Output buffers used while constructing format documents.
23pub use crate::buffer::{Buffer, VecBuffer};
24/// Formatting document elements and document construction helpers.
25pub use crate::format_element::{
26    Document, FormatElement, LineMode, best_fit, group, hard_line_break, indent, soft_line_break,
27    soft_line_break_or_space, space, text, token, verbatim,
28};
29/// Core formatter traits, options, and formatted output wrappers.
30pub use crate::formatter::{
31    Format, FormatContext, FormatError, FormatOptions, FormatResult, Formatted, Formatter,
32    SimpleFormatContext, SimpleFormatOptions,
33};
34/// Printer configuration and rendered output types.
35pub use crate::printer::{IndentStyle, LineEnding, PrintError, Printed, Printer, PrinterOptions};
36
37#[cfg(test)]
38mod tests {
39    use crate::prelude::*;
40    use crate::{SimpleFormatContext, SimpleFormatOptions, format};
41
42    #[test]
43    fn prints_indented_hard_lines() {
44        let context = SimpleFormatContext::new(SimpleFormatOptions::default());
45        let doc = Document::from_elements(vec![
46            text("if"),
47            hard_line_break(),
48            indent(Document::from_elements(vec![
49                text("echo hi"),
50                hard_line_break(),
51                text("echo bye"),
52            ])),
53        ]);
54
55        let printed = format!(context, [doc]).unwrap().print().unwrap();
56        assert_eq!(printed.as_code(), "if\n\techo hi\n\techo bye");
57    }
58
59    #[test]
60    fn group_flattens_when_content_fits() {
61        let context = SimpleFormatContext::new(SimpleFormatOptions::default());
62        let doc = Document::from_element(group(Document::from_elements(vec![
63            text("echo"),
64            soft_line_break_or_space(),
65            text("hello"),
66        ])));
67
68        let printed = format!(context, [doc]).unwrap().print().unwrap();
69        assert_eq!(printed.as_code(), "echo hello");
70    }
71
72    #[test]
73    fn group_expands_when_content_overflows() {
74        let mut options = SimpleFormatOptions::default();
75        options.printer_options.line_width = 8;
76        let context = SimpleFormatContext::new(options);
77        let doc = Document::from_element(group(Document::from_elements(vec![
78            text("echo"),
79            soft_line_break_or_space(),
80            text("long-value"),
81        ])));
82
83        let printed = format!(context, [doc]).unwrap().print().unwrap();
84        assert_eq!(printed.as_code(), "echo\nlong-value");
85    }
86
87    #[test]
88    fn best_fit_chooses_expanded_variant() {
89        let mut options = SimpleFormatOptions::default();
90        options.printer_options.line_width = 6;
91        let context = SimpleFormatContext::new(options);
92        let doc = Document::from_element(best_fit(
93            Document::from_elements(vec![text("alpha"), space(), text("beta")]),
94            Document::from_elements(vec![text("alpha"), hard_line_break(), text("beta")]),
95        ));
96
97        let printed = format!(context, [doc]).unwrap().print().unwrap();
98        assert_eq!(printed.as_code(), "alpha\nbeta");
99    }
100
101    #[test]
102    fn group_expands_for_wide_unicode_text() {
103        let mut options = SimpleFormatOptions::default();
104        options.printer_options.line_width = 3;
105        let context = SimpleFormatContext::new(options);
106        let doc = Document::from_element(group(Document::from_elements(vec![
107            text("a"),
108            soft_line_break_or_space(),
109            text("界"),
110        ])));
111
112        let printed = format!(context, [doc]).unwrap().print().unwrap();
113        assert_eq!(printed.as_code(), "a\n界");
114    }
115
116    #[test]
117    fn hard_lines_use_configured_crlf_endings() {
118        let mut options = SimpleFormatOptions::default();
119        options.printer_options.line_ending = LineEnding::CrLf;
120        let context = SimpleFormatContext::new(options);
121        let doc = Document::from_elements(vec![token("a"), hard_line_break(), token("b")]);
122
123        let printed = format!(context, [doc]).unwrap().print().unwrap();
124        assert_eq!(printed.as_code(), "a\r\nb");
125    }
126
127    #[test]
128    fn verbatim_preserves_source_text() {
129        let context = SimpleFormatContext::new(SimpleFormatOptions::default());
130        let doc = Document::from_elements(vec![
131            text("begin"),
132            hard_line_break(),
133            verbatim("  raw\ntext"),
134        ]);
135
136        let printed = format!(context, [doc]).unwrap().print().unwrap();
137        assert_eq!(printed.as_code(), "begin\n  raw\ntext");
138    }
139
140    #[test]
141    fn nested_groups_and_indents_expand_consistently() {
142        let mut options = SimpleFormatOptions::default();
143        options.printer_options.line_width = 8;
144        let context = SimpleFormatContext::new(options);
145        let doc = Document::from_element(group(Document::from_elements(vec![
146            token("if"),
147            soft_line_break_or_space(),
148            token("true"),
149            token(";"),
150            soft_line_break(),
151            indent(Document::from_elements(vec![
152                token("echo"),
153                soft_line_break_or_space(),
154                token("long-value"),
155            ])),
156        ])));
157
158        let printed = format!(context, [doc]).unwrap().print().unwrap();
159        assert_eq!(printed.as_code(), "if\ntrue;\n\techo\n\tlong-value");
160    }
161
162    #[test]
163    fn indents_with_spaces_when_configured() {
164        let mut options = SimpleFormatOptions::default();
165        options.printer_options.indent_style = IndentStyle::Space;
166        options.printer_options.indent_width = 2;
167        let context = SimpleFormatContext::new(options);
168        let doc = Document::from_elements(vec![
169            token("if"),
170            hard_line_break(),
171            indent(Document::from_elements(vec![
172                token("echo hi"),
173                hard_line_break(),
174                token("echo bye"),
175            ])),
176        ]);
177
178        let printed = format!(context, [doc]).unwrap().print().unwrap();
179        assert_eq!(printed.as_code(), "if\n  echo hi\n  echo bye");
180    }
181}