Skip to content

Commit

Permalink
Move common functionality to lein-search/util namespace
Browse files Browse the repository at this point in the history
  • Loading branch information
purcell committed Aug 9, 2010
1 parent 94b4df4 commit af1da90
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 152 deletions.
167 changes: 167 additions & 0 deletions src/lein_search/util.clj
@@ -0,0 +1,167 @@
(ns lein-search.util
(:use [clojure.contrib.duck-streams :only (reader writer with-out-writer)])
(:use [clojure.contrib.str-utils :only (chomp str-join)])
(:import (java.io File PushbackReader) java.util.zip.GZIPInputStream java.net.URL))


(def *lein-dir* (str (System/getProperty "user.home") "/.lein"))

;;; User input functions

(defn good-read-line []
(binding [*in* (-> System/in java.io.InputStreamReader.
clojure.lang.LineNumberingPushbackReader.)]
(read-line)))

(defn prompt-for-input [prompt]
(print prompt)
(flush)
(chomp (good-read-line)))

(defn yes-or-no-prompt [question]
(condp = (prompt-for-input (str question " (y/n) "))
"y" true
"n" false
(recur question)))

(defn prompt-for-number [prompt]
(try (Integer/parseInt (prompt-for-input prompt))
(catch java.lang.NumberFormatException e nil)))

(defn choose-from-numbered-list-if-multiple
"Return first item immediately if there is only one, otherwise prompt the user
with a numbered list of choices."
[choices prompt formatter]
(if (= 1 (count choices))
(first choices)
(do (println
(str-join "\n"
(for [[n i] (map vector (iterate inc 1) choices)]
(str n ": " (formatter i)))))
(loop []
(let [v (prompt-for-number (str prompt ": "))]
(if (or (nil? v) (nil? (nth choices (dec v) nil)))
(recur)
(nth choices (dec v))))))))

;;; Reading/writing clojure forms

(defn read-clj [f]
(with-open [r (reader f)]
(read (java.io.PushbackReader. r))))

(defn- project-clj-path [project]
(str (:root project) "/project.clj"))

(defn read-project-clj [project]
(read-clj (project-clj-path project)))

(defn write-project-clj [project forms]
(with-out-writer (project-clj-path project)
(pr forms)))


;;; Modifying defproject forms

;; TODO: this won't add a :dev-dependencies or :dependencies if not already present
(defn update-dependency-list
"Modify the project's dependency list of the given type by passing it through f"
[project dep-type f]
(->> project
(reduce
(fn [[form prev] n]
(if (= prev dep-type)
[(cons (vec (f n)) form) nil]
[(cons n form) n]))
[() nil])
first
reverse))

(defn add-artifact [project type artifact version]
(update-dependency-list project type
(fn [deps]
(cons [(symbol artifact) version] deps))))

(defn update-artifact [project dep-type artifact new-version]
(update-dependency-list project dep-type
(fn [deps]
(for [[a v] deps]
[a (if (= a (symbol artifact))
new-version
v)]))))



;;; Grokking version strings

