generator.go 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355
  1. // Copyright 2024 The tk9.0-go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. //go:build none
  5. package main
  6. import (
  7. "archive/zip"
  8. "bytes"
  9. "crypto/sha256"
  10. "fmt"
  11. "io"
  12. "io/fs"
  13. "os"
  14. "path/filepath"
  15. "runtime"
  16. "runtime/debug"
  17. "slices"
  18. "strings"
  19. "github.com/adrg/xdg"
  20. "golang.org/x/net/html"
  21. util "modernc.org/fileutil/ccgo"
  22. tcllib "modernc.org/libtcl9.0/library"
  23. libtk "modernc.org/libtk9.0"
  24. tklib "modernc.org/libtk9.0/library"
  25. ngrab "modernc.org/ngrab/lib"
  26. "modernc.org/rec/lib"
  27. )
  28. const (
  29. header = `// Code generated by generator.go, DO NOT EDIT.
  30. package tk9_0 // import "modernc.org/tk9.0"
  31. import "fmt"
  32. `
  33. head = `<html>
  34. <head>
  35. <style>
  36. div {
  37. margin-left: 50px;
  38. }
  39. p {
  40. padding : 0;
  41. margin : 0;
  42. background-color: #FDFDC9;
  43. }
  44. .comment {
  45. background-color: lightgray;
  46. }
  47. .TH {
  48. background-color: #9E9E9E;
  49. }
  50. .SH {
  51. background-color: #2196F3;
  52. }
  53. .SO .text {
  54. background-color: #00FFFF;
  55. }
  56. .OP {
  57. background-color: #FFEB3B;
  58. }
  59. .BS {
  60. background-color: #F44336;
  61. }
  62. .CS .text {
  63. background-color: #87CEEB;
  64. }
  65. .DS {
  66. background-color: #F44336;
  67. }
  68. .RS {
  69. background-color: #F44336;
  70. }
  71. .sso {
  72. background-color: #616161;
  73. }
  74. .SS {
  75. background-color: #8f8;
  76. }
  77. .LP {
  78. background-color: #8f8;
  79. }
  80. </style>
  81. </head>
  82. <body>
  83. `
  84. goarch = runtime.GOARCH
  85. goos = runtime.GOOS
  86. ofn = "generated.go"
  87. tempDir = "html"
  88. )
  89. type cmdOpts map[string][]string
  90. type pageInfo struct {
  91. commands map[string][]string // Go command name: option names
  92. ignore bool
  93. manual bool
  94. widget bool
  95. }
  96. var (
  97. pageInfos = map[string]*pageInfo{
  98. "bell": {
  99. manual: true, // done
  100. commands: cmdOpts{"Bell": []string{
  101. "-displayof",
  102. "-nice",
  103. }},
  104. },
  105. "bind": {manual: true}, // done
  106. "bindtags": {ignore: true},
  107. "bitmap": {
  108. manual: true, // done
  109. commands: cmdOpts{"NewBitmap": []string{
  110. "-background",
  111. "-data",
  112. "-file",
  113. "-foreground",
  114. "-maskdata",
  115. "-maskfile",
  116. }},
  117. },
  118. "busy": {
  119. manual: true, // done
  120. commands: cmdOpts{".Busy": []string{
  121. "-cursor",
  122. }},
  123. },
  124. "chooseColor": {
  125. manual: true, // done
  126. commands: cmdOpts{"ChooseColor": []string{
  127. "-initialcolor",
  128. "-parent",
  129. "-title",
  130. }},
  131. },
  132. "chooseDirectory": {
  133. manual: true, // done
  134. commands: cmdOpts{"ChooseDirectory": []string{
  135. //TODO? "-command",
  136. "-initialdir",
  137. "-message",
  138. "-mustexist",
  139. "-parent",
  140. "-title",
  141. }},
  142. },
  143. "clipboard": {
  144. manual: true, // done
  145. commands: cmdOpts{
  146. "ClipboardAppend": []string{
  147. "-displayof",
  148. "-format",
  149. "-type",
  150. },
  151. "ClipboardClear": []string{
  152. "-displayof",
  153. },
  154. "ClipboardGet": []string{
  155. "-displayof",
  156. "-type",
  157. },
  158. },
  159. },
  160. "colors": {manual: true}, // done
  161. "console": {ignore: true},
  162. "cursors": {manual: true}, // done
  163. "destroy": {manual: true}, // done
  164. "dialog": {ignore: true}, // ... largely deprecated by the tk_messageBox
  165. "event": {
  166. ignore: true, //MAYBE later
  167. commands: cmdOpts{"EventGenerate": []string{
  168. "-above",
  169. "-borderwidth",
  170. "-button",
  171. "-count",
  172. "-data",
  173. "-delta",
  174. "-detail",
  175. "-focus",
  176. "-height",
  177. "-keycode",
  178. "-keysym",
  179. "-mode",
  180. "-override",
  181. "-place",
  182. "-root",
  183. "-rootx",
  184. "-rooty",
  185. "-sendevent",
  186. "-serial",
  187. "-state",
  188. "-subwindow",
  189. "-time",
  190. "-warp",
  191. "-width",
  192. "-when",
  193. "-x",
  194. "-y",
  195. }},
  196. },
  197. "focus": {
  198. manual: true, // done
  199. commands: cmdOpts{"Focus": []string{
  200. "-displayof",
  201. "-force",
  202. "-lastfor",
  203. }},
  204. },
  205. "focusNext": {ignore: true}, //MAYBE later
  206. "font": {
  207. manual: true, // done
  208. commands: cmdOpts{
  209. "NewFont": []string{
  210. "-family",
  211. "-size",
  212. "-weight",
  213. "-slant",
  214. "-underline",
  215. "-overstrike",
  216. },
  217. "FontFamilies": []string{
  218. "-displayof",
  219. },
  220. // "FontMetrics": []string{
  221. // "-ascent",
  222. // "-descent",
  223. // "-linespace",
  224. // "-fixed",
  225. // },
  226. },
  227. },
  228. "fontchooser": {
  229. manual: true, // done
  230. commands: cmdOpts{"Fontchooser": []string{
  231. "-parent",
  232. "-title",
  233. "-font",
  234. "-command",
  235. // "-visible",
  236. }},
  237. },
  238. "getOpenFile": {
  239. manual: true, // done
  240. commands: cmdOpts{
  241. "GetOpenFile": []string{
  242. // "-command", // macOS only
  243. "-defaultextension",
  244. "-filetypes",
  245. "-initialdir",
  246. "-initialfile",
  247. // "-message", // macOS only
  248. "-multiple",
  249. "-parent",
  250. "-title",
  251. // "-typevariable",
  252. },
  253. "GetSaveFile": []string{
  254. // "-command",
  255. "-confirmoverwrite",
  256. "-defaultextension",
  257. "-filetypes",
  258. "-initialdir",
  259. "-initialfile",
  260. // "-message",
  261. "-parent",
  262. "-title",
  263. // "-typevariable",
  264. },
  265. },
  266. },
  267. "grab": {
  268. manual: true, //done
  269. },
  270. "grid": {
  271. manual: true, // done
  272. commands: cmdOpts{"Grid": []string{
  273. "-column",
  274. "-columnspan",
  275. "-in",
  276. "-ipadx",
  277. "-ipady",
  278. "-padx",
  279. "-pady",
  280. "-row",
  281. "-rowspan",
  282. "-sticky",
  283. }},
  284. },
  285. "image": {ignore: true}, //MAYBE later
  286. "keysyms": {ignore: true}, //MAYBE later
  287. "loadTk": {ignore: true},
  288. "lower": {manual: true}, // done
  289. "menu": {widget: true,
  290. commands: cmdOpts{
  291. "MenuWidget.AddCommand": menuOptions,
  292. "MenuWidget.AddCascade": menuOptions,
  293. "MenuWidget.AddSeparator": menuOptions,
  294. "MenuWidget.Invoke": nil,
  295. },
  296. },
  297. "messageBox": {
  298. manual: true, // done
  299. commands: cmdOpts{"MessageBox": []string{
  300. "-command",
  301. "-default",
  302. "-detail",
  303. "-icon",
  304. "-message",
  305. "-parent",
  306. "-title",
  307. "-type",
  308. }},
  309. },
  310. "nsimage": {ignore: true}, //TODO
  311. "option": {ignore: true},
  312. "optionMenu": {manual: true},
  313. "options": {ignore: true},
  314. "pack": {
  315. manual: true, // done
  316. commands: cmdOpts{"Pack": []string{
  317. "-after",
  318. "-anchor",
  319. "-before",
  320. "-expand",
  321. "-fill",
  322. "-in",
  323. "-ipadx",
  324. "-ipady",
  325. "-padx",
  326. "-pady",
  327. "-side",
  328. }},
  329. },
  330. "palette": {ignore: true}, //MAYBE later
  331. "photo": {
  332. manual: true, // done
  333. commands: cmdOpts{"NewPhoto": []string{
  334. "-data",
  335. "-format",
  336. "-file",
  337. "-gamma",
  338. "-height",
  339. "-metadata",
  340. "-palette",
  341. "-width",
  342. }},
  343. },
  344. "place": {
  345. manual: true, // done
  346. commands: cmdOpts{"Place": []string{
  347. "-anchor",
  348. "-bordermode",
  349. "-height",
  350. "-in",
  351. "-relheight",
  352. "-relwidth",
  353. "-relx",
  354. "-rely",
  355. "-width",
  356. "-x",
  357. "-y",
  358. }},
  359. },
  360. "popup": {manual: true}, // done
  361. "print": {manual: true}, //TODO
  362. "raise": {manual: true}, // done
  363. "selection": {
  364. ignore: true, //MAYBE later
  365. // commands: cmdOpts{
  366. // "SelectionClear": []string{
  367. // "-displayof",
  368. // "-selection",
  369. // },
  370. // "SelectionGet": []string{
  371. // "-displayof",
  372. // "-selection",
  373. // "-type",
  374. // },
  375. // "SelectionHandle": []string{
  376. // "-selection",
  377. // "-type",
  378. // "-format",
  379. // },
  380. // "SelectionOwn": []string{
  381. // "-command",
  382. // "-displayof",
  383. // "-selection",
  384. // },
  385. // },
  386. },
  387. "send": {ignore: true},
  388. "sysnotify": {manual: true}, //TODO
  389. "systray": {manual: true}, //TODO
  390. "text": {widget: true, //MAYBE more
  391. commands: cmdOpts{
  392. "TextWidget.TagConfigure": []string{
  393. "-background",
  394. "-bgstipple",
  395. "-borderwidth",
  396. "-elide",
  397. "-fgstipple",
  398. "-font",
  399. "-foreground",
  400. "-justify",
  401. "-lmargin1",
  402. "-lmargin2",
  403. "-lmargincolor",
  404. "-offset",
  405. "-overstrike",
  406. "-overstrikefg",
  407. "-relief",
  408. "-rmargin",
  409. "-rmargincolor",
  410. "-selectbackground",
  411. "-selectforeground",
  412. "-spacing1",
  413. "-spacing2",
  414. "-spacing3",
  415. "-tabs",
  416. "-tabstyle",
  417. "-underline",
  418. "-underlinefg",
  419. "-wrap",
  420. },
  421. },
  422. },
  423. "tk": {
  424. manual: true, //MAYBE later
  425. // commands: cmdOpts{
  426. // "Caret": []string{
  427. // "-x",
  428. // "-y",
  429. // "-height",
  430. // },
  431. // "Inactive": []string{
  432. // "-displayof",
  433. // "-reset",
  434. // },
  435. // "Scaling": []string{
  436. // "-displayof",
  437. // },
  438. // "UseInputMethods": []string{
  439. // "-displayof",
  440. // },
  441. // },
  442. },
  443. "tk_mac": {ignore: true},
  444. "tkerror": {ignore: true},
  445. "tkvars": {ignore: true},
  446. "tkwait": {manual: true}, // done
  447. "ttk_image": {ignore: true},
  448. "ttk_intro": {ignore: true},
  449. "ttk_style": {manual: true}, //TODO
  450. "ttk_vsapi": {ignore: true},
  451. "ttk_widget": {ignore: true},
  452. "winfo": {manual: true}, //TODO
  453. "wm": {manual: true}, //TODO
  454. }
  455. menuOptions = []string{
  456. "-activebackground",
  457. "-activeforeground",
  458. "-accelerator",
  459. "-background",
  460. "-bitmap",
  461. "-columnbreak",
  462. "-command",
  463. "-compound",
  464. "-font",
  465. "-foreground",
  466. "-hidemargin",
  467. "-image",
  468. "-indicatoron",
  469. "-label",
  470. "-menu",
  471. "-offvalue",
  472. "-onvalue",
  473. "-selectcolor",
  474. "-selectimage",
  475. "-state",
  476. "-underline",
  477. "-value",
  478. "-variable",
  479. }
  480. handlers = map[string]bool{
  481. "Command": true,
  482. "Invalidcommand": true,
  483. "Postcommand": true,
  484. "Tearoffcommand": true,
  485. "Validatecommand": true,
  486. "Xscrollcommand": true,
  487. "Yscrollcommand": true,
  488. }
  489. hideOpts = map[string]bool{
  490. "Data": true,
  491. "Font": true,
  492. "From": true,
  493. "To": true,
  494. "Type": true,
  495. "Values": true,
  496. }
  497. hideOptMethods = map[string]bool{
  498. "Textvariable": true,
  499. "Variable": true,
  500. }
  501. replaceOpt = map[string]string{
  502. "Button": "Btn",
  503. "Label": "Lbl",
  504. "Menu": "Mnu",
  505. "Message": "Msg",
  506. "Text": "Txt",
  507. }
  508. )
  509. func main() {
  510. hashes()
  511. makeTokenizer()
  512. w := bytes.NewBuffer(nil)
  513. w.WriteString(header)
  514. defer func() {
  515. if err := recover(); err != nil {
  516. fmt.Printf("PANIC: %v\n%s", err, debug.Stack())
  517. return
  518. }
  519. fmt.Printf("writing %v len=%v\n", ofn, len(w.Bytes()))
  520. if err := os.WriteFile(ofn, w.Bytes(), 0660); err != nil {
  521. panic(err)
  522. }
  523. }()
  524. nFilesDir := filepath.Join(xdg.ConfigHome, "ccgo", "v4", "libtk9.0", goos, goarch, libtk.Version, "doc")
  525. fmt.Printf("nFilesDir=%s\n", nFilesDir)
  526. util.MustShell(true, nil, "sh", "-c", fmt.Sprintf("rm -rf %s", tempDir))
  527. util.MustShell(true, nil, "mkdir", "-p", tempDir)
  528. fmt.Printf("nFilesDir=%s tempDir=%s\n", nFilesDir, tempDir)
  529. m, err := filepath.Glob(filepath.Join(nFilesDir, "*.n"))
  530. if err != nil {
  531. panic(err)
  532. }
  533. slices.Sort(m)
  534. t, err := ngrab.NewTask(io.Discard, m)
  535. if err != nil {
  536. panic(err)
  537. }
  538. if err := t.Main(); err != nil {
  539. panic(err)
  540. }
  541. htmlFiles := makeHTML(t)
  542. j := newJob(w, htmlFiles)
  543. j.main()
  544. }
  545. func makeTokenizer() {
  546. return // No more needed, only adds diff noise.
  547. args := []string{
  548. "-lexstring", "mlToken",
  549. "-pkg", "tk9_0",
  550. `([^$]|\\\$)*`, // Not TeX, incl. "\$"
  551. `\$([^$]|\\\$)*[^\\]\$`, // $TeX$, incl. $Te\$X$
  552. `\$\$([^$]|\\\$)*[^\\]\$\$`, // $$TeX$$, incl. $Te\$X$
  553. }
  554. var b bytes.Buffer
  555. rc, err := rec.Main(args, &b, io.Discard)
  556. if err != nil {
  557. panic(err)
  558. }
  559. if rc != 0 {
  560. panic(rc)
  561. }
  562. if err = os.WriteFile("mltoken.go", b.Bytes(), 0660); err != nil {
  563. panic(err)
  564. }
  565. }
  566. func makeHTML(t *ngrab.Task) (htmlFiles []string) {
  567. var documentFN string
  568. var w *bytes.Buffer
  569. var path []string
  570. for _, v := range t.Nodes {
  571. switch v.Type {
  572. case "document":
  573. if documentFN != "" {
  574. base := filepath.Base(documentFN)
  575. base = base[:len(base)-len(".n")] + ".html"
  576. w.WriteString("<html>\n")
  577. ofn := filepath.Join(tempDir, base)
  578. if err := os.WriteFile(ofn, w.Bytes(), 0660); err != nil {
  579. panic(err)
  580. }
  581. htmlFiles = append(htmlFiles, ofn)
  582. }
  583. documentFN = v.Text
  584. w = bytes.NewBuffer(nil)
  585. w.WriteString(head)
  586. case
  587. "BS",
  588. "CS",
  589. "DS",
  590. "RS",
  591. "SO":
  592. path = append(path, v.Type)
  593. fmt.Fprintf(w, "<div class=%q title=%q>\n", v.Type, strings.Join(path, "/"))
  594. case
  595. "BE",
  596. "CE",
  597. "DE",
  598. "RE",
  599. "SE":
  600. path = path[:len(path)-1]
  601. fmt.Fprintf(w, "</div>\n")
  602. case "so":
  603. fmt.Fprintf(w, "<p class=%q title=%q>%s</p>", "sso", strings.Join(append(path, "sso"), "/"), html.EscapeString(v.Text))
  604. default:
  605. fmt.Fprintf(w, "<p class=%q title=%q>%s</p>\n", v.Type, strings.Join(append(path, v.Type), "/"), html.EscapeString(v.Text))
  606. }
  607. }
  608. if documentFN != "" {
  609. base := filepath.Base(documentFN)
  610. base = base[:len(base)-len(".n")] + ".html"
  611. w.WriteString("<html>\n")
  612. ofn := filepath.Join(tempDir, base)
  613. if err := os.WriteFile(ofn, w.Bytes(), 0660); err != nil {
  614. panic(err)
  615. }
  616. htmlFiles = append(htmlFiles, ofn)
  617. }
  618. return htmlFiles
  619. }
  620. func walk(lvl int, n *html.Node, visitor func(n *html.Node) (dive bool)) {
  621. for ; n != nil; n = n.NextSibling {
  622. if visitor(n) {
  623. walk(lvl+1, n.FirstChild, visitor)
  624. }
  625. }
  626. }
  627. func class(n *html.Node) string {
  628. for _, v := range n.Attr {
  629. if v.Key == "class" {
  630. return v.Val
  631. }
  632. }
  633. return ""
  634. }
  635. type document struct {
  636. fn string
  637. root *html.Node
  638. sh []*html.Node
  639. shx map[string]*html.Node
  640. so []*html.Node
  641. }
  642. type option struct {
  643. docs []string
  644. goName string
  645. tclName string
  646. xref map[string]struct{}
  647. isWidgetOption bool
  648. }
  649. type job struct {
  650. documents []*document
  651. files []string
  652. o *bytes.Buffer
  653. optionsDoc *document
  654. optionsByTclName map[string]*option
  655. ttkOptionsDoc *document
  656. }
  657. func newJob(o *bytes.Buffer, files []string) *job {
  658. return &job{
  659. files: files,
  660. o: o,
  661. optionsByTclName: map[string]*option{},
  662. }
  663. }
  664. func (j *job) registerOption(tclName string, docs []string, xref string) (r *option) {
  665. switch {
  666. case strings.HasPrefix(xref, "[."):
  667. xref = "[Window." + xref[len("[."):]
  668. }
  669. r = j.optionsByTclName[tclName]
  670. if r == nil {
  671. r = &option{
  672. tclName: tclName,
  673. goName: tclOptName2GoName(tclName),
  674. docs: docs,
  675. xref: map[string]struct{}{},
  676. }
  677. j.optionsByTclName[tclName] = r
  678. }
  679. if xref != "" {
  680. r.xref[xref] = struct{}{}
  681. }
  682. return r
  683. }
  684. func (j *job) w(s string, args ...any) {
  685. fmt.Fprintf(j.o, s, args...)
  686. }
  687. func (j *job) analyze(file string) {
  688. f, err := os.Open(file)
  689. if err != nil {
  690. panic(err)
  691. }
  692. defer f.Close()
  693. n, err := html.Parse(f)
  694. if err != nil {
  695. panic(err)
  696. }
  697. d := &document{
  698. fn: file,
  699. root: n,
  700. shx: map[string]*html.Node{},
  701. }
  702. switch filepath.Base(file) {
  703. case "options.html":
  704. j.optionsDoc = d
  705. case "ttk_widget.html":
  706. j.ttkOptionsDoc = d
  707. }
  708. j.documents = append(j.documents, d)
  709. walk(0, n, func(n *html.Node) (dive bool) {
  710. switch n.Type {
  711. case html.ElementNode:
  712. switch n.Data {
  713. case "p":
  714. switch class(n) {
  715. case "SH":
  716. s := strings.TrimSpace(n.FirstChild.Data)
  717. if d.shx[s] != nil {
  718. panic("internal error")
  719. }
  720. d.shx[s] = n
  721. d.sh = append(d.sh, n)
  722. }
  723. case "div":
  724. switch class(n) {
  725. case "SO":
  726. d.so = append(d.so, n)
  727. }
  728. }
  729. }
  730. return true
  731. })
  732. }
  733. func base(fn string) (r string) {
  734. x := strings.Index(fn, "/")
  735. r = fn[x+1:]
  736. return r[:len(r)-len(".html")]
  737. }
  738. func (j *job) main() {
  739. for _, v := range j.files {
  740. j.analyze(v)
  741. }
  742. for _, v := range j.documents {
  743. var a []string
  744. for k := range v.shx {
  745. a = append(a, k)
  746. }
  747. slices.Sort(a)
  748. if len(v.so) != 0 {
  749. a = append(a, fmt.Sprintf("len(so)=%v", len(v.so)))
  750. }
  751. // fmt.Printf("%s %v\n", v.fn, a)
  752. base := base(v.fn)
  753. if nfo := pageInfos[base]; nfo == nil && len(v.so) != 0 {
  754. pageInfos[base] = &pageInfo{widget: true}
  755. }
  756. }
  757. var fail bool
  758. for _, fn := range j.files {
  759. base := base(fn)
  760. nfo := pageInfos[base]
  761. switch {
  762. case nfo == nil:
  763. fmt.Printf("%s: missing page info\n", base)
  764. case nfo.ignore, nfo.manual, nfo.widget:
  765. // ok
  766. default:
  767. fmt.Printf("%s: missing classificaiotn\n", base)
  768. fail = true
  769. }
  770. }
  771. if fail {
  772. return
  773. }
  774. for i, fn := range j.files {
  775. base := base(fn)
  776. nfo := pageInfos[base]
  777. switch {
  778. case nfo.widget:
  779. j.widget(fn, j.documents[i])
  780. if len(nfo.commands) != 0 {
  781. j.manual(fn, j.documents[i], nfo)
  782. }
  783. case nfo.manual:
  784. j.manual(fn, j.documents[i], nfo)
  785. }
  786. }
  787. j.stdOptions()
  788. j.ttkStdOptions()
  789. var a []string
  790. for k := range j.optionsByTclName {
  791. a = append(a, k)
  792. }
  793. slices.Sort(a)
  794. for _, k := range a {
  795. v := j.optionsByTclName[k]
  796. if hideOpts[v.goName] || hideOptMethods[v.goName] {
  797. continue
  798. }
  799. j.w("\n\n// %s option.", v.goName)
  800. if handlers[v.goName] {
  801. j.w("\n//\n// See also [Event handlers].")
  802. }
  803. if len(v.docs) != 0 {
  804. j.w("\n//\n// %s", strings.Join(v.docs, "\n//"))
  805. }
  806. if len(v.xref) != 0 {
  807. j.w("\n//\n// Known uses:")
  808. var a []string
  809. for k := range v.xref {
  810. a = append(a, k)
  811. }
  812. slices.Sort(a)
  813. for _, v := range a {
  814. j.w("\n// - %s", v)
  815. }
  816. }
  817. switch ok := handlers[v.goName]; {
  818. case ok:
  819. j.w("\n//\n// [Event handlers]: https://pkg.go.dev/modernc.org/tk9.0#hdr-Event_handlers")
  820. j.w("\nfunc %s(handler any) Opt {", v.goName)
  821. j.w("\n\treturn newEventHandler(%q, handler)", v.tclName)
  822. j.w("\n}")
  823. default:
  824. j.w("\nfunc %s(val any) Opt {", v.goName)
  825. j.w("\nreturn rawOption(fmt.Sprintf(`%s %%s`, optionString(val)))", v.tclName)
  826. j.w("\n}")
  827. if !v.isWidgetOption {
  828. break
  829. }
  830. j.w("\n\n// %s — Get the configured option value.", v.goName)
  831. if len(v.xref) != 0 {
  832. j.w("\n//\n// Known uses:")
  833. var a []string
  834. for k := range v.xref {
  835. a = append(a, k)
  836. }
  837. slices.Sort(a)
  838. for _, v := range a {
  839. if !strings.Contains(v, "command specific") {
  840. j.w("\n// - %s", v)
  841. }
  842. }
  843. }
  844. j.w("\nfunc (w *Window) %s() string {", v.goName)
  845. j.w("\nreturn evalErr(fmt.Sprintf(`%%s cget %s`, w))", v.tclName)
  846. j.w("\n}")
  847. }
  848. }
  849. }
  850. func (j *job) ttkStdOptions() {
  851. walk(0, j.ttkOptionsDoc.root, func(n *html.Node) (dive bool) {
  852. if nodeIs(n, "OP") {
  853. tclName := strings.TrimSpace(n.FirstChild.Data)
  854. a := strings.Fields(tclName)
  855. tclName = strings.TrimLeft(a[0], `"\`)
  856. var docs []string
  857. for n := n.NextSibling; n != nil; n = n.NextSibling {
  858. if nodeIs(n, "OP") || nodeIs(n, "SH") {
  859. break
  860. }
  861. if !nodeIs(n, "text") {
  862. continue
  863. }
  864. s := strings.TrimSpace(plain(n.FirstChild.Data))
  865. docs = append(docs, s)
  866. }
  867. o := j.registerOption(tclName, nil, "")
  868. o.isWidgetOption = true
  869. if len(o.docs) == 0 {
  870. o.docs = docs
  871. }
  872. }
  873. return true
  874. })
  875. }
  876. func (j *job) stdOptions() {
  877. walk(0, j.optionsDoc.root, func(n *html.Node) (dive bool) {
  878. if nodeIs(n, "OP") {
  879. tclName := strings.TrimSpace(n.FirstChild.Data)
  880. a := strings.Fields(tclName)
  881. tclName = strings.TrimLeft(a[0], `"\`)
  882. var docs []string
  883. for n := n.NextSibling; n != nil; n = n.NextSibling {
  884. if nodeIs(n, "OP") || nodeIs(n, "SH") {
  885. break
  886. }
  887. if !nodeIs(n, "text") {
  888. continue
  889. }
  890. s := strings.TrimSpace(plain(n.FirstChild.Data))
  891. docs = append(docs, s)
  892. }
  893. o := j.registerOption(tclName, nil, "")
  894. o.isWidgetOption = true
  895. o.docs = docs
  896. }
  897. return true
  898. })
  899. }
  900. func nodeIs(n *html.Node, pathSuffix string) bool {
  901. for _, v := range n.Attr {
  902. if v.Key == "title" && strings.HasSuffix(v.Val, pathSuffix) {
  903. return true
  904. }
  905. }
  906. return false
  907. }
  908. func (j *job) widgetStdOpts(xref string, doc *document) (r []string) {
  909. m := map[string]struct{}{}
  910. walk(0, doc.root, func(n *html.Node) (dive bool) {
  911. if nodeIs(n, "SO/text") {
  912. s := strings.TrimSpace(n.FirstChild.Data)
  913. a := strings.Fields(s)
  914. for _, v := range a {
  915. s := strings.TrimSpace(v)
  916. if s[0] == '\\' {
  917. s = s[1:]
  918. }
  919. j.registerOption(s, nil, xref).isWidgetOption = true
  920. s = tclOptName2GoName(s)
  921. m[s] = struct{}{}
  922. }
  923. }
  924. return true
  925. })
  926. for k := range m {
  927. r = append(r, k)
  928. }
  929. slices.Sort(r)
  930. return r
  931. }
  932. func (j *job) widgetStylingOptions(doc *document) (r []string) {
  933. for _, n := range doc.sh {
  934. if ch := n.FirstChild; ch != nil && strings.Contains(ch.Data, "STYLING OPTIONS") {
  935. for n := n.NextSibling; n != nil; n = n.NextSibling {
  936. if hasClass(n, "SH") {
  937. break
  938. }
  939. if hasClass(n, "PP") {
  940. r = append(r, "")
  941. continue
  942. }
  943. if ch := n.FirstChild; ch != nil {
  944. s := strings.TrimSpace(plain(ch.Data))
  945. if strings.HasPrefix(s, "-") {
  946. s = s[1:]
  947. a := strings.Fields(s)
  948. s = fmt.Sprintf(" - [%s] %s", capitalize(a[0]), strings.Join(a[1:], " "))
  949. for len(r) != 0 && r[len(r)-1] == "" {
  950. r = r[:len(r)-1]
  951. }
  952. }
  953. r = append(r, s)
  954. }
  955. }
  956. break
  957. }
  958. }
  959. return r
  960. }
  961. func capitalize(s string) string {
  962. return strings.ToUpper(string(s[0])) + s[1:]
  963. }
  964. func (j *job) widgetStdStyles(doc *document) (r []string) {
  965. for _, n := range doc.sh {
  966. if ch := n.FirstChild; ch != nil && strings.Contains(ch.Data, "STANDARD STYLES") {
  967. for n := n.NextSibling; n != nil; n = n.NextSibling {
  968. if hasClass(n, "SH") {
  969. break
  970. }
  971. if hasClass(n, "PP") {
  972. r = append(r, "")
  973. continue
  974. }
  975. if ch := n.FirstChild; ch != nil {
  976. r = append(r, strings.TrimSpace(plain(ch.Data)))
  977. }
  978. }
  979. break
  980. }
  981. }
  982. return r
  983. }
  984. func (j *job) widgetSpecificOpts(xref string, doc *document) (r []*option) {
  985. ok := false
  986. walk(0, doc.root, func(n *html.Node) (dive bool) {
  987. if nodeIs(n, "SH") {
  988. s := strings.ToLower(n.FirstChild.Data)
  989. // Reject options within section headings like "TAB OPTIONS".
  990. ok = strings.Contains(s, "widget") && strings.Contains(s, "specific") && strings.Contains(s, "options")
  991. }
  992. if ok && nodeIs(n, "OP") {
  993. tclName := strings.TrimSpace(n.FirstChild.Data)
  994. a := strings.Fields(tclName)
  995. tclName = strings.TrimLeft(a[0], `"\`)
  996. var docs []string
  997. for n := n.NextSibling; n != nil; n = n.NextSibling {
  998. if nodeIs(n, "OP") || nodeIs(n, "SH") {
  999. break
  1000. }
  1001. if !nodeIs(n, "text") {
  1002. continue
  1003. }
  1004. s := strings.TrimSpace(plain(n.FirstChild.Data))
  1005. docs = append(docs, s)
  1006. }
  1007. o := j.registerOption(tclName, nil, xref)
  1008. o.isWidgetOption = true
  1009. p := *o
  1010. p.docs = docs
  1011. r = append(r, &p)
  1012. }
  1013. return true
  1014. })
  1015. return r
  1016. }
  1017. func tclOptName2GoName(s string) (r string) {
  1018. s = strings.TrimSpace(s)
  1019. if strings.HasPrefix(s, "\\") {
  1020. s = s[1:]
  1021. }
  1022. if strings.HasPrefix(s, "-") {
  1023. s = s[1:]
  1024. }
  1025. r = export(s)
  1026. if x := replaceOpt[r]; x != "" {
  1027. r = x
  1028. }
  1029. return r
  1030. }
  1031. func (j *job) pageLink(page string) {
  1032. if page == "" {
  1033. return
  1034. }
  1035. page = page[:len(page)-len(".html")]
  1036. j.w("\n//\n// More information might be available at the [Tcl/Tk %s] page.", page)
  1037. j.w("\n//\n// [Tcl/Tk %s]: https://www.tcl-lang.org/man/tcl9.0/TkCmd/%[1]s.html", page)
  1038. }
  1039. func (j *job) manual(fn string, doc *document, nfo *pageInfo) {
  1040. for nm, options := range nfo.commands {
  1041. for _, option := range options {
  1042. j.registerOption(option, nil, fmt.Sprintf("[%s] (command specific)", nm))
  1043. }
  1044. }
  1045. }
  1046. func (j *job) widget(fn string, doc *document) {
  1047. shNameNode := doc.shx["NAME"]
  1048. txtNode := shNameNode.NextSibling.NextSibling.FirstChild
  1049. txt := txtNode.Data
  1050. txt = strings.TrimSpace(txt[strings.Index(txt, "-")+1:])
  1051. txt = strings.ToUpper(txt[:1]) + txt[1:]
  1052. base := base(fn)
  1053. gnm := tclName2GoName(base)
  1054. doc0 := []string{fmt.Sprintf("\n\n// %s — %s", gnm, txt)}
  1055. doc0 = append(doc0, j.description(doc.shx["DESCRIPTION"])...)
  1056. j.w("%s", strings.Join(doc0, "\n// "))
  1057. j.w("\n//\n// Use [Window.%s] to create a %[1]s with a particular parent.", gnm)
  1058. j.pageLink(fn[len(tempDir)+1:])
  1059. if opts := j.widgetStdOpts(fmt.Sprintf("[%s]", gnm), doc); len(opts) != 0 {
  1060. j.w("\n//\n// # Standard options\n//")
  1061. for _, v := range opts {
  1062. if hideOpts[v] {
  1063. continue
  1064. }
  1065. j.w("\n// - [%s]", v)
  1066. }
  1067. }
  1068. if sos := j.widgetSpecificOpts(fmt.Sprintf("[%s] (widget specific)", gnm), doc); len(sos) != 0 {
  1069. j.w("\n//\n// # Widget specific options")
  1070. for _, v := range sos {
  1071. j.w("\n//\n// [%s]", v.goName)
  1072. j.w("\n//\n// %s", strings.Join(v.docs, "\n// "))
  1073. }
  1074. }
  1075. if ss := j.widgetStdStyles(doc); len(ss) != 0 {
  1076. j.w("\n//\n// # Standard styles\n//")
  1077. for _, v := range ss {
  1078. j.w("\n// %s", v)
  1079. }
  1080. }
  1081. if sos := j.widgetStylingOptions(doc); len(sos) != 0 {
  1082. j.w("\n//\n// # Styling options\n//")
  1083. for _, v := range sos {
  1084. j.w("\n// %s", v)
  1085. }
  1086. }
  1087. j.w("\nfunc %s(options ...Opt) *%[1]sWidget {", gnm)
  1088. j.w("\nreturn App.%s(options...)", gnm)
  1089. j.w("\n}")
  1090. j.w("%s", doc0[0])
  1091. j.w("\n//\n// The resulting [Window] is a child of 'w'")
  1092. j.w("\n//\n// For details please see [%s]", gnm)
  1093. j.w("\nfunc (w *Window) %s(options ...Opt) *%[1]sWidget {", gnm)
  1094. cmd := strings.Replace(base, "ttk_", "ttk::", 1)
  1095. j.w("\nreturn &%sWidget{w.newChild(%q, options...)}", gnm, cmd)
  1096. j.w("\n}")
  1097. j.w("\n\n// %sWidget represents the Tcl/Tk %s widget/window", gnm, base)
  1098. j.w("\ntype %sWidget struct {", gnm)
  1099. j.w("\n*Window")
  1100. j.w("\n}")
  1101. }
  1102. func (j *job) description(n *html.Node) (r []string) {
  1103. if n == nil {
  1104. return nil
  1105. }
  1106. r = append(r, "", "# Description\n//")
  1107. stop := false
  1108. walk(0, n.NextSibling, func(n *html.Node) (dive bool) {
  1109. if stop {
  1110. return false
  1111. }
  1112. if hasClass(n, "SH") {
  1113. stop = true
  1114. return false
  1115. }
  1116. if hasClass(n, "PP") {
  1117. r = append(r, "")
  1118. return false
  1119. }
  1120. switch n.Type {
  1121. case html.TextNode:
  1122. if s := strings.TrimSpace(plain(n.Data)); s != "" &&
  1123. !strings.HasPrefix(s, `\"`) &&
  1124. !strings.HasPrefix(s, "-") {
  1125. r = append(r, s)
  1126. }
  1127. }
  1128. return true
  1129. })
  1130. return r
  1131. }
  1132. func hasClass(n *html.Node, cls string) bool {
  1133. for _, v := range n.Attr {
  1134. if v.Key == "class" && v.Val == cls {
  1135. return true
  1136. }
  1137. }
  1138. return false
  1139. }
  1140. func plain(s string) (r string) {
  1141. r = s
  1142. r = strings.ReplaceAll(r, "\\-", "-")
  1143. r = strings.ReplaceAll(r, "\\fB", "")
  1144. r = strings.ReplaceAll(r, "\\fI", "")
  1145. r = strings.ReplaceAll(r, "\\fP", "")
  1146. r = strings.ReplaceAll(r, "\\fR", "")
  1147. return r
  1148. }
  1149. func tclName2GoName(s string) string {
  1150. switch {
  1151. case strings.HasPrefix(s, "ttk_"):
  1152. return "T" + export(s[len("ttk_"):])
  1153. default:
  1154. return export(s)
  1155. }
  1156. }
  1157. func export(s string) (r string) {
  1158. a := strings.Split(s, "_")
  1159. for i, v := range a {
  1160. a[i] = strings.ToUpper(v[:1]) + v[1:]
  1161. }
  1162. return strings.Join(a, "")
  1163. }
  1164. // origin returns caller's short position, skipping skip frames.
  1165. //
  1166. //lint:ignore U1000 debug helper
  1167. func origin(skip int) string {
  1168. pc, fn, fl, _ := runtime.Caller(skip)
  1169. f := runtime.FuncForPC(pc)
  1170. var fns string
  1171. if f != nil {
  1172. fns = f.Name()
  1173. if x := strings.LastIndex(fns, "."); x > 0 {
  1174. fns = fns[x+1:]
  1175. }
  1176. if strings.HasPrefix(fns, "func") {
  1177. num := true
  1178. for _, c := range fns[len("func"):] {
  1179. if c < '0' || c > '9' {
  1180. num = false
  1181. break
  1182. }
  1183. }
  1184. if num {
  1185. return origin(skip + 2)
  1186. }
  1187. }
  1188. }
  1189. return fmt.Sprintf("%s:%d:%s", filepath.Base(fn), fl, fns)
  1190. }
  1191. // todo prints and return caller's position and an optional message tagged with TODO. Output goes to stderr.
  1192. //
  1193. //lint:ignore U1000 debug helper
  1194. func todo(s string, args ...interface{}) string {
  1195. switch {
  1196. case s == "":
  1197. s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...)
  1198. default:
  1199. s = fmt.Sprintf(s, args...)
  1200. }
  1201. r := fmt.Sprintf("%s\n\tTODO %s", origin(2), s)
  1202. // fmt.Fprintf(os.Stderr, "%s\n", r)
  1203. // os.Stdout.Sync()
  1204. return r
  1205. }
  1206. // trc prints and return caller's position and an optional message tagged with TRC. Output goes to stderr.
  1207. //
  1208. //lint:ignore U1000 debug helper
  1209. func trc(s string, args ...interface{}) string {
  1210. switch {
  1211. case s == "":
  1212. s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...)
  1213. default:
  1214. s = fmt.Sprintf(s, args...)
  1215. }
  1216. r := fmt.Sprintf("%s: TRC %s", origin(2), s)
  1217. fmt.Fprintf(os.Stderr, "%s\n", r)
  1218. os.Stderr.Sync()
  1219. return r
  1220. }
  1221. func hashes() {
  1222. filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
  1223. if err != nil {
  1224. panic(err)
  1225. }
  1226. base := filepath.Base(path)
  1227. if d.IsDir() || !strings.HasSuffix(base, ".zip") {
  1228. return nil
  1229. }
  1230. r, err := zip.OpenReader(path)
  1231. if err != nil {
  1232. panic(err)
  1233. }
  1234. defer r.Close()
  1235. fmt.Printf("// %s\n", path)
  1236. for _, f := range r.File {
  1237. // fmt.Printf("Contents of %s:\n", f.Name)
  1238. r, err := f.Open()
  1239. if err != nil {
  1240. panic(err)
  1241. }
  1242. h := sha256.New()
  1243. _, err = io.Copy(h, r)
  1244. r.Close()
  1245. if err != nil {
  1246. panic(err)
  1247. }
  1248. fmt.Printf("%q: \"%0x\",\n", f.Name, h.Sum(nil))
  1249. }
  1250. return nil
  1251. })
  1252. fmt.Printf("// other\n")
  1253. fmt.Printf("%q: \"%0x\",\n", "tcl_library.zip", sha256.Sum256([]byte(tcllib.Zip)))
  1254. fmt.Printf("%q: \"%0x\",\n", "tk_library.zip", sha256.Sum256([]byte(tklib.Zip)))
  1255. }