From 06c7025da8559accc6d0d0d6d4c7b0fe82e5b765 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Thu, 9 Jan 2025 01:19:20 -0700 Subject: [PATCH] Improve CLI interactions and caching URL (#1320) * add ux improvements for deps and other minor changes * cleanup * cleanup * revert getDownloadInfo * Revert "revert getDownloadInfo" This reverts commit 742761c5bdbe95c1aba137dbabc9c66c1a245e44. * cleanup options * cleanup options * cleanup options * cleanup options * print verify dependencies * prettify list * prettify list * prettify list * prettify list * prettify list * prettify list * prettify list * prettify list * prettify list * prettify list * prettify list --- src/nimble.nim | 109 ++++++++++++++++++++-------- src/nimblepkg/cli.nim | 6 ++ src/nimblepkg/deps.nim | 6 +- src/nimblepkg/download.nim | 129 ++++++++++++++++++++-------------- src/nimblepkg/options.nim | 64 ++++++++++++----- src/nimblepkg/packageinfo.nim | 29 ++++---- src/nimblepkg/publish.nim | 82 ++++++++++++++++++++- src/nimblepkg/urls.nim | 14 +++- src/nimblepkg/vcstools.nim | 16 +++++ tests/testscommon.nim | 3 +- tests/tissues.nim | 2 +- tests/ttaskdeps.nim | 1 + 12 files changed, 341 insertions(+), 120 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 639ccf77..dcdba58d 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -57,7 +57,7 @@ proc displaySatisfiedMsg(solvedPkgs: seq[SolvedPackage], pkgToInstall: seq[(stri for pkg in solvedPkgs: if pkg.pkgName notin pkgToInstall.mapIt(it[0]): for req in pkg.requirements: - displayInfo(pkgDepsAlreadySatisfiedMsg(req)) + displayInfo(pkgDepsAlreadySatisfiedMsg(req), MediumPriority) proc displayUsingSpecialVersionWarning(solvedPkgs: seq[SolvedPackage], options: Options) = var messages = newSeq[string]() @@ -182,7 +182,7 @@ proc processFreeDependencies(pkgInfo: PackageInfo, display("Verifying", "dependencies for $1@$2" % [pkgInfo.basicInfo.name, $pkgInfo.basicInfo.version], - priority = HighPriority) + priority = MediumPriority) var reverseDependencies: seq[PackageBasicInfo] = @[] @@ -208,7 +208,7 @@ proc processFreeDependencies(pkgInfo: PackageInfo, resolvedDep.name) if not found: - display("Installing", $resolvedDep, priority = HighPriority) + display("Installing", $resolvedDep, priority = MediumPriority) let toInstall = @[(resolvedDep.name, resolvedDep.ver)] let (packages, installedPkg) = install(toInstall, options, doPrompt = false, first = false, fromLockFile = false, @@ -230,7 +230,7 @@ proc processFreeDependencies(pkgInfo: PackageInfo, # This package has been installed so we add it to our pkgList. pkgList.add pkg else: - displayInfo(pkgDepsAlreadySatisfiedMsg(dep)) + displayInfo(pkgDepsAlreadySatisfiedMsg(dep), MediumPriority) result.incl pkg # Process the dependencies of this dependency. let fullInfo = pkg.toFullInfo(options) @@ -512,13 +512,13 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, display("Installing", "$1@$2" % [pkginfo.basicInfo.name, $pkginfo.basicInfo.version], - priority = HighPriority) + priority = MediumPriority) let oldPkg = pkgInfo.packageExists(options) if oldPkg.isSome: # In the case we already have the same package in the cache then only merge # the new package special versions to the old one. - displayWarning(pkgAlreadyExistsInTheCacheMsg(pkgInfo)) + displayWarning(pkgAlreadyExistsInTheCacheMsg(pkgInfo), MediumPriority) if not options.useSatSolver: #The dep path is not created when using the sat solver as packages are collected upfront var oldPkg = oldPkg.get oldPkg.metaData.specialVersions.incl pkgInfo.metaData.specialVersions @@ -615,7 +615,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, pkgInfo.isInstalled = true - displaySuccess(pkgInstalledMsg(pkgInfo.basicInfo.name)) + displaySuccess(pkgInstalledMsg(pkgInfo.basicInfo.name), MediumPriority) result.deps.incl pkgInfo result.pkg = pkgInfo @@ -1023,7 +1023,7 @@ proc search(options: Options) = var found = false template onFound {.dirty.} = echoPackage(pkg) - if pkg.alias.len == 0 and options.queryVersions: + if pkg.alias.len == 0 and options.action.showSearchVersions: echoPackageVersions(pkg) echo(" ") found = true @@ -1049,29 +1049,61 @@ proc list(options: Options) = let pkgList = getPackageList(options) for pkg in pkgList: echoPackage(pkg) - if pkg.alias.len == 0 and options.queryVersions: + if pkg.alias.len == 0 and options.action.showListVersions: echoPackageVersions(pkg) echo(" ") +proc listNimBinaries(options: Options) = + let nimBininstalledPkgs = getInstalledPkgsMin(options.nimBinariesDir, options) + displayFormatted(Message, "nim") + displayFormatted(Hint, "\n") + for idx, pkg in nimBininstalledPkgs: + assert pkg.basicInfo.name == "nim" + if idx == nimBininstalledPkgs.len() - 1: + displayFormatted(Hint, "└── ") + else: + displayFormatted(Hint, "├── ") + displayFormatted(Success, "@" & $pkg.basicInfo.version) + displayFormatted(Hint, " ") + displayFormatted(Details, fmt"({pkg.myPath.splitPath().head})") + displayFormatted(Hint, "\n") + displayFormatted(Hint, "\n") + proc listInstalled(options: Options) = type VersionChecksumTuple = tuple[version: Version, checksum: Sha1Hash] - var h: OrderedTable[string, seq[VersionChecksumTuple]] + var vers: OrderedTable[string, seq[VersionChecksumTuple]] let pkgs = getInstalledPkgsMin(options.getPkgsDir(), options) for pkg in pkgs: let pName = pkg.basicInfo.name pVersion = pkg.basicInfo.version pChecksum = pkg.basicInfo.checksum - if not h.hasKey(pName): h[pName] = @[] - var s = h[pName] + if not vers.hasKey(pName): vers[pName] = @[] + var s = vers[pName] add(s, (pVersion, pChecksum)) - h[pName] = s + vers[pName] = s - h.sort(proc (a, b: (string, seq[VersionChecksumTuple])): int = + vers.sort(proc (a, b: (string, seq[VersionChecksumTuple])): int = cmpIgnoreCase(a[0], b[0])) - for k in keys(h): - echo k & " [" & h[k].join(", ") & "]" + + displayInfo("Package list format: {PackageName} ") + displayInfo(" {PackageName} ") + displayInfo(" {Version} ({CheckSum})") + for k in keys(vers): + displayFormatted(Message, k) + displayFormatted(Hint, "\n") + if options.action.showListVersions: + for idx, item in vers[k]: + if idx == vers[k].len() - 1: + displayFormatted(Hint, "└── ") + else: + displayFormatted(Hint, "├── ") + displayFormatted(Success, "@", $item.version) + displayFormatted(Hint, " ") + displayFormatted(Details, fmt"({item.checksum})") + displayFormatted(Hint, "\n") + # " [" & vers[k].join(", ") & "]" type VersionAndPath = tuple[version: Version, path: string] @@ -1925,9 +1957,25 @@ proc lock(options: Options) = updateSyncFile(pkgInfo, options) displayLockOperationFinish(lockExists) -proc depsTree(options: Options) = +proc depsPrint(options: Options, + pkgInfo: PackageInfo, + dependencies: seq[PackageInfo], + errors: ValidationErrors) = ## Prints the dependency tree + if options.action.format == "json": + if options.action.depsAction == "inverted": + raise nimbleError("Deps JSON format does not support inverted tree") + echo (%depsRecursive(pkgInfo, dependencies, errors)).pretty + elif options.action.depsAction == "inverted": + printDepsHumanReadableInverted(pkgInfo, dependencies, errors) + elif options.action.depsAction == "tree": + printDepsHumanReadable(pkgInfo, dependencies, errors) + else: + printDepsHumanReadable(pkgInfo, dependencies, errors, true) + +proc deps(options: Options) = + ## handles deps actions let pkgInfo = getPkgInfo(getCurrentDir(), options) var errors = validateDevModeDepsWorkingCopiesBeforeLock(pkgInfo, options) @@ -1942,12 +1990,10 @@ proc depsTree(options: Options) = if not dependencyGraph.contains name: errors.del name - if options.action.format == "json": - echo (%depsRecursive(pkgInfo, dependencies, errors)).pretty - elif options.action.format == "inverted": - printDepsHumanReadableInverted(pkgInfo, dependencies, errors) + if options.action.depsAction in ["", "tree", "inverted"]: + depsPrint(options, pkgInfo, dependencies, errors) else: - printDepsHumanReadable(pkgInfo, dependencies, errors) + raise nimbleError("Unknown deps flag: " & options.action.depsAction) proc syncWorkingCopy(name: string, path: Path, dependentPkg: PackageInfo, options: Options) = @@ -2312,8 +2358,12 @@ proc doAction(options: var Options) = of actionSearch: search(options) of actionList: - if options.queryInstalled: listInstalled(options) - else: list(options) + if options.action.onlyInstalled: + listInstalled(options) + elif options.action.onlyNimBinaries: + listNimBinaries(options) + else: + list(options) of actionPath: listPaths(options) of actionBuild: @@ -2331,7 +2381,10 @@ proc doAction(options: var Options) = init(options) of actionPublish: var pkgInfo = getPkgInfo(getCurrentDir(), options) - publish(pkgInfo, options) + if options.action.publishAction == "tags": + publishTags(pkgInfo, options) + else: + publish(pkgInfo, options) of actionDump: dump(options) of actionTasks: @@ -2343,7 +2396,7 @@ proc doAction(options: var Options) = of actionLock: lock(options) of actionDeps: - depsTree(options) + deps(options) of actionSync: sync(options) of actionSetup: @@ -2493,8 +2546,8 @@ when isMainModule: var opt: Options try: opt = parseCmdLine() - opt.setNimbleDir - opt.loadNimbleData + opt.setNimbleDir() + opt.loadNimbleData() if opt.action.typ in {actionTasks, actionRun, actionBuild, actionCompile, actionDevelop}: # Implicitly disable package validation for these commands. opt.disableValidation = true diff --git a/src/nimblepkg/cli.nim b/src/nimblepkg/cli.nim index 759a6ed7..a09b8e0e 100644 --- a/src/nimblepkg/cli.nim +++ b/src/nimblepkg/cli.nim @@ -62,12 +62,18 @@ proc isSuppressed(displayType: DisplayType): bool = return true proc displayFormatted*(displayType: DisplayType, msgs: varargs[string]) = + ## for styling outputs lines using the DisplayTypes for msg in msgs: if globalCLI.showColor: stdout.styledWrite(foregrounds[displayType], msg) else: stdout.write(msg) +proc displayInfoLine*(field, msg: string) = + displayFormatted(Success, field) + displayFormatted(Details, msg) + displayFormatted(Hint, "\n") + proc displayCategory(category: string, displayType: DisplayType, priority: Priority) = if isSuppressed(displayType): diff --git a/src/nimblepkg/deps.nim b/src/nimblepkg/deps.nim index 99819aca..cb970800 100644 --- a/src/nimblepkg/deps.nim +++ b/src/nimblepkg/deps.nim @@ -35,7 +35,8 @@ proc depsRecursive*(pkgInfo: PackageInfo, proc printDepsHumanReadable*(pkgInfo: PackageInfo, dependencies: seq[PackageInfo], errors: ValidationErrors, - levelInfos: seq[tuple[skip: bool]] = @[] + directOnly = false, + levelInfos: seq[tuple[skip: bool]] = @[], ) = ## print human readable tree deps ## @@ -82,7 +83,8 @@ proc printDepsHumanReadable*(pkgInfo: PackageInfo, displayFormatted(Error, fmt" - error: {errMsg}") if found: var levelInfos = levelInfos & @[(skip: isLast)] - printDepsHumanReadable(depPkgInfo, dependencies, errors, levelInfos) + if not directOnly: + printDepsHumanReadable(depPkgInfo, dependencies, errors, directOnly, levelInfos) if levelInfos.len() == 0: displayFormatted(Hint, "\n") diff --git a/src/nimblepkg/download.nim b/src/nimblepkg/download.nim index 1e4106e8..d2323fef 100644 --- a/src/nimblepkg/download.nim +++ b/src/nimblepkg/download.nim @@ -445,6 +445,31 @@ proc doDownload(url, downloadDir: string, verRange: VersionRange, result.vcsRevision = downloadDir.getVcsRevision {.warning[ProveInit]: on.} +proc pkgDirHasNimble*(dir: string, options: Options): bool = + try: + discard findNimbleFile(dir, true, options) + return true + except NimbleError: + #Continue with the download + discard + +proc downloadPkgDir*(url: string, + verRange: VersionRange, + subdir: string, + options: Options, + vcsRevision: Sha1Hash = notSetSha1Hash, + downloadPath: string = "" +): (string, string) = + let downloadDir = + if downloadPath == "": + (getNimbleTempDir() / getDownloadDirName(url, verRange, vcsRevision)) + else: + downloadPath + + createDir(downloadDir) + + result = (downloadDir, downloadDir / subdir) + proc downloadPkg*(url: string, verRange: VersionRange, downMethod: DownloadMethod, subdir: string, @@ -464,38 +489,21 @@ proc downloadPkg*(url: string, verRange: VersionRange, ## If specified this parameter will cause specific VCS revision to be ## checked out. + let (downloadDir, pkgDir) = downloadPkgDir(url, verRange, subdir, options, vcsRevision, downloadPath) + result.dir = pkgDir + + #when using a persistent download dir we can skip the download if it's already done + if pkgDirHasNimble(result.dir, options): + return # already downloaded, skipping + if options.offline: raise nimbleError("Cannot download in offline mode.") - let downloadDir = - if downloadPath == "": - (getNimbleTempDir() / getDownloadDirName(url, verRange, vcsRevision)) - else: - downloadPath - - createDir(downloadDir) - var modUrl = - if url.startsWith("git://") and options.config.cloneUsingHttps: - "https://" & url[6 .. ^1] - else: url - # Fixes issue #204 - # github + https + trailing url slash causes a - # checkout/ls-remote to fail with Repository not found - if modUrl.contains("github.com") and modUrl.endswith("/"): - modUrl = modUrl[0 .. ^2] + let modUrl = modifyUrl(url, options.config.cloneUsingHttps) let downloadMethod = if downloadTarball(modUrl, options): "http" else: $downMethod - result.dir = downloadDir / subdir - #when using a persistent download dir we can skip the download if it's already done - try: - discard findNimbleFile(result.dir, true, options) - return - except NimbleError: - #Continue with the download - discard - if subdir.len > 0: display("Downloading", "$1 using $2 (subdir is '$3')" % [modUrl, downloadMethod, subdir], @@ -525,14 +533,16 @@ proc echoPackageVersions*(pkg: Package) = let versions = getTagsListRemote(pkg.url, downMethod).getVersionList() if versions.len > 0: let sortedVersions = toSeq(values(versions)) - echo(" versions: " & join(sortedVersions, ", ")) + displayInfoLine(" versions: ", join(sortedVersions, ", ")) else: - echo(" versions: (No versions tagged in the remote repository)") + displayInfoLine(" versions: ", "(No versions tagged in the remote repository)") except CatchableError: - echo(getCurrentExceptionMsg()) + displayFormatted(Error, " Error: ") + displayFormatted(Error, getCurrentExceptionMsg()) + displayFormatted(Hint, "\n") of DownloadMethod.hg: - echo(" versions: (Remote tag retrieval not supported by " & - $pkg.downloadMethod & ")") + displayInfoLine(" versions: ", "(Remote tag retrieval not supported by " & + $pkg.downloadMethod & ")") proc removeTrailingSlash(s: string): string = s.strip(chars = {'/'}, leading = false) @@ -584,35 +594,46 @@ proc refresh*(options: Options) = for name, list in options.config.packageLists: fetchList(list, options) -proc getDownloadInfo*(pv: PkgTuple, options: Options, - doPrompt: bool, ignorePackageCache = false): (DownloadMethod, string, - Table[string, string]) = - if pv.name.isURL: - let (url, metadata) = getUrlData(pv.name) - return (checkUrlType(url), url, metadata) +proc getDownloadInfo*( + pv: PkgTuple, options: Options, + doPrompt: bool, + ignorePackageCache = false, +): (DownloadMethod, string, Table[string, string]) = + + # echo "getDownloadInfo:pv.name: ", $pv.name + var pkg = initPackage() + if getPackage(pv.name, options, pkg, ignorePackageCache): + let (url, metadata) = getUrlData(pkg.url) + result = (pkg.downloadMethod, url, metadata) + # echo "getDownloadInfo:getPackage: ", $result + return + elif pv.name.isURL: + # echo "getDownloadInfo:isURL:name: ", $pv.name + # echo "getDownloadInfo:isURL:options.nimbleData: ", $options.nimbleData + let (url, urlmeta) = getUrlData(pv.name) + var metadata = urlmeta + metadata["urlOnly"] = "true" + result = (checkUrlType(url), url, metadata) + # echo "getDownloadInfo:isURL: ", $result + return elif pv.name.isForgeAlias: let url = newForge(pv.name).expand() return (checkUrlType(url), url, default(Table[string, string])) else: - var pkg = initPackage() - if getPackage(pv.name, options, pkg, ignorePackageCache): - let (url, metadata) = getUrlData(pkg.url) - return (pkg.downloadMethod, url, metadata) + # If package is not found give the user a chance to refresh + # package.json + if doPrompt and not options.offline and + options.prompt(pv.name & " not found in any local packages.json, " & + "check internet for updated packages?"): + refresh(options) + + # Once we've refreshed, try again, but don't prompt if not found + # (as we've already refreshed and a failure means it really + # isn't there) + # Also ignore the package cache so the old info isn't used + return getDownloadInfo(pv, options, false, true) else: - # If package is not found give the user a chance to refresh - # package.json - if doPrompt and not options.offline and - options.prompt(pv.name & " not found in any local packages.json, " & - "check internet for updated packages?"): - refresh(options) - - # Once we've refreshed, try again, but don't prompt if not found - # (as we've already refreshed and a failure means it really - # isn't there) - # Also ignore the package cache so the old info isn't used - return getDownloadInfo(pv, options, false, true) - else: - raise nimbleError(pkgNotFoundMsg(pv)) + raise nimbleError(pkgNotFoundMsg(pv)) when isMainModule: import unittest diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 8f913b0e..ead8d180 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -16,13 +16,13 @@ type NimBin* = object path*: string version*: Version + DumpMode* = enum kdumpIni, kdumpJson + Options* = object forcePrompts*: ForcePrompt depsOnly*: bool uninstallRevDeps*: bool - queryVersions*: bool - queryInstalled*: bool nimbleDir*: string verbosity*: cli.Priority action*: Action @@ -79,8 +79,9 @@ type Action* = object case typ*: ActionType - of actionNil, actionList, actionPublish, actionTasks, actionCheck, - actionSetup, actionClean, actionManual: nil + of actionNil, actionTasks, actionCheck, + actionSetup, actionClean, actionManual: + discard of actionSync: listOnly*: bool of actionRefresh: @@ -96,6 +97,11 @@ type global*: bool of actionSearch: search*: seq[string] # Search string. + showSearchVersions*: bool + of actionList: + onlyNimBinaries*: bool + onlyInstalled*: bool + showListVersions*: bool of actionInit, actionDump: projName*: string vcsOption*: string @@ -115,6 +121,9 @@ type custRunFlags*: seq[string] of actionDeps: format*: string + depsAction*: string + of actionPublish: + publishAction*: string of actionShellEnv, actionShell: discard @@ -191,10 +200,11 @@ Commands: can be optionally specified. search pkg/tag Searches for a specified package. Search is performed by tag and by name. - [--ver] Queries remote server for package version. + [--ver, --version] Queries remote server for package version. list Lists all packages. - [--ver] Queries remote server for package version. [-i, --installed] Lists all installed packages. + [--ver, --version] Also display versions for packages. + [-n, --nimbinaries] Lists all installed packages. tasks Lists the tasks specified in the Nimble package's Nimble file. path pkgname ... Shows absolute path to the installed packages @@ -239,9 +249,10 @@ Nimble Options: --ver Query remote server for package version information when searching or listing packages. --nimbleDir:dirname Set the Nimble directory. - --nim:path Use specified path for Nim compiler - --silent Hide all Nimble and Nim output - --verbose Show all non-debug output. + --nim:path Use specified path for Nim compiler. + --silent Hide all Nimble and Nim output. + --info Show some informative output. + --verbose Show extra non-debugging output. --debug Show all output including debug messages. --offline Don't use network. --noColor Don't colorise output. @@ -251,11 +262,11 @@ Nimble Options: --developFile Specifies the name of the develop file which to be manipulated. If not present creates it. --useSystemNim Use system nim and ignore nim from the lock - file if any + file if any. --solver:sat|legacy Use the SAT solver (default) or the legacy for dependency resolution. - --requires Add extra packages to the dependency resolution. Uses the same syntax as the Nimble file. Example: nimble install --requires "pkg1; pkg2 >= 1.2" + --requires Add extra packages to the dependency resolution. Uses the same syntax as the Nimble file. Example: nimble install --requires "pkg1; pkg2 >= 1.2". --disableNimBinaries Disable the use of nim precompiled binaries. Note in some platforms precompiled binaries are not available but the flag can still be used to avoid compile the Nim version once and reuse it. - --maximumTaggedVersions Maximum number of tags to check for a package when discovering versions for the SAT solver. 0 means all. + --maximumTaggedVersions Maximum number of tags to check for a package when discovering versions for the SAT solver. 0 means all. For more information read the GitHub readme: https://github.com/nim-lang/nimble#readme """ @@ -614,6 +625,7 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = of "reject", "n": result.forcePrompts = forcePromptNo of "nimbledir": result.nimbleDir = val of "silent": result.verbosity = SilentPriority + of "info": result.verbosity = MediumPriority of "verbose": result.verbosity = LowPriority of "debug": result.verbosity = DebugPriority of "offline": result.offline = true @@ -653,12 +665,20 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = var wasFlagHandled = true # Action-specific flags. case result.action.typ - of actionSearch, actionList: + of actionSearch: + case f + of "versions", "ver": + result.action.showSearchVersions = true + else: + wasFlagHandled = false + of actionList: case f of "installed", "i": - result.queryInstalled = true - of "ver": - result.queryVersions = true + result.action.onlyInstalled = true + of "nimbinaries", "n": + result.action.onlyNimBinaries = true + of "versions", "ver": + result.action.showListVersions = true else: wasFlagHandled = false of actionDump: @@ -669,7 +689,7 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = wasFlagHandled = false of actionInstall: case f - of "depsonly", "d": + of "depsonly", "deps", "d": result.depsOnly = true of "norebuild": result.action.noRebuild = true @@ -736,10 +756,20 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = result.action.listOnly = true else: wasFlagHandled = false + of actionPublish: + case f + of "tags": + result.action.publishAction = "tags" + else: + wasFlagHandled = false of actionDeps: case f of "format": result.action.format = val + of "tree": + result.action.depsAction = "tree" + of "inverted": + result.action.depsAction = "inverted" else: wasFlagHandled = false else: diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index 0f9ee04d..b27e4472 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -389,23 +389,24 @@ proc getOutputDir*(pkgInfo: PackageInfo, bin: string): string = result &= ".out" proc echoPackage*(pkg: Package) = - echo(pkg.name & ":") + displayFormatted(Message, pkg.name & ":") + displayFormatted(Hint, "\n") + if pkg.alias.len > 0: - echo(" Alias for ", pkg.alias) + displayFormatted(Warning, " Alias for ", pkg.alias) + displayFormatted(Hint, "\n") else: - echo(" url: " & pkg.url & " (" & $pkg.downloadMethod & ")") - echo(" tags: " & pkg.tags.join(", ")) - echo(" description: " & pkg.description) - echo(" license: " & pkg.license) + displayInfoLine(" url: ", pkg.url & " (" & $pkg.downloadMethod & ")") + displayInfoLine(" tags: ", pkg.tags.join(", ")) + displayInfoLine(" description: ", pkg.description) + displayInfoLine(" license: ", pkg.license) if pkg.web.len > 0: - echo(" website: " & pkg.web) - -proc getDownloadDirName*(pkg: Package, verRange: VersionRange): string = - result = pkg.name - let verSimple = getSimpleString(verRange) - if verSimple != "": - result.add "_" - result.add verSimple + displayInfoLine(" website: ", pkg.web) + +proc echoPackage*(pkg: PackageInfo) = + displayFormatted(Message, pkg.basicInfo.name & ":") + displayFormatted(Hint, "\n") + proc checkInstallFile(pkgInfo: PackageInfo, origDir, file: string): bool = diff --git a/src/nimblepkg/publish.nim b/src/nimblepkg/publish.nim index 8e4c7587..f79d9968 100644 --- a/src/nimblepkg/publish.nim +++ b/src/nimblepkg/publish.nim @@ -6,8 +6,8 @@ import system except TResult import httpclient, strutils, json, os, browsers, times, uri -import common, tools, cli, config, options, packageinfotypes -import strformat +import common, tools, cli, config, options, packageinfotypes, sha1hashes, version, download +import strformat, sequtils, pegs, sets {.warning[UnusedImport]: off.} from net import SslCVerifyMode, newContext @@ -247,3 +247,81 @@ proc publish*(p: PackageInfo, o: Options) = doCmd("git push https://" & auth.token & "@github.com/" & auth.user & "/packages " & branchName) let prUrl = createPullRequest(auth, p, url, branchName) display("Success:", "Pull request successful, check at " & prUrl , Success, HighPriority) + +proc vcsFindCommits*(repoDir, nimbleFile: string, downloadMethod: DownloadMethod): seq[(Sha1Hash, string)] = + var output: string + case downloadMethod: + of DownloadMethod.git: + output = tryDoCmdEx(&"git -C {repoDir} log --format=\"%H %s\" -- $2") + of DownloadMethod.hg: + assert false, "hg not supported" + + for line in output.splitLines(): + let line = line.strip() + if line != "": + result.add((line[0..39].initSha1Hash(), line[40..^1])) + +proc vcsDiff*(commit: Sha1Hash, repoDir, nimbleFile: string, downloadMethod: DownloadMethod): seq[string] = + case downloadMethod: + of DownloadMethod.git: + let (output, exitCode) = doCmdEx(&"git -C {repoDir} diff {commit}~ {commit} {nimbleFile}") + if exitCode != QuitSuccess: + return @[] + else: + return output.splitLines() + of DownloadMethod.hg: + assert false, "hg not supported" + +proc createTag*(tag: string, commit: Sha1Hash, message, repoDir, nimbleFile: string, downloadMethod: DownloadMethod): bool = + case downloadMethod: + of DownloadMethod.git: + let (output, code) = doCmdEx(&"git -C {repoDir} tag -a {tag.quoteShell()} {commit} -m {message.quoteShell()}") + result = code == QuitSuccess + if not result: + displayError(&"Failed to create tag {tag.quoteShell()} with error {output}") + of DownloadMethod.hg: + assert false, "hg not supported" + +proc findVersions(commits: seq[(Sha1Hash, string)], projdir, nimbleFile: string, downloadMethod: DownloadMethod) = + ## parse the versions + var + versions: HashSet[Version] + existingTags: HashSet[Version] + for tag in getTagsList(projdir, downloadMethod): + let tag = tag.strip(leading=true, chars={'v'}) + try: + existingTags.incl(newVersion(tag)) + except ParseVersionError: + discard + + # adapted from @beef331's algorithm https://github.com/beef331/graffiti/blob/master/src/graffiti.nim + for (commit, message) in commits: + # echo "commit: ", commit + let diffs = vcsDiff(commit, projdir, nimbleFile, downloadMethod) + for line in diffs: + var matches: array[0..MaxSubpatterns, string] + if line.find(peg"'+version' \s* '=' \s* {[\34\39]} {@} $1", matches) > -1: + let version = newVersion(matches[1]) + if version notin versions: + versions.incl(version) + if version in existingTags: + displayInfo(&"Found existing tag for version {version}", MediumPriority) + else: + displayInfo(&"Found new version {version} at {commit}", MediumPriority) + let res = createTag(&"v{version}", commit, message, projdir, nimbleFile, downloadMethod) + if not res: + displayError(&"Unable to create tag {version}") + +proc publishTags*(p: PackageInfo, o: Options) = + discard + echo "publishTags:myPath: ", $p.myPath + echo "publishTags:basic: ", $p.basicInfo + # echo "publishTags: ", $p + let (projdir, file, ext) = p.myPath.splitFile() + let nimblefile = file & ext + let dlmethod = p.metadata.downloadMethod + let commits = vcsFindCommits(projdir, nimbleFile, dlmethod) + echo "publishTags:commits: ", $commits.len() + + findVersions(commits, projdir, nimbleFile, dlmethod) + echo "" diff --git a/src/nimblepkg/urls.nim b/src/nimblepkg/urls.nim index 4fca305c..c56d72b7 100644 --- a/src/nimblepkg/urls.nim +++ b/src/nimblepkg/urls.nim @@ -1,4 +1,16 @@ -import std/pegs +import std/pegs, std/strutils proc isURL*(name: string): bool = name.startsWith(peg" @'://' ") or name.startsWith(peg"\ident+'@'@':'.+") + +proc modifyUrl*(url: string, usingHttps: bool): string = + result = + if url.startsWith("git://") and usingHttps: + "https://" & url[6 .. ^1] + else: + url + # Fixes issue #204 + # github + https + trailing url slash causes a + # checkout/ls-remote to fail with Repository not found + if result.contains("github.com") and result.endswith("/"): + result = result[0 .. ^2] diff --git a/src/nimblepkg/vcstools.nim b/src/nimblepkg/vcstools.nim index 3defd1f2..e2b921ed 100644 --- a/src/nimblepkg/vcstools.nim +++ b/src/nimblepkg/vcstools.nim @@ -200,6 +200,22 @@ proc getVcsRevision*(dir: Path): Sha1Hash = return initSha1Hash(vcsRevision.strip(chars = Whitespace + {'+'})) +proc getVcsRevisions*(dir: Path): Sha1Hash = + ## Returns current revision number if the directory `dir` is under version + ## control, or an invalid Sha1 checksum otherwise. + ## + ## Raises a `NimbleError` if: + ## - the external command fails. + ## - the directory does not exist. + ## - there is no vcsRevisions in the repository. + + let vcsRevision = tryDoVcsCmd(dir, + gitCmd = "rev-parse HEAD", + hgCmd = "id -i --debug", + noVcsAction = $notSetSha1Hash) + + return initSha1Hash(vcsRevision.strip(chars = Whitespace + {'+'})) + proc getPackageFileListWithoutVcs(dir: Path): seq[string] = ## Recursively walks the directory `dir` and returns a list of files in it and ## its subdirectories. diff --git a/tests/testscommon.nim b/tests/testscommon.nim index b8e3c52c..09ea1d2d 100644 --- a/tests/testscommon.nim +++ b/tests/testscommon.nim @@ -29,9 +29,10 @@ let proc execNimble*(args: varargs[string]): ProcessOutput = var quotedArgs = @args + quotedArgs.insert("--info") + quotedArgs.insert("--noColor") if not args.anyIt("--nimbleDir:" in it or "-l" == it or "--local" == it): quotedArgs.insert("--nimbleDir:" & installDir) - quotedArgs.insert("--noColor") quotedArgs.insert(nimblePath) quotedArgs = quotedArgs.map((x: string) => x.quoteShell) diff --git a/tests/tissues.nim b/tests/tissues.nim index 6514a730..cd445713 100644 --- a/tests/tissues.nim +++ b/tests/tissues.nim @@ -40,7 +40,7 @@ suite "issues": if line.contains("issue799"): let nimbleInstallDir = getPackageDir( pkgsDir, &"nimble-{nimbleVersion}") - let pkgInstalledPath = "--path:'" & nimble_install_dir & "'" + let pkgInstalledPath = "--path:" & nimbleInstallDir.quoteShell & "" check line.contains(pkgInstalledPath) test "issue 793": diff --git a/tests/ttaskdeps.nim b/tests/ttaskdeps.nim index 3a1e43f2..a459ebe9 100644 --- a/tests/ttaskdeps.nim +++ b/tests/ttaskdeps.nim @@ -119,6 +119,7 @@ suite "Task level dependencies": test "Dependencies aren't verified twice": inDir: let (output, _) = execNimbleYes("test") + checkpoint("Failed test output: \n>>>" & output.replace("\n", "\n>>> ")) check output.count("dependencies for unittest2@0.0.4") == 1 test "Requirements for tasks in dependencies aren't used":