Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some attempts #728

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ pom.xml.asc
**/*/*.ipynb_checkpoints
concepts/functions-generating-functions/introduction_files/
concepts/functions-generating-functions/*.html
.problem-specifications/
.cpcache/
23 changes: 23 additions & 0 deletions bin/generate-tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash

# Synopsis:
# Generate the tests for each exercise with a generator template.

# Example: generate tests for all exercises with a generator template
# bin/generate-tests

# Example: generate tests for a single exercise with a generator template
# bin/generate-tests two-fer

set -eo pipefail

die() { echo "$*" >&2; exit 1; }

required_tool() {
command -v "${1}" >/dev/null 2>&1 ||
die "${1} is required but not installed. Please install it and make sure it's in your PATH."
}

required_tool clj

clj -X generator/run :exercise leap
8 changes: 8 additions & 0 deletions exercises/practice/isogram/.meta/generator.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
(ns isogram-test
(:require [clojure.test :refer [deftest testing is]]
isogram))
{% for test_case in test_cases %}
(deftest isogram_test_{{forloop.counter}}
(testing "{{test_case.path|join:" - "}}")
{% if test_case.expected %}(is (isogram/isogram? "{{test_case.input.phrase}}"))) {% else %}(is (not (isogram/isogram? "{{test_case.input.phrase}}")))) {% endif %}
{% endfor %}
5 changes: 5 additions & 0 deletions generators/deps.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{:paths ["src"]
:deps {org.clojure/data.json {:mvn/version "2.5.1"}
selmer/selmer {:mvn/version "1.12.61"}
io.github.tonsky/toml-clj {:mvn/version "0.1.0"}
clj-jgit/clj-jgit {:mvn/version "1.1.0"}}}
65 changes: 65 additions & 0 deletions generators/src/canonical_data.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
(ns canonical-data
(:require [clojure.data.json :as json]
[clojure.java.io :as io]
[toml-clj.core :as toml]
[clj-jgit.porcelain :refer [git-clone git-pull load-repo]]
[log]
[paths]))

(def git-url "https://github.com/exercism/problem-specifications.git")

(defn- pull-repo []
(-> paths/prob-specs-dir
(load-repo)
(git-pull)))

(defn- clone-repo [] (git-clone git-url :branch "main" :dir paths/prob-specs-dir))

(defn sync-repo []
(try
(pull-repo)
(catch java.io.FileNotFoundException _ (clone-repo))))

(defn- canonical-data [slug]
(let [file (paths/canonical-data-file slug)]
(if (.exists file)
(json/read (io/reader file) :key-fn keyword)
(log/error (str "No canonical-data.json found for exercise '" slug "'")))))

(defn- excluded-uuids [slug]
(let [file (paths/tests-toml-file slug)]
(if (.exists file)
(->> file
(io/reader)
(toml/read)
(filter #(= false (get (last %) "include")))
(map first)
(set))
(log/error (str "No tests.toml data found for exercise '" slug "'")))))

(defn- excluded? [slug]
(let [excluded (excluded-uuids slug)]
(fn [node] (contains? excluded (:uuid node)))))

(defn- node->test-case [node path]
(-> node
(assoc :path path :error (get-in node [:expected :error]))
(dissoc :reimplements :comments :scenarios)))

(defn- test-case-nodes
([node] (test-case-nodes node []))
([node path]
(let [description (:description node)
children (:cases node)
updated-path (if description (conj path description) path)]
(if children
(mapcat #(test-case-nodes % updated-path) children)
[(node->test-case node updated-path)]))))

(defn test-cases [slug]
(->> slug
(canonical-data)
(test-case-nodes)
(remove (excluded? slug))
(into [])))

19 changes: 19 additions & 0 deletions generators/src/generator.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
(ns generator
(:require [clojure.string :as str]
[canonical-data]
[templates]
[log]))

(defn- slugs-to-generate [slug]
(let [slugs templates/exercises-with-template]
(if (str/blank? slug)
slugs
(if (contains? slugs slug)
[slug]
(log/error (str "No template found for exercise '" slug "'"))))))

(defn- run [{:keys [exercise]}]
(canonical-data/sync-repo)
(doseq [slug (slugs-to-generate (str exercise))]
(log/normal (str "Generating tests for exercise '" slug "'"))
(templates/generate-tests-file slug (canonical-data/test-cases slug))))
8 changes: 8 additions & 0 deletions generators/src/log.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
(ns log)

(defn normal [message]
(println message))

(defn error [message]
(println message)
(System/exit 1))
14 changes: 14 additions & 0 deletions generators/src/paths.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
(ns paths
(:require [clojure.java.io :as io]
[clojure.string :as str]))

(def generators-dir (.getCanonicalPath (io/file ".")))
(def root-dir (.getCanonicalPath (io/file generators-dir "..")))
(def prob-specs-dir (io/file generators-dir ".problem-specifications"))
(def exercises-dir (io/file root-dir "exercises" "practice"))
(defn exercise-dir [slug] (io/file exercises-dir slug))
(defn canonical-data-file [slug] (io/file prob-specs-dir "exercises" slug "canonical-data.json"))
(defn tests-toml-file [slug] (io/file (exercise-dir slug) ".meta" "tests.toml"))
(defn generator-template-file [slug] (io/file (exercise-dir slug) ".meta" "generator.template"))
(defn tests-file-name [slug] (str (str/replace slug "-" "_") "_test.clj"))
(defn tests-file [slug] (io/file (exercise-dir slug) "test" (tests-file-name slug)))
24 changes: 24 additions & 0 deletions generators/src/templates.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(ns templates
(:require [selmer.parser :as selmer]
[log]
[paths]))

(def exercises-with-template
(->> paths/exercises-dir
(file-seq)
(filter #(.isFile %))
(filter #(= "generator.template" (.getName %)))
(map #(-> % (.getParentFile) (.getParentFile) (.getName)))
(set)))

(defn- render-template [data template]
(selmer/render (slurp template) data))

(defn- render [slug test-cases]
(let [data {:slug slug :test_cases test-cases}]
(render-template data (paths/generator-template-file slug))))

(defn generate-tests-file [slug test-cases]
(->> test-cases
(render slug)
(spit (paths/tests-file slug))))
9 changes: 0 additions & 9 deletions project.clj

This file was deleted.