1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
|
## Part of 'koch' responsible for the documentation generation.
import std/[os, strutils, osproc, sets, pathnorm, sequtils, pegs]
import officialpackages
export exec
when defined(nimPreviewSlimSystem):
import std/assertions
from std/private/globs import nativeToUnixPath, walkDirRecFilter, PathEntry
import "../compiler/nimpaths"
const
gaCode* = " --doc.googleAnalytics:UA-48159761-1"
paCode* = ""
# errormax: subsequent errors are probably consequences of 1st one; a simple
# bug could cause unlimited number of errors otherwise, hard to debug in CI.
docDefines = "-d:nimExperimentalLinenoiseExtra"
nimArgs = "--errormax:3 --hint:Conf:off --hint:Path:off --hint:Processing:off --hint:XDeclaredButNotUsed:off --warning:UnusedImport:off -d:boot --putenv:nimversion=$# $#" % [system.NimVersion, docDefines]
gitUrl = "https://github.com/nim-lang/Nim"
docHtmlOutput = "doc/html"
webUploadOutput = "web/upload"
var nimExe*: string
const allowList = ["jsbigints.nim", "jsheaders.nim", "jsformdata.nim", "jsfetch.nim", "jsutils.nim"]
template isJsOnly(file: string): bool =
file.isRelativeTo("lib/js") or
file.extractFilename in allowList
proc exe*(f: string): string =
result = addFileExt(f, ExeExt)
when defined(windows):
result = result.replace('/','\\')
proc findNimImpl*(): tuple[path: string, ok: bool] =
if nimExe.len > 0: return (nimExe, true)
let nim = "nim".exe
result.path = "bin" / nim
result.ok = true
if fileExists(result.path): return
for dir in split(getEnv("PATH"), PathSep):
result.path = dir / nim
if fileExists(result.path): return
# assume there is a symlink to the exe or something:
return (nim, false)
proc findNim*(): string = findNimImpl().path
template inFold*(desc, body) =
if existsEnv("GITHUB_ACTIONS"):
echo "::group::" & desc
elif existsEnv("TF_BUILD"):
echo "##[group]" & desc
body
if existsEnv("GITHUB_ACTIONS"):
echo "::endgroup::"
elif existsEnv("TF_BUILD"):
echo "##[endgroup]"
proc execFold*(desc, cmd: string, errorcode: int = QuitFailure, additionalPath = "") =
## Execute shell command. Add log folding for various CI services.
let desc = if desc.len == 0: cmd else: desc
inFold(desc):
exec(cmd, errorcode, additionalPath)
proc execCleanPath*(cmd: string,
additionalPath = ""; errorcode: int = QuitFailure) =
# simulate a poor man's virtual environment
let prevPath = getEnv("PATH")
when defined(windows):
let cleanPath = r"$1\system32;$1;$1\System32\Wbem" % getEnv"SYSTEMROOT"
else:
const cleanPath = r"/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin"
putEnv("PATH", cleanPath & PathSep & additionalPath)
echo(cmd)
if execShellCmd(cmd) != 0: quit("FAILURE", errorcode)
putEnv("PATH", prevPath)
proc nimexec*(cmd: string) =
# Consider using `nimCompile` instead
exec findNim().quoteShell() & " " & cmd
proc nimCompile*(input: string, outputDir = "bin", mode = "c", options = "") =
let output = outputDir / input.splitFile.name.exe
let cmd = findNim().quoteShell() & " " & mode & " -o:" & output & " " & options & " " & input
exec cmd
proc nimCompileFold*(desc, input: string, outputDir = "bin", mode = "c", options = "", outputName = "") =
let outputName2 = if outputName.len == 0: input.splitFile.name.exe else: outputName.exe
let output = outputDir / outputName2
let cmd = findNim().quoteShell() & " " & mode & " -o:" & output & " " & options & " " & input
execFold(desc, cmd)
const officialPackagesMarkdown = """
""".splitWhitespace()
proc getMd2html(): seq[string] =
for a in walkDirRecFilter("doc"):
let path = a.path
if a.kind == pcFile and path.splitFile.ext == ".md" and path.lastPathPart notin
["docs.md",
"docstyle.md" # docstyle.md shouldn't be converted to html separately;
# it's included in contributing.md.
]:
# `docs` is redundant with `overview`, might as well remove that file?
result.add path
for md in officialPackagesMarkdown:
result.add md
doAssert "doc/manual/var_t_return.md".unixToNativePath in result # sanity check
const
mdPdfList = """
manual.md
lib.md
tut1.md
tut2.md
tut3.md
nimc.md
niminst.md
mm.md
""".splitWhitespace().mapIt("doc" / it)
withoutIndex = """
lib/wrappers/tinyc.nim
lib/wrappers/pcre.nim
lib/wrappers/openssl.nim
lib/posix/posix.nim
lib/posix/linux.nim
lib/posix/termios.nim
""".splitWhitespace()
# some of these are include files so shouldn't be docgen'd
ignoredModules = """
lib/pure/future.nim
lib/pure/collections/hashcommon.nim
lib/pure/collections/tableimpl.nim
lib/pure/collections/setimpl.nim
lib/pure/ioselects/ioselectors_kqueue.nim
lib/pure/ioselects/ioselectors_select.nim
lib/pure/ioselects/ioselectors_poll.nim
lib/pure/ioselects/ioselectors_epoll.nim
lib/posix/posix_macos_amd64.nim
lib/posix/posix_other.nim
lib/posix/posix_nintendoswitch.nim
lib/posix/posix_nintendoswitch_consts.nim
lib/posix/posix_linux_amd64.nim
lib/posix/posix_linux_amd64_consts.nim
lib/posix/posix_other_consts.nim
lib/posix/posix_freertos_consts.nim
lib/posix/posix_openbsd_amd64.nim
lib/posix/posix_haiku.nim
lib/pure/md5.nim
lib/std/sha1.nim
lib/pure/htmlparser.nim
""".splitWhitespace()
officialPackagesList = """
""".splitWhitespace()
officialPackagesListWithoutIndex = """
""".splitWhitespace()
when (NimMajor, NimMinor) < (1, 1) or not declared(isRelativeTo):
proc isRelativeTo(path, base: string): bool =
let path = path.normalizedPath
let base = base.normalizedPath
let ret = relativePath(path, base)
result = path.len > 0 and not ret.startsWith ".."
proc getDocList(): seq[string] =
##
var docIgnore: HashSet[string]
for a in withoutIndex: docIgnore.incl a
for a in ignoredModules: docIgnore.incl a
# don't ignore these even though in lib/system (not include files)
const goodSystem = """
lib/system/nimscript.nim
lib/system/assertions.nim
lib/system/iterators.nim
lib/system/exceptions.nim
lib/system/dollars.nim
lib/system/ctypes.nim
""".splitWhitespace()
proc follow(a: PathEntry): bool =
result = a.path.lastPathPart notin ["nimcache", htmldocsDirname,
"includes", "deprecated", "genode"] and
not a.path.isRelativeTo("lib/fusion") # fusion was un-bundled but we need to keep this in case user has it installed
for entry in walkDirRecFilter("lib", follow = follow):
let a = entry.path
if entry.kind != pcFile or a.splitFile.ext != ".nim" or
(a.isRelativeTo("lib/system") and a.nativeToUnixPath notin goodSystem) or
a.nativeToUnixPath in docIgnore:
continue
result.add a
result.add normalizePath("nimsuggest/sexp.nim")
let doc = getDocList()
proc sexec(cmds: openArray[string]) =
## Serial queue wrapper around exec.
for cmd in cmds:
echo(cmd)
let (outp, exitCode) = osproc.execCmdEx(cmd)
if exitCode != 0: quit outp
proc mexec(cmds: openArray[string]) =
## Multiprocessor version of exec
let r = execProcesses(cmds, {poStdErrToStdOut, poParentStreams, poEchoCmd})
if r != 0:
echo "external program failed, retrying serial work queue for logs!"
sexec(cmds)
proc buildDocSamples(nimArgs, destPath: string) =
## Special case documentation sample proc.
##
## TODO: consider integrating into the existing generic documentation builders
## now that we have a single `doc` command.
exec(findNim().quoteShell() & " doc $# -o:$# $#" %
[nimArgs, destPath / "docgen_sample.html", "doc" / "docgen_sample.nim"])
proc buildDocPackages(nimArgs, destPath: string, indexOnly: bool) =
# compiler docs; later, other packages (perhaps tools, testament etc)
let nim = findNim().quoteShell()
# to avoid broken links to manual from compiler dir, but a multi-package
# structure could be supported later
proc docProject(outdir, options, mainproj: string) =
exec("$nim doc --project --outdir:$outdir $nimArgs --git.url:$gitUrl $index $options $mainproj" % [
"nim", nim,
"outdir", outdir,
"nimArgs", nimArgs,
"gitUrl", gitUrl,
"options", options,
"mainproj", mainproj,
"index", if indexOnly: "--index:only" else: ""
])
let extra = "-u:boot"
# xxx keep in sync with what's in $nim_prs_D/config/nimdoc.cfg, or, rather,
# start using nims instead of nimdoc.cfg
docProject(destPath/"compiler", extra, "compiler/index.nim")
proc buildDoc(nimArgs, destPath: string, indexOnly: bool) =
# call nim for the documentation:
let rst2html = getMd2html()
var
commands = newSeq[string](rst2html.len + len(doc) + withoutIndex.len +
officialPackagesList.len + officialPackagesListWithoutIndex.len)
i = 0
let nim = findNim().quoteShell()
let index = if indexOnly: "--index:only" else: ""
for d in items(rst2html):
commands[i] = nim & " md2html $# --git.url:$# -o:$# $# $#" %
[nimArgs, gitUrl,
destPath / changeFileExt(splitFile(d).name, "html"), index, d]
i.inc
for d in items(doc):
let extra = if isJsOnly(d): "--backend:js" else: ""
var nimArgs2 = nimArgs
if d.isRelativeTo("compiler"): doAssert false
commands[i] = nim & " doc $# $# --git.url:$# --outdir:$# $# $#" %
[extra, nimArgs2, gitUrl, destPath, index, d]
i.inc
for d in items(withoutIndex):
commands[i] = nim & " doc $# --git.url:$# -o:$# $#" %
[nimArgs, gitUrl,
destPath / changeFileExt(splitFile(d).name, "html"), d]
i.inc
for d in items(officialPackagesList):
var nimArgs2 = nimArgs
if d.isRelativeTo("compiler"): doAssert false
commands[i] = nim & " doc $# --outdir:$# --index:on $#" %
[nimArgs2, destPath, d]
i.inc
for d in items(officialPackagesListWithoutIndex):
commands[i] = nim & " doc $# -o:$# $#" %
[nimArgs,
destPath / changeFileExt(splitFile(d).name, "html"), d]
i.inc
mexec(commands)
proc nim2pdf(src: string, dst: string, nimArgs: string) =
# xxx expose as a `nim` command or in some other reusable way.
let outDir = "build" / "xelatextmp" # xxx factor pending https://github.com/timotheecour/Nim/issues/616
# note: this will generate temporary files in gitignored `outDir`: aux toc log out tex
exec("$# md2tex $# --outdir:$# $#" % [findNim().quoteShell(), nimArgs, outDir.quoteShell, src.quoteShell])
let texFile = outDir / src.lastPathPart.changeFileExt("tex")
for i in 0..<3: # call LaTeX three times to get cross references right:
let xelatexLog = outDir / "xelatex.log"
# `>` should work on windows, if not, we can use `execCmdEx`
let cmd = "xelatex -interaction=nonstopmode -output-directory=$# $# > $#" % [outDir.quoteShell, texFile.quoteShell, xelatexLog.quoteShell]
exec(cmd) # on error, user can inspect `xelatexLog`
if i == 1: # build .ind file
var texFileBase = texFile
texFileBase.removeSuffix(".tex")
let cmd = "makeindex $# > $#" % [
texFileBase.quoteShell, xelatexLog.quoteShell]
exec(cmd)
moveFile(texFile.changeFileExt("pdf"), dst)
proc buildPdfDoc*(args: string, destPath: string) =
let args = nimArgs & " " & args
var pdfList: seq[string]
createDir(destPath)
if os.execShellCmd("xelatex -version") != 0:
doAssert false, "xelatex not found" # or, raise an exception
else:
for src in items(mdPdfList):
let dst = destPath / src.lastPathPart.changeFileExt("pdf")
pdfList.add dst
nim2pdf(src, dst, args)
echo "\nOutput PDF files: \n ", pdfList.join(" ") # because `nim2pdf` is a bit verbose
proc buildJS(): string =
let nim = findNim()
exec("$# js -d:release --out:$# tools/nimblepkglist.nim" %
[nim.quoteShell(), webUploadOutput / "nimblepkglist.js"])
# xxx deadcode? and why is it only for webUploadOutput, not for local docs?
result = getDocHacksJs(nimr = getCurrentDir(), nim)
proc buildDocsDir*(args: string, dir: string) =
let args = nimArgs & " " & args
let docHackJsSource = buildJS()
#gitClonePackages(@["asyncftpclient", "punycode", "smtp", "db_connector", "checksums", "atlas", "htmlparser"])
createDir(dir)
buildDocSamples(args, dir)
# generate `.idx` files and top-level `theindex.html`:
buildDoc(args, dir, indexOnly=true) # bottleneck
let nim = findNim().quoteShell()
exec(nim & " buildIndex -o:$1/theindex.html $1" % [dir])
# caveat: this works so long it's called before `buildDocPackages` which
# populates `compiler/` with unrelated idx files that shouldn't be in index,
# so should work in CI but you may need to remove your generated html files
# locally after calling `./koch docs`. The clean fix would be for `idx` files
# to be transient with `--project` (eg all in memory).
buildDocPackages(args, dir, indexOnly=true)
# generate HTML and package-level `theindex.html`:
buildDoc(args, dir, indexOnly=false) # bottleneck
buildDocPackages(args, dir, indexOnly=false)
copyFile(dir / "overview.html", dir / "index.html")
copyFile(docHackJsSource, dir / docHackJsSource.lastPathPart)
proc buildDocs*(args: string, localOnly = false, localOutDir = "") =
let localOutDir =
if localOutDir.len == 0:
docHtmlOutput
else:
localOutDir
var args = args
if not localOnly:
buildDocsDir(args, webUploadOutput / NimVersion)
let gaFilter = peg"@( y'--doc.googleAnalytics:' @(\s / $) )"
args = args.replace(gaFilter)
buildDocsDir(args, localOutDir)
|