user2219372
user2219372

Reputation: 2455

How to dynamicly include all source files in a folder with clojure?

I define a map of functions:

(ns fs
  (:require [folder/a :as a]
            [folder/b :as b]
            [folder/c :as c]) 

(def functions {:a  a/f :b b/f :c c/f})

(doseq [[_ f] functions] (f))

Now I want to add more namespaces within the folder, and I don't want to modify the above code. How can functions be dynamically populated with the f from each namespace in a folder.

Upvotes: 1

Views: 89

Answers (1)

user2609980
user2609980

Reputation: 10514

First some helpers:

(defn directory
  "Get directory from path"
  [path]
  (clojure.java.io/file path))

(defn file-names
  "Get file names of the files in the directory."
  [files]
  (map (fn [file] (.getName file)) files))

(defn namespace
  "Remove the extension from the file name and prefix with folder-name"
  [folder-name file-name]
  (->> (clojure.string/split file-name #"\.")
       (butlast)
       (apply str)
       (str folder-name ".")
       (symbol)))

Then retrieve all namespaces from your folder, you need the path and the folder name:

(def namespaces
  (let [names (->> "/path/to/your/folder-name"
                   directory
                   file-seq ;; Gets the tree structure of the directory
                   rest ;; Get rid of the the directory name
                   file-names)]
    (map (partial namespace "folder-name") names)))

Next get the public functions in every namespace via ns-publics:

(def functions
  (->> namespaces
       (map (juxt keyword (comp vals ns-publics)))
       (into {})))

;; => prints {:namespace-key '(fn-a fn-b) :namespace-key2 '(fn-b fn-c)}

Note that this gets a list of all the public functions in a namesapce after the namespace keys.

We can execute the functions as follows:

(doseq [[_ ns-fns] functions
        f ns-fns] (f))

Of course, this only works for functions that have an arity of zero. Otherwise you have to pass arguments to f.

Upvotes: 1

Related Questions