pallet / stevedore

A shell script embedding in clojure
93 stars 14 forks source link

Devise dispatch mechanism for core Stevedore functionality #8

Closed frenchy64 closed 13 years ago

frenchy64 commented 13 years ago

Core stevedore generates bash script. We want to generate other languages (eg. batch script).

A dispatch mechanism on OS exists with defscript/defimpl in pallet.script. pallet.script/implement can probably be reused here. defimpl is inappropriate because it contains an implicit call to script. We need lower level string generating functions.

Of course, we don't want to have to unquote to access stevedore, as in defimpl.

frenchy64 commented 13 years ago

One possibility would be to use Clojure protocols to define a common set of functionality stevedore implementations should provide.

(defprotocol StevedoreImpl
  "Protocol for Stevedore implementations in different languages"
  (emit-when 
    "Emit a form equivilant to when"
    [this when test & form])
  (emit-group 
    [this group & exprs]))

Provide a few implementations..

(deftype BashScript
  StevedoreImpl
  (emit-when 
    [this when test & form]
    (str "if "
         (if (logical-test? test) (str "[ " (emit test) " ]") (emit test))
         "; then"
         (str \newline (string/trim (emit-do form)) \newline)
         "fi"))

  (emit-group 
    [this group & exprs]
    (str "{ " (string/join "; " (map emit exprs)) "; }")))

(deftype BatchScript
  StevedoreImpl
  (emit-when
    [this when test & form]
    (str "IF "
         "making this up"
         "ENDIF"))
  (emit-group 
    [this group & exprs]
    (str "not supported?")))

We could reuse the base infrastructure provided by stevedore that parses the stevedore DSL, and extract out any implementation specific details in the StevedoreImpl protocol.

(defmethod emit-special 'when [type [when test & form]]
  (apply emit-when *stevedore-impl* when test form))

(defmethod emit-special 'group
  [type [ group & exprs]]
  (apply emit-group *stevedore-impl* when test form))

To tie it all together, we could use with-stevedore-impl.

(defmacro with-stevedore-impl
  [impl & body]
  `(binding [*stevedore-impl* impl]
     ~@body))

(with-stevedore-impl :bash
  (script
    (when (< 1 2)
      (println "something"))))

with-stevedore-impl would be a middle layer between with-script-context and choosing the stevedore implementation.

(def *os-to-stevedore-impl*
  {:linux pallet.script.impl.BashScript
   :windows pallet.script.impl.BatchScript})

(defn get-stevedore-impl-from-template
  [template]
  .....)

(defmacro with-script-context
  [template & body]
  (let [stevedore-impl (get-stevedore-impl-from-template template)]
    `(with-stevedore-impl stevedore-impl
       (binding [*script-context* (filter identity ~template)]
         ~@body))))

Thoughts?

hugoduncan commented 13 years ago

We might be able to remove the multimethod and just have the protocol.

At the moment there is a list of special-forms and a predicate special-form?. If the protocol were in a separate namespace, this could be changed to a call to resolve to obtain the protocol function.

frenchy64 commented 13 years ago

Currently leaning towards using a multimethod emit instead of protocols. Much simpler, and makes implementation inheritance saner.

(defmulti emit
  "Emit a shell expression as a string. Dispatched on the :type of the
   expression."
  (fn [ expr ] [ *current-stevedore-impl*  (type expr)]))

(derive ::bash3 ::script-common)
(derive ::batch ::script-common)

(derive ::bash4 ::bash3)

;; common functions
(defmethod emit [::script-common clojure.lang.IPersistentList] [expr]
  (emit-s-expr expr))

(defmethod emit [::script-common clojure.lang.Cons]
  [expr]
  (if (= 'list (first expr))
    (emit-s-expr (rest expr))
    (emit-s-expr expr)))

(defmethod emit-special [::bash3 'file-exists?] [type [file-exists? path]]
  (str "-e " (emit path)))

;;; hypothetical difference btwn bash3 and 4
(defmethod emit-special [::bash4 'file-exists?] [type [file-exists? path]]
  (str "-empty " (emit path)))

(defmethod emit-special [::batch 'file-exists?] [type [file-exists? path]]
  (str "exists?!? " (emit path)))
frenchy64 commented 13 years ago

Added with-stevedore-impl and changed emit to dispatch on current implementation.

https://github.com/pallet/stevedore/commit/4e48503000cbbfe24ade8ff974f4c2663c4b0f16

frenchy64 commented 13 years ago

Merged into develop.