I wrote a package manager in clojure that does 5 things:
depend a b //creates a and b (if they don't exist) and adds dependency on a
install a //installs a and its dependencies
list //prints out the install packages
sys //prints out all packages
remove or uninstall packages and dependencies that are no longer needed.
I did this as an exercise to learn Clojure. I'm not sure if this is idiomatic are not. I tried to avoid typedef since that seems like attempting OOP on Clojure. I'm just wondering if there are any very obvious non idiomatic code or code smells.
(use '[clojure.string :only [split, lower-case]])
(def DEPEND "depend")
(def INSTALL "install")
(def REMOVE "remove")
(def UNINSTALL "uninstall")
(def SYS "sys")
(def LIST "list")
(def INFO "info")
(def EXIT "exit")
(def END "end")
(def all-packages (atom {}))
(def installed-packages (atom (sorted-set)))
(defn in? [seq elm]
(some #(= elm %) seq))
(defn create
"makes a package element with provided name, option list of providers
are packages requried by package, optional list of clients are packages using
package"
([name]
(create name #{} #{}))
([name providers clients]
{:name name, :providers providers, :clients clients}))
(defn add-client
[package client]
(create (:name package) (:providers package) (conj (:clients package) client)))
(defn remove-client
[package client]
(create (:name package) (:providers package) (disj (:clients package) client)))
(defn add-provider
"add a provided to package"
([package] package)
([package provider]
(create (:name package) (conj (:providers package) provider) (:clients package)))
([package provider & more-providers]
(reduce add-provider package (cons provider more-providers))))
(defn get-providers [package]
(get (get @all-packages package) :providers ))
(defn get-clients [package]
(get (get @all-packages package) :clients ))
(defn get-package [package]
(get @all-packages package))
(defn exists? [package]
(contains? @all-packages package))
(defn dependent? [first second]
(if (in? (cons first (get-providers first)) second)
(do (println (str "\t" first) "depends on" second) true)
(some #(dependent? % second) (get-providers first))))
(defn update-sys [package]
(swap! all-packages assoc (:name package) package))
(defn add-sys-package
"adds a package to all-packages"
[package & deps]
(doseq [dep deps]
(if-not (exists? dep) (update-sys (create dep))))
(if (not-any? #(dependent? % package) deps)
(update-sys (apply add-provider (cons (create package) deps)))
(println "Ignoring command")))
(defn print-sys []
(doseq [[k,v] @all-packages] (println "\t" v)))
(defn print-installed []
(doseq [v @installed-packages] (println "\t" v)))
(defn installed? [package]
(contains? @installed-packages package))
(defn install-new [package]
(do (println "\t installing" package)
(swap! installed-packages conj package)))
(defn install
[package self-install]
(if-not (exists? package) (add-sys-package package))
(if-not (installed? package)
(do (doseq [provider (get-providers package)] (if-not (installed? provider) (install provider false)))
(doseq [provider (get-providers package)] (update-sys (add-client (get-package provider) package)))
(install-new package))
(do
(if self-install (update-sys (add-client (get-package package) package)))
(println "\t" package "is already installed."))))
(defn not-needed? [package self-uninstall]
(def clients
(if self-uninstall
(disj (get-clients package) package)
(get-clients package)))
(empty? clients))
(defn uninstall-package [package]
(println "\t uninstalling" package)
(swap! installed-packages disj package))
(defn uninstall
[package self-uninstall]
(if (installed? package)
(if (not-needed? package self-uninstall)
(do (doseq [provider (get-providers package)] (update-sys (remove-client (get-package provider) package)))
(uninstall-package package)
(doseq [provider (filter #(not-needed? % false) (get-providers package))] (uninstall provider false)))
(println "\t" package "is still needed"))
(println "\t" package "is not installed")))
(def run (atom true))
(defn stop-run []
(reset! run false))
(defn exit []
(println "goodbye") (stop-run))
(defn runprog []
(println "starting")
(reset! run true)
(while (true? @run)
(def line (read-line))
(def command (first (split line #" +")))
(def args (rest (split line #" +")))
(condp = (lower-case command)
DEPEND (apply add-sys-package args)
LIST (print-installed)
INSTALL (install (first args) true)
INFO (println (get-package (first args)))
[REMOVE UNINSTALL] (uninstall (first args) true)
UNINSTALL (uninstall (first args) true)
SYS (print-sys)
EXIT (exit)
END (exit)
())))
(runprog)
Edit: After incorporating almost all suggestions github gist