A lean XML builder for Gleam

started work on 1.1.x

Changed files
+92
src
+92
src/xmleam/xml_builder.gleam
··· 1 + ////Goals for this module: 2 + //// 1. make xml like the string_builder 3 + //// ie. xml_builder.new() 4 + //// |> xml_builder.block_tag("name", { 5 + //// xml_builder.new() 6 + //// |> xml_builder.tag("hello", "world") 7 + //// |> xml_builder.tag("maybe", "here")}) 8 + //// |> xml_builder.option_tag("link", Opt.("href", "https://example.com")) 9 + //// To: 10 + //// <?xml version="1.0" encoding="UTF-8" xml?> 11 + //// <name> 12 + //// <hello> world </hello> 13 + //// </name> 14 + 15 + import gleam/string_builder 16 + import gleam/string 17 + import gleam/result 18 + import gleam/bool 19 + 20 + pub type BuilderError { 21 + ContentsEmpty 22 + TagNameEmpty 23 + OptionsEmpty 24 + VersionEmpty 25 + EncodingEmpty 26 + TagPlacedBeforeNew 27 + } 28 + 29 + pub type XmlBuilder = 30 + Result(string_builder.StringBuilder, BuilderError) 31 + 32 + /// this function starts the builder and 33 + /// this function assumes version 1.0 and encoding UTF-8 if you need specific verisons or encoding 34 + /// use new_advanced_document(version, encoding) 35 + pub fn new_document() -> XmlBuilder { 36 + string_builder.new() 37 + |> string_builder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>") 38 + |> Ok 39 + } 40 + 41 + /// this funcion starts the builder and 42 + /// allows you to put in your own version and encoding 43 + pub fn new_advanced_document(version: String, encoding: String) -> XmlBuilder { 44 + let version_empty = string.is_empty(version) 45 + use <- bool.guard(when: version_empty, return: Error(VersionEmpty)) 46 + let encoding_empty = string.is_empty(encoding) 47 + use <- bool.guard(when: encoding_empty, return: Error(EncodingEmpty)) 48 + 49 + string_builder.new() 50 + |> string_builder.append("<?xml version=\"") 51 + |> string_builder.append(version) 52 + |> string_builder.append("\" encoding=\"") 53 + |> string_builder.append(encoding) 54 + |> string_builder.append("\"?> \n") 55 + |> Ok 56 + } 57 + 58 + /// this function starts the blocks inside of tags 59 + pub fn new() -> XmlBuilder { 60 + string_builder.new() 61 + |> Ok 62 + } 63 + 64 + /// this is a basic tag that takes in a label and contents and a 65 + /// document in the form of an XmlBuilder 66 + /// this is intended to be used in a pipe chain 67 + /// ie. new_document() 68 + /// |> tag("hello", "world") 69 + /// Throws an error if anything is left blank 70 + pub fn tag(label: String, contents: String, document: XmlBuilder) -> XmlBuilder { 71 + let label_empty = string.is_empty(label) 72 + use <- bool.guard(when: label_empty, return: Error(TagNameEmpty)) 73 + let contents_empty = string.is_empty(contents) 74 + use <- bool.guard(when: contents_empty, return: Error(ContentsEmpty)) 75 + let documents_empty = 76 + string_builder.is_empty(result.unwrap(document, string_builder.new())) 77 + use <- bool.guard(when: documents_empty, return: Error(TagPlacedBeforeNew)) 78 + 79 + string_builder.new() 80 + |> string_builder.append("<") 81 + |> string_builder.append(label) 82 + |> string_builder.append("> ") 83 + |> string_builder.append(contents) 84 + |> string_builder.append(" </") 85 + |> string_builder.append(label) 86 + |> string_builder.append("> \n") 87 + |> string_builder.append_builder(to: result.unwrap( 88 + document, 89 + string_builder.new(), 90 + )) 91 + |> Ok 92 + }