dependenciesorg.clojure/clojure |
| 1.2.0 | ring/ring-jetty-adapter |
| 0.3.6 | necessary-evil |
| 1.0.0-SNAPSHOT |
dev dependenciesswank-clojure |
| 1.3.0-SNAPSHOT | marginalia |
| 0.5.0 |
|
(this space intentionally left almost blank) |
| |
| |
RobotFramework XML-RPC Remote Server in Clojure
This XML-RPC server is designed to be used with RobotFramework (RF), to
allow developers to write RF keywords in Clojure.
If you use Leiningen, just run lein run to use the example keyword
library included in the robot-remote-server.keyword namespace.
Otherwise, (:use) the robot-remote-server.core namespace in your own
namespace containing RF keywords and add (server-start! (init-handler))
to start the remote server.
You can pass a map of options to (server-start!) like you would to
(run-jetty) . To stop the server, use (server-stop!) or send
:stop_remote_server via RPC.
Because RF sends requests to the /RPC2 path, that has been enforced for this
server using the wrap-rpc middleware defined in this namespace.
| | (ns robot-remote-server.core
(:require [necessary-evil.core :as xml-rpc]
[clojure.string :as str])
(:import org.mortbay.jetty.Server)
(:use [robot-remote-server keyword]
ring.adapter.jetty))
| | (defonce *robot-remote-server* (atom nil))
Given a namespace and a fn-name as string, return the function in that namespace by that name
| | (defn find-kw-fn
[a-ns fn-name]
(ns-resolve a-ns (symbol fn-name)))
Make it nicer for Clojure developers to write keywords; replace underscores with dashes
| | (defn clojurify-name
[s]
(str/replace s "_" "-"))
Ring middleware to limit server's response to the particular path that RobotFramework petitions
| | (defn wrap-rpc
[handler]
(fn [req]
(when (= "/RPC2" (:uri req))
(handler req))))
Get arguments for a given RF keyword function identified by the string kw-name and located in the a-ns namespace
| | (defn get-keyword-arguments*
[a-ns kw-name]
(let [clj-kw-name (clojurify-name kw-name)
a-fn (find-kw-fn a-ns clj-kw-name)]
(vec (map str (last (:arglists (meta a-fn)))))))
Get documentation string for a given RF keyword function identified by the string kw-name and located in the a-ns namespace
| | (defn get-keyword-documentation*
[a-ns kw-name]
(let [clj-kw-name (clojurify-name kw-name)
a-fn (find-kw-fn a-ns clj-kw-name)]
(:doc (meta a-fn))))
Get a list of RF keyword functions located in the a-ns namespace
Given a RF-formatted string representation of a Clojure function kw-name in the a-ns namespace called with args as a vector, evaluate the function
| | (defn get-keyword-names*
[a-ns]
(vec
(map #(str/replace % "-" "_") ; RF expects underscores
(remove #(or (re-find #"(\*|!)" %) (re-find #"^-" %)) ; non-keyword functions
(map str
(map first (ns-publics a-ns)))))))
(defn run-keyword*
[a-ns kw-name args]
(let [result {:status "PASS", ; RF expects this map
:return "",
:output "",
:error "",
:traceback ""}
clj-kw-name (clojurify-name kw-name) ; translate RF keyword to Clojure fn
a-fn (find-kw-fn a-ns clj-kw-name)
output (with-out-str (try
(apply a-fn args)
(catch Exception e
(assoc result
:status "FAIL"
:error (with-out-str (prn e))
:traceback (with-out-str (.printStackTrace e))))))]
(assoc result :output output :return output)))
WARNING: Less-than-functional code follows
Use of *robot-remote-server* inside the init-handler macro and in the two
functions that follow. This has been done so that the XML-RPC server can offer the
stop_remote_server command if desired.
| |
Create handler for XML-RPC server. Set expose-stop to false to prevent exposing the stop_remote_server RPC command. Justification for using macro: delayed evaluation of *ns*
| | (defmacro init-handler
[expose-stop]
(let [this-ns *ns*]
(if (true? expose-stop)
`(->
(xml-rpc/end-point
{:get_keyword_arguments (fn [kw-name#]
(get-keyword-arguments* ~this-ns kw-name#))
:get_keyword_documentation (fn [kw-name#]
(get-keyword-documentation* ~this-ns kw-name#))
:get_keyword_names (fn []
(get-keyword-names* ~this-ns))
:run_keyword (fn [kw-name# args#]
(run-keyword* ~this-ns kw-name# args#))
:stop_remote_server (fn []
(.stop @*robot-remote-server*))})
wrap-rpc)
`(->
(xml-rpc/end-point
{:get_keyword_arguments (fn [kw-name#]
(get-keyword-arguments* ~this-ns kw-name#))
:get_keyword_documentation (fn [kw-name#]
(get-keyword-documentation* ~this-ns kw-name#))
:get_keyword_names (fn []
(get-keyword-names* ~this-ns))
:run_keyword (fn [kw-name# args#]
(run-keyword* ~this-ns kw-name# args#))})
wrap-rpc))))
Given a Ring handler hndlr , start a Jetty server
| | (defn server-start!
([hndlr] (server-start! hndlr {:port 8270, :join? false}))
([hndlr opts]
(when (and (not (nil? @*robot-remote-server*)) (.isRunning @*robot-remote-server*))
(.stop @*robot-remote-server*))
(reset! *robot-remote-server* (run-jetty hndlr opts))))
Stop the global Jetty server instance
| | (defn server-stop!
[]
(.stop @*robot-remote-server*))
| |
| |
| | (ns robot-remote-server.keyword
(:use robot-remote-server.core)
(:import javax.swing.JOptionPane))
Documentation for myKeyword
| | (defn my-keyword
[arg1 arg2]
(println (str "My first keyword! Arg1: " arg1 ", Arg2: " arg2)))
Open a JOptionPane, just testing things
| | (defn open-dialog
[]
(JOptionPane/showMessageDialog
nil "Hello, Clojure World!" "Greeting"
JOptionPane/INFORMATION_MESSAGE))
| | (defn -main
[]
(do (use 'robot-remote-server.core)
(server-start! (init-handler false))))
| |
| |
| | (ns robot-remote-server.util)
TODO: Extend ResponseElements protocol to deal with nil
| |
| | (comment
(defn- handle-return-val
"Convert everything to RobotFramework-acceptable types. See implementations in other languages for examples"
[ret]
(condp class ret
java.lang.String ret
java.util.concurrent.atomic.AtomicInteger ret
java.util.concurrent.atomic.AtomicLong ret
java.math.BigDecimal ret
java.math.BigInteger ret
java.lang.Byte ret
java.lang.Double ret
java.lang.Float ret
java.lang.Integer ret
java.lang.Long ret
java.lang.Short ret
clojure.lang.PersistentVector (map handle-return-val ret)
clojure.lang.PersistentArrayMap (into {}
(for [[k v] ret]
[(.toString k) (handle-return-val v)]))
clojure.lang.PersistentTreeMap (into {}
(for [[k v] ret]
[(.toString k) (handle-return-val v)]))
:else ret))
(defonce a-server (run-jetty #'app-handler {:port 8271 :join? false}))
(doto (Thread. #(run-jetty #'app-handler {:port 8271})) .start)
)
| |