1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912 |
- #
- #
- # The Nim Compiler
- # (c) Copyright 2012 Andreas Rumpf
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- ## This is the Nim documentation generator. Cross-references are generated
- ## by knowing how the anchors are going to be named.
- ##
- ## .. importdoc:: ../docgen.md
- ##
- ## For corresponding users' documentation see [Nim DocGen Tools Guide].
- import
- ast, strutils, strtabs, algorithm, sequtils, options, msgs, os, idents,
- wordrecg, syntaxes, renderer, lexer,
- packages/docutils/[rst, rstidx, rstgen, dochelpers],
- json, xmltree, trees, types,
- typesrenderer, astalgo, lineinfos, intsets,
- pathutils, tables, nimpaths, renderverbatim, osproc, packages
- import packages/docutils/rstast except FileIndex, TLineInfo
- from uri import encodeUrl
- from nodejs import findNodeJs
- when defined(nimPreviewSlimSystem):
- import std/[assertions, syncio]
- const
- exportSection = skField
- docCmdSkip = "skip"
- DocColOffset = "## ".len # assuming that a space was added after ##
- type
- ItemFragment = object ## A fragment from each item will be eventually
- ## constructed by converting `rst` fields to strings.
- case isRst: bool
- of true:
- rst: PRstNode
- of false: ## contains ready markup e.g. from runnableExamples
- str: string
- ItemPre = seq[ItemFragment] ## A pre-processed item.
- Item = object ## Any item in documentation, e.g. symbol
- ## entry. Configuration variable ``doc.item``
- ## is used for its HTML rendering.
- descRst: ItemPre ## Description of the item (may contain
- ## runnableExamples).
- substitutions: seq[string] ## Variable names in `doc.item`...
- sortName: string ## The string used for sorting in output
- info: rstast.TLineInfo ## place where symbol was defined (for messages)
- anchor: string ## e.g. HTML anchor
- name: string ## short name of the symbol, not unique
- ## (includes backticks ` if present)
- detailedName: string ## longer name like `proc search(x: int): int`
- ModSection = object ## Section like Procs, Types, etc.
- secItems: Table[string, seq[Item]]
- ## Map basic name -> pre-processed items.
- finalMarkup: string ## The items, after RST pass 2 and rendering.
- ModSections = array[TSymKind, ModSection]
- TocItem = object ## HTML TOC item
- content: string
- sortName: string
- TocSectionsFinal = array[TSymKind, string]
- ExampleGroup = ref object
- ## a group of runnableExamples with same rdoccmd
- rdoccmd: string ## from 1st arg in `runnableExamples(rdoccmd): body`
- docCmd: string ## from user config, e.g. --doccmd:-d:foo
- code: string ## contains imports; each import contains `body`
- index: int ## group index
- JsonItem = object # pre-processed item: `rst` should be finalized
- json: JsonNode
- rst: PRstNode
- rstField: string
- TDocumentor = object of rstgen.RstGenerator
- modDescPre: ItemPre # module description, not finalized
- modDescFinal: string # module description, after RST pass 2 and rendering
- module: PSym
- modDeprecationMsg: string
- section: ModSections # entries of ``.nim`` file (for `proc`s, etc)
- tocSimple: array[TSymKind, seq[TocItem]]
- # TOC entries for non-overloadable symbols (e.g. types, constants)...
- tocTable: array[TSymKind, Table[string, seq[TocItem]]]
- # ...otherwise (e.g. procs)
- toc2: TocSectionsFinal # TOC `content`, which is probably wrapped
- # in `doc.section.toc2`
- toc: TocSectionsFinal # final TOC (wrapped in `doc.section.toc`)
- indexValFilename: string
- analytics: string # Google Analytics javascript, "" if doesn't exist
- seenSymbols: StringTableRef # avoids duplicate symbol generation for HTML.
- jEntriesPre: seq[JsonItem] # pre-processed RST + JSON content
- jEntriesFinal: JsonNode # final JSON after RST pass 2 and rendering
- types: TStrTable
- sharedState: PRstSharedState
- standaloneDoc: bool # is markup (.rst/.md) document?
- conf*: ConfigRef
- cache*: IdentCache
- exampleCounter: int
- emitted: IntSet # we need to track which symbols have been emitted
- # already. See bug #3655
- thisDir*: AbsoluteDir
- exampleGroups: OrderedTable[string, ExampleGroup]
- wroteSupportFiles*: bool
- nimToRstFid: Table[lineinfos.FileIndex, rstast.FileIndex]
- ## map Nim FileIndex -> RST one, it's needed because we keep them separate
- PDoc* = ref TDocumentor ## Alias to type less.
- proc add(dest: var ItemPre, rst: PRstNode) = dest.add ItemFragment(isRst: true, rst: rst)
- proc add(dest: var ItemPre, str: string) = dest.add ItemFragment(isRst: false, str: str)
- proc addRstFileIndex(d: PDoc, fileIndex: lineinfos.FileIndex): rstast.FileIndex =
- let invalid = rstast.FileIndex(-1)
- result = d.nimToRstFid.getOrDefault(fileIndex, default = invalid)
- if result == invalid:
- let fname = toFullPath(d.conf, fileIndex)
- result = addFilename(d.sharedState, fname)
- d.nimToRstFid[fileIndex] = result
- proc addRstFileIndex(d: PDoc, info: lineinfos.TLineInfo): rstast.FileIndex =
- addRstFileIndex(d, info.fileIndex)
- proc cmpDecimalsIgnoreCase(a, b: string): int =
- ## For sorting with correct handling of cases like 'uint8' and 'uint16'.
- ## Also handles leading zeros well (however note that leading zeros are
- ## significant when lengths of numbers mismatch, e.g. 'bar08' > 'bar8' !).
- runnableExamples:
- doAssert cmpDecimalsIgnoreCase("uint8", "uint16") < 0
- doAssert cmpDecimalsIgnoreCase("val00032", "val16suffix") > 0
- doAssert cmpDecimalsIgnoreCase("val16suffix", "val16") > 0
- doAssert cmpDecimalsIgnoreCase("val_08_32", "val_08_8") > 0
- doAssert cmpDecimalsIgnoreCase("val_07_32", "val_08_8") < 0
- doAssert cmpDecimalsIgnoreCase("ab8", "ab08") < 0
- doAssert cmpDecimalsIgnoreCase("ab8de", "ab08c") < 0 # sanity check
- let aLen = a.len
- let bLen = b.len
- var
- iA = 0
- iB = 0
- while iA < aLen and iB < bLen:
- if isDigit(a[iA]) and isDigit(b[iB]):
- var
- limitA = iA # index after the last (least significant) digit
- limitB = iB
- while limitA < aLen and isDigit(a[limitA]): inc limitA
- while limitB < bLen and isDigit(b[limitB]): inc limitB
- var pos = max(limitA-iA, limitB-iA)
- while pos > 0:
- if limitA-pos < iA: # digit in `a` is 0 effectively
- result = ord('0') - ord(b[limitB-pos])
- elif limitB-pos < iB: # digit in `b` is 0 effectively
- result = ord(a[limitA-pos]) - ord('0')
- else:
- result = ord(a[limitA-pos]) - ord(b[limitB-pos])
- if result != 0: return
- dec pos
- result = (limitA - iA) - (limitB - iB) # consider 'bar08' > 'bar8'
- if result != 0: return
- iA = limitA
- iB = limitB
- else:
- result = ord(toLowerAscii(a[iA])) - ord(toLowerAscii(b[iB]))
- if result != 0: return
- inc iA
- inc iB
- result = (aLen - iA) - (bLen - iB)
- proc prettyString(a: object): string =
- # xxx pending std/prettyprint refs https://github.com/nim-lang/RFCs/issues/203#issuecomment-602534906
- for k, v in fieldPairs(a):
- result.add k & ": " & $v & "\n"
- proc presentationPath*(conf: ConfigRef, file: AbsoluteFile): RelativeFile =
- ## returns a relative file that will be appended to outDir
- let file2 = $file
- template bail() =
- result = relativeTo(file, conf.projectPath)
- proc nimbleDir(): AbsoluteDir =
- getNimbleFile(conf, file2).parentDir.AbsoluteDir
- case conf.docRoot:
- of docRootDefault:
- result = getRelativePathFromConfigPath(conf, file)
- let dir = nimbleDir()
- if not dir.isEmpty:
- let result2 = relativeTo(file, dir)
- if not result2.isEmpty and (result.isEmpty or result2.string.len < result.string.len):
- result = result2
- if result.isEmpty: bail()
- of "@pkg":
- let dir = nimbleDir()
- if dir.isEmpty: bail()
- else: result = relativeTo(file, dir)
- of "@path":
- result = getRelativePathFromConfigPath(conf, file)
- if result.isEmpty: bail()
- elif conf.docRoot.len > 0:
- # we're (currently) requiring `isAbsolute` to avoid confusion when passing
- # a relative path (would it be relative with regard to $PWD or to projectfile)
- conf.globalAssert conf.docRoot.isAbsolute, arg=conf.docRoot
- conf.globalAssert conf.docRoot.dirExists, arg=conf.docRoot
- # needed because `canonicalizePath` called on `file`
- result = file.relativeTo conf.docRoot.expandFilename.AbsoluteDir
- else:
- bail()
- if isAbsolute(result.string):
- result = file.string.splitPath()[1].RelativeFile
- result = result.string.replace("..", dotdotMangle).RelativeFile
- doAssert not result.isEmpty
- doAssert not isAbsolute(result.string)
- proc whichType(d: PDoc; n: PNode): PSym =
- if n.kind == nkSym:
- if d.types.strTableContains(n.sym):
- result = n.sym
- else:
- for i in 0..<n.safeLen:
- let x = whichType(d, n[i])
- if x != nil: return x
- proc attachToType(d: PDoc; p: PSym): PSym =
- let params = p.ast[paramsPos]
- template check(i) =
- result = whichType(d, params[i])
- if result != nil: return result
- # first check the first parameter, then the return type,
- # then the other parameter:
- if params.len > 1: check(1)
- if params.len > 0: check(0)
- for i in 2..<params.len: check(i)
- template declareClosures(currentFilename: AbsoluteFile, destFile: string) =
- proc compilerMsgHandler(filename: string, line, col: int,
- msgKind: rst.MsgKind, arg: string) {.gcsafe.} =
- # translate msg kind:
- var k: TMsgKind
- case msgKind
- of meCannotOpenFile: k = errCannotOpenFile
- of meExpected: k = errXExpected
- of meMissingClosing: k = errRstMissingClosing
- of meGridTableNotImplemented: k = errRstGridTableNotImplemented
- of meMarkdownIllformedTable: k = errRstMarkdownIllformedTable
- of meIllformedTable: k = errRstIllformedTable
- of meNewSectionExpected: k = errRstNewSectionExpected
- of meGeneralParseError: k = errRstGeneralParseError
- of meInvalidDirective: k = errRstInvalidDirectiveX
- of meInvalidField: k = errRstInvalidField
- of meFootnoteMismatch: k = errRstFootnoteMismatch
- of meSandboxedDirective: k = errRstSandboxedDirective
- of mwRedefinitionOfLabel: k = warnRstRedefinitionOfLabel
- of mwUnknownSubstitution: k = warnRstUnknownSubstitutionX
- of mwAmbiguousLink: k = warnRstAmbiguousLink
- of mwBrokenLink: k = warnRstBrokenLink
- of mwUnsupportedLanguage: k = warnRstLanguageXNotSupported
- of mwUnsupportedField: k = warnRstFieldXNotSupported
- of mwUnusedImportdoc: k = warnRstUnusedImportdoc
- of mwRstStyle: k = warnRstStyle
- {.gcsafe.}:
- globalError(conf, newLineInfo(conf, AbsoluteFile filename, line, col), k, arg)
- proc docgenFindFile(s: string): string {.gcsafe.} =
- result = options.findFile(conf, s).string
- if result.len == 0:
- result = getCurrentDir() / s
- if not fileExists(result): result = ""
- proc docgenFindRefFile(targetRelPath: string):
- tuple[targetPath: string, linkRelPath: string] {.gcsafe.} =
- let fromDir = splitFile(destFile).dir # dir where we reference from
- let basedir = os.splitFile(currentFilename.string).dir
- let outDirPath: RelativeFile =
- presentationPath(conf, AbsoluteFile(basedir / targetRelPath))
- # use presentationPath because `..` path can be be mangled to `_._`
- result.targetPath = string(conf.outDir / outDirPath)
- if not fileExists(result.targetPath):
- # this can happen if targetRelPath goes to parent directory `OUTDIR/..`.
- # Trying it, this may cause ambiguities, but allows us to insert
- # "packages" into each other, which is actually used in Nim repo itself.
- let destPath = fromDir / targetRelPath
- if destPath != result.targetPath and fileExists(destPath):
- result.targetPath = destPath
- result.linkRelPath = relativePath(result.targetPath.splitFile.dir,
- fromDir).replace('\\', '/')
- proc parseRst(text: string,
- line, column: int,
- conf: ConfigRef, sharedState: PRstSharedState): PRstNode =
- result = rstParsePass1(text, line, column, sharedState)
- proc getOutFile2(conf: ConfigRef; filename: RelativeFile,
- ext: string, guessTarget: bool): AbsoluteFile =
- if optWholeProject in conf.globalOptions or guessTarget:
- let d = conf.outDir
- createDir(d)
- result = d / changeFileExt(filename, ext)
- elif not conf.outFile.isEmpty:
- result = absOutFile(conf)
- else:
- result = getOutFile(conf, filename, ext)
- proc isLatexCmd(conf: ConfigRef): bool =
- conf.cmd in {cmdRst2tex, cmdMd2tex, cmdDoc2tex}
- proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef,
- outExt: string = HtmlExt, module: PSym = nil,
- standaloneDoc = false, preferMarkdown = true,
- hasToc = true): PDoc =
- let destFile = getOutFile2(conf, presentationPath(conf, filename), outExt, false).string
- declareClosures(currentFilename = filename, destFile = destFile)
- new(result)
- result.module = module
- result.conf = conf
- result.cache = cache
- result.outDir = conf.outDir.string
- result.standaloneDoc = standaloneDoc
- var options= {roSupportRawDirective, roSupportMarkdown, roSandboxDisabled}
- if preferMarkdown:
- options.incl roPreferMarkdown
- if not standaloneDoc: options.incl roNimFile
- # (options can be changed dynamically in `setDoctype` by `{.doctype.}`)
- result.hasToc = hasToc
- result.sharedState = newRstSharedState(
- options, filename.string,
- docgenFindFile, docgenFindRefFile, compilerMsgHandler, hasToc)
- initRstGenerator(result[], (if conf.isLatexCmd: outLatex else: outHtml),
- conf.configVars, filename.string,
- docgenFindFile, compilerMsgHandler)
- if conf.configVars.hasKey("doc.googleAnalytics") and
- conf.configVars.hasKey("doc.plausibleAnalytics"):
- doAssert false, "Either use googleAnalytics or plausibleAnalytics"
- if conf.configVars.hasKey("doc.googleAnalytics"):
- result.analytics = """
- <script>
- (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
- (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
- m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
- })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
- ga('create', '$1', 'auto');
- ga('send', 'pageview');
- </script>
- """ % [conf.configVars.getOrDefault"doc.googleAnalytics"]
- elif conf.configVars.hasKey("doc.plausibleAnalytics"):
- result.analytics = """
- <script defer data-domain="$1" src="https://plausible.io/js/plausible.js"></script>
- """ % [conf.configVars.getOrDefault"doc.plausibleAnalytics"]
- else:
- result.analytics = ""
- result.seenSymbols = newStringTable(modeCaseInsensitive)
- result.id = 100
- result.jEntriesFinal = newJArray()
- initStrTable result.types
- result.onTestSnippet =
- proc (gen: var RstGenerator; filename, cmd: string; status: int; content: string) {.gcsafe.} =
- if conf.docCmd == docCmdSkip: return
- inc(gen.id)
- var d = (ptr TDocumentor)(addr gen)
- var outp: AbsoluteFile
- if filename.len == 0:
- let nameOnly = splitFile(d.filename).name
- # "snippets" needed, refs bug #17183
- outp = getNimcacheDir(conf) / "snippets".RelativeDir / RelativeDir(nameOnly) /
- RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim")
- elif isAbsolute(filename):
- outp = AbsoluteFile(filename)
- else:
- # Nim's convention: every path is relative to the file it was written in:
- let nameOnly = splitFile(d.filename).name
- outp = AbsoluteDir(nameOnly) / RelativeFile(filename)
- # Make sure the destination directory exists
- createDir(outp.splitFile.dir)
- # Include the current file if we're parsing a nim file
- let importStmt = if d.standaloneDoc: "" else: "import \"$1\"\n" % [d.filename.replace("\\", "/")]
- writeFile(outp, importStmt & content)
- proc interpSnippetCmd(cmd: string): string =
- # backward compatibility hacks; interpolation commands should explicitly use `$`
- if cmd.startsWith "nim ": result = "$nim " & cmd[4..^1]
- else: result = cmd
- # factor with D20210224T221756
- result = result.replace("$1", "$options") % [
- "nim", os.getAppFilename().quoteShell,
- "libpath", quoteShell(d.conf.libpath),
- "docCmd", d.conf.docCmd,
- "backend", $d.conf.backend,
- "options", outp.quoteShell,
- # xxx `quoteShell` seems buggy if user passes options = "-d:foo somefile.nim"
- ]
- let cmd = cmd.interpSnippetCmd
- rawMessage(conf, hintExecuting, cmd)
- let (output, gotten) = execCmdEx(cmd)
- if gotten != status:
- rawMessage(conf, errGenerated, "snippet failed: cmd: '$1' status: $2 expected: $3 output: $4" % [cmd, $gotten, $status, output])
- result.emitted = initIntSet()
- result.destFile = destFile
- result.thisDir = result.destFile.AbsoluteFile.splitFile.dir
- template dispA(conf: ConfigRef; dest: var string, xml, tex: string,
- args: openArray[string]) =
- if not conf.isLatexCmd: dest.addf(xml, args)
- else: dest.addf(tex, args)
- proc getVarIdx(varnames: openArray[string], id: string): int =
- for i in 0..high(varnames):
- if cmpIgnoreStyle(varnames[i], id) == 0:
- return i
- result = -1
- proc genComment(d: PDoc, n: PNode): PRstNode =
- if n.comment.len > 0:
- d.sharedState.currFileIdx = addRstFileIndex(d, n.info)
- result = parseRst(n.comment,
- toLinenumber(n.info),
- toColumn(n.info) + DocColOffset,
- d.conf, d.sharedState)
- proc genRecCommentAux(d: PDoc, n: PNode): PRstNode =
- if n == nil: return nil
- result = genComment(d, n)
- if result == nil:
- if n.kind in {nkStmtList, nkStmtListExpr, nkTypeDef, nkConstDef,
- nkObjectTy, nkRefTy, nkPtrTy, nkAsgn, nkFastAsgn, nkSinkAsgn, nkHiddenStdConv}:
- # notin {nkEmpty..nkNilLit, nkEnumTy, nkTupleTy}:
- for i in 0..<n.len:
- result = genRecCommentAux(d, n[i])
- if result != nil: return
- else:
- n.comment = ""
- proc genRecComment(d: PDoc, n: PNode): PRstNode =
- if n == nil: return nil
- result = genComment(d, n)
- if result == nil:
- if n.kind in {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef,
- nkMacroDef, nkTemplateDef, nkConverterDef}:
- result = genRecCommentAux(d, n[bodyPos])
- else:
- result = genRecCommentAux(d, n)
- proc getPlainDocstring(n: PNode): string =
- ## Gets the plain text docstring of a node non destructively.
- ##
- ## You need to call this before genRecComment, whose side effects are removal
- ## of comments from the tree. The proc will recursively scan and return all
- ## the concatenated ``##`` comments of the node.
- if n == nil: result = ""
- elif startsWith(n.comment, "##"):
- result = n.comment
- else:
- for i in 0..<n.safeLen:
- result = getPlainDocstring(n[i])
- if result.len > 0: return
- proc externalDep(d: PDoc; module: PSym): string =
- if optWholeProject in d.conf.globalOptions or d.conf.docRoot.len > 0:
- let full = AbsoluteFile toFullPath(d.conf, FileIndex module.position)
- let tmp = getOutFile2(d.conf, presentationPath(d.conf, full), HtmlExt, sfMainModule notin module.flags)
- result = relativeTo(tmp, d.thisDir, '/').string
- else:
- result = extractFilename toFullPath(d.conf, FileIndex module.position)
- proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var string;
- renderFlags: TRenderFlags = {};
- procLink: string) =
- var r: TSrcGen
- var literal = ""
- initTokRender(r, n, renderFlags)
- var kind = tkEof
- var tokenPos = 0
- var procTokenPos = 0
- template escLit(): untyped = esc(d.target, literal)
- while true:
- getNextTok(r, kind, literal)
- inc tokenPos
- case kind
- of tkEof:
- break
- of tkComment:
- dispA(d.conf, result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}",
- [escLit])
- of tokKeywordLow..tokKeywordHigh:
- if kind in {tkProc, tkMethod, tkIterator, tkMacro, tkTemplate, tkFunc, tkConverter}:
- procTokenPos = tokenPos
- dispA(d.conf, result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}",
- [literal])
- of tkOpr:
- dispA(d.conf, result, "<span class=\"Operator\">$1</span>", "\\spanOperator{$1}",
- [escLit])
- of tkStrLit..tkTripleStrLit, tkCustomLit:
- dispA(d.conf, result, "<span class=\"StringLit\">$1</span>",
- "\\spanStringLit{$1}", [escLit])
- of tkCharLit:
- dispA(d.conf, result, "<span class=\"CharLit\">$1</span>", "\\spanCharLit{$1}",
- [escLit])
- of tkIntLit..tkUInt64Lit:
- dispA(d.conf, result, "<span class=\"DecNumber\">$1</span>",
- "\\spanDecNumber{$1}", [escLit])
- of tkFloatLit..tkFloat128Lit:
- dispA(d.conf, result, "<span class=\"FloatNumber\">$1</span>",
- "\\spanFloatNumber{$1}", [escLit])
- of tkSymbol:
- let s = getTokSym(r)
- # -2 because of the whitespace in between:
- if procTokenPos == tokenPos-2 and procLink != "":
- dispA(d.conf, result, "<a href=\"#$2\"><span class=\"Identifier\">$1</span></a>",
- "\\spanIdentifier{$1}", [escLit, procLink])
- elif s != nil and s.kind in {skType, skVar, skLet, skConst} and
- sfExported in s.flags and s.owner != nil and
- belongsToProjectPackage(d.conf, s.owner) and d.target == outHtml:
- let external = externalDep(d, s.owner)
- result.addf "<a href=\"$1#$2\"><span class=\"Identifier\">$3</span></a>",
- [changeFileExt(external, "html"), literal,
- escLit]
- else:
- dispA(d.conf, result, "<span class=\"Identifier\">$1</span>",
- "\\spanIdentifier{$1}", [escLit])
- of tkSpaces, tkInvalid:
- result.add(literal)
- of tkHideableStart:
- template fun(s) = dispA(d.conf, result, s, "\\spanOther{$1}", [escLit])
- if renderRunnableExamples in renderFlags: fun "$1"
- else:
- # 1st span is required for the JS to work properly
- fun """
- <span>
- <span class="Other pragmadots">...</span>
- </span>
- <span class="pragmawrap">""".replace("\n", "") # Must remove newlines because wrapped in a <pre>
- of tkHideableEnd:
- template fun(s) = dispA(d.conf, result, s, "\\spanOther{$1}", [escLit])
- if renderRunnableExamples in renderFlags: fun "$1"
- else: fun "</span>"
- of tkCurlyDotLe: dispA(d.conf, result, "$1", "\\spanOther{$1}", [escLit])
- of tkCurlyDotRi: dispA(d.conf, result, "$1", "\\spanOther{$1}", [escLit])
- of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi,
- tkBracketDotLe, tkBracketDotRi, tkParDotLe,
- tkParDotRi, tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot,
- tkAccent, tkColonColon,
- tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr,
- tkBracketLeColon:
- dispA(d.conf, result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}",
- [escLit])
- proc exampleOutputDir(d: PDoc): AbsoluteDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples"
- proc runAllExamples(d: PDoc) =
- # This used to be: `let backend = if isDefined(d.conf, "js"): "js"` (etc), however
- # using `-d:js` (etc) cannot work properly, e.g. would fail with `importjs`
- # since semantics are affected by `config.backend`, not by isDefined(d.conf, "js")
- let outputDir = d.exampleOutputDir
- for _, group in d.exampleGroups:
- if group.docCmd == docCmdSkip: continue
- let outp = outputDir / RelativeFile("$1_group$2_examples.nim" % [d.filename.splitFile.name, $group.index])
- group.code = "# autogenerated by docgen\n# source: $1\n# rdoccmd: $2\n$3" % [d.filename, group.rdoccmd, group.code]
- writeFile(outp, group.code)
- # most useful semantics is that `docCmd` comes after `rdoccmd`, so that we can (temporarily) override
- # via command line
- # D20210224T221756:here
- var pathArgs = "--path:$path" % [ "path", quoteShell(d.conf.projectPath) ]
- for p in d.conf.searchPaths:
- pathArgs = "$args --path:$path" % [ "args", pathArgs, "path", quoteShell(p) ]
- let cmd = "$nim $backend -r --lib:$libpath --warning:UnusedImport:off $pathArgs --nimcache:$nimcache $rdoccmd $docCmd $file" % [
- "nim", quoteShell(os.getAppFilename()),
- "backend", $d.conf.backend,
- "pathArgs", pathArgs,
- "libpath", quoteShell(d.conf.libpath),
- "nimcache", quoteShell(outputDir),
- "file", quoteShell(outp),
- "rdoccmd", group.rdoccmd,
- "docCmd", group.docCmd,
- ]
- if d.conf.backend == backendJs and findNodeJs() == "":
- discard "ignore JS runnableExample"
- elif os.execShellCmd(cmd) != 0:
- d.conf.quitOrRaise "[runnableExamples] failed: generated file: '$1' group: '$2' cmd: $3" % [outp.string, group[].prettyString, cmd]
- else:
- # keep generated source file `outp` to allow inspection.
- rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string])
- # removeFile(outp.changeFileExt(ExeExt)) # it's in nimcache, no need to remove
- proc quoted(a: string): string = result.addQuoted(a)
- proc toInstantiationInfo(conf: ConfigRef, info: TLineInfo): (string, int, int) =
- # xxx expose in compiler/lineinfos.nim
- (conf.toMsgFilename(info), info.line.int, info.col.int + ColOffset)
- proc prepareExample(d: PDoc; n: PNode, topLevel: bool): tuple[rdoccmd: string, code: string] =
- ## returns `rdoccmd` and source code for this runnableExamples
- var rdoccmd = ""
- if n.len < 2 or n.len > 3: globalError(d.conf, n.info, "runnableExamples invalid")
- if n.len == 3:
- let n1 = n[1]
- # xxx this should be evaluated during sempass
- if n1.kind notin nkStrKinds: globalError(d.conf, n1.info, "string litteral expected")
- rdoccmd = n1.strVal
- let useRenderModule = false
- let loc = d.conf.toFileLineCol(n.info)
- let code = extractRunnableExamplesSource(d.conf, n)
- if d.conf.errorCounter > 0:
- return (rdoccmd, code)
- let comment = "autogenerated by docgen\nloc: $1\nrdoccmd: $2" % [loc, rdoccmd]
- let outputDir = d.exampleOutputDir
- createDir(outputDir)
- inc d.exampleCounter
- let outp = outputDir / RelativeFile("$#_examples_$#.nim" % [d.filename.extractFilename.changeFileExt"", $d.exampleCounter])
- if useRenderModule:
- var docComment = newTree(nkCommentStmt)
- docComment.comment = comment
- var runnableExamples = newTree(nkStmtList,
- docComment,
- newTree(nkImportStmt, newStrNode(nkStrLit, "std/assertions")),
- newTree(nkImportStmt, newStrNode(nkStrLit, d.filename)))
- runnableExamples.info = n.info
- for a in n.lastSon: runnableExamples.add a
- # buggy, refs bug #17292
- # still worth fixing as it can affect other code relying on `renderModule`,
- # so we keep this code path here for now, which could still be useful in some
- # other situations.
- renderModule(runnableExamples, outp.string, conf = d.conf)
- else:
- var code2 = code
- if code.len > 0 and "codeReordering" notin code:
- let codeIndent = extractRunnableExamplesSource(d.conf, n, indent = 2)
- # hacky but simplest solution, until we devise a way to make `{.line.}`
- # work without introducing a scope
- code2 = """
- {.line: $#.}:
- $#
- """ % [$toInstantiationInfo(d.conf, n.info), codeIndent]
- code2 = """
- #[
- $#
- ]#
- import std/assertions
- import $#
- $#
- """ % [comment, d.filename.quoted, code2]
- writeFile(outp.string, code2)
- if rdoccmd notin d.exampleGroups:
- d.exampleGroups[rdoccmd] = ExampleGroup(rdoccmd: rdoccmd, docCmd: d.conf.docCmd, index: d.exampleGroups.len)
- d.exampleGroups[rdoccmd].code.add "import $1\n" % outp.string.quoted
- var codeShown: string
- if topLevel: # refs https://github.com/nim-lang/RFCs/issues/352
- let title = canonicalImport(d.conf, AbsoluteFile d.filename)
- codeShown = "import $#\n$#" % [title, code]
- else:
- codeShown = code
- result = (rdoccmd, codeShown)
- when false:
- proc extractImports(n: PNode; result: PNode) =
- if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}:
- result.add copyTree(n)
- n.kind = nkEmpty
- return
- for i in 0..<n.safeLen: extractImports(n[i], result)
- let imports = newTree(nkStmtList)
- var savedLastSon = copyTree n.lastSon
- extractImports(savedLastSon, imports)
- for imp in imports: runnableExamples.add imp
- runnableExamples.add newTree(nkBlockStmt, newNode(nkEmpty), copyTree savedLastSon)
- type RunnableState = enum
- rsStart
- rsComment
- rsRunnable
- rsDone
- proc getAllRunnableExamplesImpl(d: PDoc; n: PNode, dest: var ItemPre,
- state: RunnableState, topLevel: bool):
- RunnableState =
- ##[
- Simple state machine to tell whether we render runnableExamples and doc comments.
- This is to ensure that we can interleave runnableExamples and doc comments freely;
- the logic is easy to change but currently a doc comment following another doc comment
- will not render, to avoid rendering in following case:
- proc fn* =
- runnableExamples: discard
- ## d1
- runnableExamples: discard
- ## d2
- ## internal explanation # <- this one should be out; it's part of rest of function body and would likey not make sense in doc comment
- discard # some code
- ]##
- case n.kind
- of nkCommentStmt:
- if state in {rsStart, rsRunnable}:
- dest.add genRecComment(d, n)
- return rsComment
- of nkCallKinds:
- if isRunnableExamples(n[0]) and
- n.len >= 2 and n.lastSon.kind == nkStmtList:
- if state in {rsStart, rsComment, rsRunnable}:
- let (rdoccmd, code) = prepareExample(d, n, topLevel)
- var msg = "Example:"
- if rdoccmd.len > 0: msg.add " cmd: " & rdoccmd
- var s: string
- dispA(d.conf, s, "\n<p><strong class=\"examples_text\">$1</strong></p>\n",
- "\n\n\\textbf{$1}\n", [msg])
- dest.add s
- inc d.listingCounter
- let id = $d.listingCounter
- dest.add(d.config.getOrDefault"doc.listing_start" % [id, "langNim", ""])
- var dest2 = ""
- renderNimCode(dest2, code, d.target)
- dest.add dest2
- dest.add(d.config.getOrDefault"doc.listing_end" % id)
- return rsRunnable
- else:
- localError(d.conf, n.info, errUser, "runnableExamples must appear before the first non-comment statement")
- else: discard
- return rsDone
- # change this to `rsStart` if you want to keep generating doc comments
- # and runnableExamples that occur after some code in routine
- proc getRoutineBody(n: PNode): PNode =
- ##[
- nim transforms these quite differently:
- proc someType*(): int =
- ## foo
- result = 3
- =>
- result =
- ## foo
- 3;
- proc someType*(): int =
- ## foo
- 3
- =>
- ## foo
- result = 3;
- so we normalize the results to get to the statement list containing the
- (0 or more) doc comments and runnableExamples.
- ]##
- result = n[bodyPos]
- # This won't be transformed: result.id = 10. Namely result[0].kind != nkSym.
- if result.kind == nkAsgn and result[0].kind == nkSym and
- n.len > bodyPos+1 and n[bodyPos+1].kind == nkSym:
- doAssert result.len == 2
- result = result[1]
- proc getAllRunnableExamples(d: PDoc, n: PNode, dest: var ItemPre) =
- var n = n
- var state = rsStart
- template fn(n2, topLevel) =
- state = getAllRunnableExamplesImpl(d, n2, dest, state, topLevel)
- dest.add genComment(d, n)
- case n.kind
- of routineDefs:
- n = n.getRoutineBody
- case n.kind
- of nkCommentStmt, nkCallKinds: fn(n, topLevel = false)
- else:
- for i in 0..<n.safeLen:
- fn(n[i], topLevel = false)
- if state == rsDone: discard # check all sons
- else: fn(n, topLevel = true)
- proc isVisible(d: PDoc; n: PNode): bool =
- result = false
- if n.kind == nkPostfix:
- if n.len == 2 and n[0].kind == nkIdent:
- var v = n[0].ident
- result = v.id == ord(wStar) or v.id == ord(wMinus)
- elif n.kind == nkSym:
- # we cannot generate code for forwarded symbols here as we have no
- # exception tracking information here. Instead we copy over the comment
- # from the proc header.
- if optDocInternal in d.conf.globalOptions:
- result = {sfFromGeneric, sfForward}*n.sym.flags == {}
- else:
- result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported}
- if result and containsOrIncl(d.emitted, n.sym.id):
- result = false
- elif n.kind == nkPragmaExpr:
- result = isVisible(d, n[0])
- proc getName(n: PNode): string =
- case n.kind
- of nkPostfix: result = getName(n[1])
- of nkPragmaExpr: result = getName(n[0])
- of nkSym: result = n.sym.renderDefinitionName
- of nkIdent: result = n.ident.s
- of nkAccQuoted:
- result = "`"
- for i in 0..<n.len: result.add(getName(n[i]))
- result = "`"
- of nkOpenSymChoice, nkClosedSymChoice:
- result = getName(n[0])
- else:
- result = ""
- proc getNameEsc(d: PDoc, n: PNode): string =
- esc(d.target, getName(n))
- proc getNameIdent(cache: IdentCache; n: PNode): PIdent =
- case n.kind
- of nkPostfix: result = getNameIdent(cache, n[1])
- of nkPragmaExpr: result = getNameIdent(cache, n[0])
- of nkSym: result = n.sym.name
- of nkIdent: result = n.ident
- of nkAccQuoted:
- var r = ""
- for i in 0..<n.len: r.add(getNameIdent(cache, n[i]).s)
- result = getIdent(cache, r)
- of nkOpenSymChoice, nkClosedSymChoice:
- result = getNameIdent(cache, n[0])
- else:
- result = nil
- proc getRstName(n: PNode): PRstNode =
- case n.kind
- of nkPostfix: result = getRstName(n[1])
- of nkPragmaExpr: result = getRstName(n[0])
- of nkSym: result = newRstLeaf(n.sym.renderDefinitionName)
- of nkIdent: result = newRstLeaf(n.ident.s)
- of nkAccQuoted:
- result = getRstName(n[0])
- for i in 1..<n.len: result.text.add(getRstName(n[i]).text)
- of nkOpenSymChoice, nkClosedSymChoice:
- result = getRstName(n[0])
- else:
- result = nil
- proc newUniquePlainSymbol(d: PDoc, original: string): string =
- ## Returns a new unique plain symbol made up from the original.
- ##
- ## When a collision is found in the seenSymbols table, new numerical variants
- ## with underscore + number will be generated.
- if not d.seenSymbols.hasKey(original):
- result = original
- d.seenSymbols[original] = ""
- return
- # Iterate over possible numeric variants of the original name.
- var count = 2
- while true:
- result = original & "_" & $count
- if not d.seenSymbols.hasKey(result):
- d.seenSymbols[result] = ""
- break
- count += 1
- proc complexName(k: TSymKind, n: PNode, baseName: string): string =
- ## Builds a complex unique href name for the node.
- ##
- ## Pass as ``baseName`` the plain symbol obtained from the nodeName. The
- ## format of the returned symbol will be ``baseName(.callable type)?,(param
- ## type)?(,param type)*``. The callable type part will be added only if the
- ## node is not a proc, as those are the common ones. The suffix will be a dot
- ## and a single letter representing the type of the callable. The parameter
- ## types will be added with a preceding dash. Return types won't be added.
- ##
- ## If you modify the output of this proc, please update the anchor generation
- ## section of ``doc/docgen.rst``.
- result = baseName
- case k
- of skProc, skFunc: discard
- of skMacro: result.add(".m")
- of skMethod: result.add(".e")
- of skIterator: result.add(".i")
- of skTemplate: result.add(".t")
- of skConverter: result.add(".c")
- else: discard
- if n.safeLen > paramsPos and n[paramsPos].kind == nkFormalParams:
- let params = renderParamTypes(n[paramsPos])
- if params.len > 0:
- result.add(defaultParamSeparator)
- result.add(params)
- proc docstringSummary(rstText: string): string =
- ## Returns just the first line or a brief chunk of text from a rst string.
- ##
- ## Most docstrings will contain a one liner summary, so stripping at the
- ## first newline is usually fine. If after that the content is still too big,
- ## it is stripped at the first comma, colon or dot, usual English sentence
- ## separators.
- ##
- ## No guarantees are made on the size of the output, but it should be small.
- ## Also, we hope to not break the rst, but maybe we do. If there is any
- ## trimming done, an ellipsis unicode char is added.
- const maxDocstringChars = 100
- assert(rstText.len < 2 or (rstText[0] == '#' and rstText[1] == '#'))
- result = rstText.substr(2).strip
- var pos = result.find('\L')
- if pos > 0:
- result.setLen(pos - 1)
- result.add("…")
- if pos < maxDocstringChars:
- return
- # Try to keep trimming at other natural boundaries.
- pos = result.find({'.', ',', ':'})
- let last = result.len - 1
- if pos > 0 and pos < last:
- result.setLen(pos - 1)
- result.add("…")
- proc genDeprecationMsg(d: PDoc, n: PNode): string =
- ## Given a nkPragma wDeprecated node output a well-formatted section
- if n == nil: return
- case n.safeLen:
- of 0: # Deprecated w/o any message
- result = getConfigVar(d.conf, "doc.deprecationmsg") % [
- "label" , "Deprecated", "message", ""]
- of 2: # Deprecated w/ a message
- if n[1].kind in {nkStrLit..nkTripleStrLit}:
- result = getConfigVar(d.conf, "doc.deprecationmsg") % [
- "label", "Deprecated:", "message", xmltree.escape(n[1].strVal)]
- else:
- doAssert false
- type DocFlags = enum
- kDefault
- kForceExport
- proc genSeeSrc(d: PDoc, path: string, line: int): string =
- let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc")
- if docItemSeeSrc.len > 0:
- let path = relativeTo(AbsoluteFile path, AbsoluteDir getCurrentDir(), '/')
- when false:
- let cwd = canonicalizePath(d.conf, getCurrentDir())
- var path = path
- if path.startsWith(cwd):
- path = path[cwd.len+1..^1].replace('\\', '/')
- let gitUrl = getConfigVar(d.conf, "git.url")
- if gitUrl.len > 0:
- let defaultBranch =
- if NimPatch mod 2 == 1: "devel"
- else: "version-$1-$2" % [$NimMajor, $NimMinor]
- let commit = getConfigVar(d.conf, "git.commit", defaultBranch)
- let develBranch = getConfigVar(d.conf, "git.devel", "devel")
- dispA(d.conf, result, "$1", "", [docItemSeeSrc % [
- "path", path.string, "line", $line, "url", gitUrl,
- "commit", commit, "devel", develBranch]])
- proc symbolPriority(k: TSymKind): int =
- result = case k
- of skMacro: -3
- of skTemplate: -2
- of skIterator: -1
- else: 0 # including skProc which have higher priority
- # documentation itself has even higher priority 1
- proc getTypeKind(n: PNode): string =
- case n[2].kind
- of nkEnumTy: "enum"
- of nkObjectTy: "object"
- of nkTupleTy: "tuple"
- else: ""
- proc toLangSymbol(k: TSymKind, n: PNode, baseName: string): LangSymbol =
- ## Converts symbol info (names/types/parameters) in `n` into format
- ## `LangSymbol` convenient for ``rst.nim``/``dochelpers.nim``.
- result.name = baseName.nimIdentNormalize
- result.symKind = k.toHumanStr
- if k in routineKinds:
- var
- paramTypes: seq[string]
- renderParamTypes(paramTypes, n[paramsPos], toNormalize=true)
- let paramNames = renderParamNames(n[paramsPos], toNormalize=true)
- # In some rare cases (system.typeof) parameter type is not set for default:
- doAssert paramTypes.len <= paramNames.len
- for i in 0 ..< paramNames.len:
- if i < paramTypes.len:
- result.parameters.add (paramNames[i], paramTypes[i])
- else:
- result.parameters.add (paramNames[i], "")
- result.parametersProvided = true
- result.outType = renderOutType(n[paramsPos], toNormalize=true)
- if k in {skProc, skFunc, skType, skIterator}:
- # Obtain `result.generics`
- # Use `n[miscPos]` since n[genericParamsPos] does not contain constraints
- var genNode: PNode = nil
- if k == skType:
- genNode = n[1] # FIXME: what is index 1?
- else:
- if n[miscPos].kind != nkEmpty:
- genNode = n[miscPos][1] # FIXME: what is index 1?
- if genNode != nil:
- var literal = ""
- var r: TSrcGen
- initTokRender(r, genNode, {renderNoBody, renderNoComments,
- renderNoPragmas, renderNoProcDefs, renderExpandUsing})
- var kind = tkEof
- while true:
- getNextTok(r, kind, literal)
- if kind == tkEof:
- break
- if kind != tkSpaces:
- result.generics.add(literal.nimIdentNormalize)
- if k == skType: result.symTypeKind = getTypeKind(n)
- proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind, docFlags: DocFlags, nonExports: bool = false) =
- if (docFlags != kForceExport) and not isVisible(d, nameNode): return
- let
- name = getName(nameNode)
- nameEsc = esc(d.target, name)
- var plainDocstring = getPlainDocstring(n) # call here before genRecComment!
- var result = ""
- var literal, plainName = ""
- var kind = tkEof
- var comm: ItemPre
- if n.kind in routineDefs:
- getAllRunnableExamples(d, n, comm)
- else:
- comm.add genRecComment(d, n)
- var r: TSrcGen
- # Obtain the plain rendered string for hyperlink titles.
- initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments,
- renderNoPragmas, renderNoProcDefs, renderExpandUsing})
- while true:
- getNextTok(r, kind, literal)
- if kind == tkEof:
- break
- plainName.add(literal)
- var pragmaNode = getDeclPragma(n)
- if pragmaNode != nil: pragmaNode = findPragma(pragmaNode, wDeprecated)
- inc(d.id)
- let
- plainNameEsc = esc(d.target, plainName.strip)
- typeDescr =
- if k == skType and getTypeKind(n) != "": getTypeKind(n)
- else: k.toHumanStr
- detailedName = typeDescr & " " & (
- if k in routineKinds: plainName else: name)
- uniqueName = if k in routineKinds: plainNameEsc else: nameEsc
- sortName = if k in routineKinds: plainName.strip else: name
- cleanPlainSymbol = renderPlainSymbolName(nameNode)
- complexSymbol = complexName(k, n, cleanPlainSymbol)
- plainSymbolEnc = encodeUrl(cleanPlainSymbol, usePlus = false)
- symbolOrId = d.newUniquePlainSymbol(complexSymbol)
- symbolOrIdEnc = encodeUrl(symbolOrId, usePlus = false)
- deprecationMsg = genDeprecationMsg(d, pragmaNode)
- rstLangSymbol = toLangSymbol(k, n, cleanPlainSymbol)
- # we generate anchors automatically for subsequent use in doc comments
- let lineinfo = rstast.TLineInfo(
- line: nameNode.info.line, col: nameNode.info.col,
- fileIndex: addRstFileIndex(d, nameNode.info))
- addAnchorNim(d.sharedState, external = false, refn = symbolOrId,
- tooltip = detailedName, langSym = rstLangSymbol,
- priority = symbolPriority(k), info = lineinfo,
- module = addRstFileIndex(d, FileIndex d.module.position))
- let renderFlags =
- if nonExports: {renderNoBody, renderNoComments, renderDocComments, renderSyms,
- renderExpandUsing, renderNonExportedFields}
- else: {renderNoBody, renderNoComments, renderDocComments, renderSyms, renderExpandUsing}
- nodeToHighlightedHtml(d, n, result, renderFlags, symbolOrIdEnc)
- let seeSrc = genSeeSrc(d, toFullPath(d.conf, n.info), n.info.line.int)
- d.section[k].secItems.mgetOrPut(cleanPlainSymbol, newSeq[Item]()).add Item(
- descRst: comm,
- sortName: sortName,
- info: lineinfo,
- anchor: symbolOrId,
- detailedName: detailedName,
- name: name,
- substitutions: @[
- "uniqueName", uniqueName,
- "header", result, "itemID", $d.id,
- "header_plain", plainNameEsc, "itemSym", cleanPlainSymbol,
- "itemSymEnc", plainSymbolEnc,
- "itemSymOrIDEnc", symbolOrIdEnc, "seeSrc", seeSrc,
- "deprecationMsg", deprecationMsg])
- let external = d.destFile.AbsoluteFile.relativeTo(d.conf.outDir, '/').changeFileExt(HtmlExt).string
- var attype = ""
- if k in routineKinds and nameNode.kind == nkSym:
- let att = attachToType(d, nameNode.sym)
- if att != nil:
- attype = esc(d.target, att.name.s)
- elif k == skType and nameNode.kind == nkSym and nameNode.sym.typ.kind in {tyEnum, tyBool}:
- let etyp = nameNode.sym.typ
- for e in etyp.n:
- if e.sym.kind != skEnumField: continue
- let plain = renderPlainSymbolName(e)
- let symbolOrId = d.newUniquePlainSymbol(plain)
- setIndexTerm(d[], ieNim, htmlFile = external, id = symbolOrId,
- term = plain, linkTitle = nameNode.sym.name.s & '.' & plain,
- linkDesc = xmltree.escape(getPlainDocstring(e).docstringSummary),
- line = n.info.line.int)
- d.tocSimple[k].add TocItem(
- sortName: sortName,
- content: getConfigVar(d.conf, "doc.item.toc") % [
- "name", name, "header_plain", plainNameEsc,
- "itemSymOrIDEnc", symbolOrIdEnc])
- d.tocTable[k].mgetOrPut(cleanPlainSymbol, newSeq[TocItem]()).add TocItem(
- sortName: sortName,
- content: getConfigVar(d.conf, "doc.item.tocTable") % [
- "name", name, "header_plain", plainNameEsc,
- "itemSymOrID", symbolOrId.replace(",", ",<wbr>"),
- "itemSymOrIDEnc", symbolOrIdEnc])
- setIndexTerm(d[], ieNim, htmlFile = external, id = symbolOrId, term = name,
- linkTitle = detailedName,
- linkDesc = xmltree.escape(plainDocstring.docstringSummary),
- line = n.info.line.int)
- if k == skType and nameNode.kind == nkSym:
- d.types.strTableAdd nameNode.sym
- proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonItem =
- if not isVisible(d, nameNode): return
- var
- name = getNameEsc(d, nameNode)
- comm = genRecComment(d, n)
- r: TSrcGen
- initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments, renderExpandUsing})
- result.json = %{ "name": %name, "type": %($k), "line": %n.info.line.int,
- "col": %n.info.col}
- if comm != nil:
- result.rst = comm
- result.rstField = "description"
- if r.buf.len > 0:
- result.json["code"] = %r.buf
- if k in routineKinds:
- result.json["signature"] = newJObject()
- if n[paramsPos][0].kind != nkEmpty:
- result.json["signature"]["return"] = %($n[paramsPos][0])
- if n[paramsPos].len > 1:
- result.json["signature"]["arguments"] = newJArray()
- for paramIdx in 1 ..< n[paramsPos].len:
- for identIdx in 0 ..< n[paramsPos][paramIdx].len - 2:
- let
- paramName = $n[paramsPos][paramIdx][identIdx]
- paramType = $n[paramsPos][paramIdx][^2]
- if n[paramsPos][paramIdx][^1].kind != nkEmpty:
- let paramDefault = $n[paramsPos][paramIdx][^1]
- result.json["signature"]["arguments"].add %{"name": %paramName, "type": %paramType, "default": %paramDefault}
- else:
- result.json["signature"]["arguments"].add %{"name": %paramName, "type": %paramType}
- if n[pragmasPos].kind != nkEmpty:
- result.json["signature"]["pragmas"] = newJArray()
- for pragma in n[pragmasPos]:
- result.json["signature"]["pragmas"].add %($pragma)
- if n[genericParamsPos].kind != nkEmpty:
- result.json["signature"]["genericParams"] = newJArray()
- for genericParam in n[genericParamsPos]:
- var param = %{"name": %($genericParam)}
- if genericParam.sym.typ.sons.len > 0:
- param["types"] = newJArray()
- for kind in genericParam.sym.typ.sons:
- param["types"].add %($kind)
- result.json["signature"]["genericParams"].add param
- if optGenIndex in d.conf.globalOptions:
- genItem(d, n, nameNode, k, kForceExport)
- proc setDoctype(d: PDoc, n: PNode) =
- ## Processes `{.doctype.}` pragma changing Markdown/RST parsing options.
- if n == nil:
- return
- if n.len != 2:
- localError(d.conf, n.info, errUser,
- "doctype pragma takes exactly 1 argument"
- )
- return
- var dt = ""
- case n[1].kind
- of nkStrLit:
- dt = toLowerAscii(n[1].strVal)
- of nkIdent:
- dt = toLowerAscii(n[1].ident.s)
- else:
- localError(d.conf, n.info, errUser,
- "unknown argument type $1 provided to doctype" % [$n[1].kind]
- )
- return
- case dt
- of "markdown":
- d.sharedState.options.incl roSupportMarkdown
- d.sharedState.options.incl roPreferMarkdown
- of "rstmarkdown":
- d.sharedState.options.incl roSupportMarkdown
- d.sharedState.options.excl roPreferMarkdown
- of "rst":
- d.sharedState.options.excl roSupportMarkdown
- d.sharedState.options.excl roPreferMarkdown
- else:
- localError(d.conf, n.info, errUser,
- (
- "unknown doctype value \"$1\", should be from " &
- "\"RST\", \"Markdown\", \"RstMarkdown\""
- ) % [dt]
- )
- proc checkForFalse(n: PNode): bool =
- result = n.kind == nkIdent and cmpIgnoreStyle(n.ident.s, "false") == 0
- proc traceDeps(d: PDoc, it: PNode) =
- const k = skModule
- if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket:
- let sep = it[0]
- let dir = it[1]
- let a = newNodeI(nkInfix, it.info)
- a.add sep
- a.add dir
- a.add sep # dummy entry, replaced in the loop
- for x in it[2]:
- a[2] = x
- traceDeps(d, a)
- elif it.kind == nkSym and belongsToProjectPackage(d.conf, it.sym):
- let external = externalDep(d, it.sym)
- if d.section[k].finalMarkup != "": d.section[k].finalMarkup.add(", ")
- dispA(d.conf, d.section[k].finalMarkup,
- "<a class=\"reference external\" href=\"$2\">$1</a>",
- "$1", [esc(d.target, external.prettyLink),
- changeFileExt(external, "html")])
- proc exportSym(d: PDoc; s: PSym) =
- const k = exportSection
- if s.kind == skModule and belongsToProjectPackage(d.conf, s):
- let external = externalDep(d, s)
- if d.section[k].finalMarkup != "": d.section[k].finalMarkup.add(", ")
- dispA(d.conf, d.section[k].finalMarkup,
- "<a class=\"reference external\" href=\"$2\">$1</a>",
- "$1", [esc(d.target, external.prettyLink),
- changeFileExt(external, "html")])
- elif s.kind != skModule and s.owner != nil:
- let module = originatingModule(s)
- if belongsToProjectPackage(d.conf, module):
- let
- complexSymbol = complexName(s.kind, s.ast, s.name.s)
- symbolOrId = d.newUniquePlainSymbol(complexSymbol)
- external = externalDep(d, module)
- if d.section[k].finalMarkup != "": d.section[k].finalMarkup.add(", ")
- # XXX proper anchor generation here
- dispA(d.conf, d.section[k].finalMarkup,
- "<a href=\"$2#$3\"><span class=\"Identifier\">$1</span></a>",
- "$1", [esc(d.target, s.name.s),
- changeFileExt(external, "html"),
- symbolOrId])
- proc documentNewEffect(cache: IdentCache; n: PNode): PNode =
- let s = n[namePos].sym
- if tfReturnsNew in s.typ.flags:
- result = newIdentNode(getIdent(cache, "new"), n.info)
- proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, idx: int): PNode =
- let spec = effectSpec(x, effectType)
- if isNil(spec):
- let s = n[namePos].sym
- let actual = s.typ.n[0]
- if actual.len != effectListLen: return
- let real = actual[idx]
- if real == nil: return
- let realLen = real.len
- # warning: hack ahead:
- var effects = newNodeI(nkBracket, n.info, realLen)
- for i in 0..<realLen:
- var t = typeToString(real[i].typ)
- if t.startsWith("ref "): t = substr(t, 4)
- effects[i] = newIdentNode(getIdent(cache, t), n.info)
- # set the type so that the following analysis doesn't screw up:
- effects[i].typ = real[i].typ
- result = newTreeI(nkExprColonExpr, n.info,
- newIdentNode(getIdent(cache, $effectType), n.info), effects)
- proc documentWriteEffect(cache: IdentCache; n: PNode; flag: TSymFlag; pragmaName: string): PNode =
- let s = n[namePos].sym
- let params = s.typ.n
- var effects = newNodeI(nkBracket, n.info)
- for i in 1..<params.len:
- if params[i].kind == nkSym and flag in params[i].sym.flags:
- effects.add params[i]
- if effects.len > 0:
- result = newTreeI(nkExprColonExpr, n.info,
- newIdentNode(getIdent(cache, pragmaName), n.info), effects)
- proc documentRaises*(cache: IdentCache; n: PNode) =
- if n[namePos].kind != nkSym: return
- let pragmas = n[pragmasPos]
- let p1 = documentEffect(cache, n, pragmas, wRaises, exceptionEffects)
- let p2 = documentEffect(cache, n, pragmas, wTags, tagEffects)
- let p3 = documentWriteEffect(cache, n, sfWrittenTo, "writes")
- let p4 = documentNewEffect(cache, n)
- let p5 = documentWriteEffect(cache, n, sfEscapes, "escapes")
- let p6 = documentEffect(cache, n, pragmas, wForbids, forbiddenEffects)
- if p1 != nil or p2 != nil or p3 != nil or p4 != nil or p5 != nil or p6 != nil:
- if pragmas.kind == nkEmpty:
- n[pragmasPos] = newNodeI(nkPragma, n.info)
- if p1 != nil: n[pragmasPos].add p1
- if p2 != nil: n[pragmasPos].add p2
- if p3 != nil: n[pragmasPos].add p3
- if p4 != nil: n[pragmasPos].add p4
- if p5 != nil: n[pragmasPos].add p5
- if p6 != nil: n[pragmasPos].add p6
- proc generateDoc*(d: PDoc, n, orig: PNode, config: ConfigRef, docFlags: DocFlags = kDefault) =
- ## Goes through nim nodes recursively and collects doc comments.
- ## Main function for `doc`:option: command,
- ## which is implemented in ``docgen2.nim``.
- template genItemAux(skind) =
- genItem(d, n, n[namePos], skind, docFlags)
- let showNonExports = optShowNonExportedFields in config.globalOptions
- case n.kind
- of nkPragma:
- let pragmaNode = findPragma(n, wDeprecated)
- d.modDeprecationMsg.add(genDeprecationMsg(d, pragmaNode))
- let doctypeNode = findPragma(n, wDoctype)
- setDoctype(d, doctypeNode)
- of nkCommentStmt: d.modDescPre.add(genComment(d, n))
- of nkProcDef, nkFuncDef:
- when useEffectSystem: documentRaises(d.cache, n)
- genItemAux(skProc)
- of nkMethodDef:
- when useEffectSystem: documentRaises(d.cache, n)
- genItemAux(skMethod)
- of nkIteratorDef:
- when useEffectSystem: documentRaises(d.cache, n)
- genItemAux(skIterator)
- of nkMacroDef: genItemAux(skMacro)
- of nkTemplateDef: genItemAux(skTemplate)
- of nkConverterDef:
- when useEffectSystem: documentRaises(d.cache, n)
- genItemAux(skConverter)
- of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
- for i in 0..<n.len:
- if n[i].kind != nkCommentStmt:
- # order is always 'type var let const':
- genItem(d, n[i], n[i][0],
- succ(skType, ord(n.kind)-ord(nkTypeSection)), docFlags, showNonExports)
- of nkStmtList:
- for i in 0..<n.len: generateDoc(d, n[i], orig, config)
- of nkWhenStmt:
- # generate documentation for the first branch only:
- if not checkForFalse(n[0][0]):
- generateDoc(d, lastSon(n[0]), orig, config)
- of nkImportStmt:
- for it in n: traceDeps(d, it)
- of nkExportStmt:
- for it in n:
- if it.kind == nkSym:
- if d.module != nil and d.module == it.sym.owner:
- generateDoc(d, it.sym.ast, orig, config, kForceExport)
- elif it.sym.ast != nil:
- exportSym(d, it.sym)
- of nkExportExceptStmt: discard "transformed into nkExportStmt by semExportExcept"
- of nkFromStmt, nkImportExceptStmt: traceDeps(d, n[0])
- of nkCallKinds:
- var comm: ItemPre
- getAllRunnableExamples(d, n, comm)
- if comm.len != 0: d.modDescPre.add(comm)
- else: discard
- proc overloadGroupName(s: string, k: TSymKind): string =
- ## Turns a name like `f` into anchor `f-procs-all`
- s & "-" & k.toHumanStr & "s-all"
- proc setIndexTitle(d: PDoc, useMetaTitle: bool) =
- let titleKind = if d.standaloneDoc: ieMarkupTitle else: ieNimTitle
- let external = AbsoluteFile(d.destFile)
- .relativeTo(d.conf.outDir, '/')
- .changeFileExt(HtmlExt)
- .string
- var term, linkTitle: string
- if useMetaTitle and d.meta[metaTitle].len != 0:
- term = d.meta[metaTitleRaw]
- linkTitle = d.meta[metaTitleRaw]
- else:
- let filename = extractFilename(d.filename)
- term =
- if d.standaloneDoc: filename # keep .rst/.md extension
- else: changeFileExt(filename, "") # rm .nim extension
- linkTitle =
- if d.standaloneDoc: term # keep .rst/.md extension
- else: canonicalImport(d.conf, AbsoluteFile d.filename)
- if not d.standaloneDoc:
- linkTitle = "module " & linkTitle
- setIndexTerm(d[], titleKind, htmlFile = external, id = "",
- term = term, linkTitle = linkTitle)
- proc finishGenerateDoc*(d: var PDoc) =
- ## Perform 2nd RST pass for resolution of links/footnotes/headings...
- # copy file map `filenames` to ``rstgen.nim`` for its warnings
- d.filenames = d.sharedState.filenames
- # Main title/subtitle are allowed only in the first RST fragment of document
- var firstRst = PRstNode(nil)
- for fragment in d.modDescPre:
- if fragment.isRst:
- firstRst = fragment.rst
- break
- d.hasToc = d.hasToc or d.sharedState.hasToc
- # in --index:only mode we do NOT want to load other .idx, only write ours:
- let importdoc = optGenIndexOnly notin d.conf.globalOptions and
- optNoImportdoc notin d.conf.globalOptions
- preparePass2(d.sharedState, firstRst, importdoc)
- if optGenIndexOnly in d.conf.globalOptions:
- # Top-level doc.comments may contain titles and :idx: statements:
- for fragment in d.modDescPre:
- if fragment.isRst:
- traverseForIndex(d[], fragment.rst)
- setIndexTitle(d, useMetaTitle = d.standaloneDoc)
- # Symbol-associated doc.comments may contain :idx: statements:
- for k in TSymKind:
- for _, overloadChoices in d.section[k].secItems:
- for item in overloadChoices:
- for fragment in item.descRst:
- if fragment.isRst:
- traverseForIndex(d[], fragment.rst)
- # add anchors to overload groups before RST resolution
- for k in TSymKind:
- if k in routineKinds:
- for plainName, overloadChoices in d.section[k].secItems:
- if overloadChoices.len > 1:
- let refn = overloadGroupName(plainName, k)
- let tooltip = "$1 ($2 overloads)" % [
- k.toHumanStr & " " & plainName, $overloadChoices.len]
- let name = nimIdentBackticksNormalize(plainName)
- # save overload group to ``.idx``
- let external = d.destFile.AbsoluteFile.relativeTo(d.conf.outDir, '/').
- changeFileExt(HtmlExt).string
- setIndexTerm(d[], ieNimGroup, htmlFile = external, id = refn,
- term = name, linkTitle = k.toHumanStr,
- linkDesc = "", line = overloadChoices[0].info.line.int)
- if optGenIndexOnly in d.conf.globalOptions: continue
- addAnchorNim(d.sharedState, external=false, refn, tooltip,
- LangSymbol(symKind: k.toHumanStr,
- name: name,
- isGroup: true),
- priority = symbolPriority(k),
- # select index `0` just to have any meaningful warning:
- info = overloadChoices[0].info,
- module = addRstFileIndex(d, FileIndex d.module.position))
- if optGenIndexOnly in d.conf.globalOptions:
- return
- # Finalize fragments of ``.nim`` or ``.rst`` file
- proc renderItemPre(d: PDoc, fragments: ItemPre, result: var string) =
- for f in fragments:
- case f.isRst:
- of true:
- var resolved = resolveSubs(d.sharedState, f.rst)
- renderRstToOut(d[], resolved, result)
- of false: result &= f.str
- proc cmp(x, y: Item): int = cmpDecimalsIgnoreCase(x.sortName, y.sortName)
- for k in TSymKind:
- # add symbols to section for each `k`, while optionally wrapping
- # overloadable items with the same basic name by ``doc.item2``
- let overloadableNames = toSeq(keys(d.section[k].secItems))
- for plainName in overloadableNames.sorted(cmpDecimalsIgnoreCase):
- var overloadChoices = d.section[k].secItems[plainName]
- overloadChoices.sort(cmp)
- var nameContent = ""
- for item in overloadChoices:
- var itemDesc: string
- renderItemPre(d, item.descRst, itemDesc)
- nameContent.add(
- getConfigVar(d.conf, "doc.item") % (
- item.substitutions & @[
- "desc", itemDesc,
- "name", item.name,
- "itemSymOrID", item.anchor]))
- if k in routineKinds:
- let plainNameEsc1 = esc(d.target, plainName.strip)
- let plainNameEsc2 = esc(d.target, plainName.strip, escMode=emUrl)
- d.section[k].finalMarkup.add(
- getConfigVar(d.conf, "doc.item2") % (
- @["header_plain", plainNameEsc1,
- "overloadGroupName", overloadGroupName(plainNameEsc2, k),
- "content", nameContent]))
- else:
- d.section[k].finalMarkup.add(nameContent)
- d.section[k].secItems.clear
- renderItemPre(d, d.modDescPre, d.modDescFinal)
- d.modDescPre.setLen 0
- # Finalize fragments of ``.json`` file
- for i, entry in d.jEntriesPre:
- if entry.rst != nil:
- let resolved = resolveSubs(d.sharedState, entry.rst)
- var str: string
- renderRstToOut(d[], resolved, str)
- entry.json[entry.rstField] = %str
- d.jEntriesPre[i].rst = nil
- d.jEntriesFinal.add entry.json # generates docs
- setIndexTitle(d, useMetaTitle = d.standaloneDoc)
- completePass2(d.sharedState)
- proc add(d: PDoc; j: JsonItem) =
- if j.json != nil or j.rst != nil: d.jEntriesPre.add j
- proc generateJson*(d: PDoc, n: PNode, includeComments: bool = true) =
- case n.kind
- of nkPragma:
- let doctypeNode = findPragma(n, wDoctype)
- setDoctype(d, doctypeNode)
- of nkCommentStmt:
- if includeComments:
- d.add JsonItem(rst: genComment(d, n), rstField: "comment",
- json: %Table[string, string]())
- else:
- d.modDescPre.add(genComment(d, n))
- of nkProcDef, nkFuncDef:
- when useEffectSystem: documentRaises(d.cache, n)
- d.add genJsonItem(d, n, n[namePos], skProc)
- of nkMethodDef:
- when useEffectSystem: documentRaises(d.cache, n)
- d.add genJsonItem(d, n, n[namePos], skMethod)
- of nkIteratorDef:
- when useEffectSystem: documentRaises(d.cache, n)
- d.add genJsonItem(d, n, n[namePos], skIterator)
- of nkMacroDef:
- d.add genJsonItem(d, n, n[namePos], skMacro)
- of nkTemplateDef:
- d.add genJsonItem(d, n, n[namePos], skTemplate)
- of nkConverterDef:
- when useEffectSystem: documentRaises(d.cache, n)
- d.add genJsonItem(d, n, n[namePos], skConverter)
- of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
- for i in 0..<n.len:
- if n[i].kind != nkCommentStmt:
- # order is always 'type var let const':
- d.add genJsonItem(d, n[i], n[i][0],
- succ(skType, ord(n.kind)-ord(nkTypeSection)))
- of nkStmtList:
- for i in 0..<n.len:
- generateJson(d, n[i], includeComments)
- of nkWhenStmt:
- # generate documentation for the first branch only:
- if not checkForFalse(n[0][0]):
- generateJson(d, lastSon(n[0]), includeComments)
- else: discard
- proc genTagsItem(d: PDoc, n, nameNode: PNode, k: TSymKind): string =
- result = getNameEsc(d, nameNode) & "\n"
- proc generateTags*(d: PDoc, n: PNode, r: var string) =
- case n.kind
- of nkCommentStmt:
- if startsWith(n.comment, "##"):
- let stripped = n.comment.substr(2).strip
- r.add stripped
- of nkProcDef:
- when useEffectSystem: documentRaises(d.cache, n)
- r.add genTagsItem(d, n, n[namePos], skProc)
- of nkFuncDef:
- when useEffectSystem: documentRaises(d.cache, n)
- r.add genTagsItem(d, n, n[namePos], skFunc)
- of nkMethodDef:
- when useEffectSystem: documentRaises(d.cache, n)
- r.add genTagsItem(d, n, n[namePos], skMethod)
- of nkIteratorDef:
- when useEffectSystem: documentRaises(d.cache, n)
- r.add genTagsItem(d, n, n[namePos], skIterator)
- of nkMacroDef:
- r.add genTagsItem(d, n, n[namePos], skMacro)
- of nkTemplateDef:
- r.add genTagsItem(d, n, n[namePos], skTemplate)
- of nkConverterDef:
- when useEffectSystem: documentRaises(d.cache, n)
- r.add genTagsItem(d, n, n[namePos], skConverter)
- of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
- for i in 0..<n.len:
- if n[i].kind != nkCommentStmt:
- # order is always 'type var let const':
- r.add genTagsItem(d, n[i], n[i][0],
- succ(skType, ord(n.kind)-ord(nkTypeSection)))
- of nkStmtList:
- for i in 0..<n.len:
- generateTags(d, n[i], r)
- of nkWhenStmt:
- # generate documentation for the first branch only:
- if not checkForFalse(n[0][0]):
- generateTags(d, lastSon(n[0]), r)
- else: discard
- proc genSection(d: PDoc, kind: TSymKind, groupedToc = false) =
- const sectionNames: array[skModule..skField, string] = [
- "Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Funcs",
- "Methods", "Iterators", "Converters", "Macros", "Templates", "Exports"
- ]
- if d.section[kind].finalMarkup == "": return
- var title = sectionNames[kind]
- d.section[kind].finalMarkup = getConfigVar(d.conf, "doc.section") % [
- "sectionid", $ord(kind), "sectionTitle", title,
- "sectionTitleID", $(ord(kind) + 50), "content", d.section[kind].finalMarkup]
- proc cmp(x, y: TocItem): int = cmpDecimalsIgnoreCase(x.sortName, y.sortName)
- if groupedToc:
- let overloadableNames = toSeq(keys(d.tocTable[kind]))
- for plainName in overloadableNames.sorted(cmpDecimalsIgnoreCase):
- var overloadChoices = d.tocTable[kind][plainName]
- overloadChoices.sort(cmp)
- var content: string
- for item in overloadChoices:
- content.add item.content
- d.toc2[kind].add getConfigVar(d.conf, "doc.section.toc2") % [
- "sectionid", $ord(kind), "sectionTitle", title,
- "sectionTitleID", $(ord(kind) + 50),
- "content", content, "plainName", plainName]
- else:
- for item in d.tocSimple[kind].sorted(cmp):
- d.toc2[kind].add item.content
- let sectionValues = @[
- "sectionID", $ord(kind), "sectionTitleID", $(ord(kind) + 50),
- "sectionTitle", title
- ]
- # Check if the toc has any children
- if d.toc2[kind] != "":
- # Use the dropdown version instead and store the children in the dropdown
- d.toc[kind] = getConfigVar(d.conf, "doc.section.toc") % (sectionValues & @[
- "content", d.toc2[kind]
- ])
- else:
- # Just have the link
- d.toc[kind] = getConfigVar(d.conf, "doc.section.toc_item") % sectionValues
- proc relLink(outDir: AbsoluteDir, destFile: AbsoluteFile, linkto: RelativeFile): string =
- $relativeTo(outDir / linkto, destFile.splitFile().dir, '/')
- proc genOutFile(d: PDoc, groupedToc = false): string =
- var
- code, content: string
- title = ""
- var j = 0
- var toc = ""
- renderTocEntries(d[], j, 1, toc)
- for i in TSymKind:
- var shouldSort = i in routineKinds and groupedToc
- genSection(d, i, shouldSort)
- toc.add(d.toc[i])
- if toc != "" or d.target == outLatex:
- # for Latex $doc.toc will automatically generate TOC if `d.hasToc` is set
- toc = getConfigVar(d.conf, "doc.toc") % ["content", toc]
- for i in TSymKind: code.add(d.section[i].finalMarkup)
- # Extract the title. Non API modules generate an entry in the index table.
- if d.meta[metaTitle].len != 0:
- title = d.meta[metaTitle]
- else:
- title = canonicalImport(d.conf, AbsoluteFile d.filename)
- title = esc(d.target, title)
- var subtitle = ""
- if d.meta[metaSubtitle] != "":
- dispA(d.conf, subtitle, "<h2 class=\"subtitle\">$1</h2>",
- "\\\\\\vspace{0.5em}\\large $1", [esc(d.target, d.meta[metaSubtitle])])
- var groupsection = getConfigVar(d.conf, "doc.body_toc_groupsection")
- let bodyname = if d.hasToc and not d.standaloneDoc and not d.conf.isLatexCmd:
- groupsection.setLen 0
- "doc.body_toc_group"
- elif d.hasToc: "doc.body_toc"
- else: "doc.body_no_toc"
- let seeSrc = genSeeSrc(d, d.filename, 1)
- content = getConfigVar(d.conf, bodyname) % [
- "title", title, "subtitle", subtitle,
- "tableofcontents", toc, "moduledesc", d.modDescFinal, "date", getDateStr(),
- "time", getClockStr(), "content", code,
- "deprecationMsg", d.modDeprecationMsg,
- "theindexhref", relLink(d.conf.outDir, d.destFile.AbsoluteFile,
- theindexFname.RelativeFile),
- "body_toc_groupsection", groupsection, "seeSrc", seeSrc]
- if optCompileOnly notin d.conf.globalOptions:
- # XXX what is this hack doing here? 'optCompileOnly' means raw output!?
- code = getConfigVar(d.conf, "doc.file") % [
- "nimdoccss", relLink(d.conf.outDir, d.destFile.AbsoluteFile,
- nimdocOutCss.RelativeFile),
- "dochackjs", relLink(d.conf.outDir, d.destFile.AbsoluteFile,
- docHackJsFname.RelativeFile),
- "title", title, "subtitle", subtitle, "tableofcontents", toc,
- "moduledesc", d.modDescFinal, "date", getDateStr(), "time", getClockStr(),
- "content", content, "author", d.meta[metaAuthor],
- "version", esc(d.target, d.meta[metaVersion]), "analytics", d.analytics,
- "deprecationMsg", d.modDeprecationMsg]
- else:
- code = content
- result = code
- proc indexFile(d: PDoc): AbsoluteFile =
- let dir = d.conf.outDir
- result = dir / changeFileExt(presentationPath(d.conf,
- AbsoluteFile d.filename),
- IndexExt)
- let (finalDir, _, _) = result.string.splitFile
- createDir(finalDir)
- proc generateIndex*(d: PDoc) =
- if optGenIndex in d.conf.globalOptions:
- let dest = indexFile(d)
- writeIndexFile(d[], dest.string)
- proc updateOutfile(d: PDoc, outfile: AbsoluteFile) =
- if d.module == nil or sfMainModule in d.module.flags: # nil for e.g. for commandRst2Html
- if d.conf.outFile.isEmpty:
- d.conf.outFile = outfile.relativeTo(d.conf.outDir)
- if isAbsolute(d.conf.outFile.string):
- d.conf.outFile = splitPath(d.conf.outFile.string)[1].RelativeFile
- proc writeOutput*(d: PDoc, useWarning = false, groupedToc = false) =
- if optGenIndexOnly in d.conf.globalOptions:
- d.conf.outFile = indexFile(d).relativeTo(d.conf.outDir) # just for display
- return
- runAllExamples(d)
- var content = genOutFile(d, groupedToc)
- if optStdout in d.conf.globalOptions:
- write(stdout, content)
- else:
- template outfile: untyped = d.destFile.AbsoluteFile
- #let outfile = getOutFile2(d.conf, shortenDir(d.conf, filename), outExt)
- let dir = outfile.splitFile.dir
- createDir(dir)
- updateOutfile(d, outfile)
- try:
- writeFile(outfile, content)
- except IOError:
- rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile,
- outfile.string)
- if not d.wroteSupportFiles: # nimdoc.css + dochack.js
- let nimr = $d.conf.getPrefixDir()
- case d.target
- of outHtml:
- copyFile(docCss.interp(nimr = nimr), $d.conf.outDir / nimdocOutCss)
- of outLatex:
- copyFile(docCls.interp(nimr = nimr), $d.conf.outDir / nimdocOutCls)
- if optGenIndex in d.conf.globalOptions:
- let docHackJs2 = getDocHacksJs(nimr, nim = getAppFilename())
- copyFile(docHackJs2, $d.conf.outDir / docHackJs2.lastPathPart)
- d.wroteSupportFiles = true
- proc writeOutputJson*(d: PDoc, useWarning = false) =
- runAllExamples(d)
- var modDesc: string
- for desc in d.modDescFinal:
- modDesc &= desc
- let content = %*{"orig": d.filename,
- "nimble": getPackageName(d.conf, d.filename),
- "moduleDescription": modDesc,
- "entries": d.jEntriesFinal}
- if optStdout in d.conf.globalOptions:
- write(stdout, $content)
- else:
- let dir = d.destFile.splitFile.dir
- createDir(dir)
- var f: File
- if open(f, d.destFile, fmWrite):
- write(f, $content)
- close(f)
- updateOutfile(d, d.destFile.AbsoluteFile)
- else:
- localError(d.conf, newLineInfo(d.conf, AbsoluteFile d.filename, -1, -1),
- warnUser, "unable to open file \"" & d.destFile &
- "\" for writing")
- proc handleDocOutputOptions*(conf: ConfigRef) =
- if optWholeProject in conf.globalOptions:
- # Backward compatibility with previous versions
- # xxx this is buggy when user provides `nim doc --project -o:sub/bar.html main`,
- # it'd write to `sub/bar.html/main.html`
- conf.outDir = AbsoluteDir(conf.outDir / conf.outFile)
- proc commandDoc*(cache: IdentCache, conf: ConfigRef) =
- ## implementation of deprecated ``doc0`` command (without semantic checking)
- handleDocOutputOptions conf
- var ast = parseFile(conf.projectMainIdx, cache, conf)
- if ast == nil: return
- var d = newDocumentor(conf.projectFull, cache, conf, hasToc = true)
- generateDoc(d, ast, ast, conf)
- finishGenerateDoc(d)
- writeOutput(d)
- generateIndex(d)
- proc commandRstAux(cache: IdentCache, conf: ConfigRef;
- filename: AbsoluteFile, outExt: string,
- preferMarkdown: bool) =
- var filen = addFileExt(filename, "txt")
- var d = newDocumentor(filen, cache, conf, outExt, standaloneDoc = true,
- preferMarkdown = preferMarkdown, hasToc = false)
- let rst = parseRst(readFile(filen.string),
- line=LineRstInit, column=ColRstInit,
- conf, d.sharedState)
- d.modDescPre = @[ItemFragment(isRst: true, rst: rst)]
- finishGenerateDoc(d)
- writeOutput(d)
- generateIndex(d)
- proc commandRst2Html*(cache: IdentCache, conf: ConfigRef,
- preferMarkdown=false) =
- commandRstAux(cache, conf, conf.projectFull, HtmlExt, preferMarkdown)
- proc commandRst2TeX*(cache: IdentCache, conf: ConfigRef,
- preferMarkdown=false) =
- commandRstAux(cache, conf, conf.projectFull, TexExt, preferMarkdown)
- proc commandJson*(cache: IdentCache, conf: ConfigRef) =
- ## implementation of a deprecated jsondoc0 command
- var ast = parseFile(conf.projectMainIdx, cache, conf)
- if ast == nil: return
- var d = newDocumentor(conf.projectFull, cache, conf, hasToc = true)
- d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string;
- status: int; content: string) {.gcsafe.} =
- localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1),
- warnUser, "the ':test:' attribute is not supported by this backend")
- generateJson(d, ast)
- finishGenerateDoc(d)
- let json = d.jEntriesFinal
- let content = pretty(json)
- if optStdout in d.conf.globalOptions:
- write(stdout, content)
- else:
- #echo getOutFile(gProjectFull, JsonExt)
- let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt)
- try:
- writeFile(filename, content)
- except IOError:
- rawMessage(conf, errCannotOpenFile, filename.string)
- proc commandTags*(cache: IdentCache, conf: ConfigRef) =
- var ast = parseFile(conf.projectMainIdx, cache, conf)
- if ast == nil: return
- var d = newDocumentor(conf.projectFull, cache, conf, hasToc = true)
- d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string;
- status: int; content: string) {.gcsafe.} =
- localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1),
- warnUser, "the ':test:' attribute is not supported by this backend")
- var
- content = ""
- generateTags(d, ast, content)
- if optStdout in d.conf.globalOptions:
- write(stdout, content)
- else:
- #echo getOutFile(gProjectFull, TagsExt)
- let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt)
- try:
- writeFile(filename, content)
- except IOError:
- rawMessage(conf, errCannotOpenFile, filename.string)
- proc commandBuildIndex*(conf: ConfigRef, dir: string, outFile = RelativeFile"") =
- if optGenIndexOnly in conf.globalOptions:
- return
- var content = mergeIndexes(dir)
- var outFile = outFile
- if outFile.isEmpty: outFile = theindexFname.RelativeFile.changeFileExt("")
- let filename = getOutFile(conf, outFile, HtmlExt)
- let code = getConfigVar(conf, "doc.file") % [
- "nimdoccss", relLink(conf.outDir, filename, nimdocOutCss.RelativeFile),
- "dochackjs", relLink(conf.outDir, filename, docHackJsFname.RelativeFile),
- "title", "Index",
- "subtitle", "", "tableofcontents", "", "moduledesc", "",
- "date", getDateStr(), "time", getClockStr(),
- "content", content, "author", "", "version", "", "analytics", ""]
- # no analytics because context is not available
- try:
- writeFile(filename, code)
- except IOError:
- rawMessage(conf, errCannotOpenFile, filename.string)
- proc commandBuildIndexJson*(conf: ConfigRef, dir: string, outFile = RelativeFile"") =
- var (modules, symbols, docs) = readIndexDir(dir)
- let documents = toSeq(keys(Table[IndexEntry, seq[IndexEntry]](docs)))
- let body = %*({"documents": documents, "modules": modules, "symbols": symbols})
- var outFile = outFile
- if outFile.isEmpty: outFile = theindexFname.RelativeFile.changeFileExt("")
- let filename = getOutFile(conf, outFile, JsonExt)
- try:
- writeFile(filename, $body)
- except IOError:
- rawMessage(conf, errCannotOpenFile, filename.string)
|