## Part of 'koch' responsible for the documentation generation.
import os, strutils, osproc, sets, pathnorm
const
gaCode* = " --doc.googleAnalytics:UA-48159761-1"
# --warning[LockLevel]:off pending #13218
nimArgs = "--warning[LockLevel]:off --hint[Conf]:off --hint[Path]:off --hint[Processing]:off -d:boot --putenv:nimversion=$#" % system.NimVersion
gitUrl = "https://github.com/nim-lang/Nim"
docHtmlOutput = "doc/html"
webUploadOutput = "web/upload"
docHackDir = "tools/dochack"
var nimExe*: string
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 existsFile(result.path): return
for dir in split(getEnv("PATH"), PathSep):
result.path = dir / nim
if existsFile(result.path): return
# assume there is a symlink to the exe or something:
return (nim, false)
proc findNim*(): string = findNimImpl().path
proc exec*(cmd: string, errorcode: int = QuitFailure, additionalPath = "") =
let prevPath = getEnv("PATH")
if additionalPath.len > 0:
var absolute = additionalPath
if not absolute.isAbsolute:
absolute = getCurrentDir() / absolute
echo("Adding to $PATH: ", absolute)
putEnv("PATH", (if prevPath.len > 0: prevPath & PathSep else: "") & absolute)
echo(cmd)
if execShellCmd(cmd) != 0: quit("FAILURE", errorcode)
putEnv("PATH", prevPath)
template inFold*(desc, body) =
if existsEnv("TRAVIS"):
echo "travis_fold:start:" & desc.replace(" ", "_")
body
if existsEnv("TRAVIS"):
echo "travis_fold:end:" & desc.replace(" ", "_")
proc execFold*(desc, cmd: string, errorcode: int = QuitFailure, additionalPath = "") =
## Execute shell command. Add log folding on Travis CI.
# https://github.com/travis-ci/travis-ci/issues/2285#issuecomment-42724719
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 = "") =
let output = outputDir / input.splitFile.name.exe
let cmd = findNim().quoteShell() & " " & mode & " -o:" & output & " " & options & " " & input
execFold(desc, cmd)
const
pdf = """
doc/manual.rst
doc/lib.rst
doc/tut1.rst
doc/tut2.rst
doc/tut3.rst
doc/nimc.rst
doc/niminst.rst
doc/gc.rst
""".splitWhitespace()
rst2html = """
doc/intern.rst
doc/apis.rst
doc/lib.rst
doc/manual.rst
doc/manual_experimental.rst
doc/destructors.rst
doc/tut1.rst
doc/tut2.rst
doc/tut3.rst
doc/nimc.rst
doc/hcr.rst
doc/drnim.rst
doc/overview.rst
doc/filters.rst
doc/tools.rst
doc/niminst.rst
doc/nimgrep.rst
doc/gc.rst
doc/estp.rst
doc/idetools.rst
doc/docgen.rst
doc/koch.rst
doc/backends.rst
doc/nimsuggest.rst
doc/nep1.rst
doc/nims.rst
doc/contributing.rst
doc/codeowners.rst
doc/packaging.rst
doc/manual/var_t_return.rst
""".splitWhitespace()
doc0 = """
lib/system/threads.nim
lib/system/channels.nim
""".splitWhitespace() # ran by `nim doc0` instead of `nim doc`
withoutIndex = """
lib/wrappers/mysql.nim
lib/wrappers/iup.nim
lib/wrappers/sqlite3.nim
lib/wrappers/postgres.nim
lib/wrappers/tinyc.nim
lib/wrappers/odbcsql.nim
lib/wrappers/pcre.nim
lib/wrappers/openssl.nim
lib/posix/posix.nim
lib/posix/linux.nim
lib/posix/termios.nim
lib/js/jscore.nim
""".splitWhitespace()
# some of these are include files so shouldn't be docgen'd
ignoredModules = """
lib/prelude.nim
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_openbsd_amd64.nim
lib/posix/posix_haiku.nim
""".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 doc0: docIgnore.incl a
for a in withoutIndex: docIgnore.incl a
for a in ignoredModules: docIgnore.incl a
# don't ignore these even though in lib/system
const goodSystem = """
lib/system/io.nim
lib/system/nimscript.nim
lib/system/assertions.nim
lib/system/iterators.nim
lib/system/dollars.nim
lib/system/widestrs.nim
""".splitWhitespace()
for a in walkDirRec("lib"):
if a.splitFile.ext != ".nim" or
a.isRelativeTo("lib/pure/includes") or
a.isRelativeTo("lib/genode") or
a.isRelativeTo("lib/deprecated") or
(a.isRelativeTo("lib/system") and a.replace('\\', '/') notin goodSystem) or
a.replace('\\', '/') 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 buildDoc(nimArgs, destPath: string) =
# call nim for the documentation:
var
commands = newSeq[string](rst2html.len + len(doc0) + len(doc) + withoutIndex.len)
i = 0
let nim = findNim().quoteShell()
for d in items(rst2html):
commands[i] = nim & " rst2html $# --git.url:$# -o:$# --index:on $#" %
[nimArgs, gitUrl,
destPath / changeFileExt(splitFile(d).name, "html"), d]
i.inc
for d in items(doc0):
commands[i] = nim & " doc0 $# --git.url:$# -o:$# --index:on $#" %
[nimArgs, gitUrl,
destPath / changeFileExt(splitFile(d).name, "html"), d]
i.inc
for d in items(doc):
var nimArgs2 = nimArgs
if d.isRelativeTo("compiler"):
nimArgs2.add " --docroot"
commands[i] = nim & " doc $# --git.url:$# --outdir:$# --index:on $#" %
[nimArgs2, gitUrl, destPath, d]
i.inc
for d in items(withoutIndex):
commands[i] = nim & " doc2 $# --git.url:$# -o:$# $#" %
[nimArgs, gitUrl,
destPath / changeFileExt(splitFile(d).name, "html"), d]
i.inc
mexec(commands)
exec(nim & " buildIndex -o:$1/theindex.html $1" % [destPath])
proc buildPdfDoc*(nimArgs, destPath: string) =
createDir(destPath)
if os.execShellCmd("pdflatex -version") != 0:
echo "pdflatex not found; no PDF documentation generated"
else:
const pdflatexcmd = "pdflatex -interaction=nonstopmode "
for d in items(pdf):
exec(findNim().quoteShell() & " rst2tex $# $#" % [nimArgs, d])
let tex = splitFile(d).name & ".tex"
removeFile("doc" / tex)
moveFile(tex, "doc" / tex)
# call LaTeX twice to get cross references right:
exec(pdflatexcmd & changeFileExt(d, "tex"))
exec(pdflatexcmd & changeFileExt(d, "tex"))
# delete all the crappy temporary files:
let pdf = splitFile(d).name & ".pdf"
let dest = destPath / pdf
removeFile(dest)
moveFile(dest=dest, source=pdf)
removeFile(changeFileExt(pdf, "aux"))
if existsFile(changeFileExt(pdf, "toc")):
removeFile(changeFileExt(pdf, "toc"))
removeFile(changeFileExt(pdf, "log"))
removeFile(changeFileExt(pdf, "out"))
removeFile(changeFileExt(d, "tex"))
proc buildJS() =
exec(findNim().quoteShell() & " js -d:release --out:$1 tools/nimblepkglist.nim" %
[webUploadOutput / "nimblepkglist.js"])
exec(findNim().quoteShell() & " js " & (docHackDir / "dochack.nim"))
proc buildDocs*(args: string) =
const
docHackJs = "dochack.js"
let
a = nimArgs & " " & args
docHackJsSource = docHackDir / docHackJs
docHackJsDest = docHtmlOutput / docHackJs
buildJS() # This call generates docHackJsSource
let docup = webUploadOutput / NimVersion
createDir(docup)
buildDocSamples(a, docup)
buildDoc(a, docup)
# 'nimArgs' instead of 'a' is correct here because we don't want
# that the offline docs contain the 'gaCode'!
createDir(docHtmlOutput)
buildDocSamples(nimArgs, docHtmlOutput)
buildDoc(nimArgs, docHtmlOutput)
copyFile(docHackJsSource, docHackJsDest)
copyFile(docHackJsSource, docup / docHackJs)