From 337d39684a30e87d2dc124be3b62a68b7221edb9 Mon Sep 17 00:00:00 2001 From: Sam Ritchie Date: Sun, 27 Nov 2011 14:14:02 -0500 Subject: [PATCH] Extracted koan logic out into koan-engine. --- .gitignore | 1 + project.clj | 8 +- resources/koans.clj | 199 ++++++++++++++++++++++++ script/repl | 2 +- script/run | 2 +- script/run.clj | 6 +- script/test | 3 +- script/test.clj | 6 +- src/path_to_answer_sheet.clj | 279 ---------------------------------- src/path_to_enlightenment.clj | 49 ------ src/runner/freshness.clj | 34 ----- src/runner/koans.clj | 58 ------- 12 files changed, 214 insertions(+), 433 deletions(-) create mode 100644 resources/koans.clj delete mode 100644 src/path_to_answer_sheet.clj delete mode 100644 src/path_to_enlightenment.clj delete mode 100644 src/runner/freshness.clj delete mode 100644 src/runner/koans.clj diff --git a/.gitignore b/.gitignore index 1f08613..2f90bd0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ lib releases *.jar .DS_Store +.lein-deps-sum diff --git a/project.clj b/project.clj index ca06e9e..fa432a7 100644 --- a/project.clj +++ b/project.clj @@ -1,6 +1,6 @@ (defproject functional-koans "0.4.5" - :description "The functional koans" + :description "The functional koans." :dependencies [[org.clojure/clojure "1.3.0"] - [fresh "1.0.2"] - [jline "0.9.94" :exclusions [junit]]] - :dev-dependencies [[swank-clojure "1.3.0" :exclusions [org.clojure/clojure]]]) + [koan-engine "0.1.0"]] + :dev-dependencies [[swank-clojure "1.3.0" :exclusions [org.clojure/clojure]] + [lein-koan "0.1.0"]]) diff --git a/resources/koans.clj b/resources/koans.clj new file mode 100644 index 0000000..8b9dded --- /dev/null +++ b/resources/koans.clj @@ -0,0 +1,199 @@ +[["equalities" {"__" [true + 2 + 7 + 5 + 4/2 + false + 6/3 + 3]}] + + ["lists" {"__" [1 2 3 4 5 + 1 + [2 3 4 5] + () + [:a :b :c :d :e] + [0 :a :b :c :d :e] + :a + [:b :c :d :e] + "No dice!" + ()]}] + + ["vectors" {"__" [1 + [] + [1] + [nil] + 2 + [333] + :peanut + :jelly + :jelly + [:butter :and] + 3]}] + + ["sets" {"__" [nil + 3 + #{1 2 3 4 5} + #{1 2 3 4 5} + #{2 3} + #{1 4}]}] + + ["maps" {"__" [{} + 0 + 1 + 2 + 2 + 1 + 1 + "Vancouver" + nil + :key-not-found + true + false + "February" + 1 "January" + 2006 2010 2014 + "Vancouver"]}] + + ["functions" {"__" [20 + 10 5 + 30 2 + 15 + 20 *] + "___" [(fn [f] (f 5)) + (fn [f] (f 5))]}] + + ["conditionals" {"__" [:a + [] + nil + :glory + 4 6 :your-road + 'doom 0 + :cocked-pistol + :say-what?]}] + + ["higher_order_functions" {"__" [4 8 12 + (* x x) + [false false true false false] + () + [:anything :goes :here] + (< x 31) + (* 10 x) (< x 4) + 24 + 100 + (count a) (count b)]}] + + ["runtime_polymorphism" {"__" [(str (:name a) " eats veggies.") + (str (:name a) " eats animals.") + (str "I don't know what " (:name a) " eats.") + "Hello World!" + "Hello, you silly world." + "Hello to this group: Peter, Paul, Mary!" ]}] + + ["lazy_sequences" {"__" [[1 2 3 4] + [0 1 2 3 4] + 10 + 95 + (range 20) + :a] + "___" [(fn [x] :foo)]}] + + ["sequence_comprehensions" {"__" [[0 1 2 3 4 5] + (* index index) + (range 10) + (odd? index) (* index index) + [row column] + ]}] + + ["creating_functions" {"__" [true false true + 4 + :a :b :c :d + :c :d + 4 + 8] + "___" [(complement nil?) + multiply-by-5 + (comp dec square)]}] + + ["recursion" {"__" [true + acc + (loop [coll coll + acc ()] + (if (seq coll) + (recur (rest coll) (conj acc (first coll))) + acc)) + (loop [n n + acc 1] + (if (zero? n) + acc + (recur (dec n) (* acc n))))] + "___" [not]}] + + ["destructuring" {"__" [":bar:foo" + (format (str "First comes %s, " + "then comes %s, " + "then comes %s with the baby carriage") + a b c) + (apply str + (interpose " " + (apply list + first-name + last-name + (interleave (repeat "aka") aliases)))) + {:original-parts full-name + :named-parts {:first first-name :last last-name}} + (str street-address ", " city ", " state) + city state + (str street-address ", " city ", " state)] + "___" [(fn [[fname lname] + {:keys [street-address city state]}] + (str fname " " lname ", " + street-address ", " city ", " state)) + ]}] + + ["refs" {"__" ["hello" + "hello" + "better" + "better!!!" + (dosync (ref-set the-world 0)) + (map :jerry [@the-world @bizarro-world]) + ] + "___" [(fn [x] (+ 20 x))]}] + + ["atoms" {"__" [0 + 1 + (swap! atomic-clock (partial + 4)) + 20 + 20 + atomic-clock 20 :fin + ]}] + + ["macros" {"__" [~(first form) + ~(nth form 2) + form + (drop 2 form) + "Hello, Macros!" + 10 + '(+ 9 1) + '(* 10 2) + '(+ 10 (2 * 3))]}] + + ["datatypes" {"__" [(print + (str "You're really the " + (.category this) + ", " recipient "... sorry.")) + "peace" + "literature" + "physics" + nil + [true false] + (str "Congratulations on your Best Picture Oscar, " + "Evil Alien Conquerors!")]}] + + ["java_interop" {"__" [java.lang.String + "SELECT * FROM" + 10 + 1024 + ] + "___" [#(.toUpperCase %) + ] + }]] diff --git a/script/repl b/script/repl index 940f8ed..adeec3b 100755 --- a/script/repl +++ b/script/repl @@ -1,7 +1,7 @@ #!/bin/sh CLASSPATH=src -for f in lib/*.jar; do +for f in lib/*.jar lib/dev/*.jar resources/; do CLASSPATH=$CLASSPATH:$f done diff --git a/script/run b/script/run index 860831b..47a265b 100755 --- a/script/run +++ b/script/run @@ -1,7 +1,7 @@ #!/bin/sh CLASSPATH=src -for f in lib/*.jar; do +for f in lib/*.jar lib/dev/*.jar resources/; do CLASSPATH=$CLASSPATH:$f done diff --git a/script/run.clj b/script/run.clj index 65f1f84..7d38877 100644 --- a/script/run.clj +++ b/script/run.clj @@ -1,4 +1,4 @@ -(load "path_to_enlightenment") +(load "koan_engine/runner") (do - (in-ns 'path-to-enlightenment) - (run)) \ No newline at end of file + (in-ns 'koan-engine.runner) + (exec "run")) diff --git a/script/test b/script/test index baabff4..813ac44 100755 --- a/script/test +++ b/script/test @@ -1,7 +1,8 @@ #!/bin/sh CLASSPATH=src -for f in lib/*.jar; do + +for f in lib/*.jar lib/dev/*.jar resources/; do CLASSPATH=$CLASSPATH:$f done diff --git a/script/test.clj b/script/test.clj index 1e4fbfa..cf0c3a3 100644 --- a/script/test.clj +++ b/script/test.clj @@ -1,4 +1,4 @@ -(load "path_to_answer_sheet") +(load "koan_engine/runner") (do - (in-ns 'path-to-answer-sheet) - (run)) \ No newline at end of file + (in-ns 'koan-engine.runner) + (exec "test")) diff --git a/src/path_to_answer_sheet.clj b/src/path_to_answer_sheet.clj deleted file mode 100644 index da986c8..0000000 --- a/src/path_to_answer_sheet.clj +++ /dev/null @@ -1,279 +0,0 @@ -(ns path-to-answer-sheet - (:use [runner.koans :only [ordered-koans]] - [path-to-enlightenment :only [meditations fancy-assert __ ___]] - [clojure.string :only [join split trim] :as string])) - -(def answers - {"equalities" {"__" [true - 2 - 7 - 5 - 4/2 - false - 6/3 - 3]} - - "lists" {"__" [1 2 3 4 5 - 1 - [2 3 4 5] - () - [:a :b :c :d :e] - [0 :a :b :c :d :e] - :a - [:b :c :d :e] - "No dice!" - ()]} - - "vectors" {"__" [1 - [] - [1] - [nil] - 2 - [333] - :peanut - :jelly - :jelly - [:butter :and] - 3]} - - "sets" {"__" [nil - 3 - #{1 2 3 4 5} - #{1 2 3 4 5} - #{2 3} - #{1 4}]} - - "maps" {"__" [{} - 0 - 1 - 2 - 2 - 1 - 1 - "Vancouver" - nil - :key-not-found - true - false - "February" - 1 "January" - 2006 2010 2014 - "Vancouver"]} - - "functions" {"__" [20 - 10 5 - 30 2 - 15 - 20 '*] - "___" ['(fn [f] (f 5)) - '(fn [f] (f 5))]} - - "conditionals" {"__" [:a - [] - nil - :glory - 4 6 :your-road - ''doom 0 - :cocked-pistol - :say-what?]} - - "higher_order_functions" {"__" [4 8 12 - '(* x x) - [false false true false false] - () - [:anything :goes :here] - '(< x 31) - '(* 10 x) '(< x 4) - 24 - 100 - '(count a) '(count b)]} - - "runtime_polymorphism" {"__" ['(str (:name a) " eats veggies.") - '(str (:name a) " eats animals.") - '(str "I don't know what " (:name a) " eats.") - "Hello World!" - "Hello, you silly world." - "Hello to this group: Peter, Paul, Mary!" ]} - - "lazy_sequences" {"__" [[1 2 3 4] - [0 1 2 3 4] - 10 - 95 - '(range 20) - :a] - "___" ['(fn [x] :foo)]} - - "sequence_comprehensions" {"__" [[0 1 2 3 4 5] - '(* index index) - '(range 10) - '(odd? index) '(* index index) - '[row column] - ]} - - "creating_functions" {"__" [true false true - 4 - :a :b :c :d - :c :d - 4 - 8] - "___" ['(complement nil?) - 'multiply-by-5 - '(comp dec square)]} - - "recursion" {"__" [true - 'acc - '(loop [coll coll - acc ()] - (if (seq coll) - (recur (rest coll) (conj acc (first coll))) - acc)) - '(loop [n n - acc 1] - (if (zero? n) - acc - (recur (dec n) (* acc n))))] - "___" ['not]} - - "destructuring" {"__" [":bar:foo" - '(format (str "First comes %s, " - "then comes %s, " - "then comes %s with the baby carriage") - a b c) - '(apply str - (interpose " " - (apply list - first-name - last-name - (interleave (repeat "aka") aliases)))) - '{:original-parts full-name - :named-parts {:first first-name :last last-name}} - '(str street-address ", " city ", " state) - 'city 'state - '(str street-address ", " city ", " state)] - "___" ['(fn [[fname lname] - {:keys [street-address city state]}] - (str fname " " lname ", " - street-address ", " city ", " state)) - ]} - "refs" {"__" ["hello" - "hello" - "better" - "better!!!" - '(dosync (ref-set the-world 0)) - '(map :jerry [@the-world @bizarro-world]) - ] - "___" ['(fn [x] (+ 20 x))]} - - "atoms" {"__" [0 - 1 - '(swap! atomic-clock (partial + 4)) - 20 - 20 - 'atomic-clock 20 :fin - ]} - - "macros" {"__" ['~(first form) - '~(nth form 2) - 'form - '(drop 2 form) - "Hello, Macros!" - 10 - ''(+ 9 1) - ''(* 10 2) - ''(+ 10 (2 * 3))]} - - "datatypes" {"__" ['(print - (str "You're really the " - (.category this) - ", " recipient "... sorry.")) - "peace" - "literature" - "physics" - nil - [true false] - (str "Congratulations on your Best Picture Oscar, " - "Evil Alien Conquerors!")]} - - "java_interop" {"__" ['java.lang.String - "SELECT * FROM" - 10 - 1024 - ] - "___" ['#(.toUpperCase %) - ] - } - - }) - -(defn replace-with [s k replacements] - (let [unreplaced-texts (split s (re-pattern (str "\\b" k "\\b")))] - (join - (butlast - (interleave - unreplaced-texts - (concat (map pr-str replacements) (repeat k))))))) - -(defn koan-text [koan] - (slurp (str "src/koans/" koan ".clj"))) - -(defn answers-for [koan sym] - ((answers koan {}) sym [])) - -(defn fill-in-answers [text koan sym] - (replace-with text sym (answers-for koan sym))) - -(defn print-non-failing-error [koan] - (println (str "\n" koan ".clj is passing without filling in the blanks"))) - -(defmacro ensure-failure [& forms] - (let [pairs (partition 2 forms) - tests (map (fn [[doc# code#]] - `(if (try - (fancy-assert ~code# ~doc#) - false - (catch AssertionError e# true) - (catch Exception e# true)) - :pass - (throw (AssertionError. (pr-str ~doc# ~code#))))) - pairs)] - `(do ~@tests))) - -(defn ensure-failing-without-answers [] - (if (every? - (fn [koan] - (let [form (koan-text koan) - form (string/replace form "(meditations" "(ensure-failure") - fake-err (java.io.PrintStream. (java.io.ByteArrayOutputStream.)) - real-err System/err - result (try - (load-string form) - true - (catch AssertionError e (prn e) false) - (catch Exception e (prn e) false))] - (if result - :pass - (print-non-failing-error koan)))) - ordered-koans) - (println "\nTests all fail before the answers are filled in."))) - -(defn ensure-passing-with-answers [] - (try - (dorun (map - (fn [koan] - (load-string - (-> (koan-text koan) - (fill-in-answers koan "__") - (fill-in-answers koan "___")))) - ordered-koans)) - (println "\nAll tests pass after the answers are filled in.") - - (catch Exception e - (println "\nAnswer sheet fail: " e) - (.printStackTrace e) - (println "Answer sheet fail")))) - -(defn run [] - (ensure-failing-without-answers) - (ensure-passing-with-answers)) - - diff --git a/src/path_to_enlightenment.clj b/src/path_to_enlightenment.clj deleted file mode 100644 index 4001658..0000000 --- a/src/path_to_enlightenment.clj +++ /dev/null @@ -1,49 +0,0 @@ -(ns path-to-enlightenment - (:use [runner.freshness :only [setup-freshener]]) - (:require [clojure.set] - [clojure.string])) - -(def __ :fill-in-the-blank) -(def ___ (fn [& args] __)) - -(defmacro fancy-assert - ([x] (fancy-assert x "")) - ([x message] - `(try - (assert ~x ~message) - (catch Exception e# - (throw (Exception. (str '~message "\n" '~x ) - e#)))))) - -(defmacro meditations [& forms] - (let [pairs (partition 2 forms) - tests (map (fn [[doc# code#]] - `(fancy-assert ~code# ~doc#)) - pairs)] - `(do ~@tests))) - -(defn require-version [[required-major required-minor]] - (let [{:keys [major minor]} *clojure-version*] - (if (or (< major required-major) - (and (== major required-major) (< minor required-minor))) - (throw (Exception. (str "Clojure version " required-major "." - required-minor " or higher required.")))))) - -(defn parse-required-version [] - (let [rdr (clojure.lang.LineNumberingPushbackReader. - (java.io.FileReader. (java.io.File. "project.clj"))) - project-form (read rdr) - version-string (->> project-form - (drop 3) - (apply hash-map) - :dependencies - (map (fn [xs] (vec (take 2 xs)))) - (into {}) - ('org.clojure/clojure))] - (map read-string - (take 3 (clojure.string/split version-string #"[\.\-]"))))) - -(defn run [] - (require-version (parse-required-version)) - (setup-freshener)) - diff --git a/src/runner/freshness.clj b/src/runner/freshness.clj deleted file mode 100644 index 33bf432..0000000 --- a/src/runner/freshness.clj +++ /dev/null @@ -1,34 +0,0 @@ -(ns runner.freshness - (:use [fresh.core :only [clj-files-in freshener]] - [clojure.java.io :only [file]] - [runner.koans :only [among-paths? - namaste - next-koan-path - ordered-koans - ordered-koan-paths - tests-pass?]]) - (:import [java.util.concurrent ScheduledThreadPoolExecutor TimeUnit])) - -(defn files-to-keep-fresh [] - (clj-files-in (file "src" "koans"))) - -(defn report-refresh [report] - (when-let [refreshed-files (seq (:reloaded report))] - (let [these-koans (filter - (among-paths? refreshed-files) - (ordered-koan-paths))] - (when (every? tests-pass? these-koans) - (if-let [next-koan-file (file (next-koan-path (last these-koans)))] - (report-refresh {:reloaded [next-koan-file]}) - (namaste)))) - (println)) - :refreshed) - -(def refresh! (freshener files-to-keep-fresh report-refresh)) - -(def scheduler (ScheduledThreadPoolExecutor. 1)) - -(defn setup-freshener [] - (println "Starting auto-runner...") - (.scheduleWithFixedDelay scheduler refresh! 0 500 TimeUnit/MILLISECONDS) - (.awaitTermination scheduler Long/MAX_VALUE TimeUnit/SECONDS)) diff --git a/src/runner/koans.clj b/src/runner/koans.clj deleted file mode 100644 index bc7c514..0000000 --- a/src/runner/koans.clj +++ /dev/null @@ -1,58 +0,0 @@ -(ns runner.koans - (:use [clojure.java.io :only [file]])) - -(def ordered-koans - ["equalities" - "lists" - "vectors" - "sets" - "maps" - "functions" - "conditionals" - "higher_order_functions" - "runtime_polymorphism" - "lazy_sequences" - "sequence_comprehensions" - "creating_functions" - "recursion" - "destructuring" - "refs" - "atoms" - "macros" - "datatypes" - "java_interop"]) - -(defn ordered-koan-paths [] - (map (fn [koan-name] - (.getCanonicalPath (file "src" "koans" (str koan-name ".clj")))) - ordered-koans)) - -(defn among-paths? [files] - (into #{} (map #(.getCanonicalPath %) files))) - -(defn next-koan-path [last-koan-path] - (loop [[this-koan & more :as koan-paths] (ordered-koan-paths)] - (when (seq more) - (if (= last-koan-path this-koan) - (first more) - (recur more))))) - -(defn tests-pass? [file-path] - (use '[path-to-enlightenment :only [meditations __ ___]]) - (try - (load-file file-path) - true - (catch Exception e - (println) - (println "Problem in" file-path) - (println "---------------------") - (println "Assertion failed!") - (let [actual-error (or (.getCause e) e) - message (or (.getMessage actual-error) - (.toString actual-error))] - (println (.replaceFirst message "^Assert failed: " ""))) - false))) - -(defn namaste [] - (println "\nYou have achieved clojure enlightenment. Namaste.")) -