Call functions read from EDN files - clojure

I have an EDN configuration file in which the entries refer to existing functions, e.g.:
:attribute-modules {:content {:class lohan.extractors.content/process}
:schema {:class lohan.extractors.schema/process}
:label {:class lohan.extractors.label/process}
:user {:class lohan.extractors.user/process}
:env {:class lohan.extractors.env/process}}
Using clojure.edn/read-edn these entries are read as Symbols, but I want to be able to call them at runtime. The purpose of this is to provide a way for the user to supply his own set of functions.
How can I achieve this?

You can invoke a function held in a var referenced by a Symbol by using resolve.
For example, if you wanted to invoke + by using its Symbol you can use:
((resolve '+) 1 2)
;=> 3
Therefore, using your example you can do:
((resolve (get-in (clojure.edn/read-string "{:content {:class ohan.extractors.content/process}
:schema {:class lohan.extractors.schema/process}
:label {:class lohan.extractors.label/process}
:user {:class lohan.extractors.user/process}
:env {:class lohan.extractors.env/process}}")
[:content :class])))
You would either need to limit the set of allowed symbols accessible to users or have a high level of trust in the users that are providing the edn in order to prevent them from executing any function in the running environment that you do not wish them to have access to.

Related

Accessing value from defn

I need advice,
I try to make function :
(def user-map [new-name new-phone new-email]
{:name new-name
:phone new-phone
:email new-email})
With new-name, new-phone, new-email are user input. But when i try to compile it, it says too many arguments to def, after change def to defn, when i try to execute user-map in REPL i get something like
#<temp_atom$user_address zenedu.mm.dbase.temp_atom$user_address#714924b5
instead of actual map.
I need to get to the map, any advice?
It sounds like perhaps you are conceptually combining the value that will be returned from calling user-map as a function with some arguments and evaluating the symbol user-map on its own.
Evaluating
(user-map "me" "123456789" "me#here.com")
Which will return a map, by looking up the var user-map in the current namespace and calling the function stored in that var with these arguments. Where evaluating just
user-map
Will simply look up the var user-map in the current namespace and return the contents of that var, which in the case where you used defn, will be the function it's self. The REPL then prints the object I'd of that function.
In your use case, you need defn to define a builder (like a constructor in Java) for the object you want. The log
#<temp_atom$user_address zenedu.mm.dbase.temp_atom$user_address#714924b5
suggests that you are using another structure user-address somewhere in the application and it looks like there is confusion between a user-map object and this user-address.
Anyway, you may be interested to have a look at defrecord that provides a convenient way to build objects with a constructor (and potentially other functions related to this object), e.g.
(defrecord user [name phone email])
defrecord provides 2 constructors ->user and map->user:
(def me (->user "abb" "0102030405" "abb#mail.com"))
(def you (map->user {:email "das#mail.com" :phone "9090909090" :name "das"}))
And you can access the properties of a user through keywords exactly like a map:
user> (:name me)
"abb"
user> (:phone you)
"9090909090"
OK, you should use defn instead of def.
But what information really varies here? The number and order of the map keys, in this case [:name :phone :email].
A generic function that will build - from a key sequence - a function that will build the map from the value sequence is
(defn map-builder [keys]
(fn [& values] (zipmap keys values)))
You can then define
(def user-map (map-builder [:name :phone :email]))
... which works as required:
(user-map "me" "123456789" "me#here.com")
;{:email "me#here.com", :phone "123456789", :name "me"}
If performance is pressing, by all means use records instead of maps, as #AbbéRésina suggests.
Putting it simply...
The error you are receiving is due to essentially passing a vector as a second value to def. If you want to use def in this instance go with...
(def user-map-other
(fn [new-name new-phone new-email]
{:name new-name
:phone new-phone
:email new-email}))
Here we are using an anonymous function that accepts your three parameters. Here is a link to learn more about them => http://clojuredocs.org/clojure.core/fn
To gain access to the values contained in your function we can use get in this instance.
(get (user-map-other "Ben" "999" "bbb#mail.com") :phone) => "999"
(get (user-map-other "Ben" "999" "bbb#mail.com") :name) => "Ben"
(get (user-map-other "Ben" "999" "bbb#mail.com") :email) => "bbb#mail.com"
A more concise method would be to use defn as represented below.
(defn user-map [new-name new-phone new-email]
{:name new-name
:phone new-phone
:email new-email})
(get (user-map "Ben" "999" "bbb#mail.com") :phone) => "999"
(get (user-map "Ben" "999" "bbb#mail.com") :name) => "Ben"
(get (user-map "Ben" "999" "bbb#mail.com") :email) => "bbb#mail.com"

Check if URL parameter exists in map

(defroutes my-routes
(GET "/:id" [id] (html/display-thing id)))
(def my-map
{:id 1 :title "One"
:id 2 :title "Two"})
Is there a nice way to check if the url parameter id exists in my-map else continue checking if the other routes match? I know you can do something similar with regex like so: ["/:id", :id #"[0-9]+"] and suspect it might be possible to plug in an arbitrary predicate function.
Not actually at a REPL, but isn't this as straightforward as returning nil from html/display-thing if there's no id element in my-map? Take a look at (source GET) to see how the macro passes control to the next route if the method or URL don't match.

How to lookup all vars containing specific metadata

Suppose I've added specific metadata to my vars:
(defn ^:run-at-startup init []
(prn "Initializing...")
:done)
(meta (var init))
; {:arglists ([]), :ns #<Namespace user>, :name init, :end-column 34,
; :run-at-startup true, :column 1, :line 5, :file "NO_SOURCE_FILE", :end-line 5}
Then I would like to lookup all the vars (across different namespaces) that contains it. Is it possible?
Here is why. My app consists of several modules that must be initialized at startup. New modules could be added and existing removed (not at runtime, of course), and it's initializers must be called without knowing any specifics of the module. I think of adding metadata to initializers, then looking it all up and calling.
I would like to know if there are better ways.
So, if you require all the namespaces that contain your non-private initializers, all-ns is able to retrieve a list of those namespaces. If you do not know what namespaces exist, you can probably use e.g. tools.namespace to find out.
The following function finds all vars that contain a certain metadata key set to true, returning a seq of the vars' values.
(defn find-by-var-meta
[metadata-flag]
(->> (all-ns)
(mapcat ns-publics)
(keep
(fn [[_ v]]
(when (-> v meta metadata-flag)
(var-get v))))))
The resulting seq can then be traversed and everything that is a function can be called. So, in your case this should look like this:
(require '[my.namespace.initializers a b c])
(find-by-var-meta :run-at-startup) ;; => seq of initializers from the above ns.
And a quick check in the REPL:
(defn ^:run-at-startup add-one [x] (inc x)) ;; => #'user/add-one
((first (find-by-var-meta :run-at-startup)) 5) ;; => 6
(As seen here, you also don't need to specify a full map for metadata if you only want to set a key - or multiple ones - to true.)

Clojure, using Enlive to extract raw HTML from a selector?

I need to retrieve some some raw HTML from a certain part of an HTML page.
I wrote the scraper and it grabs the appropriate div, but it returns a map of tags.
(:use [net.cgrand.enlive-html :as html])
(defn fetch-url [url]
(html/html-resource (java.net.URL. url)))
(defn parse-test []
(let [url "http://www.ncbi.nlm.nih.gov/pubmedhealth/PMH0000928/"
url-data (fetch-url url)
id "a693025"]
(first (html/select url-data [(keyword (str "#" id "-why"))]))))
This outputs:
{:tag :div, :attrs {:class "section", :id "a693025-why"}, :content ({:tag :h2, :attrs nil, :content ({:tag :span, :attrs {:class "title"}, :content ("Why is this medication prescribed?")})} {:tag :p, :attrs nil, :content ("Zolpidem is used to treat insomnia (difficulty falling asleep or staying asleep). Zolpidem belongs to a class of medications called sedative-hypnotics. It works by slowing activity in the brain to allow sleep.")})}
How do I convert this to raw html? I couldn't find any enlive function to do this.
(apply str (html/emit* [(parse-test)]))
; => "<div class=\"section\" id=\"a693025-why\"><h2><span class=\"title\">Why is this medication prescribed?</span></h2><p>Zolpidem is used to treat insomnia (difficulty falling asleep or staying asleep). Zolpidem belongs to a class of medications called sedative-hypnotics. It works by slowing activity in the brain to allow sleep.</p></div>"

How to make a record from a sequence of values

I have a simple record definition, for example
(defrecord User [name email place])
What is the best way to make a record having it's values in a sequence
(def my-values ["John" "john#example.com" "Dreamland"])
I hoped for something like
(apply User. my-values)
but that won't work. I ended up doing:
(defn make-user [v]
(User. (nth v 0) (nth v 1) (nth v 2)))
But I'm sensing there is some better way for achieving this...
Warning: works only for literal sequables! (see Mihał's comment)
Try this macro:
(defmacro instantiate [klass values]
`(new ~klass ~#values))
If you expand it with:
(macroexpand '(instantiate User ["John" "john#example.com" "Dreamland"]))
you'll get this:
(new User "John" "john#example.com" "Dreamland")
which is basically what you need.
And you can use it for instantiating other record types, or Java classes. Basically, this is just a class constructor that takes a one sequence of parameters instead of many parameters.
the defrecord function creates a compiled class with some immutable fields in it. it's not a proper clojure functions (ie: not a class that implements iFn). If you want to call it's constructor with apply (which expects an iFun) you need to wrap it in an anonymous function so apply will be able to digest it.
(apply #(User. %1 %2 %3 %4) my-values)
it's closer to what you started with though your approach of defining a constructor with a good descriptive name has its own charm :)
from the API:
Note that method bodies are
not closures, the local environment includes only the named fields,
and those fields can be accessed directy.
Writing your own constructor function is probably the way to go. As Arthur Ulfeldt said, you then have a function you can use as a function (e.g. with apply) rather than a Java-interop constructor call.
With your own constructor function you can also do argument validation or supply default arguments. You gain another level of abstraction to work with; you can define make-user to return a hash-map for quick development, and if you later decide to change to records, you can do so without breaking everything. You can write constructors with multiple arities, or that take keyword arguments, or do any number of other things.
(defn- default-user [name]
(str (.toLowerCase name) "#example.com"))
(defn make-user
([name] (make-user name nil nil))
([name place] (make-user name nil place))
([name user place]
(when-not name
(throw (Exception. "Required argument `name` missing/empty.")))
(let [user (or user (default-user name))]
(User. name user place))))
(defn make-user-keyword-args [& {:keys [name user place]}]
(make-user name user place))
(defn make-user-from-hashmap [args]
(apply make-user (map args [:name :user :place])))
user> (apply make-user ["John" "john#example.com" "Somewhere"])
#:user.User{:name "John", :email "john#example.com", :place "Somewhere"}
user> (make-user "John")
#:user.User{:name "John", :email "john#example.com", :place nil}
user> (make-user-keyword-args :place "Somewhere" :name "John")
#:user.User{:name "John", :email "john#example.com", :place "Somewhere"}
user> (make-user-from-hashmap {:user "foo"})
; Evaluation aborted.
; java.lang.Exception: Required argument `name` missing/empty.
One simple thing you can do is to make use of destructuring.
(defn make-user [[name email place]]
(User. name email place))
Then you can just call it like this
(make-user ["John" "John#example.com" "Dreamland"])
Update for Clojure 1.4
defrecord now defines ->User and map->User thus following in Goran's footstaps, one can now
(defmacro instantiate [rec args] `(apply ~(symbol (str "->" rec)) ~args))
which also works with non-literal sequences as in (instantiate User my-values).
Alternatively, along the lines of map->User one can define a function seq->User
(defmacro def-seq-> [rec] `(defn ~(symbol (str "seq->" rec)) [arg#] (apply ~(symbol (str "->" rec)) arg#)))
(def-seq-> User)
which will allow (seq->User my-values).
The idiomatic way to call a Record constructor is with the Clojure symbol ->MyRecord and that works just fine with apply.
(def my-values ["John" "john#example.com" "Dreamland"])
(defrecord User [name email place])
(apply ->User my-values)
; => #my-ns.User{:name "John",
:email "john#example.com",
:place "Dreamland"}