Skip to content

Commit

Permalink
Split own package manager into separate files
Browse files Browse the repository at this point in the history
  • Loading branch information
aNNiMON committed Oct 8, 2024
1 parent cfb79be commit e263de5
Show file tree
Hide file tree
Showing 6 changed files with 387 additions and 377 deletions.
384 changes: 7 additions & 377 deletions ownlang-desktop/src/main/resources/scripts/own.own
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use std, files, http, json, functional, downloader, robot, java
use std

PB = newClass("java.lang.ProcessBuilder")
include "own/Registry.own"
include "own/Config.own"
include "own/Packages.own"
include "own/Projects.own"
include "own/Own.own"

DEBUG=getenv("OWN_DEBUG", false) == "true"
DEBUG=getenv("OWN_DEBUG", "false") == "true"
logger = {}
logger.color = def(num = 0) = sprintf("%s[0%sm", toChar(27), num > 0 ? (";" + num) : "")
logger.error = def(a = "", b = "", c = "") = echo(logger.color(31) + a, b, c + logger.color())
Expand All @@ -13,380 +17,6 @@ logger.debug = match(DEBUG) {
case _: def(a = "", b = "", c = "") = 0
}

class Registry {
GLOBAL_URL = "https://raw.githubusercontent.com/aNNiMON/Own-Programming-Language-Tutorial/registry/registry.json"

def Registry(ctx) {
this.ctx = ctx
this.registry = {}
this.registryPackages = {}
this.registryCacheFile = ctx.TMPDIR + "/ownlang-registry.json"
}

def isRegistryLoaded() = length(this.registry) > 0

def getPackages() {
this.ctx.logger.debug("Registry.getPackages")
if !this.isRegistryLoaded() {
this.loadRegistry()
}
return this.registryPackages
}

def loadRegistry() {
this.ctx.logger.debug("Registry.loadRegistry")
if (!this.loadFromCache()) {
this.loadFromHttp()
}
}

def loadFromCache() {
this.ctx.logger.debug("Registry.loadFromCache")
if exists(this.registryCacheFile) {
f = fopen(this.registryCacheFile, "r")
this.registry = jsondecode(readText(f))
this.registryPackages = this.registry.packages ?? {}
fclose(f)
return true
}
return false
}

def loadFromHttp() {
this.ctx.logger.debug("Registry.loadFromHttp")
extract(ok, content) = httpSync(this.GLOBAL_URL)
this.registry = ok ? jsondecode(content) : {}
this.registryPackages = this.registry.packages ?? {}
if (ok) this.updateCache()
return ok
}

def updateCache() {
this.ctx.logger.debug("Registry.updateCache")
f = fopen(this.registryCacheFile, "w")
writeText(f, jsonencode(this.registry, 0))
flush(f)
fclose(f)
}
}

class Config {
def Config(ctx) {
this.ctx = ctx
this.configName = "own-package.json"
this.config = {}
}

def createNewConfig() = {"dependencies": {}}

def getConfig() {
this.ctx.logger.debug("Config.getConfig")
if length(this.config) == 0 {
f = fopen(this.configName, "i")
this.config = exists(f)
? this.loadConfig()
: this.createNewConfig()
fclose(f)
}
return this.config
}

def loadConfig() {
this.ctx.logger.debug("Config.loadConfig")
f = fopen(this.configName, "r")
config = jsondecode(readText(f))
fclose(f)
return config
}

def saveConfig(config) {
this.ctx.logger.debug("Config.saveConfig")
f = fopen(this.configName, "w")
writeText(f, jsonencode(config, 2))
flush(f)
fclose(f)
}

def mergeConfig(config) {
this.ctx.logger.debug("Config.mergeConfig")
mapping = {
"name": def() = this.getDefaultProjectName(),
"version": def() = this.getDefaultVersion(),
"author": def() = this.getDefaultAuthor(),
"program": def() = this.getDefaultProgram(),
"scripts": def() = {
"default": ["$ownlang$", "-f", "$program$"]
},
"dependencies": def() = {},
}
for property, defaultValue : mapping {
if !arrayKeyExists(property, config)
config[property] = defaultValue()
}
return config
}

def getDefaultProjectName() {
this.ctx.logger.debug("Config.getDefaultProjectName")
parts = getprop("user.dir").replace("\\", "/").split("/")
name = parts[parts.length - 1]
.toLowerCase()
.replace("\\s+", "_")
.replaceAll("[^a-z\\-_]", "")
return match(length(name)) {
case 0: "project"
case x if x > 20: substring(name, 0, 20)
case _: name
}
}

def getDefaultVersion() = "1.0.0"
def getDefaultAuthor() = getprop("user.name", "unknown")
def getDefaultProgram() = "main.own"
}

