├── .gitignore ├── CHANGES.md ├── README.md ├── project.clj ├── src └── leiningen │ ├── localrepo.clj │ └── localrepo │ └── internal.clj └── test └── leiningen └── test_localrepo.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /.lein-repl-history 2 | /pom.xml* 3 | /target 4 | /.classpath 5 | /.project 6 | /.settings 7 | 8 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes and TODO 2 | 3 | 4 | ## 0.5.4 / 2017-June-21 5 | 6 | * Fix #11 - `lein help localrepo` now lists complete help text 7 | * Upgrade dependency `clojure/tools.cli` version to `0.3.5` 8 | * Exit with error message and non-zero exit code when `coords` command cannot determine artifact-ID and version 9 | 10 | 11 | ## 0.5.3 / 2013-December-05 12 | 13 | * Report current directory too if specified file/dir is not found 14 | 15 | 16 | ## 0.5.2 / 2013-July-07 17 | 18 | * Report error if user tries to install non-existent file 19 | 20 | 21 | ## 0.5.1 / 2013-June-29 22 | 23 | * Omit description and URL from default POM file 24 | 25 | 26 | ## 0.5.0 / 2013-May-27 27 | 28 | * Allow user to override default local repo path 29 | * Let user specify a POM file during install (auto-generate if unspecified) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lein-localrepo 2 | 3 | Leiningen plugin to work with local Maven repository. 4 | 5 | 6 | ## Installation 7 | 8 | ### Lein 2 users 9 | 10 | The recommended way is to install as a global plugin in `~/.lein/profiles.clj` 11 | (for Windows users `%USERPROFILE%\.lein\profiles.clj`): 12 | 13 | {:user {:plugins [[lein-localrepo "0.5.4"]]}} 14 | 15 | You may also install as a project plugin in `project.clj`: 16 | 17 | :plugins [[lein-localrepo "0.5.4"]] 18 | 19 | 20 | ### Lein 1.x users 21 | 22 | Either install as a plugin: 23 | 24 | $ lein plugin install lein-localrepo "0.3" 25 | 26 | Or, include as a dev-dependency: 27 | 28 | :dev-dependencies [lein-localrepo "0.3"] 29 | 30 | 31 | ## Usage 32 | 33 | ### Guess Leiningen (Maven) coordinates of a file 34 | 35 | $ lein localrepo coords 36 | 37 | Example: 38 | 39 | $ lein localrepo coords foo-bar-1.0.6.jar 40 | 41 | Output: 42 | 43 | foo-bar-1.0.6.jar foo-bar/foo-bar 1.0.6 44 | 45 | 46 | ### Install artifacts to local Maven repository 47 | 48 | $ lein localrepo install [-r repo-path] [-p pom-file] <[groupId/]artifactId> 49 | 50 | If no POM file is specified, a minimal POM will be automatically generated. 51 | 52 | Examples: 53 | 54 | $ lein localrepo install foo-1.0.6.jar com.example/foo 1.0.6 55 | $ lein localrepo install foomatic-1.3.9.jar foomatic 1.3.9 56 | $ lein localrepo coords /tmp/foobar-1.0.0-SNAPSHOT.jar | xargs lein localrepo install 57 | 58 | 59 | ### List artifacts in local Maven repository: 60 | 61 | $ lein localrepo list [-r repo-path] [-s | -f | -d] 62 | 63 | Examples: 64 | 65 | $ lein localrepo list # lists all artifacts, all versions 66 | $ lein localrepo list -s # lists all artifacts with description 67 | $ lein localrepo list -f # lists all artifacts and filenames 68 | $ lein localrepo list -d # lists all artifacts with detail 69 | 70 | 71 | ### Remove artifacts from local Maven repository (Not Yet Implemented): 72 | 73 | $ lein localrepo remove <[groupId/]artifactId> [] 74 | 75 | Examples: 76 | 77 | $ lein localrepo remove com.example/foo # removes all versions 78 | $ lein localrepo remove foomatic # removes all versions 79 | $ lein localrepo remove com.example/foo 1.0.3 # removes only specified version 80 | 81 | Note: 82 | As an alternative while this feature is being implemented, removing artifacts is composed of two steps: 83 | First, find the path to the artifact with `lein classpath | tr ":" "\n" | grep m2.*`. 84 | Second, delete the directory of that artifact from the group ID or the root of that artifact. 85 | 86 | 87 | ## Getting in touch 88 | 89 | On Twitter: [@kumarshantanu](http://twitter.com/kumarshantanu) 90 | 91 | On Leiningen mailing list: [http://groups.google.com/group/leiningen](http://groups.google.com/group/leiningen) 92 | 93 | 94 | ## License 95 | 96 | Copyright (C) 2011-2017 Shantanu Kumar 97 | 98 | Distributed under the Eclipse Public License, the same as Clojure. 99 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject lein-localrepo "0.5.4" 2 | :description "Leiningen local repository plugin" 3 | :url "https://github.com/kumarshantanu/lein-localrepo" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/tools.cli "0.3.5"]] 7 | :eval-in-leiningen true) 8 | -------------------------------------------------------------------------------- /src/leiningen/localrepo.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.localrepo 2 | (:require 3 | [leiningen.core.main :as main] 4 | [cemerick.pomegranate.aether :as aether] 5 | [clojure.java.io :as jio] 6 | [clojure.string :as str] 7 | [clojure.pprint :as ppr] 8 | [clojure.set :as set] 9 | [clojure.tools.cli :as cli] 10 | [clojure.xml :as xml] 11 | [leiningen.localrepo.internal :as in]) 12 | (:import 13 | (java.util Date) 14 | (java.text DateFormat) 15 | (java.io File) 16 | (java.util.jar JarFile))) 17 | 18 | 19 | (def local-repo-path (str/join File/separator [(System/getProperty "user.home") 20 | ".m2" "repository"])) 21 | 22 | 23 | (defn pwd [] 24 | (.getParent (.getAbsoluteFile (File. ".")))) 25 | 26 | 27 | (defn assert-dir 28 | [^String dir] 29 | (if (in/dir? (jio/file dir)) 30 | dir 31 | (main/abort (format "ERROR: '%s' is not a directory, current directory is: %s" dir (pwd))))) 32 | 33 | 34 | (defn assert-file 35 | [^String file] 36 | (if (in/file? (jio/file file)) 37 | file 38 | (main/abort (format "ERROR: '%s' is not a file, current directory is: %s" file (pwd))))) 39 | 40 | 41 | (defn split-artifactid 42 | "Given 'groupIp/artifactId' string split them up and return 43 | as a vector of 2 elements." 44 | [^String artifact-id] 45 | (let [tokens (str/split artifact-id #"/") 46 | tcount (count tokens) 47 | [gi ai] tokens] 48 | (if (or (zero? tcount) 49 | (> tcount 2) 50 | (nil? gi)) 51 | (in/illegal-arg "Invalid groupId/artifactId:" artifact-id) 52 | (if (nil? ai) 53 | [gi gi] 54 | [gi ai])))) 55 | 56 | 57 | (def doc-coords 58 | "Guess Leiningen coordinates of given filename. 59 | Arguments: 60 | 61 | Example: 62 | Input - local/jars/foo-bar-1.0.6.jar 63 | Output - foo-bar-1.0.6.jar foo-bar 1.0.6") 64 | 65 | 66 | (defn c-coords 67 | "Guess Leiningen coordinates of given filename. 68 | Example: 69 | Input - local/jars/foo-bar-1.0.6.jar 70 | Output - foo-bar-1.0.6.jar foo-bar 1.0.6" 71 | [^String filepath] 72 | (let [filename (in/pick-filename filepath) 73 | tokens (drop-last 74 | (re-find (re-matcher #"(.+)\-(\d.+)\.(\w+)" 75 | filename))) 76 | [_ artifact-id version] tokens] 77 | (if (and artifact-id version) 78 | (println filepath (str artifact-id "/" artifact-id) version) 79 | (main/abort "ERROR: Cannot determine artifact-ID and version")))) 80 | 81 | 82 | (def doc-install 83 | "Install artifact to local repository 84 | Arguments: 85 | [options] 86 | Options: 87 | -r | --repo repo-path 88 | -p | --pom POM-file (minimal POM is generated if no POM file specified) 89 | Example: 90 | foo-1.0.jar bar/foo 1.0") 91 | 92 | 93 | (def default-pom-format 94 | " 95 | 96 | 4.0.0 97 | %s 98 | %s 99 | %s 100 | %s 101 | ") 102 | 103 | 104 | (defn default-pom-file 105 | "Generate default temporary POM file returning the file name" 106 | [group-id artifact-id version] 107 | (let [pom-file (File/createTempFile "pom" ".xml") 108 | pom-name (.getAbsolutePath pom-file)] 109 | (.deleteOnExit pom-file) 110 | (spit pom-name (format default-pom-format 111 | group-id artifact-id version artifact-id)) 112 | pom-name)) 113 | 114 | 115 | (defn c-install* 116 | [repo-path pom-filename filename artifact-id version] 117 | (aether/install :local-repo (assert-dir repo-path) 118 | :coordinates [(symbol artifact-id) version] 119 | :jar-file (jio/file (assert-file filename)) 120 | :pom-file (assert-file pom-filename)) 121 | (main/exit 0)) 122 | 123 | 124 | (defn c-install 125 | [& args] 126 | (let [[options args banner] (cli/cli args 127 | ["-r" "--repo" "Local repo path" :default local-repo-path] 128 | ["-p" "--pom" "Artifact POM file"]) 129 | help-abort (fn [] 130 | (println doc-install) 131 | (main/abort))] 132 | (cond 133 | (not= 3 (count args)) (help-abort) 134 | :otherwise (let [[filename artifact-id version] args 135 | [pom-group-id pom-artifact-id] (let [i (.indexOf artifact-id "/")] 136 | (if (>= i 0) 137 | [(.substring artifact-id 0 i) 138 | (.substring artifact-id (inc i))] 139 | [artifact-id artifact-id])) 140 | pom-filename (if (:pom options) 141 | (:pom options) 142 | (default-pom-file pom-group-id pom-artifact-id version))] 143 | (c-install* (:repo options) pom-filename filename artifact-id version))))) 144 | 145 | 146 | (defn read-artifact-description 147 | [pom-file] 148 | (if (.isFile pom-file) 149 | (let [raw-content (slurp pom-file) 150 | xml-content (xml/parse pom-file) 151 | map-content (in/xml-map xml-content)] 152 | (first (:description (:project map-content)))) 153 | "(No description available)")) 154 | 155 | 156 | (defn read-artifact-details 157 | "Given a POM file, read project details" 158 | [pom-file] 159 | (if (.isFile pom-file) 160 | (let [raw-content (slurp pom-file) 161 | xml-content (xml/parse pom-file) 162 | map-content (in/xml-map xml-content)] 163 | (with-out-str 164 | (ppr/pprint map-content))) 165 | "(No details available)")) 166 | 167 | 168 | (defn read-artifact-entries 169 | "Read artifact entries from specified `dir` and return as a list. If 170 | dir contains only sub-dirs then it recurses to find actual entries." 171 | [dir] 172 | (assert (in/dir? dir)) 173 | (let [entries (filter #(not (.startsWith (.getName %) ".")) 174 | (.listFiles dir)) 175 | {subdirs :dir 176 | nondirs :file} (group-by in/dir-or-file entries)] 177 | (if (not (empty? subdirs)) 178 | (reduce into [] (map read-artifact-entries subdirs)) 179 | (let [ignore-ext #{"lastUpdated" "pom" "properties" 180 | "repositories" "sha1" "xml"} 181 | arti-file? #(in/not-contains? ignore-ext 182 | (in/pick-filename-ext %))] 183 | (for [each (filter #(arti-file? (.getName %)) nondirs)] 184 | ;; parent = version, parent->parent = artifactId 185 | ;; parent->parent->parent[relative-path] = groupId 186 | (let [parent (.getParentFile each) 187 | version (.getName parent) 188 | artifact-id (.getName (.getParentFile parent)) 189 | group-path (in/java-filepath 190 | (in/relative-path local-repo-path 191 | (-> parent 192 | .getParentFile 193 | .getParentFile 194 | .getAbsolutePath))) 195 | group-clean (let [slash? #(= \/ %) 196 | rtrim #(if (slash? (last %)) (drop-last %) %) 197 | ltrim #(if (slash? (first %)) (rest %) %)] 198 | (apply str (-> group-path rtrim ltrim))) 199 | group-id (str/replace group-clean "/" ".")] 200 | [group-id 201 | artifact-id 202 | version 203 | (jio/file each) 204 | (jio/file (let [[dir fne] (in/split-filepath 205 | (.getAbsolutePath each)) 206 | [fnm ext] (in/split-filename fne)] 207 | (str dir "/" fnm ".pom")))])))))) 208 | 209 | 210 | (def doc-list 211 | "List artifacts in local Maven repo 212 | Arguments: 213 | [-r | --repo repo-path] [-d | -f | -s] 214 | Options: 215 | -r | --repo repo-path => specifies the path of the local Maven repository 216 | No arguments lists with concise information 217 | -d lists in detail 218 | -f lists with filenames of artifacts 219 | -s lists with project description") 220 | 221 | 222 | (defn c-list* 223 | "List artifacts in local Maven repo" 224 | [repo-path listing-type] 225 | (let [artifact-entries (sort (read-artifact-entries 226 | (jio/file (assert-dir repo-path)))) 227 | artifact-str (fn artstr 228 | ([gi ai] (if (= gi ai) ai (str gi "/" ai))) 229 | ([[gi ai & more]] (artstr gi ai))) 230 | each-artifact (fn [f] ; args to f: 1. art-name, 2. artifacts 231 | (let [by-art-id (group-by artifact-str 232 | artifact-entries)] 233 | (doseq [art-str (keys by-art-id)] 234 | (f art-str (get by-art-id art-str))))) 235 | df (DateFormat/getDateTimeInstance) 236 | date-format #(.format df %) 237 | ljustify (fn [s n] 238 | (let [s (str/trim (str s))] 239 | (if (> (count s) n) s 240 | (apply str 241 | (take n (concat 242 | s (repeat n \space))))))) 243 | rjustify (fn [s n] 244 | (let [s (str/trim (str s))] 245 | (if (> (count s) n) s 246 | (apply str 247 | (take-last 248 | n (concat (repeat n \space) 249 | s))))))] 250 | (cond 251 | (nil? listing-type) 252 | (each-artifact 253 | (fn [art-name artifacts] 254 | (println 255 | (format "%s (%s)" art-name 256 | (str/join ", " 257 | (distinct (for [[g a v f p] artifacts] 258 | v))))))) 259 | (= :description listing-type) 260 | (each-artifact 261 | (fn [art-name artifacts] 262 | (println 263 | (format "%s (%s) -- %s" (ljustify art-name 20) 264 | (str/join ", " 265 | (distinct (for [[g a v f p] artifacts] 266 | v))) 267 | (or (some #(read-artifact-description 268 | (last %)) artifacts) 269 | ""))))) 270 | (= :filename listing-type) 271 | (each-artifact 272 | (fn [art-name artifacts] 273 | (doseq [each artifacts] 274 | (let [[g a v ^File f] each 275 | an (ljustify (format "[%s \"%s\"]" 276 | art-name v) 30) 277 | nm (ljustify (.getName f) 30) 278 | sp (ljustify (format "%s %s" an nm) 62) 279 | ln (rjustify (.length f) 280 | (min (- 70 (count sp)) 10))] 281 | (println 282 | (format "%s %s %s" sp ln 283 | (date-format 284 | (Date. (.lastModified f))))))))) 285 | (= :detail listing-type) 286 | (each-artifact 287 | (fn [art-name artifacts] 288 | (println 289 | (format "%s (%s)\n%s" (ljustify art-name 20) 290 | (str/join ", " 291 | (distinct (for [[g a v f p] artifacts] 292 | v))) 293 | (or (some #(read-artifact-details 294 | (last %)) artifacts) 295 | ""))))) 296 | :otherwise 297 | (throw (RuntimeException. 298 | (str "Expected valid listing type, found " listing-type)))))) 299 | 300 | 301 | (defn c-list 302 | [& args] 303 | (let [[options args banner] (cli/cli args 304 | ["-r" "--repo" "Local repo path" :default local-repo-path] 305 | ["-d" "--detail" "List in detail" :flag true] 306 | ["-f" "--filename" "List with filenames" :flag true] 307 | ["-s" "--description" "List with description" :flag true]) 308 | list-types #{:detail :filename :description} 309 | help-abort (fn [] 310 | (println doc-list) 311 | (println "Only either of -d, -f, -s may be selected") 312 | (main/abort))] 313 | (cond 314 | (seq args) (do (println "Invalid arguments" args) (help-abort)) 315 | (->> (keys options) 316 | (filter options) 317 | set 318 | (set/intersection list-types) 319 | rest 320 | seq) (help-abort) 321 | :otherwise (c-list* (:repo options) 322 | (some #(and (options %) %) list-types))))) 323 | 324 | 325 | (def doc-remove 326 | "Remove artifacts from local Maven repo") 327 | (defn c-remove 328 | "Remove artifacts from local Maven repo" 329 | [& args] 330 | (println "Not yet implemented")) 331 | 332 | 333 | (def doc-help 334 | "Display help for plugin, or for specified command 335 | Arguments: 336 | [] the command to show help for 337 | No argument lists generic help page") 338 | 339 | 340 | (defn c-help 341 | "Display help for plugin, or for specified command" 342 | ([] 343 | (println " 344 | Leiningen plugin to work with local Maven repository. 345 | 346 | coords Guess Leiningen (Maven) coords of a file 347 | install Install artifact to local repository 348 | list List artifacts in local repository 349 | remove Remove artifact from local repository (Not Yet Implemented) 350 | help This help screen 351 | 352 | For help on individual commands use 'help' with command name, e.g.: 353 | 354 | $ lein localrepo help install 355 | ")) 356 | ([command] 357 | (case command 358 | "coords" (println doc-coords) 359 | "install" (println doc-install) 360 | "list" (println doc-list) 361 | "remove" (println doc-remove) 362 | "help" (println doc-help) 363 | (in/illegal-arg "Illegal command:" command 364 | ", Allowed: coords, install, list, remove, help")))) 365 | 366 | 367 | (defn apply-cmd 368 | [pred cmd f args] 369 | (if (pred) (apply f args) 370 | (c-help cmd))) 371 | 372 | 373 | (defn ^:no-project-needed localrepo 374 | "Work with local Maven repository 375 | 376 | Usage: lein localrepo (commands are listed below) 377 | 378 | coords Guess Leiningen (Maven) coords of a file 379 | install Install artifact to local repository 380 | list List artifacts in local repository 381 | remove Remove artifact from local repository (Not Yet Implemented) 382 | help This help screen 383 | 384 | For help on individual commands use 'help' with command name, e.g.: 385 | 386 | $ lein localrepo help install" 387 | ([_] 388 | (c-help)) 389 | ([_ command & args] 390 | (let [argc (count args)] 391 | (case command 392 | "coords" (apply-cmd #(= argc 1) command c-coords args) 393 | "install" (apply-cmd #(#{3 5 7} argc) command c-install args) 394 | "list" (apply-cmd #(#{0 1 2 3} argc) command c-list args) 395 | "remove" (apply-cmd #(>= argc 0) command c-remove args) 396 | "help" (apply-cmd #(or (= argc 0) 397 | (= argc 1)) command c-help args) 398 | (c-help))))) 399 | -------------------------------------------------------------------------------- /src/leiningen/localrepo/internal.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.localrepo.internal 2 | "Utility functions - mostly taken/adapted from Clj-MiscUtil: 3 | https://bitbucket.org/kumarshantanu/clj-miscutil/src" 4 | (:require [clojure.string :as str] 5 | [clojure.pprint :as ppr]) 6 | (:import (java.io File))) 7 | 8 | 9 | (defn illegal-arg 10 | "Throw IllegalArgumentException using the args" 11 | [arg & more] 12 | (let [msg (apply str (interpose " " (into [arg] more)))] 13 | (throw (IllegalArgumentException. msg)))) 14 | 15 | 16 | (defn ^String java-filepath 17 | "Accept path (of a file) as argument and return a uniform file path for all 18 | operating systems. 19 | Example: \"C:\\path\\to\\file.txt\" becomes \"C:/path/to/file.txt\" 20 | See also: split-filepath" 21 | [s] 22 | (let [p (if (instance? File s) (.getAbsolutePath ^File s) 23 | (str s))] 24 | (.replace ^String p "\\" "/"))) 25 | 26 | 27 | (defn ^String split-filepath 28 | "Given a complete path, split into filedir and filename and return as vector. 29 | The filedir is normalized as uniform Java filepath. 30 | See also: java-filepath" 31 | [s] 32 | (let [jf (java-filepath s) 33 | sf (str/split jf #"/")] 34 | [(str/join "/" (drop-last sf)) (last sf)])) 35 | 36 | 37 | (defn ^String pick-dirname 38 | "Given a filepath, return the directory portion from it." 39 | [s] 40 | (first (split-filepath s))) 41 | 42 | 43 | (defn ^String pick-filename 44 | "Given a filepath, return the filename portion from it." 45 | [s] 46 | (last (split-filepath s))) 47 | 48 | 49 | (defn split-filename 50 | "Given a partial or complete file path, split into filename and extension and 51 | return as vector." 52 | [s] 53 | (let [f (pick-filename s) 54 | sfe (str/split f #"\.") 55 | sfc (count sfe) 56 | sf1 (first sfe) 57 | sf2 (second sfe)] 58 | (cond 59 | (= 1 sfc) (conj sfe "") 60 | (and (= 2 sfc) (empty? sf1)) [(str "." sf2) ""] 61 | :else [(str/join "." (drop-last sfe)) (last sfe)]))) 62 | 63 | 64 | (defn ^String pick-filename-name 65 | "Given a filepath, return the filename (without extension) portion from it." 66 | [s] 67 | (first (split-filename s))) 68 | 69 | 70 | (defn ^String pick-filename-ext 71 | "Given a filepath, return the file extension portion from it." 72 | [s] 73 | (last (split-filename s))) 74 | 75 | 76 | (defn relative-path 77 | "Given base path and an absolute path, finds the relative path." 78 | [^String base ^String path] 79 | ;; new File(base).toURI() 80 | ;; .relativize(new 81 | ;; File(path).toURI()).getPath(); 82 | (let [base-uri (-> base 83 | File. 84 | .toURI) 85 | path-uri (-> path 86 | File. 87 | .toURI)] 88 | (-> base-uri 89 | (.relativize path-uri) 90 | .getPath))) 91 | 92 | 93 | (defn dir? 94 | "Return true if `d` is a directory, false otherwise." 95 | [d] 96 | (and (instance? File d) 97 | (.isDirectory d))) 98 | 99 | 100 | (defn file? 101 | "Return true if `f` is a file, false otherwise." 102 | [f] 103 | (and (instance? File f) 104 | (.isFile f))) 105 | 106 | 107 | (defn dir-or-file 108 | "Return :dir if `f` (java.io.File) is a directory, :file otherwise." 109 | [f] 110 | (assert (instance? File f)) 111 | (if (dir? f) :dir :file)) 112 | 113 | 114 | (defn not-contains? 115 | "Same as (not (contains? haystack needle))" 116 | [haystack needle] 117 | (not (contains? haystack needle))) 118 | 119 | 120 | (defn common-keys 121 | "Find the common keys in maps `m1` and `m2`." 122 | [m1 m2] 123 | (let [m1-ks (into #{} (keys m1)) 124 | m2-ks (into #{} (keys m2))] 125 | (filter #(contains? m2-ks %) m1-ks))) 126 | 127 | 128 | (defn add-into 129 | "" 130 | [old-map new-map] 131 | (let [com-ks (into [] (common-keys old-map new-map)) 132 | com-kv (zipmap com-ks 133 | (map #(do [(get old-map %) 134 | (get new-map %)]) 135 | com-ks))] 136 | (merge (merge old-map new-map) com-kv))) 137 | 138 | 139 | (defn as-vector 140 | "Convert/wrap given argument as a vector." 141 | [anything] 142 | (if (vector? anything) anything 143 | (if (or (seq? anything) (set? anything)) (into [] anything) 144 | (if (map? anything) [anything] 145 | (if (nil? anything) [] 146 | [anything]))))) 147 | 148 | 149 | (defn merge-incl 150 | "Merge two maps inclusively. Values get wrapped inside a vector, e.g 151 | user=> (merge-incl {:a 10 :b 20} {:b 30 :c 40}) 152 | {:a [10] :b [20 30] :c [40]}" 153 | [old-map new-map] 154 | (let [common-ks (into [] (common-keys old-map new-map)) 155 | old-keys (keys old-map) 156 | new-keys (keys new-map) 157 | all-keys (distinct (into old-keys new-keys))] 158 | (reduce (fn [m each-k] 159 | (let [old-v (get old-map each-k) 160 | new-v (get new-map each-k) 161 | added (into (as-vector old-v) 162 | (as-vector new-v))] 163 | (into m {each-k added}))) 164 | {} all-keys))) 165 | 166 | 167 | (defn xml-map 168 | "Discard attributes in XML structure `e` and turn elements as keys 169 | with respective values in a nested map. Values wind up in vectors. 170 | Example: 171 | (clojure.xml/parse (slurp \"filename.xml\"))" 172 | [e] 173 | (let [is-map? #(or (map? %) (instance? clojure.lang.MapEntry %)) 174 | tag? #(and (is-map? %) 175 | (contains? % :tag)) 176 | not-tag? (comp not tag?)] 177 | (cond 178 | (tag? e) (let [] 179 | {(:tag e) (xml-map (:content e))}) 180 | (map? e) (let [] 181 | [e]) 182 | (coll? e) (cond 183 | (some not-tag? e) (let [v (as-vector 184 | (map xml-map e)) 185 | [w] v] 186 | (if (and (= (count v) 1) 187 | (vector? w)) 188 | w v)) 189 | :else (let [r (reduce merge-incl {} 190 | (map xml-map e))] 191 | r)) 192 | :else (let [] 193 | e)))) 194 | -------------------------------------------------------------------------------- /test/leiningen/test_localrepo.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.test-localrepo 2 | (:require [leiningen.localrepo.internal :as in] 3 | [clojure.xml :as xml] 4 | [clojure.pprint :as ppr]) 5 | (:use clojure.test)) 6 | 7 | 8 | (def xml-test-data 9 | [[" 10 | project" {:project ["project"]}] 11 | [" 12 | 13 | value 1 14 | value 2 15 | " {:project {:elem ["value 1" "value 2"]}}] 16 | [" 17 | 18 | 19 | Child 20 | 21 | " {:project {:elem1 [{:child ["Child"]}]}}] 22 | [" 23 | 24 | 25 | Child 1 - instance 1 26 | Child 1 - instance 2 27 | Child 2 28 | 29 | " {:project {:elem1 [{:child1 ["Child 1 - instance 1" 30 | "Child 1 - instance 2"] 31 | :child2 ["Child 2"]}]}}]]) 32 | 33 | 34 | (deftest test-xml-parsing 35 | (println "=================================================") 36 | (doseq [each-data xml-test-data] 37 | (let [[xml-str xml-data] each-data 38 | parsed-xml (xml/parse (java.io.StringBufferInputStream. 39 | xml-str)) 40 | parsed-data (in/xml-map parsed-xml)] 41 | (is (not (string? parsed-data))) 42 | (println "<<<<<<<<<<<<") 43 | (println xml-str) 44 | (ppr/pprint parsed-xml) 45 | (ppr/pprint parsed-data) 46 | (println ">>>>>>>>>>>>") 47 | (is (= xml-data parsed-data))))) 48 | --------------------------------------------------------------------------------