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