···2020use camino::Utf8Path;
2121use ecow::{EcoString, eco_format};
2222use erlang_generation::{
2323- BitArraySegmentSpecifier, ErlangBuilder, ErlangModuleName, ErlangSourceBuilder,
2323+ BitArraySegmentSpecifier, DocContent, ErlangBuilder, ErlangModuleName, ErlangSourceBuilder,
2424};
2525use itertools::Itertools;
2626use num_bigint::BigInt;
···260260 if self.module.type_info.is_internal {
261261 // The module is internal so we need to add a `-moduledoc(false).`
262262 // attribute to make sure its documentation is hidden.
263263- let doc = builder.start_moduledoc_attribute();
264264- builder.atom("false");
265265- builder.end_doc_attribute(doc);
263263+ builder.moduledoc_attribute(DocContent::False);
266264 } else if self.module.documentation.is_empty() {
267265 // The module is not internal, but it has no docs.
268266 // We don't have to do anything.
269267 } else {
270268 // The module has some documentation that we're going to include
271269 // with a `-moduledoc` attribute.
272272- let doc = builder.start_moduledoc_attribute();
273273- let documentation = &self.module.documentation.iter().join("\n");
274274- builder.string(documentation);
275275- builder.end_doc_attribute(doc);
270270+ builder.moduledoc_attribute(DocContent::String(
271271+ &self.module.documentation.iter().join("\n"),
272272+ ));
276273 }
277274 }
278275···692689 self.module_generator.module.type_info.is_internal || function.publicity.is_internal();
693690694691 if is_internal {
695695- let attribute = builder.start_doc_attribute();
696696- builder.atom("false");
697697- builder.end_doc_attribute(attribute);
692692+ builder.doc_attribute(DocContent::False);
698693 } else if let Some((_, documentation)) = &function.documentation
699694 && !documentation.is_empty()
700695 {
701701- let attribute = builder.start_doc_attribute();
702702- builder.string(documentation);
703703- builder.end_doc_attribute(attribute);
696696+ builder.doc_attribute(DocContent::String(documentation));
704697 }
705698 }
706699
···6464 Unit(u8),
6565}
66666767+/// This represents what can go in a moduledoc or doc comment. Either the atom
6868+/// false, if the function/module is to be hidden from the public docs, or the
6969+/// string content to include.
7070+pub enum DocContent<'string> {
7171+ False,
7272+ String(&'string str),
7373+}
7474+6775/// A trait defining operations to describe the content of an Erlang module.
6876///
6977/// This might look strange in places, for example why is there a `start_tuple`
···116124117125 /// Represents an open tuple pattern that has yet to be closed.
118126 type TuplePattern;
119119-120120- /// Represents an open doc/moduledoc attribute.
121121- type DocAttribute;
122127123128 /// Represents an open record attribute.
124129 type RecordAttribute;
···201206 exported: impl IntoIterator<Item = (Name, usize)>,
202207 );
203208204204- /// Starts a `-doc` attribute.
205205- /// What is generated after calling this function will end up inside the
206206- /// `-doc` attribute.
207207- /// You'll most likely always put a string or the atom "false" inside it.
208208- ///
209209+ /// Creates a `-doc` attribute with the given content.
209210 /// For example:
210211 ///
211212 /// ```ignore
212212- /// let doc = builder.start_doc_attribute();
213213- /// builder.atom("false");
214214- /// builder.close_doc_attribute(doc);
213213+ /// todo!()
215214 /// ```
216215 ///
217216 /// Corresponds to:
···220219 /// -doc(false).
221220 /// ```
222221 ///
223223- fn start_doc_attribute(&mut self) -> Self::DocAttribute;
222222+ fn doc_attribute(&mut self, content: DocContent<'_>);
224223225225- /// Starts a `-moduledoc` attribute.
226226- /// What is generated after calling this function will end up inside the
227227- /// `-moduledoc` attribute.
228228- /// You'll most likely always put a string or the atom "false" inside it.
229229- ///
224224+ /// Creates a `-moduledoc` attribute with the given content.
230225 /// For example:
231226 ///
232227 /// ```ignore
233233- /// let doc = builder.start_moduledoc_attribute();
234234- /// builder.atom("false");
235235- /// builder.close_doc_attribute(doc);
228228+ /// todo!()
236229 /// ```
237230 ///
238231 /// Corresponds to:
···241234 /// -moduledoc(false).
242235 /// ```
243236 ///
244244- fn start_moduledoc_attribute(&mut self) -> Self::DocAttribute;
245245-246246- /// This closes the currently open doc/moduledoc attribute.
247247- /// Code generated after this is not gonna be part of it.
248248- ///
249249- fn end_doc_attribute(&mut self, attribute: Self::DocAttribute);
237237+ fn moduledoc_attribute(&mut self, content: DocContent<'_>);
250238251239 /// This generates the code for a `-compile([]).` attribute where all the
252240 /// strings produces by the given iterator are going to be passed as atom
···12741262/// printed code from an `ErlangSourceBuilder`.
12751263#[derive(Debug)]
12761264enum ErlangSourceBuilderPosition {
12771277- /// We're generating a top level documentation attribute like `-doc(false)`,
12781278- /// or `-moduledoc(~"wibble wobble")`.
12791279- DocAttribute,
12801280-12811265 /// We're generating a function spec like `-spec wibble(atom()) -> atom()`.
12821266 FunctionSpec,
12831267···16911675 type ClauseBody = ();
16921676 type ClauseGuards = ();
16931677 type ClausePattern = ();
16941694- type DocAttribute = ();
16951678 type Function = ();
16961679 type FunctionSpec = ();
16971680 type FunctionType = ();
···17781761 self.code.push_str("]).\n");
17791762 }
1780176317811781- fn start_doc_attribute(&mut self) -> Self::DocAttribute {
17641764+ fn doc_attribute(&mut self, content: DocContent<'_>) {
17821765 self.new_top_level_form();
17831766 self.code.push_str("-doc(");
17841784- self.position
17851785- .push(ErlangSourceBuilderPosition::DocAttribute);
17671767+ self.doc_content(content);
17681768+ self.code.push_str(").\n")
17861769 }
1787177017881788- fn start_moduledoc_attribute(&mut self) -> Self::DocAttribute {
17711771+ fn moduledoc_attribute(&mut self, content: DocContent<'_>) {
17891772 self.new_top_level_form();
17901773 self.code.push_str("-moduledoc(");
17911791- self.position
17921792- .push(ErlangSourceBuilderPosition::DocAttribute);
17931793- }
17941794-17951795- fn end_doc_attribute(&mut self, _attribute: Self::DocAttribute) {
17961796- self.close_currently_open_item();
17741774+ self.doc_content(content);
17751775+ self.code.push_str(").\n")
17971776 }
1798177717991778 fn compile_attribute<'a>(&mut self, arguments: impl IntoIterator<Item = &'a str>) {
···24852464 };
2486246524872466 match position {
24882488- ErlangSourceBuilderPosition::DocAttribute => (),
24892489-24902467 ErlangSourceBuilderPosition::RecordField {
24912468 expected: expected @ ExpectedRecordFieldItem::Name,
24922469 } => *expected = ExpectedRecordFieldItem::Type,
···27552732 | ErlangSourceBuilderPosition::List { .. }
27562733 | ErlangSourceBuilderPosition::FunctionStatement { .. }
27572734 | ErlangSourceBuilderPosition::AnonymousFunctionStatement { .. }
27582758- | ErlangSourceBuilderPosition::DocAttribute
27592735 | ErlangSourceBuilderPosition::UnaryOperator
27602736 | ErlangSourceBuilderPosition::Case { .. }
27612737 | ErlangSourceBuilderPosition::BinaryOperator { .. }
···28942870 | ErlangSourceBuilderPosition::UnionType { .. }
28952871 | ErlangSourceBuilderPosition::TupleType { .. }
28962872 | ErlangSourceBuilderPosition::RecordField { .. }
28972897- | ErlangSourceBuilderPosition::DocAttribute
28982873 | ErlangSourceBuilderPosition::RecordAttribute { .. }
28992874 | ErlangSourceBuilderPosition::List {
29002875 kind: ListKind::Pattern,
···30192994 }
30202995 // There's nothing left to do when a union type ends.
30212996 ErlangSourceBuilderPosition::UnionType { .. } => (),
30223022- // When a doc attribute is closed we need to add the closed
30233023- // parentheses and a newline.
30243024- ErlangSourceBuilderPosition::DocAttribute => self.code.push_str(").\n"),
30252997 // When a case expression is over, we need to add the closing `end`.
30262998 ErlangSourceBuilderPosition::Case {
30272999 expected: ExpectedCaseItem::Branches { .. },
···31633135 })
31643136 .into()
31653137 }
31663166-31673167- ErlangSourceBuilderPosition::DocAttribute => {
31683168- // Escaping strings generated inside doc attributes is a little
31693169- // different: since their content doesn't come from a Gleam
31703170- // literal string but freeform text, their content might contain
31713171- // all sorts of unescaped characters.
31723172- content.replace("\\", "\\\\").replace("\"", "\\\"")
31733173- }
31743138 }
31753139 }
31763140···34103374 | ErlangSourceBuilderPosition::TupleType { .. }
34113375 | ErlangSourceBuilderPosition::FunctionStatement { .. }
34123376 | ErlangSourceBuilderPosition::AnonymousFunctionStatement { .. }
34133413- | ErlangSourceBuilderPosition::DocAttribute
34143377 | ErlangSourceBuilderPosition::FunctionSpec
34153378 | ErlangSourceBuilderPosition::Tuple { .. }
34163379 | ErlangSourceBuilderPosition::TuplePattern { .. }
···34953458 fn error_with_position(&self, expected: &str) -> String {
34963459 let position = self.position.last();
34973460 format!("tried {expected}, position: {position:?}")
34613461+ }
34623462+34633463+ fn doc_content(&mut self, content: DocContent<'_>) {
34643464+ match content {
34653465+ DocContent::False => self.code.push_str("false"),
34663466+ // Escaping strings generated inside doc attributes is a little
34673467+ // different: since their content doesn't come from a Gleam
34683468+ // literal string but freeform text, their content might contain
34693469+ // all sorts of unescaped characters.
34703470+ DocContent::String(content) => {
34713471+ self.code.push_str("~\"");
34723472+ self.code
34733473+ .push_str(&content.replace("\\", "\\\\").replace("\"", "\\\""));
34743474+ self.code.push('"');
34753475+ }
34763476+ }
34983477 }
34993478}
35003479