lucid.core functions for the code environment

Author: Chris Zheng  (z@caudate.me)
Date: 29 June 2017
Repository: https://www.github.com/zcaudate/lucidity
Version: 1.3.13

lucid.core provides utilities that either support the rest of the lucidity suite or are useful standalone tools by themselves. Each one is installed individually and usually only provides one or two top level function for use:

1    core.asm

lucid.core.asm allows exploration of classes on the filesystem, independent of the JVM classloader.

Add to project.clj:

[im.chit/lucid.core.asm "1.3.13"]

All functionality is in the lucid.core.asm namespace:

(use 'lucid.core.asm)


dynamic-loader ^

returns the clojure runtime classloader

v 1.1
(defn dynamic-loader
  []
  (clojure.lang.DynamicClassLoader. classloader/+rt+))
link
(dynamic-loader) => #(instance? clojure.lang.DynamicClassLoader %)

load-class ^

loads class from an external source

v 1.1
(defmulti load-class
  (fn [x & args] (type x)))
link
(load-class "target/classes/test/Cat.class") => test.Cat (load-class "<.m2>/org/yaml/snakeyaml/1.5/snakeyaml-1.5.jar" "org/yaml/snakeyaml/Dumper.class") => org.yaml.snakeyaml.Dumper (load-class '[org.yaml/snakeyaml "1.5"] "org/yaml/snakeyaml/Dumper.class") => org.yaml.snakeyaml.Dumper

to-bytes ^

opens `.class` file from an external source

v 1.1
(defmulti to-bytes
  (fn [x] (type x)))
link
(to-bytes "target/classes/test/Dog.class") => checks/bytes?

unload-class ^

unloads a class from the current namespace

v 1.1
(defn unload-class
  [name]
  (clojure.lang.Util/clearCache *rq* *class-cache*)
  (.remove *class-cache* name))
link
(unload-class "test.Cat") ;; #object[java.lang.ref.SoftReference 0x10074132 ;; "java.lang.ref.SoftReference@10074132"]

2    core.code

lucid.core.code is analyses source and test code and is used by lucid.publish and lucid.unit to build additional functionality.

Add to project.clj:

[im.chit/lucid.core.code "1.3.13"]

All functionality is in the lucid.core.code namespace:

(use 'lucid.core.code)


analyse-file ^

analyses a source or test file for information

v 1.2
(defn analyse-file
  ([path]
   (analyse-file-fn* path (-> (fs/path path)
                              fs/attributes
                              :last-modified-time)))
  ([type path]
   (analyse-file-fn* type path (-> (fs/path path)
                                   fs/attributes
                                   :last-modified-time))))
link
(analyse-file "src/lucid/core/code.clj") => (contains-in {'lucid.core.code {'analyse-file {:source {:code string?, :line {:row number? :col number? :end-row number? :end-col number?}, :path "src/lucid/core/code.clj"}}}}) (analyse-file "test/lucid/core/code_test.clj") => (contains-in {'lucid.core.code {'analyse-file {:test {:code vector? :line {:row number? :col number? :end-row number? :end-col number?} :path "test/lucid/core/code_test.clj"}, :meta {:added "1.2"}, :intro "analyses a source or test file for information"}}})

3    core.debug

lucid.core.debug contains macros and helpers for debugging

Add to project.clj:

[im.chit/lucid.core.debug "1.3.13"]

All functionality is in the lucid.core.debug namespace:

(use 'lucid.core.debug)


dbg-> ^

prints each stage of the `->` macro

v 1.1
(defmacro dbg->
  [n & funcs]
  (let [wfncs (map #(dbg-print % '->) funcs)]
    `(do (println "n")
         (println ~n)
         (-> ~n ~@wfncs))))
link
(-> (dbg-> {:a 1} (assoc :b 2) (merge {:c 3})) (with-out-str) (string/split-lines)) => ["" "" "{:a 1}" "-> (assoc :b 2) :: {:a 1, :b 2}" "-> (merge {:c 3}) :: {:a 1, :b 2, :c 3}"]

dbg->> ^

prints each stage of the `->>` macro

v 1.1
(defmacro dbg->>
  [n & funcs]
  (let [wfncs (map #(dbg-print % '->>) funcs)]
    `(do (println "n")
         (println ~n)
         (->> ~n ~@wfncs))))
link
(-> (dbg->> (range 5) (map inc) (take 2)) (with-out-str) (string/split-lines)) => ["" "" "(0 1 2 3 4)" "->> (map inc) :: (1 2 3 4 5)" "->> (take 2) :: (1 2)"]

->doto ^

used to perform side-effects within a `->` macro

v 1.2
(defmacro ->doto
  [x & forms]
  `(do (-> ~x ~@forms)
       ~x))
link
(-> {:a 1} (->doto (update-in [:a] inc) print) (assoc :b 2)) ;; {:a 2} => {:a 1, :b 2}

->>doto ^

used to perform side effects within a `->>` macro

v 1.2
(defmacro ->>doto
  [& forms]
  (let [[x forms] [(last forms) (butlast forms)]]
    `(do (->> ~x ~@forms)
         ~x)))
link
(->> [1 2 3] (->>doto (map inc) print) (cons 0)) ;; (2 3 4) => [0 1 2 3]

->prn ^

used to print within the macro

v 1.2
(defmacro ->prn
  ([x] `(->prn ~x nil))
    ([x tag]
     `(do (if ~tag
            (print (str ~tag " ")))
          (prn ~x)
          ~x)))
link
(-> [1 2 3] (->prn) (conj 4)) ;; [1 2 3] => [1 2 3 4]

4    core.inject

lucid.core.inject' is used to create extra symbols in namespaces. It has been quite popular due this article.

Add to project.clj dependencies:

[im.chit/lucid.core.inject "1.3.13"]

All functionality is in the lucid.core.inject namespace:

(use 'lucid.core.inject)


in ^

takes a list of injections and output the created vars

v 1.1
(defmacro in
  [& args]
  (cons 'vector
        (mapcat inject-row
                (inject-split-args args))))
link
(in c (clojure.repl apropos source doc) d (clojure.repl apropos source doc) e (clojure.repl apropos source doc)) => [#'c/apropos #'c/source #'c/doc #'d/apropos #'d/source #'d/doc #'e/apropos #'e/source #'e/doc]

inject ^

takes a list of injections and output the created vars

v 1.1
(defn inject
  [& args]
  (->> args
       (mapcat inject-split-args)
       (mapcat inject-row)
       vec))
link
(inject '[(clojure.repl apropos source doc) b (clojure.repl apropos source doc)]) => [#'./apropos #'./source #'./doc #'b/apropos #'b/source #'b/doc]

inject enables both macros and functions to be imported:

(inject '[clojure.core [clojure.repl dir]])
=> [#'clojure.core/dir]

(dir clojure.repl)
;; apropos
;; demunge
;; dir
;; dir-fn
;; doc
;; find-doc
;; pst
;; root-cause
;; set-break-handler!
;; source
;; source-fn
;; stack-element-str
;; thread-stopper

This function is extremely useful when adding additional functionality that is needed which is not included in clojure.core. It can be used to import both macros and funcions into a given namespace:

The macro inject/in enables better support:

;; the default injected namespace is `.`
(inject/in
 
 ;; note that `:refer, :all and :exclude can be used
 [lucid.core.inject :refer [inject [in inject-in]]]
 
 ;; imports all functions from lucid.package
 [lucid.package]
 
 ;; inject into clojure.core
 clojure.core
 [lucid.mind .> .? .* .% .%> .& .>ns .>var]
             
 ;; inject into `>` namespace
 >
 [clojure.pprint pprint]
 [clojure.java.shell sh])
  
=> [#'./inject
    #'./inject-in
    #'./pull
    #'./resolve-coordinates
    #'./jar-entry
    #'./add-url
    #'./resolve-jar
    #'./resolve-with-dependencies
    #'./maven-file
    #'./coordinate
    #'clojure.core/.>
    #'clojure.core/.?
    #'clojure.core/.*
    #'clojure.core/.%
    #'clojure.core/.%>
    #'clojure.core/.&
    #'clojure.core/.>ns
    #'clojure.core/.>var
    #'>/pprint
    #'>/sh]

5    core.java

lucid.core.java is used to work with mixed java and clojure projects

Add to project.clj dependencies:

[im.chit/lucid.core.java "1.3.13"]

All functionality is in the lucid.core.java namespace:

(use 'lucid.core.java)


java-sources ^

lists source classes in a project

v 1.2
(defn java-sources
  [{dirs :java-source-paths}]
  (->> (mapcat (fn [dir]
                 (->> (fs/select dir {:include [".java"]})
                      (map (juxt #(->> %
                                       (fs/relativize (fs/path dir))
                                       path->class)
                                 identity))))
               dirs)
       (into {})))
link
(-> (java-sources (project/project)) (keys) (sort)) => '[test.Cat test.Dog test.DogBuilder test.Person test.PersonBuilder test.Pet]

javac ^

compiles classes using the built-in compiler

v 1.2
(defn javac
  [& classes]
  (let [proj      (project/project)
        sources   (java-sources proj)
        compiler  (ToolProvider/getSystemJavaCompiler)
        collector (DiagnosticCollector.)
        manager   (.getStandardFileManager compiler collector nil nil)
        arr       (->> (keep sources classes)
                       (map #(.toFile %))
                       (into-array)
                       (Arrays/asList)
                       (.getJavaFileObjectsFromFiles manager))]
    (.call (.getTask compiler
                     *err*
                     manager
                     collector
                     (Arrays/asList (make-array String 0))
                     nil
                     arr))
    (javac-output collector)
    (->> (:java-source-paths proj)
         (map (fn [dir]
                (->> (fs/move dir "target/classes" {:include [".class$"]})
                     (reduce-kv (fn [total in target]
                                  (assoc total
                                         (path->class (fs/relativize dir in))
                                         target))
                                {}))))
         (apply merge))))
link
(javac 'test.Cat 'test.Dog) ;;=> outputs `.class` files in target directory

reimport ^

compiles and reimports java source code dynamically

v 1.2
(defn reimport
  [& classes]
  (for [[cls file] (apply javac classes)]
    (do (asm/unload-class (str cls))
        (asm/load-class file))))
link
(reimport 'test.Cat 'test.Dog) ;;=> (test.Cat test.Dog)

6    core.namespace

lucid.core.namespace provides additional namespace utilities.

Add to project.clj dependencies:

[im.chit/lucid.core.namespace "1.3.13"]

All functionality is in the lucid.core.namespace namespace:

(use 'lucid.core.namespace)


clear-aliases ^

removes all namespace aliases

v 1.2
(defn clear-aliases
  ([] (clear-aliases (.getName *ns*)))
  ([ns]
   (doseq [alias (keys (ns-aliases ns))]
     (ns-unalias ns alias))))
link
;; require clojure.string (require '[clojure.string :as string]) => nil ;; error if a new namespace is set to the same alias (require '[clojure.set :as string]) => (throws) ;; Alias string already exists in namespace ;; clearing all aliases (clear-aliases) (ns-aliases *ns*) => {} ;; okay to require (require '[clojure.set :as string]) => nil

clear-mappings ^

removes all mapped vars in the namespace

v 1.2
(defn clear-mappings
  ([] (clear-mappings (.getName *ns*)))
  ([ns]
   (doseq [func (keys (ns-map ns))]
     (ns-unmap ns func))))
link
;; require `join` (require '[clojure.string :refer [join]]) ;; check that it runs (join ["a" "b" "c"]) => "abc" ;; clear mappings (clear-mappings) ;; the mapped symbol is gone (join ["a" "b" "c"]) => (throws) ;; "Unable to resolve symbol: join in this context"

run ^

runs a function, automatically loading it if not loaded

v 1.2
(defmacro run
  [func & args]
  (let [f (when-let [nsp (namespace func)]
            (require (symbol nsp))
            (resolve func))]
    (if f
      `(~func ~@args)
      :function-not-loaded)))
link
(ns/run clojure.core/apply + 1 [2 3 4]) => 10 (ns/run wrong-function + 1 [2 3 4]) => :function-not-loaded