From e780ff0ca3506114fd5fc5f4867318d2da720497 Mon Sep 17 00:00:00 2001 From: xTrayambak Date: Wed, 14 Aug 2024 12:38:34 +0530 Subject: [PATCH 1/2] (add) command: add `sponsor` command --- src/nimble.nim | 61 +++++++++++++++++++++++++++++- src/nimblepkg/options.nim | 8 +++- src/nimblepkg/packageinfo.nim | 54 +++++++++++++++++++++++++- src/nimblepkg/packageinfotypes.nim | 12 +++++- 4 files changed, 130 insertions(+), 5 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 0fae59aa..7da2f33c 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -1,7 +1,7 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import os, tables, strtabs, json, algorithm, sets, uri, sugar, sequtils, osproc, +import os, tables, strtabs, json, math, browsers, algorithm, sets, uri, sugar, sequtils, osproc, strformat import std/options as std_opt @@ -2087,6 +2087,63 @@ proc sync(options: Options) = if errors.len > 0: raise validationErrors(errors) +proc sponsor(options: Options) = + ## Allows the user to sponsor a package's developer, if they have the fields set-up in their package manifest. + ## This is done in a case-sensitive manner and the search terminates upon a single match. + let pkgList = getPackageList(options) + + if options.action.optionalPackage.len < 1: + var accepting: uint = 0 + + for pkg in pkgList: + if pkg.donations.len > 0: + inc(accepting) + displayInfo(pkg.name) + + for donation in pkg.donations: + let url = donation.constructDonationURL() + displayInfo( + "$1: $2 ($3)" % [ + $donation.meth, donation.username, url + ] + ) + + displayHint("To sponsor this library's developer, run `nimble sponsor " & pkg.name & '`') + echo('\n') + + displayInfo( + "$1 $2 $3 accepting donations, amounting to $4% of all packages." % [ + $accepting, + (if accepting == 1: "package" else: "packages"), + (if accepting == 1: "is" else: "are"), + $round(accepting.int / pkgList.len) + ] + ) + return + + for pkg in pkgList: + if pkg.name == options.action.optionalPackage: + displayInfo("Found package: " & pkg.name) + + if pkg.donations.len < 1: + displayError("This package's maintainer has not set up any donation links in the package manifest.") + displayError("You can contact them directly to sponsor them in some other way instead.") + return + + for donation in pkg.donations: + let url = donation.constructDonationURL() + displayInfo( + "$1: $2 ($3)" % [ + $donation.meth, donation.username, url + ] + ) + + openDefaultBrowser(url) + + return + + displayError("No such package by the name of $1 was found." % [options.action.optionalPackage]) + proc append(existingContent: var string; newContent: string) = ## Appends `newContent` to the `existingContent` on a new line by inserting it ## if the new line doesn't already exist. @@ -2335,6 +2392,8 @@ proc doAction(options: var Options) = assert false of actionAdd: addPackages(options.action.packages, options) + of actionSponsor: + sponsor(options) of actionCustom: var optsCopy = options optsCopy.task = options.action.command.normalize diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index be410181..5645be2c 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -63,7 +63,7 @@ type actionInstall, actionSearch, actionList, actionBuild, actionPath, actionUninstall, actionCompile, actionDoc, actionCustom, actionTasks, actionDevelop, actionCheck, actionLock, actionRun, actionSync, actionSetup, - actionClean, actionDeps, actionShellEnv, actionShell, actionAdd + actionClean, actionDeps, actionShellEnv, actionShell, actionAdd, actionSponsor DevelopActionType* = enum datAdd, datRemoveByPath, datRemoveByName, datInclude, datExclude @@ -108,6 +108,8 @@ type custRunFlags*: seq[string] of actionDeps: format*: string + of actionSponsor: + optionalPackage*: string of actionShellEnv, actionShell: discard @@ -330,6 +332,8 @@ proc parseActionType*(action: string): ActionType = result = actionShell of "add": result = actionAdd + of "sponsor": + result = actionSponsor else: result = actionCustom @@ -510,6 +514,8 @@ proc parseArgument*(key: string, result: var Options) = result.action.file = key of actionRun: result.setRunOptions(key, key, true) + of actionSponsor: + result.action.optionalPackage = key of actionCustom: result.action.arguments.add(key) else: diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index 64851ec8..cc159d78 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -3,7 +3,7 @@ # Stdlib imports import system except TResult -import hashes, json, strutils, os, sets, tables, times, httpclient, strformat +import hashes, json, strutils, os, sets, tables, uri, times, httpclient, strformat from net import SslError # Local imports @@ -18,6 +18,21 @@ proc initPackageInfo*(): PackageInfo = proc initPackage*(): Package = result = Package(version: notSetVersion) +proc `$`*(meth: DonationMethod): string {.inline.} = + ## Turns a `DonationMethod` into a pretty string. + case meth + of DonationMethod.GitHub: "GitHub" + of DonationMethod.OpenCollective: "OpenCollective" + of DonationMethod.Patreon: "Patreon" + +proc parseDonationMethod*(meth: string): DonationMethod {.inline.} = + case meth + of "github", "gh": return DonationMethod.GitHub + of "opencollective": return DonationMethod.OpenCollective + of "patreon": return DonationMethod.Patreon + else: + raise nimbleError("Invalid donation method: " & meth) + proc isLoaded*(pkgInfo: PackageInfo): bool = return pkgInfo.myPath.len > 0 @@ -98,6 +113,17 @@ proc fromJson(obj: JSonNode): Package = result.tags.add(t.str) result.description = obj.optionalField("description") result.web = obj.optionalField("web") + + if "donations" in obj: + for d in obj.getOrDefault("donations"): + result.donations &= + Donation( + meth: parseDonationMethod(d["meth"].getStr()), + username: d["username"].getStr() + ) + else: + result.donations = @[] + {.warning[ProveInit]: on.} proc needsRefresh*(options: Options): bool = @@ -356,6 +382,26 @@ proc findPkg*(pkglist: seq[PackageInfo], dep: PkgTuple, r = pkg result = true +proc constructDonationURL*(donation: Donation): string = + ## Constructs a donation URL from a `Donation` object that is made via fields in a packages.json file. + ## It also performs validation to ensure that an invalid URL cannot be accidentally passed on if data in the packages.json file is malformed. + var url = "https://" + + case donation.meth + of DonationMethod.GitHub: + url &= "github.com/sponsors/" & donation.username + of DonationMethod.OpenCollective: + url &= "opencollective.com/" & donation.username + of DonationMethod.Patreon: + url &= "patreon.com/" & donation.username + + try: + let parsed = parseURI(url) + except UriParseError as exc: + raise nimbleError("Failed to parse URL properly whilst constructing donation URL - please report this to the Nimble developers! (" & exc.msg & ')') + + url + proc findAllPkgs*(pkglist: seq[PackageInfo], dep: PkgTuple): seq[PackageInfo] = ## Searches ``pkglist`` for packages of which version is within the range ## of ``dep.ver``. This is similar to ``findPkg`` but returns multiple @@ -367,7 +413,6 @@ proc findAllPkgs*(pkglist: seq[PackageInfo], dep: PkgTuple): seq[PackageInfo] = if withinRange(pkg, dep.ver): result.add pkg - proc getRealDir*(pkgInfo: PackageInfo): string = ## Returns the directory containing the package source files. if pkgInfo.srcDir != "" and (not pkgInfo.isInstalled or pkgInfo.isLink): @@ -395,6 +440,11 @@ proc echoPackage*(pkg: Package) = echo(" license: " & pkg.license) if pkg.web.len > 0: echo(" website: " & pkg.web) + if pkg.donations.len > 0: + echo(" donations:") + for i, link in pkg.donations: + let url = constructDonationURL(link) + echo(" " & $link.meth & ": " & link.username & " (" & url & ')') proc getDownloadDirName*(pkg: Package, verRange: VersionRange): string = result = pkg.name diff --git a/src/nimblepkg/packageinfotypes.nim b/src/nimblepkg/packageinfotypes.nim index 96945af1..26be655a 100644 --- a/src/nimblepkg/packageinfotypes.nim +++ b/src/nimblepkg/packageinfotypes.nim @@ -8,6 +8,11 @@ type DownloadMethod* {.pure.} = enum git = "git", hg = "hg" + DonationMethod* {.pure.} = enum + GitHub = "github" + OpenCollective = "opencollective" + Patreon = "patreon" + Checksums* = object sha1*: Sha1Hash @@ -71,7 +76,11 @@ type isLink*: bool paths*: seq[string] entryPoints*: seq[string] #useful for tools like the lsp. - + + Donation* = object + meth*: DonationMethod + username*: string + Package* = object ## Definition of package from packages.json. # Required fields in a package. name*: string @@ -84,6 +93,7 @@ type version*: Version dvcsTag*: string web*: string # Info url for humans. + donations*: seq[Donation] ## A list of donation methods that can be used to support its developer. alias*: string ## A name of another package, that this package aliases. PackageDependenciesInfo* = tuple[deps: HashSet[PackageInfo], pkg: PackageInfo] From 846fab99406b604d18d0b8cb6dfa3fbdd67c6b6f Mon Sep 17 00:00:00 2001 From: xTrayambak Date: Wed, 14 Aug 2024 20:31:32 +0530 Subject: [PATCH 2/2] (fix) donations: remove hardcoded services in favor of URLs --- src/nimble.nim | 17 +++--------- src/nimblepkg/packageinfo.nim | 44 ++---------------------------- src/nimblepkg/packageinfotypes.nim | 13 ++------- 3 files changed, 9 insertions(+), 65 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 7da2f33c..516cc40c 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -2101,12 +2101,7 @@ proc sponsor(options: Options) = displayInfo(pkg.name) for donation in pkg.donations: - let url = donation.constructDonationURL() - displayInfo( - "$1: $2 ($3)" % [ - $donation.meth, donation.username, url - ] - ) + displayInfo($donation) displayHint("To sponsor this library's developer, run `nimble sponsor " & pkg.name & '`') echo('\n') @@ -2130,14 +2125,10 @@ proc sponsor(options: Options) = displayError("You can contact them directly to sponsor them in some other way instead.") return - for donation in pkg.donations: - let url = donation.constructDonationURL() - displayInfo( - "$1: $2 ($3)" % [ - $donation.meth, donation.username, url - ] - ) + for donationUrl in pkg.donations: + let url = $donationUrl + displayInfo(url) openDefaultBrowser(url) return diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index cc159d78..777987dc 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -18,21 +18,6 @@ proc initPackageInfo*(): PackageInfo = proc initPackage*(): Package = result = Package(version: notSetVersion) -proc `$`*(meth: DonationMethod): string {.inline.} = - ## Turns a `DonationMethod` into a pretty string. - case meth - of DonationMethod.GitHub: "GitHub" - of DonationMethod.OpenCollective: "OpenCollective" - of DonationMethod.Patreon: "Patreon" - -proc parseDonationMethod*(meth: string): DonationMethod {.inline.} = - case meth - of "github", "gh": return DonationMethod.GitHub - of "opencollective": return DonationMethod.OpenCollective - of "patreon": return DonationMethod.Patreon - else: - raise nimbleError("Invalid donation method: " & meth) - proc isLoaded*(pkgInfo: PackageInfo): bool = return pkgInfo.myPath.len > 0 @@ -116,11 +101,7 @@ proc fromJson(obj: JSonNode): Package = if "donations" in obj: for d in obj.getOrDefault("donations"): - result.donations &= - Donation( - meth: parseDonationMethod(d["meth"].getStr()), - username: d["username"].getStr() - ) + result.donations &= parseUri(d.getStr()) else: result.donations = @[] @@ -382,26 +363,6 @@ proc findPkg*(pkglist: seq[PackageInfo], dep: PkgTuple, r = pkg result = true -proc constructDonationURL*(donation: Donation): string = - ## Constructs a donation URL from a `Donation` object that is made via fields in a packages.json file. - ## It also performs validation to ensure that an invalid URL cannot be accidentally passed on if data in the packages.json file is malformed. - var url = "https://" - - case donation.meth - of DonationMethod.GitHub: - url &= "github.com/sponsors/" & donation.username - of DonationMethod.OpenCollective: - url &= "opencollective.com/" & donation.username - of DonationMethod.Patreon: - url &= "patreon.com/" & donation.username - - try: - let parsed = parseURI(url) - except UriParseError as exc: - raise nimbleError("Failed to parse URL properly whilst constructing donation URL - please report this to the Nimble developers! (" & exc.msg & ')') - - url - proc findAllPkgs*(pkglist: seq[PackageInfo], dep: PkgTuple): seq[PackageInfo] = ## Searches ``pkglist`` for packages of which version is within the range ## of ``dep.ver``. This is similar to ``findPkg`` but returns multiple @@ -443,8 +404,7 @@ proc echoPackage*(pkg: Package) = if pkg.donations.len > 0: echo(" donations:") for i, link in pkg.donations: - let url = constructDonationURL(link) - echo(" " & $link.meth & ": " & link.username & " (" & url & ')') + echo " " & $(i + 1) & ": " & $link proc getDownloadDirName*(pkg: Package, verRange: VersionRange): string = result = pkg.name diff --git a/src/nimblepkg/packageinfotypes.nim b/src/nimblepkg/packageinfotypes.nim index 26be655a..845954f4 100644 --- a/src/nimblepkg/packageinfotypes.nim +++ b/src/nimblepkg/packageinfotypes.nim @@ -1,18 +1,13 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import sets, tables +import sets, tables, uri import version, sha1hashes type DownloadMethod* {.pure.} = enum git = "git", hg = "hg" - DonationMethod* {.pure.} = enum - GitHub = "github" - OpenCollective = "opencollective" - Patreon = "patreon" - Checksums* = object sha1*: Sha1Hash @@ -77,9 +72,7 @@ type paths*: seq[string] entryPoints*: seq[string] #useful for tools like the lsp. - Donation* = object - meth*: DonationMethod - username*: string + DonationLink* = URI Package* = object ## Definition of package from packages.json. # Required fields in a package. @@ -93,7 +86,7 @@ type version*: Version dvcsTag*: string web*: string # Info url for humans. - donations*: seq[Donation] ## A list of donation methods that can be used to support its developer. + donations*: seq[DonationLink] ## A list of donation website URIs that can be used to support its developer. alias*: string ## A name of another package, that this package aliases. PackageDependenciesInfo* = tuple[deps: HashSet[PackageInfo], pkg: PackageInfo]