wdl_format/
lib.rs

1//! Formatting facilities for WDL.
2
3pub mod config;
4pub mod element;
5mod token;
6pub mod v1;
7
8use std::fmt::Write;
9
10pub use config::Config;
11pub use token::*;
12use wdl_ast::Element;
13use wdl_ast::Node as AstNode;
14
15use crate::element::FormatElement;
16
17/// Newline constant used for formatting on windows platforms.
18#[cfg(windows)]
19pub const NEWLINE: &str = "\r\n";
20/// Newline constant used for formatting on non-windows platforms.
21#[cfg(not(windows))]
22pub const NEWLINE: &str = "\n";
23
24/// A space.
25pub const SPACE: &str = " ";
26
27/// A tab.
28pub const TAB: &str = "\t";
29
30/// Returns exactly one entity from an enumerable list of entities (usually a
31/// [`Vec`]).
32#[macro_export]
33macro_rules! exactly_one {
34    ($entities:expr_2021, $name:expr_2021) => {
35        match $entities.len() {
36            0 => unreachable!("we should never have zero {}", $name),
37            // SAFETY: we just checked to ensure that exactly
38            // one element exists, so this will always unwrap.
39            1 => $entities.pop().unwrap(),
40            _ => unreachable!("we should not have two or more {}", $name),
41        }
42    };
43}
44
45/// An element that can be written to a token stream.
46pub trait Writable {
47    /// Writes the element to the token stream.
48    fn write(&self, stream: &mut TokenStream<PreToken>);
49}
50
51impl Writable for &FormatElement {
52    fn write(&self, stream: &mut TokenStream<PreToken>) {
53        match self.element() {
54            Element::Node(node) => match node {
55                AstNode::AccessExpr(_) => v1::expr::format_access_expr(self, stream),
56                AstNode::AdditionExpr(_) => v1::expr::format_addition_expr(self, stream),
57                AstNode::ArrayType(_) => v1::decl::format_array_type(self, stream),
58                AstNode::Ast(_) => v1::format_ast(self, stream),
59                AstNode::BoundDecl(_) => v1::decl::format_bound_decl(self, stream),
60                AstNode::CallAfter(_) => v1::workflow::call::format_call_after(self, stream),
61                AstNode::CallAlias(_) => v1::workflow::call::format_call_alias(self, stream),
62                AstNode::CallExpr(_) => v1::expr::format_call_expr(self, stream),
63                AstNode::CallInputItem(_) => {
64                    v1::workflow::call::format_call_input_item(self, stream)
65                }
66                AstNode::CallStatement(_) => {
67                    v1::workflow::call::format_call_statement(self, stream)
68                }
69                AstNode::CallTarget(_) => v1::workflow::call::format_call_target(self, stream),
70                AstNode::CommandSection(_) => v1::task::format_command_section(self, stream),
71                AstNode::ConditionalStatement(_) => {
72                    v1::workflow::format_conditional_statement(self, stream)
73                }
74                AstNode::DefaultOption(_) => v1::expr::format_default_option(self, stream),
75                AstNode::DivisionExpr(_) => v1::expr::format_division_expr(self, stream),
76                AstNode::EqualityExpr(_) => v1::expr::format_equality_expr(self, stream),
77                AstNode::ExponentiationExpr(_) => {
78                    v1::expr::format_exponentiation_expr(self, stream)
79                }
80                AstNode::GreaterEqualExpr(_) => v1::expr::format_greater_equal_expr(self, stream),
81                AstNode::GreaterExpr(_) => v1::expr::format_greater_expr(self, stream),
82                AstNode::IfExpr(_) => v1::expr::format_if_expr(self, stream),
83                AstNode::ImportAlias(_) => v1::import::format_import_alias(self, stream),
84                AstNode::ImportStatement(_) => v1::import::format_import_statement(self, stream),
85                AstNode::IndexExpr(_) => v1::expr::format_index_expr(self, stream),
86                AstNode::InequalityExpr(_) => v1::expr::format_inequality_expr(self, stream),
87                AstNode::InputSection(_) => v1::format_input_section(self, stream),
88                AstNode::LessEqualExpr(_) => v1::expr::format_less_equal_expr(self, stream),
89                AstNode::LessExpr(_) => v1::expr::format_less_expr(self, stream),
90                AstNode::LiteralArray(_) => v1::expr::format_literal_array(self, stream),
91                AstNode::LiteralBoolean(_) => v1::expr::format_literal_boolean(self, stream),
92                AstNode::LiteralFloat(_) => v1::expr::format_literal_float(self, stream),
93                AstNode::LiteralHints(_) => v1::format_literal_hints(self, stream),
94                AstNode::LiteralHintsItem(_) => v1::format_literal_hints_item(self, stream),
95                AstNode::LiteralInput(_) => v1::format_literal_input(self, stream),
96                AstNode::LiteralInputItem(_) => v1::format_literal_input_item(self, stream),
97                AstNode::LiteralInteger(_) => v1::expr::format_literal_integer(self, stream),
98                AstNode::LiteralMap(_) => v1::expr::format_literal_map(self, stream),
99                AstNode::LiteralMapItem(_) => v1::expr::format_literal_map_item(self, stream),
100                AstNode::LiteralNone(_) => v1::expr::format_literal_none(self, stream),
101                AstNode::LiteralNull(_) => v1::meta::format_literal_null(self, stream),
102                AstNode::LiteralObject(_) => v1::expr::format_literal_object(self, stream),
103                AstNode::LiteralObjectItem(_) => v1::expr::format_literal_object_item(self, stream),
104                AstNode::LiteralOutput(_) => v1::format_literal_output(self, stream),
105                AstNode::LiteralOutputItem(_) => v1::format_literal_output_item(self, stream),
106                AstNode::LiteralPair(_) => v1::expr::format_literal_pair(self, stream),
107                AstNode::LiteralString(_) => v1::expr::format_literal_string(self, stream),
108                AstNode::LiteralStruct(_) => v1::r#struct::format_literal_struct(self, stream),
109                AstNode::LiteralStructItem(_) => {
110                    v1::r#struct::format_literal_struct_item(self, stream)
111                }
112                AstNode::LogicalAndExpr(_) => v1::expr::format_logical_and_expr(self, stream),
113                AstNode::LogicalNotExpr(_) => v1::expr::format_logical_not_expr(self, stream),
114                AstNode::LogicalOrExpr(_) => v1::expr::format_logical_or_expr(self, stream),
115                AstNode::MapType(_) => v1::decl::format_map_type(self, stream),
116                AstNode::MetadataArray(_) => v1::meta::format_metadata_array(self, stream),
117                AstNode::MetadataObject(_) => v1::meta::format_metadata_object(self, stream),
118                AstNode::MetadataObjectItem(_) => {
119                    v1::meta::format_metadata_object_item(self, stream)
120                }
121                AstNode::MetadataSection(_) => v1::meta::format_metadata_section(self, stream),
122                AstNode::ModuloExpr(_) => v1::expr::format_modulo_expr(self, stream),
123                AstNode::MultiplicationExpr(_) => {
124                    v1::expr::format_multiplication_expr(self, stream)
125                }
126                AstNode::NameRefExpr(_) => v1::expr::format_name_ref_expr(self, stream),
127                AstNode::NegationExpr(_) => v1::expr::format_negation_expr(self, stream),
128                AstNode::OutputSection(_) => v1::format_output_section(self, stream),
129                AstNode::PairType(_) => v1::decl::format_pair_type(self, stream),
130                AstNode::ObjectType(_) => v1::decl::format_object_type(self, stream),
131                AstNode::ParameterMetadataSection(_) => {
132                    v1::meta::format_parameter_metadata_section(self, stream)
133                }
134                AstNode::ParenthesizedExpr(_) => v1::expr::format_parenthesized_expr(self, stream),
135                AstNode::Placeholder(_) => v1::expr::format_placeholder(self, stream),
136                AstNode::PrimitiveType(_) => v1::decl::format_primitive_type(self, stream),
137                AstNode::RequirementsItem(_) => v1::task::format_requirements_item(self, stream),
138                AstNode::RequirementsSection(_) => {
139                    v1::task::format_requirements_section(self, stream)
140                }
141                AstNode::RuntimeItem(_) => v1::task::format_runtime_item(self, stream),
142                AstNode::RuntimeSection(_) => v1::task::format_runtime_section(self, stream),
143                AstNode::ScatterStatement(_) => {
144                    v1::workflow::format_scatter_statement(self, stream)
145                }
146                AstNode::SepOption(_) => v1::expr::format_sep_option(self, stream),
147                AstNode::StructDefinition(_) => {
148                    v1::r#struct::format_struct_definition(self, stream)
149                }
150                AstNode::SubtractionExpr(_) => v1::expr::format_subtraction_expr(self, stream),
151                AstNode::TaskDefinition(_) => v1::task::format_task_definition(self, stream),
152                AstNode::TaskHintsItem(_) => v1::task::format_task_hints_item(self, stream),
153                AstNode::TaskHintsSection(_) => v1::task::format_task_hints_section(self, stream),
154                AstNode::TrueFalseOption(_) => v1::expr::format_true_false_option(self, stream),
155                AstNode::TypeRef(_) => v1::decl::format_type_ref(self, stream),
156                AstNode::UnboundDecl(_) => v1::decl::format_unbound_decl(self, stream),
157                AstNode::VersionStatement(_) => v1::format_version_statement(self, stream),
158                AstNode::WorkflowDefinition(_) => {
159                    v1::workflow::format_workflow_definition(self, stream)
160                }
161                AstNode::WorkflowHintsArray(_) => {
162                    v1::workflow::format_workflow_hints_array(self, stream)
163                }
164                AstNode::WorkflowHintsItem(_) => {
165                    v1::workflow::format_workflow_hints_item(self, stream)
166                }
167                AstNode::WorkflowHintsObject(_) => {
168                    v1::workflow::format_workflow_hints_object(self, stream)
169                }
170                AstNode::WorkflowHintsObjectItem(_) => {
171                    v1::workflow::format_workflow_hints_object_item(self, stream)
172                }
173                AstNode::WorkflowHintsSection(_) => {
174                    v1::workflow::format_workflow_hints_section(self, stream)
175                }
176            },
177            Element::Token(token) => {
178                stream.push_ast_token(token);
179            }
180        }
181    }
182}
183
184/// A formatter.
185#[derive(Debug, Default)]
186pub struct Formatter {
187    /// The configuration.
188    config: Config,
189}
190
191impl Formatter {
192    /// Creates a new formatter.
193    pub fn new(config: Config) -> Self {
194        Self { config }
195    }
196
197    /// Gets the configuration for this formatter.
198    pub fn config(&self) -> &Config {
199        &self.config
200    }
201
202    /// Formats an element.
203    pub fn format<W: Writable>(&self, element: W) -> std::result::Result<String, std::fmt::Error> {
204        let mut result = String::new();
205
206        for token in self.to_stream(element) {
207            write!(result, "{token}", token = token.display(self.config()))?;
208        }
209
210        Ok(result)
211    }
212
213    /// Gets the [`PostToken`] stream.
214    fn to_stream<W: Writable>(&self, element: W) -> TokenStream<PostToken> {
215        let mut stream = TokenStream::default();
216        element.write(&mut stream);
217
218        let mut postprocessor = Postprocessor::default();
219        postprocessor.run(stream, self.config())
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use wdl_ast::Document;
226    use wdl_ast::Node;
227
228    use crate::Formatter;
229    use crate::element::node::AstNodeFormatExt as _;
230
231    #[test]
232    fn smoke() {
233        let (document, diagnostics) = Document::parse(
234            "## WDL
235version 1.2  # This is a comment attached to the version.
236
237# This is a comment attached to the task keyword.
238task foo # This is an inline comment on the task ident.
239{
240
241} # This is an inline comment on the task close brace.
242
243# This is a comment attached to the workflow keyword.
244workflow bar # This is an inline comment on the workflow ident.
245{
246  # This is attached to the call keyword.
247  call foo {}
248} # This is an inline comment on the workflow close brace.",
249        );
250
251        assert!(diagnostics.is_empty());
252        let document = Node::Ast(document.ast().into_v1().unwrap()).into_format_element();
253        let formatter = Formatter::default();
254        let result = formatter.format(&document);
255        match result {
256            Ok(s) => {
257                print!("{}", s);
258            }
259            Err(err) => {
260                panic!("failed to format document: {}", err);
261            }
262        }
263    }
264}