Skip to content

Commit

Permalink
Tooling to generate new exercises
Browse files Browse the repository at this point in the history
  • Loading branch information
ingydotnet committed Aug 1, 2024
1 parent 293441d commit 0571a90
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 110 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
/bin/configlet.exe
/bin/shellcheck
/bin/ys*
/done/
/new/
/note/
/shellcheck*
/.clojure/
/out.txt
/.clojure/
/.problem/
40 changes: 29 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ CHECKS := \
check-shell \
check-exercism \
check-verify \
test-exercises \

GEN_FILES := \
$(EXERCISE_MAKEFILES) \
Expand All @@ -39,10 +38,15 @@ GEN_FILES := \

SHELL_FILES := \
$(BIN)/fetch-configlet \
$(BIN)/new-exercise \
$(BIN)/verify-exercises \

YAML_FILES := \
config.yaml \

PROBLEM_REPO := .problem
PROBLEM_REPO_URL := https://github.com/exercism/problem-specifications

CLOJURE_REPO := .clojure
CLOJURE_REPO_URL := https://github.com/exercism/clojure

Expand All @@ -56,7 +60,7 @@ SHELLCHECK_RELEASE := \

LINE := $(shell printf '%.0s-' {1..80})

exercise ?=
slug ?=
test ?=
v ?=

