diff --git a/compiler/ast.nim b/compiler/ast.nim index 3d05fd2ca780b..6ac4a7175b409 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1026,6 +1026,26 @@ const defaultAlignment = -1 defaultOffset = -1 + +proc getnimblePkg*(a: PSym): PSym = + result = a + while result != nil: + case result.kind + of skModule: + result = result.owner + assert result.kind == skPackage + of skPackage: + if result.owner == nil: + break + else: + result = result.owner + else: + assert false, $result.kind + +proc getnimblePkgId*(a: PSym): int = + let b = a.getnimblePkg + result = if b == nil: -1 else: b.id + var ggDebug* {.deprecated.}: bool ## convenience switch for trying out things #var # gMainPackageId*: int diff --git a/compiler/commands.nim b/compiler/commands.nim index 6633c1aa27027..9b79dc47a1bcf 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -413,6 +413,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "docseesrcurl": expectArg(conf, switch, arg, pass, info) conf.docSeeSrcUrl = arg + of "docroot": + conf.docRoot = if arg.len == 0: "@default" else: arg of "mainmodule", "m": discard "allow for backwards compatibility, but don't do anything" of "define", "d": diff --git a/compiler/docgen.nim b/compiler/docgen.nim index a20d0ea04edd4..15ff2e167e741 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -48,6 +48,47 @@ type PDoc* = ref TDocumentor ## Alias to type less. +proc nativeToUnix(path: string): string = + doAssert not path.isAbsolute # absolute files need more care for the drive + when DirSep == '\\': + result = replace(path, '\\', '/') + else: result = path + +proc presentationPath*(conf: ConfigRef, file: AbsoluteFile, isTitle = false): 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 "@default": # using `@` instead of `$` to avoid shell quoting complications + 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: + doAssert conf.docRoot.isAbsolute, conf.docRoot # or globalError + doAssert conf.docRoot.existsDir, conf.docRoot + result = relativeTo(file, conf.docRoot.AbsoluteDir) + else: + bail() + if isTitle: + result = result.string.nativeToUnix.RelativeFile + else: + result = result.string.replace("..", "@@").RelativeFile ## refs #13223 + doAssert not result.isEmpty + proc whichType(d: PDoc; n: PNode): PSym = if n.kind == nkSym: if d.types.strTableContains(n.sym): @@ -175,7 +216,7 @@ proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef, if execShellCmd(c2) != status: rawMessage(conf, errGenerated, "executing of external program failed: " & c2) result.emitted = initIntSet() - result.destFile = getOutFile2(conf, relativeTo(filename, conf.projectPath), + result.destFile = getOutFile2(conf, presentationPath(conf, filename), outExt, htmldocsDir, false) result.thisDir = result.destFile.splitFile.dir @@ -295,14 +336,13 @@ proc getPlainDocstring(n: PNode): string = if result.len > 0: return proc belongsToPackage(conf: ConfigRef; module: PSym): bool = - result = module.kind == skModule and module.owner != nil and - module.owner.id == conf.mainPackageId + result = module.kind == skModule and module.getnimblePkgId == conf.mainPackageId proc externalDep(d: PDoc; module: PSym): string = - if optWholeProject in d.conf.globalOptions: + 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, full.relativeTo(d.conf.projectPath), HtmlExt, - htmldocsDir, sfMainModule notin module.flags) + let tmp = getOutFile2(d.conf, presentationPath(d.conf, full), HtmlExt, + htmldocsDir, sfMainModule notin module.flags) result = relativeTo(tmp, d.thisDir, '/').string else: result = extractFilename toFullPath(d.conf, FileIndex module.position) @@ -1031,11 +1071,12 @@ proc genOutFile(d: PDoc): Rope = # Extract the title. Non API modules generate an entry in the index table. if d.meta[metaTitle].len != 0: title = d.meta[metaTitle] - let external = AbsoluteFile(d.filename).relativeTo(d.conf.projectPath, '/').changeFileExt(HtmlExt).string + let external = presentationPath(d.conf, AbsoluteFile d.filename).changeFileExt(HtmlExt).string.nativeToUnix setIndexTerm(d[], external, "", title) else: # Modules get an automatic title for the HTML, but no entry in the index. - title = extractFilename(changeFileExt(d.filename, "")) + # better than `extractFilename(changeFileExt(d.filename, ""))` as it disambiguates dups + title = $presentationPath(d.conf, AbsoluteFile d.filename, isTitle = true).changeFileExt("") let bodyname = if d.hasToc and not d.isPureRst: "doc.body_toc_group" elif d.hasToc: "doc.body_toc" @@ -1060,10 +1101,14 @@ proc generateIndex*(d: PDoc) = let dir = if not d.conf.outDir.isEmpty: d.conf.outDir else: d.conf.projectPath / htmldocsDir createDir(dir) - let dest = dir / changeFileExt(relativeTo(AbsoluteFile d.filename, - d.conf.projectPath), IndexExt) + let dest = dir / changeFileExt(presentationPath(d.conf, AbsoluteFile d.filename), IndexExt) writeIndexFile(d[], dest.string) +proc updateOutfile(d: PDoc, outfile: AbsoluteFile) = + if d.module == nil or sfMainModule in d.module.flags: # nil for eg for commandRst2Html + if d.conf.outFile.isEmpty and not d.conf.outDir.isEmpty: + d.conf.outFile = outfile.relativeTo(d.conf.outDir) + proc writeOutput*(d: PDoc, useWarning = false) = runAllExamples(d) var content = genOutFile(d) @@ -1073,7 +1118,7 @@ proc writeOutput*(d: PDoc, useWarning = false) = template outfile: untyped = d.destFile #let outfile = getOutFile2(d.conf, shortenDir(d.conf, filename), outExt, htmldocsDir) createDir(outfile.splitFile.dir) - d.conf.outFile = outfile.extractFilename.RelativeFile + updateOutfile(d, outfile) if not writeRope(content, outfile): rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile, outfile.string) @@ -1100,7 +1145,7 @@ proc writeOutputJson*(d: PDoc, useWarning = false) = if open(f, d.destFile.string, fmWrite): write(f, $content) close(f) - d.conf.outFile = d.destFile.extractFilename.RelativeFile + updateOutfile(d, d.destFile) else: localError(d.conf, newLineInfo(d.conf, AbsoluteFile d.filename, -1, -1), warnUser, "unable to open file \"" & d.destFile.string & diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim index aa4d9cbef497c..b5ef7e32d9558 100644 --- a/compiler/docgen2.nim +++ b/compiler/docgen2.nim @@ -22,8 +22,8 @@ type config: ConfigRef PGen = ref TGen -template shouldProcess(g): bool = - (g.module.owner.id == g.doc.conf.mainPackageId and optWholeProject in g.doc.conf.globalOptions) or +proc shouldProcess(g: PGen): bool = + (optWholeProject in g.doc.conf.globalOptions and g.module.getnimblePkgId == g.doc.conf.mainPackageId) or sfMainModule in g.module.flags or g.config.projectMainIdx == g.module.info.fileIndex template closeImpl(body: untyped) {.dirty.} = diff --git a/compiler/main.nim b/compiler/main.nim index 5efdd87af91e9..c8bdc94475660 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -293,7 +293,9 @@ proc mainCommand*(graph: ModuleGraph) = for s in definedSymbolNames(conf.symbols): definedSymbols.elems.add(%s) var libpaths = newJArray() + var lazyPaths = newJArray() for dir in conf.searchPaths: libpaths.elems.add(%dir.string) + for dir in conf.lazyPaths: lazyPaths.elems.add(%dir.string) var hints = newJObject() # consider factoring with `listHints` for a in hintMin..hintMax: @@ -311,6 +313,7 @@ proc mainCommand*(graph: ModuleGraph) = (key: "project_path", val: %conf.projectFull.string), (key: "defined_symbols", val: definedSymbols), (key: "lib_paths", val: %libpaths), + (key: "lazyPaths", val: %lazyPaths), (key: "outdir", val: %conf.outDir.string), (key: "out", val: %conf.outFile.string), (key: "nimcache", val: %getNimcacheDir(conf).string), diff --git a/compiler/modules.nim b/compiler/modules.nim index 9ef9dfead47fa..47d946a01bba5 100644 --- a/compiler/modules.nim +++ b/compiler/modules.nim @@ -39,7 +39,8 @@ proc partialInitModule(result: PSym; graph: ModuleGraph; fileIdx: FileIndex; fil # but starting with version 0.20 we now produce a fake Nimble package instead # to resolve the conflicts: let pck3 = fakePackageName(graph.config, filename) - packSym = newSym(skPackage, getIdent(graph.cache, pck3), nil, result.info) + # this makes the new `packSym`'s owner be the original `packSym` + packSym = newSym(skPackage, getIdent(graph.cache, pck3), packSym, result.info) initStrTable(packSym.tab) graph.packageSyms.strTableAdd(packSym) diff --git a/compiler/options.nim b/compiler/options.nim index d8f6fd0b9d0f9..c39ffa792b0f3 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -263,6 +263,7 @@ type implicitIncludes*: seq[string] # modules that are to be implicitly included docSeeSrcUrl*: string # if empty, no seeSrc will be generated. \ # The string uses the formatting variables `path` and `line`. + docRoot*: string ## see nim --fullhelp for --docRoot # the used compiler cIncludes*: seq[AbsoluteDir] # directories to search for included files @@ -656,6 +657,24 @@ template patchModule(conf: ConfigRef) {.dirty.} = let ov = conf.moduleOverrides[key] if ov.len > 0: result = AbsoluteFile(ov) +when (NimMajor, NimMinor) < (1, 1) or not declared(isRelativeTo): + proc isRelativeTo(path, base: string): bool = + # pending #13212 use os.isRelativeTo + let path = path.normalizedPath + let base = base.normalizedPath + let ret = relativePath(path, base) + result = path.len > 0 and not ret.startsWith ".." + +proc getRelativePathFromConfigPath*(conf: ConfigRef; f: AbsoluteFile): RelativeFile = + let f = $f + template search(paths) = + for it in paths: + let it = $it + if f.isRelativeTo(it): + return relativePath(f, it).RelativeFile + search(conf.searchPaths) + search(conf.lazyPaths) + proc findFile*(conf: ConfigRef; f: string; suppressStdlib = false): AbsoluteFile = if f.isAbsolute: result = if f.existsFile: AbsoluteFile(f) else: AbsoluteFile"" diff --git a/compiler/packagehandling.nim b/compiler/packagehandling.nim index e807ae3774b11..2243e70634797 100644 --- a/compiler/packagehandling.nim +++ b/compiler/packagehandling.nim @@ -15,10 +15,8 @@ iterator myParentDirs(p: string): string = if current.len == 0: break yield current -proc resetPackageCache*(conf: ConfigRef) = - conf.packageCache = newPackageCache() - -proc getPackageName*(conf: ConfigRef; path: string): string = +proc getNimbleFile*(conf: ConfigRef; path: string): string = + ## returns absolute path to nimble file, eg: /pathto/cligen.nimble var parents = 0 block packageSearch: for d in myParentDirs(path): @@ -27,7 +25,7 @@ proc getPackageName*(conf: ConfigRef; path: string): string = return conf.packageCache[d] inc parents for file in walkFiles(d / "*.nimble"): - result = file.splitFile.name + result = file break packageSearch # we also store if we didn't find anything: when not defined(nimNoNilSeqs): @@ -38,6 +36,11 @@ proc getPackageName*(conf: ConfigRef; path: string): string = dec parents if parents <= 0: break +proc getPackageName*(conf: ConfigRef; path: string): string = + ## returns nimble package name, eg: `cligen` + let path = getNimbleFile(conf, path) + result = path.splitFile.name + proc fakePackageName*(conf: ConfigRef; path: AbsoluteFile): string = # Convert `path` so that 2 modules with same name # in different directory get different name and they can be diff --git a/compiler/pathutils.nim b/compiler/pathutils.nim index e3dd69628856f..1c35eb0b23b51 100644 --- a/compiler/pathutils.nim +++ b/compiler/pathutils.nim @@ -67,7 +67,7 @@ when true: proc `/`*(base: AbsoluteDir; f: RelativeFile): AbsoluteFile = let base = postProcessBase(base) - assert(not isAbsolute(f.string)) + assert(not isAbsolute(f.string), f.string) result = AbsoluteFile newStringOfCap(base.string.len + f.string.len) var state = 0 addNormalizePath(base.string, result.string, state) @@ -83,7 +83,10 @@ when true: proc relativeTo*(fullPath: AbsoluteFile, baseFilename: AbsoluteDir; sep = DirSep): RelativeFile = - RelativeFile(relativePath(fullPath.string, baseFilename.string, sep)) + # this currently fails for `tests/compilerapi/tcompilerapi.nim` + # it's needed otherwise would returns an absolute path + # assert not baseFilename.isEmpty, $fullPath + result = RelativeFile(relativePath(fullPath.string, baseFilename.string, sep)) proc toAbsolute*(file: string; base: AbsoluteDir): AbsoluteFile = if isAbsolute(file): result = AbsoluteFile(file) diff --git a/doc/advopt.txt b/doc/advopt.txt index 9dd59333cd2c5..64d7a66e3721b 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -67,6 +67,13 @@ Advanced options: --clib:LIBNAME link an additional C library (you should omit platform-specific extensions) --project document the whole project (doc2) + --docRoot:path nim doc --docRoot:/foo --project --outdir:docs /foo/sub/main.nim + generates: docs/sub/main.html + if path == @pkg, will use nimble file enclosing dir + if path == @path, will use first matching dir in --path + if path == @default (the default and most useful), will use + best match among @pkg,@path. + if these are nonexistant, will use project path --docSeeSrcUrl:url activate 'see source' for doc and doc2 commands (see doc.item.seesrc in config/nimdoc.cfg) --docInternal also generate documentation for non-exported symbols diff --git a/nimdoc/testproject/expected/subdir/subdir_b/utils.html b/nimdoc/testproject/expected/subdir/subdir_b/utils.html index dc9143390365b..75cb2508f7861 100644 --- a/nimdoc/testproject/expected/subdir/subdir_b/utils.html +++ b/nimdoc/testproject/expected/subdir/subdir_b/utils.html @@ -17,7 +17,7 @@ -