Fork of daniellemaywood.uk/gleam — Wasm codegen work
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}