packages feed

atomo-0.1: src/Atomo/Kernel/Eco.hs

{-# LANGUAGE QuasiQuotes #-}
module Atomo.Kernel.Eco where

import Atomo.Environment
import Atomo.Haskell


load :: VM ()
load = loadVersions >> loadEco

loadEco :: VM ()
loadEco = mapM_ eval [$es|
    Eco = Object clone do: {
        -- all packages OK for loading
        -- name -> [package]
        packages = []

        -- versions loaded by @use:
        -- name -> package
        loaded = []
    }

    Eco Package = Object clone do: {
        version = 0 . 1
        dependencies = []
        include = []
        executables = []
    }

    (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: ("required package not found: " .. c from .. " (satisfying " .. c to show .. ")")

                -- 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 package "
                                    d from
                                    " (" .. d to (map: { p | p version as: String }) (join: ", ") .. ")"
                                    " satisfy constraint "
                                    c to show
                                    " for package " .. pkg name show
                                ] concat

                        -- 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"

        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 })
        }
    } 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: (name: String) version: (v: Version) :=
        (Eco path-to: name) </> (v as: String)

    Eco executable: (name: String) :=
        Directory home </> ".eco" </> "bin" </> name

    Eco install: (path: String) := {
        pkg = Eco Package load-from: (path </> "package.eco")
        target = Eco path-to: pkg

        contents = "main.atomo" . ("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 |
            File copy: (path </> e to) to: (Eco executable: e from)
            File set-executable: (Eco executable: e from) to: True
        }
    } 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)
        }
    } call

    Eco uninstall: (name: String) :=
        Directory (contents: (Eco path-to: name)) each: { v |
            Eco uninstall: name version: (v as: Version)
        }

    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 packages (lookup: name) match: {
                    @none -> raise: ("package not found: " .. name)
                    @(ok: []) -> raise: ("no versions for package: " .. name)
                    @(ok: pkgs) -> {
                        satisfactory = pkgs filter: { p | p version join: check }

                        when: satisfactory empty?
                            do: {
                                raise: 
                                    [
                                        "no versions of package "
                                        name
                                        " (" .. versions (map: @(as: String)) (join: ", ") .. ")"
                                        " satisfy constraint "
                                        check show
                                    ] concat
                            }

                        Eco loaded << (name -> satisfactory head)

                        context load: ((Eco path-to: satisfactory head) </> "main.atomo")
                    } call
                }
            } call

            @(ok: p) ->
                when: (p version join: check) not
                    do: { raise: ("package " .. name .. " already loaded, but version (" .. (v as: String) .. ") does not satisfy constraint " .. check show) }
        }

        @ok
    } call

    (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 Package load-from: (file: String) :=
        Eco Package clone do: { load: file }

    Eco load
|]

loadVersions :: VM ()
loadVersions = mapM_ eval [$es|
    Version = Object clone

    (major: Integer) . (minor: Integer) :=
        Version clone do: {
            major = major
            minor = minor
        }

    (major: Integer) . (rest: Version) :=
        Version clone do: {
            major = major
            minor = rest
        }

    (v: Version) show :=
        v major show .. " . " .. v minor show

    (v: Version) as: String :=
        v major show .. "." .. v minor (as: String)

    (s: String) as: Version :=
        s (split-on: '.') (map: @(as: Integer)) reduce-right: @.

    (n: Integer) as: Version := n . 0

    (a: Version) == (b: Version) :=
        (a major == b major) and: { a minor == b minor }

    (a: Version) > (b: Version) :=
        (a major > b major) or: { a minor > b minor }

    (n: Integer) > (v: Version) :=
        (n > v major) or: { v major == n && v minor /= 0 }

    (v: Version) > (n: Integer) :=
        (v major > n) or: { v major == n && v minor /= 0 }

    (a: Version) < (b: Version) :=
        (a major < b major) or: { a minor < b minor }

    (n: Integer) < (v: Version) :=
        (n < v major) or: { v major == n && v minor /= 0 }

    (v: Version) < (n: Integer) :=
        (v major < n) or: { v major == n && v minor /= 0 }

    (a: Version) >= (b: Version) :=
        (a major >= b major) or: { a minor >= b minor }

    (n: Integer) >= (v: Version) :=
        n >= v major

    (v: Version) >= (n: Integer) :=
        v major >= n

    (a: Version) <= (b: Version) :=
        (a major <= b major) or: { a minor <= b minor }

    (n: Integer) <= (v: Version) :=
        n <= v major

    (v: Version) <= (n: Integer) :=
        v major <= n
|]