docs.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  1. const fs = require('fs')
  2. const path = require('path')
  3. const format = require('string-format')
  4. const { DocsWriter } = require('../docswriter')
  5. const { TLObject, Usability } = require('../parsers')
  6. const { snakeToCamelCase } = require('../utils')
  7. const CORE_TYPES = new Set([
  8. 'int',
  9. 'long',
  10. 'int128',
  11. 'int256',
  12. 'double',
  13. 'vector',
  14. 'string',
  15. 'bool',
  16. 'true',
  17. 'bytes',
  18. 'date',
  19. ])
  20. const mkdir = (path) => fs.mkdirSync(path, { recursive: true })
  21. const titleCase = (text) =>
  22. text
  23. .toLowerCase()
  24. .split(/(\W)/)
  25. .map((word) => `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`)
  26. .join('')
  27. /**
  28. * ``ClassName -> class_name.html``.
  29. */
  30. const getFileName = (tlobject) => {
  31. const name = tlobject instanceof TLObject ? tlobject.name : tlobject
  32. // Courtesy of http://stackoverflow.com/a/1176023/4759433
  33. const s1 = name.replace(/(.)([A-Z][a-z]+)/, '$1_$2')
  34. const result = s1.replace(/([a-z0-9])([A-Z])/, '$1_$2').toLowerCase()
  35. return `${result}.html`
  36. }
  37. /**
  38. * ``TLObject -> const { ... } = require(...);``.
  39. */
  40. const getImportCode = (tlobject) => {
  41. const kind = tlobject.isFunction ? 'functions' : 'types'
  42. const ns = tlobject.namespace ? `/${tlobject.namespace}` : ''
  43. return `const { ${tlobject.className} } = require('gramjs/tl/${kind}${ns}');`
  44. }
  45. /**
  46. * Returns the path for the given TLObject.
  47. */
  48. const getPathFor = (tlobject) => {
  49. let outDir = tlobject.isFunction ? 'methods' : 'constructors'
  50. if (tlobject.namespace) {
  51. outDir += `/${tlobject.namespace}`
  52. }
  53. return `${outDir}/${getFileName(tlobject)}`
  54. }
  55. /**
  56. * Similar to `getPathFor` but for only type names.
  57. */
  58. const getPathForType = (type) => {
  59. if (CORE_TYPES.has(type.toLowerCase())) {
  60. return `index.html#${type.toLowerCase()}`
  61. } else if (type.includes('.')) {
  62. const [namespace, name] = type.split('.')
  63. return `types/${namespace}/${getFileName(name)}`
  64. } else {
  65. return `types/${getFileName(type)}`
  66. }
  67. }
  68. /**
  69. * Finds the <title> for the given HTML file, or (Unknown).
  70. */
  71. const findTitle = (htmlFile) => {
  72. const f = fs.readFileSync(htmlFile, { encoding: 'utf-8' })
  73. for (const line of f.split('\n')) {
  74. if (line.includes('<title>')) {
  75. // +7 to skip '<title>'.length
  76. return line.slice(
  77. line.indexOf('<title>') + 7,
  78. line.indexOf('</title>')
  79. )
  80. }
  81. }
  82. return '(Unknown)'
  83. }
  84. /**
  85. * Builds the menu used for the current ``DocumentWriter``.
  86. */
  87. const buildMenu = (docs) => {
  88. const paths = []
  89. let current = docs.filename
  90. while (current !== '.') {
  91. current = path.join(current, '..')
  92. paths.push(current)
  93. }
  94. for (const path_ of paths.reverse()) {
  95. const name = path.parse(path_).name
  96. docs.addMenu(
  97. name === '.' ? 'API' : titleCase(name),
  98. `${path_}/index.html`
  99. )
  100. }
  101. if (path.parse(docs.filename).name !== 'index') {
  102. docs.addMenu(docs.title, docs.filename)
  103. }
  104. docs.endMenu()
  105. }
  106. /**
  107. * Generates the index file for the specified folder
  108. */
  109. const generateIndex = (folder, paths, botsIndex, botsIndexPaths) => {
  110. botsIndexPaths = botsIndexPaths || []
  111. // Determine the namespaces listed here (as sub folders)
  112. // and the files (.html files) that we should link to
  113. const namespaces = []
  114. const files = []
  115. const INDEX = 'index.html'
  116. const BOT_INDEX = 'botindex.html'
  117. for (const item of botsIndexPaths.length ? botsIndexPaths : fs.readdirSync(folder)) {
  118. const fullPath = botsIndexPaths.length ? item : `${folder}/${item}`
  119. if (fs.statSync(fullPath).isDirectory()) {
  120. namespaces.push(fullPath)
  121. } else if (![INDEX, BOT_INDEX].includes(item)) {
  122. files.push(fullPath)
  123. }
  124. }
  125. // Now that everything is setup, write the index.html file
  126. const filename = `${folder}/${botsIndex ? BOT_INDEX : INDEX}`
  127. const docs = new DocsWriter(filename, getPathForType).open()
  128. // Title should be the current folder name
  129. docs.writeHead(
  130. titleCase(folder.replace(new RegExp(`\\${path.sep}`, 'g'), '/')),
  131. paths.css,
  132. paths.defaultCss
  133. )
  134. docs.setMenuSeparator(paths.arrow)
  135. buildMenu(docs)
  136. docs.writeTitle(
  137. titleCase(
  138. path
  139. .join(filename, '..')
  140. .replace(new RegExp(`\\${path.sep}`, 'g'), '/')
  141. )
  142. )
  143. if (botsIndex) {
  144. docs.writeText(
  145. `These are the methods that you may be able to use as a bot. Click <a href="${INDEX}">here</a> to view them all.`
  146. )
  147. } else {
  148. docs.writeText(
  149. `Click <a href="${BOT_INDEX}">here</a> to view the methods that you can use as a bot.`
  150. )
  151. }
  152. if (namespaces.length) {
  153. docs.writeTitle('Namespaces', 3)
  154. docs.beginTable(4)
  155. namespaces.sort()
  156. for (const namespace of namespaces) {
  157. // For every namespace, also write the index of it
  158. const namespacePaths = []
  159. if (botsIndex) {
  160. for (const item of botsIndexPaths) {
  161. if (path.relative(item, '..') === namespace) {
  162. namespacePaths.push(item)
  163. }
  164. }
  165. }
  166. generateIndex(namespace, paths, botsIndex, namespacePaths)
  167. docs.addRow(
  168. titleCase(path.parse(namespace).name),
  169. `${namespace}/${botsIndex ? BOT_INDEX : INDEX}`
  170. )
  171. }
  172. docs.endTable()
  173. }
  174. docs.writeTitle('Available items')
  175. docs.beginTable(2)
  176. files
  177. .sort((x, y) => x.localeCompare(y))
  178. .forEach((file) => {
  179. docs.addRow(findTitle(file), file)
  180. })
  181. docs.endTable()
  182. docs.endBody()
  183. docs.close()
  184. }
  185. /**
  186. * Generates a proper description for the given argument.
  187. */
  188. const getDescription = (arg) => {
  189. const desc = []
  190. let otherwise = false
  191. if (arg.canBeInferred) {
  192. desc.push('If left unspecified, it will be inferred automatically.')
  193. otherwise = true
  194. } else if (arg.isFlag) {
  195. desc.push(
  196. 'This argument defaults to <code>null</code> and can be omitted.'
  197. )
  198. otherwise = true
  199. }
  200. if (
  201. [
  202. 'InputPeer',
  203. 'InputUser',
  204. 'InputChannel',
  205. 'InputNotifyPeer',
  206. 'InputDialogPeer',
  207. ].includes(arg.type)
  208. ) {
  209. desc.push(
  210. 'Anything entity-like will work if the library can find its <code>Input</code> version (e.g., usernames, <code>Peer</code>, <code>User</code> or <code>Channel</code> objects, etc.).'
  211. )
  212. }
  213. if (arg.isVector) {
  214. if (arg.isGeneric) {
  215. desc.push('A list of other Requests must be supplied.')
  216. } else {
  217. desc.push('A list must be supplied.')
  218. }
  219. } else if (arg.isGeneric) {
  220. desc.push('A different Request must be supplied for this argument.')
  221. } else {
  222. otherwise = false // Always reset to false if no other text is added
  223. }
  224. if (otherwise) {
  225. desc.splice(1, 0, 'Otherwise,')
  226. desc[desc.length - 1] =
  227. desc[desc.length - 1].slice(0, -1).toLowerCase() +
  228. desc[desc.length - 1].slice(1)
  229. }
  230. return desc
  231. .join(' ')
  232. .replace(
  233. /list/g,
  234. '<span class="tooltip" title="Any iterable that supports .length will work too">list</span>'
  235. )
  236. }
  237. /**
  238. * Copies the src file into dst applying the replacements dict
  239. */
  240. const copyReplace = (src, dst, replacements) => {
  241. const infile = fs.readFileSync(src, { encoding: 'utf-8' })
  242. fs.writeFileSync(
  243. dst,
  244. format(infile, replacements)
  245. // infile.replace(
  246. // new RegExp(
  247. // Object.keys(replacements)
  248. // .map(k => escapeRegex(k))
  249. // .join('|')
  250. // ),
  251. // m => replacements[m].toString()
  252. // )
  253. )
  254. }
  255. /**
  256. * Generates the documentation HTML files from from ``scheme.tl``
  257. * to ``/methods`` and ``/constructors``, etc.
  258. */
  259. const writeHtmlPages = (tlobjects, methods, layer, inputRes) => {
  260. // Save 'Type: [Constructors]' for use in both:
  261. // * Seeing the return type or constructors belonging to the same type.
  262. // * Generating the types documentation, showing available constructors.
  263. const paths = {
  264. '404': '404.html',
  265. 'css': 'css',
  266. 'arrow': 'img/arrow.svg',
  267. 'search.js': 'js/search.js',
  268. 'indexAll': 'index.html',
  269. 'botIndex': 'botindex.html',
  270. 'indexTypes': 'types/index.html',
  271. 'indexMethods': 'methods/index.html',
  272. 'indexConstructors': 'constructors/index.html',
  273. 'defaultCss': 'light',
  274. }
  275. const typeToConstructors = {}
  276. const typeToFunctions = {}
  277. for (const tlobject of tlobjects) {
  278. const d = tlobject.isFunction ? typeToFunctions : typeToConstructors
  279. if (!d[tlobject.innermostResult]) {
  280. d[tlobject.innermostResult] = []
  281. }
  282. d[tlobject.innermostResult].push(tlobject)
  283. }
  284. for (const [t, cs] of Object.entries(typeToConstructors)) {
  285. typeToConstructors[t] = cs.sort((x, y) => x.name.localeCompare(y.name))
  286. }
  287. methods = methods.reduce((x, m) => ({ ...x, [m.name]: m }), {})
  288. const botDocsPath = []
  289. for (const tlobject of tlobjects) {
  290. const filename = getPathFor(tlobject)
  291. const docs = new DocsWriter(filename, getPathForType).open()
  292. docs.writeHead(tlobject.className, paths.css, paths.defaultCss)
  293. // Create the menu (path to the current TLObject)
  294. docs.setMenuSeparator(paths.arrow)
  295. buildMenu(docs)
  296. // Create the page title
  297. docs.writeTitle(tlobject.className)
  298. if (tlobject.isFunction) {
  299. let start
  300. if (tlobject.usability === Usability.USER) {
  301. start = '<strong>Only users</strong> can'
  302. } else if (tlobject.usability === Usability.BOT) {
  303. botDocsPath.push(filename)
  304. start = '<strong>Only bots</strong> can'
  305. } else if (tlobject.usability === Usability.BOTH) {
  306. botDocsPath.push(filename)
  307. start = '<strong>Both users and bots</strong> can'
  308. } else {
  309. botDocsPath.push(filename)
  310. start = 'Both users and bots <strong>may</strong> be able to'
  311. }
  312. docs.writeText(
  313. `${start} use this method. <a href="#examples">See code examples.</a>`
  314. )
  315. }
  316. // Write the code definition for this TLObject
  317. docs.writeCode(tlobject)
  318. docs.writeCopyButton(
  319. 'Copy import to clipboard',
  320. getImportCode(tlobject)
  321. )
  322. // Write the return type (or constructors belonging to the same type)
  323. docs.writeTitle(tlobject.isFunction ? 'Returns' : 'Belongs to', 3)
  324. let [genericArg] = tlobject.args
  325. .filter((arg) => arg.genericDefinition)
  326. .map((arg) => arg.name)
  327. if (tlobject.result === genericArg) {
  328. // We assume it's a function returning a generic type
  329. [genericArg] = tlobject.args
  330. .filter((arg) => arg.isGeneric)
  331. .map((arg) => arg.name)
  332. docs.writeText(
  333. `This function returns the result of whatever the result from invoking the request passed through <i>${genericArg}</i> is.`
  334. )
  335. } else {
  336. let inner = tlobject.result
  337. if (/^vector</i.test(tlobject.result)) {
  338. docs.writeText('A list of the following type is returned.')
  339. inner = tlobject.innermostResult
  340. }
  341. docs.beginTable(1)
  342. docs.addRow(inner, getPathForType(inner))
  343. docs.endTable()
  344. const cs = typeToConstructors[inner] || []
  345. if (!cs.length) {
  346. docs.writeText('This type has no instances available.')
  347. } else if (cs.length === 1) {
  348. docs.writeText('This type can only be an instance of:')
  349. } else {
  350. docs.writeText('This type can be an instance of either:')
  351. }
  352. docs.beginTable(2)
  353. for (const constructor of cs) {
  354. const link = getPathFor(constructor)
  355. docs.addRow(constructor.className, link)
  356. }
  357. docs.endTable()
  358. }
  359. // Return (or similar types) written. Now parameters/members
  360. docs.writeTitle(tlobject.isFunction ? 'Parameters' : 'Members', 3)
  361. // Sort the arguments in the same way they're sorted
  362. // on the generated code (flags go last)
  363. const args = tlobject
  364. .sortedArgs()
  365. .filter((a) => !a.flagIndicator && !a.genericDefinition)
  366. if (args.length) {
  367. // Writing parameters
  368. docs.beginTable(3)
  369. for (const arg of args) {
  370. // Name row
  371. docs.addRow(arg.name, null, true)
  372. // Type row
  373. const friendlyType = arg.type === 'true' ? 'flag' : arg.type
  374. if (arg.isGeneric) {
  375. docs.addRow(`!${friendlyType}`, null, null, 'center')
  376. } else {
  377. docs.addRow(
  378. friendlyType,
  379. getPathForType(arg.type),
  380. null,
  381. 'center'
  382. )
  383. }
  384. // Add a description for this argument
  385. docs.addRow(getDescription(arg))
  386. }
  387. docs.endTable()
  388. } else {
  389. if (tlobject.isFunction) {
  390. docs.writeText('This request takes no input parameters.')
  391. } else {
  392. docs.writeText('This type has no members.')
  393. }
  394. }
  395. if (tlobject.isFunction) {
  396. docs.writeTitle('Known RPC errors')
  397. const methodInfo = methods[tlobject.fullname]
  398. const errors = methodInfo && methodInfo.errors
  399. if (!errors || !errors.length) {
  400. docs.writeText(
  401. 'This request can\'t cause any RPC error as far as we know.'
  402. )
  403. } else {
  404. docs.writeText(
  405. `This request can cause ${errors.length} known error${
  406. errors.length === 1 ? '' : 's'
  407. }:`
  408. )
  409. docs.beginTable(2)
  410. for (const error of errors) {
  411. docs.addRow(`<code>${error.name}</code>`)
  412. docs.addRow(`${error.description}.`)
  413. }
  414. docs.endTable()
  415. docs.writeText(
  416. 'You can import these from <code>gramjs/errors</code>.'
  417. )
  418. }
  419. docs.writeTitle('Example', null, 'examples')
  420. if (tlobject.friendly) {
  421. const [ns, friendly] = tlobject.friendly
  422. docs.writeText(
  423. `Please refer to the documentation of <a href="https://docs.telethon.dev/en/latest/modules/client.html#telethon.client.${ns}.${friendly}"><code>client.${friendly}()</code></a> to learn about the parameters and see several code examples on how to use it.`
  424. )
  425. docs.writeText(
  426. 'The method above is the recommended way to do it. If you need more control over the parameters or want to learn how it is implemented, open the details by clicking on the "Details" text.'
  427. )
  428. docs.write('<details>')
  429. }
  430. docs.write(`<pre><strong>const</strong> { TelegramClient } <strong>=</strong> require('gramjs');
  431. <strong>const</strong> { functions, types } <strong>=</strong> require('gramjs/tl');
  432. (<strong>async</strong> () => {
  433. <strong>const</strong> client <strong>=</strong> <strong>new</strong> TelegramClient(name, apiId, apiHash);
  434. await client.start();
  435. <strong>const</strong> result <strong>= await</strong> client.invoke(`)
  436. tlobject.asExample(docs, 1)
  437. docs.write(');\n')
  438. if (tlobject.result.startsWith('Vector')) {
  439. docs.write(
  440. `<strong>for</strong> x <strong>in</strong> result:
  441. print(x`
  442. )
  443. } else {
  444. docs.write(' console.log(result')
  445. if (
  446. tlobject.result !== 'Bool' &&
  447. !tlobject.result.startsWith('Vector')
  448. ) {
  449. docs.write('.stringify()')
  450. }
  451. }
  452. docs.write(');\n})();</pre>')
  453. if (tlobject.friendly) {
  454. docs.write('</details>')
  455. }
  456. const depth = '../'.repeat(tlobject.namespace ? 2 : 1)
  457. docs.addScript(`prependPath = "${depth}";`)
  458. docs.addScript(null, paths['search.js'])
  459. docs.endBody()
  460. }
  461. docs.close()
  462. }
  463. // Find all the available types (which are not the same as the constructors)
  464. // Each type has a list of constructors associated to it, hence is a map
  465. for (const [t, cs] of Object.entries(typeToConstructors)) {
  466. const filename = getPathForType(t)
  467. const outDir = path.join(filename, '..')
  468. if (outDir) {
  469. mkdir(outDir)
  470. }
  471. // Since we don't have access to the full TLObject, split the type
  472. let name = t
  473. if (t.includes('.')) {
  474. [, name] = t.split('.')
  475. }
  476. const docs = new DocsWriter(filename, getPathForType).open()
  477. docs.writeHead(snakeToCamelCase(name), paths.css, paths.defaultCss)
  478. docs.setMenuSeparator(paths.arrow)
  479. buildMenu(docs)
  480. // Main file title
  481. docs.writeTitle(snakeToCamelCase(name))
  482. // List available constructors for this type
  483. docs.writeTitle('Available constructors', 3)
  484. if (!cs.length) {
  485. docs.writeText('This type has no constructors available.')
  486. } else if (cs.length === 1) {
  487. docs.writeText('This type has one constructor available.')
  488. } else {
  489. docs.writeText(
  490. `This type has ${cs.length} constructors available.`
  491. )
  492. }
  493. docs.beginTable(2)
  494. for (const constructor of cs) {
  495. // Constructor full name
  496. const link = getPathFor(constructor)
  497. docs.addRow(constructor.className, link)
  498. }
  499. docs.endTable()
  500. // List all the methods which return this type
  501. docs.writeTitle('Methods returning this type', 3)
  502. const functions = typeToFunctions[t] || []
  503. if (!functions.length) {
  504. docs.writeText('No method returns this type.')
  505. } else if (functions.length === 1) {
  506. docs.writeText('Only the following method returns this type.')
  507. } else {
  508. docs.writeText(
  509. `The following ${functions.length} methods return this type as a result.`
  510. )
  511. }
  512. docs.beginTable(2)
  513. for (const func of functions) {
  514. const link = getPathFor(func)
  515. docs.addRow(func.className, link)
  516. }
  517. docs.endTable()
  518. // List all the methods which take this type as input
  519. docs.writeTitle('Methods accepting this type as input', 3)
  520. const otherMethods = tlobjects
  521. .filter((u) => u.isFunction && u.args.some((a) => a.type === t))
  522. .sort((x, y) => x.name.localeCompare(y.name))
  523. if (!otherMethods.length) {
  524. docs.writeText(
  525. 'No methods accept this type as an input parameter.'
  526. )
  527. } else if (otherMethods.length === 1) {
  528. docs.writeText('Only this method has a parameter with this type.')
  529. } else {
  530. docs.writeText(
  531. `The following ${otherMethods.length} methods accept this type as an input parameter.`
  532. )
  533. }
  534. docs.beginTable(2)
  535. for (const ot of otherMethods) {
  536. const link = getPathFor(ot)
  537. docs.addRow(ot.className, link)
  538. }
  539. docs.endTable()
  540. // List every other type which has this type as a member
  541. docs.writeTitle('Other types containing this type', 3)
  542. const otherTypes = tlobjects
  543. .filter((u) => !u.isFunction && u.args.some((a) => a.type === t))
  544. .sort((x, y) => x.name.localeCompare(y.name))
  545. if (!otherTypes.length) {
  546. docs.writeText('No other types have a member of this type.')
  547. } else if (otherTypes.length === 1) {
  548. docs.writeText(
  549. 'You can find this type as a member of this other type.'
  550. )
  551. } else {
  552. docs.writeText(
  553. `You can find this type as a member of any of the following ${otherTypes.length} types.`
  554. )
  555. }
  556. docs.beginTable(2)
  557. for (const ot of otherTypes) {
  558. const link = getPathFor(ot)
  559. docs.addRow(ot.className, link)
  560. }
  561. docs.endTable()
  562. docs.endBody()
  563. docs.close()
  564. }
  565. // After everything's been written, generate an index.html per folder.
  566. // This will be done automatically and not taking into account any extra
  567. // information that we have available, simply a file listing all the others
  568. // accessible by clicking on their title
  569. for (const folder of ['types', 'methods', 'constructors']) {
  570. generateIndex(folder, paths)
  571. }
  572. generateIndex('methods', paths, true, botDocsPath)
  573. // Write the final core index, the main index for the rest of files
  574. const types = new Set()
  575. const methods_ = []
  576. const cs = []
  577. for (const tlobject of tlobjects) {
  578. if (tlobject.isFunction) {
  579. methods_.push(tlobject)
  580. } else {
  581. cs.push(tlobject)
  582. }
  583. if (!CORE_TYPES.has(tlobject.result.toLowerCase())) {
  584. if (/^vector</i.test(tlobject.result)) {
  585. types.add(tlobject.innermostResult)
  586. } else {
  587. types.add(tlobject.result)
  588. }
  589. }
  590. }
  591. fs.copyFileSync(`${inputRes}/404.html`, paths['404'])
  592. copyReplace(`${inputRes}/core.html`, paths.indexAll, {
  593. typeCount: [...types].length,
  594. methodCount: methods_.length,
  595. constructorCount: tlobjects.length - methods_.length,
  596. layer,
  597. })
  598. let fmt = (xs) => {
  599. const zs = [] // create an object to hold those which have duplicated keys
  600. for (const x of xs) {
  601. zs[x.className] = x.className in zs
  602. }
  603. return xs
  604. .map((x) =>
  605. zs[x.className] && x.namespace ?
  606. `"${x.namespace}.${x.className}"` :
  607. `"${x.className}"`
  608. )
  609. .join(', ')
  610. }
  611. const requestNames = fmt(methods_)
  612. const constructorNames = fmt(cs)
  613. fmt = (xs, formatter) => {
  614. return xs
  615. .map(
  616. (x) =>
  617. `"${formatter(x).replace(
  618. new RegExp(`\\${path.sep}`, 'g'),
  619. '/'
  620. )}"`
  621. )
  622. .join(', ')
  623. }
  624. const typeNames = fmt([...types], (x) => x)
  625. const requestUrls = fmt(methods_, getPathFor)
  626. const typeUrls = fmt([...types], getPathForType)
  627. const constructorUrls = fmt(cs, getPathFor)
  628. mkdir(path.join(paths['search.js'], '..'))
  629. copyReplace(`${inputRes}/js/search.js`, paths['search.js'], {
  630. requestNames,
  631. typeNames,
  632. constructorNames,
  633. requestUrls,
  634. typeUrls,
  635. constructorUrls,
  636. })
  637. }
  638. const copyResources = (resDir) => {
  639. for (const [dirname, files] of [
  640. ['css', ['docs.light.css', 'docs.dark.css']],
  641. ['img', ['arrow.svg']],
  642. ]) {
  643. mkdir(dirname)
  644. for (const file of files) {
  645. fs.copyFileSync(
  646. `${resDir}/${dirname}/${file}`,
  647. `${dirname}/${file}`
  648. )
  649. }
  650. }
  651. }
  652. /**
  653. * Pre-create the required directory structure.
  654. */
  655. const createStructure = (tlobjects) => {
  656. const typeNs = new Set()
  657. const methodsNs = new Set()
  658. for (const obj of tlobjects) {
  659. if (obj.namespace) {
  660. if (obj.isFunction) {
  661. methodsNs.add(obj.namespace)
  662. } else {
  663. typeNs.add(obj.namespace)
  664. }
  665. }
  666. }
  667. const outputDir = '.'
  668. const typeDir = `${outputDir}/types`
  669. mkdir(typeDir)
  670. const consDir = `${outputDir}/constructors`
  671. mkdir(consDir)
  672. for (const ns of typeNs) {
  673. mkdir(`${typeDir}/${ns}`)
  674. mkdir(`${consDir}/${ns}`)
  675. }
  676. const methDir = `${outputDir}/methods`
  677. mkdir(methDir)
  678. for (const ns of typeNs) {
  679. mkdir(`${methDir}/${ns}`)
  680. }
  681. }
  682. const generateDocs = (tlobjects, methodsInfo, layer, inputRes) => {
  683. createStructure(tlobjects)
  684. writeHtmlPages(tlobjects, methodsInfo, layer, inputRes)
  685. copyResources(inputRes)
  686. }
  687. module.exports = {
  688. generateDocs,
  689. }