odin_html_docs_main.odin 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. package odin_html_docs
  2. import doc "core:odin/doc-format"
  3. import "core:fmt"
  4. import "core:io"
  5. import "core:os"
  6. import "core:strings"
  7. import "core:path/slashpath"
  8. import "core:sort"
  9. import "core:slice"
  10. header: ^doc.Header
  11. files: []doc.File
  12. pkgs: []doc.Pkg
  13. entities: []doc.Entity
  14. types: []doc.Type
  15. pkgs_to_use: map[string]^doc.Pkg // trimmed path
  16. pkg_to_path: map[^doc.Pkg]string // trimmed path
  17. array :: proc(a: $A/doc.Array($T)) -> []T {
  18. return doc.from_array(header, a)
  19. }
  20. str :: proc(s: $A/doc.String) -> string {
  21. return doc.from_string(header, s)
  22. }
  23. errorf :: proc(format: string, args: ..any) -> ! {
  24. fmt.eprintf("%s ", os.args[0])
  25. fmt.eprintf(format, ..args)
  26. fmt.eprintln()
  27. os.exit(1)
  28. }
  29. common_prefix :: proc(strs: []string) -> string {
  30. if len(strs) == 0 {
  31. return ""
  32. }
  33. n := max(int)
  34. for str in strs {
  35. n = min(n, len(str))
  36. }
  37. prefix := strs[0][:n]
  38. for str in strs[1:] {
  39. for len(prefix) != 0 && str[:len(prefix)] != prefix {
  40. prefix = prefix[:len(prefix)-1]
  41. }
  42. if len(prefix) == 0 {
  43. break
  44. }
  45. }
  46. return prefix
  47. }
  48. write_html_header :: proc(w: io.Writer, title: string) {
  49. fmt.wprintf(w, `<!DOCTYPE html>
  50. <html>
  51. <head>
  52. <meta charset="utf-8">
  53. <meta name="viewport" content="width=device-width, initial-scale=1">
  54. <title>%s</title>
  55. <script type="text/javascript" src="https://livejs.com/live.js"></script>
  56. <link rel="stylesheet" type="text/css" href="/style.css">
  57. </style>
  58. </head>
  59. <body>`, title)
  60. fmt.wprintln(w, "\n<div class=\"container\">")
  61. fmt.wprintln(w, "\n<a href=\"/core\">Core Directory</a>")
  62. }
  63. write_html_footer :: proc(w: io.Writer) {
  64. fmt.wprintf(w, "</div></body>\n</html>\n")
  65. }
  66. main :: proc() {
  67. if len(os.args) != 2 {
  68. errorf("expected 1 .odin-doc file")
  69. }
  70. data, ok := os.read_entire_file(os.args[1])
  71. if !ok {
  72. errorf("unable to read file:", os.args[1])
  73. }
  74. err: doc.Reader_Error
  75. header, err = doc.read_from_bytes(data)
  76. switch err {
  77. case .None:
  78. case .Header_Too_Small:
  79. errorf("file is too small for the file format")
  80. case .Invalid_Magic:
  81. errorf("invalid magic for the file format")
  82. case .Data_Too_Small:
  83. errorf("data is too small for the file format")
  84. case .Invalid_Version:
  85. errorf("invalid file format version")
  86. }
  87. files = array(header.files)
  88. pkgs = array(header.pkgs)
  89. entities = array(header.entities)
  90. types = array(header.types)
  91. fullpaths: [dynamic]string
  92. defer delete(fullpaths)
  93. for pkg in pkgs[1:] {
  94. append(&fullpaths, str(pkg.fullpath))
  95. }
  96. path_prefix := common_prefix(fullpaths[:])
  97. pkgs_to_use = make(map[string]^doc.Pkg)
  98. for fullpath, i in fullpaths {
  99. path := strings.trim_prefix(fullpath, path_prefix)
  100. if strings.has_prefix(path, "core/") {
  101. pkgs_to_use[strings.trim_prefix(path, "core/")] = &pkgs[i+1]
  102. }
  103. }
  104. sort.map_entries_by_key(&pkgs_to_use)
  105. for path, pkg in pkgs_to_use {
  106. pkg_to_path[pkg] = path
  107. }
  108. b := strings.make_builder()
  109. w := strings.to_writer(&b)
  110. {
  111. strings.reset_builder(&b)
  112. write_html_header(w, "core library - pkg.odin-lang.org")
  113. write_core_directory(w)
  114. write_html_footer(w)
  115. os.make_directory("core", 0)
  116. os.write_entire_file("core/index.html", b.buf[:])
  117. }
  118. for path, pkg in pkgs_to_use {
  119. strings.reset_builder(&b)
  120. write_html_header(w, fmt.tprintf("package %s - pkg.odin-lang.org", path))
  121. write_pkg(w, path, pkg)
  122. write_html_footer(w)
  123. os.make_directory(fmt.tprintf("core/%s", path), 0)
  124. os.write_entire_file(fmt.tprintf("core/%s/index.html", path), b.buf[:])
  125. }
  126. }
  127. write_core_directory :: proc(w: io.Writer) {
  128. Node :: struct {
  129. dir: string,
  130. path: string,
  131. name: string,
  132. pkg: ^doc.Pkg,
  133. next: ^Node,
  134. first_child: ^Node,
  135. }
  136. add_child :: proc(parent: ^Node, child: ^Node) -> ^Node {
  137. assert(parent != nil)
  138. end := &parent.first_child
  139. for end^ != nil {
  140. end = &end^.next
  141. }
  142. child.next = end^
  143. end^ = child
  144. return child
  145. }
  146. root: Node
  147. for path, pkg in pkgs_to_use {
  148. dir, _, inner := strings.partition(path, "/")
  149. node: ^Node = nil
  150. for node = root.first_child; node != nil; node = node.next {
  151. if node.dir == dir {
  152. break
  153. }
  154. }
  155. if inner == "" {
  156. if node == nil {
  157. add_child(&root, new_clone(Node{
  158. dir = dir,
  159. name = dir,
  160. path = path,
  161. pkg = pkg,
  162. }))
  163. } else {
  164. node.dir = dir
  165. node.name = dir
  166. node.path = path
  167. node.pkg = pkg
  168. }
  169. } else {
  170. if node == nil {
  171. node = add_child(&root, new_clone(Node{
  172. dir = dir,
  173. name = dir,
  174. }))
  175. }
  176. assert(node != nil)
  177. child := add_child(node, new_clone(Node{
  178. dir = dir,
  179. name = inner,
  180. path = path,
  181. pkg = pkg,
  182. }))
  183. }
  184. }
  185. fmt.wprintln(w, "<h2>Directories</h2>")
  186. fmt.wprintln(w, "\t<table>")
  187. fmt.wprintln(w, "\t\t<tbody>")
  188. for dir := root.first_child; dir != nil; dir = dir.next {
  189. if dir.first_child != nil {
  190. fmt.wprint(w, `<tr aria-controls="`)
  191. for child := dir.first_child; child != nil; child = child.next {
  192. fmt.wprintf(w, "pkg-%s ", str(child.pkg.name))
  193. }
  194. fmt.wprint(w, `" class="directory-pkg"><td class="pkg-name" data-aria-owns="`)
  195. for child := dir.first_child; child != nil; child = child.next {
  196. fmt.wprintf(w, "pkg-%s ", str(child.pkg.name))
  197. }
  198. fmt.wprintf(w, `" id="pkg-%s">`, dir.dir)
  199. } else {
  200. fmt.wprintf(w, `<tr id="pkg-%s" class="directory-pkg"><td class="pkg-name">`, dir.dir)
  201. }
  202. if dir.pkg != nil {
  203. fmt.wprintf(w, `<a href="/core/%s">%s</a>`, dir.path, dir.name)
  204. } else {
  205. fmt.wprintf(w, "%s", dir.name)
  206. }
  207. fmt.wprintf(w, "</td>")
  208. if dir.pkg != nil {
  209. line_doc, _, _ := strings.partition(str(dir.pkg.docs), "\n")
  210. line_doc = strings.trim_space(line_doc)
  211. if line_doc != "" {
  212. fmt.wprintf(w, `<td class="pkg-line-doc">%s</td>`, line_doc)
  213. }
  214. }
  215. fmt.wprintf(w, "</tr>\n")
  216. for child := dir.first_child; child != nil; child = child.next {
  217. assert(child.pkg != nil)
  218. fmt.wprintf(w, `<tr id="pkg-%s" class="directory-pkg directory-child"><td class="pkg-name">`, str(child.pkg.name))
  219. fmt.wprintf(w, `<a href="/core/%s/">%s</a>`, child.path, child.name)
  220. fmt.wprintf(w, "</td>")
  221. line_doc, _, _ := strings.partition(str(child.pkg.docs), "\n")
  222. line_doc = strings.trim_space(line_doc)
  223. if line_doc != "" {
  224. fmt.wprintf(w, `<td class="pkg-line-doc">%s</td>`, line_doc)
  225. }
  226. fmt.wprintf(w, "</tr>\n")
  227. }
  228. }
  229. fmt.wprintln(w, "\t\t</tbody>")
  230. fmt.wprintln(w, "\t</table>")
  231. }
  232. is_entity_blank :: proc(e: doc.Entity_Index) -> bool {
  233. name := str(entities[e].name)
  234. return name == "" || name == "_"
  235. }
  236. Write_Type_Flag :: enum {
  237. Is_Results,
  238. Variadic,
  239. }
  240. Write_Type_Flags :: distinct bit_set[Write_Type_Flag]
  241. write_type :: proc(w: io.Writer, pkg: doc.Pkg_Index, type: doc.Type, flags: Write_Type_Flags) {
  242. type_entites := array(type.entities)
  243. type_types := array(type.types)
  244. switch type.kind {
  245. case .Invalid:
  246. // ignore
  247. case .Basic:
  248. type_flags := transmute(doc.Type_Flags_Basic)type.flags
  249. if .Untyped in type_flags {
  250. io.write_string(w, str(type.name))
  251. } else {
  252. fmt.wprintf(w, `<a href="">%s</a>`, str(type.name))
  253. }
  254. case .Named:
  255. e := entities[type_entites[0]]
  256. name := str(type.name)
  257. fmt.wprintf(w, `<span>`)
  258. tn_pkg := files[e.pos.file].pkg
  259. if tn_pkg != pkg {
  260. fmt.wprintf(w, `%s.`, str(pkgs[pkg].name))
  261. }
  262. fmt.wprintf(w, `<a href="/core/{0:s}/#{1:s}">{1:s}</a></span>`, pkg_to_path[&pkgs[tn_pkg]], name)
  263. case .Generic:
  264. name := str(type.name)
  265. io.write_byte(w, '$')
  266. io.write_string(w, name)
  267. if len(array(type.types)) == 1 {
  268. io.write_byte(w, '/')
  269. write_type(w, pkg, types[type_types[0]], flags)
  270. }
  271. case .Pointer:
  272. io.write_byte(w, '^')
  273. write_type(w, pkg, types[type_types[0]], flags)
  274. case .Array:
  275. assert(type.elem_count_len == 1)
  276. io.write_byte(w, '[')
  277. io.write_uint(w, uint(type.elem_counts[0]))
  278. io.write_byte(w, ']')
  279. write_type(w, pkg, types[type_types[0]], flags)
  280. case .Enumerated_Array:
  281. io.write_byte(w, '[')
  282. write_type(w, pkg, types[type_types[0]], flags)
  283. io.write_byte(w, ']')
  284. write_type(w, pkg, types[type_types[1]], flags)
  285. case .Slice:
  286. if .Variadic in flags {
  287. io.write_string(w, "..")
  288. } else {
  289. io.write_string(w, "[]")
  290. }
  291. write_type(w, pkg, types[type_types[0]], flags - {.Variadic})
  292. case .Dynamic_Array:
  293. io.write_string(w, "[dynamic]")
  294. write_type(w, pkg, types[type_types[0]], flags)
  295. case .Map:
  296. io.write_string(w, "map[")
  297. write_type(w, pkg, types[type_types[0]], flags)
  298. io.write_byte(w, ']')
  299. write_type(w, pkg, types[type_types[1]], flags)
  300. case .Struct:
  301. type_flags := transmute(doc.Type_Flags_Struct)type.flags
  302. io.write_string(w, "struct {}")
  303. case .Union:
  304. type_flags := transmute(doc.Type_Flags_Union)type.flags
  305. io.write_string(w, "union {}")
  306. case .Enum:
  307. io.write_string(w, "enum {}")
  308. case .Tuple:
  309. entity_indices := type_entites
  310. if len(entity_indices) == 0 {
  311. return
  312. }
  313. require_parens := (.Is_Results in flags) && (len(entity_indices) > 1 || !is_entity_blank(entity_indices[0]))
  314. if require_parens { io.write_byte(w, '(') }
  315. for entity_index, i in entity_indices {
  316. e := &entities[entity_index]
  317. name := str(e.name)
  318. if i > 0 {
  319. io.write_string(w, ", ")
  320. }
  321. if .Param_Using in e.flags { io.write_string(w, "using ") }
  322. if .Param_Const in e.flags { io.write_string(w, "#const ") }
  323. if .Param_Auto_Cast in e.flags { io.write_string(w, "#auto_cast ") }
  324. if .Param_CVararg in e.flags { io.write_string(w, "#c_vararg ") }
  325. if .Param_No_Alias in e.flags { io.write_string(w, "#no_alias ") }
  326. if .Param_Any_Int in e.flags { io.write_string(w, "#any_int ") }
  327. if name != "" {
  328. io.write_string(w, name)
  329. io.write_string(w, ": ")
  330. }
  331. param_flags := flags - {.Is_Results}
  332. if .Param_Ellipsis in e.flags {
  333. param_flags += {.Variadic}
  334. }
  335. write_type(w, pkg, types[e.type], param_flags)
  336. }
  337. if require_parens { io.write_byte(w, ')') }
  338. case .Proc:
  339. type_flags := transmute(doc.Type_Flags_Proc)type.flags
  340. io.write_string(w, "proc")
  341. cc := str(type.calling_convention)
  342. if cc != "" {
  343. io.write_byte(w, ' ')
  344. io.write_quoted_string(w, cc)
  345. io.write_byte(w, ' ')
  346. }
  347. params := array(type.types)[0]
  348. results := array(type.types)[1]
  349. io.write_byte(w, '(')
  350. write_type(w, pkg, types[params], flags)
  351. io.write_byte(w, ')')
  352. if results != 0 {
  353. assert(.Diverging not_in type_flags)
  354. io.write_string(w, " -> ")
  355. write_type(w, pkg, types[results], flags+{.Is_Results})
  356. }
  357. if .Diverging in type_flags {
  358. io.write_string(w, " -> !")
  359. }
  360. if .Optional_Ok in type_flags {
  361. io.write_string(w, " #optional_ok")
  362. }
  363. case .Bit_Set:
  364. type_flags := transmute(doc.Type_Flags_Bit_Set)type.flags
  365. case .Simd_Vector:
  366. io.write_string(w, "#simd[")
  367. io.write_uint(w, uint(type.elem_counts[0]))
  368. io.write_byte(w, ']')
  369. case .SOA_Struct_Fixed:
  370. io.write_string(w, "#soa[")
  371. io.write_uint(w, uint(type.elem_counts[0]))
  372. io.write_byte(w, ']')
  373. case .SOA_Struct_Slice:
  374. io.write_string(w, "#soa[]")
  375. case .SOA_Struct_Dynamic:
  376. io.write_string(w, "#soa[dynamic]")
  377. case .Relative_Pointer:
  378. io.write_string(w, "#relative(")
  379. write_type(w, pkg, types[type_types[1]], flags)
  380. io.write_string(w, ") ")
  381. write_type(w, pkg, types[type_types[0]], flags)
  382. case .Relative_Slice:
  383. io.write_string(w, "#relative(")
  384. write_type(w, pkg, types[type_types[1]], flags)
  385. io.write_string(w, ") ")
  386. write_type(w, pkg, types[type_types[0]], flags)
  387. case .Multi_Pointer:
  388. io.write_string(w, "[^]")
  389. write_type(w, pkg, types[type_types[0]], flags)
  390. case .Matrix:
  391. io.write_string(w, "matrix[")
  392. io.write_uint(w, uint(type.elem_counts[0]))
  393. io.write_string(w, ", ")
  394. io.write_uint(w, uint(type.elem_counts[1]))
  395. io.write_string(w, "]")
  396. write_type(w, pkg, types[type_types[0]], flags)
  397. }
  398. }
  399. write_docs :: proc(w: io.Writer, pkg: ^doc.Pkg, docs: string) {
  400. if docs == "" {
  401. return
  402. }
  403. it := docs
  404. was_code := true
  405. was_paragraph := true
  406. for line in strings.split_iterator(&it, "\n") {
  407. if strings.has_prefix(line, "\t") {
  408. if !was_code {
  409. was_code = true;
  410. fmt.wprint(w, `<pre class="doc-code"><code>`)
  411. }
  412. fmt.wprintf(w, "%s\n", strings.trim_prefix(line, "\t"))
  413. continue
  414. } else if was_code {
  415. was_code = false
  416. fmt.wprintln(w, "</code></pre>")
  417. }
  418. text := strings.trim_space(line)
  419. if text == "" {
  420. if was_paragraph {
  421. was_paragraph = false
  422. fmt.wprintln(w, "</p>")
  423. }
  424. continue
  425. }
  426. if !was_paragraph {
  427. fmt.wprintln(w, "<p>")
  428. }
  429. assert(!was_code)
  430. was_paragraph = true
  431. fmt.wprintln(w, text)
  432. }
  433. if was_code {
  434. // assert(!was_paragraph, str(pkg.name))
  435. was_code = false
  436. fmt.wprintln(w, "</code>")
  437. } else if was_paragraph {
  438. fmt.wprintln(w, "</p>")
  439. }
  440. }
  441. write_pkg :: proc(w: io.Writer, path: string, pkg: ^doc.Pkg) {
  442. fmt.wprintf(w, "<h1>package core:%s</h1>\n", path)
  443. fmt.wprintln(w, "<h2>Documentation</h2>")
  444. docs := strings.trim_space(str(pkg.docs))
  445. if docs != "" {
  446. fmt.wprintln(w, "<h3>Overview</h3>")
  447. fmt.wprintln(w, "<div id=\"pkg-overview\">")
  448. defer fmt.wprintln(w, "</div>")
  449. write_docs(w, pkg, docs)
  450. }
  451. fmt.wprintln(w, "<h3>Index</h3>")
  452. fmt.wprintln(w, `<section class="documentation-index">`)
  453. pkg_procs: [dynamic]^doc.Entity
  454. pkg_proc_groups: [dynamic]^doc.Entity
  455. pkg_types: [dynamic]^doc.Entity
  456. pkg_vars: [dynamic]^doc.Entity
  457. pkg_consts: [dynamic]^doc.Entity
  458. for entity_index in array(pkg.entities) {
  459. e := &entities[entity_index]
  460. name := str(e.name)
  461. if name == "" || name[0] == '_' {
  462. continue
  463. }
  464. switch e.kind {
  465. case .Invalid, .Import_Name, .Library_Name:
  466. // ignore
  467. case .Constant: append(&pkg_consts, e)
  468. case .Variable: append(&pkg_vars, e)
  469. case .Type_Name: append(&pkg_types, e)
  470. case .Procedure: append(&pkg_procs, e)
  471. case .Proc_Group: append(&pkg_proc_groups, e)
  472. }
  473. }
  474. entity_key :: proc(e: ^doc.Entity) -> string {
  475. return str(e.name)
  476. }
  477. slice.sort_by_key(pkg_procs[:], entity_key)
  478. slice.sort_by_key(pkg_proc_groups[:], entity_key)
  479. slice.sort_by_key(pkg_types[:], entity_key)
  480. slice.sort_by_key(pkg_vars[:], entity_key)
  481. slice.sort_by_key(pkg_consts[:], entity_key)
  482. print_index :: proc(w: io.Writer, name: string, entities: []^doc.Entity) {
  483. fmt.wprintf(w, "<h4>%s</h4>\n", name)
  484. fmt.wprintln(w, `<section class="documentation-index">`)
  485. fmt.wprintln(w, "<ul>")
  486. for e in entities {
  487. name := str(e.name)
  488. fmt.wprintf(w, "<li><a href=\"#{0:s}\">{0:s}</a></li>\n", name)
  489. }
  490. fmt.wprintln(w, "</ul>")
  491. fmt.wprintln(w, "</section>")
  492. }
  493. print_index(w, "Procedures", pkg_procs[:])
  494. print_index(w, "Procedure Groups", pkg_proc_groups[:])
  495. print_index(w, "Types", pkg_types[:])
  496. print_index(w, "Variables", pkg_vars[:])
  497. print_index(w, "Constants", pkg_consts[:])
  498. fmt.wprintln(w, "</section>")
  499. print_entity :: proc(w: io.Writer, e: ^doc.Entity) {
  500. pkg := &pkgs[files[e.pos.file].pkg]
  501. name := str(e.name)
  502. fmt.wprintf(w, "<h4 id=\"{0:s}\"><a href=\"#{0:s}\">{0:s}</a></h3>\n", name)
  503. switch e.kind {
  504. case .Invalid, .Import_Name, .Library_Name:
  505. // ignore
  506. case .Constant:
  507. case .Variable:
  508. case .Type_Name:
  509. case .Procedure:
  510. fmt.wprint(w, "<pre>")
  511. fmt.wprintf(w, "%s :: ", name)
  512. write_type(w, files[e.pos.file].pkg, types[e.type], nil)
  513. where_clauses := array(e.where_clauses)
  514. if len(where_clauses) != 0 {
  515. io.write_string(w, " where ")
  516. for clause, i in where_clauses {
  517. if i > 0 {
  518. io.write_string(w, ", ")
  519. }
  520. io.write_string(w, str(clause))
  521. }
  522. }
  523. fmt.wprint(w, " {…}")
  524. fmt.wprintln(w, "</pre>")
  525. case .Proc_Group:
  526. }
  527. write_docs(w, pkg, strings.trim_space(str(e.docs)))
  528. }
  529. print_entities :: proc(w: io.Writer, title: string, entities: []^doc.Entity) {
  530. fmt.wprintf(w, "<h3>%s</h3>\n", title)
  531. fmt.wprintln(w, `<section class="documentation">`)
  532. for e in entities {
  533. print_entity(w, e)
  534. }
  535. fmt.wprintln(w, "</section>")
  536. }
  537. print_entities(w, "Procedures", pkg_procs[:])
  538. print_entities(w, "Procedure Groups", pkg_proc_groups[:])
  539. print_entities(w, "Types", pkg_types[:])
  540. print_entities(w, "Variables", pkg_vars[:])
  541. print_entities(w, "Constants", pkg_consts[:])
  542. fmt.wprintln(w, "<h3>Source Files</h3>")
  543. fmt.wprintln(w, "<ul>")
  544. for file_index in array(pkg.files) {
  545. file := files[file_index]
  546. filename := slashpath.base(str(file.name))
  547. fmt.wprintf(w, `<li><a href="https://github.com/odin-lang/Odin/tree/master/core/%s/%s">%s</a></li>`, path, filename, filename)
  548. fmt.wprintln(w)
  549. }
  550. fmt.wprintln(w, "</ul>")
  551. }