Expand All @@ -65,7 +69,7 @@ test-name := all exercises
override test := exercises/practice/*/.meta/test/*.ys
else
test-name := $(test)
override test := exercises/practice/$(test)/test/.meta/*.ys
override test := exercises/practice/$(test)/.meta/test/*.ys
endif

export YSPATH := $(shell IFS=:; p=$aexercises/practice/*/.meta$b; \
Expand All @@ -75,11 +79,15 @@ export YSPATH := $(shell IFS=:; p=$aexercises/practice/*/.meta$b; \
#------------------------------------------------------------------------------
default:

new: $(CFGLET) $(YS) $(CLOJURE_REPO)
ifndef exercise
$(error Please set the 'exercise' variable)
.PHONY: new
new: $(CFGLET) $(YS) $(PROBLEM_REPO) $(CLOJURE_REPO)
ifndef slug
$(error Please set the 'slug' variable)
endif
exercise=$(exercise) new-exercise
@exercise=$(slug) new-exercise

generate-all:
generate-all-exercises $(slug) $(slugs)

check: $(YS) $(CHECKS)
@echo $(LINE)
Expand All @@ -94,9 +102,12 @@ deps: $(YS) $(CFGLET) $(SHELLCHECK)
test-exercises: $(YS)
@echo $(LINE)
@echo '*** Running tests for $(test-name)'
prove $(if $v,-v ,)$(test)
@$(MAKE) --no-print-directory run-tests
@echo '*** All exercises test ok'

run-tests:
prove $(if $v,-v ,)$(test)

check-yaml: $(YS) $(YAML_FILES)
@echo $(LINE)
@echo '*** Test YAML files'
Expand All @@ -109,7 +120,7 @@ check-yaml: $(YS) $(YAML_FILES)
check-shell: $(SHELLCHECK)
@echo $(LINE)
@echo '*** Test shell script files'
@$< $(SHELL_FILES)
$< $(SHELL_FILES)
@echo '*** Shell script files are OK'
@echo

Expand All @@ -123,7 +134,7 @@ check-exercism: $(CFGLET) update
check-verify: update
@echo $(LINE)
@echo '*** Test all exercises are verified'
$(VERIFY) $(test)
$(VERIFY) $(slug)
@echo '*** All exercises are verified OK'
@echo

Expand All @@ -134,7 +145,11 @@ realclean: clean
$(RM) $(CFGLET)
$(RM) $(SHELLCHECK)
$(RM) $(BIN)/ys*
$(RM) -r $(CLOJURE_REPO)
$(RM) -r $(CLOJURE_REPO) $(PROBLEM_REPO)

reset:
git checkout -- config.*
git status -- exercises | grep exercises | xargs rm -fr

exercises/practice/%/Makefile: common/exercise.mk
cp -p $< $@
Expand All @@ -146,6 +161,9 @@ $(EXERCISE_META_TESTS):
%.json: %.yaml $(YS) Makefile
$(YS) -l $< | jq > $@

$(PROBLEM_REPO):
git clone $(PROBLEM_REPO_URL) $@

$(CLOJURE_REPO):
git clone $(CLOJURE_REPO_URL) $@

Expand Down
67 changes: 67 additions & 0 deletions bin/generate-all-exercises
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash

set -euo pipefail

P=.problem/exercises
E=exercises/practice

if [[ $# -gt 0 ]]; then
SLUGS=("$@")
else
for exercise in $P/*; do
SLUGS+=($(basename "$exercise"))
done
fi

i=0
for exercise in "${SLUGS[@]}"; do
slug=$(basename "$exercise")
CDJ=$P/$slug/canonical-data.json
branch=exercise-$slug
new=new/$branch

if [[ $slug == armstrong-numbers ]]; then
echo "SKIP - '$slug' triggers a ys bug"
continue
fi
if [[ -e $E/$slug ]]; then
echo "SKIP - directory '$E/$slug' already exists"
continue
fi
if git show-ref --quiet "refs/heads/$branch"; then
echo "SKIP - branch 'exercise-$slug' already exists"
continue
fi
if [[ -e $P/$slug/.deprecated ]]; then
echo "SKIP - exercise '$slug' is deprecated"
continue
fi
if [[ ! -e $CDJ ]]; then
echo "SKIP - '$CDJ' not found"
continue
fi
if [[ $(jq '.cases[0].property' "$CDJ") == null ]]; then
echo "SKIP - '$slug' has multiple property values"
continue
fi

make --no-print-directory new slug="$slug" || {
echo "FAILED - to generate new exercise '$slug'"
rm -fr "$E/$slug"
continue
}

git branch "$branch" HEAD
git worktree add -f "$new" "$branch"
cp config.* "$new"
mv "$E/$slug" "$new/$E/"
git -C "$new/$E" add .
git -C "$new/$E" commit -m "Implement exercise '$slug'"

make reset

echo "*** Generated new exercise '$slug'"

i=$((i++))
[[ $i -lt ${COUNT:-9999} ]] || break
done
21 changes: 21 additions & 0 deletions bin/generate-stub-program
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env ys-0

defn main(slug):
data =:
json/load:
slurp: ".problem/exercises/$slug/canonical-data.json"

case =: data.cases.0
func =: case.property.kebab-symbol()
args =:
join ' ': case.input.keys().mapv(kebab-symbol)

say: |-
!yamlscript/v0

defn ${func}($args):
# Implement the '$func' function.

defn kebab-symbol(v):
replace v /[A-Z]/:
fn [c]: +"-" + c.lower-case()
51 changes: 51 additions & 0 deletions bin/generate-test-file
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env ys-0

defn main(slug):
data =:
json/load:
slurp: ".problem/exercises/$slug/canonical-data.json"

tests =:
loop [[case & cases] data.cases, tests []]:
if cases.seq():
recur cases: tests.conj(gen-test(case))
=>: tests

test =: |
#!/usr/bin/env ys-0

require ys::taptest: :all

use: $slug
$gen-comments(data)
test::
$(join("\n" tests).trimr())

done: # $count(tests)

say: test

defn gen-test(case):
test =::
- name:: case.description.capitalize()
code:: gen-code(case)
want:: case.expected
uuid:: case.uuid
SKIP: true

yaml/dump: test

defn gen-code(case):
"$(case.property)($(join ' ' case.input.vals().mapv(pr-str)))"

defn gen-comments(data):
if data.comments:
then:
comments =:
data
.comments
.map(trim)
.join("\n")
.replace(/(?m)^/ '# ')
=>: "\n$comments\n"
else: ''
Loading

0 comments on commit 0571a90

Please sign in to comment.