Eco = Object clone do: { -- all packages OK for loading -- name -> [package] packages = [] -- versions loaded by @use: -- name -> package loaded = [] } Eco Package = Object clone Eco Package new := Eco Package clone do: { version = 0 . 1 dependencies = [] include = [] executables = [] contexts = [] environment = Lobby } (e: Eco) initialize := e initialize: (Eco Package load-from: "package.eco") (e: Eco) initialize: pkg := { pkg dependencies each: { c | e packages (find: c from) match: { @none -> error: @(package-unavailable: c from needed: c to) -- filter out versions not satisfying our dependency @(ok: d) -> { safe = d to (filter: { p | p version join: c to }) safe match: { [] -> error: @(no-versions-of: d satisfy: c to for: pkg name) -- initialize the first safe version of the package (p . _) -> { -- remove unsafe versions from the ecosystem d to = safe e initialize: p } call } } call } } @ok } call (e: Eco) load := { eco = Directory home ".eco" "lib" when: Directory (exists?: eco) do: { e packages = Directory (contents: eco) map: { c | versions = Directory (contents: (eco c)) pkgs = versions map: { v | Eco Package load-from: (eco c v "package.eco") } c -> pkgs (sort-by: { a b | a version < b version }) } } @ok } call Eco path-to: (c: Eco Package) := Eco path-to: c name version: c version Eco path-to: (name: String) := Directory home ".eco" "lib" name Eco path-to: pkg version: (v: Version) := (Eco path-to: pkg) (v as: String) Eco executable: (name: String) := Directory home ".eco" "bin" name Eco which-main: (path: String) := if: File (exists?: (path "main.hs")) then: { "main.hs" } else: { "main.atomo" } Eco install: (path: String) := { pkg = Eco Package load-from: (path "package.eco") target = Eco path-to: pkg contents = (Eco which-main: path) . ("package.eco" . pkg include) Directory create-tree-if-missing: target contents each: { c | if: Directory (exists?: (path c)) then: { Directory copy: (path c) to: (target c) } else: { File copy: (path c) to: (target c) } } pkg executables each: { e | exe = [ "#!/usr/bin/env atomo" "require: " .. (".." "lib" pkg name pkg version (as: String) e to) show ] unlines with-output-to: (Eco executable: e from) do: { exe print } File set-executable: (Eco executable: e from) to: True } @ok } call Eco uninstall: (name: String) version: (version: Version) := { path = Eco path-to: name version: version pkg = Eco Package load-from: (path "package.eco") Directory remove-recursive: path pkg executables each: { e | File remove: (Eco executable: e from) } @ok } call Eco uninstall: (name: String) := { Directory (contents: (Eco path-to: name)) each: { v | Eco uninstall: name version: (v to: Version) } @ok } call Eco Package load-from: (file: String) := Eco Package new do: { load: file } (p: Eco Package) name: (n: String) := p name = n (p: Eco Package) description: (d: String) := p description = d (p: Eco Package) version: (v: Version) := p version = v (p: Eco Package) author: (a: String) := p author = a (p: Eco Package) include: (l: List) := p include = l (p: Eco Package) depends-on: (ds: List) := p dependencies = ds map: { d | if: (d is-a?: Association) then: { d } else: { d -> { any } } } (p: Eco Package) executables: (es: List) := p executables = es Eco load: name version: check in: env := Eco packages (lookup: name) match: { @none -> error: @(package-unavailable: name) @(ok: []) -> error: @(no-package-versions: name) @(ok: pkgs) -> { pkg = pkgs (filter: { p | p version join: check }) match: { [] -> error: @(no-versions-of: name satisfy: check) (p . _) -> p } path = Eco path-to: pkg pkg contexts << env super env load: (path (Eco which-main: path)) pkg environment = env when: name (in?: Eco loaded (map: @from)) not do: { Eco loaded << (name -> pkg) } env } call } context use: (name: String) := context use: name version: { any } context use: (name: String) version: (v: Version) := context use: name version: (evaluate: `({ == ~v })) macro (context use: name version: (v: Primitive)) `(~context use: ~name version: { == ~v }) macro (context use: name version: (v: `(~_ . ~_))) `(~context use: ~name version: { == ~v }) context use: (name: String) version: (check: Block) := Eco loaded (lookup: name) match: { @none -> (Eco load: name version: check in: context clone) @(ok: p) -> condition: { p version (join: check) not -> error: @(incompatible-version-loaded: name needed: check) (context in?: p contexts) -> p environment otherwise -> (Eco load: name version: check in: context clone) } } @(package-unavailable: name) describe-error := "package unavailable: " .. name @(package-unavailable: name needed: constraint) describe-error := "package unavailable: " .. name .. " (needed " .. constraint show .. ")" @(no-versions-of: pkg satisfy: check for: needer) describe-error := "no versions of '" .. pkg .. "' satisfy version constraint " .. check show .. " needed by " .. needer @(no-package-versions: pkg) describe-error := "weird: package '" .. pkg .. "' has no versions" @(no-versions-of: pkg satisfy: check) describe-error := "no versions of '" .. pkg .. "' satisfy version constraint " .. check show @(incompatible-version-loaded: pkg needed: check) describe-error := "package '" .. pkg .. "' with version constraint " .. check show .. " required, but an incompatible version has already been loaded"