class Packages {
def Packages(ctx) {
this.ctx = ctx
this.packagesDir = "own-modules"
this.usageFile = ".usage.own"
}

def addDependencies(packages) {
this.ctx.logger.debug("Packages.addDependencies", packages)
if packages.isEmpty() {
this.ctx.logger.error("No package names provided. Try: own add telegram-bot")
return false
}
res = true
config = this.ctx.config.getConfig()
for package : packages {
res = res && this.addDependency(package, config)
}
return res
}

def addDependency(packageName, config) {
this.ctx.logger.debug("Packages.addDependency", packageName)
if packageName == "" {
this.ctx.logger.error("No package name provided. Try: own add telegram-bot")
return false
}
// Check in global registry
registryPackages = this.ctx.registry.getPackages()
if !arrayKeyExists(packageName, registryPackages) {
this.ctx.logger.error("Unknown package " + packageName + ". Check if the name specified correctly")
return false
}
dep = registryPackages[packageName]
// Check if exists in config
if arrayKeyExists(packageName, config.dependencies ?? {}) {
// Exists, check version match
if dep.version == config.dependencies[packageName] {
this.ctx.logger.info("Package " + packageName + " is up to date")
return true
}
}
// Install this dependency
dep.name = packageName
config.dependencies[packageName] = dep.version
downloaded = this.ctx.packages.downloadPackage(dep)
logText = "Installing " + packageName + " " + dep.version
if downloaded {
this.ctx.logger.success(logText + " [success]")
} else {
this.ctx.logger.error(logText + " [failure]")
}
return downloaded
}

def downloadDependencies(dependencies) {
this.ctx.logger.debug("Packages.downloadDependencies")
if length(dependencies) == 0 {
this.ctx.logger.error("No dependency packages provided, or own-package.json is missing")
return false
}
registryPackages = this.ctx.registry.getPackages()
for name, version : dependencies {
logText = "Installing " + name + " " + version
dep = registryPackages[name] ?? {"downloaded": 0}
if !(dep.downloaded ?? 0) {
dep.name = name
dep.downloaded = this.ctx.packages.downloadPackage(dep)
}
if dep.downloaded {
this.ctx.logger.success(logText + " [success]")
} else {
this.ctx.logger.error(logText + " [failure]")
}
}
return true
}

def getPackageFiles(dep) = match (dep.type ?? 0) {
case "gist": {
this.ctx.logger.debug("Packages.getPackageFiles for gist")
extract(ok, content) = httpSync("https://api.github.com/gists/" + dep.id)
return match ok {
case false: []
case true: stream(jsondecode(content).files)
.map(def(r) = [r[0], r[1].raw_url])
.toArray()
}
}
case _: []
}

def getPackagePath(package) {
this.ctx.logger.debug("Packages.getPackagePath", package)
dir = this.packagesDir + "/" + package
stream([this.packagesDir, dir])
.filterNot(::exists)
.forEach(::mkdir)
return dir
}

def downloadPackage(dep) {
this.ctx.logger.debug("Packages.downloadPackage", dep.name)
files = this.getPackageFiles(dep)
dir = this.getPackagePath(dep.name)
result = 0
for mf : files {
extract(name, url) = mf
result += downloader(url, dir + "/" + name)
}
return result > 0
}

def showUsages(packages) {
this.ctx.logger.debug("Packages.showUsages", packages)
if packages.isEmpty() {
this.ctx.logger.error("No package names provided")
return false
}
config = this.ctx.config.getConfig()
for package : packages {
this.showUsage(package, config)
}
return true
}

def showUsage(package, config) {
file = this.packagesDir + "/" + package + "/" + this.usageFile
exists = arrayKeyExists(package, config.dependencies ?? {})
&& exists(file)
if !exists {
this.ctx.logger.error("[" + package + "] "
+ "Error: no .usage.own file provided in the package,"
+ " or the package is not installed")
return false
}
this.ctx.logger.success("\n[" + package + "]")
f = fopen(file, "r")
this.ctx.logger.info(readText(f))
fclose(f)
return true
}
}

