A README.md => README.md +6 -0
@@ 0,0 1,6 @@
+# brrr
+
+Yet another package manager.
+
+![](./carbon.png "A screenshot of the following command: brrr install exa")
+(make with https://carbon.now.sh/)<
\ No newline at end of file
A brrr.nimble => brrr.nimble +15 -0
@@ 0,0 1,15 @@
+# Package
+
+version = "0.1.0"
+author = "Siegfried Ehret"
+description = "A new awesome nimble package"
+license = "MIT"
+srcDir = "src"
+bin = @["brrr"]
+
+
+# Dependencies
+
+requires "nim >= 1.6.0"
+requires "yaml >= 0.16.0"
+requires "semver >= 1.1.1"<
\ No newline at end of file
A carbon.png => carbon.png +0 -0
A src/brrr.nim => src/brrr.nim +19 -0
@@ 0,0 1,19 @@
+import lib/[common, stash], options
+
+proc ctrlc() {.noconv.} =
+ log("Bye!")
+ quit()
+
+when isMainModule:
+ var exitCode = QuitSuccess
+ setControlCHook(ctrlc)
+ initStash()
+
+ var opt: Options
+ try:
+ opt = parseCmdLine()
+ opt.doAction()
+ except BrrrQuit as quit:
+ exitCode = quit.exitCode
+
+ quit(exitCode)
A src/commands/help.nim => src/commands/help.nim +16 -0
@@ 0,0 1,16 @@
+import std/strutils
+import ../lib/common
+
+proc writeHelp*() =
+ echo """
+ brrr <command>
+
+ # Commands:
+
+ help Show this help
+ version Show the version
+ install <package> Install a package
+
+ # TODO config info
+ """.dedent()
+ raise brrrQuit()
A src/commands/install.nim => src/commands/install.nim +75 -0
@@ 0,0 1,75 @@
+import std/[algorithm, httpclient, osproc, sequtils, strutils,
+ sugar, tables, times]
+import ../lib/[common, repository, stash, types, utils]
+
+const star = "*"
+
+proc getFilename(url: string): string =
+ let splits = rsplit(url, "/", maxsplit = 1)
+ return splits[1]
+
+proc downloadPackage(url: string, filename: string,
+ targetDir: string = "/tmp/") =
+ log("Downloading " & url)
+ try:
+ var client = newHttpClient()
+ writeFile(targetDir & "/" & filename, client.getContent(url))
+ except IOError:
+ log("Failed to fetch package")
+
+proc downloadVersion(brrrFile: BrrrFile, packageVersion: string,
+ installVersion: string, arch: string) =
+ let templateVersion = brrrFile.versions[installVersion]
+ let downloadUrl = brrrFile.templates[templateVersion][arch].url.multiReplace((
+ "{brrr_package_version}", packageVersion))
+ let filename = getFilename(downloadUrl)
+ downloadPackage(downloadUrl, filename, getPackageDir(brrrFile.name))
+
+proc runScripts(pkgDef: BrrrFile, packageVersion: string,
+ installVersion: string, arch: string) =
+ let templateVersion = pkgDef.versions[installVersion]
+ for script in pkgDef.templates[templateVersion][arch].install:
+ let runScript = script.multiReplace(
+ ("{brrr_package_version}", packageVersion),
+ ("{brrr_from}", getPackageDir(pkgDef.name)),
+ ("{brrr_to}", getBrrrBinDir()))
+ log("Running script: " & runScript)
+ let logs = execCmdEx(runScript, workingDir = getPackageDir(pkgDef.name))
+ log(logs[0], 2)
+
+proc installPackage(pkg: PkgTuple, pkgDef: BrrrFile): string =
+ let logs = execCmdEx(pkgDef.get_tags)
+ var tags = logs[0].split("\n").filter(x => x != "")
+ tags.sort(semverCmp)
+
+ result = tags[tags.len - 1]
+
+ let hasVersion = pkgDef.versions.hasKey(result)
+ let hasStarVersion = pkgDef.versions.hasKey(star)
+ let osArch = getOS()
+ createPackageDir(pkg.name)
+
+ if hasVersion:
+ downloadVersion(pkgDef, result,
+ result, osArch)
+ runScripts(pkgDef, result, result, osArch)
+ elif hasStarVersion:
+ downloadVersion(pkgDef, result,
+ star, osArch)
+ runScripts(pkgDef, result, star, osArch)
+ else:
+ raise brrrError("Can't find version")
+
+proc install*(packages: seq[PkgTuple]) =
+ let timeStart = now()
+ let pkg = packages[0]
+ try:
+ var (url, pkgDef) = downloadPackageDefinition(pkg)
+ let version = installPackage(pkg, pkgDef)
+ addPackageToConfig(pkg.name, version, url)
+ let timeEnd = now()
+ let duration = timeEnd - timeStart
+ log($pkg.name & "@" & version & " installed successfully (" & $duration & ")")
+ except BrrrError as error:
+ log("Error while installing " & $pkg)
+ log(error.message, 2)
A src/commands/version.nim => src/commands/version.nim +5 -0
@@ 0,0 1,5 @@
+import ../lib/common
+
+proc writeVersion*() =
+ log("brrr version " & brrrVersion)
+ raise brrrQuit()
A src/lib/common.nim => src/lib/common.nim +36 -0
@@ 0,0 1,36 @@
+import std/strutils
+
+const brrrVersion* = "0.1.0"
+
+type
+ BrrrError* = object of CatchableError
+ message*: string
+
+ BrrrQuit* = object of Defect
+ exitCode*: int
+
+proc newBrrrError*[ErrorType](msg: string,
+ details: ref CatchableError = nil): ref ErrorType =
+ result = newException(ErrorType, msg, details)
+
+proc brrrError*(msg: string, details: ref CatchableError = nil): ref BrrrError =
+ newBrrrError[BrrrError](msg, details)
+
+proc brrrQuit*(exitCode = QuitSuccess): ref BrrrQuit =
+ result = newException(BrrrQuit, "")
+ result.exitCode = exitCode
+
+proc log*(message: string, padding = 0) =
+ var msg = message
+ stripLineEnd(msg)
+ if msg.len > 0:
+ if padding == 0:
+ echo indent("❄️ " & msg, padding)
+ else:
+ echo indent(msg, padding)
+
+proc getOS*(): string =
+ return
+ # when defined(windows): "windows"
+ when defined(macosx): "macos"
+ elif defined(linux): "linux"
A src/lib/repository.nim => src/lib/repository.nim +22 -0
@@ 0,0 1,22 @@
+import std/[httpclient, streams, strutils]
+import yaml/serialization
+import ./common, ./types
+
+const baseUrl = "https://nyrst.github.io/freezer/{brrr_package_name}.yaml"
+
+proc downloadPackageDefinition*(pkg: PkgTuple): (string, BrrrFile) =
+ let url = baseUrl.replace("{brrr_package_name}", pkg.name)
+ log("Fetching package definition: " & url)
+ var client = newHttpClient()
+ var s = newStringStream(client.getContent(url))
+ var brrrFile: BrrrFile
+ load(s, brrrFile)
+ s.close()
+ return (url, brrrFile)
+
+proc getLocalPackageDefinition*(file: string): BrrrFile =
+ var brrrFile: BrrrFile
+ var s = newFileStream(file)
+ load(s, brrrFile)
+ s.close()
+ return brrrFile
A src/lib/stash.nim => src/lib/stash.nim +53 -0
@@ 0,0 1,53 @@
+import std/[os, parsecfg]
+import ./common
+
+const brrrHome = getConfigDir() / "brrr"
+
+proc getBrrrBinDir*(): string =
+ result = brrrHome / "bin"
+
+proc getBrrrPackagesDir(): string =
+ result = brrrHome / "packages"
+
+proc getPackageDir*(packageName: string): string =
+ result = getBrrrPackagesDir() / packageName
+
+proc getBrrrConfigPath(): string =
+ result = brrrHome / "brrr.cfg"
+
+proc getBrrrConfig(): Config =
+ let configPath = getBrrrConfigPath()
+ var info: FileInfo
+ try:
+ info = getFileInfo(configPath)
+ echo info
+ except OSError:
+ var config = newConfig()
+ config.writeConfig(configPath)
+ return loadConfig(configPath)
+
+proc addPackageToConfig*(name, version, url: string) =
+ var config = getBrrrConfig()
+ config.setSectionKey(name, "version", version)
+ config.setSectionKey(name, "url", url)
+ config.writeConfig(getBrrrConfigPath())
+
+proc initStash*() =
+ try:
+ discard existsOrCreateDir(brrrHome)
+ discard existsOrCreateDir(getBrrrBinDir())
+ discard existsOrCreateDir(getBrrrPackagesDir())
+ except IOError:
+ log("Failed to initialize brrr home")
+
+proc createPackageDir*(packageName: string) =
+ let packageDir = getPackageDir(packageName)
+ try:
+ removeDir(packageDir)
+ except OSError:
+ discard
+
+ createDir(packageDir)
+
+# proc getConfig() =
+# let config =
A src/lib/types.nim => src/lib/types.nim +25 -0
@@ 0,0 1,25 @@
+import std/tables
+
+type BrrrTemplate = object
+ url*: string
+ install*: seq[string]
+ uninstall*: seq[string]
+
+type BrrrTemplates* = Table[string, Table[string, BrrrTemplate]]
+
+type BrrrVersions* = Table[string, string]
+
+type BrrrFile* = object
+ name*: string
+ latest_version*: string
+ brrr_version*: string
+ url*: string
+ get_tags*: string
+ templates*: BrrrTemplates
+ versions*: BrrrVersions
+
+type
+ PkgTuple* = tuple[name: string, ver: string]
+
+proc `$`*(dep: PkgTuple): string =
+ return dep.name & "@" & $dep.ver
A src/lib/utils.nim => src/lib/utils.nim +8 -0
@@ 0,0 1,8 @@
+import semver
+
+proc semverCmp*(a, b: string): int =
+ let aVersion = parseVersion(a)
+ let bVersion = parseVersion(b)
+ if aVersion == bVersion: 0
+ elif aVersion > bVersion: 1
+ else: -1
A src/options.nim => src/options.nim +81 -0
@@ 0,0 1,81 @@
+import commands/[help, install, version], lib/types
+import std/[parseopt, strutils]
+
+# Many lines come from nimble.
+
+type
+ ActionType* = enum
+ actionNil, actionInstall
+ Action* = object
+ case typ*: ActionType
+ of actionNil: nil
+ of actionInstall:
+ packages*: seq[PkgTuple]
+ Options* = object
+ action*: Action
+ showHelp*: bool
+ showVersion*: bool
+
+proc parseActionType*(action: string): ActionType =
+ case action.normalize()
+ of "install":
+ result = actionInstall
+ else:
+ result = actionNil
+
+proc parseCommand*(key: string, result: var Options) =
+ result.action = Action(typ: parseActionType(key))
+
+proc parseArgument*(key: string, result: var Options) =
+ case result.action.typ
+ of actionNil:
+ assert false
+ of actionInstall:
+ if '@' in key:
+ let i = find(key, '@')
+ let (pkgName, pkgVer) = (key[0 .. i-1], key[i+1 .. key.len-1])
+ result.action.packages.add((pkgName, pkgVer))
+ else:
+ result.action.packages.add((key, "*"))
+
+proc doAction*(options: var Options) =
+ if options.showHelp:
+ writeHelp()
+ if options.showVersion:
+ writeVersion()
+ case options.action.typ
+ of actionInstall:
+ install(options.action.packages)
+ of actionNil:
+ writeHelp()
+
+proc initOptions*(): Options =
+ Options(
+ action: Action(typ: actionNil),
+ )
+
+proc parseCmdLine*(): Options =
+ result = initOptions()
+
+ # Parse command line params first. A simple `--version` shouldn't require
+ # a config to be parsed.
+ for kind, key, val in getOpt():
+ case kind
+ of cmdArgument:
+ if result.action.typ == actionNil:
+ parseCommand(key, result)
+ else:
+ parseArgument(key, result)
+ of cmdLongOption, cmdShortOption:
+ discard
+ of cmdEnd: assert(false) # cannot happen
+
+ # result.config = parseConfig()
+
+ if result.action.typ == actionNil and not result.showVersion:
+ result.showHelp = true
+
+ if result.action.typ != actionNil and result.showVersion:
+ # We've got another command that should be handled. For example:
+ # nimble run foobar -v
+ result.showVersion = false<
\ No newline at end of file