Extracted koan logic out into koan-engine.

This commit is contained in:
Sam Ritchie 2011-11-27 14:14:02 -05:00 committed by Colin Jones
parent e2b5f87984
commit 337d39684a
12 changed files with 214 additions and 433 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ lib
releases releases
*.jar *.jar
.DS_Store .DS_Store
.lein-deps-sum

View File

@ -1,6 +1,6 @@
(defproject functional-koans "0.4.5" (defproject functional-koans "0.4.5"
:description "The functional koans" :description "The functional koans."
:dependencies [[org.clojure/clojure "1.3.0"] :dependencies [[org.clojure/clojure "1.3.0"]
[fresh "1.0.2"] [koan-engine "0.1.0"]]
[jline "0.9.94" :exclusions [junit]]] :dev-dependencies [[swank-clojure "1.3.0" :exclusions [org.clojure/clojure]]
:dev-dependencies [[swank-clojure "1.3.0" :exclusions [org.clojure/clojure]]]) [lein-koan "0.1.0"]])

199
resources/koans.clj Normal file
View File

@ -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 %)
]
}]]

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
CLASSPATH=src CLASSPATH=src
for f in lib/*.jar; do for f in lib/*.jar lib/dev/*.jar resources/; do
CLASSPATH=$CLASSPATH:$f CLASSPATH=$CLASSPATH:$f
done done

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
CLASSPATH=src CLASSPATH=src
for f in lib/*.jar; do for f in lib/*.jar lib/dev/*.jar resources/; do
CLASSPATH=$CLASSPATH:$f CLASSPATH=$CLASSPATH:$f
done done

View File

@ -1,4 +1,4 @@
(load "path_to_enlightenment") (load "koan_engine/runner")
(do (do
(in-ns 'path-to-enlightenment) (in-ns 'koan-engine.runner)
(run)) (exec "run"))

View File

@ -1,7 +1,8 @@
#!/bin/sh #!/bin/sh
CLASSPATH=src CLASSPATH=src
for f in lib/*.jar; do
for f in lib/*.jar lib/dev/*.jar resources/; do
CLASSPATH=$CLASSPATH:$f CLASSPATH=$CLASSPATH:$f
done done

View File

@ -1,4 +1,4 @@
(load "path_to_answer_sheet") (load "koan_engine/runner")
(do (do
(in-ns 'path-to-answer-sheet) (in-ns 'koan-engine.runner)
(run)) (exec "test"))

View File

@ -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))

View File

@ -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))

View File

@ -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))

View File

@ -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."))