(defn latest-stable [versions]
(first (filter (partial re-find #"^(\d+).(\d+).(\d+)$") versions)))

(defn split-version [v]
(if-let [[_ major minor patch suffix] (when v (re-find #"(\d+)\.(\d+)(?:\.(\d+))?(?:-(.*))?" v))]
[(Integer/parseInt major) (Integer/parseInt minor) (Integer/parseInt (or patch "0")) (or suffix "")]
[0 0 0 ""]))

(defn compare-versions [v1 v2]
(let [vers1 (split-version v1)
vers2 (split-version v2)
version-comparison (.compareTo (subvec vers1 0 3) (subvec vers2 0 3))]
(if (zero? version-comparison)
(let [v1-suffix (last vers1)
v2-suffix (last vers2)]
(cond
(= v1-suffix v2-suffix) 0
(= v1-suffix "") 1
(= v2-suffix "") -1
(= v1-suffix "SNAPSHOT") 1
(= v2-suffix "SNAPSHOT") -1
:else (.compareTo v1-suffix v2-suffix)))
version-comparison)))


;;; Clojars cache

(defn- read-clojars-index [url]
(with-open [r (reader (GZIPInputStream. (.openStream (URL. url))))]
(let [r (PushbackReader. r)]
(loop [result [] code (read r false false)]
(if code
(recur (conj result code) (read r false false))
result)))))

(defn write-clojars-cache []
(if (not (.exists (File. *lein-dir*))) (.mkdirs (File. *lein-dir*)))
(with-out-writer (str *lein-dir* "/clojars")
(pr (read-clojars-index "http://clojars.org/repo/feed.clj.gz"))))

(defn read-clojars-cache []
(read-clj (str *lein-dir* "/clojars")))

(defn find-clojar [what]
(let [[group artifact] (if-let [match (re-find #"^([^/]+)/(.+)$" what)]
(next match)
[what what])]
(->> (read-clojars-cache)
(filter
(fn [{artifact-id :artifact-id group-id :group-id}]
(and
(= artifact-id artifact)
(= group-id group)))))))

(defn search-clojar [what]
(let [p (re-pattern what)]
(doall (filter
(fn [{description :description artifact-id :artifact-id group-id :group-id}]
(or
(re-find p (or description ""))
(re-find p artifact-id)
(re-find p group-id)))
(read-clojars-cache)))))

(defn clojars-artifact-name
([group-id article-id]
(if (= group-id article-id)
article-id
(str group-id "/" article-id)))
([{artifact-id :artifact-id group-id :group-id}]
(clojars-artifact-name group-id artifact-id)))
79 changes: 4 additions & 75 deletions src/leiningen/add.clj
Expand Up @@ -2,77 +2,16 @@
"Interactively adds a dependency from clojars.
With one parameter it will add the latest stable of the corresponding version with two arguments it will take the second argument as version.
If the first parameter is --dev or -d it will work exactly as without --dev just that it will add a dev dependency."
(:use (clojure.contrib duck-streams seq-utils str-utils)
[leiningen.update-repo :only [*lein-dir*]]
[leiningen.search :only [read-clj search-clojar artifact-name]]))
(:use [clojure.contrib.str-utils :only (str-join)]
lein-search.util))


(defn good-read-line []
(binding [*in* (-> System/in (java.io.InputStreamReader.) (clojure.lang.LineNumberingPushbackReader.))] (read-line)))

(defn prompt-for-input [prompt]
(print prompt)
(flush)
(chomp (good-read-line)))

(defn prompt-for-number [prompt]
(try (Integer/parseInt (prompt-for-input prompt))
(catch java.lang.NumberFormatException e nil)))

(defn update-dependency-list
"Modify the project's dependency list of the given type by passing it through f"
[project dep-type f]
(->> project
(reduce
(fn [[form prev] n]
(if (= prev dep-type)
[(cons (vec (f n)) form) nil]
[(cons n form) n]))
[() nil])
first
reverse))

(defn add-artifact [project type artifact version]
(update-dependency-list project type
(fn [deps]
(cons [(symbol artifact) version] deps))))

(defn find-clojar [what]
(let [[group artifact] (if-let [match (re-find #"^([^/]+)/(.+)$" what)]
(next match)
[what what])]
(->> (read-clj (str *lein-dir* "/clojars"))
(filter
(fn [{artifact-id :artifact-id group-id :group-id}]
(and
(= artifact-id artifact)
(= group-id group)))))))

(defn choose-from-numbered-list-if-multiple
"Return first item immediately if there is only one, otherwise prompt the user
with a numbered list of choices."
[choices prompt formatter]
(if (= 1 (count choices))
(first choices)
(do (println
(str-join "\n"
(for [[n i] (map vector (iterate inc 1) choices)]
(str n ": " (formatter i)))))
(loop []
(let [v (prompt-for-number (str prompt ": "))]
(if (or (nil? v) (nil? (nth choices (dec v) nil)))
(recur)
(nth choices (dec v))))))))

(defn get-version [[artifact-name available-versions]]
[artifact-name
(choose-from-numbered-list-if-multiple available-versions
"Please select a version"
(fn [v] (str artifact-name " " v)))])

(defn latest-stable [versions]
(first (filter (partial re-find #"^(\d+).(\d+).(\d+)$") versions)))

(defn get-artifact-id [res]
(get-version
(choose-from-numbered-list-if-multiple res
Expand All @@ -81,16 +20,6 @@ with a numbered list of choices."
(str name " (" (str-join ", " vers) ")")))))

(defn add [project artifact & args]
(defn- project-clj-path [project]
(str (:root project) "/project.clj"))

(defn read-project-clj [project]
(read-clj (project-clj-path project)))

(defn write-project-clj [project forms]
(with-out-writer (project-clj-path project)
(pr forms)))

(let [dev (or (= artifact "--dev") (= artifact "-d"))
artifact (if dev (first args) artifact)
args (if dev (rest args) args)
Expand All @@ -100,8 +29,8 @@ with a numbered list of choices."
(if (empty? res)
(println "Sorry; nothing on clojars that matches" artifact (if version ""))
(if (and version (not-any? (partial = version) (:versions res)))
(println "Sorry; there is no version" version "for" (artifact-name res) ". Try one of:" (str-join ", " (:versions res)))
(let [[a v] [(artifact-name res) (if version version (latest-stable (:versions res)))]]
(println "Sorry; there is no version" version "for" (clojars-artifact-name res) ". Try one of:" (str-join ", " (:versions res)))
(let [[a v] [(clojars-artifact-name res) (if version version (latest-stable (:versions res)))]]
(println "Adding:" a v)
(write-project-clj project
(add-artifact (read-project-clj project)
Expand Down
30 changes: 4 additions & 26 deletions src/leiningen/search.clj
@@ -1,39 +1,17 @@
(ns leiningen.search
"Searches the indexed clojars.org repository. Giving -v as first argument prints the versions instead of the description."
(:use (clojure.contrib duck-streams seq-utils str-utils)
[leiningen.update-repo :only [*lein-dir* compare-versions]])
(:use [clojure.contrib.str-utils :only (str-join re-sub)]
lein-search.util)
(:import java.io.File))


(defn read-clj [f]
(with-open [r (reader f)]
(read (java.io.PushbackReader. r))))

(defn search-clojar [what]
(let [p (re-pattern what)]
(doall (filter
(fn [{description :description artifact-id :artifact-id group-id :group-id}]
(or
(re-find p (or description ""))
(re-find p artifact-id)
(re-find p group-id)))
(read-clj (str *lein-dir* "/clojars"))))))

(defn artifact-name
([group-id article-id]
(if (= group-id article-id)
article-id
(str group-id "/" article-id)))
([{artifact-id :artifact-id group-id :group-id}]
(artifact-name group-id artifact-id)))

(defn search [project what & args]
(let [show-versions (= "-v" what)
what (if show-versions (first args) what)]
(if (.exists (File. (str *lein-dir* "/clojars")))
(let [m (search-clojar what)]
(println "Results for " what ":")
(if show-versions
(println (str-join "\n" (map (fn [{versions :versions artifact-id :artifact-id group-id :group-id}] (str (artifact-name group-id artifact-id) ": " (str-join ", " versions))) m)))
(println (str-join "\n" (map (fn [{description :description artifact-id :artifact-id group-id :group-id}] (format "%-40s - %s" (artifact-name group-id artifact-id) (re-sub #"\n\s*" " " (or description "No description given")))) m)))))
(println (str-join "\n" (map (fn [{versions :versions artifact-id :artifact-id group-id :group-id}] (str (clojars-artifact-name group-id artifact-id) ": " (str-join ", " versions))) m)))
(println (str-join "\n" (map (fn [{description :description artifact-id :artifact-id group-id :group-id}] (format "%-40s - %s" (clojars-artifact-name group-id artifact-id) (re-sub #"\n\s*" " " (or description "No description given")))) m)))))
(println "No repo index found, please run lein update first."))))
18 changes: 1 addition & 17 deletions src/leiningen/update.clj
@@ -1,24 +1,8 @@
(ns leiningen.update
"lein update checks for newer versions of currently used dependencies and aks the user if they should be updated to the latest stable."
(:use (clojure.contrib duck-streams seq-utils str-utils)
[leiningen.add :only [latest-stable add-artifact find-clojar good-read-line update-dependency-list prompt-for-input read-project-clj write-project-clj]]
[leiningen.update-repo :only [compare-versions]]))
(:use lein-search.util))


(defn update-artifact [project dep-type artifact new-version]
(update-dependency-list project dep-type
(fn [deps]
(for [[a v] deps]
[a (if (= a (symbol artifact))
new-version
v)]))))

(defn yes-or-no-prompt [question]
(condp = (prompt-for-input (str question " (y/n) "))
"y" true
"n" false
(recur question)))

(defn ask-for-update [artifact version new-version]
(yes-or-no-prompt (str "You are currently using "artifact" version "version". Do you want to update to "new-version"?")))

Expand Down
36 changes: 2 additions & 34 deletions src/leiningen/update_repo.clj
@@ -1,40 +1,8 @@
(ns leiningen.update-repo
"Updates the clojars.org repositories index."
(:use [clojure.contrib.duck-streams :only (reader writer with-out-writer)])
(:import (java.io File) java.util.zip.GZIPInputStream java.net.URL))
(:use lein-search.util))

(def *lein-dir* (str (System/getProperty "user.home") "/.lein"))

(defn split-version [v]
(if-let [[_ major minor patch suffix] (when v (re-find #"(\d+)\.(\d+)(?:\.(\d+))?(?:-(.*))?" v))]
[(Integer/parseInt major) (Integer/parseInt minor) (Integer/parseInt (or patch "0")) (or suffix "")]
[0 0 0 ""]))

(defn compare-versions [v1 v2]
(let [vers1 (split-version v1)
vers2 (split-version v2)
version-comparison (.compareTo (subvec vers1 0 3) (subvec vers2 0 3))]
(if (zero? version-comparison)
(let [v1-suffix (last vers1)
v2-suffix (last vers2)]
(cond
(= v1-suffix v2-suffix) 0
(= v1-suffix "") 1
(= v2-suffix "") -1
(= v1-suffix "SNAPSHOT") 1
(= v2-suffix "SNAPSHOT") -1
:else (.compareTo v1-suffix v2-suffix)))
version-comparison)))

(defn read-index [url]
(with-open [r (reader (GZIPInputStream. (.openStream (URL. url))))]
(loop [result [] code (read r false false)]
(if code
(recur (conj result code) (read r false false))
result))))

(defn update-repo [project & args]
(if (not (.exists (File. *lein-dir*))) (.mkdirs (File. *lein-dir*)))
(println "Getting the list of packages on clojars.org ...")
(with-out-writer (str *lein-dir* "/clojars")
(pr (read-index "http://clojars.org/repo/feed.clj.gz"))))
(write-clojars-cache))

0 comments on commit af1da90

Please sign in to comment.