~siegfriedehret/brrr

942b6e6fb1025133a4c2d56eeb92da837841847e — Siegfried Ehret 2 years ago d00f3c8
🎁 init project and add install command
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