Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / licence-bundler / src / licence_bundler.gleam
5.8 kB 220 lines
1import gleam/bool 2import gleam/io 3import gleam/list 4import gleam/result 5import gleam/set.{type Set} 6import gleam/string 7import gleam/string_tree 8import htmb 9import shellout 10import simplifile 11import tom 12 13pub fn main() -> Nil { 14 let gleam_crates = determine_gleam_crates() 15 let active_licences = determine_licences() 16 17 let assert Ok(output) = 18 shellout.command( 19 run: "cargo", 20 with: [ 21 "tree", 22 "--format", 23 "{p} {l}", 24 "--edges", 25 "no-dev,no-build,no-proc-macro", 26 ], 27 in: "..", 28 opt: [], 29 ) 30 31 let rust_dependencies = 32 output 33 |> string.split("\n") 34 |> list.filter(fn(line) { line != "" }) 35 |> list.map(trim_line_prefix) 36 |> list.filter_map(dependency_information(_, gleam_crates, active_licences)) 37 |> set.from_list 38 |> set.to_list 39 |> list.sort(fn(a, b) { 40 string.compare( 41 a.name <> a.licence_identifier, 42 b.name <> b.licence_identifier, 43 ) 44 }) 45 46 let assert Ok(licences) = 47 active_licences 48 |> set.to_list 49 |> list.sort(string.compare) 50 |> list.try_map(fn(identifier) { 51 simplifile.read("../licences/" <> identifier <> ".txt") 52 |> result.map(Licence(identifier:, text: _)) 53 }) 54 55 let h = htmb.h 56 let t = htmb.text 57 58 let licence_slug = fn(identifier) { string.replace(identifier, " ", "-") } 59 let licence_link = fn(licence_identifier) { 60 let slug = licence_slug(licence_identifier) 61 h("a", [#("href", "#" <> slug)], [t(licence_identifier)]) 62 } 63 64 let html = 65 h("html", [#("lang", "en")], [ 66 h("head", [], []), 67 h("body", [], [ 68 h("h1", [], [t("Gleam dependency licences")]), 69 h("section", [], [ 70 h("h2", [], [t("Rust dependency packages")]), 71 h_ul(rust_dependencies, fn(package) { 72 assert licences != [] as { package.name <> " missing licences" } 73 let licences = 74 list.map(package.used_licences, licence_link) 75 |> list.intersperse(t(" or ")) 76 let url = 77 "https://crates.io/crates/" 78 <> package.name 79 <> "/" 80 <> package.version 81 [ 82 h("a", [#("href", url)], [ 83 t(package.name <> "@" <> package.version), 84 ]), 85 t(" using "), 86 ..licences 87 ] 88 }), 89 ]), 90 h("section", [], [ 91 h("h2", [], [t("Licences")]), 92 h_ul(licences, fn(licence) { 93 let id = licence.identifier 94 [ 95 h("h3", [#("id", licence_slug(id))], [t(id)]), 96 h("pre", [], [t(licence.text)]), 97 ] 98 }), 99 ]), 100 ]), 101 ]) 102 |> htmb.render_page 103 |> string_tree.to_string 104 105 let assert Ok(_) = simplifile.write("../gleam-licences.html", html) 106 io.println("Written ../gleam-licences.html") 107 108 Nil 109} 110 111fn h_ul(items: List(a), mapper: fn(a) -> List(htmb.Html)) -> htmb.Html { 112 htmb.h("ul", [], list.map(items, fn(item) { htmb.h("li", [], mapper(item)) })) 113} 114 115fn determine_licences() -> Set(String) { 116 let assert Ok(files) = simplifile.read_directory("../licences") 117 files 118 |> list.map(string.remove_suffix(_, ".txt")) 119 |> set.from_list 120} 121 122fn determine_gleam_crates() -> Set(String) { 123 let assert Ok(toml) = simplifile.read("../Cargo.toml") 124 let assert Ok(config) = tom.parse(toml) 125 let assert Ok(crates) = tom.get_array(config, ["workspace", "members"]) 126 let assert Ok(paths) = list.try_map(crates, tom.as_string) 127 let crates = 128 list.map(paths, fn(path) { 129 let assert Ok(toml) = simplifile.read("../" <> path <> "/Cargo.toml") 130 let assert Ok(config) = tom.parse(toml) 131 let assert Ok(name) = tom.get_string(config, ["package", "name"]) 132 name 133 }) 134 set.from_list(crates) 135} 136 137type Crate { 138 Crate( 139 name: String, 140 version: String, 141 licence_identifier: String, 142 used_licences: List(String), 143 ) 144} 145 146type Licence { 147 Licence(identifier: String, text: String) 148} 149 150fn dependency_information( 151 line: String, 152 gleam_crates: Set(String), 153 active_licences: Set(String), 154) -> Result(Crate, Nil) { 155 let line = string.remove_suffix(line, " (*)") 156 let assert [name, version, ..licence_parts] = string.split(line, " ") 157 use <- bool.guard(set.contains(gleam_crates, name), return: Error(Nil)) 158 let version = string.remove_prefix(version, "v") 159 let licence_identifier = string.join(licence_parts, " ") 160 let used_licences = 161 licence_identifier 162 |> string.split(" OR ") 163 |> list.flat_map(string.split(_, " AND ")) 164 |> list.flat_map(string.split(_, "/")) 165 |> list.map(string.remove_prefix(_, "(")) 166 |> list.map(string.remove_suffix(_, ")")) 167 |> list.map(string.remove_suffix(_, "+")) 168 |> list.filter(set.contains(active_licences, _)) 169 assert used_licences != [] 170 as { string.inspect(line) <> " parsed to no used licences" } 171 Ok(Crate(name:, version:, licence_identifier:, used_licences:)) 172} 173 174/// Convert lines like these: 175/// 176/// ```txt 177/// │ └── wasm-bindgen v0.2.106 MIT OR Apache-2.0 178/// ``` 179/// 180/// into lines like these 181/// 182/// ```txt 183/// wasm-bindgen v0.2.106 MIT OR Apache-2.0 184/// ``` 185/// 186fn trim_line_prefix(line: String) -> String { 187 case line { 188 " " <> line | "" <> line | "" <> line | "" <> line | "" <> line -> 189 trim_line_prefix(line) 190 191 "" 192 | "a" <> _ 193 | "b" <> _ 194 | "c" <> _ 195 | "d" <> _ 196 | "e" <> _ 197 | "f" <> _ 198 | "g" <> _ 199 | "h" <> _ 200 | "i" <> _ 201 | "j" <> _ 202 | "k" <> _ 203 | "l" <> _ 204 | "m" <> _ 205 | "n" <> _ 206 | "o" <> _ 207 | "p" <> _ 208 | "q" <> _ 209 | "r" <> _ 210 | "s" <> _ 211 | "t" <> _ 212 | "u" <> _ 213 | "v" <> _ 214 | "w" <> _ 215 | "x" <> _ 216 | "y" <> _ 217 | "z" <> _ -> line 218 _ -> panic as { "Unexpected line prefix: " <> string.inspect(line) } 219 } 220}