packages feed

atomo-0.2: prelude/eco.atomo

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 -> raise: @(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: {
              [] ->
                raise: @(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"
            "load: " .. (path </> 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 as: 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 -> { True } }
    }

(p: Eco Package) executables: (es: List) :=
  p executables = es

Eco load: name version: check in: env :=
  Eco packages (lookup: name) match: {
    @none -> raise: @(package-unavailable: name)
    @(ok: []) -> raise: @(no-package-versions: name)
    @(ok: pkgs) ->
      { pkg =
          pkgs (filter: { p | p version join: check }) match: {
            [] -> raise: @(no-versions-of: name satisfy: check)
            (p . _) -> p
          }

        path = Eco path-to: pkg

        pkg contexts = 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 = Eco loaded << (name -> pkg) }

        env
      } call
  }

context use: (name: String) :=
  context use: name version: { True }

context use: (name: String) version: (v: Version) :=
  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 ->
          raise: @(incompatible-version-loaded: name needed: check)

        (context in?: p contexts) -> p environment

        otherwise ->
          (Eco load: name version: check in: context clone)
      }
  }