Контекст
Производственный REPL не для слабонервных. Я бы не стал добавлять REPL в производство в 95% проектов, частью которых я был; однако в настоящее время я работаю с двумя другими разработчиками, которым полностью доверяю возможности производственного REPL. Как простое правило, я бы не дал доступ к производственному REPL любому, у кого нет корневого доступа к серверу — если вам нельзя доверять с одним, скорее всего, вам нельзя доверять с помощью разное. Однако, если вам можно доверять, я бы предпочел, чтобы у вас были все доступные инструменты.
Боковая панель : самая интересная история, которую я слышал о REPL в prod, была следующей: отладка программы, работающей на оборудовании стоимостью 100 миллионов долларов, которое находится на расстоянии 100 миллионов миль, является интересным опытом. Наличие цикла чтения-проверки-печати на космическом корабле оказалось неоценимым в поиске и устранении проблемы. —
Лиспинг в JPL
Не поймите меня неправильно, я не регулярно пересматриваю функции в производстве. Я редко использую продукт REPL, но когда я делаю, я почти всегда использую его для чтения справочных данных. В очень редком случае я оберну существующую функцию новой версией, которая регистрирует значение входящих аргументов. Короче говоря, я использую продукт REPL (опять же, экономно) для сбора дополнительных данных, а не для исправления ошибок. Я не говорю, что никогда не использовал бы это, чтобы исправить ошибку, но это был бы чрезвычайно исключительный случай.
Код
На самом деле очень легко подключить веб-REPL к приложению Clojure, которое уже имеет веб-интерфейс. Приложения Clojure, над которыми я работаю, обычно имеют веб-сокеты, а в следующем решении используются веб-сокеты; однако нет никаких причин, по которым вы не могли бы использовать простые веб-запросы и ответы. Наконец, я на самом деле не писал этот код. Кто-то из
DRW написал это (я понятия не имею, кто), и с тех пор его копировали несколько раз. Без лишних слов, код:
<!DOCTYPE HTML> <html> <head> <link href="css/web-repl.css" rel="stylesheet" type="text/css"> <script src="js/lib/jquery-1.5.min.js" type="text/javascript"></script> <script src="js/lib/jquery.websocket-0.0.1.js" type="text/javascript"></script> <script src="js/lib/jquery.console.js" type="text/javascript"></script> <script src="js/web-repl.js" type="text/javascript"></script> <title>Web REPL</title> </head> <body> <div id="console"/> </body> </html>
var ws = null; $(document).ready(function () { ws = $.websocket("ws://" + window.location.host + "/websocket", { events: { 'web-repl-response': function(info) { currentCallback([ {msg: info.response, className:"jquery-console-message-value"} ]); } } }); $("#console").console({ promptLabel: 'Clojure> ', commandValidate:function(line) { if (line == "") { return false; } else { return true; } }, commandHandle:function(line, callback) { currentCallback = callback; ws.send('selfish', {type: "web-repl", command: line}); }, welcomeMessage:'Enter some Clojure code, and it will be evaluated ON THE SERVER -- CAREFUL!!!.', autofocus:true, animateScroll:true, promptHistory:true }) });
(ns web-repl (:require clojure.main) (:use [clojure.stacktrace :only [root-cause]])) (defonce repl-sessions (ref {})) (defn current-bindings [] (binding [*ns* *ns* *warn-on-reflection* *warn-on-reflection* *math-context* *math-context* *print-meta* *print-meta* *print-length* *print-length* *print-level* *print-level* *compile-path* (System/getProperty "clojure.compile.path" "classes") *command-line-args* *command-line-args* *assert* *assert* *1 nil *2 nil *3 nil *e nil] (get-thread-bindings))) (defn bindings-for [session-key] (when-not (@repl-sessions session-key) (dosync (commute repl-sessions assoc session-key (current-bindings)))) (@repl-sessions session-key)) (defn store-bindings-for [session-key] (dosync (commute repl-sessions assoc session-key (current-bindings)))) (defmacro with-session [session-key & body] `(with-bindings (bindings-for ~session-key) (let [r# ~@body] (store-bindings-for ~session-key) r#))) (defn do-eval [txt session-key] (with-session session-key (let [form (binding [*read-eval* false] (read-string txt))] (with-open [writer (java.io.StringWriter.)] (binding [*out* writer] (try (let [r (pr-str (eval form))] (str (.toString writer) (str r))) (catch Exception e (str (root-cause e)))))))))
#console { height: 820px; background: #eee; margin: 10px; border-radius: 5px; -moz-border-radius: 5px; border: 1px solid #aaa; } #console div.jquery-console-inner { height: 800px; margin: 10px 10px; overflow: auto; text-align: left; } #console div.jquery-console-welcome { color: #ef0505; font-family: sans-serif; font-weight: bold; padding: 0.1em; } #console div.jquery-console-message-value { color: #0066FF; font-family: monospace; padding: 0.1em; } #console div.jquery-console-prompt-box { color: #444; font-family: monospace; } #console div.jquery-console-focus span.jquery-console-cursor { background: #333; color: #eee; font-weight: bold; } #console div.jquery-console-message-error { color: #ef0505; font-family: sans-serif; font-weight: bold; padding: 0.1em; } #console div.jquery-console-message-success { color: #187718; font-family: monospace; padding: 0.1em; } #console span.jquery-console-prompt-label { font-weight: bold; }
; the code I work with has a publish-fn that we use to publish json back to a web-socket. ; therefore, the following line is all we need to add to our application to get the web REPL working (publish-fn (hash-map :type :web-repl-response :response (web-repl/do-eval command publish-fn))))