Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 84 additions & 16 deletions src/libpython_clj/require.clj
Original file line number Diff line number Diff line change
Expand Up @@ -191,33 +191,101 @@
(intern fn-ns (with-meta fn-sym (py-fn-metadata fn-name f
options)) f)))

(defn- parse-flags
"FSM style parser for flags. Designed to support both
unary style flags aka '[foo :reload] and
boolean flags '[foo :reload true] to support Clojure
style 'require syntax. Possibly overengineered."
[supported-flags reqs]

(letfn [(supported-flag-item
;; scanned a supported tag token
[supported-flags flag results item items]
(cond
;; add flag, continue scanning
(true? item) (next-flag-item
supported-flags
(conj results flag)
(first items)
(rest items))

;; don't add flag, continue scanning
(false? item) (next-flag-item
supported-flags
results
(first items)
(rest items))


:else
;; unary flag -- add flag but scan current item/s
(next-flag-item
supported-flags
(conj results flag)
item
items)))


;; scan flags
(next-flag-item [supported-flags results item items]
(cond
;; supported flag scanned, begin FSM parse
(get supported-flags item)
(let [flag (get supported-flags item)
remaining-flags (clojure.set/difference
supported-flags #{flag})]
(supported-flag-item
remaining-flags
flag
results
(first items)
(rest items)))

;; FSM complete
(nil? item) (into #{} results)

;; no flag scanned, continue scanning
:else (recur
supported-flags
results
(first items)
(rest items))))

;; entrypoint
(get-flags [supported-flags reqs]
(next-flag-item supported-flags
[]
(first reqs)
(rest reqs)))]
(trampoline get-flags supported-flags reqs)))



(defn ^:private load-python-lib [req]
(let [supported-flags #{:reload :no-arglists}
[module-name & etc] req
flags (into #{}
(filter supported-flags)
etc)
flags (flags* supported-flags etc)
etc (into {}
(comp
(remove supported-flags)
(remove boolean?)
(partition-all 2)
(map vec))
etc)
reload? (:reload flags)
no-arglists? (:no-arglists flags)
module-name-or-ns (:as etc module-name)
exclude (into #{} (:exclude etc))
refer (cond
(= :all (:refer etc)) #{:all}
(= :* (:refer etc)) #{:*}
:else (into
#{}
(:refer etc)))
current-ns *ns*
current-ns-sym (symbol (str current-ns))
python-namespace (find-ns module-name-or-ns)
this-module (import-module (str module-name))]
refer (cond
(= :all (:refer etc)) #{:all}
(= :* (:refer etc)) #{:*}
:else (into
#{}
(:refer etc)))
current-ns *ns*
current-ns-sym (symbol (str current-ns))
python-namespace (find-ns module-name-or-ns)
this-module (import-module (str module-name))]

(cond
reload?
Expand Down Expand Up @@ -250,9 +318,9 @@

(let [python-namespace (find-ns module-name-or-ns)
;;ns-publics is a map of symbol to var. Var's have metadata on them.
public-data (->> (ns-publics python-namespace)
(remove #(exclude (first %)))
(into {}))]
public-data (->> (ns-publics python-namespace)
(remove #(exclude (first %)))
(into {}))]

;;Always make the loaded namespace available to the current namespace.
(intern current-ns-sym
Expand Down
31 changes: 30 additions & 1 deletion test/libpython_clj/require_python_test.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(ns libpython-clj.require-python-test
(:require [libpython-clj.require :refer [require-python]]
(:require [libpython-clj.require :as req :refer [require-python]]
[libpython-clj.python :as py]
[clojure.test :refer :all]))

Expand All @@ -21,3 +21,32 @@
(is (= 10.0 (double (floor 10.1))))
(is (= pymath (py/import-module "math")))
(is (thrown? Throwable (require-python '[math :refer [blah]])))))


(deftest parse-flags-test
;; sanity check
(is (= #{:reload :foo}
#{:foo :reload}))
(let [parse-flags #'req/parse-flags]
(is (= #{:reload} (parse-flags
#{:reload}
'[:reload foo])))
(is (= #{} (parse-flags #{} '[:reload foo])))
(is (= #{:reload} (parse-flags #{:reload} '[:reload true])))
(is (= #{:reload} (parse-flags #{:reload} '[:reload :as foo])))
(is (= #{:reload} (parse-flags #{:reload} '[:reload foo :as])))
(is (= #{:reload} (parse-flags #{:reload} '[foo :reload :as bar])))
(is (= #{} (parse-flags #{:reload} '[:reload false])))
(is (= #{} (parse-flags #{:reload} '[:reload false :reload])))
(is (= #{} (parse-flags #{:reload} '[:reload false :reload true])))
(is (= #{:reload} (parse-flags #{:reload} '[:reload :reload false])))
(is (= #{:reload} (parse-flags #{:reload} '[:reload true :reload false])))
(is (= #{:a :b}) (parse-flags #{:a :b} '[:a true :b]))
(is (= #{:a :b}) (parse-flags #{:a :b} '[:a :b]))
(is (= #{:a :b} (parse-flags #{:a :b} '[:a true :b])))
(is (= #{:a :b} (parse-flags #{:a :b} '[:a :b true])))
(is (= #{:a :b} (parse-flags #{:a :b} '[:a true :b true])))
(is (= #{:b} (parse-flags #{:a :b} '[:a false :b true])))
(is (= #{:b} (parse-flags #{:a :b} '[:b true :a false])))
(is (= #{:b} (parse-flags #{:a :b} '[:b :a false])))
(is (= #{:b} (parse-flags #{:a :b} '[:b :a false :a true])))))