Skip to main content

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