| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,341 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (ns ^{:doc "Socket server support" | ||
| :author "Alex Miller"} | ||
| clojure.core.server | ||
| (:require [clojure.string :as str] | ||
| [clojure.edn :as edn] | ||
| [clojure.main :as m]) | ||
| (:import | ||
| [clojure.lang LineNumberingPushbackReader] | ||
| [java.net InetAddress Socket ServerSocket SocketException] | ||
| [java.io Reader Writer PrintWriter BufferedWriter BufferedReader InputStreamReader OutputStreamWriter] | ||
| [java.util Properties] | ||
| [java.util.concurrent.locks ReentrantLock])) | ||
|
|
||
| (set! *warn-on-reflection* true) | ||
|
|
||
| (def ^:dynamic *session* nil) | ||
|
|
||
| ;; lock protects servers | ||
| (defonce ^:private lock (ReentrantLock.)) | ||
| (defonce ^:private servers {}) | ||
|
|
||
| (defmacro ^:private with-lock | ||
| [lock-expr & body] | ||
| `(let [lockee# ~(with-meta lock-expr {:tag 'java.util.concurrent.locks.ReentrantLock})] | ||
| (.lock lockee#) | ||
| (try | ||
| ~@body | ||
| (finally | ||
| (.unlock lockee#))))) | ||
|
|
||
| (defmacro ^:private thread | ||
| [^String name daemon & body] | ||
| `(doto (Thread. (fn [] ~@body) ~name) | ||
| (.setDaemon ~daemon) | ||
| (.start))) | ||
|
|
||
| (defn- required | ||
| "Throw if opts does not contain prop." | ||
| [opts prop] | ||
| (when (nil? (get opts prop)) | ||
| (throw (ex-info (str "Missing required socket server property " prop) opts)))) | ||
|
|
||
| (defn- validate-opts | ||
| "Validate server config options" | ||
| [{:keys [name port accept] :as opts}] | ||
| (doseq [prop [:name :port :accept]] (required opts prop)) | ||
| (when (or (not (integer? port)) (not (<= 0 port 65535))) | ||
| (throw (ex-info (str "Invalid socket server port: " port) opts)))) | ||
|
|
||
| (defn- accept-connection | ||
| "Start accept function, to be invoked on a client thread, given: | ||
| conn - client socket | ||
| name - server name | ||
| client-id - client identifier | ||
| in - in stream | ||
| out - out stream | ||
| err - err stream | ||
| accept - accept fn symbol to invoke | ||
| args - to pass to accept-fn" | ||
| [^Socket conn name client-id in out err accept args] | ||
| (try | ||
| (binding [*in* in | ||
| *out* out | ||
| *err* err | ||
| *session* {:server name :client client-id}] | ||
| (with-lock lock | ||
| (alter-var-root #'servers assoc-in [name :sessions client-id] {})) | ||
| (require (symbol (namespace accept))) | ||
| (let [accept-fn (resolve accept)] | ||
| (apply accept-fn args))) | ||
| (catch SocketException _disconnect) | ||
| (finally | ||
| (with-lock lock | ||
| (alter-var-root #'servers update-in [name :sessions] dissoc client-id)) | ||
| (.close conn)))) | ||
|
|
||
| (defn start-server | ||
| "Start a socket server given the specified opts: | ||
| :address Host or address, string, defaults to loopback address | ||
| :port Port, integer, required | ||
| :name Name, required | ||
| :accept Namespaced symbol of the accept function to invoke, required | ||
| :args Vector of args to pass to accept function | ||
| :bind-err Bind *err* to socket out stream?, defaults to true | ||
| :server-daemon Is server thread a daemon?, defaults to true | ||
| :client-daemon Are client threads daemons?, defaults to true | ||
| Returns server socket." | ||
| [opts] | ||
| (validate-opts opts) | ||
| (let [{:keys [address port name accept args bind-err server-daemon client-daemon] | ||
| :or {bind-err true | ||
| server-daemon true | ||
| client-daemon true}} opts | ||
| address (InetAddress/getByName address) ;; nil returns loopback | ||
| socket (ServerSocket. port 0 address)] | ||
| (with-lock lock | ||
| (alter-var-root #'servers assoc name {:name name, :socket socket, :sessions {}})) | ||
| (thread | ||
| (str "Clojure Server " name) server-daemon | ||
| (try | ||
| (loop [client-counter 1] | ||
| (when (not (.isClosed socket)) | ||
| (try | ||
| (let [conn (.accept socket) | ||
| in (LineNumberingPushbackReader. (InputStreamReader. (.getInputStream conn))) | ||
| out (BufferedWriter. (OutputStreamWriter. (.getOutputStream conn))) | ||
| client-id (str client-counter)] | ||
| (thread | ||
| (str "Clojure Connection " name " " client-id) client-daemon | ||
| (accept-connection conn name client-id in out (if bind-err out *err*) accept args))) | ||
| (catch SocketException _disconnect)) | ||
| (recur (inc client-counter)))) | ||
| (finally | ||
| (with-lock lock | ||
| (alter-var-root #'servers dissoc name))))) | ||
| socket)) | ||
|
|
||
| (defn stop-server | ||
| "Stop server with name or use the server-name from *session* if none supplied. | ||
| Returns true if server stopped successfully, nil if not found, or throws if | ||
| there is an error closing the socket." | ||
| ([] | ||
| (stop-server (:server *session*))) | ||
| ([name] | ||
| (with-lock lock | ||
| (let [server-socket ^ServerSocket (get-in servers [name :socket])] | ||
| (when server-socket | ||
| (alter-var-root #'servers dissoc name) | ||
| (.close server-socket) | ||
| true))))) | ||
|
|
||
| (defn stop-servers | ||
| "Stop all servers ignores all errors, and returns nil." | ||
| [] | ||
| (with-lock lock | ||
| (doseq [name (keys servers)] | ||
| (future (stop-server name))))) | ||
|
|
||
| (defn- parse-props | ||
| "Parse clojure.server.* from properties to produce a map of server configs." | ||
| [^Properties props] | ||
| (reduce | ||
| (fn [acc ^String k] | ||
| (let [[k1 k2 k3] (str/split k #"\.")] | ||
| (if (and (= k1 "clojure") (= k2 "server")) | ||
| (let [v (get props k)] | ||
| (conj acc (merge {:name k3} (edn/read-string v)))) | ||
| acc))) | ||
| [] | ||
| (.stringPropertyNames props))) | ||
|
|
||
| (defn start-servers | ||
| "Start all servers specified in the system properties." | ||
| [system-props] | ||
| (doseq [server (parse-props system-props)] | ||
| (start-server server))) | ||
|
|
||
| (defn repl-init | ||
| "Initialize repl in user namespace and make standard repl requires." | ||
| [] | ||
| (in-ns 'user) | ||
| (apply require clojure.main/repl-requires)) | ||
|
|
||
| (defn repl-read | ||
| "Enhanced :read hook for repl supporting :repl/quit." | ||
| [request-prompt request-exit] | ||
| (or ({:line-start request-prompt :stream-end request-exit} | ||
| (m/skip-whitespace *in*)) | ||
| (let [input (read {:read-cond :allow} *in*)] | ||
| (m/skip-if-eol *in*) | ||
| (case input | ||
| :repl/quit request-exit | ||
| input)))) | ||
|
|
||
| (defn repl | ||
| "REPL with predefined hooks for attachable socket server." | ||
| [] | ||
| (m/repl | ||
| :init repl-init | ||
| :read repl-read)) | ||
|
|
||
| (defn- ex->data | ||
| [ex phase] | ||
| (assoc (Throwable->map ex) :phase phase)) | ||
|
|
||
| (defn prepl | ||
| "a REPL with structured output (for programs) | ||
| reads forms to eval from in-reader (a LineNumberingPushbackReader) | ||
| Closing the input or passing the form :repl/quit will cause it to return | ||
| Calls out-fn with data, one of: | ||
| {:tag :ret | ||
| :val val ;;eval result, or Throwable->map data if exception thrown | ||
| :ns ns-name-string | ||
| :ms long ;;eval time in milliseconds | ||
| :form string ;;iff successfully read | ||
| :exception true ;;iff exception thrown | ||
| } | ||
| {:tag :out | ||
| :val string} ;chars from during-eval *out* | ||
| {:tag :err | ||
| :val string} ;chars from during-eval *err* | ||
| {:tag :tap | ||
| :val val} ;values from tap> | ||
| You might get more than one :out or :err per eval, but exactly one :ret | ||
| tap output can happen at any time (i.e. between evals) | ||
| If during eval an attempt is made to read *in* it will read from in-reader unless :stdin is supplied | ||
| Alpha, subject to change." | ||
| {:added "1.10"} | ||
| [in-reader out-fn & {:keys [stdin]}] | ||
| (let [EOF (Object.) | ||
| tapfn #(out-fn {:tag :tap :val %1})] | ||
| (m/with-bindings | ||
| (in-ns 'user) | ||
| (binding [*in* (or stdin in-reader) | ||
| *out* (PrintWriter-on #(out-fn {:tag :out :val %1}) nil true) | ||
| *err* (PrintWriter-on #(out-fn {:tag :err :val %1}) nil true)] | ||
| (try | ||
| (add-tap tapfn) | ||
| (loop [] | ||
| (when (try | ||
| (let [[form s] (read+string {:eof EOF :read-cond :allow} in-reader)] | ||
| (try | ||
| (when-not (identical? form EOF) | ||
| (let [start (System/nanoTime) | ||
| ret (eval form) | ||
| ms (quot (- (System/nanoTime) start) 1000000)] | ||
| (when-not (= :repl/quit ret) | ||
| (set! *3 *2) | ||
| (set! *2 *1) | ||
| (set! *1 ret) | ||
| (out-fn {:tag :ret | ||
| :val (if (instance? Throwable ret) | ||
| (Throwable->map ret) | ||
| ret) | ||
| :ns (str (.name *ns*)) | ||
| :ms ms | ||
| :form s}) | ||
| true))) | ||
| (catch Throwable ex | ||
| (set! *e ex) | ||
| (out-fn {:tag :ret :val (ex->data ex (or (-> ex ex-data :clojure.error/phase) :execution)) | ||
| :ns (str (.name *ns*)) :form s | ||
| :exception true}) | ||
| true))) | ||
| (catch Throwable ex | ||
| (set! *e ex) | ||
| (out-fn {:tag :ret :val (ex->data ex :read-source) | ||
| :ns (str (.name *ns*)) | ||
| :exception true}) | ||
| true)) | ||
| (recur))) | ||
| (finally | ||
| (remove-tap tapfn))))))) | ||
|
|
||
| (defn- resolve-fn [valf] | ||
| (if (symbol? valf) | ||
| (or (resolve valf) | ||
| (when-let [nsname (namespace valf)] | ||
| (require (symbol nsname)) | ||
| (resolve valf)) | ||
| (throw (Exception. (str "can't resolve: " valf)))) | ||
| valf)) | ||
|
|
||
| (defn io-prepl | ||
| "prepl bound to *in* and *out*, suitable for use with e.g. server/repl (socket-repl). | ||
| :ret and :tap vals will be processed by valf, a fn of one argument | ||
| or a symbol naming same (default pr-str) | ||
| Alpha, subject to change." | ||
| {:added "1.10"} | ||
| [& {:keys [valf] :or {valf pr-str}}] | ||
| (let [valf (resolve-fn valf) | ||
| out *out* | ||
| lock (Object.)] | ||
| (prepl *in* | ||
| (fn [m] | ||
| (binding [*out* out, *flush-on-newline* true, *print-readably* true] | ||
| (locking lock | ||
| (prn (if (#{:ret :tap} (:tag m)) | ||
| (try | ||
| (assoc m :val (valf (:val m))) | ||
| (catch Throwable ex | ||
| (assoc m :val (valf (ex->data ex :print-eval-result)) | ||
| :exception true))) | ||
| m)))))))) | ||
|
|
||
| (defn remote-prepl | ||
| "Implements a prepl on in-reader and out-fn by forwarding to a | ||
| remote [io-]prepl over a socket. Messages will be read by readf, a | ||
| fn of a LineNumberingPushbackReader and EOF value or a symbol naming | ||
| same (default #(read %1 false %2)), | ||
| :ret and :tap vals will be processed by valf, a fn of one argument | ||
| or a symbol naming same (default read-string). If that function | ||
| throws, :val will be unprocessed. | ||
| Alpha, subject to change." | ||
| {:added "1.10"} | ||
| [^String host port ^Reader | ||
| in-reader out-fn & {:keys [valf readf] :or {valf read-string, readf #(read %1 false %2)}}] | ||
| (let [valf (resolve-fn valf) | ||
| readf (resolve-fn readf) | ||
| ^long port (if (string? port) (Integer/valueOf ^String port) port) | ||
| socket (Socket. host port) | ||
| rd (-> socket .getInputStream InputStreamReader. BufferedReader. LineNumberingPushbackReader.) | ||
| wr (-> socket .getOutputStream OutputStreamWriter.) | ||
| EOF (Object.)] | ||
| (thread "clojure.core.server/remote-prepl" true | ||
| (try (loop [] | ||
| (let [{:keys [tag val] :as m} (readf rd EOF)] | ||
| (when-not (identical? m EOF) | ||
| (out-fn | ||
| (if (#{:ret :tap} tag) | ||
| (try | ||
| (assoc m :val (valf val)) | ||
| (catch Throwable ex | ||
| (assoc m :val (ex->data ex :read-eval-result) | ||
| :exception true))) | ||
| m)) | ||
| (recur)))) | ||
| (finally | ||
| (.close wr)))) | ||
| (let [buf (char-array 1024)] | ||
| (try (loop [] | ||
| (let [n (.read in-reader buf)] | ||
| (when-not (= n -1) | ||
| (.write wr buf 0 n) | ||
| (.flush wr) | ||
| (recur)))) | ||
| (finally | ||
| (.close rd)))))) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (ns | ||
| ^{:author "Stuart Halloway", | ||
| :doc "Non-core data functions."} | ||
| clojure.data | ||
| (:require [clojure.set :as set])) | ||
|
|
||
| (set! *warn-on-reflection* true) | ||
|
|
||
| (declare diff) | ||
|
|
||
| (defn- atom-diff | ||
| "Internal helper for diff." | ||
| [a b] | ||
| (if (= a b) [nil nil a] [a b nil])) | ||
|
|
||
| ;; for big things a sparse vector class would be better | ||
| (defn- vectorize | ||
| "Convert an associative-by-numeric-index collection into | ||
| an equivalent vector, with nil for any missing keys" | ||
| [m] | ||
| (when (seq m) | ||
| (reduce | ||
| (fn [result [k v]] (assoc result k v)) | ||
| (vec (repeat (apply max (keys m)) nil)) | ||
| m))) | ||
|
|
||
| (defn- diff-associative-key | ||
| "Diff associative things a and b, comparing only the key k." | ||
| [a b k] | ||
| (let [va (get a k) | ||
| vb (get b k) | ||
| [a* b* ab] (diff va vb) | ||
| in-a (contains? a k) | ||
| in-b (contains? b k) | ||
| same (and in-a in-b | ||
| (or (not (nil? ab)) | ||
| (and (nil? va) (nil? vb))))] | ||
| [(when (and in-a (or (not (nil? a*)) (not same))) {k a*}) | ||
| (when (and in-b (or (not (nil? b*)) (not same))) {k b*}) | ||
| (when same {k ab}) | ||
| ])) | ||
|
|
||
| (defn- diff-associative | ||
| "Diff associative things a and b, comparing only keys in ks." | ||
| [a b ks] | ||
| (reduce | ||
| (fn [diff1 diff2] | ||
| (doall (map merge diff1 diff2))) | ||
| [nil nil nil] | ||
| (map | ||
| (partial diff-associative-key a b) | ||
| ks))) | ||
|
|
||
| (defn- diff-sequential | ||
| [a b] | ||
| (vec (map vectorize (diff-associative | ||
| (if (vector? a) a (vec a)) | ||
| (if (vector? b) b (vec b)) | ||
| (range (max (count a) (count b))))))) | ||
|
|
||
| (defprotocol ^{:added "1.3"} EqualityPartition | ||
| "Implementation detail. Subject to change." | ||
| (^{:added "1.3"} equality-partition [x] "Implementation detail. Subject to change.")) | ||
|
|
||
| (defprotocol ^{:added "1.3"} Diff | ||
| "Implementation detail. Subject to change." | ||
| (^{:added "1.3"} diff-similar [a b] "Implementation detail. Subject to change.")) | ||
|
|
||
| (extend nil | ||
| Diff | ||
| {:diff-similar atom-diff}) | ||
|
|
||
| (extend Object | ||
| Diff | ||
| {:diff-similar (fn [^Object a b] | ||
| ((if (.. a getClass isArray) diff-sequential atom-diff) a b))} | ||
| EqualityPartition | ||
| {:equality-partition (fn [^Object x] | ||
| (if (.. x getClass isArray) :sequential :atom))}) | ||
|
|
||
| (extend-protocol EqualityPartition | ||
| nil | ||
| (equality-partition [x] :atom) | ||
|
|
||
| java.util.Set | ||
| (equality-partition [x] :set) | ||
|
|
||
| java.util.List | ||
| (equality-partition [x] :sequential) | ||
|
|
||
| java.util.Map | ||
| (equality-partition [x] :map)) | ||
|
|
||
| (defn- as-set-value | ||
| [s] | ||
| (if (set? s) s (into #{} s))) | ||
|
|
||
| (extend-protocol Diff | ||
| java.util.Set | ||
| (diff-similar | ||
| [a b] | ||
| (let [aval (as-set-value a) | ||
| bval (as-set-value b)] | ||
| [(not-empty (set/difference aval bval)) | ||
| (not-empty (set/difference bval aval)) | ||
| (not-empty (set/intersection aval bval))])) | ||
|
|
||
| java.util.List | ||
| (diff-similar [a b] | ||
| (diff-sequential a b)) | ||
|
|
||
| java.util.Map | ||
| (diff-similar [a b] | ||
| (diff-associative a b (set/union (keys a) (keys b))))) | ||
|
|
||
| (defn diff | ||
| "Recursively compares a and b, returning a tuple of | ||
| [things-only-in-a things-only-in-b things-in-both]. | ||
| Comparison rules: | ||
| * For equal a and b, return [nil nil a]. | ||
| * Maps are subdiffed where keys match and values differ. | ||
| * Sets are never subdiffed. | ||
| * All sequential things are treated as associative collections | ||
| by their indexes, with results returned as vectors. | ||
| * Everything else (including strings!) is treated as | ||
| an atom and compared for equality." | ||
| {:added "1.3"} | ||
| [a b] | ||
| (if (= a b) | ||
| [nil nil a] | ||
| (if (= (equality-partition a) (equality-partition b)) | ||
| (diff-similar a b) | ||
| (atom-diff a b)))) | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (ns ^{:doc "Functions to turn objects into data. Alpha, subject to change"} | ||
| clojure.datafy | ||
| (:require [clojure.core.protocols :as p])) | ||
|
|
||
| (set! *warn-on-reflection* true) | ||
|
|
||
| (defn datafy | ||
| "Attempts to return x as data. | ||
| datafy will return the value of clojure.core.protocols/datafy. If | ||
| the value has been transformed and the result supports | ||
| metadata, :clojure.datafy/obj will be set on the metadata to the | ||
| original value of x, and :clojure.datafy/class to the name of the | ||
| class of x, as a symbol." | ||
| [x] | ||
| (let [v (p/datafy x)] | ||
| (if (identical? v x) | ||
| v | ||
| (if (instance? clojure.lang.IObj v) | ||
| (vary-meta v assoc ::obj x ::class (-> x class .getName symbol)) | ||
| v)))) | ||
|
|
||
| (defn nav | ||
| "Returns (possibly transformed) v in the context of coll and k (a | ||
| key/index or nil). Callers should attempt to provide the key/index | ||
| context k for Indexed/Associative/ILookup colls if possible, but not | ||
| to fabricate one e.g. for sequences (pass nil). nav returns the | ||
| value of clojure.core.protocols/nav." | ||
| [coll k v] | ||
| (p/nav coll k v)) | ||
|
|
||
| (defn- sortmap [m] | ||
| (into (sorted-map) m)) | ||
|
|
||
| (extend-protocol p/Datafiable | ||
| Throwable | ||
| (datafy [x] | ||
| (Throwable->map x)) | ||
|
|
||
| clojure.lang.IRef | ||
| (datafy [r] | ||
| (with-meta [(deref r)] (meta r))) | ||
|
|
||
| clojure.lang.Namespace | ||
| (datafy [n] | ||
| (with-meta {:name (.getName n) | ||
| :publics (-> n ns-publics sortmap) | ||
| :imports (-> n ns-imports sortmap) | ||
| :interns (-> n ns-interns sortmap)} | ||
| (meta n))) | ||
|
|
||
| java.lang.Class | ||
| (datafy [c] | ||
| (let [{:keys [members] :as ret} ((requiring-resolve 'clojure.reflect/reflect) c)] | ||
| (assoc ret :name (-> c .getName symbol) :members (->> members (group-by :name) sortmap))))) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (ns ^{:doc "edn reading." | ||
| :author "Rich Hickey"} | ||
| clojure.edn | ||
| (:refer-clojure :exclude [read read-string])) | ||
|
|
||
| (defn read | ||
| "Reads the next object from stream, which must be an instance of | ||
| java.io.PushbackReader or some derivee. stream defaults to the | ||
| current value of *in*. | ||
| Reads data in the edn format (subset of Clojure data): | ||
| http://edn-format.org | ||
| opts is a map that can include the following keys: | ||
| :eof - value to return on end-of-file. When not supplied, eof throws an exception. | ||
| :readers - a map of tag symbols to data-reader functions to be considered before default-data-readers. | ||
| When not supplied, only the default-data-readers will be used. | ||
| :default - A function of two args, that will, if present and no reader is found for a tag, | ||
| be called with the tag and the value." | ||
|
|
||
| {:added "1.5"} | ||
| ([] | ||
| (read *in*)) | ||
| ([stream] | ||
| (read {} stream)) | ||
| ([opts stream] | ||
| (clojure.lang.EdnReader/read stream opts))) | ||
|
|
||
| (defn read-string | ||
| "Reads one object from the string s. Returns nil when s is nil or empty. | ||
| Reads data in the edn format (subset of Clojure data): | ||
| http://edn-format.org | ||
| opts is a map as per clojure.edn/read" | ||
| {:added "1.5"} | ||
| ([s] (read-string {:eof nil} s)) | ||
| ([opts s] (when s (clojure.lang.EdnReader/readString s opts)))) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,294 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (ns clojure.instant | ||
| (:import [java.util Calendar Date GregorianCalendar TimeZone] | ||
| [java.sql Timestamp])) | ||
|
|
||
|
|
||
| (set! *warn-on-reflection* true) | ||
|
|
||
| ;;; ------------------------------------------------------------------------ | ||
| ;;; convenience macros | ||
|
|
||
| (defmacro ^:private fail | ||
| [msg] | ||
| `(throw (RuntimeException. ~msg))) | ||
|
|
||
| (defmacro ^:private verify | ||
| ([test msg] `(when-not ~test (fail ~msg))) | ||
| ([test] `(verify ~test ~(str "failed: " (pr-str test))))) | ||
|
|
||
| (defn- divisible? | ||
| [num div] | ||
| (zero? (mod num div))) | ||
|
|
||
| (defn- indivisible? | ||
| [num div] | ||
| (not (divisible? num div))) | ||
|
|
||
|
|
||
| ;;; ------------------------------------------------------------------------ | ||
| ;;; parser implementation | ||
|
|
||
| (defn- parse-int [^String s] | ||
| (Long/parseLong s)) | ||
|
|
||
| (defn- zero-fill-right [^String s width] | ||
| (cond (= width (count s)) s | ||
| (< width (count s)) (.substring s 0 width) | ||
| :else (loop [b (StringBuilder. s)] | ||
| (if (< (.length b) width) | ||
| (recur (.append b \0)) | ||
| (.toString b))))) | ||
|
|
||
| (def ^:private timestamp | ||
| #"(\d\d\d\d)(?:-(\d\d)(?:-(\d\d)(?:[T](\d\d)(?::(\d\d)(?::(\d\d)(?:[.](\d+))?)?)?)?)?)?(?:[Z]|([-+])(\d\d):(\d\d))?") | ||
|
|
||
| (defn parse-timestamp | ||
| "Parse a string containing an RFC3339-like like timestamp. | ||
| The function new-instant is called with the following arguments. | ||
| min max default | ||
| --- ------------ ------- | ||
| years 0 9999 N/A (s must provide years) | ||
| months 1 12 1 | ||
| days 1 31 1 (actual max days depends | ||
| hours 0 23 0 on month and year) | ||
| minutes 0 59 0 | ||
| seconds 0 60 0 (though 60 is only valid | ||
| nanoseconds 0 999999999 0 when minutes is 59) | ||
| offset-sign -1 1 0 | ||
| offset-hours 0 23 0 | ||
| offset-minutes 0 59 0 | ||
| These are all integers and will be non-nil. (The listed defaults | ||
| will be passed if the corresponding field is not present in s.) | ||
| Grammar (of s): | ||
| date-fullyear = 4DIGIT | ||
| date-month = 2DIGIT ; 01-12 | ||
| date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on | ||
| ; month/year | ||
| time-hour = 2DIGIT ; 00-23 | ||
| time-minute = 2DIGIT ; 00-59 | ||
| time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second | ||
| ; rules | ||
| time-secfrac = '.' 1*DIGIT | ||
| time-numoffset = ('+' / '-') time-hour ':' time-minute | ||
| time-offset = 'Z' / time-numoffset | ||
| time-part = time-hour [ ':' time-minute [ ':' time-second | ||
| [time-secfrac] [time-offset] ] ] | ||
| timestamp = date-year [ '-' date-month [ '-' date-mday | ||
| [ 'T' time-part ] ] ] | ||
| Unlike RFC3339: | ||
| - we only parse the timestamp format | ||
| - timestamp can elide trailing components | ||
| - time-offset is optional (defaults to +00:00) | ||
| Though time-offset is syntactically optional, a missing time-offset | ||
| will be treated as if the time-offset zero (+00:00) had been | ||
| specified. | ||
| " | ||
| [new-instant ^CharSequence cs] | ||
| (if-let [[_ years months days hours minutes seconds fraction | ||
| offset-sign offset-hours offset-minutes] | ||
| (re-matches timestamp cs)] | ||
| (new-instant | ||
| (parse-int years) | ||
| (if-not months 1 (parse-int months)) | ||
| (if-not days 1 (parse-int days)) | ||
| (if-not hours 0 (parse-int hours)) | ||
| (if-not minutes 0 (parse-int minutes)) | ||
| (if-not seconds 0 (parse-int seconds)) | ||
| (if-not fraction 0 (parse-int (zero-fill-right fraction 9))) | ||
| (cond (= "-" offset-sign) -1 | ||
| (= "+" offset-sign) 1 | ||
| :else 0) | ||
| (if-not offset-hours 0 (parse-int offset-hours)) | ||
| (if-not offset-minutes 0 (parse-int offset-minutes))) | ||
| (fail (str "Unrecognized date/time syntax: " cs)))) | ||
|
|
||
|
|
||
| ;;; ------------------------------------------------------------------------ | ||
| ;;; Verification of Extra-Grammatical Restrictions from RFC3339 | ||
|
|
||
| (defn- leap-year? | ||
| [year] | ||
| (and (divisible? year 4) | ||
| (or (indivisible? year 100) | ||
| (divisible? year 400)))) | ||
|
|
||
| (def ^:private days-in-month | ||
| (let [dim-norm [nil 31 28 31 30 31 30 31 31 30 31 30 31] | ||
| dim-leap [nil 31 29 31 30 31 30 31 31 30 31 30 31]] | ||
| (fn [month leap-year?] | ||
| ((if leap-year? dim-leap dim-norm) month)))) | ||
|
|
||
| (defn validated | ||
| "Return a function which constructs an instant by calling constructor | ||
| after first validating that those arguments are in range and otherwise | ||
| plausible. The resulting function will throw an exception if called | ||
| with invalid arguments." | ||
| [new-instance] | ||
| (fn [years months days hours minutes seconds nanoseconds | ||
| offset-sign offset-hours offset-minutes] | ||
| (verify (<= 1 months 12)) | ||
| (verify (<= 1 days (days-in-month months (leap-year? years)))) | ||
| (verify (<= 0 hours 23)) | ||
| (verify (<= 0 minutes 59)) | ||
| (verify (<= 0 seconds (if (= minutes 59) 60 59))) | ||
| (verify (<= 0 nanoseconds 999999999)) | ||
| (verify (<= -1 offset-sign 1)) | ||
| (verify (<= 0 offset-hours 23)) | ||
| (verify (<= 0 offset-minutes 59)) | ||
| (new-instance years months days hours minutes seconds nanoseconds | ||
| offset-sign offset-hours offset-minutes))) | ||
|
|
||
|
|
||
| ;;; ------------------------------------------------------------------------ | ||
| ;;; print integration | ||
|
|
||
| (def ^:private ^ThreadLocal thread-local-utc-date-format | ||
| ;; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access. | ||
| ;; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335 | ||
| (proxy [ThreadLocal] [] | ||
| (initialValue [] | ||
| (doto (java.text.SimpleDateFormat. "yyyy-MM-dd'T'HH:mm:ss.SSS-00:00") | ||
| ;; RFC3339 says to use -00:00 when the timezone is unknown (+00:00 implies a known GMT) | ||
| (.setTimeZone (java.util.TimeZone/getTimeZone "GMT")))))) | ||
|
|
||
| (defn- print-date | ||
| "Print a java.util.Date as RFC3339 timestamp, always in UTC." | ||
| [^java.util.Date d, ^java.io.Writer w] | ||
| (let [^java.text.DateFormat utc-format (.get thread-local-utc-date-format)] | ||
| (.write w "#inst \"") | ||
| (.write w (.format utc-format d)) | ||
| (.write w "\""))) | ||
|
|
||
| (defmethod print-method java.util.Date | ||
| [^java.util.Date d, ^java.io.Writer w] | ||
| (print-date d w)) | ||
|
|
||
| (defmethod print-dup java.util.Date | ||
| [^java.util.Date d, ^java.io.Writer w] | ||
| (print-date d w)) | ||
|
|
||
| (defn- print-calendar | ||
| "Print a java.util.Calendar as RFC3339 timestamp, preserving timezone." | ||
| [^java.util.Calendar c, ^java.io.Writer w] | ||
| (let [calstr (format "%1$tFT%1$tT.%1$tL%1$tz" c) | ||
| offset-minutes (- (.length calstr) 2)] | ||
| ;; calstr is almost right, but is missing the colon in the offset | ||
| (.write w "#inst \"") | ||
| (.write w calstr 0 offset-minutes) | ||
| (.write w ":") | ||
| (.write w calstr offset-minutes 2) | ||
| (.write w "\""))) | ||
|
|
||
| (defmethod print-method java.util.Calendar | ||
| [^java.util.Calendar c, ^java.io.Writer w] | ||
| (print-calendar c w)) | ||
|
|
||
| (defmethod print-dup java.util.Calendar | ||
| [^java.util.Calendar c, ^java.io.Writer w] | ||
| (print-calendar c w)) | ||
|
|
||
|
|
||
| (def ^:private ^ThreadLocal thread-local-utc-timestamp-format | ||
| ;; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access. | ||
| ;; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335 | ||
| (proxy [ThreadLocal] [] | ||
| (initialValue [] | ||
| (doto (java.text.SimpleDateFormat. "yyyy-MM-dd'T'HH:mm:ss") | ||
| (.setTimeZone (java.util.TimeZone/getTimeZone "GMT")))))) | ||
|
|
||
| (defn- print-timestamp | ||
| "Print a java.sql.Timestamp as RFC3339 timestamp, always in UTC." | ||
| [^java.sql.Timestamp ts, ^java.io.Writer w] | ||
| (let [^java.text.DateFormat utc-format (.get thread-local-utc-timestamp-format)] | ||
| (.write w "#inst \"") | ||
| (.write w (.format utc-format ts)) | ||
| ;; add on nanos and offset | ||
| ;; RFC3339 says to use -00:00 when the timezone is unknown (+00:00 implies a known GMT) | ||
| (.write w (format ".%09d-00:00" (.getNanos ts))) | ||
| (.write w "\""))) | ||
|
|
||
| (defmethod print-method java.sql.Timestamp | ||
| [^java.sql.Timestamp ts, ^java.io.Writer w] | ||
| (print-timestamp ts w)) | ||
|
|
||
| (defmethod print-dup java.sql.Timestamp | ||
| [^java.sql.Timestamp ts, ^java.io.Writer w] | ||
| (print-timestamp ts w)) | ||
|
|
||
|
|
||
| ;;; ------------------------------------------------------------------------ | ||
| ;;; reader integration | ||
|
|
||
| (defn- construct-calendar | ||
| "Construct a java.util.Calendar, preserving the timezone | ||
| offset, but truncating the subsecond fraction to milliseconds." | ||
| ^GregorianCalendar | ||
| [years months days hours minutes seconds nanoseconds | ||
| offset-sign offset-hours offset-minutes] | ||
| (doto (GregorianCalendar. years (dec months) days hours minutes seconds) | ||
| (.set Calendar/MILLISECOND (quot nanoseconds 1000000)) | ||
| (.setTimeZone (TimeZone/getTimeZone | ||
| (format "GMT%s%02d:%02d" | ||
| (if (neg? offset-sign) "-" "+") | ||
| offset-hours offset-minutes))))) | ||
|
|
||
| (defn- construct-date | ||
| "Construct a java.util.Date, which expresses the original instant as | ||
| milliseconds since the epoch, UTC." | ||
| [years months days hours minutes seconds nanoseconds | ||
| offset-sign offset-hours offset-minutes] | ||
| (.getTime (construct-calendar years months days | ||
| hours minutes seconds nanoseconds | ||
| offset-sign offset-hours offset-minutes))) | ||
|
|
||
| (defn- construct-timestamp | ||
| "Construct a java.sql.Timestamp, which has nanosecond precision." | ||
| [years months days hours minutes seconds nanoseconds | ||
| offset-sign offset-hours offset-minutes] | ||
| (doto (Timestamp. | ||
| (.getTimeInMillis | ||
| (construct-calendar years months days | ||
| hours minutes seconds 0 | ||
| offset-sign offset-hours offset-minutes))) | ||
| ;; nanos must be set separately, pass 0 above for the base calendar | ||
| (.setNanos nanoseconds))) | ||
|
|
||
| (defn read-instant-date | ||
| "To read an instant as a java.util.Date, bind *data-readers* to a map with | ||
| this var as the value for the 'inst key. The timezone offset will be used | ||
| to convert into UTC." | ||
| [^CharSequence cs] | ||
| (parse-timestamp (validated construct-date) cs)) | ||
|
|
||
| (defn read-instant-calendar | ||
| "To read an instant as a java.util.Calendar, bind *data-readers* to a map with | ||
| this var as the value for the 'inst key. Calendar preserves the timezone | ||
| offset." | ||
| [^CharSequence cs] | ||
| (parse-timestamp (validated construct-calendar) cs)) | ||
|
|
||
| (defn read-instant-timestamp | ||
| "To read an instant as a java.sql.Timestamp, bind *data-readers* to a | ||
| map with this var as the value for the 'inst key. Timestamp preserves | ||
| fractional seconds with nanosecond precision. The timezone offset will | ||
| be used to convert into UTC." | ||
| [^CharSequence cs] | ||
| (parse-timestamp (validated construct-timestamp) cs)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (ns clojure.java.basis | ||
| "The lib basis includes which libraries and versions were loaded both | ||
| for direct dependencies and transitive dependencies, as well as the | ||
| classpath and possibly other information from the resolution process. | ||
| This basis will be known if the runtime was started by the Clojure CLI. | ||
| The Clojure CLI or tools.deps merge a set of deps maps (often from | ||
| deps.edn files). Additional runtime modifications are supplied via argmap | ||
| keys, provided via alias maps in the merged deps. Deps maps typically have | ||
| :paths, :deps, and :aliases keys. | ||
| The basis is a superset of merged deps.edn files with the following | ||
| additional keys: | ||
| :basis-config - params used to configure basis deps sources, can be | ||
| string path, deps map, nil, or :default | ||
| :root - default = loaded as a resource from tools.deps) | ||
| :user - default = ~/.clojure/deps.edn) | ||
| :project - default = ./deps.edn) | ||
| :extra - default = nil | ||
| :aliases - coll of keyword aliases to include during dep calculation | ||
| :argmap - effective argmap (after resolving and merging argmaps from aliases) | ||
| :libs - map of lib to coord for all included libraries | ||
| :classpath - classpath map, keys are paths (to directory or .jar), values | ||
| are maps with source identifier (either :lib-name or :path-key) | ||
| :classpath-roots - vector of paths in classpath order (keys of :classpath)" | ||
| (:require | ||
| [clojure.java.basis.impl :as impl])) | ||
|
|
||
| (defn initial-basis | ||
| "Initial runtime basis at launch, nil if unknown (process not started by CLI)" | ||
| {:added "1.12"} | ||
| [] | ||
| @impl/init-basis) | ||
|
|
||
| (defn current-basis | ||
| "Return the current basis, which may have been modified since runtime launch." | ||
| {:added "1.12"} | ||
| [] | ||
| @@impl/the-basis) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (ns clojure.java.basis.impl | ||
| (:require | ||
| [clojure.edn :as edn] | ||
| [clojure.java.io :as jio]) | ||
| (:import | ||
| [java.io PushbackReader])) | ||
|
|
||
| (set! *warn-on-reflection* true) | ||
|
|
||
| (defn- read-edn | ||
| "Coerce f to a reader via clojure.java.io/reader and read one edn value. | ||
| The reader should contain a single value. Empty input returns nil. | ||
| The reader will be read to EOF and closed." | ||
| [f] | ||
| (let [reader (jio/reader f) | ||
| EOF (Object.)] | ||
| (with-open [rdr (PushbackReader. reader)] | ||
| (let [val (edn/read {:default tagged-literal :eof EOF} rdr)] | ||
| (if (identical? EOF val) | ||
| nil | ||
| (if (not (identical? EOF (edn/read {:eof EOF} rdr))) | ||
| (throw (ex-info "Invalid file, expected edn to contain a single value." {})) | ||
| val)))))) | ||
|
|
||
| (defn- read-basis | ||
| "Read basis edn from basis file or throw" | ||
| [basis-file] | ||
| (when-let [f (jio/file basis-file)] | ||
| (when (.exists f) | ||
| (read-edn f)))) | ||
|
|
||
| ;; delay construction until needed, access via initial-basis | ||
| (def init-basis | ||
| (delay (read-basis (System/getProperty "clojure.basis")))) | ||
|
|
||
| ;; delay construction until needed, access via current-basis | ||
| (def the-basis | ||
| (delay (atom @init-basis))) | ||
|
|
||
| (defn update-basis! | ||
| "Update the runtime basis by applying f with args" | ||
| [f & args] | ||
| (apply swap! @the-basis f args)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (ns | ||
| ^{:author "Christophe Grand", | ||
| :doc "Start a web browser from Clojure"} | ||
| clojure.java.browse | ||
| (:require [clojure.java.shell :as sh] | ||
| [clojure.string :as str]) | ||
| (:import (java.io File) | ||
| (java.net URI) | ||
| (java.lang ProcessBuilder ProcessBuilder$Redirect))) | ||
|
|
||
| (defn- macosx? [] | ||
| (-> "os.name" System/getProperty .toLowerCase | ||
| (.startsWith "mac os x"))) | ||
|
|
||
| (defn- xdg-open-loc [] | ||
| ;; try/catch needed to mask exception on Windows without Cygwin | ||
| (let [which-out (try (:out (sh/sh "which" "xdg-open")) | ||
| (catch Exception e ""))] | ||
| (if (= which-out "") | ||
| nil | ||
| (str/trim-newline which-out)))) | ||
|
|
||
| (defn- open-url-script-val [] | ||
| (if (macosx?) | ||
| "/usr/bin/open" | ||
| (xdg-open-loc))) | ||
|
|
||
| ;; We could assign (open-url-script-val) to *open-url-script* right | ||
| ;; away in the def below, but clojure.java.shell/sh creates a future | ||
| ;; that causes a long wait for the JVM to exit during Clojure compiles | ||
| ;; (unless we can somehow here make it call (shutdown-agents) later). | ||
| ;; Better to initialize it when we first need it, in browse-url. | ||
|
|
||
| (def ^:dynamic *open-url-script* (atom :uninitialized)) | ||
|
|
||
| (defn- open-url-in-browser | ||
| "Opens url (a string) in the default system web browser. May not | ||
| work on all platforms. Returns url on success, nil if not | ||
| supported." | ||
| [url] | ||
| (try | ||
| (when (clojure.lang.Reflector/invokeStaticMethod "java.awt.Desktop" | ||
| "isDesktopSupported" (to-array nil)) | ||
| (-> (clojure.lang.Reflector/invokeStaticMethod "java.awt.Desktop" | ||
| "getDesktop" (to-array nil)) | ||
| (.browse (URI. url))) | ||
| url) | ||
| (catch ClassNotFoundException e | ||
| nil))) | ||
|
|
||
| (defn- open-url-in-swing | ||
| "Opens url (a string) in a Swing window." | ||
| [url] | ||
| ; the implementation of this function resides in another namespace to be loaded "on demand" | ||
| ; this fixes a bug on mac os x where the process turns into a GUI app | ||
| ; see http://code.google.com/p/clojure-contrib/issues/detail?id=32 | ||
| (require 'clojure.java.browse-ui) | ||
| ((find-var 'clojure.java.browse-ui/open-url-in-swing) url)) | ||
|
|
||
| (defn browse-url | ||
| "Open url in a browser" | ||
| {:added "1.2"} | ||
| [url] | ||
| (let [script @*open-url-script* | ||
| script (if (= :uninitialized script) | ||
| (reset! *open-url-script* (open-url-script-val)) | ||
| script)] | ||
| (or (when script | ||
| (try | ||
| (let [command [script (str url)] | ||
| null-file (File. (if (.startsWith (System/getProperty "os.name") "Windows") "NUL" "/dev/null")) | ||
| pb (doto (ProcessBuilder. ^java.util.List command) | ||
| ;; emulate ProcessBuilder.Redirect.DISCARD added in Java 9 | ||
| (.redirectOutput null-file) | ||
| (.redirectError null-file))] | ||
| (.start pb) ;; do not wait for the process | ||
| true) | ||
| (catch Throwable _ false))) | ||
| (open-url-in-browser url) | ||
| (open-url-in-swing url)))) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (ns | ||
| ^{:author "Christophe Grand", | ||
| :doc "Helper namespace for clojure.java.browse. | ||
| Prevents console apps from becoming GUI unnecessarily."} | ||
| clojure.java.browse-ui) | ||
|
|
||
| (defn- open-url-in-swing | ||
| [url] | ||
| (let [htmlpane (javax.swing.JEditorPane. url)] | ||
| (.setEditable htmlpane false) | ||
| (.addHyperlinkListener htmlpane | ||
| (proxy [javax.swing.event.HyperlinkListener] [] | ||
| (hyperlinkUpdate [^javax.swing.event.HyperlinkEvent e] | ||
| (when (= (.getEventType e) (. javax.swing.event.HyperlinkEvent$EventType ACTIVATED)) | ||
| (if (instance? javax.swing.text.html.HTMLFrameHyperlinkEvent e) | ||
| (-> htmlpane .getDocument (.processHTMLFrameHyperlinkEvent e)) | ||
| (.setPage htmlpane (.getURL e))))))) | ||
| (doto (javax.swing.JFrame.) | ||
| (.setContentPane (javax.swing.JScrollPane. htmlpane)) | ||
| (.setBounds 32 32 700 900) | ||
| (.setVisible true)))) | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
| (ns | ||
| ^{:author "Christophe Grand, Stuart Sierra", | ||
| :doc "A repl helper to quickly open javadocs."} | ||
| clojure.java.javadoc | ||
| (:use [clojure.java.browse :only (browse-url)] ) | ||
| (:import | ||
| (java.io File))) | ||
|
|
||
| (def ^:dynamic *feeling-lucky-url* "http://www.google.com/search?btnI=I%27m%20Feeling%20Lucky&q=allinurl:") | ||
| (def ^:dynamic *feeling-lucky* true) | ||
|
|
||
| (def ^:dynamic *local-javadocs* (ref (list))) | ||
|
|
||
| (def ^:dynamic *core-java-api* | ||
| (case (System/getProperty "java.specification.version") | ||
| "1.8" "http://docs.oracle.com/javase/8/docs/api/" | ||
| "9" "http://docs.oracle.com/javase/9/docs/api/" | ||
| "10" "http://docs.oracle.com/javase/10/docs/api/" | ||
| "11" "https://docs.oracle.com/en/java/javase/11/docs/api/%s/" | ||
| "12" "https://docs.oracle.com/en/java/javase/12/docs/api/%s/" | ||
| "13" "https://docs.oracle.com/en/java/javase/13/docs/api/%s/" | ||
| "14" "https://docs.oracle.com/en/java/javase/14/docs/api/%s/" | ||
| "15" "https://docs.oracle.com/en/java/javase/15/docs/api/%s/" | ||
| "http://docs.oracle.com/javase/8/docs/api/")) | ||
|
|
||
| (def ^:dynamic *remote-javadocs* | ||
| (ref (sorted-map | ||
| "com.google.common." "http://google.github.io/guava/releases/23.0/api/docs/" | ||
| "java." *core-java-api* | ||
| "javax." *core-java-api* | ||
| "org.ietf.jgss." *core-java-api* | ||
| "org.omg." *core-java-api* | ||
| "org.w3c.dom." *core-java-api* | ||
| "org.xml.sax." *core-java-api* | ||
| "org.apache.commons.codec." "http://commons.apache.org/proper/commons-codec/apidocs/" | ||
| "org.apache.commons.io." "http://commons.apache.org/proper/commons-io/javadocs/api-release/" | ||
| "org.apache.commons.lang." "http://commons.apache.org/proper/commons-lang/javadocs/api-2.6/" | ||
| "org.apache.commons.lang3." "http://commons.apache.org/proper/commons-lang/javadocs/api-release/"))) | ||
|
|
||
| (defn add-local-javadoc | ||
| "Adds to the list of local Javadoc paths." | ||
| {:added "1.2"} | ||
| [path] | ||
| (dosync (commute *local-javadocs* conj path))) | ||
|
|
||
| (defn add-remote-javadoc | ||
| "Adds to the list of remote Javadoc URLs. package-prefix is the | ||
| beginning of the package name that has docs at this URL." | ||
| {:added "1.2"} | ||
| [package-prefix url] | ||
| (dosync (commute *remote-javadocs* assoc package-prefix url))) | ||
|
|
||
| (defn- fill-in-module-name [^String url ^String classname] | ||
| ;; The getModule method was introduced in JDK 9, and did not exist | ||
| ;; in earlier JDK versions. Avoid calling it unless its result is | ||
| ;; needed. | ||
| (if (.contains url "%s") | ||
| (let [klass (Class/forName classname) | ||
| module-name (.getName (.getModule klass))] | ||
| (format url module-name)) | ||
| url)) | ||
|
|
||
| (defn- javadoc-url | ||
| "Searches for a URL for the given class name. Tries | ||
| *local-javadocs* first, then *remote-javadocs*. Returns a string." | ||
| {:tag String, | ||
| :added "1.2"} | ||
| [^String classname] | ||
| (let [file-path (.replace classname \. File/separatorChar) | ||
| url-path (.replace classname \. \/)] | ||
| (if-let [file ^File (first | ||
| (filter #(.exists ^File %) | ||
| (map #(File. (str %) (str file-path ".html")) | ||
| @*local-javadocs*)))] | ||
| (-> file .toURI str) | ||
| ;; If no local file, try remote URLs: | ||
| (or (some (fn [[prefix url]] | ||
| (when (.startsWith classname prefix) | ||
| (str (fill-in-module-name url classname) | ||
| url-path ".html"))) | ||
| @*remote-javadocs*) | ||
| ;; if *feeling-lucky* try a web search | ||
| (when *feeling-lucky* (str *feeling-lucky-url* url-path ".html")))))) | ||
|
|
||
| (defn javadoc | ||
| "Opens a browser window displaying the javadoc for the argument. | ||
| Tries *local-javadocs* first, then *remote-javadocs*." | ||
| {:added "1.2"} | ||
| [class-or-object] | ||
| (let [^Class c (if (instance? Class class-or-object) | ||
| class-or-object | ||
| (class class-or-object))] | ||
| (if-let [url (javadoc-url (.getName c))] | ||
| (browse-url url) | ||
| (println "Could not find Javadoc for" c)))) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (ns clojure.java.process | ||
| "A process invocation API wrapping the Java process API. | ||
| The primary function is 'start' which starts a process and handles the | ||
| streams as directed. It returns the Process object. Use 'exit-ref' to wait | ||
| for completion and receive the exit value, and ‘stdout', 'stderr', 'stdin' | ||
| to access the process streams. The 'exec' function handles the common case | ||
| to 'start' a process, wait for process exit, and return stdout." | ||
| (:require | ||
| [clojure.java.io :as jio]) | ||
| (:import | ||
| [java.io File InputStream OutputStream] | ||
| [java.lang ProcessBuilder ProcessBuilder$Redirect Process] | ||
| [java.util List] | ||
| [clojure.lang IDeref IBlockingDeref] | ||
| [java.util.concurrent Executors ExecutorService ThreadFactory])) | ||
|
|
||
| (set! *warn-on-reflection* true) | ||
|
|
||
| ;; this is built into Java 9, backfilled here for Java 8 | ||
| (def ^:private ^File null-file | ||
| (delay | ||
| (jio/file | ||
| (if (.startsWith (System/getProperty "os.name") "Windows") | ||
| "NUL" | ||
| "/dev/null")))) | ||
|
|
||
| (defn to-file | ||
| "Coerce f to a file per clojure.java.io/file and return a ProcessBuilder.Redirect writing to the file. | ||
| Set ':append' in opts to append. This can be passed to 'start' in :out or :err." | ||
| {:added "1.12"} | ||
| ^ProcessBuilder$Redirect [f & {:keys [append] :as opts}] | ||
| (let [fo (jio/file f)] | ||
| (if append | ||
| (ProcessBuilder$Redirect/appendTo fo) | ||
| (ProcessBuilder$Redirect/to fo)))) | ||
|
|
||
| (defn from-file | ||
| "Coerce f to a file per clojure.java.io/file and return a ProcessBuilder.Redirect reading from the file. | ||
| This can be passed to 'start' in :in." | ||
| {:added "1.12"} | ||
| ^ProcessBuilder$Redirect [f] | ||
| (ProcessBuilder$Redirect/from (jio/file f))) | ||
|
|
||
| (defn start | ||
| "Start an external command, defined in args. | ||
| The process environment vars are inherited from the parent by | ||
| default (use :clear-env to clear them). | ||
| If needed, provide options in map as first arg: | ||
| :in - a ProcessBuilder.Redirect (default = :pipe) or :inherit | ||
| :out - a ProcessBuilder.Redirect (default = :pipe) or :inherit :discard | ||
| :err - a ProcessBuilder.Redirect (default = :pipe) or :inherit :discard :stdout | ||
| :dir - current directory when the process runs (default=\".\") | ||
| :clear-env - if true, remove all inherited parent env vars | ||
| :env - {env-var value} of environment variables to set (all strings) | ||
| Returns the java.lang.Process." | ||
| {:added "1.12"} | ||
| ^Process [& opts+args] | ||
| (let [[opts command] (if (map? (first opts+args)) | ||
| [(first opts+args) (rest opts+args)] | ||
| [{} opts+args]) | ||
| {:keys [in out err dir env clear-env] | ||
| :or {in :pipe, out :pipe, err :pipe, dir "."}} opts | ||
| pb (ProcessBuilder. ^List command) | ||
| to-redirect (fn to-redirect | ||
| [x] | ||
| (case x | ||
| :pipe ProcessBuilder$Redirect/PIPE | ||
| :inherit ProcessBuilder$Redirect/INHERIT | ||
| :discard (ProcessBuilder$Redirect/to @null-file) | ||
| ;; in Java 9+, just use ProcessBuilder$Redirect/DISCARD | ||
| x))] | ||
| (.directory pb (jio/file dir)) | ||
| (.redirectInput pb ^ProcessBuilder$Redirect (to-redirect in)) | ||
| (.redirectOutput pb ^ProcessBuilder$Redirect (to-redirect out)) | ||
| (if | ||
| (= err :stdout) (.redirectErrorStream pb true) | ||
| (.redirectError pb ^ProcessBuilder$Redirect (to-redirect err))) | ||
| (when clear-env | ||
| (.clear (.environment pb))) | ||
| (when env | ||
| (let [pb-env (.environment pb)] | ||
| (run! (fn [[k v]] (.put pb-env k v)) env))) | ||
| (.start pb))) | ||
|
|
||
| (defn stdin | ||
| "Given a process, return the stdin of the external process (an OutputStream)" | ||
| ^OutputStream [^Process process] | ||
| (.getOutputStream process)) | ||
|
|
||
| (defn stdout | ||
| "Given a process, return the stdout of the external process (an InputStream)" | ||
| ^InputStream [^Process process] | ||
| (.getInputStream process)) | ||
|
|
||
| (defn stderr | ||
| "Given a process, return the stderr of the external process (an InputStream)" | ||
| ^InputStream [^Process process] | ||
| (.getErrorStream process)) | ||
|
|
||
| (defn exit-ref | ||
| "Given a Process (the output of 'start'), return a reference that can be | ||
| used to wait for process completion then returns the exit value." | ||
| [^Process process] | ||
| (reify | ||
| IDeref | ||
| (deref [_] (long (.waitFor process))) | ||
|
|
||
| IBlockingDeref | ||
| (deref [_ timeout-ms timeout-val] | ||
| (if (.waitFor process timeout-ms java.util.concurrent.TimeUnit/MILLISECONDS) | ||
| (long (.exitValue process)) | ||
| timeout-val)))) | ||
|
|
||
| ;; A thread factory for daemon threads | ||
| (defonce ^:private io-thread-factory | ||
| (let [counter (atom 0)] | ||
| (reify ThreadFactory | ||
| (newThread [_ r] | ||
| (doto (Thread. r) | ||
| (.setName (str "Clojure Process IO " (swap! counter inc))) | ||
| (.setDaemon true)))))) | ||
|
|
||
| ;; An ExecutorService for cached, daemon threads | ||
| (defonce ^:private io-executor | ||
| (Executors/newCachedThreadPool ^ThreadFactory io-thread-factory)) | ||
|
|
||
| (defn io-task | ||
| {:skip-wiki true} | ||
| [^Runnable f] | ||
| (let [f (bound-fn* f) | ||
| fut (.submit ^ExecutorService io-executor ^Callable f)] | ||
| (reify | ||
| clojure.lang.IDeref | ||
| (deref [_] (#'clojure.core/deref-future fut)) | ||
| clojure.lang.IBlockingDeref | ||
| (deref | ||
| [_ timeout-ms timeout-val] | ||
| (#'clojure.core/deref-future fut timeout-ms timeout-val)) | ||
| clojure.lang.IPending | ||
| (isRealized [_] (.isDone fut)) | ||
| java.util.concurrent.Future | ||
| (get [_] (.get fut)) | ||
| (get [_ timeout unit] (.get fut timeout unit)) | ||
| (isCancelled [_] (.isCancelled fut)) | ||
| (isDone [_] (.isDone fut)) | ||
| (cancel [_ interrupt?] (.cancel fut interrupt?))))) | ||
|
|
||
| (defn exec | ||
| "Execute a command and on successful exit, return the captured output, | ||
| else throw RuntimeException. Args are the same as 'start' and options | ||
| if supplied override the default 'exec' settings." | ||
| {:added "1.12"} | ||
| [& opts+args] | ||
| (let [[opts command] (if (map? (first opts+args)) | ||
| [(first opts+args) (rest opts+args)] | ||
| [{} opts+args]) | ||
| opts (merge {:err :inherit} opts)] | ||
| (let [proc (apply start opts command) | ||
| captured (io-task #(slurp (stdout proc))) | ||
| exit (deref (exit-ref proc))] | ||
| (if (zero? exit) | ||
| @captured | ||
| (throw (RuntimeException. (str "Process failed with exit=" exit))))))) | ||
|
|
||
| (comment | ||
| ;; shell out and inherit the i/o | ||
| (start {:out :inherit, :err :stdout} "ls" "-l") | ||
|
|
||
| ;; write out and err to files, wait for process to exit, return exit code | ||
| @(exit-ref (start {:out (to-file "out") :err (to-file "err")} "ls" "-l")) | ||
|
|
||
| ;; capture output to string | ||
| (-> (start "ls" "-l") stdout slurp) | ||
|
|
||
| ;; with exec | ||
| (exec "ls" "-l") | ||
|
|
||
| ;; read input from file | ||
| (-> (exec {:in (from-file "deps.edn")} "wc" "-l") clojure.string/trim parse-long) | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (ns | ||
| ^{:author "Chris Houser, Stuart Halloway", | ||
| :doc "Conveniently launch a sub-process providing its stdin and | ||
| collecting its stdout"} | ||
| clojure.java.shell | ||
| (:use [clojure.java.io :only (as-file copy)]) | ||
| (:import (java.io ByteArrayOutputStream StringWriter) | ||
| (java.nio.charset Charset))) | ||
|
|
||
| (def ^:dynamic *sh-dir* nil) | ||
| (def ^:dynamic *sh-env* nil) | ||
|
|
||
| (defmacro with-sh-dir | ||
| "Sets the directory for use with sh, see sh for details." | ||
| {:added "1.2"} | ||
| [dir & forms] | ||
| `(binding [*sh-dir* ~dir] | ||
| ~@forms)) | ||
|
|
||
| (defmacro with-sh-env | ||
| "Sets the environment for use with sh, see sh for details." | ||
| {:added "1.2"} | ||
| [env & forms] | ||
| `(binding [*sh-env* ~env] | ||
| ~@forms)) | ||
|
|
||
| (defn- aconcat | ||
| "Concatenates arrays of given type." | ||
| [type & xs] | ||
| (let [target (make-array type (apply + (map count xs)))] | ||
| (loop [i 0 idx 0] | ||
| (when-let [a (nth xs i nil)] | ||
| (System/arraycopy a 0 target idx (count a)) | ||
| (recur (inc i) (+ idx (count a))))) | ||
| target)) | ||
|
|
||
| (defn- parse-args | ||
| [args] | ||
| (let [default-encoding "UTF-8" ;; see sh doc string | ||
| default-opts {:out-enc default-encoding :in-enc default-encoding :dir *sh-dir* :env *sh-env*} | ||
| [cmd opts] (split-with string? args)] | ||
| [cmd (merge default-opts (apply hash-map opts))])) | ||
|
|
||
| (defn- ^"[Ljava.lang.String;" as-env-strings | ||
| "Helper so that callers can pass a Clojure map for the :env to sh." | ||
| [arg] | ||
| (cond | ||
| (nil? arg) nil | ||
| (map? arg) (into-array String (map (fn [[k v]] (str (name k) "=" v)) arg)) | ||
| true arg)) | ||
|
|
||
| (defn- stream-to-bytes | ||
| [in] | ||
| (with-open [bout (ByteArrayOutputStream.)] | ||
| (copy in bout) | ||
| (.toByteArray bout))) | ||
|
|
||
| (defn- stream-to-string | ||
| ([in] (stream-to-string in (.name (Charset/defaultCharset)))) | ||
| ([in enc] | ||
| (with-open [bout (StringWriter.)] | ||
| (copy in bout :encoding enc) | ||
| (.toString bout)))) | ||
|
|
||
| (defn- stream-to-enc | ||
| [stream enc] | ||
| (if (= enc :bytes) | ||
| (stream-to-bytes stream) | ||
| (stream-to-string stream enc))) | ||
|
|
||
| (defn sh | ||
| "Passes the given strings to Runtime.exec() to launch a sub-process. | ||
| Options are | ||
| :in may be given followed by any legal input source for | ||
| clojure.java.io/copy, e.g. InputStream, Reader, File, byte[], | ||
| or String, to be fed to the sub-process's stdin. | ||
| :in-enc option may be given followed by a String, used as a character | ||
| encoding name (for example \"UTF-8\" or \"ISO-8859-1\") to | ||
| convert the input string specified by the :in option to the | ||
| sub-process's stdin. Defaults to UTF-8. | ||
| If the :in option provides a byte array, then the bytes are passed | ||
| unencoded, and this option is ignored. | ||
| :out-enc option may be given followed by :bytes or a String. If a | ||
| String is given, it will be used as a character encoding | ||
| name (for example \"UTF-8\" or \"ISO-8859-1\") to convert | ||
| the sub-process's stdout to a String which is returned. | ||
| If :bytes is given, the sub-process's stdout will be stored | ||
| in a byte array and returned. Defaults to UTF-8. | ||
| :env override the process env with a map (or the underlying Java | ||
| String[] if you are a masochist). | ||
| :dir override the process dir with a String or java.io.File. | ||
| You can bind :env or :dir for multiple operations using with-sh-env | ||
| and with-sh-dir. | ||
| sh returns a map of | ||
| :exit => sub-process's exit code | ||
| :out => sub-process's stdout (as byte[] or String) | ||
| :err => sub-process's stderr (String via platform default encoding)" | ||
| {:added "1.2"} | ||
| [& args] | ||
| (let [[cmd opts] (parse-args args) | ||
| proc (.exec (Runtime/getRuntime) | ||
| ^"[Ljava.lang.String;" (into-array cmd) | ||
| (as-env-strings (:env opts)) | ||
| (as-file (:dir opts))) | ||
| {:keys [in in-enc out-enc]} opts] | ||
| (if in | ||
| (future | ||
| (with-open [os (.getOutputStream proc)] | ||
| (copy in os :encoding in-enc))) | ||
| (.close (.getOutputStream proc))) | ||
| (with-open [stdout (.getInputStream proc) | ||
| stderr (.getErrorStream proc)] | ||
| (let [out (future (stream-to-enc stdout out-enc)) | ||
| err (future (stream-to-string stderr)) | ||
| exit-code (.waitFor proc)] | ||
| {:exit exit-code :out @out :err @err})))) | ||
|
|
||
| (comment | ||
|
|
||
| (println (sh "ls" "-l")) | ||
| (println (sh "ls" "-l" "/no-such-thing")) | ||
| (println (sh "sed" "s/[aeiou]/oo/g" :in "hello there\n")) | ||
| (println (sh "sed" "s/[aeiou]/oo/g" :in (java.io.StringReader. "hello there\n"))) | ||
| (println (sh "cat" :in "x\u25bax\n")) | ||
| (println (sh "echo" "x\u25bax")) | ||
| (println (sh "echo" "x\u25bax" :out-enc "ISO-8859-1")) ; reads 4 single-byte chars | ||
| (println (sh "cat" "myimage.png" :out-enc :bytes)) ; reads binary file into bytes[] | ||
| (println (sh "cmd" "/c dir 1>&2")) | ||
|
|
||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| ;;; pprint.clj -- Pretty printer and Common Lisp compatible format function (cl-format) for Clojure | ||
|
|
||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| ;; Author: Tom Faulhaber | ||
| ;; April 3, 2009 | ||
|
|
||
| (ns | ||
| ^{:author "Tom Faulhaber", | ||
| :doc "A Pretty Printer for Clojure | ||
| clojure.pprint implements a flexible system for printing structured data | ||
| in a pleasing, easy-to-understand format. Basic use of the pretty printer is | ||
| simple, just call pprint instead of println. More advanced users can use | ||
| the building blocks provided to create custom output formats. | ||
| Out of the box, pprint supports a simple structured format for basic data | ||
| and a specialized format for Clojure source code. More advanced formats, | ||
| including formats that don't look like Clojure data at all like XML and | ||
| JSON, can be rendered by creating custom dispatch functions. | ||
| In addition to the pprint function, this module contains cl-format, a text | ||
| formatting function which is fully compatible with the format function in | ||
| Common Lisp. Because pretty printing directives are directly integrated with | ||
| cl-format, it supports very concise custom dispatch. It also provides | ||
| a more powerful alternative to Clojure's standard format function. | ||
| See documentation for pprint and cl-format for more information or | ||
| complete documentation on the Clojure web site on GitHub.", | ||
| :added "1.2"} | ||
| clojure.pprint | ||
| (:refer-clojure :exclude (deftype)) | ||
| (:use [clojure.walk :only [walk]])) | ||
|
|
||
| (set! *warn-on-reflection* true) | ||
|
|
||
| (load "pprint/utilities") | ||
| (load "pprint/column_writer") | ||
| (load "pprint/pretty_writer") | ||
| (load "pprint/pprint_base") | ||
| (load "pprint/cl_format") | ||
| (load "pprint/dispatch") | ||
| (load "pprint/print_table") | ||
|
|
||
| nil |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| ;;; column_writer.clj -- part of the pretty printer for Clojure | ||
|
|
||
|
|
||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| ;; Author: Tom Faulhaber | ||
| ;; April 3, 2009 | ||
| ;; Revised to use proxy instead of gen-class April 2010 | ||
|
|
||
| ;; This module implements a column-aware wrapper around an instance of java.io.Writer | ||
|
|
||
| (in-ns 'clojure.pprint) | ||
|
|
||
| (import [clojure.lang IDeref] | ||
| [java.io Writer]) | ||
|
|
||
| (def ^:dynamic ^{:private true} *default-page-width* 72) | ||
|
|
||
| (defn- get-field [^Writer this sym] | ||
| (sym @@this)) | ||
|
|
||
| (defn- set-field [^Writer this sym new-val] | ||
| (alter @this assoc sym new-val)) | ||
|
|
||
| (defn- get-column [this] | ||
| (get-field this :cur)) | ||
|
|
||
| (defn- get-line [this] | ||
| (get-field this :line)) | ||
|
|
||
| (defn- get-max-column [this] | ||
| (get-field this :max)) | ||
|
|
||
| (defn- set-max-column [this new-max] | ||
| (dosync (set-field this :max new-max)) | ||
| nil) | ||
|
|
||
| (defn- get-writer [this] | ||
| (get-field this :base)) | ||
|
|
||
| (defn- c-write-char [^Writer this ^Integer c] | ||
| (dosync (if (= c (int \newline)) | ||
| (do | ||
| (set-field this :cur 0) | ||
| (set-field this :line (inc (get-field this :line)))) | ||
| (set-field this :cur (inc (get-field this :cur))))) | ||
| (.write ^Writer (get-field this :base) c)) | ||
|
|
||
| (defn- column-writer | ||
| ([writer] (column-writer writer *default-page-width*)) | ||
| ([^Writer writer max-columns] | ||
| (let [fields (ref {:max max-columns, :cur 0, :line 0 :base writer})] | ||
| (proxy [Writer IDeref] [] | ||
| (deref [] fields) | ||
| (flush [] | ||
| (.flush writer)) | ||
| (write | ||
| ([^chars cbuf ^Integer off ^Integer len] | ||
| (let [^Writer writer (get-field this :base)] | ||
| (.write writer cbuf off len))) | ||
| ([x] | ||
| (condp = (class x) | ||
| String | ||
| (let [^String s x | ||
| nl (.lastIndexOf s (int \newline))] | ||
| (dosync (if (neg? nl) | ||
| (set-field this :cur (+ (get-field this :cur) (count s))) | ||
| (do | ||
| (set-field this :cur (- (count s) nl 1)) | ||
| (set-field this :line (+ (get-field this :line) | ||
| (count (filter #(= % \newline) s))))))) | ||
| (.write ^Writer (get-field this :base) s)) | ||
|
|
||
| Integer | ||
| (c-write-char this x) | ||
| Long | ||
| (c-write-char this x)))))))) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (in-ns 'clojure.pprint) | ||
|
|
||
| (defn print-table | ||
| "Prints a collection of maps in a textual table. Prints table headings | ||
| ks, and then a line of output for each row, corresponding to the keys | ||
| in ks. If ks are not specified, use the keys of the first item in rows." | ||
| {:added "1.3"} | ||
| ([ks rows] | ||
| (when (seq rows) | ||
| (let [widths (map | ||
| (fn [k] | ||
| (apply max (count (str k)) (map #(count (str (get % k))) rows))) | ||
| ks) | ||
| spacers (map #(apply str (repeat % "-")) widths) | ||
| fmts (map #(str "%" % "s") widths) | ||
| fmt-row (fn [leader divider trailer row] | ||
| (str leader | ||
| (apply str (interpose divider | ||
| (for [[col fmt] (map vector (map #(get row %) ks) fmts)] | ||
| (format fmt (str col))))) | ||
| trailer))] | ||
| (println) | ||
| (println (fmt-row "| " " | " " |" (zipmap ks ks))) | ||
| (println (fmt-row "|-" "-+-" "-|" (zipmap ks spacers))) | ||
| (doseq [row rows] | ||
| (println (fmt-row "| " " | " " |" row)))))) | ||
| ([rows] (print-table (keys (first rows)) rows))) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| ;;; utilities.clj -- part of the pretty printer for Clojure | ||
|
|
||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| ;; Author: Tom Faulhaber | ||
| ;; April 3, 2009 | ||
|
|
||
| ;; This module implements some utility function used in formatting and pretty | ||
| ;; printing. The functions here could go in a more general purpose library, | ||
| ;; perhaps. | ||
|
|
||
| (in-ns 'clojure.pprint) | ||
|
|
||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| ;;; Helper functions for digesting formats in the various | ||
| ;;; phases of their lives. | ||
| ;;; These functions are actually pretty general. | ||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
|
|
||
| (defn- map-passing-context [func initial-context lis] | ||
| (loop [context initial-context | ||
| lis lis | ||
| acc []] | ||
| (if (empty? lis) | ||
| [acc context] | ||
| (let [this (first lis) | ||
| remainder (next lis) | ||
| [result new-context] (apply func [this context])] | ||
| (recur new-context remainder (conj acc result)))))) | ||
|
|
||
| (defn- consume [func initial-context] | ||
| (loop [context initial-context | ||
| acc []] | ||
| (let [[result new-context] (apply func [context])] | ||
| (if (not result) | ||
| [acc new-context] | ||
| (recur new-context (conj acc result)))))) | ||
|
|
||
| (defn- consume-while [func initial-context] | ||
| (loop [context initial-context | ||
| acc []] | ||
| (let [[result continue new-context] (apply func [context])] | ||
| (if (not continue) | ||
| [acc context] | ||
| (recur new-context (conj acc result)))))) | ||
|
|
||
| (defn- unzip-map | ||
| "Take a map that has pairs in the value slots and produce a pair of | ||
| maps, the first having all the first elements of the pairs and the | ||
| second all the second elements of the pairs" | ||
| [m] | ||
| [(into {} (for [[k [v1 v2]] m] [k v1])) | ||
| (into {} (for [[k [v1 v2]] m] [k v2]))]) | ||
|
|
||
| (defn- tuple-map | ||
| "For all the values, v, in the map, replace them with [v v1]" | ||
| [m v1] | ||
| (into {} (for [[k v] m] [k [v v1]]))) | ||
|
|
||
| (defn- rtrim | ||
| "Trim all instances of c from the end of sequence s" | ||
| [s c] | ||
| (let [len (count s)] | ||
| (if (and (pos? len) (= (nth s (dec (count s))) c)) | ||
| (loop [n (dec len)] | ||
| (cond | ||
| (neg? n) "" | ||
| (not (= (nth s n) c)) (subs s 0 (inc n)) | ||
| true (recur (dec n)))) | ||
| s))) | ||
|
|
||
| (defn- ltrim | ||
| "Trim all instances of c from the beginning of sequence s" | ||
| [s c] | ||
| (let [len (count s)] | ||
| (if (and (pos? len) (= (nth s 0) c)) | ||
| (loop [n 0] | ||
| (if (or (= n len) (not (= (nth s n) c))) | ||
| (subs s n) | ||
| (recur (inc n)))) | ||
| s))) | ||
|
|
||
| (defn- prefix-count | ||
| "Return the number of times that val occurs at the start of sequence aseq, | ||
| if val is a seq itself, count the number of times any element of val | ||
| occurs at the beginning of aseq" | ||
| [aseq val] | ||
| (let [test (if (coll? val) (set val) #{val})] | ||
| (loop [pos 0] | ||
| (if (or (= pos (count aseq)) (not (test (nth aseq pos)))) | ||
| pos | ||
| (recur (inc pos)))))) | ||
|
|
||
| (defn- prerr | ||
| "Println to *err*" | ||
| [& args] | ||
| (binding [*out* *err*] | ||
| (apply println args))) | ||
|
|
||
| (defmacro ^{:private true} prlabel | ||
| "Print args to *err* in name = value format" | ||
| [prefix arg & more-args] | ||
| `(prerr ~@(cons (list 'quote prefix) (mapcat #(list (list 'quote %) "=" %) | ||
| (cons arg (seq more-args)))))) | ||
|
|
||
| ;; Flush the pretty-print buffer without flushing the underlying stream | ||
| (definterface PrettyFlush | ||
| (^void ppflush [])) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| (ns ^{:author "Stuart Halloway" | ||
| :added "1.3" | ||
| :doc "Reflection on Host Types | ||
| Alpha - subject to change. | ||
| Two main entry points: | ||
| * type-reflect reflects on something that implements TypeReference. | ||
| * reflect (for REPL use) reflects on the class of an instance, or | ||
| on a class if passed a class | ||
| Key features: | ||
| * Exposes the read side of reflection as pure data. Reflecting | ||
| on a type returns a map with keys :bases, :flags, and :members. | ||
| * Canonicalizes class names as Clojure symbols. Types can extend | ||
| to the TypeReference protocol to indicate that they can be | ||
| unambiguously resolved as a type name. The canonical format | ||
| requires one non-Java-ish convention: array brackets are <> | ||
| instead of [] so they can be part of a Clojure symbol. | ||
| * Pluggable Reflectors for different implementations. The default | ||
| JavaReflector is good when you have a class in hand, or use | ||
| the AsmReflector for \"hands off\" reflection without forcing | ||
| classes to load. | ||
| Platform implementers must: | ||
| * Create an implementation of Reflector. | ||
| * Create one or more implementations of TypeReference. | ||
| * def default-reflector to be an instance that satisfies Reflector."} | ||
| clojure.reflect | ||
| (:require [clojure.set :as set])) | ||
|
|
||
| (defprotocol Reflector | ||
| "Protocol for reflection implementers." | ||
| (do-reflect [reflector typeref])) | ||
|
|
||
| (defprotocol TypeReference | ||
| "A TypeReference can be unambiguously converted to a type name on | ||
| the host platform. | ||
| All typerefs are normalized into symbols. If you need to | ||
| normalize a typeref yourself, call typesym." | ||
| (typename [o] "Returns Java name as returned by ASM getClassName, e.g. byte[], java.lang.String[]")) | ||
|
|
||
| (declare default-reflector) | ||
|
|
||
| (defn type-reflect | ||
| "Alpha - subject to change. | ||
| Reflect on a typeref, returning a map with :bases, :flags, and | ||
| :members. In the discussion below, names are always Clojure symbols. | ||
| :bases a set of names of the type's bases | ||
| :flags a set of keywords naming the boolean attributes | ||
| of the type. | ||
| :members a set of the type's members. Each member is a map | ||
| and can be a constructor, method, or field. | ||
| Keys common to all members: | ||
| :name name of the type | ||
| :declaring-class name of the declarer | ||
| :flags keyword naming boolean attributes of the member | ||
| Keys specific to constructors: | ||
| :parameter-types vector of parameter type names | ||
| :exception-types vector of exception type names | ||
| Key specific to methods: | ||
| :parameter-types vector of parameter type names | ||
| :exception-types vector of exception type names | ||
| :return-type return type name | ||
| Keys specific to fields: | ||
| :type type name | ||
| Options: | ||
| :ancestors in addition to the keys described above, also | ||
| include an :ancestors key with the entire set of | ||
| ancestors, and add all ancestor members to | ||
| :members. | ||
| :reflector implementation to use. Defaults to JavaReflector, | ||
| AsmReflector is also an option." | ||
| {:added "1.3"} | ||
| [typeref & options] | ||
| (let [{:keys [ancestors reflector]} | ||
| (merge {:reflector default-reflector} | ||
| (apply hash-map options)) | ||
| refl (partial do-reflect reflector) | ||
| result (refl typeref)] | ||
| ;; could make simpler loop of two args: names an | ||
| (if ancestors | ||
| (let [make-ancestor-map (fn [names] | ||
| (zipmap names (map refl names)))] | ||
| (loop [reflections (make-ancestor-map (:bases result))] | ||
| (let [ancestors-visited (set (keys reflections)) | ||
| ancestors-to-visit (set/difference (set (mapcat :bases (vals reflections))) | ||
| ancestors-visited)] | ||
| (if (seq ancestors-to-visit) | ||
| (recur (merge reflections (make-ancestor-map ancestors-to-visit))) | ||
| (apply merge-with into result {:ancestors ancestors-visited} | ||
| (map #(select-keys % [:members]) (vals reflections))))))) | ||
| result))) | ||
|
|
||
| (defn reflect | ||
| "Alpha - subject to change. | ||
| Reflect on the type of obj (or obj itself if obj is a class). | ||
| Return value and options are the same as for type-reflect. " | ||
| {:added "1.3"} | ||
| [obj & options] | ||
| (apply type-reflect (if (class? obj) obj (class obj)) options)) | ||
|
|
||
| (load "reflect/java") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,267 @@ | ||
| ; Copyright (c) Rich Hickey. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | ||
| ; which can be found in the file epl-v10.html at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| ;; Java-specific parts of clojure.reflect | ||
| (in-ns 'clojure.reflect) | ||
|
|
||
| (require '[clojure.datafy :refer (datafy)] | ||
| '[clojure.set :as set] | ||
| '[clojure.string :as str]) | ||
| (import '[clojure.asm ClassReader ClassVisitor Type Opcodes] | ||
| '[java.lang.reflect Modifier] | ||
| java.io.InputStream) | ||
|
|
||
| (set! *warn-on-reflection* true) | ||
|
|
||
| (extend-protocol TypeReference | ||
| clojure.lang.Symbol | ||
| (typename [s] (str/replace (str s) "<>" "[]")) | ||
|
|
||
| Class | ||
| ;; neither .getName not .getSimpleName returns the right thing, so best to delegate to Type | ||
| (typename | ||
| [c] | ||
| (typename (Type/getType c))) | ||
|
|
||
| Type | ||
| (typename | ||
| [t] | ||
| (-> (.getClassName t)))) | ||
|
|
||
| (defn- typesym | ||
| "Given a typeref, create a legal Clojure symbol version of the | ||
| type's name." | ||
| [t] | ||
| (cond-> | ||
| (-> (typename t) | ||
| (str/replace "[]" "<>") | ||
| (symbol)) | ||
| (class? t) (with-meta {'clojure.core.protocols/datafy | ||
| (fn [_] (datafy t))}))) | ||
|
|
||
| (defn- resource-name | ||
| "Given a typeref, return implied resource name. Used by Reflectors | ||
| such as ASM that need to find and read classbytes from files." | ||
| [typeref] | ||
| (-> (typename typeref) | ||
| (str/replace "." "/") | ||
| (str ".class"))) | ||
|
|
||
| (defn- access-flag | ||
| [[name flag & contexts]] | ||
| {:name name :flag flag :contexts (set (map keyword contexts))}) | ||
|
|
||
| (defn- field-descriptor->class-symbol | ||
| "Convert a Java field descriptor to a Clojure class symbol. Field | ||
| descriptors are described in section 4.3.2 of the JVM spec, 2nd ed.: | ||
| http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#14152" | ||
| [^String d] | ||
| {:pre [(string? d)]} | ||
| (typesym (Type/getType d))) | ||
|
|
||
| (defn- internal-name->class-symbol | ||
| "Convert a Java internal name to a Clojure class symbol. Internal | ||
| names uses slashes instead of dots, e.g. java/lang/String. See | ||
| Section 4.2 of the JVM spec, 2nd ed.: | ||
| http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#14757" | ||
| [d] | ||
| {:pre [(string? d)]} | ||
| (typesym (Type/getObjectType d))) | ||
|
|
||
| (def ^{:doc "The Java access bitflags, along with their friendly names and | ||
| the kinds of objects to which they can apply."} | ||
| flag-descriptors | ||
| (vec | ||
| (map access-flag | ||
| [[:public 0x0001 :class :field :method] | ||
| [:private 0x002 :class :field :method] | ||
| [:protected 0x0004 :class :field :method] | ||
| [:static 0x0008 :field :method] | ||
| [:final 0x0010 :class :field :method] | ||
| ;; :super is ancient history and is unfindable (?) by | ||
| ;; reflection. skip it | ||
| #_[:super 0x0020 :class] | ||
| [:synchronized 0x0020 :method] | ||
| [:volatile 0x0040 :field] | ||
| [:bridge 0x0040 :method] | ||
| [:varargs 0x0080 :method] | ||
| [:transient 0x0080 :field] | ||
| [:native 0x0100 :method] | ||
| [:interface 0x0200 :class] | ||
| [:abstract 0x0400 :class :method] | ||
| [:strict 0x0800 :method] | ||
| [:synthetic 0x1000 :class :field :method] | ||
| [:annotation 0x2000 :class] | ||
| [:enum 0x4000 :class :field :inner]]))) | ||
|
|
||
| (defn- parse-flags | ||
| "Convert reflection bitflags into a set of keywords." | ||
| [flags context] | ||
| (reduce | ||
| (fn [result fd] | ||
| (if (and (get (:contexts fd) context) | ||
| (not (zero? (bit-and flags (:flag fd))))) | ||
| (conj result (:name fd)) | ||
| result)) | ||
| #{} | ||
| flag-descriptors)) | ||
|
|
||
| (defrecord Constructor | ||
| [name declaring-class parameter-types exception-types flags]) | ||
|
|
||
| (defn- constructor->map | ||
| [^java.lang.reflect.Constructor constructor] | ||
| (Constructor. | ||
| (symbol (.getName constructor)) | ||
| (typesym (.getDeclaringClass constructor)) | ||
| (vec (map typesym (.getParameterTypes constructor))) | ||
| (vec (map typesym (.getExceptionTypes constructor))) | ||
| (parse-flags (.getModifiers constructor) :method))) | ||
|
|
||
| (defn- declared-constructors | ||
| "Return a set of the declared constructors of class as a Clojure map." | ||
| [^Class cls] | ||
| (set (map | ||
| constructor->map | ||
| (.getDeclaredConstructors cls)))) | ||
|
|
||
| (defrecord Method | ||
| [name return-type declaring-class parameter-types exception-types flags]) | ||
|
|
||
| (defn- method->map | ||
| [^java.lang.reflect.Method method] | ||
| (Method. | ||
| (symbol (.getName method)) | ||
| (typesym (.getReturnType method)) | ||
| (typesym (.getDeclaringClass method)) | ||
| (vec (map typesym (.getParameterTypes method))) | ||
| (vec (map typesym (.getExceptionTypes method))) | ||
| (parse-flags (.getModifiers method) :method))) | ||
|
|
||
| (defn- declared-methods | ||
| "Return a set of the declared constructors of class as a Clojure map." | ||
| [^Class cls] | ||
| (set (map | ||
| method->map | ||
| (.getDeclaredMethods cls)))) | ||
|
|
||
| (defrecord Field | ||
| [name type declaring-class flags]) | ||
|
|
||
| (defn- field->map | ||
| [^java.lang.reflect.Field field] | ||
| (Field. | ||
| (symbol (.getName field)) | ||
| (typesym (.getType field)) | ||
| (typesym (.getDeclaringClass field)) | ||
| (parse-flags (.getModifiers field) :field))) | ||
|
|
||
| (defn- declared-fields | ||
| "Return a set of the declared fields of class as a Clojure map." | ||
| [^Class cls] | ||
| (set (map | ||
| field->map | ||
| (.getDeclaredFields cls)))) | ||
|
|
||
| (defn- typeref->class | ||
| ^Class [typeref classloader] | ||
| (if (class? typeref) | ||
| typeref | ||
| (clojure.lang.RT/classForName (typename typeref) false classloader))) | ||
|
|
||
| (deftype JavaReflector [classloader] | ||
| Reflector | ||
| (do-reflect [_ typeref] | ||
| (let [cls (typeref->class typeref classloader)] | ||
| {:bases (not-empty (set (map typesym (bases cls)))) | ||
| :flags (parse-flags (.getModifiers cls) :class) | ||
| :members (set/union (declared-fields cls) | ||
| (declared-methods cls) | ||
| (declared-constructors cls))}))) | ||
|
|
||
| (def ^:private default-reflector | ||
| (JavaReflector. (.getContextClassLoader (Thread/currentThread)))) | ||
|
|
||
| (defn- parse-method-descriptor | ||
| [^String md] | ||
| {:parameter-types (vec (map typesym (Type/getArgumentTypes md))) | ||
| :return-type (typesym (Type/getReturnType md))}) | ||
|
|
||
| (defprotocol ClassResolver | ||
| (^InputStream resolve-class [this name] | ||
| "Given a class name, return that typeref's class bytes as an InputStream.")) | ||
|
|
||
| (extend-protocol ClassResolver | ||
| clojure.lang.Fn | ||
| (resolve-class [this typeref] (this typeref)) | ||
|
|
||
| ClassLoader | ||
| (resolve-class [this typeref] | ||
| (.getResourceAsStream this (resource-name typeref)))) | ||
|
|
||
| (deftype AsmReflector [class-resolver] | ||
| Reflector | ||
| (do-reflect [_ typeref] | ||
| (with-open [is (resolve-class class-resolver typeref)] | ||
| (let [class-symbol (typesym typeref) | ||
| r (ClassReader. is) | ||
| result (atom {:bases #{} :flags #{} :members #{}})] | ||
| (.accept | ||
| r | ||
| (proxy | ||
| [ClassVisitor] | ||
| [Opcodes/ASM4] | ||
| (visit [version access name signature superName interfaces] | ||
| (let [flags (parse-flags access :class) | ||
| ;; ignore java.lang.Object on interfaces to match reflection | ||
| superName (if (and (flags :interface) | ||
| (= superName "java/lang/Object")) | ||
| nil | ||
| superName) | ||
| bases (->> (cons superName interfaces) | ||
| (remove nil?) | ||
| (map internal-name->class-symbol) | ||
| (map symbol) | ||
| (set) | ||
| (not-empty))] | ||
| (swap! result merge {:bases bases | ||
| :flags flags}))) | ||
| (visitAnnotation [desc visible]) | ||
| (visitSource [name debug]) | ||
| (visitInnerClass [name outerName innerName access]) | ||
| (visitField [access name desc signature value] | ||
| (swap! result update :members (fnil conj #{}) | ||
| (Field. (symbol name) | ||
| (field-descriptor->class-symbol desc) | ||
| class-symbol | ||
| (parse-flags access :field))) | ||
| nil) | ||
| (visitMethod [access name desc signature exceptions] | ||
| (when-not (= name "<clinit>") | ||
| (let [constructor? (= name "<init>")] | ||
| (swap! result update :members (fnil conj #{}) | ||
| (let [{:keys [parameter-types return-type]} (parse-method-descriptor desc) | ||
| flags (parse-flags access :method)] | ||
| (if constructor? | ||
| (Constructor. class-symbol | ||
| class-symbol | ||
| parameter-types | ||
| (vec (map internal-name->class-symbol exceptions)) | ||
| flags) | ||
| (Method. (symbol name) | ||
| return-type | ||
| class-symbol | ||
| parameter-types | ||
| (vec (map internal-name->class-symbol exceptions)) | ||
| flags)))))) | ||
| nil) | ||
| (visitEnd []) | ||
| ) 0) | ||
| @result)))) | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,289 @@ | ||
| ; Copyright (c) Chris Houser, Dec 2008. All rights reserved. | ||
| ; The use and distribution terms for this software are covered by the | ||
| ; Common Public License 1.0 (http://opensource.org/licenses/cpl.php) | ||
| ; which can be found in the file CPL.TXT at the root of this distribution. | ||
| ; By using this software in any fashion, you are agreeing to be bound by | ||
| ; the terms of this license. | ||
| ; You must not remove this notice, or any other, from this software. | ||
|
|
||
| ; Utilities meant to be used interactively at the REPL | ||
|
|
||
| (ns | ||
| ^{:author "Chris Houser, Christophe Grand, Stephen Gilardi, Michel Salim" | ||
| :doc "Utilities meant to be used interactively at the REPL"} | ||
| clojure.repl | ||
| (:require [clojure.spec.alpha :as spec]) | ||
| (:import (java.io LineNumberReader InputStreamReader PushbackReader) | ||
| (clojure.lang RT Reflector))) | ||
|
|
||
| (def ^:private special-doc-map | ||
| '{. {:url "java_interop#dot" | ||
| :forms [(.instanceMember instance args*) | ||
| (.instanceMember Classname args*) | ||
| (Classname/staticMethod args*) | ||
| Classname/staticField] | ||
| :doc "The instance member form works for both fields and methods. | ||
| They all expand into calls to the dot operator at macroexpansion time."} | ||
| def {:forms [(def symbol doc-string? init?)] | ||
| :doc "Creates and interns a global var with the name | ||
| of symbol in the current namespace (*ns*) or locates such a var if | ||
| it already exists. If init is supplied, it is evaluated, and the | ||
| root binding of the var is set to the resulting value. If init is | ||
| not supplied, the root binding of the var is unaffected."} | ||
| do {:forms [(do exprs*)] | ||
| :doc "Evaluates the expressions in order and returns the value of | ||
| the last. If no expressions are supplied, returns nil."} | ||
| if {:forms [(if test then else?)] | ||
| :doc "Evaluates test. If not the singular values nil or false, | ||
| evaluates and yields then, otherwise, evaluates and yields else. If | ||
| else is not supplied it defaults to nil."} | ||
| monitor-enter {:forms [(monitor-enter x)] | ||
| :doc "Synchronization primitive that should be avoided | ||
| in user code. Use the 'locking' macro."} | ||
| monitor-exit {:forms [(monitor-exit x)] | ||
| :doc "Synchronization primitive that should be avoided | ||
| in user code. Use the 'locking' macro."} | ||
| new {:forms [(Classname. args*) (new Classname args*)] | ||
| :url "java_interop#new" | ||
| :doc "The args, if any, are evaluated from left to right, and | ||
| passed to the constructor of the class named by Classname. The | ||
| constructed object is returned."} | ||
| quote {:forms [(quote form)] | ||
| :doc "Yields the unevaluated form."} | ||
| recur {:forms [(recur exprs*)] | ||
| :doc "Evaluates the exprs in order, then, in parallel, rebinds | ||
| the bindings of the recursion point to the values of the exprs. | ||
| Execution then jumps back to the recursion point, a loop or fn method."} | ||
| set! {:forms[(set! var-symbol expr) | ||
| (set! (. instance-expr instanceFieldName-symbol) expr) | ||
| (set! (. Classname-symbol staticFieldName-symbol) expr)] | ||
| :url "vars#set" | ||
| :doc "Used to set thread-local-bound vars, Java object instance | ||
| fields, and Java class static fields."} | ||
| throw {:forms [(throw expr)] | ||
| :doc "The expr is evaluated and thrown, therefore it should | ||
| yield an instance of some derivee of Throwable."} | ||
| try {:forms [(try expr* catch-clause* finally-clause?)] | ||
| :doc "catch-clause => (catch classname name expr*) | ||
| finally-clause => (finally expr*) | ||
| Catches and handles Java exceptions."} | ||
| var {:forms [(var symbol)] | ||
| :doc "The symbol must resolve to a var, and the Var object | ||
| itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) | ||
|
|
||
| (defn- special-doc [name-symbol] | ||
| (assoc (or (special-doc-map name-symbol) (meta (resolve name-symbol))) | ||
| :name name-symbol | ||
| :special-form true)) | ||
|
|
||
| (defn- namespace-doc [nspace] | ||
| (assoc (meta nspace) :name (ns-name nspace))) | ||
|
|
||
| (defn- print-doc [{n :ns | ||
| nm :name | ||
| :keys [forms arglists special-form doc url macro spec] | ||
| :as m}] | ||
| (println "-------------------------") | ||
| (println (or spec (str (when n (str (ns-name n) "/")) nm))) | ||
| (when forms | ||
| (doseq [f forms] | ||
| (print " ") | ||
| (prn f))) | ||
| (when arglists | ||
| (prn arglists)) | ||
| (cond | ||
| special-form | ||
| (println "Special Form") | ||
| macro | ||
| (println "Macro") | ||
| spec | ||
| (println "Spec")) | ||
| (when doc (println " " doc)) | ||
| (when special-form | ||
| (if (contains? m :url) | ||
| (when url | ||
| (println (str "\n Please see http://clojure.org/" url))) | ||
| (println (str "\n Please see http://clojure.org/special_forms#" nm)))) | ||
| (when n | ||
| (when-let [fnspec (spec/get-spec (symbol (str (ns-name n)) (name nm)))] | ||
| (println "Spec") | ||
| (doseq [role [:args :ret :fn]] | ||
| (when-let [spec (get fnspec role)] | ||
| (println " " (str (name role) ":") (spec/describe spec))))))) | ||
|
|
||
| (defn find-doc | ||
| "Prints documentation for any var whose documentation or name | ||
| contains a match for re-string-or-pattern" | ||
| {:added "1.0"} | ||
| [re-string-or-pattern] | ||
| (let [re (re-pattern re-string-or-pattern) | ||
| ms (concat (mapcat #(sort-by :name (map meta (vals (ns-interns %)))) | ||
| (all-ns)) | ||
| (map namespace-doc (all-ns)) | ||
| (map special-doc (keys special-doc-map)))] | ||
| (doseq [m ms | ||
| :when (and (:doc m) | ||
| (or (re-find (re-matcher re (:doc m))) | ||
| (re-find (re-matcher re (str (:name m))))))] | ||
| (print-doc m)))) | ||
|
|
||
| (defmacro doc | ||
| "Prints documentation for a var or special form given its name, | ||
| or for a spec if given a keyword" | ||
| {:added "1.0"} | ||
| [name] | ||
| (if-let [special-name ('{& fn catch try finally try} name)] | ||
| `(#'print-doc (#'special-doc '~special-name)) | ||
| (cond | ||
| (special-doc-map name) `(#'print-doc (#'special-doc '~name)) | ||
| (keyword? name) `(#'print-doc {:spec '~name :doc '~(spec/describe name)}) | ||
| (find-ns name) `(#'print-doc (#'namespace-doc (find-ns '~name))) | ||
| (resolve name) `(#'print-doc (meta (var ~name)))))) | ||
|
|
||
| ;; ---------------------------------------------------------------------- | ||
| ;; Examine Clojure functions (Vars, really) | ||
|
|
||
| (defn source-fn | ||
| "Returns a string of the source code for the given symbol, if it can | ||
| find it. This requires that the symbol resolve to a Var defined in | ||
| a namespace for which the .clj is in the classpath. Returns nil if | ||
| it can't find the source. For most REPL usage, 'source' is more | ||
| convenient. | ||
| Example: (source-fn 'filter)" | ||
| [x] | ||
| (when-let [v (resolve x)] | ||
| (when-let [filepath (:file (meta v))] | ||
| (when-let [strm (.getResourceAsStream (RT/baseLoader) filepath)] | ||
| (with-open [rdr (LineNumberReader. (InputStreamReader. strm))] | ||
| (dotimes [_ (dec (:line (meta v)))] (.readLine rdr)) | ||
| (let [text (StringBuilder.) | ||
| pbr (proxy [PushbackReader] [rdr] | ||
| (read [] (let [i (proxy-super read)] | ||
| (.append text (char i)) | ||
| i))) | ||
| read-opts (if (.endsWith ^String filepath "cljc") {:read-cond :allow} {})] | ||
| (if (= :unknown *read-eval*) | ||
| (throw (IllegalStateException. "Unable to read source while *read-eval* is :unknown.")) | ||
| (read read-opts (PushbackReader. pbr))) | ||
| (str text))))))) | ||
|
|
||
| (defmacro source | ||
| "Prints the source code for the given symbol, if it can find it. | ||
| This requires that the symbol resolve to a Var defined in a | ||
| namespace for which the .clj is in the classpath. | ||
| Example: (source filter)" | ||
| [n] | ||
| `(println (or (source-fn '~n) (str "Source not found")))) | ||
|
|
||
| (defn apropos | ||
| "Given a regular expression or stringable thing, return a seq of all | ||
| public definitions in all currently-loaded namespaces that match the | ||
| str-or-pattern." | ||
| [str-or-pattern] | ||
| (let [matches? (if (instance? java.util.regex.Pattern str-or-pattern) | ||
| #(re-find str-or-pattern (str %)) | ||
| #(.contains (str %) (str str-or-pattern)))] | ||
| (sort (mapcat (fn [ns] | ||
| (let [ns-name (str ns)] | ||
| (map #(symbol ns-name (str %)) | ||
| (filter matches? (keys (ns-publics ns)))))) | ||
| (all-ns))))) | ||
|
|
||
| (defn dir-fn | ||
| "Returns a sorted seq of symbols naming public vars in | ||
| a namespace or namespace alias. Looks for aliases in *ns*" | ||
| [ns] | ||
| (sort (map first (ns-publics (the-ns (get (ns-aliases *ns*) ns ns)))))) | ||
|
|
||
| (defmacro dir | ||
| "Prints a sorted directory of public vars in a namespace" | ||
| [nsname] | ||
| `(doseq [v# (dir-fn '~nsname)] | ||
| (println v#))) | ||
|
|
||
| (defn demunge | ||
| "Given a string representation of a fn class, | ||
| as in a stack trace element, returns a readable version." | ||
| {:added "1.3"} | ||
| [fn-name] | ||
| (clojure.lang.Compiler/demunge fn-name)) | ||
|
|
||
| (defn root-cause | ||
| "Returns the initial cause of an exception or error by peeling off all of | ||
| its wrappers" | ||
| {:added "1.3"} | ||
| [^Throwable t] | ||
| (loop [cause t] | ||
| (if (and (instance? clojure.lang.Compiler$CompilerException cause) | ||
| (not= (.source ^clojure.lang.Compiler$CompilerException cause) "NO_SOURCE_FILE")) | ||
| cause | ||
| (if-let [cause (.getCause cause)] | ||
| (recur cause) | ||
| cause)))) | ||
|
|
||
| (defn stack-element-str | ||
| "Returns a (possibly unmunged) string representation of a StackTraceElement" | ||
| {:added "1.3"} | ||
| [^StackTraceElement el] | ||
| (let [file (.getFileName el) | ||
| clojure-fn? (and file (or (.endsWith file ".clj") | ||
| (.endsWith file ".cljc") | ||
| (= file "NO_SOURCE_FILE")))] | ||
| (str (if clojure-fn? | ||
| (demunge (.getClassName el)) | ||
| (str (.getClassName el) "." (.getMethodName el))) | ||
| " (" (.getFileName el) ":" (.getLineNumber el) ")"))) | ||
|
|
||
| (defn pst | ||
| "Prints a stack trace of the exception, to the depth requested. If none supplied, uses the root cause of the | ||
| most recent repl exception (*e), and a depth of 12." | ||
| {:added "1.3"} | ||
| ([] (pst 12)) | ||
| ([e-or-depth] | ||
| (if (instance? Throwable e-or-depth) | ||
| (pst e-or-depth 12) | ||
| (when-let [e *e] | ||
| (pst (root-cause e) e-or-depth)))) | ||
| ([^Throwable e depth] | ||
| (binding [*out* *err*] | ||
| (when (#{:read-source :macro-syntax-check :macroexpansion :compile-syntax-check :compilation} | ||
| (-> e ex-data :clojure.error/phase)) | ||
| (println "Note: The following stack trace applies to the reader or compiler, your code was not executed.")) | ||
| (println (str (-> e class .getSimpleName) " " | ||
| (.getMessage e) | ||
| (when-let [info (ex-data e)] (str " " (pr-str info))))) | ||
| (let [st (.getStackTrace e) | ||
| cause (.getCause e)] | ||
| (doseq [el (take depth | ||
| (remove #(#{"clojure.lang.RestFn" "clojure.lang.AFn"} (.getClassName %)) | ||
| st))] | ||
| (println (str \tab (stack-element-str el)))) | ||
| (when cause | ||
| (println "Caused by:") | ||
| (pst cause (min depth | ||
| (+ 2 (- (count (.getStackTrace cause)) | ||
| (count st)))))))))) | ||
|
|
||
| ;; ---------------------------------------------------------------------- | ||
| ;; Handle Ctrl-C keystrokes | ||
|
|
||
| (defn thread-stopper | ||
| "Returns a function that takes one arg and uses that as an exception message | ||
| to stop the given thread. Defaults to the current thread" | ||
| ([] (thread-stopper (Thread/currentThread))) | ||
| ([thread] (fn [msg] (.stop thread (Error. msg))))) | ||
|
|
||
| (defn set-break-handler! | ||
| "Register INT signal handler. After calling this, Ctrl-C will cause | ||
| the given function f to be called with a single argument, the signal. | ||
| Uses thread-stopper if no function given." | ||
| ([] (set-break-handler! (thread-stopper))) | ||
| ([f] | ||
| (sun.misc.Signal/handle | ||
| (sun.misc.Signal. "INT") | ||
| (proxy [sun.misc.SignalHandler] [] | ||
| (handle [signal] | ||
| (f (str "-- caught signal " signal))))))) |