Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Install clojure tools
uses: DeLaGuardo/setup-clojure@4.0
with:
cli: 1.10.1.693 # Clojure CLI based on tools.deps
cli: 1.12.4.1618 # Clojure CLI based on tools.deps

- name: Run Clj Unit tests
run: make test-clj
Expand Down
4 changes: 2 additions & 2 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
:extra-deps {medley/medley {:mvn/version "0.8.4"}
camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.0"}
org.clojure/tools.namespace {:mvn/version "0.2.11"}
org.clojure/clojure {:mvn/version "1.11.1"}
}}
org.clojure/clojure {:mvn/version "1.12.4"}
zprint/zprint {:mvn/version "1.3.0"}}}
:bb {:extra-deps {tubular/tubular {:mvn/version "1.3.0"}}
:extra-paths ["dev" "test"]}
:test {:extra-paths ["test"]
Expand Down
113 changes: 72 additions & 41 deletions dev/defwrapper.clj
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
(ns defwrapper
"based on gist by @plexus"
(:require [clojure.string :as string])
(:require [clojure.string :as string]
[metadata])
(:import (java.time.format DateTimeFormatter)
(java.time Instant)
(java.lang.reflect Modifier Method)))

(set! *warn-on-reflection* true)
(set! *print-meta* true)

(defonce newline-placeholder
;; Workaround to print newlines as a real newline in a file.
(str (random-uuid)))

(defn class-methods [^Class class]
(seq (.getMethods class)))

Expand All @@ -29,6 +34,11 @@
(defn method-name [^java.lang.reflect.Method method]
(.getName method))

(defn method-fqn [^Method method]
(-> (str method)
(string/split #" ")
peek))

(defn class-name [^Class klazz]
(let [original-name (.getName klazz)]
(symbol (if (= "java.time.temporal.TemporalUnit" (.getName klazz))
Expand All @@ -42,13 +52,6 @@
(clojure.string/replace #"([a-z0-9])([A-Z])" "$1-$2")
(clojure.string/lower-case)))

(defn class->name [^Class class]
(->
(if (.isArray class)
(str (.getName (.getComponentType class)) "-array")
(.getName class))
(string/replace "." "-")))

(defn method-public? [^java.lang.reflect.Method method]
(java.lang.reflect.Modifier/isPublic (.getModifiers method)))

Expand Down Expand Up @@ -119,20 +122,24 @@
(symbol (apply str "." (string/lower-case f) r))))
(list (symbol (str "." nm)))))

(defn tagged-local [value tag]
(let [tag (ensure-boxed-long-double tag)]
(defn tagged-local [param-name value ^Class klazz]
(let [tag (ensure-boxed-long-double klazz)]
(cond
(= 'long tag)
`(long ~value)
[param-name `(~(symbol 'long) ~value)]

(= 'double tag)
`(double ~value)
[param-name `(~(symbol 'double) ~value)]

(= 'java.lang.Integer tag)
`(int ~value)
[param-name `(~(symbol 'int) ~value)]

(= 'java.lang.Character tag)
[param-name `(~(symbol 'char) ~value)]

:else
(vary-meta value assoc :tag (.getName tag)))))
[(vary-meta param-name assoc :tag klazz)
value])))

(defn method-call [static? klazz nam ext]
(if static?
Expand All @@ -145,8 +152,8 @@
(defn wrapper-multi-tail [klazz methods ext helpful?]
(let [static? (method-static? (first methods))
nam (method-name (first methods))
this (gensym "this")
arg-vec (take (parameter-count (first methods)) (repeatedly gensym))
this 'this
arg-vec (take (parameter-count (first methods)) (map #(symbol (str "arg" %)) (range)))
ret (if (apply = (map return-type methods))
(return-type (first methods))
java.lang.Object)
Expand All @@ -155,41 +162,55 @@
`(~@method-call
~@(when-not static? [(tagged this klazz ext)])
~@arg-vec)
`(cond
~@(mapcat
(fn [method]
`[(and ~@(map (fn [sym ^Class klz]
(if (.isArray klz)
`(= ~(.getComponentType klz)
(.getComponentType (class ~sym)))
`(instance? ~(ensure-boxed (class-name klz)) ~sym)))
arg-vec
(parameter-types method)))
(let [~@(mapcat (fn [sym ^Class klz]
[sym (tagged-local sym klz)])
arg-vec
(parameter-types method))]
(~@method-call
~@(when-not static? [(tagged this klazz ext)])
~@arg-vec))])
`(~(symbol 'cond)
~@(mapcat
(fn [^Method method]
(let [param-names (->> (method-fqn method)
(get metadata/java-time-metadata)
(:params)
(mapv (comp symbol camel->kebab)))
_ (assert (= (count arg-vec) (count param-names)) (method-fqn method))
conds (map (fn [sym ^Class klz]
(if (.isArray klz)
`(~(symbol '=) ~(.getComponentType klz)
(.getComponentType (~(symbol 'class) ~sym)))
`(~(symbol 'instance?) ~(ensure-boxed (class-name klz)) ~sym)))
arg-vec
(parameter-types method))]
`[~@(if (= 1 (count conds))
conds
[(apply list 'and conds)])
(~(symbol 'let) [~@(mapcat tagged-local
param-names
arg-vec
(parameter-types method))]
(~@method-call
~@(when-not static? [this])
~@param-names))]))
methods)
:else (throw (IllegalArgumentException. "no corresponding java.time method with these args"))))
bod (if helpful?
`(~(if (= :clj ext) 'cljc.java-time.extn.calendar-awareness/calendar-aware-clj
'cljc.java-time.extn.calendar-awareness/calendar-aware-cljs)
~bod)
bod)]
`(~(tagged `[~@(when-not static? [this]) ~@arg-vec] ret ext)
`(~(tagged `[~@(when-not static? [(tagged this klazz ext)]) ~@arg-vec] ret ext)
~bod)))

(defn wrapper-tail [klazz method ext helpful?]
(let [nam (method-name method)
ret (return-type method)
par (parameter-types method)
static? (method-static? method)
arg-vec (into (if static? [] [(tagged (gensym "this") klazz ext)])
(map #(tagged (gensym (class->name %)) % ext))
par)
param-names (->> (method-fqn method)
(get metadata/java-time-metadata)
(:params)
(mapv (comp symbol camel->kebab)))
_ (assert (= (count par) (count param-names)) (method-fqn method))
arg-vec (into (if static? [] [(tagged 'this klazz ext)])
(map #(tagged %1 %2 ext)
param-names
par))
method-call (method-call static? klazz nam ext)
bod `(~@method-call ~@(map #(vary-meta % dissoc :tag) arg-vec))
bod (if helpful?
Expand All @@ -201,12 +222,21 @@
~bod)))

(defn method-wrapper-form [fname klazz methods ext helpful?]
(let [arities (group-by parameter-count methods)
(let [arities (into (sorted-map) (group-by parameter-count methods))
static? (method-static? (first methods))]
`(defn ~fname
`(~(symbol 'defn) ~fname
~@(when (= 1 (count methods))
(some->> (first methods)
(method-fqn)
(get metadata/java-time-metadata)
(:doc)
(string/trim)
(#(string/escape % {\newline newline-placeholder}))
(vector)))
{:arglists '~(map (comp (partial into (if static? [] [(.getName klazz)]))
#(map (fn [x] (.getName x)) %)
parameter-types) methods)}
parameter-types)
(mapcat val arities))}
~@(map (fn [[cnt meths]]
(if (= 1 (count meths))
(wrapper-tail klazz (first meths) ext helpful?)
Expand All @@ -223,6 +253,7 @@
(filter method-public?)
(filter concrete?)
(remove (set (class-methods Object)))
(sort-by method-fqn)
(group-by method-name)))

(def helpful-exceptions
Expand Down Expand Up @@ -255,4 +286,4 @@
(-> (seq (.getDeclaredMethods java.time.LocalDateTime)))
(method-wrapper-form "foo" java.time.LocalDateTime
meths ext (contains? helpful-fns fname))
)
)
56 changes: 41 additions & 15 deletions dev/gen.clj
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
(ns gen
(:require [clojure.reflect :as rf]
[clojure.set :as set]
[clojure.walk :as walk]
[defwrapper :as df]
[clojure.string :as string]
[camel-snake-kebab.core :as csk]
[clojure.java.io :as io])
[clojure.java.io :as io]
[metadata]
[zprint.core :as zprint])
(:import [java.time Period
LocalDate
LocalTime
Expand Down Expand Up @@ -66,37 +69,60 @@
)
(symbol (str "^" x)))))

(defn remove-line-column-meta
[form]
(walk/postwalk
(fn [x]
(if (meta x)
(vary-meta x dissoc :line :column)
x))
form))

(defn gen-for-class [c sub-p ext]
;; header
(println (header (.getSimpleName c) (csk/->kebab-case (.getSimpleName c))
sub-p ext))
;; fields
(doseq [m (:members (rf/reflect c))]
(doseq [m (:members (rf/reflect c))
:let [fqn (str (.getName c) \. (:name m))
doc (some->> fqn
(get metadata/java-time-metadata)
(:doc)
(string/trim)
(#(string/escape % {\newline df/newline-placeholder})))]]
(when (and (not (:parameter-types m)) (not-empty (set/intersection #{:public} (:flags m))))
(println
(list 'def (csk/->kebab-case (:name m))
(if (= :clj ext)
(prn
`(~(symbol 'def) ~(csk/->kebab-case (:name m))
~@(when doc [doc])
~(if (= :clj ext)
(symbol (str (.getName c) "/" (:name m)))
(list 'goog.object/get c (str "\"" (:name m) "\"")))))))
(list 'goog.object/get c (str (:name m))))))))
;; constructors
(when (= java.time.format.DateTimeFormatterBuilder c)
(prn '(clojure.core/defn new {:arglists (quote ([]))}
(^java.time.format.DateTimeFormatterBuilder [] (java.time.format.DateTimeFormatterBuilder.)))))
(prn (remove-line-column-meta
'(defn new {:arglists (quote ([]))}
(^java.time.format.DateTimeFormatterBuilder [] (java.time.format.DateTimeFormatterBuilder.))))))
;; methods
(doseq [f (df/defwrapper c ext)]
(let [f (if (= 'is-leap (second f))
'(clojure.core/defn is-leap {:arglists (quote (["long"]))}
(^java.lang.Boolean [^long long57050] (. java.time.Year isLeap long57050)))
(remove-line-column-meta
'(defn is-leap {:arglists (quote (["long"]))}
(^java.lang.Boolean [^long year] (. java.time.Year isLeap year))))
f)]
(pr f))
(println)))

(defn get-and-write [c ext sub-p]
(let [f (str "./src/cljc/java_time/" (when sub-p (str sub-p "/")) (csk/->snake_case (.getSimpleName c)) "." (name ext))
_ (io/make-parents f)
w (io/writer f)]
(binding [*out* w]
(gen-for-class c sub-p ext))))
(let [file-name (str (csk/->snake_case (.getSimpleName c)) "." (name ext))
f (str "./src/cljc/java_time/" (when sub-p (str sub-p "/")) file-name)]
(io/make-parents f)
(with-open [w (io/writer f)]
(binding [*out* w]
(gen-for-class c sub-p ext)))
(zprint/zprint-file f file-name f {:parse {:interpose "\n\n"}})
(spit f (-> (slurp f)
(string/replace df/newline-placeholder "\n")
(string/replace #"\h*<p>" "")))))

(defn generate-library-code! []
;todo - chrono and zone packages. needs cljs.java-time also
Expand Down
Loading
Loading