class Projects {
def Projects(ctx) {
this.ctx = ctx
this.defaultPrograms = {
"main.own" : "use std\n\necho(\"Hello, world!\", ARGS)"
}
}

def savePrograms() {
this.ctx.logger.debug("Projects.savePrograms")
for name, content : this.defaultPrograms {
if exists(name) continue
f = fopen(name, "w")
writeText(f, content)
flush(f)
fclose(f)
}
}
}

class Own {
def Own(ctx) {
this.ctx = ctx
}

def init() {
this.ctx.logger.debug("Own.init")
config = this.ctx.config.getConfig()
config = this.ctx.config.mergeConfig(config)
this.ctx.config.saveConfig(config)
this.ctx.projects.savePrograms()
this.ctx.logger.success("Project created")
}

def install() {
this.ctx.logger.debug("Own.install")
config = this.ctx.config.getConfig()
this.ctx.packages.downloadDependencies(config.dependencies ?? {})
}

def addDependencies() {
packages = this.nonEmptyArgs()
this.ctx.logger.debug("Own.addDependencies", packages)
this.ctx.packages.addDependencies(packages)
this.ctx.config.saveConfig(this.ctx.config.getConfig())
}

def showUsages() {
packages = this.nonEmptyArgs()
this.ctx.logger.debug("Own.showUsages", packages)
this.ctx.packages.showUsages(packages)
}

def runScript(script) {
this.ctx.logger.debug("Own.runScript", script)
config = this.ctx.config.getConfig()
vars = {
"$ownlang$": "ownlang" + (this.ctx.OSNAME.startsWith("win") ? ".cmd" : ""),
"$program$": config.program ?? "",
"$dir$": getprop("user.dir").replace("\\", "/")
}
if (script == "") script = "default"
if arrayKeyExists(script, config.scripts ?? {}) {
commands = []
for c : config.scripts[script] {
commands += arrayKeyExists(c, vars) ? vars[c] : c
}
this.ctx.logger.debug("Own.runScript run:", commands)
pb = new PB(commands)
pb.inheritIO()
p = pb.start()
} else {
this.ctx.logger.error("No script '%s' found in config's scripts section".sprintf(script))
}
}

def usage() {
this.ctx.logger.info("own package manager v1.0.0")
this.ctx.logger.info("")
this.ctx.logger.info("
|Usage: own [options]
| options:
| init initialize new project
| add [package] add new package dependency to own-package.json
| usage [package] display the package usage (from .usage.own) if exists
| install install packages defined in own-package.json
| run run program defined in the config program section
| run [script] run script defined in the config scripts section
".stripMargin())
}

def nonEmptyArgs(skip = 1) = stream(ARGS)
.skip(skip)
.filter(def(s) = !s.isEmpty())
.toArray()
}


ctx = {}
ctx.DEBUG = DEBUG
ctx.TMPDIR = getenv("TEMP", getenv("TMP", "."))
Expand Down
Loading

0 comments on commit e263de5

Please sign in to comment.