dochack.nim 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. import karax
  2. import fuzzysearch
  3. proc findNodeWith(x: Element; tag, content: cstring): Element =
  4. if x.nodeName == tag and x.textContent == content:
  5. return x
  6. for i in 0..<x.len:
  7. let it = x[i]
  8. let y = findNodeWith(it, tag, content)
  9. if y != nil: return y
  10. return nil
  11. proc clone(e: Element): Element {.importcpp: "#.cloneNode(true)", nodecl.}
  12. proc parent(e: Element): Element {.importcpp: "#.parentNode", nodecl.}
  13. proc markElement(x: Element) {.importcpp: "#.__karaxMarker__ = true", nodecl.}
  14. proc isMarked(x: Element): bool {.
  15. importcpp: "#.hasOwnProperty('__karaxMarker__')", nodecl.}
  16. proc title(x: Element): cstring {.importcpp: "#.title", nodecl.}
  17. proc sort[T](x: var openArray[T]; cmp: proc(a, b: T): int) {.importcpp:
  18. "#.sort(#)", nodecl.}
  19. proc parentWith(x: Element; tag: cstring): Element =
  20. result = x.parent
  21. while result.nodeName != tag:
  22. result = result.parent
  23. if result == nil: return nil
  24. proc extractItems(x: Element; items: var seq[Element]) =
  25. if x == nil: return
  26. if x.nodeName == cstring"A":
  27. items.add x
  28. else:
  29. for i in 0..<x.len:
  30. let it = x[i]
  31. extractItems(it, items)
  32. # HTML trees are so shitty we transform the TOC into a decent
  33. # data-structure instead and work on that.
  34. type
  35. TocEntry = ref object
  36. heading: Element
  37. kids: seq[TocEntry]
  38. sortId: int
  39. doSort: bool
  40. proc extractItems(x: TocEntry; heading: cstring;
  41. items: var seq[Element]) =
  42. if x == nil: return
  43. if x.heading != nil and x.heading.textContent == heading:
  44. for i in 0..<x.kids.len:
  45. items.add x.kids[i].heading
  46. else:
  47. for i in 0..<x.kids.len:
  48. let it = x.kids[i]
  49. extractItems(it, heading, items)
  50. proc toHtml(x: TocEntry; isRoot=false): Element =
  51. if x == nil: return nil
  52. if x.kids.len == 0:
  53. if x.heading == nil: return nil
  54. return x.heading.clone
  55. result = tree("DIV")
  56. if x.heading != nil and not isMarked(x.heading):
  57. result.add x.heading.clone
  58. let ul = tree("UL")
  59. if isRoot:
  60. ul.setClass("simple simple-toc")
  61. else:
  62. ul.setClass("simple")
  63. if x.dosort:
  64. x.kids.sort(proc(a, b: TocEntry): int =
  65. if a.heading != nil and b.heading != nil:
  66. let x = a.heading.textContent
  67. let y = b.heading.textContent
  68. if x < y: return -1
  69. if x > y: return 1
  70. return 0
  71. else:
  72. # ensure sorting is stable:
  73. return a.sortId - b.sortId
  74. )
  75. for k in x.kids:
  76. let y = toHtml(k)
  77. if y != nil:
  78. ul.add tree("LI", y)
  79. if ul.len != 0: result.add ul
  80. if result.len == 0: result = nil
  81. #proc containsWord(a, b: cstring): bool {.asmNoStackFrame.} =
  82. #{.emit: """
  83. #var escaped = `b`.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  84. #return new RegExp("\\b" + escaped + "\\b").test(`a`);
  85. #""".}
  86. proc isWhitespace(text: cstring): bool {.asmNoStackFrame.} =
  87. {.emit: """
  88. return !/[^\s]/.test(`text`);
  89. """.}
  90. proc isWhitespace(x: Element): bool =
  91. x.nodeName == cstring"#text" and x.textContent.isWhitespace or
  92. x.nodeName == cstring"#comment"
  93. proc toToc(x: Element; father: TocEntry) =
  94. if x.nodeName == cstring"UL":
  95. let f = TocEntry(heading: nil, kids: @[], sortId: father.kids.len)
  96. var i = 0
  97. while i < x.len:
  98. var nxt = i+1
  99. while nxt < x.len and x[nxt].isWhitespace:
  100. inc nxt
  101. if nxt < x.len and x[i].nodeName == cstring"LI" and x[i].len == 1 and
  102. x[nxt].nodeName == cstring"UL":
  103. let e = TocEntry(heading: x[i][0], kids: @[], sortId: f.kids.len)
  104. let it = x[nxt]
  105. for j in 0..<it.len:
  106. toToc(it[j], e)
  107. f.kids.add e
  108. i = nxt+1
  109. else:
  110. toToc(x[i], f)
  111. inc i
  112. father.kids.add f
  113. elif isWhitespace(x):
  114. discard
  115. elif x.nodeName == cstring"LI":
  116. var idx: seq[int] = @[]
  117. for i in 0 ..< x.len:
  118. if not x[i].isWhitespace: idx.add i
  119. if idx.len == 2 and x[idx[1]].nodeName == cstring"UL":
  120. let e = TocEntry(heading: x[idx[0]], kids: @[],
  121. sortId: father.kids.len)
  122. let it = x[idx[1]]
  123. for j in 0..<it.len:
  124. toToc(it[j], e)
  125. father.kids.add e
  126. else:
  127. for i in 0..<x.len:
  128. toToc(x[i], father)
  129. else:
  130. father.kids.add TocEntry(heading: x, kids: @[],
  131. sortId: father.kids.len)
  132. proc tocul(x: Element): Element =
  133. # x is a 'ul' element
  134. result = tree("UL")
  135. for i in 0..<x.len:
  136. let it = x[i]
  137. if it.nodeName == cstring"LI":
  138. result.add it.clone
  139. elif it.nodeName == cstring"UL":
  140. result.add tocul(it)
  141. proc getSection(toc: Element; name: cstring): Element =
  142. let sec = findNodeWith(toc, "A", name)
  143. if sec != nil:
  144. result = sec.parentWith("LI")
  145. proc uncovered(x: TocEntry): TocEntry =
  146. if x.kids.len == 0 and x.heading != nil:
  147. return if not isMarked(x.heading): x else: nil
  148. result = TocEntry(heading: x.heading, kids: @[], sortId: x.sortId,
  149. doSort: x.doSort)
  150. for i in 0..<x.kids.len:
  151. let y = uncovered(x.kids[i])
  152. if y != nil: result.kids.add y
  153. if result.kids.len == 0: result = nil
  154. proc mergeTocs(orig, news: TocEntry): TocEntry =
  155. result = uncovered(orig)
  156. if result == nil:
  157. result = news
  158. else:
  159. for i in 0..<news.kids.len:
  160. result.kids.add news.kids[i]
  161. proc buildToc(orig: TocEntry; types, procs: seq[Element]): TocEntry =
  162. var newStuff = TocEntry(heading: nil, kids: @[], doSort: true)
  163. for t in types:
  164. let c = TocEntry(heading: t.clone, kids: @[], doSort: true)
  165. t.markElement()
  166. for p in procs:
  167. if not isMarked(p):
  168. let xx = karax.getElementsByClass(p.parent, cstring"attachedType")
  169. if xx.len == 1 and xx[0].textContent == t.textContent:
  170. #kout(cstring"found ", p.nodeName)
  171. let q = tree("A", text(p.title))
  172. q.setAttr("href", p.getAttribute("href"))
  173. c.kids.add TocEntry(heading: q, kids: @[])
  174. p.markElement()
  175. newStuff.kids.add c
  176. result = mergeTocs(orig, newStuff)
  177. var alternative: Element
  178. proc togglevis(d: Element) =
  179. asm """
  180. if (`d`.style.display == 'none')
  181. `d`.style.display = 'inline';
  182. else
  183. `d`.style.display = 'none';
  184. """
  185. proc groupBy*(value: cstring) {.exportc.} =
  186. let toc = getElementById("toc-list")
  187. if alternative.isNil:
  188. var tt = TocEntry(heading: nil, kids: @[])
  189. toToc(toc, tt)
  190. tt = tt.kids[0]
  191. var types: seq[Element] = @[]
  192. var procs: seq[Element] = @[]
  193. extractItems(tt, "Types", types)
  194. extractItems(tt, "Procs", procs)
  195. extractItems(tt, "Converters", procs)
  196. extractItems(tt, "Methods", procs)
  197. extractItems(tt, "Templates", procs)
  198. extractItems(tt, "Macros", procs)
  199. extractItems(tt, "Iterators", procs)
  200. let ntoc = buildToc(tt, types, procs)
  201. let x = toHtml(ntoc, isRoot=true)
  202. alternative = tree("DIV", x)
  203. if value == cstring"type":
  204. replaceById("tocRoot", alternative)
  205. else:
  206. replaceById("tocRoot", tree("DIV"))
  207. togglevis(getElementById"toc-list")
  208. var
  209. db: seq[Element]
  210. contents: seq[cstring]
  211. template normalize(x: cstring): cstring = x.toLower.replace("_", "")
  212. proc dosearch(value: cstring): Element =
  213. if db.len == 0:
  214. var stuff: Element
  215. {.emit: """
  216. var request = new XMLHttpRequest();
  217. request.open("GET", "theindex.html", false);
  218. request.send(null);
  219. var doc = document.implementation.createHTMLDocument("theindex");
  220. doc.documentElement.innerHTML = request.responseText;
  221. //parser=new DOMParser();
  222. //doc=parser.parseFromString("<html></html>", "text/html");
  223. `stuff` = doc.documentElement;
  224. """.}
  225. db = stuff.getElementsByClass"reference"
  226. contents = @[]
  227. for ahref in db:
  228. contents.add ahref.getAttribute("data-doc-search-tag")
  229. let ul = tree("UL")
  230. result = tree("DIV")
  231. result.setClass"search_results"
  232. var matches: seq[(Element, int)] = @[]
  233. for i in 0..<db.len:
  234. let c = contents[i]
  235. if c == cstring"Examples" or c == cstring"PEG construction":
  236. # Some manual exclusions.
  237. # Ideally these should be fixed in the index to be more
  238. # descriptive of what they are.
  239. continue
  240. let (score, matched) = fuzzymatch(value, c)
  241. if matched:
  242. matches.add((db[i], score))
  243. matches.sort do (a, b: auto) -> int:
  244. b[1] - a[1]
  245. for i in 0 ..< min(matches.len, 19):
  246. matches[i][0].innerHTML = matches[i][0].getAttribute("data-doc-search-tag")
  247. ul.add(tree("LI", matches[i][0]))
  248. if ul.len == 0:
  249. result.add tree("B", text"no search results")
  250. else:
  251. result.add tree("B", text"search results")
  252. result.add ul
  253. var oldtoc: Element
  254. var timer: Timeout
  255. proc search*() {.exportc.} =
  256. proc wrapper() =
  257. let elem = getElementById("searchInput")
  258. let value = elem.value
  259. if value.len != 0:
  260. if oldtoc.isNil:
  261. oldtoc = getElementById("tocRoot")
  262. let results = dosearch(value)
  263. replaceById("tocRoot", results)
  264. elif not oldtoc.isNil:
  265. replaceById("tocRoot", oldtoc)
  266. if timer != nil: clearTimeout(timer)
  267. timer = setTimeout(wrapper, 400)