Last active
May 19, 2019 18:48
-
-
Save saikyun/d16eee46c99b6e5715ee7121a22e2c80 to your computer and use it in GitHub Desktop.
generating specs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns spec-gen.core | |
(:require [clojure.spec.alpha :as s] | |
[clojure.string :as str] | |
[clojure.java.io :as io]) | |
(:gen-class)) | |
(def atom-specs | |
[(s/def ::number number?) | |
(s/def ::string string?) | |
(s/def ::keyword keyword?) | |
(s/def ::symbol symbol?)]) | |
(def coll-specs | |
[(s/def ::map map?) | |
(s/def ::set set?) | |
(s/def ::vector vector?) | |
(s/def ::list list?) | |
(s/def ::lazy-seq (partial instance? clojure.lang.LazySeq)) | |
(s/def ::coll coll?) | |
(s/def ::seq seq?) | |
(s/def ::seqable seqable?)]) | |
(def fn-specs | |
[(s/def ::ifn ifn?)]) | |
(def atom-coll-fn-specs (concat atom-specs coll-specs fn-specs)) | |
(defn gen-type-spec | |
[type] | |
(eval `(s/def ~(keyword (str *ns*) (.getName type)) ~(fn [o] (instance? type o))))) | |
(def all-specs atom-coll-fn-specs) | |
(defn check-specs | |
[check-f specs value] | |
(map #(vector % (check-f % value)) specs)) | |
(def valid-specs (partial check-specs s/valid?)) | |
(defn first-spec [specs value] | |
(first (first (filter second (valid-specs specs value))))) | |
(declare gen-nested-spec) | |
(defn gen-or-spec | |
"Takes a list of specs and a list of values, | |
then returns a string which is a generated name for the spec, and a `s/or` spec. | |
If `l` only contains values conforming to a single spec, | |
it just returns that spec instead of the `s/or`-spec. | |
The generated name looks like this `::<::number+::string>`, if there was | |
a `::number` and a `::string` in the `set-of-specs`." | |
[specs l] | |
(let [set-of-specs (into (sorted-set) (map #(gen-nested-spec specs %) l)) | |
spec-name (str/join "+" (map #(apply str (rest (str %))) set-of-specs))] | |
[spec-name | |
(if (< 1 (count set-of-specs)) | |
`(s/or | |
~@(->> set-of-specs | |
(map-indexed | |
#(vector | |
(if (keyword? %2) | |
(keyword (name %2)) | |
(keyword (str "kind-" %1))) | |
%2)) | |
(apply concat))) | |
(first set-of-specs))])) | |
(defn gen-list-spec | |
[specs l] | |
(let [[spec-name s-or] (gen-or-spec specs l) | |
spec-name (keyword (str *ns*) (str "coll-of<" spec-name ">"))] | |
`(s/def ~spec-name (s/coll-of ~s-or)))) | |
(defn gen-nested-spec | |
[specs value] | |
(let [res (first-spec specs value) | |
res (if (some #{res} coll-specs) | |
(case res | |
(::vector ::list) (eval (gen-list-spec specs value)) | |
::lazy-seq (eval (gen-list-spec specs (take 100 value))) | |
res) | |
res)] | |
(if res | |
res | |
(gen-type-spec (type value))))) | |
(defn spec-it! [name value] (eval `(s/def ~name ~(gen-nested-spec all-specs value)))) | |
(comment | |
(let [values [1 "hej" 1]] | |
(gen-or-spec all-specs values)) | |
(gen-nested-spec atom-coll-fn-specs [1 2 3]) | |
(gen-list-spec atom-coll-fn-specs [1 2 3 "string" ["hej"]]) | |
(gen-nested-spec atom-coll-fn-specs 5) | |
(spec-it! ::hmmm (String. "hej")) | |
(s/explain ::hmmm (String. "wat")) | |
(s/explain ::hmmm 123) | |
(spec-it! ::file (io/file "Example.txt")) | |
(s/explain ::file "wat") | |
(s/explain ::file (io/file "wat")) | |
(s/explain ::file nil) | |
(s/explain ::file 123) | |
(s/explain ::file (io/file "Yoo.html")) | |
(spec-it! ::cool [1 2 3]) ;;=> ::cool | |
(gen-nested-spec atom-coll-fn-specs [1 2 3]) | |
(gen-or-spec atom-coll-fn-specs [1 2 3]) | |
(gen-list-spec atom-coll-fn-specs [1 2 3]) | |
(S/valid? :spec-gen.core/coll-of<spec-gen.core/number> [5 6]) | |
(s/valid? ::cool [5 6]) ;;=> true | |
(s/valid? ::cool [5 6 "HUE"]) ;;=> false | |
) | |
;; ==================== Generate specs for functions ============== | |
(defn args->specs | |
"args is a map, with the keys being symbols, and vals being values. | |
E.g. (args-list->specs {'x 10, 'y 20})" | |
[specs args] | |
(for [[arg value] args] | |
(do (prn arg value (type value)) | |
[arg (gen-nested-spec specs value)]))) | |
(defn args-list->specs | |
[specs args-list] | |
(save :wat) | |
(->> args-list | |
(map (partial args->specs specs)) | |
(apply concat) | |
(group-by first) | |
(map (fn [[k v]] [k (into #{} (map second v))])))) | |
(comment | |
(let [lol "hej"] | |
(gen-nested-spec atom-coll-fn-specs lol)) | |
(args-list->specs | |
atom-coll-fn-specs | |
['([x 10], [y 20]) | |
{'x 20}]) | |
(into {} | |
(for [[f-var call-data] @miracle.save/f-saves] | |
[f-var (args-list->specs | |
atom-coll-fn-specs | |
(map :spec-args call-data))])) | |
) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment