Skip to main content

shuck_format/
lib.rs

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